diff --git a/src/core/deletejob.cpp b/src/core/deletejob.cpp --- a/src/core/deletejob.cpp +++ b/src/core/deletejob.cpp @@ -39,9 +39,13 @@ #include #include #include +#include +#include #include "job_p.h" +Q_DECLARE_METATYPE(QList::iterator) + extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol. static bool isHttpProtocol(const QString &protocol) @@ -58,13 +62,33 @@ DELETEJOB_STATE_DELETING_DIRS }; -/* -static const char* const s_states[] = { - "DELETEJOB_STATE_STATING", - "DELETEJOB_STATE_DELETING_FILES", - "DELETEJOB_STATE_DELETING_DIRS" +class DeleteJobIOWorkerPrivate : public QObject { + Q_OBJECT + +public: + +Q_SIGNALS: + void rmfileResult(bool succeeded, const QList::iterator it); + void rmddirResult(bool succeeded, const QList::iterator it); + +public Q_SLOTS: + + /** + * Deletes the file @p points to + * The file must be a LocalFile + */ + void rmfile(const QList::iterator it){ + emit rmfileResult(QFile::remove((*it).toLocalFile()), it); + } + + /** + * Deletes the directory @p points to + * The directory must be a LocalFile + */ + void rmdir(const QList::iterator it) { + emit rmddirResult(QDir().rmdir((*it).toLocalFile()), it); + } }; -*/ class DeleteJobPrivate: public KIO::JobPrivate { @@ -77,6 +101,7 @@ , m_srcList(src) , m_currentStat(m_srcList.begin()) , m_reportTimer(nullptr) + , m_ioworker(nullptr) { } DeleteJobState state; @@ -91,6 +116,8 @@ QList::iterator m_currentStat; QSet m_parentDirs; QTimer *m_reportTimer; + DeleteJobIOWorkerPrivate *m_ioworker; + QThread m_thread; void statNextSrc(); void currentSourceStated(bool isDir, bool isLink); @@ -102,6 +129,15 @@ void slotStart(); void slotEntries(KIO::Job *, const KIO::UDSEntryList &list); + /// Callback of worker rmfile + void rmFileResult(bool result, const QList::iterator it); + /// Callback of worker rmdir + void rmdirResult(bool result, const QList::iterator it); + SimpleJob* deleteFileUsingJob (const QList::iterator it); + void deleteDirUsingJob (const QList::iterator it); + + ~DeleteJobPrivate(); + Q_DECLARE_PUBLIC(DeleteJob) static inline DeleteJob *newJob(const QList &src, JobFlags flags) @@ -138,16 +174,35 @@ { } +DeleteJobPrivate::~DeleteJobPrivate() +{ + m_thread.quit(); + m_thread.wait(); +} + QList DeleteJob::urls() const { return d_func()->m_srcList; } void DeleteJobPrivate::slotStart() { + qRegisterMetaType::iterator>("QList::iterator"); + + m_ioworker = new DeleteJobIOWorkerPrivate; + m_ioworker->moveToThread(&m_thread); + m_thread.connect(&m_thread, &QThread::finished, m_ioworker, &QObject::deleteLater); + m_ioworker->connect(m_ioworker, &DeleteJobIOWorkerPrivate::rmddirResult, [=](bool result, const QList::iterator it){ + this->rmdirResult(result, it); + }); + m_ioworker->connect(m_ioworker, &DeleteJobIOWorkerPrivate::rmfileResult, [=](bool result, const QList::iterator it){ + this->rmFileResult(result, it); + }); + + m_thread.start(); + statNextSrc(); } - void DeleteJobPrivate::slotReport() { Q_Q(DeleteJob); @@ -279,85 +334,134 @@ deleteNextFile(); } + +void DeleteJobPrivate::rmFileResult(bool result, const QList::iterator it) +{ + Q_Q(DeleteJob); + + SimpleJob *job = nullptr; + if (result) { + m_processedFiles++; + } else { + // fallback if unlink() failed (we'll use the job's error handling in that case) + job = deleteFileUsingJob(it); + } + + if (it == files.begin()) { // not a link + files.erase(it); + } else { + symlinks.erase(it); + } + if (job) { + q->addSubjob(job); + } else { + deleteNextFile(); + } +} + +SimpleJob* DeleteJobPrivate::deleteFileUsingJob (const QList::iterator it) +{ + Q_Q(DeleteJob); + + SimpleJob *job; + if (isHttpProtocol((*it).scheme())) { + job = KIO::http_delete((*it), KIO::HideProgressInfo); + } else { + job = KIO::file_delete((*it), KIO::HideProgressInfo); + job->setParentJob(q); + } + Scheduler::setJobPriority(job, 1); + return job; +} + void DeleteJobPrivate::deleteNextFile() { Q_Q(DeleteJob); //qDebug(); + + // if there is something else to delete + // the loop is run using callbacks slotResult and rmFileResult if (!files.isEmpty() || !symlinks.isEmpty()) { - SimpleJob *job; - do { - // Take first file to delete out of list - QList::iterator it = files.begin(); - bool isLink = false; - if (it == files.end()) { // No more files - it = symlinks.begin(); // Pick up a symlink to delete - isLink = true; - } - // Normal deletion - // If local file, try do it directly - if ((*it).isLocalFile() && QFile::remove((*it).toLocalFile())) { - //kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile(); - job = nullptr; - m_processedFiles++; - if (m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files - m_currentURL = *it; - slotReport(); - } - } else { - // if remote - or if unlink() failed (we'll use the job's error handling in that case) - //qDebug() << "calling file_delete on" << *it; - if (isHttpProtocol(it->scheme())) { - job = KIO::http_delete(*it, KIO::HideProgressInfo); - } else { - job = KIO::file_delete(*it, KIO::HideProgressInfo); - job->setParentJob(q); - } - Scheduler::setJobPriority(job, 1); - m_currentURL = (*it); - } + // Take first file to delete out of list + QList::iterator it = files.begin(); + bool isLink = (it == files.end()); // No more files + if (isLink) { + it = symlinks.begin(); // Pick up a symlink to delete + } + m_currentURL = (*it); + // Normal deletion + // If local file, try do it directly + if ((*it).isLocalFile()){ + // separate thread will do the work + //m_ioworker->rmfile((*it).toLocalFile()); + QMetaObject::invokeMethod(m_ioworker, "rmfile", Qt::QueuedConnection, + Q_ARG(const QList::iterator, it)); + return; + } else { + // if remote + SimpleJob *job = deleteFileUsingJob(it); + if (isLink) { symlinks.erase(it); } else { files.erase(it); } - if (job) { - q->addSubjob(job); - return; - } - // loop only if direct deletion worked (job=0) and there is something else to delete - } while (!job && (!files.isEmpty() || !symlinks.isEmpty())); + + q->addSubjob(job); + return; + } } + state = DELETEJOB_STATE_DELETING_DIRS; deleteNextDir(); } +void DeleteJobPrivate::rmdirResult(bool result, const QList::iterator it) +{ + if (result) { + m_processedDirs++; + dirs.erase(it); + deleteNextDir(); + } else { + // fallback + deleteDirUsingJob(it); + } +} + +void DeleteJobPrivate::deleteDirUsingJob (const QList::iterator it) +{ + Q_Q(DeleteJob); + + // Call rmdir - works for kioslaves with canDeleteRecursive too, + // CMD_DEL will trigger the recursive deletion in the slave. + SimpleJob *job = KIO::rmdir(*it); + job->setParentJob(q); + job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true")); + Scheduler::setJobPriority(job, 1); + dirs.erase(it); + q->addSubjob(job); +} + void DeleteJobPrivate::deleteNextDir() { Q_Q(DeleteJob); + if (!dirs.isEmpty()) { // some dirs to delete ? - do { - // Take first dir to delete out of list - last ones first ! - QList::iterator it = --dirs.end(); - // If local dir, try to rmdir it directly - if ((*it).isLocalFile() && QDir().rmdir((*it).toLocalFile())) { - m_processedDirs++; - if (m_processedDirs % 100 == 1) { // update progress info every 100 dirs - m_currentURL = *it; - slotReport(); - } - } else { - // Call rmdir - works for kioslaves with canDeleteRecursive too, - // CMD_DEL will trigger the recursive deletion in the slave. - SimpleJob *job = KIO::rmdir(*it); - job->setParentJob(q); - job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true")); - Scheduler::setJobPriority(job, 1); - dirs.erase(it); - q->addSubjob(job); - return; - } - dirs.erase(it); - } while (!dirs.isEmpty()); + + // the loop is run using callbacks slotResult and rmdirResult + // Take first dir to delete out of list - last ones first ! + QList::iterator it = --dirs.end(); + m_currentURL = (*it); + // If local dir, try to rmdir it directly + if ((*it).isLocalFile()) { + // delete it on separate worker thread + QMetaObject::invokeMethod(m_ioworker, "rmdir", Qt::QueuedConnection, + Q_ARG(const QList::iterator, it)); + return; + } else { + deleteDirUsingJob(it); + return; + } } // Re-enable watching on the dirs that held the deleted files @@ -511,4 +615,5 @@ return job; } +#include "deletejob.moc" #include "moc_deletejob.cpp"