archive_kerfuffle.cpp 14.6 KB
Newer Older
1 2
/*
 * Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
3
 * Copyright (c) 2008 Harald Hvaal <haraldhv@stud.ntnu.no>
4
 * Copyright (c) 2009-2011 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 "archive_kerfuffle.h"
30
#include "ark_debug.h"
31
#include "archiveinterface.h"
32
#include "jobs.h"
33
#include "mimetypes.h"
34
#include "pluginmanager.h"
35

36
#include <KPluginFactory>
37
#include <KPluginLoader>
38

39
#include <QMimeDatabase>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
40 41
#include <QRegularExpression>

42 43 44
namespace Kerfuffle
{

45 46 47 48 49 50 51
Archive *Archive::create(const QString &fileName, QObject *parent)
{
    return create(fileName, QString(), parent);
}

Archive *Archive::create(const QString &fileName, const QString &fixedMimeType, QObject *parent)
{
52
    qCDebug(ARK) << "Going to create archive" << fileName;
53

54 55 56 57
    PluginManager pluginManager;
    const QMimeType mimeType = fixedMimeType.isEmpty() ? determineMimeType(fileName) : QMimeDatabase().mimeTypeForName(fixedMimeType);

    const QVector<Plugin*> offers = pluginManager.preferredPluginsFor(mimeType);
58
    if (offers.isEmpty()) {
59
        qCCritical(ARK) << "Could not find a plugin to handle" << fileName;
60
        return new Archive(NoPlugin, parent);
61 62
    }

63
    Archive *archive = nullptr;
64
    for (Plugin *plugin : offers) {
65
        archive = create(fileName, plugin, parent);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
66 67 68 69 70
        // Use the first valid plugin, according to the priority sorting.
        if (archive->isValid()) {
            return archive;
        }
    }
71

Elvis Angelaccio's avatar
Elvis Angelaccio committed
72 73 74
    qCCritical(ARK) << "Failed to find a usable plugin for" << fileName;
    return archive;
}
75

76
Archive *Archive::create(const QString &fileName, Plugin *plugin, QObject *parent)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
77
{
78 79 80
    Q_ASSERT(plugin);

    qCDebug(ARK) << "Checking plugin" << plugin->metaData().pluginId();
81

82
    KPluginFactory *factory = KPluginLoader(plugin->metaData().fileName()).factory();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
83
    if (!factory) {
84
        qCWarning(ARK) << "Invalid plugin factory for" << plugin->metaData().pluginId();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
85 86
        return new Archive(FailedPlugin, parent);
    }
87

Ragnar Thomsen's avatar
Ragnar Thomsen committed
88 89
    const QVariantList args = {QVariant(QFileInfo(fileName).absoluteFilePath()),
                               QVariant().fromValue(plugin->metaData())};
90
    ReadOnlyArchiveInterface *iface = factory->create<ReadOnlyArchiveInterface>(nullptr, args);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
91
    if (!iface) {
92
        qCWarning(ARK) << "Could not create plugin instance" << plugin->metaData().pluginId();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
93
        return new Archive(FailedPlugin, parent);
94 95
    }

96 97 98
    if (!plugin->isValid()) {
        qCDebug(ARK) << "Cannot use plugin" << plugin->metaData().pluginId() << "- check whether" << plugin->readOnlyExecutables() << "are installed.";
        return new Archive(FailedPlugin, parent);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
99 100
    }

101 102
    qCDebug(ARK) << "Successfully loaded plugin" << plugin->metaData().pluginId();
    return new Archive(iface, !plugin->isReadWrite(), parent);
103 104
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
105 106 107 108 109 110 111 112
BatchExtractJob *Archive::batchExtract(const QString &fileName, const QString &destination, bool autoSubfolder, bool preservePaths, QObject *parent)
{
    auto loadJob = load(fileName, parent);
    auto batchJob = new BatchExtractJob(loadJob, destination, autoSubfolder, preservePaths);

    return batchJob;
}

113
CreateJob *Archive::create(const QString &fileName, const QString &mimeType, const QVector<Archive::Entry*> &entries, const CompressionOptions &options, QObject *parent)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
{
    auto archive = create(fileName, mimeType, parent);
    auto createJob = new CreateJob(archive, entries, options);

    return createJob;
}

Archive *Archive::createEmpty(const QString &fileName, const QString &mimeType, QObject *parent)
{
    auto archive = create(fileName, mimeType, parent);
    Q_ASSERT(archive->isEmpty());

    return archive;
}

LoadJob *Archive::load(const QString &fileName, QObject *parent)
{
    return load(fileName, QString(), parent);
}

LoadJob *Archive::load(const QString &fileName, const QString &mimeType, QObject *parent)
{
    auto archive = create(fileName, mimeType, parent);
    auto loadJob = new LoadJob(archive);

    return loadJob;
}

LoadJob *Archive::load(const QString &fileName, Plugin *plugin, QObject *parent)
{
    auto archive = create(fileName, plugin, parent);
    auto loadJob = new LoadJob(archive);

    return loadJob;
}

150 151
Archive::Archive(ArchiveError errorCode, QObject *parent)
        : QObject(parent)
152
        , m_iface(nullptr)
153 154 155
        , m_error(errorCode)
{
    qCDebug(ARK) << "Created archive instance with error";
156 157
}

158
Archive::Archive(ReadOnlyArchiveInterface *archiveInterface, bool isReadOnly, QObject *parent)
159 160 161
        : QObject(parent)
        , m_iface(archiveInterface)
        , m_isReadOnly(isReadOnly)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
162
        , m_isSingleFolder(false)
Ragnar Thomsen's avatar
Ragnar Thomsen committed
163
        , m_isMultiVolume(false)
164
        , m_extractedFilesSize(0)
165
        , m_error(NoError)
166
        , m_encryptionType(Unencrypted)
167
{
168
    qCDebug(ARK) << "Created archive instance";
169

Elvis Angelaccio's avatar
Elvis Angelaccio committed
170 171
    Q_ASSERT(m_iface);
    m_iface->setParent(this);
172

Ragnar Thomsen's avatar
Ragnar Thomsen committed
173
    connect(m_iface, &ReadOnlyArchiveInterface::compressionMethodFound, this, &Archive::onCompressionMethodFound);
174
    connect(m_iface, &ReadOnlyArchiveInterface::encryptionMethodFound, this, &Archive::onEncryptionMethodFound);
175 176
}

177
void Archive::onCompressionMethodFound(const QString &method)
Ragnar Thomsen's avatar
Ragnar Thomsen committed
178
{
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    QStringList methods = property("compressionMethods").toStringList();

    if (!methods.contains(method) &&
        method != QLatin1String("Store")) {
        methods.append(method);
    }
    methods.sort();

    setProperty("compressionMethods", methods);
}

void Archive::onEncryptionMethodFound(const QString &method)
{
    QStringList methods = property("encryptionMethods").toStringList();

    if (!methods.contains(method)) {
        methods.append(method);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
196
    }
197 198 199
    methods.sort();

    setProperty("encryptionMethods", methods);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
200
}
Ragnar Thomsen's avatar
Ragnar Thomsen committed
201

202 203 204 205
Archive::~Archive()
{
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
206 207
QString Archive::completeBaseName() const
{
208
    const QString suffix = QFileInfo(fileName()).suffix();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
209 210 211 212 213
    QString base = QFileInfo(fileName()).completeBaseName();

    // Special case for compressed tar archives.
    if (base.right(4).toUpper() == QLatin1String(".TAR")) {
        base.chop(4);
214 215 216 217 218 219 220 221 222 223 224 225

    // Multi-volume 7z's are named name.7z.001.
    } else if (base.right(3).toUpper() == QLatin1String(".7Z")) {
        base.chop(3);

    // Multi-volume zip's are named name.zip.001.
    } else if (base.right(4).toUpper() == QLatin1String(".ZIP")) {
        base.chop(4);

    // For multivolume rar's we want to remove the ".partNNN" suffix.
    } else if (suffix.toUpper() == QLatin1String("RAR")) {
        base.remove(QRegularExpression(QStringLiteral("\\.part[0-9]{1,3}$")));
Elvis Angelaccio's avatar
Elvis Angelaccio committed
226 227 228 229 230
    }

    return base;
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
231
QString Archive::fileName() const
232
{
233
    return isValid() ? m_iface->filename() : QString();
234 235
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
236 237
QString Archive::comment() const
{
238
    return isValid() ? m_iface->comment() : QString();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
239 240
}

241 242 243
CommentJob* Archive::addComment(const QString &comment)
{
    if (!isValid()) {
244
        return nullptr;
245 246 247 248
    }

    qCDebug(ARK) << "Going to add comment:" << comment;
    Q_ASSERT(!isReadOnly());
Elvis Angelaccio's avatar
Elvis Angelaccio committed
249
    CommentJob *job = new CommentJob(comment, static_cast<ReadWriteArchiveInterface*>(m_iface));
250 251 252
    return job;
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
253 254 255
TestJob* Archive::testArchive()
{
    if (!isValid()) {
256
        return nullptr;
Ragnar Thomsen's avatar
Ragnar Thomsen committed
257 258 259 260
    }

    qCDebug(ARK) << "Going to test archive";

Elvis Angelaccio's avatar
Elvis Angelaccio committed
261
    TestJob *job = new TestJob(m_iface);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
262 263 264
    return job;
}

265
QMimeType Archive::mimeType()
266
{
267 268 269 270 271 272 273 274 275
    if (!isValid()) {
        return QMimeType();
    }

    if (!m_mimeType.isValid()) {
        m_mimeType = determineMimeType(fileName());
    }

    return m_mimeType;
276 277
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
278 279
bool Archive::isEmpty() const
{
280
    return (numberOfEntries() == 0);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
281 282 283
}

bool Archive::isReadOnly() const
284
{
285
    return isValid() ? (m_iface->isReadOnly() || m_isReadOnly ||
286
                        (isMultiVolume() && (numberOfEntries() > 0))) : false;
287 288
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
289 290 291 292 293 294
bool Archive::isSingleFile() const
{
    // If the only entry is a folder, isSingleFolder() is true.
    return numberOfEntries() == 1 && !isSingleFolder();
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
295
bool Archive::isSingleFolder() const
Elvis Angelaccio's avatar
Elvis Angelaccio committed
296
{
297 298 299 300
    if (!isValid()) {
        return false;
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
301
    return m_isSingleFolder;
Elvis Angelaccio's avatar
Elvis Angelaccio committed
302 303
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
304 305
bool Archive::hasComment() const
{
306
    return isValid() ? !comment().isEmpty() : false;
Ragnar Thomsen's avatar
Ragnar Thomsen committed
307 308
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
309
bool Archive::isMultiVolume() const
310
{
311 312 313
    if (!isValid()) {
        return false;
    }
Elvis Angelaccio's avatar
Elvis Angelaccio committed
314

315 316 317
    return m_iface->isMultiVolume();
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
318 319 320 321 322
void Archive::setMultiVolume(bool value)
{
    m_iface->setMultiVolume(value);
}

323 324 325 326 327
int Archive::numberOfVolumes() const
{
    return m_iface->numberOfVolumes();
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
328
Archive::EncryptionType Archive::encryptionType() const
329
{
330 331 332 333
    if (!isValid()) {
        return Unencrypted;
    }

334 335 336
    return m_encryptionType;
}

337 338 339 340 341
QString Archive::password() const
{
    return m_iface->password();
}

342
uint Archive::numberOfEntries() const
Ragnar Thomsen's avatar
Ragnar Thomsen committed
343
{
344 345 346 347
    if (!isValid()) {
        return 0;
    }

348
    return m_iface->numberOfEntries();
349 350
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
351
qulonglong Archive::unpackedSize() const
Ragnar Thomsen's avatar
Ragnar Thomsen committed
352
{
353 354 355 356
    if (!isValid()) {
        return 0;
    }

Ragnar Thomsen's avatar
Ragnar Thomsen committed
357 358 359 360 361
    return m_extractedFilesSize;
}

qulonglong Archive::packedSize() const
{
362
    return isValid() ? static_cast<qulonglong>(QFileInfo(fileName()).size()) : 0;
Ragnar Thomsen's avatar
Ragnar Thomsen committed
363 364
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
365
QString Archive::subfolderName() const
Elvis Angelaccio's avatar
Elvis Angelaccio committed
366
{
367 368 369 370
    if (!isValid()) {
        return QString();
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
371 372 373
    return m_subfolderName;
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
374 375
bool Archive::isValid() const
{
376
    return m_iface && (m_error == NoError);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
377 378 379 380 381 382 383
}

ArchiveError Archive::error() const
{
    return m_error;
}

384
DeleteJob* Archive::deleteFiles(QVector<Archive::Entry*> &entries)
385
{
386
    if (!isValid()) {
387
        return nullptr;
388 389
    }

390
    qCDebug(ARK) << "Going to delete" << entries.size() << "entries";
391

392
    if (m_iface->isReadOnly()) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
393
        return nullptr;
394
    }
395
    DeleteJob *newJob = new DeleteJob(entries, static_cast<ReadWriteArchiveInterface*>(m_iface));
396 397 398 399

    return newJob;
}

400
AddJob* Archive::addFiles(const QVector<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options)
401
{
402
    if (!isValid()) {
403
        return nullptr;
404 405
    }

406 407
    CompressionOptions newOptions = options;
    if (encryptionType() != Unencrypted) {
408
        newOptions.setEncryptedArchiveHint(true);
409 410 411
    }

    qCDebug(ARK) << "Going to add files" << files << "with options" << newOptions;
412
    Q_ASSERT(!m_iface->isReadOnly());
Elvis Angelaccio's avatar
Elvis Angelaccio committed
413

414
    AddJob *newJob = new AddJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
Laurent Montel's avatar
Laurent Montel committed
415
    connect(newJob, &AddJob::result, this, &Archive::onAddFinished);
416 417 418
    return newJob;
}

419
MoveJob* Archive::moveFiles(const QVector<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options)
420 421
{
    if (!isValid()) {
422
        return nullptr;
423 424 425 426
    }

    CompressionOptions newOptions = options;
    if (encryptionType() != Unencrypted) {
427
        newOptions.setEncryptedArchiveHint(true);
428 429
    }

430
    qCDebug(ARK) << "Going to move files" << files << "to destination" << destination << "with options" << newOptions;
431 432 433 434 435 436
    Q_ASSERT(!m_iface->isReadOnly());

    MoveJob *newJob = new MoveJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
    return newJob;
}

437
CopyJob* Archive::copyFiles(const QVector<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
438 439
{
    if (!isValid()) {
440
        return nullptr;
441 442 443 444
    }

    CompressionOptions newOptions = options;
    if (encryptionType() != Unencrypted) {
445
        newOptions.setEncryptedArchiveHint(true);
446 447 448 449 450 451 452 453 454
    }

    qCDebug(ARK) << "Going to copy files" << files << "with options" << newOptions;
    Q_ASSERT(!m_iface->isReadOnly());

    CopyJob *newJob = new CopyJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
    return newJob;
}

Laurent Montel's avatar
Laurent Montel committed
455
ExtractJob* Archive::extractFiles(const QVector<Archive::Entry*> &files, const QString &destinationDir, ExtractionOptions options)
456
{
457
    if (!isValid()) {
458
        return nullptr;
459 460
    }

461
    ExtractionOptions newOptions = options;
462
    if (encryptionType() != Unencrypted) {
463
        newOptions.setEncryptedArchiveHint(true);
464 465
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
466
    ExtractJob *newJob = new ExtractJob(files, destinationDir, newOptions, m_iface);
467 468 469
    return newJob;
}

470
PreviewJob *Archive::preview(Archive::Entry *entry)
471 472
{
    if (!isValid()) {
473
        return nullptr;
474 475
    }

476
    PreviewJob *job = new PreviewJob(entry, (encryptionType() != Unencrypted), m_iface);
477 478 479
    return job;
}

480
OpenJob *Archive::open(Archive::Entry *entry)
481 482
{
    if (!isValid()) {
483
        return nullptr;
484 485
    }

486
    OpenJob *job = new OpenJob(entry, (encryptionType() != Unencrypted), m_iface);
487 488 489
    return job;
}

490
OpenWithJob *Archive::openWith(Archive::Entry *entry)
491 492
{
    if (!isValid()) {
493
        return nullptr;
494 495
    }

496
    OpenWithJob *job = new OpenWithJob(entry, (encryptionType() != Unencrypted), m_iface);
497 498 499
    return job;
}

500 501
void Archive::encrypt(const QString &password, bool encryptHeader)
{
502 503 504 505
    if (!isValid()) {
        return;
    }

506 507 508 509 510
    m_iface->setPassword(password);
    m_iface->setHeaderEncryptionEnabled(encryptHeader);
    m_encryptionType = encryptHeader ? HeaderEncrypted : Encrypted;
}

511 512 513 514 515 516 517 518
void Archive::onAddFinished(KJob* job)
{
    //if the archive was previously a single folder archive and an add job
    //has successfully finished, then it is no longer a single folder
    //archive (for the current implementation, which does not allow adding
    //folders/files other places than the root.
    //TODO: handle the case of creating a new file and singlefolderarchive
    //then.
Elvis Angelaccio's avatar
Elvis Angelaccio committed
519 520
    if (m_isSingleFolder && !job->error()) {
        m_isSingleFolder = false;
521 522 523 524 525 526 527 528
    }
}

void Archive::onUserQuery(Query* query)
{
    query->execute();
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
529 530 531 532 533
QString Archive::multiVolumeName() const
{
    return m_iface->multiVolumeName();
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
534 535 536 537 538
ReadOnlyArchiveInterface *Archive::interface()
{
    return m_iface;
}

539 540 541 542 543
bool Archive::hasMultipleTopLevelEntries() const
{
    return !isSingleFile() && !isSingleFolder();
}

544
} // namespace Kerfuffle