1 /*****************************************************************************
2 * Copyright (C) 2009 Csaba Karai <krusader@users.sourceforge.net> *
3 * Copyright (C) 2009-2019 Krusader Krew [https://krusader.org] *
4 * *
5 * This file is part of Krusader [https://krusader.org]. *
6 * *
7 * Krusader is free software: you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation, either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * Krusader is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
19 *****************************************************************************/
20
21 #include "abstractthreadedjob.h"
22
23 // QtCore
24 #include <QTimer>
25 #include <QDir>
26 #include <QPointer>
27 #include <QEventLoop>
28 #include <QTemporaryDir>
29 #include <QTemporaryFile>
30 // QtWidgets
31 #include <QApplication>
32
33 #include <KI18n/KLocalizedString>
34 #include <KIO/JobUiDelegate>
35 #include <KWidgetsAddons/KMessageBox>
36
37 #include "krarchandler.h"
38 #include "../krglobal.h"
39 #include "../krservices.h"
40 #include "../FileSystem/filesystemprovider.h"
41
42 extern KRarcHandler arcHandler;
43
AbstractThreadedJob()44 AbstractThreadedJob::AbstractThreadedJob() : KIO::Job(), _locker(), _waiter(), _stack(), _maxProgressValue(0),
45 _currentProgress(0), _exiting(false), _jobThread(0)
46 {
47 }
48
startAbstractJobThread(AbstractJobThread * jobThread)49 void AbstractThreadedJob::startAbstractJobThread(AbstractJobThread * jobThread)
50 {
51 _jobThread = jobThread;
52 _jobThread->setJob(this);
53 _jobThread->moveToThread(_jobThread);
54 _jobThread->start();
55 }
56
~AbstractThreadedJob()57 AbstractThreadedJob::~AbstractThreadedJob()
58 {
59 _exiting = true;
60 if (_jobThread) {
61 _jobThread->abort();
62
63 _locker.lock();
64 _waiter.wakeAll();
65 _locker.unlock();
66
67 _jobThread->wait();
68 delete _jobThread;
69 }
70 }
71
event(QEvent * e)72 bool AbstractThreadedJob::event(QEvent *e)
73 {
74 if (e->type() == QEvent::User) {
75 UserEvent *event = (UserEvent*) e;
76 switch (event->command()) {
77 case CMD_SUCCESS: {
78 emitResult();
79 }
80 break;
81 case CMD_ERROR: {
82 int error = event->args()[ 0 ].value<int>();
83 QString errorText = event->args()[ 1 ].value<QString>();
84
85 setError(error);
86 setErrorText(errorText);
87 emitResult();
88 }
89 break;
90 case CMD_INFO: {
91 QString info = event->args()[ 0 ].value<QString>();
92 QString arg1 = event->args()[ 1 ].value<QString>();
93 QString arg2 = event->args()[ 2 ].value<QString>();
94 QString arg3 = event->args()[ 3 ].value<QString>();
95 QString arg4 = event->args()[ 4 ].value<QString>();
96
97 _title = info;
98 emit description(this, info,
99 qMakePair(arg1, arg2),
100 qMakePair(arg3, arg4));
101
102 }
103 break;
104 case CMD_RESET: {
105 QString info = event->args()[ 0 ].value<QString>();
106 QString arg1 = event->args()[ 1 ].value<QString>();
107 QString arg2 = event->args()[ 2 ].value<QString>();
108 QString arg3 = event->args()[ 3 ].value<QString>();
109 QString arg4 = event->args()[ 4 ].value<QString>();
110
111 _title = info;
112 setProcessedAmount(KJob::Bytes, 0);
113 setTotalAmount(KJob::Bytes, 0);
114 emitSpeed(0);
115
116 emit description(this, info,
117 qMakePair(arg1, arg2),
118 qMakePair(arg3, arg4));
119
120 }
121 break;
122 case CMD_UPLOAD_FILES:
123 case CMD_DOWNLOAD_FILES: {
124 QList<QUrl> sources = KrServices::toUrlList(event->args()[ 0 ].value<QStringList>());
125 QUrl dest = event->args()[ 1 ].value<QUrl>();
126 KIO::Job *job = KIO::copy(sources, dest, KIO::HideProgressInfo);
127 addSubjob(job);
128 job->setUiDelegate(new KIO::JobUiDelegate());
129
130 connect(job, SIGNAL(result(KJob*)), this, SLOT(slotDownloadResult(KJob*)));
131 connect(job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)),
132 this, SLOT(slotProcessedAmount(KJob*,KJob::Unit,qulonglong)));
133 connect(job, SIGNAL(totalAmount(KJob*,KJob::Unit,qulonglong)),
134 this, SLOT(slotTotalAmount(KJob*,KJob::Unit,qulonglong)));
135 connect(job, SIGNAL(speed(KJob*,ulong)),
136 this, SLOT(slotSpeed(KJob*,ulong)));
137 connect(job, SIGNAL(description(KJob*,QString,QPair<QString,QString>,QPair<QString,QString>)),
138 this, SLOT(slotDescription(KJob*,QString,QPair<QString,QString>,QPair<QString,QString>)));
139 }
140 break;
141 case CMD_MAXPROGRESSVALUE: {
142 qulonglong maxValue = event->args()[ 0 ].value<qulonglong>();
143 _maxProgressValue = maxValue;
144 _currentProgress = 0;
145 }
146 break;
147 case CMD_ADD_PROGRESS: {
148 qulonglong progress = event->args()[ 0 ].value<qulonglong>();
149 _currentProgress += progress;
150 if (_maxProgressValue != 0) {
151 setPercent(100 * _currentProgress / _maxProgressValue);
152
153 int elapsed = _time.isNull() ? 1 : _time.secsTo(QTime::currentTime());
154
155 if (elapsed != 0 && event->args().count() > 1) {
156 _time = QTime::currentTime();
157 QString progressString = (event->args()[ 1 ].value<QString>());
158 emit description(this, _title,
159 qMakePair(progressString, QString("%1/%2").arg(_currentProgress).arg(_maxProgressValue)),
160 qMakePair(QString(), QString())
161 );
162 }
163 }
164 }
165 break;
166 case CMD_GET_PASSWORD: {
167 QString path = event->args()[ 0 ].value<QString>();
168 QString password = KRarcHandler::getPassword(path);
169
170 QList<QVariant> *resultResp = new QList<QVariant> ();
171 (*resultResp) << password;
172 addEventResponse(resultResp);
173 }
174 break;
175 case CMD_MESSAGE: {
176 QString message = event->args()[ 0 ].value<QString>();
177 KIO::JobUiDelegate *ui = static_cast<KIO::JobUiDelegate*>(uiDelegate());
178 KMessageBox::information(ui ? ui->window() : 0, message);
179 QList<QVariant> *resultResp = new QList<QVariant> ();
180 addEventResponse(resultResp);
181 }
182 break;
183 }
184 return true;
185 } else {
186 return KIO::Job::event(e);
187 }
188 }
189
addEventResponse(QList<QVariant> * obj)190 void AbstractThreadedJob::addEventResponse(QList<QVariant> * obj)
191 {
192 _locker.lock();
193 _stack.push(obj);
194 _waiter.wakeOne();
195 _locker.unlock();
196 }
197
getEventResponse(UserEvent * event)198 QList<QVariant> * AbstractThreadedJob::getEventResponse(UserEvent * event)
199 {
200 _locker.lock();
201 QApplication::postEvent(this, event);
202 _waiter.wait(&_locker);
203 if (_exiting)
204 return 0;
205 QList<QVariant> *resp = _stack.pop();
206 _locker.unlock();
207 return resp;
208 }
209
sendEvent(UserEvent * event)210 void AbstractThreadedJob::sendEvent(UserEvent * event)
211 {
212 QApplication::postEvent(this, event);
213 }
214
slotDownloadResult(KJob * job)215 void AbstractThreadedJob::slotDownloadResult(KJob* job)
216 {
217 QList<QVariant> *resultResp = new QList<QVariant> ();
218 if (job) {
219 (*resultResp) << QVariant(job->error());
220 (*resultResp) << QVariant(job->errorText());
221 } else {
222 (*resultResp) << QVariant(KJob::UserDefinedError);
223 (*resultResp) << QVariant(QString(i18n("Internal error, undefined <job> in result signal")));
224 }
225
226 addEventResponse(resultResp);
227 }
228
slotProcessedAmount(KJob *,KJob::Unit unit,qulonglong xu)229 void AbstractThreadedJob::slotProcessedAmount(KJob *, KJob::Unit unit, qulonglong xu)
230 {
231 setProcessedAmount(unit, xu);
232 }
233
slotTotalAmount(KJob *,KJob::Unit unit,qulonglong xu)234 void AbstractThreadedJob::slotTotalAmount(KJob *, KJob::Unit unit, qulonglong xu)
235 {
236 setTotalAmount(unit, xu);
237 }
238
slotSpeed(KJob *,unsigned long spd)239 void AbstractThreadedJob::slotSpeed(KJob *, unsigned long spd)
240 {
241 emitSpeed(spd);
242 }
243
slotDescription(KJob *,const QString & title,const QPair<QString,QString> & field1,const QPair<QString,QString> & field2)244 void AbstractThreadedJob::slotDescription(KJob *, const QString &title, const QPair<QString, QString> &field1,
245 const QPair<QString, QString> &field2)
246 {
247 QString mytitle = title;
248 if (!_title.isNull())
249 mytitle = _title;
250 emit description(this, mytitle, field1, field2);
251 }
252
253 class AbstractJobObserver : public KRarcObserver
254 {
255 protected:
256 AbstractJobThread * _jobThread;
257 public:
AbstractJobObserver(AbstractJobThread * thread)258 explicit AbstractJobObserver(AbstractJobThread * thread): _jobThread(thread) {}
~AbstractJobObserver()259 virtual ~AbstractJobObserver() {}
260
processEvents()261 virtual void processEvents() Q_DECL_OVERRIDE {
262 usleep(1000);
263 qApp->processEvents();
264 }
265
subJobStarted(const QString & jobTitle,int count)266 virtual void subJobStarted(const QString & jobTitle, int count) Q_DECL_OVERRIDE {
267 _jobThread->sendReset(jobTitle);
268 _jobThread->sendMaxProgressValue(count);
269 }
270
subJobStopped()271 virtual void subJobStopped() Q_DECL_OVERRIDE {
272 }
273
wasCancelled()274 virtual bool wasCancelled() Q_DECL_OVERRIDE {
275 return _jobThread->_exited;
276 }
277
error(const QString & error)278 virtual void error(const QString & error) Q_DECL_OVERRIDE {
279 _jobThread->sendError(KIO::ERR_NO_CONTENT, error);
280 }
281
detailedError(const QString & error,const QString & details)282 virtual void detailedError(const QString & error, const QString & details) Q_DECL_OVERRIDE {
283 _jobThread->sendError(KIO::ERR_NO_CONTENT, error + '\n' + details);
284 }
285
incrementProgress(int c)286 virtual void incrementProgress(int c) Q_DECL_OVERRIDE {
287 _jobThread->sendAddProgress(c, _jobThread->_progressTitle);
288 }
289 };
290
291
AbstractJobThread()292 AbstractJobThread::AbstractJobThread() : _job(0), _downloadTempDir(0), _observer(0), _tempFile(0),
293 _tempDir(0), _exited(false)
294 {
295 }
296
~AbstractJobThread()297 AbstractJobThread::~AbstractJobThread()
298 {
299 if (_downloadTempDir) {
300 delete _downloadTempDir;
301 _downloadTempDir = 0;
302 }
303 if (_observer) {
304 delete _observer;
305 _observer = 0;
306 }
307 if (_tempFile) {
308 delete _tempFile;
309 _tempFile = 0;
310 }
311 }
312
run()313 void AbstractJobThread::run()
314 {
315 QTimer::singleShot(0, this, SLOT(slotStart()));
316
317 QPointer<QEventLoop> threadLoop = new QEventLoop(this);
318 _loop = threadLoop;
319 threadLoop->exec();
320
321 _loop = 0;
322 delete threadLoop;
323 }
324
terminate()325 void AbstractJobThread::terminate()
326 {
327 if (_loop && !_exited) {
328 _loop->quit();
329 _exited = true;
330 }
331 }
332
abort()333 void AbstractJobThread::abort()
334 {
335 terminate();
336 }
337
remoteUrls(const QUrl & baseUrl,const QStringList & files)338 QList<QUrl> AbstractJobThread::remoteUrls(const QUrl &baseUrl, const QStringList & files)
339 {
340 QList<QUrl> urlList;
341 foreach(const QString &name, files) {
342 QUrl url = baseUrl;
343 url = url.adjusted(QUrl::StripTrailingSlash);
344 url.setPath(url.path() + '/' + (name));
345 urlList << url;
346 }
347 return urlList;
348 }
349
downloadIfRemote(const QUrl & baseUrl,const QStringList & files)350 QUrl AbstractJobThread::downloadIfRemote(const QUrl &baseUrl, const QStringList & files)
351 {
352 // download remote URL-s if necessary
353
354 if (!baseUrl.isLocalFile()) {
355 sendInfo(i18n("Downloading remote files"));
356
357 _downloadTempDir = new QTemporaryDir();
358 QList<QUrl> urlList = remoteUrls(baseUrl, files);
359
360 QUrl dest(_downloadTempDir->path());
361
362 QList<QVariant> args;
363 args << KrServices::toStringList(urlList);
364 args << dest;
365
366 UserEvent * downloadEvent = new UserEvent(CMD_DOWNLOAD_FILES, args);
367 QList<QVariant> * result = _job->getEventResponse(downloadEvent);
368 if (result == 0)
369 return QUrl();
370
371 int errorCode = (*result)[ 0 ].value<int>();
372 QString errorText = (*result)[ 1 ].value<QString>();
373
374 delete result;
375
376 if (errorCode) {
377 sendError(errorCode, errorText);
378 return QUrl();
379 } else {
380 return dest;
381 }
382 } else {
383 return baseUrl;
384 }
385 }
386
387
tempFileIfRemote(const QUrl & kurl,const QString & type)388 QString AbstractJobThread::tempFileIfRemote(const QUrl &kurl, const QString &type)
389 {
390 if (kurl.isLocalFile()) {
391 return kurl.path();
392 }
393
394 _tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX.") + type);
395 _tempFile->open();
396 _tempFileName = _tempFile->fileName();
397 _tempFile->close(); // necessary to create the filename
398 QFile::remove(_tempFileName);
399
400 _tempFileTarget = kurl;
401 return _tempFileName;
402 }
403
tempDirIfRemote(const QUrl & kurl)404 QString AbstractJobThread::tempDirIfRemote(const QUrl &kurl)
405 {
406 if (kurl.isLocalFile()) {
407 return kurl.adjusted(QUrl::StripTrailingSlash).path();
408 }
409
410 _tempDir = new QTemporaryDir();
411 _tempDirTarget = kurl;
412 return _tempDirName = _tempDir->path();
413 }
414
sendSuccess()415 void AbstractJobThread::sendSuccess()
416 {
417 terminate();
418
419 QList<QVariant> args;
420
421 UserEvent * errorEvent = new UserEvent(CMD_SUCCESS, args);
422 _job->sendEvent(errorEvent);
423 }
424
sendError(int errorCode,QString message)425 void AbstractJobThread::sendError(int errorCode, QString message)
426 {
427 terminate();
428
429 QList<QVariant> args;
430 args << errorCode;
431 args << message;
432
433 UserEvent * errorEvent = new UserEvent(CMD_ERROR, args);
434 _job->sendEvent(errorEvent);
435 }
436
sendInfo(QString message,QString a1,QString a2,QString a3,QString a4)437 void AbstractJobThread::sendInfo(QString message, QString a1, QString a2, QString a3, QString a4)
438 {
439 QList<QVariant> args;
440 args << message;
441 args << a1;
442 args << a2;
443 args << a3;
444 args << a4;
445
446 UserEvent * infoEvent = new UserEvent(CMD_INFO, args);
447 _job->sendEvent(infoEvent);
448 }
449
sendReset(QString message,QString a1,QString a2,QString a3,QString a4)450 void AbstractJobThread::sendReset(QString message, QString a1, QString a2, QString a3, QString a4)
451 {
452 QList<QVariant> args;
453 args << message;
454 args << a1;
455 args << a2;
456 args << a3;
457 args << a4;
458
459 UserEvent * infoEvent = new UserEvent(CMD_RESET, args);
460 _job->sendEvent(infoEvent);
461 }
462
sendMaxProgressValue(qulonglong value)463 void AbstractJobThread::sendMaxProgressValue(qulonglong value)
464 {
465 QList<QVariant> args;
466 args << value;
467
468 UserEvent * infoEvent = new UserEvent(CMD_MAXPROGRESSVALUE, args);
469 _job->sendEvent(infoEvent);
470 }
471
sendAddProgress(qulonglong value,const QString & progress)472 void AbstractJobThread::sendAddProgress(qulonglong value, const QString &progress)
473 {
474 QList<QVariant> args;
475 args << value;
476
477 if (!progress.isNull())
478 args << progress;
479
480 UserEvent * infoEvent = new UserEvent(CMD_ADD_PROGRESS, args);
481 _job->sendEvent(infoEvent);
482 }
483
countFiles(const QString & path,unsigned long & totalFiles,bool & stop)484 void countFiles(const QString &path, unsigned long &totalFiles, bool &stop)
485 {
486 const QDir dir(path);
487 if (!dir.exists()) {
488 totalFiles++; // assume it's a file
489 return;
490 }
491
492 for (const QString name : dir.entryList()) {
493 if (stop)
494 return;
495
496 if (name == QStringLiteral(".") || name == QStringLiteral(".."))
497 continue;
498
499 countFiles(dir.absoluteFilePath(name), totalFiles, stop);
500 }
501 }
502
countLocalFiles(const QUrl & baseUrl,const QStringList & names,unsigned long & totalFiles)503 void AbstractJobThread::countLocalFiles(const QUrl &baseUrl, const QStringList &names,
504 unsigned long &totalFiles)
505 {
506 sendReset(i18n("Counting files"));
507
508 FileSystem *calcSpaceFileSystem = FileSystemProvider::instance().getFilesystem(baseUrl);
509 calcSpaceFileSystem->scanDir(baseUrl);
510 for (const QString name : names) {
511 if (_exited)
512 return;
513
514 const QString path = calcSpaceFileSystem->getUrl(name).toLocalFile();
515 if (!QFileInfo(path).exists())
516 return;
517
518 countFiles(path, totalFiles, _exited);
519 }
520
521 delete calcSpaceFileSystem;
522 }
523
observer()524 KRarcObserver * AbstractJobThread::observer()
525 {
526 if (_observer)
527 return _observer;
528 _observer = new AbstractJobObserver(this);
529 return _observer;
530 }
531
uploadTempFiles()532 bool AbstractJobThread::uploadTempFiles()
533 {
534 if (_tempFile != 0 || _tempDir != 0) {
535 sendInfo(i18n("Uploading to remote destination"));
536
537 if (_tempFile) {
538 QList<QUrl> urlList;
539 urlList << QUrl::fromLocalFile(_tempFileName);
540
541 QList<QVariant> args;
542 args << KrServices::toStringList(urlList);
543 args << _tempFileTarget;
544
545 UserEvent * uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args);
546 QList<QVariant> * result = _job->getEventResponse(uploadEvent);
547 if (result == 0)
548 return false;
549
550 int errorCode = (*result)[ 0 ].value<int>();
551 QString errorText = (*result)[ 1 ].value<QString>();
552
553 delete result;
554
555 if (errorCode) {
556 sendError(errorCode, errorText);
557 return false;
558 }
559 }
560
561 if (_tempDir) {
562 QList<QUrl> urlList;
563 QDir tempDir(_tempDirName);
564 QStringList list = tempDir.entryList();
565 foreach(const QString &name, list) {
566 if (name == "." || name == "..")
567 continue;
568 QUrl url = QUrl::fromLocalFile(_tempDirName).adjusted(QUrl::StripTrailingSlash);
569 url.setPath(url.path() + '/' + (name));
570 urlList << url;
571 }
572
573 QList<QVariant> args;
574 args << KrServices::toStringList(urlList);
575 args << _tempDirTarget;
576
577 UserEvent * uploadEvent = new UserEvent(CMD_UPLOAD_FILES, args);
578 QList<QVariant> * result = _job->getEventResponse(uploadEvent);
579 if (result == 0)
580 return false;
581
582 int errorCode = (*result)[ 0 ].value<int>();
583 QString errorText = (*result)[ 1 ].value<QString>();
584
585 delete result;
586
587 if (errorCode) {
588 sendError(errorCode, errorText);
589 return false;
590 }
591 }
592 }
593 return true;
594 }
595
getPassword(const QString & path)596 QString AbstractJobThread::getPassword(const QString &path)
597 {
598 QList<QVariant> args;
599 args << path;
600
601 UserEvent * getPasswdEvent = new UserEvent(CMD_GET_PASSWORD, args);
602 QList<QVariant> * result = _job->getEventResponse(getPasswdEvent);
603 if (result == 0)
604 return QString();
605
606 QString password = (*result)[ 0 ].value<QString>();
607 if (password.isNull())
608 password = QString("");
609
610 delete result;
611 return password;
612 }
613
sendMessage(const QString & message)614 void AbstractJobThread::sendMessage(const QString &message)
615 {
616 QList<QVariant> args;
617 args << message;
618
619 UserEvent * getPasswdEvent = new UserEvent(CMD_MESSAGE, args);
620 QList<QVariant> * result = _job->getEventResponse(getPasswdEvent);
621 if (result == 0)
622 return;
623 delete result;
624 }
625
626 //! Gets some archive information that is needed in several cases.
627 /*!
628 \param path A path to the archive.
629 \param type The type of the archive.
630 \param password The password of the archive.
631 \param arcName The name of the archive.
632 \param sourceFolder A QUrl, which may be remote, of the folder where the archive is.
633 \return If the archive information has been obtained.
634 */
getArchiveInformation(QString & path,QString & type,QString & password,QString & arcName,const QUrl & sourceFolder)635 bool AbstractJobThread::getArchiveInformation(QString &path, QString &type, QString &password,
636 QString &arcName, const QUrl &sourceFolder)
637 {
638 // Safety checks (though the user shouldn't have been able to select something named "" or "..")
639 if (arcName.isEmpty())
640 return false;
641 if (arcName == "..")
642 return false;
643
644 QUrl url = sourceFolder.adjusted(QUrl::StripTrailingSlash);
645 url.setPath(url.path() + '/' + arcName);
646
647 path = url.adjusted(QUrl::StripTrailingSlash).path();
648
649 QMimeDatabase db;
650 QMimeType mt = db.mimeTypeForUrl(url);
651 QString mime = mt.isValid() ? mt.name() : QString();
652 bool encrypted = false;
653 type = arcHandler.getType(encrypted, path, mime);
654
655 // Check that the archive is supported
656 if (!KRarcHandler::arcSupported(type)) {
657 sendError(KIO::ERR_NO_CONTENT, i18nc("%1=archive filename", "%1, unsupported archive type.", arcName));
658 return false;
659 }
660
661 password = encrypted ? getPassword(path) : QString();
662
663 return true;
664 }
665