jobs.cpp 25.4 KB
Newer Older
1 2
/*
 * Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
3
 * Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
4
 * Copyright (c) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
5
 * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
28

29
#include "jobs.h"
30
#include "archiveentry.h"
31
#include "ark_debug.h"
32

33
#include <QDir>
34
#include <QDirIterator>
35
#include <QFileInfo>
36
#include <QThread>
37
#include <QTimer>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
38
#include <QUrl>
39

40
#include <KFileUtils>
41
#include <KLocalizedString>
42

43 44
namespace Kerfuffle
{
45

46 47
class Job::Private : public QThread
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
48 49
    Q_OBJECT

50
public:
Elvis Angelaccio's avatar
Elvis Angelaccio committed
51
    Private(Job *job, QObject *parent = nullptr)
52 53 54 55 56
        : QThread(parent)
        , q(job)
    {
    }

57
    void run() override;
58

59
private:
60 61 62 63 64
    Job *q;
};

void Job::Private::run()
{
65
    q->doWork();
66 67
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
68
Job::Job(Archive *archive, ReadOnlyArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
69
    : KJob()
Elvis Angelaccio's avatar
Elvis Angelaccio committed
70
    , m_archive(archive)
71
    , m_archiveInterface(interface)
72
    , d(new Private(this))
73 74 75 76
{
    setCapabilities(KJob::Killable);
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
77
Job::Job(Archive *archive)
78
    : Job(archive, nullptr)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
79 80 81
{}

Job::Job(ReadOnlyArchiveInterface *interface)
82
    : Job(nullptr, interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
83 84
{}

85 86
Job::~Job()
{
87 88
    if (d->isRunning()) {
        d->wait();
89 90
    }

91
    delete d;
92
}
93

94 95
ReadOnlyArchiveInterface *Job::archiveInterface()
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
96 97 98 99 100 101
    // Use the archive interface.
    if (archive()) {
        return archive()->interface();
    }

    // Use the interface passed to this job (e.g. JSONArchiveInterface in jobstest.cpp).
102 103 104
    return m_archiveInterface;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
Archive *Job::archive() const
{
    return m_archive;
}

QString Job::errorString() const
{
    if (!errorText().isEmpty()) {
        return errorText();
    }

    if (archive()) {
        if (archive()->error() == NoPlugin) {
            return i18n("No suitable plugin found. Ark does not seem to support this file type.");
        }

        if (archive()->error() == FailedPlugin) {
            return i18n("Failed to load a suitable plugin. Make sure any executables needed to handle the archive type are installed.");
        }
    }

    return QString();
}

129 130
void Job::start()
{
131
    jobTimer.start();
132

Elvis Angelaccio's avatar
Elvis Angelaccio committed
133 134 135 136 137 138 139 140
    // We have an archive but it's not valid, nothing to do.
    if (archive() && !archive()->isValid()) {
        QTimer::singleShot(0, this, [=]() {
            onFinished(false);
        });
        return;
    }

141
    if (archiveInterface()->waitForFinishedSignal()) {
142 143 144 145 146 147
        // CLI-based interfaces run a QProcess, no need to use threads.
        QTimer::singleShot(0, this, &Job::doWork);
    } else {
        // Run the job in another thread.
        d->start();
    }
148 149
}

150 151
void Job::connectToArchiveInterfaceSignals()
{
Laurent Montel's avatar
Laurent Montel committed
152 153 154 155 156
    connect(archiveInterface(), &ReadOnlyArchiveInterface::cancelled, this, &Job::onCancelled);
    connect(archiveInterface(), &ReadOnlyArchiveInterface::error, this, &Job::onError);
    connect(archiveInterface(), &ReadOnlyArchiveInterface::entry, this, &Job::onEntry);
    connect(archiveInterface(), &ReadOnlyArchiveInterface::progress, this, &Job::onProgress);
    connect(archiveInterface(), &ReadOnlyArchiveInterface::info, this, &Job::onInfo);
157
    connect(archiveInterface(), &ReadOnlyArchiveInterface::finished, this, &Job::onFinished);
Laurent Montel's avatar
Laurent Montel committed
158
    connect(archiveInterface(), &ReadOnlyArchiveInterface::userQuery, this, &Job::onUserQuery);
159 160 161 162 163

    auto readWriteInterface = qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());
    if (readWriteInterface) {
        connect(readWriteInterface, &ReadWriteArchiveInterface::entryRemoved, this, &Job::onEntryRemoved);
    }
164 165
}

166 167
void Job::onCancelled()
{
168
    qCDebug(ARK) << "Cancelled emitted";
169 170 171
    setError(KJob::KilledJobError);
}

172 173
void Job::onError(const QString & message, const QString & details)
{
174
    Q_UNUSED(details)
175

176
    qCDebug(ARK) << "Error emitted:" << message;
177
    setError(KJob::UserDefinedError);
178 179 180
    setErrorText(message);
}

181
void Job::onEntry(Archive::Entry *entry)
182
{
183 184 185 186 187 188 189 190
    const QString entryFullPath = entry->fullPath();
    if (QDir::cleanPath(entryFullPath).contains(QLatin1String("../"))) {
        qCWarning(ARK) << "Possibly malicious archive. Detected entry that could lead to a directory traversal attack:" << entryFullPath;
        onError(i18n("Could not load the archive because it contains ill-formed entries and might be a malicious archive."), QString());
        onFinished(false);
        return;
    }

191
    emit newEntry(entry);
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
}

void Job::onProgress(double value)
{
    setPercent(static_cast<unsigned long>(100.0*value));
}

void Job::onInfo(const QString& info)
{
    emit infoMessage(this, info);
}

void Job::onEntryRemoved(const QString & path)
{
    emit entryRemoved(path);
}

void Job::onFinished(bool result)
{
211
    qCDebug(ARK) << "Job finished, result:" << result << ", time:" << jobTimer.elapsed() << "ms";
212

Elvis Angelaccio's avatar
Elvis Angelaccio committed
213 214 215 216
    if (archive() && !archive()->isValid()) {
        setError(KJob::UserDefinedError);
    }

217 218 219
    if (!d->isInterruptionRequested()) {
        emitResult();
    }
220 221 222 223
}

void Job::onUserQuery(Query *query)
{
224 225 226 227
    if (archiveInterface()->waitForFinishedSignal()) {
        qCWarning(ARK) << "Plugins run from the main thread should call directly query->execute()";
    }

228 229 230 231 232
    emit userQuery(query);
}

bool Job::doKill()
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
233 234 235
    const bool killed = archiveInterface()->doKill();
    if (killed) {
        return true;
236 237
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
238 239 240
    if (d->isRunning()) {
        qCDebug(ARK) << "Requesting graceful thread interruption, will abort in one second otherwise.";
        d->requestInterruption();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
241
        d->wait(1000);
242
    }
Elvis Angelaccio's avatar
Elvis Angelaccio committed
243

Elvis Angelaccio's avatar
Elvis Angelaccio committed
244
    return true;
245 246
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
247 248
LoadJob::LoadJob(Archive *archive, ReadOnlyArchiveInterface *interface)
    : Job(archive, interface)
249 250 251
    , m_isSingleFolderArchive(true)
    , m_isPasswordProtected(false)
    , m_extractedFilesSize(0)
252 253
    , m_dirCount(0)
    , m_filesCount(0)
254
{
255
    qCDebug(ARK) << "Created job instance";
Elvis Angelaccio's avatar
Elvis Angelaccio committed
256
    connect(this, &LoadJob::newEntry, this, &LoadJob::onNewEntry);
257 258
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
259
LoadJob::LoadJob(Archive *archive)
260
    : LoadJob(archive, nullptr)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
261 262 263
{}

LoadJob::LoadJob(ReadOnlyArchiveInterface *interface)
264
    : LoadJob(nullptr, interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
265 266 267
{}

void LoadJob::doWork()
268
{
269
    emit description(this, i18n("Loading archive"), qMakePair(i18n("Archive"), archiveInterface()->filename()));
270
    connectToArchiveInterfaceSignals();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
271

272
    bool ret = archiveInterface()->list();
273

274
    if (!archiveInterface()->waitForFinishedSignal()) {
275 276 277 278 279
        // onFinished() needs to be called after onNewEntry(), because the former reads members set in the latter.
        // So we need to put it in the event queue, just like the single-thread case does by emitting finished().
        QTimer::singleShot(0, this, [=]() {
            onFinished(ret);
        });
280
    }
281 282
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
283 284
void LoadJob::onFinished(bool result)
{
285
    if (archive() && result) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
286 287 288 289 290 291 292 293 294 295 296 297 298
        archive()->setProperty("unpackedSize", extractedFilesSize());
        archive()->setProperty("isSingleFolder", isSingleFolderArchive());
        const auto name = subfolderName().isEmpty() ? archive()->completeBaseName() : subfolderName();
        archive()->setProperty("subfolderName", name);
        if (isPasswordProtected()) {
            archive()->setProperty("encryptionType",  archive()->password().isEmpty() ? Archive::Encrypted : Archive::HeaderEncrypted);
        }
    }

    Job::onFinished(result);
}

qlonglong LoadJob::extractedFilesSize() const
299 300 301 302
{
    return m_extractedFilesSize;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
303
bool LoadJob::isPasswordProtected() const
304 305 306 307
{
    return m_isPasswordProtected;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
308
bool LoadJob::isSingleFolderArchive() const
309
{
310 311 312 313
    if (m_filesCount == 1 && m_dirCount == 0) {
        return false;
    }

314 315 316
    return m_isSingleFolderArchive;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
317
void LoadJob::onNewEntry(const Archive::Entry *entry)
318
{
319 320
    m_extractedFilesSize += entry->property("size").toLongLong();
    m_isPasswordProtected |= entry->property("isPasswordProtected").toBool();
321

322
    if (entry->isDir()) {
323 324 325 326 327
        m_dirCount++;
    } else {
        m_filesCount++;
    }

328
    if (m_isSingleFolderArchive) {
Nicolas Fella's avatar
Nicolas Fella committed
329
        QString fullPath = entry->fullPath();
330
        // RPM filenames have the ./ prefix, and "." would be detected as the subfolder name, so we remove it.
Nicolas Fella's avatar
Nicolas Fella committed
331 332 333 334
        if (fullPath.startsWith(QLatin1String("./"))) {
            fullPath = fullPath.remove(0, 2);
        }

335
        const QString basePath = fullPath.split(QLatin1Char('/')).at(0);
336

337 338 339
        if (m_basePath.isEmpty()) {
            m_basePath = basePath;
            m_subfolderName = basePath;
340
        } else {
341 342 343 344
            if (m_basePath != basePath) {
                m_isSingleFolderArchive = false;
                m_subfolderName.clear();
            }
345 346 347 348
        }
    }
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
349
QString LoadJob::subfolderName() const
350
{
351 352 353 354
    if (!isSingleFolderArchive()) {
        return QString();
    }

355 356 357
    return m_subfolderName;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
358 359 360 361 362 363 364
BatchExtractJob::BatchExtractJob(LoadJob *loadJob, const QString &destination, bool autoSubfolder, bool preservePaths)
    : Job(loadJob->archive())
    , m_loadJob(loadJob)
    , m_destination(destination)
    , m_autoSubfolder(autoSubfolder)
    , m_preservePaths(preservePaths)
{
365
    qCDebug(ARK) << "Created job instance";
Elvis Angelaccio's avatar
Elvis Angelaccio committed
366 367 368 369 370
}

void BatchExtractJob::doWork()
{
    connect(m_loadJob, &KJob::result, this, &BatchExtractJob::slotLoadingFinished);
371 372
    connect(archiveInterface(), &ReadOnlyArchiveInterface::cancelled, this, &BatchExtractJob::onCancelled);

373 374 375 376
    if (archiveInterface()->hasBatchExtractionProgress()) {
        // progress() will be actually emitted by the LoadJob, but the archiveInterface() is the same.
        connect(archiveInterface(), &ReadOnlyArchiveInterface::progress, this, &BatchExtractJob::slotLoadingProgress);
    }
Elvis Angelaccio's avatar
Elvis Angelaccio committed
377

378
    // Forward LoadJob's signals.
Elvis Angelaccio's avatar
Elvis Angelaccio committed
379 380 381 382 383
    connect(m_loadJob, &Kerfuffle::Job::newEntry, this, &BatchExtractJob::newEntry);
    connect(m_loadJob, &Kerfuffle::Job::userQuery, this, &BatchExtractJob::userQuery);
    m_loadJob->start();
}

384 385 386 387 388 389 390 391 392
bool BatchExtractJob::doKill()
{
    if (m_step == Loading) {
        return m_loadJob->kill();
    }

    return m_extractJob->kill();
}

393 394 395 396 397 398 399 400 401 402 403 404 405
void BatchExtractJob::slotLoadingProgress(double progress)
{
    // Progress from LoadJob counts only for 50% of the BatchExtractJob's duration.
    m_lastPercentage = static_cast<unsigned long>(50.0*progress);
    setPercent(m_lastPercentage);
}

void BatchExtractJob::slotExtractProgress(double progress)
{
    // The 2nd 50% of the BatchExtractJob's duration comes from the ExtractJob.
    setPercent(m_lastPercentage + static_cast<unsigned long>(50.0*progress));
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
406 407 408
void BatchExtractJob::slotLoadingFinished(KJob *job)
{
    if (job->error()) {
409 410 411
        // Forward errors as well.
        onError(job->errorString(), QString());
        onFinished(false);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
412 413 414 415 416 417 418
        return;
    }

    // Now we can start extraction.
    setupDestination();

    Kerfuffle::ExtractionOptions options;
419
    options.setPreservePaths(m_preservePaths);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
420

421 422 423 424
    m_extractJob = archive()->extractFiles({}, m_destination, options);
    if (m_extractJob) {
        connect(m_extractJob, &KJob::result, this, &BatchExtractJob::emitResult);
        connect(m_extractJob, &Kerfuffle::Job::userQuery, this, &BatchExtractJob::userQuery);
425
        connect(archiveInterface(), &ReadOnlyArchiveInterface::error, this, &BatchExtractJob::onError);
426 427 428 429 430
        if (archiveInterface()->hasBatchExtractionProgress()) {
            // The LoadJob is done, change slot and start setting the percentage from m_lastPercentage on.
            disconnect(archiveInterface(), &ReadOnlyArchiveInterface::progress, this, &BatchExtractJob::slotLoadingProgress);
            connect(archiveInterface(), &ReadOnlyArchiveInterface::progress, this, &BatchExtractJob::slotExtractProgress);
        }
431 432
        m_step = Extracting;
        m_extractJob->start();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
433 434 435 436 437 438 439 440 441 442
    } else {
        emitResult();
    }
}

void BatchExtractJob::setupDestination()
{
    const bool isSingleFolderRPM = (archive()->isSingleFolder() &&
                                   (archive()->mimeType().name() == QLatin1String("application/x-rpm")));

443
    if (m_autoSubfolder && (archive()->hasMultipleTopLevelEntries() || isSingleFolderRPM)) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
444 445 446 447 448
        const QDir d(m_destination);
        QString subfolderName = archive()->subfolderName();

        // Special case for single folder RPM archives.
        // We don't want the autodetected folder to have a meaningless "usr" name.
449
        if (isSingleFolderRPM && subfolderName == QLatin1String("usr")) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
450 451 452 453 454
            qCDebug(ARK) << "Detected single folder RPM archive. Using archive basename as subfolder name";
            subfolderName = QFileInfo(archive()->fileName()).completeBaseName();
        }

        if (d.exists(subfolderName)) {
455
            subfolderName = KFileUtils::suggestName(QUrl::fromUserInput(m_destination, QString(), QUrl::AssumeLocalFile), subfolderName);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
456 457 458 459 460 461 462 463
        }

        d.mkdir(subfolderName);

        m_destination += QLatin1Char( '/' ) + subfolderName;
    }
}

464
CreateJob::CreateJob(Archive *archive, const QVector<Archive::Entry*> &entries, const CompressionOptions &options)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
465 466 467 468
    : Job(archive)
    , m_entries(entries)
    , m_options(options)
{
469
    qCDebug(ARK) << "Created job instance";
Elvis Angelaccio's avatar
Elvis Angelaccio committed
470 471 472 473 474 475 476 477 478 479 480 481 482 483
}

void CreateJob::enableEncryption(const QString &password, bool encryptHeader)
{
    archive()->encrypt(password, encryptHeader);
}

void CreateJob::setMultiVolume(bool isMultiVolume)
{
    archive()->setMultiVolume(isMultiVolume);
}

void CreateJob::doWork()
{
484 485
    connect(archiveInterface(), &ReadOnlyArchiveInterface::progress, this, &CreateJob::onProgress);

Elvis Angelaccio's avatar
Elvis Angelaccio committed
486
    m_addJob = archive()->addFiles(m_entries, nullptr, m_options);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
487

488 489
    if (m_addJob) {
        connect(m_addJob, &KJob::result, this, &CreateJob::emitResult);
490
        // Forward description signal from AddJob, we need to change the first argument ('this' needs to be a CreateJob).
491
        connect(m_addJob, &KJob::description, this, [=](KJob *, const QString &title, const QPair<QString,QString> &field1, const QPair<QString,QString> &) {
492 493 494
            emit description(this, title, field1);
        });

495
        m_addJob->start();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
496 497 498 499 500
    } else {
        emitResult();
    }
}

501 502 503 504 505
bool CreateJob::doKill()
{
    return m_addJob && m_addJob->kill();
}

Laurent Montel's avatar
Laurent Montel committed
506
ExtractJob::ExtractJob(const QVector<Archive::Entry*> &entries, const QString &destinationDir, ExtractionOptions options, ReadOnlyArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
507
    : Job(interface)
508
    , m_entries(entries)
509 510
    , m_destinationDir(destinationDir)
    , m_options(options)
511
{
512
    qCDebug(ARK) << "Created job instance";
513 514 515 516 517
}

void ExtractJob::doWork()
{
    QString desc;
518
    if (m_entries.count() == 0) {
519 520
        desc = i18n("Extracting all files");
    } else {
521
        desc = i18np("Extracting one file", "Extracting %1 files", m_entries.count());
522
    }
523
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()), qMakePair(i18nc("extraction folder", "Destination"), m_destinationDir));
524

525 526 527 528 529 530 531
    QFileInfo destDirInfo(m_destinationDir);
    if (destDirInfo.isDir() && (!destDirInfo.isWritable() || !destDirInfo.isExecutable())) {
        onError(xi18n("Could not write to destination <filename>%1</filename>.<nl/>Check whether you have sufficient permissions.", m_destinationDir), QString());
        onFinished(false);
        return;
    }

532
    connectToArchiveInterfaceSignals();
533

534
    qCDebug(ARK) << "Starting extraction with" << m_entries.count() << "selected files."
535
             << m_entries
536 537
             << "Destination dir:" << m_destinationDir
             << "Options:" << m_options;
538

539
    bool ret = archiveInterface()->extractFiles(m_entries, m_destinationDir, m_options);
540

541
    if (!archiveInterface()->waitForFinishedSignal()) {
542
        onFinished(ret);
543
    }
544 545
}

546 547 548 549 550
QString ExtractJob::destinationDirectory() const
{
    return m_destinationDir;
}

551 552 553 554 555
ExtractionOptions ExtractJob::extractionOptions() const
{
    return m_options;
}

556
TempExtractJob::TempExtractJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
557
    : Job(interface)
558
    , m_entry(entry)
559 560
    , m_passwordProtectedHint(passwordProtectedHint)
{
561
    m_tmpExtractDir = new QTemporaryDir();
562 563 564 565
}

QString TempExtractJob::validatedFilePath() const
{
566
    QString path = extractionDir() + QLatin1Char('/') + m_entry->fullPath();
567 568 569 570 571 572 573 574 575 576 577 578 579 580

    // Make sure a maliciously crafted archive with parent folders named ".." do
    // not cause the previewed file path to be located outside the temporary
    // directory, resulting in a directory traversal issue.
    path.remove(QStringLiteral("../"));

    return path;
}

ExtractionOptions TempExtractJob::extractionOptions() const
{
    ExtractionOptions options;

    if (m_passwordProtectedHint) {
581
        options.setEncryptedArchiveHint(true);
582 583 584 585 586
    }

    return options;
}

587 588 589 590 591
QTemporaryDir *TempExtractJob::tempDir() const
{
    return m_tmpExtractDir;
}

592 593
void TempExtractJob::doWork()
{
594 595
    // pass 1 to i18np on purpose so this translation may properly be reused.
    emit description(this, i18np("Extracting one file", "Extracting %1 files", 1));
596 597 598

    connectToArchiveInterfaceSignals();

599
    qCDebug(ARK) << "Extracting:" << m_entry;
600

601
    bool ret = archiveInterface()->extractFiles({m_entry}, extractionDir(), extractionOptions());
602 603 604 605 606 607

    if (!archiveInterface()->waitForFinishedSignal()) {
        onFinished(ret);
    }
}

608
QString TempExtractJob::extractionDir() const
609
{
610
    return m_tmpExtractDir->path();
611 612
}

613 614
PreviewJob::PreviewJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
    : TempExtractJob(entry, passwordProtectedHint, interface)
615
{
616
    qCDebug(ARK) << "Created job instance";
617 618
}

619 620
OpenJob::OpenJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
    : TempExtractJob(entry, passwordProtectedHint, interface)
621
{
622
    qCDebug(ARK) << "Created job instance";
623 624
}

625 626
OpenWithJob::OpenWithJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
    : OpenJob(entry, passwordProtectedHint, interface)
627
{
628
    qCDebug(ARK) << "Created job instance";
629 630
}

631
AddJob::AddJob(const QVector<Archive::Entry*> &entries, const Archive::Entry *destination, const CompressionOptions& options, ReadWriteArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
632
    : Job(interface)
633
    , m_entries(entries)
634
    , m_destination(destination)
635
    , m_options(options)
636
{
637
    qCDebug(ARK) << "Created job instance";
638 639 640 641
}

void AddJob::doWork()
{
642
    // Set current dir.
643
    const QString globalWorkDir = m_options.globalWorkDir();
644 645 646 647 648 649 650
    const QDir workDir = globalWorkDir.isEmpty() ? QDir::current() : QDir(globalWorkDir);
    if (!globalWorkDir.isEmpty()) {
        qCDebug(ARK) << "GlobalWorkDir is set, changing dir to " << globalWorkDir;
        m_oldWorkingDir = QDir::currentPath();
        QDir::setCurrent(globalWorkDir);
    }

651
    // Count total number of entries to be added.
652
    uint totalCount = 0;
653 654
    QElapsedTimer timer;
    timer.start();
655
    for (const Archive::Entry* entry : qAsConst(m_entries)) {
656
        totalCount++;
657
        if (QFileInfo(entry->fullPath()).isDir()) {
658
            QDirIterator it(entry->fullPath(), QDir::AllEntries | QDir::Readable | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
659 660 661 662 663 664 665
            while (it.hasNext()) {
                it.next();
                totalCount++;
            }
        }
    }

666
    qCDebug(ARK) << "Going to add" << totalCount << "entries, counted in" << timer.elapsed() << "ms";
667

Elvis Angelaccio's avatar
Elvis Angelaccio committed
668
    const QString desc = i18np("Compressing a file", "Compressing %1 files", totalCount);
669
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
670 671 672 673 674 675

    ReadWriteArchiveInterface *m_writeInterface =
        qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());

    Q_ASSERT(m_writeInterface);

676
    // The file paths must be relative to GlobalWorkDir.
677
    for (Archive::Entry *entry : qAsConst(m_entries)) {
678 679
        // #191821: workDir must be used instead of QDir::current()
        //          so that symlinks aren't resolved automatically
680
        const QString &fullPath = entry->fullPath();
681
        QString relativePath = workDir.relativeFilePath(fullPath);
682

683
        if (fullPath.endsWith(QLatin1Char('/'))) {
684 685 686
            relativePath += QLatin1Char('/');
        }

687
        entry->setFullPath(relativePath);
688 689
    }

690
    connectToArchiveInterfaceSignals();
691
    bool ret = m_writeInterface->addFiles(m_entries, m_destination, m_options, totalCount);
692

693
    if (!archiveInterface()->waitForFinishedSignal()) {
694
        onFinished(ret);
695
    }
696 697
}

698 699 700 701 702 703 704 705 706
void AddJob::onFinished(bool result)
{
    if (!m_oldWorkingDir.isEmpty()) {
        QDir::setCurrent(m_oldWorkingDir);
    }

    Job::onFinished(result);
}

707
MoveJob::MoveJob(const QVector<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions& options , ReadWriteArchiveInterface *interface)
708 709 710 711 712 713
    : Job(interface)
    , m_finishedSignalsCount(0)
    , m_entries(entries)
    , m_destination(destination)
    , m_options(options)
{
714
    qCDebug(ARK) << "Created job instance";
715 716 717 718
}

void MoveJob::doWork()
{
719
    qCDebug(ARK) << "Going to move" << m_entries.count() << "file(s)";
720

721 722
    QString desc = i18np("Moving a file", "Moving %1 files", m_entries.count());
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744

    ReadWriteArchiveInterface *m_writeInterface =
        qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());

    Q_ASSERT(m_writeInterface);

    connectToArchiveInterfaceSignals();
    bool ret = m_writeInterface->moveFiles(m_entries, m_destination, m_options);

    if (!archiveInterface()->waitForFinishedSignal()) {
        onFinished(ret);
    }
}

void MoveJob::onFinished(bool result)
{
    m_finishedSignalsCount++;
    if (m_finishedSignalsCount == archiveInterface()->moveRequiredSignals()) {
        Job::onFinished(result);
    }
}

745
CopyJob::CopyJob(const QVector<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options, ReadWriteArchiveInterface *interface)
746 747 748 749 750 751
    : Job(interface)
    , m_finishedSignalsCount(0)
    , m_entries(entries)
    , m_destination(destination)
    , m_options(options)
{
752
    qCDebug(ARK) << "Created job instance";
753 754 755 756
}

void CopyJob::doWork()
{
757
    qCDebug(ARK) << "Going to copy" << m_entries.count() << "file(s)";
758

759 760
    QString desc = i18np("Copying a file", "Copying %1 files", m_entries.count());
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782

    ReadWriteArchiveInterface *m_writeInterface =
        qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());

    Q_ASSERT(m_writeInterface);

    connectToArchiveInterfaceSignals();
    bool ret = m_writeInterface->copyFiles(m_entries, m_destination, m_options);

    if (!archiveInterface()->waitForFinishedSignal()) {
        onFinished(ret);
    }
}

void CopyJob::onFinished(bool result)
{
    m_finishedSignalsCount++;
    if (m_finishedSignalsCount == archiveInterface()->copyRequiredSignals()) {
        Job::onFinished(result);
    }
}

783
DeleteJob::DeleteJob(const QVector<Archive::Entry*> &entries, ReadWriteArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
784
    : Job(interface)
785
    , m_entries(entries)
786 787 788 789 790
{
}

void DeleteJob::doWork()
{
791 792
    QString desc = i18np("Deleting a file from the archive", "Deleting %1 files", m_entries.count());
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
793 794

    ReadWriteArchiveInterface *m_writeInterface =
795
        qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());
796 797 798

    Q_ASSERT(m_writeInterface);

799
    connectToArchiveInterfaceSignals();
800
    bool ret = m_writeInterface->deleteFiles(m_entries);
801

802
    if (!archiveInterface()->waitForFinishedSignal()) {
803
        onFinished(ret);
804
    }
805
}
806

Elvis Angelaccio's avatar
Elvis Angelaccio committed
807 808
CommentJob::CommentJob(const QString& comment, ReadWriteArchiveInterface *interface)
    : Job(interface)
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
    , m_comment(comment)
{
}

void CommentJob::doWork()
{
    emit description(this, i18n("Adding comment"));

    ReadWriteArchiveInterface *m_writeInterface =
        qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());

    Q_ASSERT(m_writeInterface);

    connectToArchiveInterfaceSignals();
    bool ret = m_writeInterface->addComment(m_comment);

    if (!archiveInterface()->waitForFinishedSignal()) {
        onFinished(ret);
    }
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
830 831
TestJob::TestJob(ReadOnlyArchiveInterface *interface)
    : Job(interface)
Ragnar Thomsen's avatar
Ragnar Thomsen committed
832 833 834 835 836 837
{
    m_testSuccess = false;
}

void TestJob::doWork()
{
838
    qCDebug(ARK) << "Job started";
Ragnar Thomsen's avatar
Ragnar Thomsen committed
839

840 841
    emit description(this, i18n("Testing archive"), qMakePair(i18n("Archive"), archiveInterface()->filename()));

Ragnar Thomsen's avatar
Ragnar Thomsen committed
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
    connectToArchiveInterfaceSignals();
    connect(archiveInterface(), &ReadOnlyArchiveInterface::testSuccess, this, &TestJob::onTestSuccess);

    bool ret = archiveInterface()->testArchive();

    if (!archiveInterface()->waitForFinishedSignal()) {
        onFinished(ret);
    }
}

void TestJob::onTestSuccess()
{
    m_testSuccess = true;
}

bool TestJob::testSucceeded()
{
    return m_testSuccess;
}

862
} // namespace Kerfuffle
863

Elvis Angelaccio's avatar
Elvis Angelaccio committed
864
#include "jobs.moc"