archive_kerfuffle.cpp 11.9 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 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * 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.
 */
27

28
#include "archive_kerfuffle.h"
29
#include "ark_debug.h"
30
#include "archiveinterface.h"
31
#include "jobs.h"
32
#include "mimetypes.h"
33
#include "pluginmanager.h"
34

35
#include <QByteArray>
Andrius Štikonas's avatar
Andrius Štikonas committed
36
#include <QDebug>
37
#include <QEventLoop>
38
#include <QFile>
39
#include <QFileInfo>
40
#include <QMimeDatabase>
41

42
#include <KPluginFactory>
43
#include <KPluginLoader>
44

45 46 47
namespace Kerfuffle
{

48 49 50 51 52 53
QDebug operator<<(QDebug d, const fileRootNodePair &pair)
{
    d.nospace() << "fileRootNodePair(" << pair.file << "," << pair.rootNode << ")";
    return d.space();
}

54 55 56 57 58 59 60
Archive *Archive::create(const QString &fileName, QObject *parent)
{
    return create(fileName, QString(), parent);
}

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

63 64
    qRegisterMetaType<ArchiveEntry>("ArchiveEntry");

65 66 67 68
    PluginManager pluginManager;
    const QMimeType mimeType = fixedMimeType.isEmpty() ? determineMimeType(fileName) : QMimeDatabase().mimeTypeForName(fixedMimeType);

    const QVector<Plugin*> offers = pluginManager.preferredPluginsFor(mimeType);
69
    if (offers.isEmpty()) {
70
        qCCritical(ARK) << "Could not find a plugin to handle" << fileName;
71
        return new Archive(NoPlugin, parent);
72 73
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
74
    Archive *archive;
75 76
    foreach (Plugin *plugin, offers) {
        archive = create(fileName, plugin, parent);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
77 78 79 80 81
        // Use the first valid plugin, according to the priority sorting.
        if (archive->isValid()) {
            return archive;
        }
    }
82

Elvis Angelaccio's avatar
Elvis Angelaccio committed
83 84 85
    qCCritical(ARK) << "Failed to find a usable plugin for" << fileName;
    return archive;
}
86

87
Archive *Archive::create(const QString &fileName, Plugin *plugin, QObject *parent)
Elvis Angelaccio's avatar
Elvis Angelaccio committed
88
{
89 90 91
    Q_ASSERT(plugin);

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

93
    KPluginFactory *factory = KPluginLoader(plugin->metaData().fileName()).factory();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
94
    if (!factory) {
95
        qCWarning(ARK) << "Invalid plugin factory for" << plugin->metaData().pluginId();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
96 97
        return new Archive(FailedPlugin, parent);
    }
98

Elvis Angelaccio's avatar
Elvis Angelaccio committed
99 100 101
    const QVariantList args = {QVariant(QFileInfo(fileName).absoluteFilePath())};
    ReadOnlyArchiveInterface *iface = factory->create<ReadOnlyArchiveInterface>(Q_NULLPTR, args);
    if (!iface) {
102
        qCWarning(ARK) << "Could not create plugin instance" << plugin->metaData().pluginId();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
103
        return new Archive(FailedPlugin, parent);
104 105
    }

106 107 108
    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
109 110
    }

111 112
    qCDebug(ARK) << "Successfully loaded plugin" << plugin->metaData().pluginId();
    return new Archive(iface, !plugin->isReadWrite(), parent);
113 114 115 116
}

Archive::Archive(ArchiveError errorCode, QObject *parent)
        : QObject(parent)
117
        , m_iface(Q_NULLPTR)
118 119 120
        , m_error(errorCode)
{
    qCDebug(ARK) << "Created archive instance with error";
121 122
}

123
Archive::Archive(ReadOnlyArchiveInterface *archiveInterface, bool isReadOnly, QObject *parent)
124 125 126 127 128
        : QObject(parent)
        , m_iface(archiveInterface)
        , m_hasBeenListed(false)
        , m_isReadOnly(isReadOnly)
        , m_isSingleFolderArchive(false)
129
        , m_extractedFilesSize(0)
130
        , m_error(NoError)
131
        , m_encryptionType(Unencrypted)
Ragnar Thomsen's avatar
Ragnar Thomsen committed
132
        , m_numberOfFiles(0)
133
{
134
    qCDebug(ARK) << "Created archive instance";
135

136 137
    Q_ASSERT(archiveInterface);
    archiveInterface->setParent(this);
138 139 140

    QMetaType::registerComparators<fileRootNodePair>();
    QMetaType::registerDebugStreamOperator<fileRootNodePair>();
Ragnar Thomsen's avatar
Ragnar Thomsen committed
141 142

    connect(m_iface, &ReadOnlyArchiveInterface::entry, this, &Archive::onNewEntry);
143 144
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
145

146 147 148 149
Archive::~Archive()
{
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
150 151 152 153 154 155 156 157 158 159 160 161
QString Archive::completeBaseName() const
{
    QString base = QFileInfo(fileName()).completeBaseName();

    // Special case for compressed tar archives.
    if (base.right(4).toUpper() == QLatin1String(".TAR")) {
        base.chop(4);
    }

    return base;
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
162
QString Archive::fileName() const
163
{
164
    return isValid() ? m_iface->filename() : QString();
165 166
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
167 168
QString Archive::comment() const
{
169
    return isValid() ? m_iface->comment() : QString();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
170 171
}

172 173 174 175 176 177 178 179
CommentJob* Archive::addComment(const QString &comment)
{
    if (!isValid()) {
        return Q_NULLPTR;
    }

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

Ragnar Thomsen's avatar
Ragnar Thomsen committed
184 185 186 187 188 189 190 191
TestJob* Archive::testArchive()
{
    if (!isValid()) {
        return Q_NULLPTR;
    }

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

Elvis Angelaccio's avatar
Elvis Angelaccio committed
192
    TestJob *job = new TestJob(m_iface);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
193 194 195
    return job;
}

196
QMimeType Archive::mimeType()
197
{
198 199 200 201 202 203 204 205 206
    if (!isValid()) {
        return QMimeType();
    }

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

    return m_mimeType;
207 208
}

209 210
bool Archive::isReadOnly() const
{
211
    return isValid() ? (m_iface->isReadOnly() || m_isReadOnly) : false;
212 213
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
214 215
bool Archive::isSingleFolderArchive()
{
216 217 218 219
    if (!isValid()) {
        return false;
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
220 221 222 223
    listIfNotListed();
    return m_isSingleFolderArchive;
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
224 225
bool Archive::hasComment() const
{
226
    return isValid() ? !comment().isEmpty() : false;
Ragnar Thomsen's avatar
Ragnar Thomsen committed
227 228
}

229 230
Archive::EncryptionType Archive::encryptionType()
{
231 232 233 234
    if (!isValid()) {
        return Unencrypted;
    }

235 236 237 238
    listIfNotListed();
    return m_encryptionType;
}

239
qulonglong Archive::numberOfFiles()
Ragnar Thomsen's avatar
Ragnar Thomsen committed
240
{
241 242 243 244
    if (!isValid()) {
        return 0;
    }

245
    listIfNotListed();
Ragnar Thomsen's avatar
Ragnar Thomsen committed
246 247 248
    return m_numberOfFiles;
}

249
qulonglong Archive::unpackedSize()
Ragnar Thomsen's avatar
Ragnar Thomsen committed
250
{
251 252 253 254
    if (!isValid()) {
        return 0;
    }

255
    listIfNotListed();
Ragnar Thomsen's avatar
Ragnar Thomsen committed
256 257 258 259 260
    return m_extractedFilesSize;
}

qulonglong Archive::packedSize() const
{
261
    return isValid() ? QFileInfo(fileName()).size() : 0;
Ragnar Thomsen's avatar
Ragnar Thomsen committed
262 263
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
264 265
QString Archive::subfolderName()
{
266 267 268 269
    if (!isValid()) {
        return QString();
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
270 271 272 273
    listIfNotListed();
    return m_subfolderName;
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
274 275 276 277 278 279 280 281 282
void Archive::onNewEntry(const ArchiveEntry &entry)
{
    if (!entry[IsDirectory].toBool()) {
        m_numberOfFiles++;
    }
}

bool Archive::isValid() const
{
283
    return m_iface && (m_error == NoError);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
284 285 286 287 288 289 290
}

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

291 292 293 294 295 296 297 298 299 300 301 302
KJob* Archive::open()
{
    return 0;
}

KJob* Archive::create()
{
    return 0;
}

ListJob* Archive::list()
{
303
    if (!isValid() || !QFileInfo::exists(fileName())) {
304 305 306
        return Q_NULLPTR;
    }

307
    qCDebug(ARK) << "Going to list files";
308

Elvis Angelaccio's avatar
Elvis Angelaccio committed
309
    ListJob *job = new ListJob(m_iface);
310 311 312 313

    //if this job has not been listed before, we grab the opportunity to
    //collect some information about the archive
    if (!m_hasBeenListed) {
Laurent Montel's avatar
Laurent Montel committed
314
        connect(job, &ListJob::result, this, &Archive::onListFinished);
315 316 317 318 319 320
    }
    return job;
}

DeleteJob* Archive::deleteFiles(const QList<QVariant> & files)
{
321 322 323 324
    if (!isValid()) {
        return Q_NULLPTR;
    }

325
    qCDebug(ARK) << "Going to delete files" << files;
326

327 328 329
    if (m_iface->isReadOnly()) {
        return 0;
    }
Elvis Angelaccio's avatar
Elvis Angelaccio committed
330
    DeleteJob *newJob = new DeleteJob(files, static_cast<ReadWriteArchiveInterface*>(m_iface));
331 332 333 334 335 336

    return newJob;
}

AddJob* Archive::addFiles(const QStringList & files, const CompressionOptions& options)
{
337 338 339 340
    if (!isValid()) {
        return Q_NULLPTR;
    }

341 342 343 344 345 346
    CompressionOptions newOptions = options;
    if (encryptionType() != Unencrypted) {
        newOptions[QStringLiteral("PasswordProtectedHint")] = true;
    }

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

    AddJob *newJob = new AddJob(files, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
Laurent Montel's avatar
Laurent Montel committed
350
    connect(newJob, &AddJob::result, this, &Archive::onAddFinished);
351 352 353
    return newJob;
}

354
ExtractJob* Archive::copyFiles(const QList<QVariant>& files, const QString& destinationDir, const ExtractionOptions& options)
355
{
356 357 358 359
    if (!isValid()) {
        return Q_NULLPTR;
    }

360
    ExtractionOptions newOptions = options;
361
    if (encryptionType() != Unencrypted) {
362
        newOptions[QStringLiteral( "PasswordProtectedHint" )] = true;
363 364
    }

Elvis Angelaccio's avatar
Elvis Angelaccio committed
365
    ExtractJob *newJob = new ExtractJob(files, destinationDir, newOptions, m_iface);
366 367 368
    return newJob;
}

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
PreviewJob *Archive::preview(const QString &file)
{
    if (!isValid()) {
        return Q_NULLPTR;
    }

    PreviewJob *job = new PreviewJob(file, (encryptionType() != Unencrypted), m_iface);
    return job;
}

OpenJob *Archive::open(const QString &file)
{
    if (!isValid()) {
        return Q_NULLPTR;
    }

    OpenJob *job = new OpenJob(file, (encryptionType() != Unencrypted), m_iface);
    return job;
}

OpenWithJob *Archive::openWith(const QString &file)
{
    if (!isValid()) {
        return Q_NULLPTR;
    }

    OpenWithJob *job = new OpenWithJob(file, (encryptionType() != Unencrypted), m_iface);
    return job;
}

399 400
void Archive::encrypt(const QString &password, bool encryptHeader)
{
401 402 403 404
    if (!isValid()) {
        return;
    }

405 406 407 408 409
    m_iface->setPassword(password);
    m_iface->setHeaderEncryptionEnabled(encryptHeader);
    m_encryptionType = encryptHeader ? HeaderEncrypted : Encrypted;
}

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
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.
    if (m_isSingleFolderArchive && !job->error()) {
        m_isSingleFolderArchive = false;
    }
}

void Archive::onListFinished(KJob* job)
{
    ListJob *ljob = qobject_cast<ListJob*>(job);
    m_extractedFilesSize = ljob->extractedFilesSize();
    m_isSingleFolderArchive = ljob->isSingleFolderArchive();
    m_subfolderName = ljob->subfolderName();
    if (m_subfolderName.isEmpty()) {
430
        m_subfolderName = completeBaseName();
431 432
    }

433 434 435 436 437
    if (ljob->isPasswordProtected()) {
        // If we already know the password, it means that the archive is header-encrypted.
        m_encryptionType = m_iface->password().isEmpty() ? Encrypted : HeaderEncrypted;
    }

438 439 440 441 442 443
    m_hasBeenListed = true;
}

void Archive::listIfNotListed()
{
    if (!m_hasBeenListed) {
Laurent Montel's avatar
Laurent Montel committed
444
        ListJob *job = list();
445 446 447
        if (!job) {
            return;
        }
448

Laurent Montel's avatar
Laurent Montel committed
449
        connect(job, &ListJob::userQuery, this, &Archive::onUserQuery);
450 451 452

        QEventLoop loop(this);

Laurent Montel's avatar
Laurent Montel committed
453
        connect(job, &KJob::result, &loop, &QEventLoop::quit);
454 455 456 457 458 459 460 461 462 463
        job->start();
        loop.exec(); // krazy:exclude=crashy
    }
}

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

464
} // namespace Kerfuffle