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