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 }