File indexing completed on 2024-04-21 16:32:40

0001 /*
0002     SPDX-FileCopyrightText: 2009 Csaba Karai <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2009-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "abstractthreadedjob.h"
0009 
0010 // QtCore
0011 #include <QDir>
0012 #include <QEventLoop>
0013 #include <QPointer>
0014 #include <QTemporaryDir>
0015 #include <QTemporaryFile>
0016 #include <QTimer>
0017 // QtWidgets
0018 #include <QApplication>
0019 
0020 #include <KI18n/KLocalizedString>
0021 #include <KIO/JobUiDelegate>
0022 #include <KWidgetsAddons/KMessageBox>
0023 
0024 #include "../FileSystem/filesystemprovider.h"
0025 #include "../krglobal.h"
0026 #include "../krservices.h"
0027 #include "krarchandler.h"
0028 
0029 AbstractThreadedJob::AbstractThreadedJob()
0030     : _maxProgressValue(0)
0031     , _currentProgress(0)
0032     , _exiting(false)
0033     , _jobThread(nullptr)
0034 {
0035 }
0036 
0037 void AbstractThreadedJob::startAbstractJobThread(AbstractJobThread *jobThread)
0038 {
0039     _jobThread = jobThread;
0040     _jobThread->setJob(this);
0041     _jobThread->moveToThread(_jobThread);
0042     _jobThread->start();
0043 }
0044 
0045 AbstractThreadedJob::~AbstractThreadedJob()
0046 {
0047     _exiting = true;
0048     if (_jobThread) {
0049         _jobThread->abort();
0050 
0051         _locker.lock();
0052         _waiter.wakeAll();
0053         _locker.unlock();
0054 
0055         _jobThread->wait();
0056         delete _jobThread;
0057     }
0058 }
0059 
0060 bool AbstractThreadedJob::event(QEvent *e)
0061 {
0062     if (e->type() == QEvent::User) {
0063         auto *event = dynamic_cast<UserEvent *>(e);
0064         switch (event->command()) {
0065         case CMD_SUCCESS: {
0066             emitResult();
0067         } break;
0068         case CMD_ERROR: {
0069             auto error = event->args()[0].value<int>();
0070             QString errorText = event->args()[1].value<QString>();
0071 
0072             setError(error);
0073             setErrorText(errorText);
0074             emitResult();
0075         } break;
0076         case CMD_INFO: {
0077             QString info = event->args()[0].value<QString>();
0078             QString arg1 = event->args()[1].value<QString>();
0079             QString arg2 = event->args()[2].value<QString>();
0080             QString arg3 = event->args()[3].value<QString>();
0081             QString arg4 = event->args()[4].value<QString>();
0082 
0083             _title = info;
0084             emit description(this, info, qMakePair(arg1, arg2), qMakePair(arg3, arg4));
0085 
0086         } break;
0087         case CMD_RESET: {
0088             QString info = event->args()[0].value<QString>();
0089             QString arg1 = event->args()[1].value<QString>();
0090             QString arg2 = event->args()[2].value<QString>();
0091             QString arg3 = event->args()[3].value<QString>();
0092             QString arg4 = event->args()[4].value<QString>();
0093 
0094             _title = info;
0095             setProcessedAmount(KJob::Bytes, 0);
0096             setTotalAmount(KJob::Bytes, 0);
0097             emitSpeed(0);
0098 
0099             emit description(this, info, qMakePair(arg1, arg2), qMakePair(arg3, arg4));
0100 
0101         } break;
0102         case CMD_UPLOAD_FILES:
0103         case CMD_DOWNLOAD_FILES: {
0104             QList<QUrl> sources = KrServices::toUrlList(event->args()[0].value<QStringList>());
0105             QUrl dest = event->args()[1].value<QUrl>();
0106             KIO::Job *job = KIO::copy(sources, dest, KIO::HideProgressInfo);
0107             addSubjob(job);
0108             job->setUiDelegate(new KIO::JobUiDelegate());
0109 
0110             connect(job, &KIO::Job::result, this, &AbstractThreadedJob::slotDownloadResult);
0111             connect(job, SIGNAL(processedAmount(KJob *, KJob::Unit, qulonglong)), this, SLOT(slotProcessedAmount(KJob *, KJob::Unit, qulonglong)));
0112             connect(job, SIGNAL(totalAmount(KJob *, KJob::Unit, qulonglong)), this, SLOT(slotTotalAmount(KJob *, KJob::Unit, qulonglong)));
0113             connect(job, SIGNAL(speed(KJob *, ulong)), this, SLOT(slotSpeed(KJob *, ulong)));
0114             connect(job,
0115                     SIGNAL(description(KJob *, QString, QPair<QString, QString>, QPair<QString, QString>)),
0116                     this,
0117                     SLOT(slotDescription(KJob *, QString, QPair<QString, QString>, QPair<QString, QString>)));
0118         } break;
0119         case CMD_MAXPROGRESSVALUE: {
0120             auto maxValue = event->args()[0].value<qulonglong>();
0121             _maxProgressValue = maxValue;
0122             _currentProgress = 0;
0123         } break;
0124         case CMD_ADD_PROGRESS: {
0125             auto progress = event->args()[0].value<qulonglong>();
0126             _currentProgress += progress;
0127             if (_maxProgressValue != 0) {
0128                 setPercent(100 * _currentProgress / _maxProgressValue);
0129 
0130                 int elapsed = _time.isNull() ? 1 : _time.secsTo(QTime::currentTime());
0131 
0132                 if (elapsed != 0 && event->args().count() > 1) {
0133                     _time = QTime::currentTime();
0134                     QString progressString = (event->args()[1].value<QString>());
0135                     emit description(this,
0136                                      _title,
0137                                      qMakePair(progressString, QString("%1/%2").arg(_currentProgress).arg(_maxProgressValue)),
0138                                      qMakePair(QString(), QString()));
0139                 }
0140             }
0141         } break;
0142         case CMD_GET_PASSWORD: {
0143             QString path = event->args()[0].value<QString>();
0144             QString password = KrArcHandler::getPassword(path);
0145 
0146             auto *resultResp = new QList<QVariant>();
0147             (*resultResp) << password;
0148             addEventResponse(resultResp);
0149         } break;
0150         case CMD_MESSAGE: {
0151             QString message = event->args()[0].value<QString>();
0152             auto *ui = dynamic_cast<KIO::JobUiDelegate *>(uiDelegate());
0153             KMessageBox::information(ui ? ui->window() : nullptr, message);
0154             auto *resultResp = new QList<QVariant>();
0155             addEventResponse(resultResp);
0156         } break;
0157         }
0158         return true;
0159     } else {
0160         return KIO::Job::event(e);
0161     }
0162 }
0163 
0164 void AbstractThreadedJob::addEventResponse(QList<QVariant> *obj)
0165 {
0166     _locker.lock();
0167     _stack.push(obj);
0168     _waiter.wakeOne();
0169     _locker.unlock();
0170 }
0171 
0172 QList<QVariant> *AbstractThreadedJob::getEventResponse(UserEvent *event)
0173 {
0174     _locker.lock();
0175     QApplication::postEvent(this, event);
0176     _waiter.wait(&_locker);
0177     if (_exiting)
0178         return nullptr;
0179     QList<QVariant> *resp = _stack.pop();
0180     _locker.unlock();
0181     return resp;
0182 }
0183 
0184 void AbstractThreadedJob::sendEvent(UserEvent *event)
0185 {
0186     QApplication::postEvent(this, event);
0187 }
0188 
0189 void AbstractThreadedJob::slotDownloadResult(KJob *job)
0190 {
0191     auto *resultResp = new QList<QVariant>();
0192     if (job) {
0193         (*resultResp) << QVariant(job->error());
0194         (*resultResp) << QVariant(job->errorText());
0195     } else {
0196         (*resultResp) << QVariant(KJob::UserDefinedError);
0197         (*resultResp) << QVariant(QString(i18n("Internal error, undefined <job> in result signal")));
0198     }
0199 
0200     addEventResponse(resultResp);
0201 }
0202 
0203 void AbstractThreadedJob::slotProcessedAmount(KJob *, KJob::Unit unit, qulonglong xu)
0204 {
0205     setProcessedAmount(unit, xu);
0206 }
0207 
0208 void AbstractThreadedJob::slotTotalAmount(KJob *, KJob::Unit unit, qulonglong xu)
0209 {
0210     setTotalAmount(unit, xu);
0211 }
0212 
0213 void AbstractThreadedJob::slotSpeed(KJob *, unsigned long spd)
0214 {
0215     emitSpeed(spd);
0216 }
0217 
0218 void AbstractThreadedJob::slotDescription(KJob *, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2)
0219 {
0220     QString mytitle = title;
0221     if (!_title.isNull())
0222         mytitle = _title;
0223     emit description(this, mytitle, field1, field2);
0224 }
0225 
0226 class AbstractJobObserver : public KrArcObserver
0227 {
0228 protected:
0229     AbstractJobThread *_jobThread;
0230 
0231 public:
0232     explicit AbstractJobObserver(AbstractJobThread *thread)
0233         : _jobThread(thread)
0234     {
0235     }
0236     ~AbstractJobObserver() override = default;
0237 
0238     void processEvents() override
0239     {
0240         usleep(1000);
0241         qApp->processEvents();
0242     }
0243 
0244     void subJobStarted(const QString &jobTitle, qulonglong count) override
0245     {
0246         _jobThread->sendReset(jobTitle);
0247         _jobThread->sendMaxProgressValue(count);
0248     }
0249 
0250     void subJobStopped() override
0251     {
0252     }
0253 
0254     bool wasCancelled() override
0255     {
0256         return _jobThread->_exited;
0257     }
0258 
0259     void error(const QString &error) override
0260     {
0261         _jobThread->sendError(KIO::ERR_NO_CONTENT, error);
0262     }
0263 
0264     void detailedError(const QString &error, const QString &details) override
0265     {
0266         _jobThread->sendError(KIO::ERR_NO_CONTENT, error + '\n' + details);
0267     }
0268 
0269     void incrementProgress(int c) override
0270     {
0271         _jobThread->sendAddProgress(c, _jobThread->_progressTitle);
0272     }
0273 };
0274 
0275 AbstractJobThread::AbstractJobThread()
0276     : _job(nullptr)
0277     , _downloadTempDir(nullptr)
0278     , _observer(nullptr)
0279     , _tempFile(nullptr)
0280     , _tempDir(nullptr)
0281     , _exited(false)
0282 {
0283 }
0284 
0285 AbstractJobThread::~AbstractJobThread()
0286 {
0287     if (_downloadTempDir) {
0288         delete _downloadTempDir;
0289         _downloadTempDir = nullptr;
0290     }
0291     if (_observer) {
0292         delete _observer;
0293         _observer = nullptr;
0294     }
0295     if (_tempFile) {
0296         delete _tempFile;
0297         _tempFile = nullptr;
0298     }
0299 }
0300 
0301 void AbstractJobThread::run()
0302 {
0303     QTimer::singleShot(0, this, &AbstractJobThread::slotStart);
0304 
0305     QPointer<QEventLoop> threadLoop = new QEventLoop(this);
0306     _loop = threadLoop;
0307     threadLoop->exec();
0308 
0309     _loop = nullptr;
0310     delete threadLoop;
0311 }
0312 
0313 void AbstractJobThread::terminate()
0314 {
0315     if (_loop && !_exited) {
0316         _loop->quit();
0317         _exited = true;
0318     }
0319 }
0320 
0321 void AbstractJobThread::abort()
0322 {
0323     terminate();
0324 }
0325 
0326 QList<QUrl> AbstractJobThread::remoteUrls(const QUrl &baseUrl, const QStringList &files)
0327 {
0328     QList<QUrl> urlList;
0329     foreach (const QString &name, files) {
0330         QUrl url = baseUrl;
0331         url = url.adjusted(QUrl::StripTrailingSlash);
0332         url.setPath(url.path() + '/' + (name));
0333         urlList << url;
0334     }
0335     return urlList;
0336 }
0337 
0338 QUrl AbstractJobThread::downloadIfRemote(const QUrl &baseUrl, const QStringList &files)
0339 {
0340     // download remote URL-s if necessary
0341 
0342     if (!baseUrl.isLocalFile()) {
0343         sendInfo(i18n("Downloading remote files"));
0344 
0345         _downloadTempDir = new QTemporaryDir();
0346         QList<QUrl> urlList = remoteUrls(baseUrl, files);
0347 
0348         const QUrl dest = QUrl::fromLocalFile(_downloadTempDir->path());
0349 
0350         QList<QVariant> args;
0351         args << KrServices::toStringList(urlList);
0352         args << dest;
0353 
0354         auto *downloadEvent = new UserEvent(CMD_DOWNLOAD_FILES, args);
0355         QList<QVariant> *result = _job->getEventResponse(downloadEvent);
0356         if (result == nullptr)
0357             return QUrl();
0358 
0359         auto errorCode = (*result)[0].value<int>();
0360         QString errorText = (*result)[1].value<QString>();
0361 
0362         delete result;
0363 
0364         if (errorCode) {
0365             sendError(errorCode, errorText);
0366             return QUrl();
0367         } else {
0368             return dest;
0369         }
0370     } else {
0371         return baseUrl;
0372     }
0373 }
0374 
0375 QString AbstractJobThread::tempFileIfRemote(const QUrl &kurl, const QString &type)
0376 {
0377     if (kurl.isLocalFile()) {
0378         return kurl.path();
0379     }
0380 
0381     _tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.") + type);
0382     _tempFile->open();
0383     _tempFileName = _tempFile->fileName();
0384     _tempFile->close(); // necessary to create the filename
0385     QFile::remove(_tempFileName);
0386 
0387     _tempFileTarget = kurl;
0388     return _tempFileName;
0389 }
0390 
0391 QString AbstractJobThread::tempDirIfRemote(const QUrl &kurl)
0392 {
0393     if (kurl.isLocalFile()) {
0394         return kurl.adjusted(QUrl::StripTrailingSlash).path();
0395     }
0396 
0397     _tempDir = new QTemporaryDir();
0398     _tempDirTarget = kurl;
0399     return _tempDirName = _tempDir->path();
0400 }
0401 
0402 void AbstractJobThread::sendSuccess()
0403 {
0404     terminate();
0405 
0406     QList<QVariant> args;
0407 
0408     auto *errorEvent = new UserEvent(CMD_SUCCESS, args);
0409     _job->sendEvent(errorEvent);
0410 }
0411 
0412 void AbstractJobThread::sendError(int errorCode, const QString &message)
0413 {
0414     terminate();
0415 
0416     QList<QVariant> args;
0417     args << errorCode;
0418     args << message;
0419 
0420     auto *errorEvent = new UserEvent(CMD_ERROR, args);
0421     _job->sendEvent(errorEvent);
0422 }
0423 
0424 void AbstractJobThread::sendInfo(const QString &message, const QString &a1, const QString &a2, const QString &a3, const QString &a4)
0425 {
0426     QList<QVariant> args;
0427     args << message;
0428     args << a1;
0429     args << a2;
0430     args << a3;
0431     args << a4;
0432 
0433     auto *infoEvent = new UserEvent(CMD_INFO, args);
0434     _job->sendEvent(infoEvent);
0435 }
0436 
0437 void AbstractJobThread::sendReset(const QString &message, const QString &a1, const QString &a2, const QString &a3, const QString &a4)
0438 {
0439     QList<QVariant> args;
0440     args << message;
0441     args << a1;
0442     args << a2;
0443     args << a3;
0444     args << a4;
0445 
0446     auto *infoEvent = new UserEvent(CMD_RESET, args);
0447     _job->sendEvent(infoEvent);
0448 }
0449 
0450 void AbstractJobThread::sendMaxProgressValue(qulonglong value)
0451 {
0452     QList<QVariant> args;
0453     args << value;
0454 
0455     auto *infoEvent = new UserEvent(CMD_MAXPROGRESSVALUE, args);
0456     _job->sendEvent(infoEvent);
0457 }
0458 
0459 void AbstractJobThread::sendAddProgress(qulonglong value, const QString &progress)
0460 {
0461     QList<QVariant> args;
0462     args << value;
0463 
0464     if (!progress.isNull())
0465         args << progress;
0466 
0467     auto *infoEvent = new UserEvent(CMD_ADD_PROGRESS, args);
0468     _job->sendEvent(infoEvent);
0469 }
0470 
0471 void countFiles(const QString &path, unsigned long &totalFiles, bool &stop)
0472 {
0473     const QDir dir(path);
0474     if (!dir.exists()) {
0475         totalFiles++; // assume it's a file
0476         return;
0477     }
0478 
0479     for (const QString &name : dir.entryList(QDir::NoDotAndDotDot)) {
0480         if (stop)
0481             return;
0482 
0483         countFiles(dir.absoluteFilePath(name), totalFiles, stop);
0484     }
0485 }
0486 
0487 void AbstractJobThread::countLocalFiles(const QUrl &baseUrl, const QStringList &names, unsigned long &totalFiles)
0488 {
0489     sendReset(i18n("Counting files"));
0490 
0491     FileSystem *calcSpaceFileSystem = FileSystemProvider::instance().getFilesystem(baseUrl);
0492     calcSpaceFileSystem->scanDir(baseUrl);
0493     for (const QString &name : names) {
0494         if (_exited)
0495             return;
0496 
0497         const QString path = calcSpaceFileSystem->getUrl(name).toLocalFile();
0498         if (!QFileInfo::exists(path))
0499             return;
0500 
0501         countFiles(path, totalFiles, _exited);
0502     }
0503 
0504     delete calcSpaceFileSystem;
0505 }
0506 
0507 KrArcObserver *AbstractJobThread::observer()
0508 {
0509     if (_observer)
0510         return _observer;
0511     _observer = new AbstractJobObserver(this);
0512     return _observer;
0513 }
0514 
0515 bool AbstractJobThread::uploadTempFiles()
0516 {
0517     if (_tempFile != nullptr || _tempDir != nullptr) {
0518         sendInfo(i18n("Uploading to remote destination"));
0519 
0520         if (_tempFile) {
0521             QList<QUrl> urlList;
0522             urlList << QUrl::fromLocalFile(_tempFileName);
0523 
0524             QList<QVariant> args;
0525             args << KrServices::toStringList(urlList);
0526             args << _tempFileTarget;
0527 
0528             auto *uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args);
0529             QList<QVariant> *result = _job->getEventResponse(uploadEvent);
0530             if (result == nullptr)
0531                 return false;
0532 
0533             auto errorCode = (*result)[0].value<int>();
0534             QString errorText = (*result)[1].value<QString>();
0535 
0536             delete result;
0537 
0538             if (errorCode) {
0539                 sendError(errorCode, errorText);
0540                 return false;
0541             }
0542         }
0543 
0544         if (_tempDir) {
0545             QList<QUrl> urlList;
0546             QDir tempDir(_tempDirName);
0547             QStringList list = tempDir.entryList();
0548             foreach (const QString &name, list) {
0549                 if (name == "." || name == "..")
0550                     continue;
0551                 QUrl url = QUrl::fromLocalFile(_tempDirName).adjusted(QUrl::StripTrailingSlash);
0552                 url.setPath(url.path() + '/' + (name));
0553                 urlList << url;
0554             }
0555 
0556             QList<QVariant> args;
0557             args << KrServices::toStringList(urlList);
0558             args << _tempDirTarget;
0559 
0560             auto *uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args);
0561             QList<QVariant> *result = _job->getEventResponse(uploadEvent);
0562             if (result == nullptr)
0563                 return false;
0564 
0565             auto errorCode = (*result)[0].value<int>();
0566             QString errorText = (*result)[1].value<QString>();
0567 
0568             delete result;
0569 
0570             if (errorCode) {
0571                 sendError(errorCode, errorText);
0572                 return false;
0573             }
0574         }
0575     }
0576     return true;
0577 }
0578 
0579 QString AbstractJobThread::getPassword(const QString &path)
0580 {
0581     QList<QVariant> args;
0582     args << path;
0583 
0584     auto *getPasswdEvent = new UserEvent(CMD_GET_PASSWORD, args);
0585     QList<QVariant> *result = _job->getEventResponse(getPasswdEvent);
0586     if (result == nullptr)
0587         return QString();
0588 
0589     QString password = (*result)[0].value<QString>();
0590     if (password.isNull())
0591         password = QString("");
0592 
0593     delete result;
0594     return password;
0595 }
0596 
0597 void AbstractJobThread::sendMessage(const QString &message)
0598 {
0599     QList<QVariant> args;
0600     args << message;
0601 
0602     auto *getPasswdEvent = new UserEvent(CMD_MESSAGE, args);
0603     QList<QVariant> *result = _job->getEventResponse(getPasswdEvent);
0604     if (result == nullptr)
0605         return;
0606     delete result;
0607 }
0608 
0609 //! Gets some archive information that is needed in several cases.
0610 /*!
0611     \param path A path to the archive.
0612     \param type The type of the archive.
0613     \param password The password of the archive.
0614     \param arcName The name of the archive.
0615     \param sourceFolder A QUrl, which may be remote, of the folder where the archive is.
0616     \return If the archive information has been obtained.
0617 */
0618 bool AbstractJobThread::getArchiveInformation(QString &path, QString &type, QString &password, QString &arcName, const QUrl &sourceFolder)
0619 {
0620     // Safety checks (though the user shouldn't have been able to select something named "" or "..")
0621     if (arcName.isEmpty())
0622         return false;
0623     if (arcName == "..")
0624         return false;
0625 
0626     QUrl url = sourceFolder.adjusted(QUrl::StripTrailingSlash);
0627     url.setPath(url.path() + '/' + arcName);
0628 
0629     path = url.adjusted(QUrl::StripTrailingSlash).path();
0630 
0631     QMimeDatabase db;
0632     QMimeType mt = db.mimeTypeForUrl(url);
0633     QString mime = mt.isValid() ? mt.name() : QString();
0634     bool encrypted = false;
0635     type = krArcMan.getType(encrypted, path, mime);
0636 
0637     // Check that the archive is supported
0638     if (!KrArcHandler::arcSupported(type)) {
0639         sendError(KIO::ERR_NO_CONTENT, i18nc("%1=archive filename", "%1, unsupported archive type.", arcName));
0640         return false;
0641     }
0642 
0643     password = encrypted ? getPassword(path) : QString();
0644 
0645     return true;
0646 }