archivemodel.cpp 29.9 KB
Newer Older
Henrique Pinto's avatar
Henrique Pinto committed
1 2 3 4
/*
 * ark -- archiver for the KDE project
 *
 * Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
5
 * Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
6
 * Copyright (C) 2010-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
7
 * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
Henrique Pinto's avatar
Henrique Pinto committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */
24

25
#include "archivemodel.h"
26
#include "ark_debug.h"
Elvis Angelaccio's avatar
Elvis Angelaccio committed
27
#include "jobs.h"
28

Elvis Angelaccio's avatar
Elvis Angelaccio committed
29
#include <KIO/Global>
30
#include <KLocalizedString>
31

32
#include <QApplication>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
33
#include <QDBusConnection>
34
#include <QMimeData>
35
#include <QMimeDatabase>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
36
#include <QRegularExpression>
37
#include <QStyle>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
38
#include <QUrl>
39

40 41
using namespace Kerfuffle;

42
// Used to speed up the loading of large archives.
43
static Archive::Entry *s_previousMatch = nullptr;
44
Q_GLOBAL_STATIC(QStringList, s_previousPieces)
45

46 47 48
ArchiveModel::ArchiveModel(const QString &dbusPathName, QObject *parent)
    : QAbstractItemModel(parent)
    , m_dbusPathName(dbusPathName)
49 50
    , m_numberOfFiles(0)
    , m_numberOfFolders(0)
51
    , m_fileEntryListed(false)
52
{
53
    initRootEntry();
54 55 56 57 58 59 60 61 62 63 64

    // Mappings between column indexes and entry properties.
    m_propertiesMap = {
        { FullPath, "fullPath" },
        { Size, "size" },
        { CompressedSize, "compressedSize" },
        { Permissions, "permissions" },
        { Owner, "owner" },
        { Group, "group" },
        { Ratio, "ratio" },
        { CRC, "CRC" },
65
        { BLAKE2, "BLAKE2" },
66 67 68 69
        { Method, "method" },
        { Version, "version" },
        { Timestamp, "timestamp" },
    };
70 71 72 73
}

ArchiveModel::~ArchiveModel()
{
74 75 76 77 78
}

QVariant ArchiveModel::data(const QModelIndex &index, int role) const
{
    if (index.isValid()) {
79
        Archive::Entry *entry = static_cast<Archive::Entry*>(index.internalPointer());
80 81
        switch (role) {
        case Qt::DisplayRole: {
82
            // TODO: complete the columns.
83 84
            int column = m_showColumns.at(index.column());
            switch (column) {
85
            case FullPath:
86
                return entry->name();
87
            case Size:
88
                if (entry->isDir()) {
89 90 91 92
                    uint dirs;
                    uint files;
                    entry->countChildren(dirs, files);
                    return KIO::itemsSummaryString(dirs + files, files, dirs, 0, false);
93
                } else if (!entry->property("link").toString().isEmpty()) {
94 95
                    return QVariant();
                } else {
96
                    return KIO::convertSize(entry->property("size").toULongLong());
97 98
                }
            case CompressedSize:
99
                if (entry->isDir() || !entry->property("link").toString().isEmpty()) {
100 101
                    return QVariant();
                } else {
102
                    qulonglong compressedSize = entry->property("compressedSize").toULongLong();
103 104 105 106 107 108
                    if (compressedSize != 0) {
                        return KIO::convertSize(compressedSize);
                    } else {
                        return QVariant();
                    }
                }
109
            case Ratio: // TODO: Use entry->metaData()[Ratio] when available.
110
                if (entry->isDir() || !entry->property("link").toString().isEmpty()) {
111 112
                    return QVariant();
                } else {
113 114
                    qulonglong compressedSize = entry->property("compressedSize").toULongLong();
                    qulonglong size = entry->property("size").toULongLong();
115 116 117 118
                    if (compressedSize == 0 || size == 0) {
                        return QVariant();
                    } else {
                        int ratio = int(100 * ((double)size - compressedSize) / size);
119
                        return QString(QString::number(ratio) + QStringLiteral(" %"));
120 121 122 123
                    }
                }

            case Timestamp: {
124
                const QDateTime timeStamp = entry->property("timestamp").toDateTime();
125
                return QLocale().toString(timeStamp, QLocale::ShortFormat);
126 127 128
            }

            default:
129
                return entry->property(m_propertiesMap[column].constData());
130 131 132 133
            }
        }
        case Qt::DecorationRole:
            if (index.column() == 0) {
Nicolas Fella's avatar
Nicolas Fella committed
134
                Archive::Entry *e = static_cast<Archive::Entry*>(index.internalPointer());
135
                QIcon::Mode mode = (filesToMove.contains(e->fullPath())) ? QIcon::Disabled : QIcon::Normal;
Nicolas Fella's avatar
Nicolas Fella committed
136
                return e->icon().pixmap(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize), mode);
137 138 139 140
            }
            return QVariant();
        case Qt::FontRole: {
            QFont f;
141
            f.setItalic(entry->property("isPasswordProtected").toBool());
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
            return f;
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}

Qt::ItemFlags ArchiveModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);

    if (index.isValid()) {
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags;
    }

159
    return Qt::NoItemFlags;
160 161 162 163 164 165
}

QVariant ArchiveModel::headerData(int section, Qt::Orientation, int role) const
{
    if (role == Qt::DisplayRole) {
        if (section >= m_showColumns.size()) {
166
            qCDebug(ARK) << "WEIRD: showColumns.size = " << m_showColumns.size()
167
            << " and section = " << section;
168 169 170 171 172 173
            return QVariant();
        }

        int columnId = m_showColumns.at(section);

        switch (columnId) {
174
        case FullPath:
175 176 177 178 179 180 181 182 183 184 185 186 187 188
            return i18nc("Name of a file inside an archive", "Name");
        case Size:
            return i18nc("Uncompressed size of a file inside an archive", "Size");
        case CompressedSize:
            return i18nc("Compressed size of a file inside an archive", "Compressed");
        case Ratio:
            return i18nc("Compression rate of file", "Rate");
        case Owner:
            return i18nc("File's owner username", "Owner");
        case Group:
            return i18nc("File's group", "Group");
        case Permissions:
            return i18nc("File permissions", "Mode");
        case CRC:
189 190 191
            return i18nc("CRC hash code", "CRC checksum");
        case BLAKE2:
            return i18nc("BLAKE2 hash code", "BLAKE2 checksum");
192 193 194
        case Method:
            return i18nc("Compression method", "Method");
        case Version:
195
            // TODO: what exactly is a file version?
196 197 198 199 200 201 202 203 204 205 206 207 208
            return i18nc("File version", "Version");
        case Timestamp:
            return i18nc("Timestamp", "Date");
        default:
            return i18nc("Unnamed column", "??");
        }
    }
    return QVariant();
}

QModelIndex ArchiveModel::index(int row, int column, const QModelIndex &parent) const
{
    if (hasIndex(row, column, parent)) {
209 210
        const Archive::Entry *parentEntry = parent.isValid()
                                            ? static_cast<Archive::Entry*>(parent.internalPointer())
211
                                            : m_rootEntry.data();
212

213
        Q_ASSERT(parentEntry->isDir());
214

215 216
        const Archive::Entry *item = parentEntry->entries().value(row, nullptr);
        if (item != nullptr) {
217
            return createIndex(row, column, const_cast<Archive::Entry*>(item));
218 219 220 221
        }
    }

    return QModelIndex();
222 223
}

224
QModelIndex ArchiveModel::parent(const QModelIndex &index) const
225
{
226
    if (index.isValid()) {
227
        Archive::Entry *item = static_cast<Archive::Entry*>(index.internalPointer());
228
        Q_ASSERT(item);
229
        if (item->getParent() && (item->getParent() != m_rootEntry.data())) {
230
            return createIndex(item->getParent()->row(), 0, item->getParent());
231 232 233
        }
    }
    return QModelIndex();
234 235
}

236
Archive::Entry *ArchiveModel::entryForIndex(const QModelIndex &index)
237
{
238
    if (index.isValid()) {
239
        Archive::Entry *item = static_cast<Archive::Entry*>(index.internalPointer());
240
        Q_ASSERT(item);
241
        return item;
242
    }
243
    return nullptr;
244
}
245

246
int ArchiveModel::rowCount(const QModelIndex &parent) const
247
{
248
    if (parent.column() <= 0) {
249 250
        const Archive::Entry *parentEntry = parent.isValid()
                                            ? static_cast<Archive::Entry*>(parent.internalPointer())
251
                                            : m_rootEntry.data();
252

253
        if (parentEntry && parentEntry->isDir()) {
254
            return parentEntry->entries().count();
255 256 257
        }
    }
    return 0;
258 259
}

260
int ArchiveModel::columnCount(const QModelIndex &parent) const
Harald Hvaal's avatar
Harald Hvaal committed
261
{
Ragnar Thomsen's avatar
Ragnar Thomsen committed
262
    Q_UNUSED(parent)
263 264 265 266
    return m_showColumns.size();
}

Qt::DropActions ArchiveModel::supportedDropActions() const
267
{
268
    return Qt::CopyAction | Qt::MoveAction;
269 270
}

271
QStringList ArchiveModel::mimeTypes() const
272
{
273
    QStringList types;
274

275
    // MIME types we accept for dragging (eg. Dolphin -> Ark).
276 277 278
    types << QStringLiteral("text/uri-list")
          << QStringLiteral("text/plain")
          << QStringLiteral("text/x-moz-url");
279 280

    // MIME types we accept for dropping (eg. Ark -> Dolphin).
281 282
    types << QStringLiteral("application/x-kde-ark-dndextract-service")
          << QStringLiteral("application/x-kde-ark-dndextract-path");
283

284
    return types;
285 286
}

287
QMimeData *ArchiveModel::mimeData(const QModelIndexList &indexes) const
288
{
289
    Q_UNUSED(indexes)
290

291
    QMimeData *mimeData = new QMimeData;
292
    mimeData->setData(QStringLiteral("application/x-kde-ark-dndextract-service"),
293
                      QDBusConnection::sessionBus().baseService().toUtf8());
294
    mimeData->setData(QStringLiteral("application/x-kde-ark-dndextract-path"),
295
                      m_dbusPathName.toUtf8());
296

297
    return mimeData;
298 299
}

300
bool ArchiveModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
301
{
302
    Q_UNUSED(action)
303

Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
304
    if (!data->hasUrls()) {
305
        return false;
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
306
    }
307

308 309 310 311 312 313 314
    if (archive()->isReadOnly() ||
        (archive()->encryptionType() != Archive::Unencrypted &&
         archive()->password().isEmpty())) {
        emit messageWidget(KMessageWidget::Error, i18n("Adding files is not supported for this archive."));
        return false;
    }

315
    QStringList paths;
316 317
    const auto urls = data->urls();
    for (const QUrl &url : urls) {
318 319 320 321
        if (!url.isLocalFile()) {
            emit messageWidget(KMessageWidget::Error, i18n("You can only add local files to an archive."));
            return false;
        }
322
        paths << url.toLocalFile();
323
    }
324

325
    const Archive::Entry *entry = nullptr;
326 327 328 329 330
    QModelIndex droppedOnto = index(row, column, parent);
    if (droppedOnto.isValid()) {
        entry = entryForIndex(droppedOnto);
        if (!entry->isDir()) {
            entry = entry->getParent();
331 332 333
        }
    }

334
    emit droppedFiles(paths, entry);
335

336
    return true;
337 338
}

339
// For a rationale, see bugs #194241, #241967 and #355839
340 341
QString ArchiveModel::cleanFileName(const QString& fileName)
{
342
    // Skip entries with filename "/" or "//" or "."
343
    // "." is present in ISO files.
344
    static QRegularExpression pattern(QStringLiteral("/+|\\."));
345 346 347
    QRegularExpressionMatch match;
    if (fileName.contains(pattern, &match) && match.captured() == fileName) {
        qCDebug(ARK) << "Skipping entry with filename" << fileName;
348
        return QString();
349
    } else if (fileName.startsWith(QLatin1String("./"))) {
350
        return fileName.mid(2);
351 352
    }

353
    return fileName;
354 355
}

356 357 358 359 360 361
void ArchiveModel::initRootEntry()
{
    m_rootEntry.reset(new Archive::Entry());
    m_rootEntry->setProperty("isDirectory", true);
}

362
Archive::Entry *ArchiveModel::parentFor(const Archive::Entry *entry, InsertBehaviour behaviour)
363
{
364
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
365
    QStringList pieces = entry->fullPath().split(QLatin1Char('/'), QString::SkipEmptyParts);
366 367 368
#else
    QStringList pieces = entry->fullPath().split(QLatin1Char('/'), Qt::SkipEmptyParts);
#endif
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
369
    if (pieces.isEmpty()) {
370
        return nullptr;
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
371
    }
372
    pieces.removeLast();
373

374
    // Used to speed up loading of large archives.
375
    if (s_previousMatch) {
376 377
        // The number of path elements must be the same for the shortcut
        // to work.
378
        if (s_previousPieces->count() == pieces.count()) {
379
            bool equal = true;
380

381
            // Check if all pieces match.
382 383
            for (int i = 0; i < s_previousPieces->count(); ++i) {
                if (s_previousPieces->at(i) != pieces.at(i)) {
384 385 386 387
                    equal = false;
                    break;
                }
            }
388

389
            // If match return it.
390
            if (equal) {
391
                return s_previousMatch;
392 393 394
            }
        }
    }
395

396
    Archive::Entry *parent = m_rootEntry.data();
397

398
    for (const QString &piece : qAsConst(pieces)) {
399 400 401 402 403 404 405
        Archive::Entry *entry = parent->find(piece);
        if (!entry) {
            // Directory entry will be traversed later (that happens for some archive formats, 7z for instance).
            // We have to create one before, in order to construct tree from its children,
            // and then delete the existing one (see ArchiveModel::newEntry).
            entry = new Archive::Entry(parent);

406
            entry->setProperty("fullPath", (parent == m_rootEntry.data())
Laurent Montel's avatar
Laurent Montel committed
407 408
                                           ? QString(piece + QLatin1Char('/'))
                                           : QString(parent->fullPath(WithTrailingSlash) + piece + QLatin1Char('/')));
409
            entry->setProperty("isDirectory", true);
410
            insertEntry(entry, behaviour);
411
        }
412 413
        if (!entry->isDir()) {
            Archive::Entry *e = new Archive::Entry(parent);
414
            e->copyMetaData(entry);
415 416
            // Maybe we have both a file and a directory of the same name.
            // We avoid removing previous entries unless necessary.
417
            insertEntry(e, behaviour);
418
        }
419
        parent = entry;
420
    }
421

422
    s_previousMatch = parent;
423
    *s_previousPieces = pieces;
424

425
    return parent;
426
}
427 428

QModelIndex ArchiveModel::indexForEntry(Archive::Entry *entry)
429
{
430
    Q_ASSERT(entry);
431
    if (entry != m_rootEntry.data()) {
432 433 434
        Q_ASSERT(entry->getParent());
        Q_ASSERT(entry->getParent()->isDir());
        return createIndex(entry->row(), 0, entry);
435 436
    }
    return QModelIndex();
437 438
}

439
void ArchiveModel::slotEntryRemoved(const QString & path)
440
{
441
    const QString entryFileName(cleanFileName(path));
442 443 444 445
    if (entryFileName.isEmpty()) {
        return;
    }

446
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
447
    Archive::Entry *entry = m_rootEntry->findByPath(entryFileName.split(QLatin1Char('/'), QString::SkipEmptyParts));
448 449 450
#else
    Archive::Entry *entry = m_rootEntry->findByPath(entryFileName.split(QLatin1Char('/'), Qt::SkipEmptyParts));
#endif
451
    if (entry) {
452 453
        Archive::Entry *parent = entry->getParent();
        QModelIndex index = indexForEntry(entry);
454
        Q_UNUSED(index);
455

456
        beginRemoveRows(indexForEntry(parent), entry->row(), entry->row());
457
        parent->removeEntryAt(entry->row());
458
        endRemoveRows();
459
    }
460 461
}

462
void ArchiveModel::slotUserQuery(Kerfuffle::Query *query)
463
{
464
    query->execute();
465 466
}

467
void ArchiveModel::slotNewEntry(Archive::Entry *entry)
468
{
469
    newEntry(entry, NotifyViews);
470 471
}

472
void ArchiveModel::slotListEntry(Archive::Entry *entry)
473
{
474
    newEntry(entry, DoNotNotifyViews);
475 476
}

477
void ArchiveModel::newEntry(Archive::Entry *receivedEntry, InsertBehaviour behaviour)
478
{
479
    if (receivedEntry->fullPath().isEmpty()) {
480
        qCDebug(ARK) << "Weird, received empty entry (no filename) - skipping";
481 482 483
        return;
    }

484
    // If there are no columns registered, then populate columns from entry. If the first entry
Yuri Chornoivan's avatar
Yuri Chornoivan committed
485
    // is a directory we check again for the first file entry to ensure all relevant columms are shown.
486
    if (m_showColumns.isEmpty() || !m_fileEntryListed) {
487 488
        QList<int> toInsert;

489 490
        const auto size = receivedEntry->property("size").toULongLong();
        const auto compressedSize = receivedEntry->property("compressedSize").toULongLong();
491
        for (auto i = m_propertiesMap.begin(); i != m_propertiesMap.end(); ++i) {
492 493 494 495
            // Singlefile plugin doesn't report the uncompressed size.
            if (i.key() == Size && size == 0 && compressedSize > 0) {
                continue;
            }
496
            if (!receivedEntry->property(i.value().constData()).toString().isEmpty()) {
497
                if (i.key() != CompressedSize || receivedEntry->compressedSizeIsSet) {
498 499 500
                    if (!m_showColumns.contains(i.key())) {
                        toInsert << i.key();
                    }
501
                }
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
502
            }
503
        }
504 505 506
        if (behaviour == NotifyViews) {
            beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1);
        }
507
        m_showColumns << toInsert;
508 509 510
        if (behaviour == NotifyViews) {
            endInsertColumns();
        }
511

512
        m_fileEntryListed = !receivedEntry->isDir();
513 514
    }

515 516 517
    // #194241: Filenames such as "./file" should be displayed as "file"
    // #241967: Entries called "/" should be ignored
    // #355839: Entries called "//" should be ignored
518
    QString entryFileName = cleanFileName(receivedEntry->fullPath());
519
    if (entryFileName.isEmpty()) { // The entry contains only "." or "./"
520
        return;
521
    }
522
    receivedEntry->setProperty("fullPath", entryFileName);
523

524 525
    // For some archive formats (e.g. AppImage and RPM) paths of folders do not
    // contain a trailing slash, so we append it.
526 527
    if (receivedEntry->property("isDirectory").toBool() &&
        !receivedEntry->property("fullPath").toString().endsWith(QLatin1Char('/'))) {
Laurent Montel's avatar
Laurent Montel committed
528
        receivedEntry->setProperty("fullPath", QString(receivedEntry->property("fullPath").toString() + QLatin1Char('/')));
529 530 531
        qCDebug(ARK) << "Trailing slash appended to entry:" << receivedEntry->property("fullPath");
    }

532
    // Skip already created entries.
533
    Archive::Entry *existing = m_rootEntry->findByPath(entryFileName.split(QLatin1Char('/')));
534 535 536 537 538 539 540
    if (existing) {
        existing->setProperty("fullPath", entryFileName);
        // Multi-volume files are repeated at least in RAR archives.
        // In that case, we need to sum the compressed size for each volume
        qulonglong currentCompressedSize = existing->property("compressedSize").toULongLong();
        existing->setProperty("compressedSize", currentCompressedSize + receivedEntry->property("compressedSize").toULongLong());
        return;
541 542
    }

543
    // Find parent entry, creating missing directory Archive::Entry's in the process.
544
    Archive::Entry *parent = parentFor(receivedEntry, behaviour);
545

546
    // Create an Archive::Entry.
547
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
548
    const QStringList path = entryFileName.split(QLatin1Char('/'), QString::SkipEmptyParts);
549 550 551
#else
    const QStringList path = entryFileName.split(QLatin1Char('/'), Qt::SkipEmptyParts);
#endif
552
    Archive::Entry *entry = parent->find(path.last());
553
    if (entry) {
554
        entry->copyMetaData(receivedEntry);
555
        entry->setProperty("fullPath", entryFileName);
556
    } else {
557 558
        receivedEntry->setParent(parent);
        insertEntry(receivedEntry, behaviour);
559
    }
560
}
561

562 563
void ArchiveModel::slotLoadingFinished(KJob *job)
{
564 565
    std::sort(m_showColumns.begin(), m_showColumns.end());

566
    if (!job->error()) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
567

568 569
        qCDebug(ARK) << "Showing columns: " << m_showColumns;

Elvis Angelaccio's avatar
Elvis Angelaccio committed
570 571
        m_archive.reset(qobject_cast<LoadJob*>(job)->archive());

572 573 574
        beginResetModel();
        endResetModel();
    }
575

576
    emit loadingFinished(job);
577 578
}

579 580 581 582
void ArchiveModel::insertEntry(Archive::Entry *entry, InsertBehaviour behaviour)
{
    Q_ASSERT(entry);
    Archive::Entry *parent = entry->getParent();
583
    Q_ASSERT(parent);
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
584
    if (behaviour == NotifyViews) {
585
        beginInsertRows(indexForEntry(parent), parent->entries().count(), parent->entries().count());
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
586
    }
587
    parent->appendEntry(entry);
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
588 589 590
    if (behaviour == NotifyViews) {
        endInsertRows();
    }
591 592
}

593 594
Kerfuffle::Archive* ArchiveModel::archive() const
{
595
    return m_archive.data();
596 597
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
598
void ArchiveModel::reset()
599
{
600 601
    m_archive.reset(nullptr);
    s_previousMatch = nullptr;
602
    s_previousPieces->clear();
603
    initRootEntry();
604

Elvis Angelaccio's avatar
Elvis Angelaccio committed
605 606
    // TODO: make sure if it's ok to not have calls to beginRemoveColumns here
    m_showColumns.clear();
607 608
    beginResetModel();
    endResetModel();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
609 610 611 612 613 614 615 616 617 618 619 620 621 622
}

void ArchiveModel::createEmptyArchive(const QString &path, const QString &mimeType, QObject *parent)
{
    reset();
    m_archive.reset(Archive::createEmpty(path, mimeType, parent));
}

KJob *ArchiveModel::loadArchive(const QString &path, const QString &mimeType, QObject *parent)
{
    reset();

    auto loadJob = Archive::load(path, mimeType, parent);
    connect(loadJob, &KJob::result, this, &ArchiveModel::slotLoadingFinished);
623
    connect(loadJob, &Job::newEntry, this, &ArchiveModel::slotListEntry);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
624 625 626 627 628
    connect(loadJob, &Job::userQuery, this, &ArchiveModel::slotUserQuery);

    emit loadingStarted();

    return loadJob;
629
}
Henrique Pinto's avatar
Henrique Pinto committed
630

Laurent Montel's avatar
Laurent Montel committed
631
ExtractJob* ArchiveModel::extractFile(Archive::Entry *file, const QString& destinationDir, Kerfuffle::ExtractionOptions options) const
Henrique Pinto's avatar
Henrique Pinto committed
632
{
633
    QVector<Archive::Entry*> files({file});
634
    return extractFiles(files, destinationDir, options);
635 636
}

Laurent Montel's avatar
Laurent Montel committed
637
ExtractJob* ArchiveModel::extractFiles(const QVector<Archive::Entry*>& files, const QString& destinationDir, Kerfuffle::ExtractionOptions options) const
638
{
639
    Q_ASSERT(m_archive);
640
    ExtractJob *newJob = m_archive->extractFiles(files, destinationDir, options);
Laurent Montel's avatar
Laurent Montel committed
641
    connect(newJob, &ExtractJob::userQuery, this, &ArchiveModel::slotUserQuery);
642
    return newJob;
Henrique Pinto's avatar
Henrique Pinto committed
643
}
644

645
Kerfuffle::PreviewJob *ArchiveModel::preview(Archive::Entry *file) const
646 647 648 649 650 651 652
{
    Q_ASSERT(m_archive);
    PreviewJob *job = m_archive->preview(file);
    connect(job, &Job::userQuery, this, &ArchiveModel::slotUserQuery);
    return job;
}

653
OpenJob *ArchiveModel::open(Archive::Entry *file) const
654 655 656 657 658 659 660
{
    Q_ASSERT(m_archive);
    OpenJob *job = m_archive->open(file);
    connect(job, &Job::userQuery, this, &ArchiveModel::slotUserQuery);
    return job;
}

661
OpenWithJob *ArchiveModel::openWith(Archive::Entry *file) const
662 663 664 665 666 667 668
{
    Q_ASSERT(m_archive);
    OpenWithJob *job = m_archive->openWith(file);
    connect(job, &Job::userQuery, this, &ArchiveModel::slotUserQuery);
    return job;
}

669
AddJob* ArchiveModel::addFiles(QVector<Archive::Entry*> &entries, const Archive::Entry *destination, const CompressionOptions& options)
670
{
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
671
    if (!m_archive) {
672
        return nullptr;
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
673
    }
674

675
    if (!m_archive->isReadOnly()) {
676
        AddJob *job = m_archive->addFiles(entries, destination, options);
Laurent Montel's avatar
Laurent Montel committed
677 678
        connect(job, &AddJob::newEntry, this, &ArchiveModel::slotNewEntry);
        connect(job, &AddJob::userQuery, this, &ArchiveModel::slotUserQuery);
679 680


681 682
        return job;
    }
683
    return nullptr;
684
}
685

686
Kerfuffle::MoveJob *ArchiveModel::moveFiles(QVector<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options)
687 688
{
    if (!m_archive) {
689
        return nullptr;
690 691 692 693 694 695 696 697 698 699 700 701
    }

    if (!m_archive->isReadOnly()) {
        MoveJob *job = m_archive->moveFiles(entries, destination, options);
        connect(job, &MoveJob::newEntry, this, &ArchiveModel::slotNewEntry);
        connect(job, &MoveJob::userQuery, this, &ArchiveModel::slotUserQuery);
        connect(job, &MoveJob::entryRemoved, this, &ArchiveModel::slotEntryRemoved);
        connect(job, &MoveJob::finished, this, &ArchiveModel::slotCleanupEmptyDirs);


        return job;
    }
702
    return nullptr;
703
}
704
Kerfuffle::CopyJob *ArchiveModel::copyFiles(QVector<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options)
705 706
{
    if (!m_archive) {
707
        return nullptr;
708 709 710 711 712 713 714 715 716 717
    }

    if (!m_archive->isReadOnly()) {
        CopyJob *job = m_archive->copyFiles(entries, destination, options);
        connect(job, &CopyJob::newEntry, this, &ArchiveModel::slotNewEntry);
        connect(job, &CopyJob::userQuery, this, &ArchiveModel::slotUserQuery);


        return job;
    }
718
    return nullptr;
719 720
}

721
DeleteJob* ArchiveModel::deleteFiles(QVector<Archive::Entry*> entries)
722
{
723 724
    Q_ASSERT(m_archive);
    if (!m_archive->isReadOnly()) {
725
        DeleteJob *job = m_archive->deleteFiles(entries);
Laurent Montel's avatar
Laurent Montel committed
726
        connect(job, &DeleteJob::entryRemoved, this, &ArchiveModel::slotEntryRemoved);
727

Laurent Montel's avatar
Laurent Montel committed
728
        connect(job, &DeleteJob::finished, this, &ArchiveModel::slotCleanupEmptyDirs);
729

Laurent Montel's avatar
Laurent Montel committed
730
        connect(job, &DeleteJob::userQuery, this, &ArchiveModel::slotUserQuery);
731 732
        return job;
    }
733
    return nullptr;
734
}
735

736
void ArchiveModel::encryptArchive(const QString &password, bool encryptHeader)
737
{