File indexing completed on 2024-07-21 03:40:59

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "batchrenamejob.h"
0009 
0010 #include "copyjob.h"
0011 #include "job_p.h"
0012 
0013 #include <QMimeDatabase>
0014 #include <QTimer>
0015 
0016 #include <KLocalizedString>
0017 
0018 #include <set>
0019 
0020 using namespace KIO;
0021 
0022 class KIO::BatchRenameJobPrivate : public KIO::JobPrivate
0023 {
0024 public:
0025     BatchRenameJobPrivate(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags)
0026         : JobPrivate()
0027         , m_srcList(src)
0028         , m_newName(newName)
0029         , m_index(index)
0030         , m_placeHolder(placeHolder)
0031         , m_listIterator(m_srcList.constBegin())
0032         , m_allExtensionsDifferent(true)
0033         , m_useIndex(true)
0034         , m_appendIndex(false)
0035         , m_flags(flags)
0036     {
0037         // There occur four cases when renaming multiple files,
0038         // 1. All files have different extension and $newName contains a valid placeholder.
0039         // 2. At least two files have same extension and $newName contains a valid placeholder.
0040         // In these two cases the placeholder character will be replaced by an integer($index).
0041         // 3. All files have different extension and new name contains an invalid placeholder
0042         //    (this means either $newName doesn't contain the placeholder or the placeholders
0043         //     are not in a connected sequence).
0044         // In this case nothing is substituted and all files have the same $newName.
0045         // 4. At least two files have same extension and $newName contains an invalid placeholder.
0046         // In this case $index is appended to $newName.
0047 
0048         // Check for extensions.
0049         std::set<QString> extensions;
0050         QMimeDatabase db;
0051         for (const QUrl &url : std::as_const(m_srcList)) {
0052             const QString extension = db.suffixForFileName(url.path());
0053             const auto [it, isInserted] = extensions.insert(extension);
0054             if (!isInserted) {
0055                 m_allExtensionsDifferent = false;
0056                 break;
0057             }
0058         }
0059 
0060         // Check for exactly one placeholder character or exactly one sequence of placeholders.
0061         int pos = newName.indexOf(placeHolder);
0062         if (pos != -1) {
0063             while (pos < newName.size() && newName.at(pos) == placeHolder) {
0064                 pos++;
0065             }
0066         }
0067         const bool validPlaceholder = (newName.indexOf(placeHolder, pos) == -1);
0068 
0069         if (!validPlaceholder) {
0070             if (!m_allExtensionsDifferent) {
0071                 m_appendIndex = true;
0072             } else {
0073                 m_useIndex = false;
0074             }
0075         }
0076     }
0077 
0078     QList<QUrl> m_srcList;
0079     QString m_newName;
0080     int m_index;
0081     QChar m_placeHolder;
0082     QList<QUrl>::const_iterator m_listIterator;
0083     bool m_allExtensionsDifferent;
0084     bool m_useIndex;
0085     bool m_appendIndex;
0086     QUrl m_oldUrl;
0087     QUrl m_newUrl; // for fileRenamed signal
0088     const JobFlags m_flags;
0089     QTimer m_reportTimer;
0090 
0091     Q_DECLARE_PUBLIC(BatchRenameJob)
0092 
0093     void slotStart();
0094     void slotReport();
0095 
0096     QString indexedName(const QString &name, int index, QChar placeHolder) const;
0097 
0098     static inline BatchRenameJob *newJob(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, JobFlags flags)
0099     {
0100         BatchRenameJob *job = new BatchRenameJob(*new BatchRenameJobPrivate(src, newName, index, placeHolder, flags));
0101         job->setUiDelegate(KIO::createDefaultJobUiDelegate());
0102         if (!(flags & HideProgressInfo)) {
0103             KIO::getJobTracker()->registerJob(job);
0104         }
0105         if (!(flags & NoPrivilegeExecution)) {
0106             job->d_func()->m_privilegeExecutionEnabled = true;
0107             job->d_func()->m_operationType = Rename;
0108         }
0109         return job;
0110     }
0111 };
0112 
0113 BatchRenameJob::BatchRenameJob(BatchRenameJobPrivate &dd)
0114     : Job(dd)
0115 {
0116     Q_D(BatchRenameJob);
0117     connect(&d->m_reportTimer, &QTimer::timeout, this, [this]() {
0118         d_func()->slotReport();
0119     });
0120     d->m_reportTimer.start(200);
0121 
0122     QTimer::singleShot(0, this, [this] {
0123         d_func()->slotStart();
0124     });
0125 }
0126 
0127 BatchRenameJob::~BatchRenameJob()
0128 {
0129 }
0130 
0131 QString BatchRenameJobPrivate::indexedName(const QString &name, int index, QChar placeHolder) const
0132 {
0133     if (!m_useIndex) {
0134         return name;
0135     }
0136 
0137     QString newName = name;
0138     QString indexString = QString::number(index);
0139 
0140     if (m_appendIndex) {
0141         newName.append(indexString);
0142         return newName;
0143     }
0144 
0145     // Insert leading zeros if necessary
0146     const int minIndexLength = name.count(placeHolder);
0147     indexString.prepend(QString(minIndexLength - indexString.length(), QLatin1Char('0')));
0148 
0149     // Replace the index placeholders by the indexString
0150     const int placeHolderStart = newName.indexOf(placeHolder);
0151     newName.replace(placeHolderStart, minIndexLength, indexString);
0152 
0153     return newName;
0154 }
0155 
0156 void BatchRenameJobPrivate::slotStart()
0157 {
0158     Q_Q(BatchRenameJob);
0159 
0160     if (m_listIterator == m_srcList.constBegin()) { //  emit total
0161         q->setTotalAmount(KJob::Items, m_srcList.count());
0162     }
0163 
0164     if (m_listIterator != m_srcList.constEnd()) {
0165         QString newName = indexedName(m_newName, m_index, m_placeHolder);
0166         const QUrl oldUrl = *m_listIterator;
0167         QMimeDatabase db;
0168         const QString extension = db.suffixForFileName(oldUrl.path());
0169         if (!extension.isEmpty()) {
0170             newName += QLatin1Char('.') + extension;
0171         }
0172 
0173         m_oldUrl = oldUrl;
0174         m_newUrl = oldUrl.adjusted(QUrl::RemoveFilename);
0175         m_newUrl.setPath(m_newUrl.path() + KIO::encodeFileName(newName));
0176 
0177         KIO::Job *job = KIO::moveAs(oldUrl, m_newUrl, KIO::HideProgressInfo);
0178         job->setParentJob(q);
0179         q->addSubjob(job);
0180     } else {
0181         m_reportTimer.stop();
0182         slotReport();
0183         q->emitResult();
0184     }
0185 }
0186 
0187 void BatchRenameJobPrivate::slotReport()
0188 {
0189     Q_Q(BatchRenameJob);
0190 
0191     const auto processed = m_listIterator - m_srcList.constBegin();
0192 
0193     q->setProcessedAmount(KJob::Items, processed);
0194     q->emitPercent(processed, m_srcList.count());
0195 
0196     emitRenaming(q, m_oldUrl, m_newUrl);
0197 }
0198 
0199 void BatchRenameJob::slotResult(KJob *job)
0200 {
0201     Q_D(BatchRenameJob);
0202     if (job->error()) {
0203         d->m_reportTimer.stop();
0204         d->slotReport();
0205         KIO::Job::slotResult(job);
0206         return;
0207     }
0208 
0209     removeSubjob(job);
0210 
0211     Q_EMIT fileRenamed(*d->m_listIterator, d->m_newUrl);
0212     ++d->m_listIterator;
0213     ++d->m_index;
0214     d->slotStart();
0215 }
0216 
0217 BatchRenameJob *KIO::batchRename(const QList<QUrl> &src, const QString &newName, int index, QChar placeHolder, KIO::JobFlags flags)
0218 {
0219     return BatchRenameJobPrivate::newJob(src, newName, index, placeHolder, flags);
0220 }
0221 
0222 #include "moc_batchrenamejob.cpp"