1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "batchrenamejob.h"
9
10#include "copyjob.h"
11#include "job_p.h"
12
13#include <QMimeDatabase>
14#include <QTimer>
15
16#include <KLocalizedString>
17
18#include <set>
19
20using namespace KIO;
21
22class KIO::BatchRenameJobPrivate : public KIO::JobPrivate
23{
24public:
25 BatchRenameJobPrivate(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags)
26 : JobPrivate()
27 , m_srcList(src)
28 , m_newName(newName)
29 , m_index(index)
30 , m_placeHolder(placeHolder)
31 , m_listIterator(m_srcList.constBegin())
32 , m_allExtensionsDifferent(true)
33 , m_useIndex(true)
34 , m_appendIndex(false)
35 , m_flags(flags)
36 {
37 // There occur four cases when renaming multiple files,
38 // 1. All files have different extension and $newName contains a valid placeholder.
39 // 2. At least two files have same extension and $newName contains a valid placeholder.
40 // In these two cases the placeholder character will be replaced by an integer($index).
41 // 3. All files have different extension and new name contains an invalid placeholder
42 // (this means either $newName doesn't contain the placeholder or the placeholders
43 // are not in a connected sequence).
44 // In this case nothing is substituted and all files have the same $newName.
45 // 4. At least two files have same extension and $newName contains an invalid placeholder.
46 // In this case $index is appended to $newName.
47
48 // Check for extensions.
49 std::set<QString> extensions;
50 QMimeDatabase db;
51 for (const QUrl &url : std::as_const(t&: m_srcList)) {
52 const QString extension = db.suffixForFileName(fileName: url.path());
53 const auto [it, isInserted] = extensions.insert(x: extension);
54 if (!isInserted) {
55 m_allExtensionsDifferent = false;
56 break;
57 }
58 }
59
60 // Check for exactly one placeholder character or exactly one sequence of placeholders.
61 int pos = newName.indexOf(c: placeHolder);
62 if (pos != -1) {
63 while (pos < newName.size() && newName.at(i: pos) == placeHolder) {
64 pos++;
65 }
66 }
67 const bool validPlaceholder = (newName.indexOf(c: placeHolder, from: pos) == -1);
68
69 if (!validPlaceholder) {
70 if (!m_allExtensionsDifferent) {
71 m_appendIndex = true;
72 } else {
73 m_useIndex = false;
74 }
75 }
76 }
77
78 QList<QUrl> m_srcList;
79 QString m_newName;
80 int m_index;
81 QChar m_placeHolder;
82 QList<QUrl>::const_iterator m_listIterator;
83 bool m_allExtensionsDifferent;
84 bool m_useIndex;
85 bool m_appendIndex;
86 QUrl m_oldUrl;
87 QUrl m_newUrl; // for fileRenamed signal
88 const JobFlags m_flags;
89 QTimer m_reportTimer;
90
91 Q_DECLARE_PUBLIC(BatchRenameJob)
92
93 void slotStart();
94 void slotReport();
95
96 QString indexedName(const QString &name, int index, QChar placeHolder) const;
97
98 static inline BatchRenameJob *newJob(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags)
99 {
100 BatchRenameJob *job = new BatchRenameJob(*new BatchRenameJobPrivate(src, newName, index, placeHolder, flags));
101 job->setUiDelegate(KIO::createDefaultJobUiDelegate());
102 if (!(flags & HideProgressInfo)) {
103 KIO::getJobTracker()->registerJob(job);
104 }
105 if (!(flags & NoPrivilegeExecution)) {
106 job->d_func()->m_privilegeExecutionEnabled = true;
107 job->d_func()->m_operationType = Rename;
108 }
109 return job;
110 }
111};
112
113BatchRenameJob::BatchRenameJob(BatchRenameJobPrivate &dd)
114 : Job(dd)
115{
116 Q_D(BatchRenameJob);
117 connect(sender: &d->m_reportTimer, signal: &QTimer::timeout, context: this, slot: [this]() {
118 d_func()->slotReport();
119 });
120 d->m_reportTimer.start(msec: 200);
121
122 QTimer::singleShot(interval: 0, receiver: this, slot: [this] {
123 d_func()->slotStart();
124 });
125}
126
127BatchRenameJob::~BatchRenameJob()
128{
129}
130
131QString BatchRenameJobPrivate::indexedName(const QString &name, int index, QChar placeHolder) const
132{
133 if (!m_useIndex) {
134 return name;
135 }
136
137 QString newName = name;
138 QString indexString = QString::number(index);
139
140 if (m_appendIndex) {
141 newName.append(s: indexString);
142 return newName;
143 }
144
145 // Insert leading zeros if necessary
146 const int minIndexLength = name.count(c: placeHolder);
147 indexString.prepend(s: QString(minIndexLength - indexString.length(), QLatin1Char('0')));
148
149 // Replace the index placeholders by the indexString
150 const int placeHolderStart = newName.indexOf(c: placeHolder);
151 newName.replace(i: placeHolderStart, len: minIndexLength, after: indexString);
152
153 return newName;
154}
155
156void BatchRenameJobPrivate::slotStart()
157{
158 Q_Q(BatchRenameJob);
159
160 if (m_listIterator == m_srcList.constBegin()) { // emit total
161 q->setTotalAmount(unit: KJob::Items, amount: m_srcList.count());
162 }
163
164 if (m_listIterator != m_srcList.constEnd()) {
165 QString newName = indexedName(name: m_newName, index: m_index, placeHolder: m_placeHolder);
166 const QUrl oldUrl = *m_listIterator;
167 QMimeDatabase db;
168 const QString extension = db.suffixForFileName(fileName: oldUrl.path());
169 if (!extension.isEmpty()) {
170 newName += QLatin1Char('.') + extension;
171 }
172
173 m_oldUrl = oldUrl;
174 m_newUrl = oldUrl.adjusted(options: QUrl::RemoveFilename);
175 m_newUrl.setPath(path: m_newUrl.path() + KIO::encodeFileName(str: newName));
176
177 KIO::Job *job = KIO::moveAs(src: oldUrl, dest: m_newUrl, flags: KIO::HideProgressInfo);
178 job->setParentJob(q);
179 q->addSubjob(job);
180 } else {
181 m_reportTimer.stop();
182 slotReport();
183 q->emitResult();
184 }
185}
186
187void BatchRenameJobPrivate::slotReport()
188{
189 Q_Q(BatchRenameJob);
190
191 const auto processed = m_listIterator - m_srcList.constBegin();
192
193 q->setProcessedAmount(unit: KJob::Items, amount: processed);
194 q->emitPercent(processedAmount: processed, totalAmount: m_srcList.count());
195
196 emitRenaming(q, src: m_oldUrl, dest: m_newUrl);
197}
198
199void BatchRenameJob::slotResult(KJob *job)
200{
201 Q_D(BatchRenameJob);
202 if (job->error()) {
203 d->m_reportTimer.stop();
204 d->slotReport();
205 KIO::Job::slotResult(job);
206 return;
207 }
208
209 removeSubjob(job);
210
211 Q_EMIT fileRenamed(oldUrl: *d->m_listIterator, newUrl: d->m_newUrl);
212 ++d->m_listIterator;
213 ++d->m_index;
214 d->slotStart();
215}
216
217BatchRenameJob *KIO::batchRename(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, KIO::JobFlags flags)
218{
219 return BatchRenameJobPrivate::newJob(src, newName, index, placeHolder, flags);
220}
221
222#include "moc_batchrenamejob.cpp"
223

source code of kio/src/core/batchrenamejob.cpp