jobs.cpp 25 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
    emit newEntry(entry);
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
}

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)
{
203
    qCDebug(ARK) << "Job finished, result:" << result << ", time:" << jobTimer.elapsed() << "ms";
204

Elvis Angelaccio's avatar
Elvis Angelaccio committed
205 206 207 208
    if (archive() && !archive()->isValid()) {
        setError(KJob::UserDefinedError);
    }

209 210 211
    if (!d->isInterruptionRequested()) {
        emitResult();
    }
212 213 214 215
}

void Job::onUserQuery(Query *query)
{
216 217 218 219
    if (archiveInterface()->waitForFinishedSignal()) {
        qCWarning(ARK) << "Plugins run from the main thread should call directly query->execute()";
    }

220 221 222 223 224
    emit userQuery(query);
}

bool Job::doKill()
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
225 226 227
    const bool killed = archiveInterface()->doKill();
    if (killed) {
        return true;
228 229
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
230 231 232
    if (d->isRunning()) {
        qCDebug(ARK) << "Requesting graceful thread interruption, will abort in one second otherwise.";
        d->requestInterruption();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
233
        d->wait(1000);
234
    }
Elvis Angelaccio's avatar
Elvis Angelaccio committed
235

Elvis Angelaccio's avatar
Elvis Angelaccio committed
236
    return true;
237 238
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
239 240
LoadJob::LoadJob(Archive *archive, ReadOnlyArchiveInterface *interface)
    : Job(archive, interface)
241 242 243
    , m_isSingleFolderArchive(true)
    , m_isPasswordProtected(false)
    , m_extractedFilesSize(0)
244 245
    , m_dirCount(0)
    , m_filesCount(0)
246
{
247
    qCDebug(ARK) << "Created job instance";
Elvis Angelaccio's avatar
Elvis Angelaccio committed
248
    connect(this, &LoadJob::newEntry, this, &LoadJob::onNewEntry);
249 250
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
251
LoadJob::LoadJob(Archive *archive)
252
    : LoadJob(archive, nullptr)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
253 254 255
{}

LoadJob::LoadJob(ReadOnlyArchiveInterface *interface)
256
    : LoadJob(nullptr, interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
257 258 259
{}

void LoadJob::doWork()
260
{
261
    emit description(this, i18n("Loading archive"), qMakePair(i18n("Archive"), archiveInterface()->filename()));
262
    connectToArchiveInterfaceSignals();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
263

264
    bool ret = archiveInterface()->list();
265

266
    if (!archiveInterface()->waitForFinishedSignal()) {
267 268 269 270 271
        // 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);
        });
272
    }
273 274
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
275 276
void LoadJob::onFinished(bool result)
{
277
    if (archive() && result) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
278 279 280 281 282 283 284 285 286 287 288 289 290
        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
291 292 293 294
{
    return m_extractedFilesSize;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
295
bool LoadJob::isPasswordProtected() const
296 297 298 299
{
    return m_isPasswordProtected;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
300
bool LoadJob::isSingleFolderArchive() const
301
{
302 303 304 305
    if (m_filesCount == 1 && m_dirCount == 0) {
        return false;
    }

306 307 308
    return m_isSingleFolderArchive;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
309
void LoadJob::onNewEntry(const Archive::Entry *entry)
310
{
311 312
    m_extractedFilesSize += entry->property("size").toLongLong();
    m_isPasswordProtected |= entry->property("isPasswordProtected").toBool();
313

314
    if (entry->isDir()) {
315 316 317 318 319
        m_dirCount++;
    } else {
        m_filesCount++;
    }

320
    if (m_isSingleFolderArchive) {
Nicolas Fella's avatar
Nicolas Fella committed
321
        QString fullPath = entry->fullPath();
322
        // RPM filenames have the ./ prefix, and "." would be detected as the subfolder name, so we remove it.
Nicolas Fella's avatar
Nicolas Fella committed
323 324 325 326
        if (fullPath.startsWith(QLatin1String("./"))) {
            fullPath = fullPath.remove(0, 2);
        }

327
        const QString basePath = fullPath.split(QLatin1Char('/')).at(0);
328

329 330 331
        if (m_basePath.isEmpty()) {
            m_basePath = basePath;
            m_subfolderName = basePath;
332
        } else {
333 334 335 336
            if (m_basePath != basePath) {
                m_isSingleFolderArchive = false;
                m_subfolderName.clear();
            }
337 338 339 340
        }
    }
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
341
QString LoadJob::subfolderName() const
342
{
343 344 345 346
    if (!isSingleFolderArchive()) {
        return QString();
    }

347 348 349
    return m_subfolderName;
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
350 351 352 353 354 355 356
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)
{
357
    qCDebug(ARK) << "Created job instance";
Elvis Angelaccio's avatar
Elvis Angelaccio committed
358 359 360 361 362
}

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

365 366 367 368
    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
369

370
    // Forward LoadJob's signals.
Elvis Angelaccio's avatar
Elvis Angelaccio committed
371 372 373 374 375
    connect(m_loadJob, &Kerfuffle::Job::newEntry, this, &BatchExtractJob::newEntry);
    connect(m_loadJob, &Kerfuffle::Job::userQuery, this, &BatchExtractJob::userQuery);
    m_loadJob->start();
}

376 377 378 379 380 381 382 383 384
bool BatchExtractJob::doKill()
{
    if (m_step == Loading) {
        return m_loadJob->kill();
    }

    return m_extractJob->kill();
}

385 386 387 388 389 390 391 392 393 394 395 396 397
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
398 399 400
void BatchExtractJob::slotLoadingFinished(KJob *job)
{
    if (job->error()) {
401 402 403
        // Forward errors as well.
        onError(job->errorString(), QString());
        onFinished(false);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
404 405 406 407 408 409 410
        return;
    }

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

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

413 414 415 416
    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);
417
        connect(archiveInterface(), &ReadOnlyArchiveInterface::error, this, &BatchExtractJob::onError);
418 419 420 421 422
        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);
        }
423 424
        m_step = Extracting;
        m_extractJob->start();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
425 426 427 428 429 430 431 432 433 434
    } else {
        emitResult();
    }
}

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

435
    if (m_autoSubfolder && (archive()->hasMultipleTopLevelEntries() || isSingleFolderRPM)) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
436 437 438 439 440
        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.
441
        if (isSingleFolderRPM && subfolderName == QLatin1String("usr")) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
442 443 444 445 446
            qCDebug(ARK) << "Detected single folder RPM archive. Using archive basename as subfolder name";
            subfolderName = QFileInfo(archive()->fileName()).completeBaseName();
        }

        if (d.exists(subfolderName)) {
447
            subfolderName = KFileUtils::suggestName(QUrl::fromUserInput(m_destination, QString(), QUrl::AssumeLocalFile), subfolderName);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
448 449 450 451 452 453 454 455
        }

        d.mkdir(subfolderName);

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

456
CreateJob::CreateJob(Archive *archive, const QVector<Archive::Entry*> &entries, const CompressionOptions &options)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
457 458 459 460
    : Job(archive)
    , m_entries(entries)
    , m_options(options)
{
461
    qCDebug(ARK) << "Created job instance";
Elvis Angelaccio's avatar
Elvis Angelaccio committed
462 463 464 465 466 467 468 469 470 471 472 473 474 475
}

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

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

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

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

480 481
    if (m_addJob) {
        connect(m_addJob, &KJob::result, this, &CreateJob::emitResult);
482
        // Forward description signal from AddJob, we need to change the first argument ('this' needs to be a CreateJob).
483
        connect(m_addJob, &KJob::description, this, [=](KJob *, const QString &title, const QPair<QString,QString> &field1, const QPair<QString,QString> &) {
484 485 486
            emit description(this, title, field1);
        });

487
        m_addJob->start();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
488 489 490 491 492
    } else {
        emitResult();
    }
}

493 494 495 496 497
bool CreateJob::doKill()
{
    return m_addJob && m_addJob->kill();
}

498
ExtractJob::ExtractJob(const QVector<Archive::Entry*> &entries, const QString &destinationDir, const ExtractionOptions &options, ReadOnlyArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
499
    : Job(interface)
500
    , m_entries(entries)
501 502
    , m_destinationDir(destinationDir)
    , m_options(options)
503
{
504
    qCDebug(ARK) << "Created job instance";
505 506 507 508 509
}

void ExtractJob::doWork()
{
    QString desc;
510
    if (m_entries.count() == 0) {
511 512
        desc = i18n("Extracting all files");
    } else {
513
        desc = i18np("Extracting one file", "Extracting %1 files", m_entries.count());
514
    }
515
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()), qMakePair(i18nc("extraction folder", "Destination"), m_destinationDir));
516

517 518 519 520 521 522 523
    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;
    }

524
    connectToArchiveInterfaceSignals();
525

526
    qCDebug(ARK) << "Starting extraction with" << m_entries.count() << "selected files."
527
             << m_entries
528 529
             << "Destination dir:" << m_destinationDir
             << "Options:" << m_options;
530

531
    bool ret = archiveInterface()->extractFiles(m_entries, m_destinationDir, m_options);
532

533
    if (!archiveInterface()->waitForFinishedSignal()) {
534
        onFinished(ret);
535
    }
536 537
}

538 539 540 541 542
QString ExtractJob::destinationDirectory() const
{
    return m_destinationDir;
}

543 544 545 546 547
ExtractionOptions ExtractJob::extractionOptions() const
{
    return m_options;
}

548
TempExtractJob::TempExtractJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
549
    : Job(interface)
550
    , m_entry(entry)
551 552
    , m_passwordProtectedHint(passwordProtectedHint)
{
553
    m_tmpExtractDir = new QTemporaryDir();
554 555 556 557
}

QString TempExtractJob::validatedFilePath() const
{
558
    QString path = extractionDir() + QLatin1Char('/') + m_entry->fullPath();
559 560 561 562 563 564 565 566 567 568 569 570 571 572

    // 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) {
573
        options.setEncryptedArchiveHint(true);
574 575 576 577 578
    }

    return options;
}

579 580 581 582 583
QTemporaryDir *TempExtractJob::tempDir() const
{
    return m_tmpExtractDir;
}

584 585
void TempExtractJob::doWork()
{
586 587
    // pass 1 to i18np on purpose so this translation may properly be reused.
    emit description(this, i18np("Extracting one file", "Extracting %1 files", 1));
588 589 590

    connectToArchiveInterfaceSignals();

591
    qCDebug(ARK) << "Extracting:" << m_entry;
592

593
    bool ret = archiveInterface()->extractFiles({m_entry}, extractionDir(), extractionOptions());
594 595 596 597 598 599

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

600
QString TempExtractJob::extractionDir() const
601
{
602
    return m_tmpExtractDir->path();
603 604
}

605 606
PreviewJob::PreviewJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
    : TempExtractJob(entry, passwordProtectedHint, interface)
607
{
608
    qCDebug(ARK) << "Created job instance";
609 610
}

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

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

623
AddJob::AddJob(const QVector<Archive::Entry*> &entries, const Archive::Entry *destination, const CompressionOptions& options, ReadWriteArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
624
    : Job(interface)
625
    , m_entries(entries)
626
    , m_destination(destination)
627
    , m_options(options)
628
{
629
    qCDebug(ARK) << "Created job instance";
630 631 632 633
}

void AddJob::doWork()
{
634
    // Set current dir.
635
    const QString globalWorkDir = m_options.globalWorkDir();
636 637 638 639 640 641 642
    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);
    }

643
    // Count total number of entries to be added.
644
    uint totalCount = 0;
645 646
    QElapsedTimer timer;
    timer.start();
647
    for (const Archive::Entry* entry : qAsConst(m_entries)) {
648
        totalCount++;
649
        if (QFileInfo(entry->fullPath()).isDir()) {
650
            QDirIterator it(entry->fullPath(), QDir::AllEntries | QDir::Readable | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
651 652 653 654 655 656 657
            while (it.hasNext()) {
                it.next();
                totalCount++;
            }
        }
    }

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

Elvis Angelaccio's avatar
Elvis Angelaccio committed
660
    const QString desc = i18np("Compressing a file", "Compressing %1 files", totalCount);
661
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
662 663 664 665 666 667

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

    Q_ASSERT(m_writeInterface);

668
    // The file paths must be relative to GlobalWorkDir.
669
    for (Archive::Entry *entry : qAsConst(m_entries)) {
670 671
        // #191821: workDir must be used instead of QDir::current()
        //          so that symlinks aren't resolved automatically
672
        const QString &fullPath = entry->fullPath();
673
        QString relativePath = workDir.relativeFilePath(fullPath);
674

675
        if (fullPath.endsWith(QLatin1Char('/'))) {
676 677 678
            relativePath += QLatin1Char('/');
        }

679
        entry->setFullPath(relativePath);
680 681
    }

682
    connectToArchiveInterfaceSignals();
683
    bool ret = m_writeInterface->addFiles(m_entries, m_destination, m_options, totalCount);
684

685
    if (!archiveInterface()->waitForFinishedSignal()) {
686
        onFinished(ret);
687
    }
688 689
}

690 691 692 693 694 695 696 697 698
void AddJob::onFinished(bool result)
{
    if (!m_oldWorkingDir.isEmpty()) {
        QDir::setCurrent(m_oldWorkingDir);
    }

    Job::onFinished(result);
}

699
MoveJob::MoveJob(const QVector<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions& options , ReadWriteArchiveInterface *interface)
700 701 702 703 704 705
    : Job(interface)
    , m_finishedSignalsCount(0)
    , m_entries(entries)
    , m_destination(destination)
    , m_options(options)
{
706
    qCDebug(ARK) << "Created job instance";
707 708 709 710
}

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

713 714
    QString desc = i18np("Moving a file", "Moving %1 files", m_entries.count());
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

    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);
    }
}

737
CopyJob::CopyJob(const QVector<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options, ReadWriteArchiveInterface *interface)
738 739 740 741 742 743
    : Job(interface)
    , m_finishedSignalsCount(0)
    , m_entries(entries)
    , m_destination(destination)
    , m_options(options)
{
744
    qCDebug(ARK) << "Created job instance";
745 746 747 748
}

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

751 752
    QString desc = i18np("Copying a file", "Copying %1 files", m_entries.count());
    emit description(this, desc, qMakePair(i18n("Archive"), archiveInterface()->filename()));
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774

    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);
    }
}

775
DeleteJob::DeleteJob(const QVector<Archive::Entry*> &entries, ReadWriteArchiveInterface *interface)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
776
    : Job(interface)
777
    , m_entries(entries)
778 779 780 781 782
{
}

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

    ReadWriteArchiveInterface *m_writeInterface =
787
        qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());
788 789 790

    Q_ASSERT(m_writeInterface);

791
    connectToArchiveInterfaceSignals();
792
    bool ret = m_writeInterface->deleteFiles(m_entries);
793

794
    if (!archiveInterface()->waitForFinishedSignal()) {
795
        onFinished(ret);
796
    }
797
}
798

Elvis Angelaccio's avatar
Elvis Angelaccio committed
799 800
CommentJob::CommentJob(const QString& comment, ReadWriteArchiveInterface *interface)
    : Job(interface)
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
    , 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
822 823
TestJob::TestJob(ReadOnlyArchiveInterface *interface)
    : Job(interface)
Ragnar Thomsen's avatar
Ragnar Thomsen committed
824 825 826 827 828 829
{
    m_testSuccess = false;
}

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

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

Ragnar Thomsen's avatar
Ragnar Thomsen committed
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
    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;
}

854
} // namespace Kerfuffle
855

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