summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladyslav Batyrenko <mvlabat@gmail.com>2016-08-15 13:23:30 (GMT)
committerVladyslav Batyrenko <mvlabat@gmail.com>2016-08-15 13:23:30 (GMT)
commitace277330b373098e4ad96a2d4f57178e9c10a97 (patch)
tree5aaa8f4d37e9c5012c5bf23a8d7d1807b1e8b1ac
parente1bc57bc626e5664bf2e808a0d2839a6dd7cb51b (diff)
[GSoC] Implement AddTo, Rename, Copy, Move, Paste Actions and Jobs
See: https://phabricator.kde.org/T917
-rw-r--r--.travis.yml35
-rw-r--r--app/batchextract.cpp2
-rw-r--r--autotests/CMakeLists.txt1
-rw-r--r--autotests/kerfuffle/CMakeLists.txt12
-rw-r--r--autotests/kerfuffle/adddialogtest.cpp164
-rw-r--r--autotests/kerfuffle/addtest.cpp132
-rw-r--r--autotests/kerfuffle/addtoarchivetest.cpp18
-rw-r--r--autotests/kerfuffle/copytest.cpp199
-rw-r--r--autotests/kerfuffle/data/test.7zbin0 -> 271 bytes
-rw-r--r--autotests/kerfuffle/data/test.rarbin0 -> 726 bytes
-rw-r--r--autotests/kerfuffle/data/test.tar.bz2bin0 -> 331 bytes
-rw-r--r--autotests/kerfuffle/data/test.zipbin0 -> 1886 bytes
-rw-r--r--autotests/kerfuffle/data/testdir2/testdir/testfile1.txt1
-rw-r--r--autotests/kerfuffle/data/testdir2/testdir/testfile2.txt1
-rw-r--r--autotests/kerfuffle/data/textfile1.txt1
-rw-r--r--autotests/kerfuffle/data/textfile2.txt1
-rw-r--r--autotests/kerfuffle/extracttest.cpp (renamed from autotests/kerfuffle/archivetest.cpp)34
-rw-r--r--autotests/kerfuffle/jobstest.cpp64
-rw-r--r--autotests/kerfuffle/jsonarchiveinterface.cpp26
-rw-r--r--autotests/kerfuffle/jsonarchiveinterface.h6
-rw-r--r--autotests/kerfuffle/jsonparser.cpp2
-rw-r--r--autotests/kerfuffle/movetest.cpp177
-rw-r--r--autotests/plugins/cli7zplugin/CMakeLists.txt2
-rw-r--r--autotests/plugins/cli7zplugin/cli7ztest.cpp21
-rw-r--r--autotests/plugins/cli7zplugin/cli7ztest.h2
-rw-r--r--autotests/plugins/clirarplugin/CMakeLists.txt2
-rw-r--r--autotests/plugins/clirarplugin/clirartest.cpp20
-rw-r--r--autotests/plugins/clirarplugin/clirartest.h2
-rw-r--r--autotests/plugins/cliunarchiverplugin/CMakeLists.txt2
-rw-r--r--autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp6
-rw-r--r--autotests/plugins/clizipplugin/CMakeLists.txt2
-rw-r--r--autotests/plugins/clizipplugin/cliziptest.cpp18
-rw-r--r--autotests/plugins/clizipplugin/cliziptest.h3
-rw-r--r--autotests/testhelper/CMakeLists.txt9
-rw-r--r--autotests/testhelper/testhelper.cpp213
-rw-r--r--autotests/testhelper/testhelper.h66
-rw-r--r--kerfuffle/CMakeLists.txt3
-rw-r--r--kerfuffle/adddialog.cpp123
-rw-r--r--kerfuffle/adddialog.h68
-rw-r--r--kerfuffle/addtoarchive.cpp6
-rw-r--r--kerfuffle/archive_kerfuffle.cpp53
-rw-r--r--kerfuffle/archive_kerfuffle.h33
-rw-r--r--kerfuffle/archiveentry.cpp98
-rw-r--r--kerfuffle/archiveentry.h41
-rw-r--r--kerfuffle/archiveinterface.cpp81
-rw-r--r--kerfuffle/archiveinterface.h55
-rw-r--r--kerfuffle/cliinterface.cpp314
-rw-r--r--kerfuffle/cliinterface.h78
-rw-r--r--kerfuffle/compressionoptionswidget.cpp158
-rw-r--r--kerfuffle/compressionoptionswidget.h67
-rw-r--r--kerfuffle/compressionoptionswidget.ui152
-rw-r--r--kerfuffle/createdialog.cpp74
-rw-r--r--kerfuffle/createdialog.h3
-rw-r--r--kerfuffle/createdialog.ui280
-rw-r--r--kerfuffle/jobs.cpp93
-rw-r--r--kerfuffle/jobs.h56
-rw-r--r--part/CMakeLists.txt3
-rw-r--r--part/archivemodel.cpp219
-rw-r--r--part/archivemodel.h35
-rw-r--r--part/archiveview.cpp63
-rw-r--r--part/archiveview.h17
-rw-r--r--part/ark_part.rc15
-rw-r--r--part/infopanel.cpp8
-rw-r--r--part/overwritedialog.cpp74
-rw-r--r--part/overwritedialog.h69
-rw-r--r--part/part.cpp422
-rw-r--r--part/part.h45
-rw-r--r--plugins/cli7zplugin/cliplugin.cpp12
-rw-r--r--plugins/cli7zplugin/cliplugin.h1
-rw-r--r--plugins/clirarplugin/cliplugin.cpp19
-rw-r--r--plugins/clirarplugin/cliplugin.h2
-rw-r--r--plugins/cliunarchiverplugin/cliplugin.cpp4
-rw-r--r--plugins/cliunarchiverplugin/cliplugin.h2
-rw-r--r--plugins/clizipplugin/cliplugin.cpp92
-rw-r--r--plugins/clizipplugin/cliplugin.h14
-rw-r--r--plugins/libarchive/libarchiveplugin.cpp123
-rw-r--r--plugins/libarchive/libarchiveplugin.h23
-rw-r--r--plugins/libarchive/readwritelibarchiveplugin.cpp602
-rw-r--r--plugins/libarchive/readwritelibarchiveplugin.h50
-rw-r--r--plugins/libsinglefileplugin/singlefileplugin.cpp2
-rw-r--r--plugins/libsinglefileplugin/singlefileplugin.h2
81 files changed, 4030 insertions, 968 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..947ae9a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,35 @@
+sudo: required
+
+language: cpp
+
+compiler:
+ - gcc
+ - clang
+
+arch:
+ packages:
+ - extra-cmake-modules
+ - kdoctools
+ - python
+ - kparts
+ - kpty
+ - libarchive
+ - hicolor-icon-theme
+ - p7zip
+ - unzip
+ - zip
+ - lrzip
+ # for GUI tests
+ - xorg-server-xvfb
+ # from AUR:
+ - rar
+ - unarchiver
+ script:
+ - "cmake -DCMAKE_BUILD_TYPE=Debug -DKDE_INSTALL_LIBDIR=lib -DKDE_INSTALL_USE_QT_SYS_PATHS=ON -DCMAKE_INSTALL_PREFIX=/usr ."
+ - "make"
+ - "sudo make install"
+ # run the tests using the xvfb-run wrapper
+ - "xvfb-run --auto-servernum ctest --output-on-failure"
+
+script:
+ - "curl -s https://raw.githubusercontent.com/mikkeloscar/arch-travis/master/arch-travis.sh | bash"
diff --git a/app/batchextract.cpp b/app/batchextract.cpp
index 112793d..f8f8fab 100644
--- a/app/batchextract.cpp
+++ b/app/batchextract.cpp
@@ -92,7 +92,7 @@ void BatchExtract::addExtraction(Kerfuffle::Archive* archive)
Kerfuffle::ExtractionOptions options;
options[QStringLiteral("PreservePaths")] = preservePaths();
- Kerfuffle::ExtractJob *job = archive->copyFiles(QList<Kerfuffle::Archive::Entry*>(), destination, options);
+ Kerfuffle::ExtractJob *job = archive->extractFiles(QList<Kerfuffle::Archive::Entry*>(), destination, options);
qCDebug(ARK) << QString(QStringLiteral("Registering job from archive %1, to %2, preservePaths %3")).arg(archive->fileName(), destination, QString::number(preservePaths()));
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 4635d3b..2ea2753 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -1,4 +1,5 @@
include(ECMAddTests)
+add_subdirectory(testhelper)
add_subdirectory(kerfuffle)
add_subdirectory(plugins)
diff --git a/autotests/kerfuffle/CMakeLists.txt b/autotests/kerfuffle/CMakeLists.txt
index d90cdfd..233ec08 100644
--- a/autotests/kerfuffle/CMakeLists.txt
+++ b/autotests/kerfuffle/CMakeLists.txt
@@ -9,11 +9,19 @@ target_link_libraries(jsoninterface kerfuffle)
ecm_add_tests(
addtoarchivetest.cpp
- archivetest.cpp
+ extracttest.cpp
+ addtest.cpp
+ movetest.cpp
+ copytest.cpp
createdialogtest.cpp
metadatatest.cpp
mimetypetest.cpp
- LINK_LIBRARIES kerfuffle Qt5::Test
+ LINK_LIBRARIES testhelper kerfuffle Qt5::Test
+ NAME_PREFIX kerfuffle-)
+
+ecm_add_test(
+ adddialogtest.cpp
+ LINK_LIBRARIES kerfuffle Qt5::Test KF5::KIOFileWidgets
NAME_PREFIX kerfuffle-)
ecm_add_tests(
diff --git a/autotests/kerfuffle/adddialogtest.cpp b/autotests/kerfuffle/adddialogtest.cpp
new file mode 100644
index 0000000..8b32702
--- /dev/null
+++ b/autotests/kerfuffle/adddialogtest.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#include "adddialog.h"
+#include "archiveformat.h"
+#include "pluginmanager.h"
+
+#include <KCollapsibleGroupBox>
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QLineEdit>
+#include <QMimeDatabase>
+#include <QTest>
+
+using namespace Kerfuffle;
+
+class AddDialogTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testBasicWidgets_data();
+ void testBasicWidgets();
+
+private:
+ PluginManager m_pluginManager;
+};
+
+void AddDialogTest::testBasicWidgets_data()
+{
+ QTest::addColumn<QString>("mimeType");
+ QTest::addColumn<bool>("supportsCompLevel");
+ QTest::addColumn<int>("initialCompLevel");
+ QTest::addColumn<int>("changeToCompLevel");
+
+ QTest::newRow("tar") << QStringLiteral("application/x-tar") << false << -1 << -1;
+ QTest::newRow("targzip") << QStringLiteral("application/x-compressed-tar") << true << 3 << 7;
+ QTest::newRow("tarbzip") << QStringLiteral("application/x-bzip-compressed-tar") << true << 3 << 7;
+ QTest::newRow("tarZ") << QStringLiteral("application/x-tarz") << false << -1 << -1;
+ QTest::newRow("tarxz") << QStringLiteral("application/x-xz-compressed-tar") << true << 3 << 7;
+ QTest::newRow("tarlzma") << QStringLiteral("application/x-lzma-compressed-tar") << true << 3 << 7;
+ QTest::newRow("tarlzop") << QStringLiteral("application/x-tzo") << true << 3 << 7;
+ QTest::newRow("tarlzip") << QStringLiteral("application/x-lzip-compressed-tar") << true << 3 << 7;
+
+ const auto writeMimeTypes = m_pluginManager.supportedWriteMimeTypes();
+
+ if (writeMimeTypes.contains(QStringLiteral("application/zip"))) {
+ QTest::newRow("zip") << QStringLiteral("application/zip") << true << 3 << 7;
+ } else {
+ qDebug() << "zip format not available, skipping test.";
+ }
+
+ if (writeMimeTypes.contains(QStringLiteral("application/x-7z-compressed"))) {
+ QTest::newRow("7z") << QStringLiteral("application/x-7z-compressed") << true << 3 << 7;
+ } else {
+ qDebug() << "7z format not available, skipping test.";
+ }
+
+ if (writeMimeTypes.contains(QStringLiteral("application/x-rar"))) {
+ QTest::newRow("rar") << QStringLiteral("application/x-rar") << true << 2 << 5;
+ } else {
+ qDebug() << "rar format not available, skipping test.";
+ }
+
+ if (writeMimeTypes.contains(QStringLiteral("application/x-lrzip-compressed-tar"))) {
+ QTest::newRow("tarlrzip") << QStringLiteral("application/x-lrzip-compressed-tar") << true << 3 << 7;
+ } else {
+ qDebug() << "tar.lrzip format not available, skipping test.";
+ }
+}
+
+void AddDialogTest::testBasicWidgets()
+{
+ QFETCH(QString, mimeType);
+ const QMimeType mime = QMimeDatabase().mimeTypeForName(mimeType);
+ QFETCH(bool, supportsCompLevel);
+ QFETCH(int, initialCompLevel);
+ QFETCH(int, changeToCompLevel);
+
+ AddDialog *dialog = new AddDialog(Q_NULLPTR, QString(), QUrl(), mime);
+
+ dialog->slotOpenOptions();
+
+ auto collapsibleCompression = dialog->optionsDialog->findChild<KCollapsibleGroupBox*>(QStringLiteral("collapsibleCompression"));
+ QVERIFY(collapsibleCompression);
+
+ if (supportsCompLevel) {
+ // Test that collapsiblegroupbox is enabled for mimetypes that support compression levels.
+ QVERIFY(collapsibleCompression->isEnabled());
+
+ auto compLevelSlider = dialog->optionsDialog->findChild<QSlider*>(QStringLiteral("compLevelSlider"));
+ QVERIFY(compLevelSlider);
+
+ const KPluginMetaData metadata = PluginManager().preferredPluginFor(mime)->metaData();
+ const ArchiveFormat archiveFormat = ArchiveFormat::fromMetadata(mime, metadata);
+ QVERIFY(archiveFormat.isValid());
+
+ // Test that min/max of slider are correct.
+ QCOMPARE(compLevelSlider->minimum(), archiveFormat.minCompressionLevel());
+ QCOMPARE(compLevelSlider->maximum(), archiveFormat.maxCompressionLevel());
+
+ // Test that the slider is set to default compression level.
+ QCOMPARE(compLevelSlider->value(), archiveFormat.defaultCompressionLevel());
+
+ // Set the compression level slider.
+ compLevelSlider->setValue(changeToCompLevel);
+ } else {
+ // Test that collapsiblegroupbox is disabled for mimetypes that don't support compression levels.
+ QVERIFY(!collapsibleCompression->isEnabled());
+ }
+
+ dialog->optionsDialog->accept();
+ dialog->accept();
+
+ if (supportsCompLevel) {
+ // Test that the value set by slider is exported from AddDialog.
+ QCOMPARE(dialog->compressionOptions()[QStringLiteral("CompressionLevel")].toInt(), changeToCompLevel);
+ }
+
+ // Test that passing a compression level in ctor works.
+ CompressionOptions opts;
+ opts[QStringLiteral("CompressionLevel")] = initialCompLevel;
+
+ dialog = new AddDialog(Q_NULLPTR, QString(), QUrl(), mime, opts);
+ dialog->slotOpenOptions();
+
+ if (supportsCompLevel) {
+
+ auto compLevelSlider = dialog->optionsDialog->findChild<QSlider*>(QStringLiteral("compLevelSlider"));
+ QVERIFY(compLevelSlider);
+
+ // Test that slider is set to the compression level supplied in ctor.
+ QCOMPARE(compLevelSlider->value(), initialCompLevel);
+ }
+ dialog->optionsDialog->accept();
+ dialog->accept();
+}
+
+QTEST_MAIN(AddDialogTest)
+
+#include "adddialogtest.moc"
diff --git a/autotests/kerfuffle/addtest.cpp b/autotests/kerfuffle/addtest.cpp
new file mode 100644
index 0000000..ef80edd
--- /dev/null
+++ b/autotests/kerfuffle/addtest.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
+
+#include "autotests/testhelper/testhelper.h"
+
+using namespace Kerfuffle;
+
+class AddTest : public QObject
+{
+ Q_OBJECT
+
+private:
+void addAllFormatsRows(const QString testName, const QString archiveName, QList<Archive::Entry*> entries, Archive::Entry *destination) {
+ QStringList formats = QStringList()
+ << QStringLiteral("7z")
+ << QStringLiteral("rar")
+ << QStringLiteral("tar.bz2")
+ << QStringLiteral("zip");
+
+ foreach (QString format, formats) {
+ const QString testNameWithFormat = testName + QStringLiteral(" (") + format + QStringLiteral(")");
+ QTest::newRow(testNameWithFormat.toUtf8())
+ << archiveName + QLatin1Char('.') + format
+ << entries
+ << destination;
+ }
+}
+
+private Q_SLOTS:
+ void testAdding_data();
+ void testAdding();
+};
+
+QTEST_GUILESS_MAIN(AddTest)
+
+void AddTest::testAdding_data()
+{
+ QTest::addColumn<QString>("archiveName");
+ QTest::addColumn<QList<Archive::Entry*>>("files");
+ QTest::addColumn<Archive::Entry*>("destination");
+
+ addAllFormatsRows(QStringLiteral("without destination"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("textfile1.txt")),
+ new Archive::Entry(this, QStringLiteral("textfile2.txt")),
+ },
+ new Archive::Entry(this));
+
+ addAllFormatsRows(QStringLiteral("with destination, files"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("textfile1.txt")),
+ new Archive::Entry(this, QStringLiteral("textfile2.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("with destination, directory"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("testdir/")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("without destination, directory 2"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("testdir2/")),
+ },
+ new Archive::Entry(this));
+
+ addAllFormatsRows(QStringLiteral("with destination, directory 2"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("testdir2/")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+}
+
+void AddTest::testAdding()
+{
+ QTemporaryDir temporaryDir;
+
+ QFETCH(QString, archiveName);
+ const QString archivePath = temporaryDir.path() + QLatin1Char('/') + archiveName;
+ Q_ASSERT(QFile::copy(QFINDTESTDATA(QStringLiteral("data/") + archiveName), archivePath));
+ Archive *archive = Archive::create(archivePath, this);
+ QVERIFY(archive);
+
+ if (!archive->isValid()) {
+ QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle);
+ }
+
+ QFETCH(QList<Archive::Entry*>, files);
+ QFETCH(Archive::Entry*, destination);
+
+ QList<Archive::Entry*> oldEntries = TestHelper::getEntryList(archive);
+
+ CompressionOptions options = CompressionOptions();
+ options.insert(QStringLiteral("GlobalWorkDir"), QFINDTESTDATA("data"));
+ AddJob *addJob = archive->addFiles(files, destination, options);
+ TestHelper::startAndWaitForResult(addJob);
+
+ QList<Archive::Entry*> resultedEntries = TestHelper::getEntryList(archive);
+ TestHelper::verifyAddedEntriesWithDestination(files, destination, oldEntries, resultedEntries);
+
+ archive->deleteLater();
+}
+
+#include "addtest.moc"
diff --git a/autotests/kerfuffle/addtoarchivetest.cpp b/autotests/kerfuffle/addtoarchivetest.cpp
index e2a3256..410bb96 100644
--- a/autotests/kerfuffle/addtoarchivetest.cpp
+++ b/autotests/kerfuffle/addtoarchivetest.cpp
@@ -40,6 +40,7 @@ private Q_SLOTS:
void testCompressHere_data();
void testCompressHere();
+ void testCreateEncryptedArchive();
};
void AddToArchiveTest::testCompressHere_data()
@@ -135,6 +136,23 @@ void AddToArchiveTest::testCompressHere()
QVERIFY(QFile(archive->fileName()).remove());
}
+void AddToArchiveTest::testCreateEncryptedArchive()
+{
+ Archive *archive = Archive::create(QStringLiteral("foo.zip"));
+ QVERIFY(archive);
+
+ if (!archive->isValid()) {
+ QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle);
+ }
+
+ QCOMPARE(archive->encryptionType(), Archive::Unencrypted);
+
+ archive->encrypt(QStringLiteral("1234"), false);
+ QCOMPARE(archive->encryptionType(), Archive::Encrypted);
+
+ archive->deleteLater();
+}
+
QTEST_MAIN(AddToArchiveTest)
#include "addtoarchivetest.moc"
diff --git a/autotests/kerfuffle/copytest.cpp b/autotests/kerfuffle/copytest.cpp
new file mode 100644
index 0000000..55b22fc
--- /dev/null
+++ b/autotests/kerfuffle/copytest.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
+
+#include "autotests/testhelper/testhelper.h"
+
+using namespace Kerfuffle;
+
+class CopyTest : public QObject
+{
+Q_OBJECT
+
+private:
+ void addAllFormatsRows(const QString testName, const QString archiveName, QList<Archive::Entry*> entries, Archive::Entry *destination) {
+ QStringList formats = QStringList()
+ << QStringLiteral("7z")
+ << QStringLiteral("rar")
+ << QStringLiteral("tar.bz2")
+ << QStringLiteral("zip");
+
+ foreach (QString format, formats) {
+ const QString testNameWithFormat = testName + QStringLiteral(" (") + format + QStringLiteral(")");
+ QTest::newRow(testNameWithFormat.toUtf8())
+ << archiveName + QLatin1Char('.') + format
+ << entries
+ << destination;
+ }
+ }
+
+private Q_SLOTS:
+ void testCopying_data();
+ void testCopying();
+};
+
+QTEST_GUILESS_MAIN(CopyTest)
+
+void CopyTest::testCopying_data()
+{
+ QTest::addColumn<QString>("archiveName");
+ QTest::addColumn<QList<Archive::Entry*>>("files");
+ QTest::addColumn<Archive::Entry*>("destination");
+
+ addAllFormatsRows(QStringLiteral("copy a single file"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("a.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy several files"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("a.txt")),
+ new Archive::Entry(this, QStringLiteral("b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy a root directory"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy a root directory 2"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir2/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy a root directory 3"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir2/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("dir1/")));
+
+ addAllFormatsRows(QStringLiteral("copy a directory"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy several directories"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy several entries"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("copy a directory inside itself"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("dir1/")));
+
+ addAllFormatsRows(QStringLiteral("copy a directory to the root"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("")));
+}
+
+void CopyTest::testCopying()
+{
+ QTemporaryDir temporaryDir;
+
+ QFETCH(QString, archiveName);
+ const QString archivePath = temporaryDir.path() + QLatin1Char('/') + archiveName;
+ Q_ASSERT(QFile::copy(QFINDTESTDATA(QStringLiteral("data/") + archiveName), archivePath));
+ Archive *archive = Archive::create(archivePath, this);
+ QVERIFY(archive);
+
+ if (!archive->isValid()) {
+ QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle);
+ }
+
+ QFETCH(QList<Archive::Entry*>, files);
+ QFETCH(Archive::Entry*, destination);
+
+ const QList<Archive::Entry*> oldEntries = TestHelper::getEntryList(archive);
+
+ CompressionOptions options = CompressionOptions();
+ options.insert(QStringLiteral("GlobalWorkDir"), QFINDTESTDATA("data"));
+ CopyJob *copyJob = archive->copyFiles(files, destination, options);
+ TestHelper::startAndWaitForResult(copyJob);
+
+ QList<Archive::Entry*> resultedEntries = TestHelper::getEntryList(archive);
+ TestHelper::verifyCopiedEntriesWithDestination(files, destination, oldEntries, resultedEntries);
+
+ archive->deleteLater();
+}
+
+#include "copytest.moc"
diff --git a/autotests/kerfuffle/data/test.7z b/autotests/kerfuffle/data/test.7z
new file mode 100644
index 0000000..836f779
--- /dev/null
+++ b/autotests/kerfuffle/data/test.7z
Binary files differ
diff --git a/autotests/kerfuffle/data/test.rar b/autotests/kerfuffle/data/test.rar
new file mode 100644
index 0000000..4e1942f
--- /dev/null
+++ b/autotests/kerfuffle/data/test.rar
Binary files differ
diff --git a/autotests/kerfuffle/data/test.tar.bz2 b/autotests/kerfuffle/data/test.tar.bz2
new file mode 100644
index 0000000..bcd4105
--- /dev/null
+++ b/autotests/kerfuffle/data/test.tar.bz2
Binary files differ
diff --git a/autotests/kerfuffle/data/test.zip b/autotests/kerfuffle/data/test.zip
new file mode 100644
index 0000000..8001b01
--- /dev/null
+++ b/autotests/kerfuffle/data/test.zip
Binary files differ
diff --git a/autotests/kerfuffle/data/testdir2/testdir/testfile1.txt b/autotests/kerfuffle/data/testdir2/testdir/testfile1.txt
new file mode 100644
index 0000000..b72e979
--- /dev/null
+++ b/autotests/kerfuffle/data/testdir2/testdir/testfile1.txt
@@ -0,0 +1 @@
+A simple text file.
diff --git a/autotests/kerfuffle/data/testdir2/testdir/testfile2.txt b/autotests/kerfuffle/data/testdir2/testdir/testfile2.txt
new file mode 100644
index 0000000..b72e979
--- /dev/null
+++ b/autotests/kerfuffle/data/testdir2/testdir/testfile2.txt
@@ -0,0 +1 @@
+A simple text file.
diff --git a/autotests/kerfuffle/data/textfile1.txt b/autotests/kerfuffle/data/textfile1.txt
new file mode 100644
index 0000000..b72e979
--- /dev/null
+++ b/autotests/kerfuffle/data/textfile1.txt
@@ -0,0 +1 @@
+A simple text file.
diff --git a/autotests/kerfuffle/data/textfile2.txt b/autotests/kerfuffle/data/textfile2.txt
new file mode 100644
index 0000000..b72e979
--- /dev/null
+++ b/autotests/kerfuffle/data/textfile2.txt
@@ -0,0 +1 @@
+A simple text file.
diff --git a/autotests/kerfuffle/archivetest.cpp b/autotests/kerfuffle/extracttest.cpp
index 2e04fe2..ca18801 100644
--- a/autotests/kerfuffle/archivetest.cpp
+++ b/autotests/kerfuffle/extracttest.cpp
@@ -33,7 +33,7 @@
using namespace Kerfuffle;
-class ArchiveTest : public QObject
+class ExtractTest : public QObject
{
Q_OBJECT
@@ -42,12 +42,11 @@ private Q_SLOTS:
void testProperties();
void testExtraction_data();
void testExtraction();
- void testCreateEncryptedArchive();
};
-QTEST_GUILESS_MAIN(ArchiveTest)
+QTEST_GUILESS_MAIN(ExtractTest)
-void ArchiveTest::testProperties_data()
+void ExtractTest::testProperties_data()
{
QTest::addColumn<QString>("archivePath");
QTest::addColumn<QString>("expectedBaseName");
@@ -173,7 +172,7 @@ void ArchiveTest::testProperties_data()
<< QStringLiteral("test");
}
-void ArchiveTest::testProperties()
+void ExtractTest::testProperties()
{
QFETCH(QString, archivePath);
Archive *archive = Archive::create(archivePath, this);
@@ -210,7 +209,7 @@ void ArchiveTest::testProperties()
archive->deleteLater();
}
-void ArchiveTest::testExtraction_data()
+void ExtractTest::testExtraction_data()
{
QTest::addColumn<QString>("archivePath");
QTest::addColumn<QList<Archive::Entry*>>("entriesToExtract");
@@ -512,7 +511,7 @@ void ArchiveTest::testExtraction_data()
<< 6;
}
-void ArchiveTest::testExtraction()
+void ExtractTest::testExtraction()
{
QFETCH(QString, archivePath);
Archive *archive = Archive::create(archivePath, this);
@@ -529,7 +528,7 @@ void ArchiveTest::testExtraction()
QFETCH(QList<Archive::Entry*>, entriesToExtract);
QFETCH(ExtractionOptions, extractionOptions);
- auto extractionJob = archive->copyFiles(entriesToExtract, destDir.path(), extractionOptions);
+ auto extractionJob = archive->extractFiles(entriesToExtract, destDir.path(), extractionOptions);
QEventLoop eventLoop(this);
connect(extractionJob, &KJob::result, &eventLoop, &QEventLoop::quit);
@@ -550,21 +549,4 @@ void ArchiveTest::testExtraction()
archive->deleteLater();
}
-void ArchiveTest::testCreateEncryptedArchive()
-{
- Archive *archive = Archive::create(QStringLiteral("foo.zip"));
- QVERIFY(archive);
-
- if (!archive->isValid()) {
- QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle);
- }
-
- QCOMPARE(archive->encryptionType(), Archive::Unencrypted);
-
- archive->encrypt(QStringLiteral("1234"), false);
- QCOMPARE(archive->encryptionType(), Archive::Encrypted);
-
- archive->deleteLater();
-}
-
-#include "archivetest.moc"
+#include "extracttest.moc"
diff --git a/autotests/kerfuffle/jobstest.cpp b/autotests/kerfuffle/jobstest.cpp
index 8a24962..6392d8e 100644
--- a/autotests/kerfuffle/jobstest.cpp
+++ b/autotests/kerfuffle/jobstest.cpp
@@ -209,7 +209,7 @@ void JobsTest::testListJob()
QCOMPARE(archiveEntries.size(), expectedEntryNames.size());
for (int i = 0; i < archiveEntries.size(); i++) {
- QCOMPARE(archiveEntries.at(i)->property("fullPath").toString(), expectedEntryNames.at(i));
+ QCOMPARE(archiveEntries.at(i)->fullPath(), expectedEntryNames.at(i));
}
listJob->deleteLater();
@@ -317,7 +317,7 @@ void JobsTest::testRemoveEntries()
QList<Archive::Entry*> expectedRemainingEntries;
Q_FOREACH (Archive::Entry *entry, entries) {
- if (!fullPathsToDelete.contains(entry->property("fullPath").toString())) {
+ if (!fullPathsToDelete.contains(entry->fullPath())) {
expectedRemainingEntries.append(entry);
}
}
@@ -340,6 +340,7 @@ void JobsTest::testAddEntries_data()
QTest::addColumn<QString>("jsonArchive");
QTest::addColumn<QList<Archive::Entry*>>("originalEntries");
QTest::addColumn<QList<Archive::Entry*>>("entriesToAdd");
+ QTest::addColumn<Archive::Entry*>("destinationEntry");
QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json")
<< QList<Archive::Entry*> {
@@ -350,7 +351,8 @@ void JobsTest::testAddEntries_data()
}
<< QList<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("foo.txt"))
- };
+ }
+ << new Archive::Entry(this);
QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json")
<< QList<Archive::Entry*> {
@@ -362,7 +364,31 @@ void JobsTest::testAddEntries_data()
<< QList<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("foo.txt")),
new Archive::Entry(this, QStringLiteral("bar.txt"))
- };
+ }
+ << new Archive::Entry(this);
+
+ QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json")
+ << QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("a.txt")),
+ new Archive::Entry(this, QStringLiteral("aDir/")),
+ new Archive::Entry(this, QStringLiteral("aDir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("c.txt"))
+ }
+ << QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("foo.txt")),
+ new Archive::Entry(this, QStringLiteral("bar.txt"))
+ }
+ << new Archive::Entry(this, QStringLiteral("aDir/"));
+
+ QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json")
+ << QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("a.txt")),
+ new Archive::Entry(this, QStringLiteral("aDir/")),
+ new Archive::Entry(this, QStringLiteral("aDir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("c.txt"))
+ }
+ << QList<Archive::Entry*> {new Archive::Entry(this, QStringLiteral("c.txt"))}
+ << new Archive::Entry(this, QStringLiteral("aDir/"));
// Error test: if we add an already existent entry, the archive must not change.
QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json")
@@ -372,7 +398,8 @@ void JobsTest::testAddEntries_data()
new Archive::Entry(this, QStringLiteral("aDir/b.txt")),
new Archive::Entry(this, QStringLiteral("c.txt"))
}
- << QList<Archive::Entry*> {new Archive::Entry(this, QStringLiteral("c.txt"))};
+ << QList<Archive::Entry*> {new Archive::Entry(this, QStringLiteral("c.txt"))}
+ << new Archive::Entry(this);
}
void JobsTest::testAddEntries()
@@ -383,27 +410,40 @@ void JobsTest::testAddEntries()
QFETCH(QList<Archive::Entry*>, originalEntries);
QStringList originalFullPaths = QStringList();
- Q_FOREACH (Archive::Entry *entry, originalEntries) {
- originalFullPaths.append(entry->property("fullPath").toString());
+ Q_FOREACH (const Archive::Entry *entry, originalEntries) {
+ originalFullPaths.append(entry->fullPath());
}
auto currentEntries = listEntries(iface);
QCOMPARE(currentEntries.size(), originalEntries.size());
QFETCH(QList<Archive::Entry*>, entriesToAdd);
- AddJob *addJob = new AddJob(entriesToAdd, CompressionOptions(), iface);
+ QFETCH(Archive::Entry*, destinationEntry);
+ AddJob *addJob = new AddJob(entriesToAdd, destinationEntry, CompressionOptions(), iface);
startAndWaitForResult(addJob);
- currentEntries = listEntries(iface);
-
+ QStringList expectedAddedFullPaths = QStringList();
+ const QString destinationPath = destinationEntry->fullPath();
int expectedEntriesCount = originalEntries.size();
- Q_FOREACH (Archive::Entry *entry, entriesToAdd) {
- if (!originalFullPaths.contains(entry->property("fullPath").toString())) {
+ Q_FOREACH (const Archive::Entry *entry, entriesToAdd) {
+ const QString fullPath = destinationPath + entry->fullPath();
+ if (!originalFullPaths.contains(fullPath)) {
expectedEntriesCount++;
+ expectedAddedFullPaths << destinationPath + entry->fullPath();
}
}
+ currentEntries = listEntries(iface);
QCOMPARE(currentEntries.size(), expectedEntriesCount);
+ QStringList currentFullPaths = QStringList();
+ Q_FOREACH (const Archive::Entry* entry, currentEntries) {
+ currentFullPaths << entry->fullPath();
+ }
+
+ Q_FOREACH (const QString fullPath, expectedAddedFullPaths) {
+ QVERIFY(currentFullPaths.contains(fullPath));
+ }
+
iface->deleteLater();
}
diff --git a/autotests/kerfuffle/jsonarchiveinterface.cpp b/autotests/kerfuffle/jsonarchiveinterface.cpp
index c0f2e21..af6f8c2 100644
--- a/autotests/kerfuffle/jsonarchiveinterface.cpp
+++ b/autotests/kerfuffle/jsonarchiveinterface.cpp
@@ -64,12 +64,12 @@ bool JSONArchiveInterface::open()
return !m_archive.isEmpty();
}
-bool JSONArchiveInterface::addFiles(const QList<Kerfuffle::Archive::Entry*> &files, const Kerfuffle::CompressionOptions& options)
+bool JSONArchiveInterface::addFiles(const QList<Kerfuffle::Archive::Entry*>& files, const Kerfuffle::Archive::Entry *destination, const Kerfuffle::CompressionOptions& options)
{
Q_UNUSED(options)
foreach (const Kerfuffle::Archive::Entry *entry, files) {
- const QString &path = entry->property("fullPath").toString();
+ const QString &path = destination->fullPath() + entry->fullPath();
if (m_archive.contains(path)) {
return false;
}
@@ -83,7 +83,25 @@ bool JSONArchiveInterface::addFiles(const QList<Kerfuffle::Archive::Entry*> &fil
return true;
}
-bool JSONArchiveInterface::copyFiles(const QList<Kerfuffle::Archive::Entry*>& files, const QString& destinationDirectory, const Kerfuffle::ExtractionOptions& options)
+bool JSONArchiveInterface::moveFiles(const QList<Kerfuffle::Archive::Entry*>& files, Kerfuffle::Archive::Entry *destination, const Kerfuffle::ExtractionOptions& options)
+{
+ Q_UNUSED(files)
+ Q_UNUSED(destination)
+ Q_UNUSED(options)
+
+ return true;
+}
+
+bool JSONArchiveInterface::copyFiles(const QList<Kerfuffle::Archive::Entry*>& files, Kerfuffle::Archive::Entry *destination, const Kerfuffle::CompressionOptions& options)
+{
+ Q_UNUSED(files)
+ Q_UNUSED(destination)
+ Q_UNUSED(options)
+
+ return false;
+}
+
+bool JSONArchiveInterface::extractFiles(const QList<Kerfuffle::Archive::Entry*>& files, const QString &destinationDirectory, const Kerfuffle::ExtractionOptions& options)
{
Q_UNUSED(files)
Q_UNUSED(destinationDirectory)
@@ -95,7 +113,7 @@ bool JSONArchiveInterface::copyFiles(const QList<Kerfuffle::Archive::Entry*>& fi
bool JSONArchiveInterface::deleteFiles(const QList<Kerfuffle::Archive::Entry*>& files)
{
foreach (const Kerfuffle::Archive::Entry *file, files) {
- const QString &fileName = file->property("fullPath").toString();
+ const QString &fileName = file->fullPath();
if (m_archive.contains(fileName)) {
m_archive.remove(fileName);
emit entryRemoved(fileName);
diff --git a/autotests/kerfuffle/jsonarchiveinterface.h b/autotests/kerfuffle/jsonarchiveinterface.h
index aaacce6..fb6aac9 100644
--- a/autotests/kerfuffle/jsonarchiveinterface.h
+++ b/autotests/kerfuffle/jsonarchiveinterface.h
@@ -55,8 +55,10 @@ public:
virtual bool list() Q_DECL_OVERRIDE;
virtual bool open() Q_DECL_OVERRIDE;
- virtual bool addFiles(const QList<Kerfuffle::Archive::Entry*>& files, const Kerfuffle::CompressionOptions& options) Q_DECL_OVERRIDE;
- virtual bool copyFiles(const QList<Kerfuffle::Archive::Entry*>& files, const QString& destinationDirectory, const Kerfuffle::ExtractionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool addFiles(const QList<Kerfuffle::Archive::Entry*>& files, const Kerfuffle::Archive::Entry *destination, const Kerfuffle::CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool moveFiles(const QList<Kerfuffle::Archive::Entry*>& files, Kerfuffle::Archive::Entry *destination, const Kerfuffle::CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool copyFiles(const QList<Kerfuffle::Archive::Entry*>& files, Kerfuffle::Archive::Entry *destination, const Kerfuffle::CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool extractFiles(const QList<Kerfuffle::Archive::Entry*>& files, const QString &destinationDirectory, const Kerfuffle::ExtractionOptions& options) Q_DECL_OVERRIDE;
virtual bool deleteFiles(const QList<Kerfuffle::Archive::Entry*>& files) Q_DECL_OVERRIDE;
virtual bool addComment(const QString& comment) Q_DECL_OVERRIDE;
virtual bool testArchive() Q_DECL_OVERRIDE;
diff --git a/autotests/kerfuffle/jsonparser.cpp b/autotests/kerfuffle/jsonparser.cpp
index 492808f..c4b63d8 100644
--- a/autotests/kerfuffle/jsonparser.cpp
+++ b/autotests/kerfuffle/jsonparser.cpp
@@ -67,7 +67,7 @@ JSONParser::JSONArchive JSONParser::createJSONArchive(const QVariant &json)
QVariantMap::const_iterator entryIterator = entryMap.constBegin();
for (; entryIterator != entryMap.constEnd(); ++entryIterator) {
- const char *key = entryIterator.key().toStdString().c_str();
+ const char *key = entryIterator.key().toUtf8();
if (e->property(key).isValid()) {
e->setProperty(key, entryIterator.value());
} else {
diff --git a/autotests/kerfuffle/movetest.cpp b/autotests/kerfuffle/movetest.cpp
new file mode 100644
index 0000000..acfbbfd
--- /dev/null
+++ b/autotests/kerfuffle/movetest.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
+
+#include "autotests/testhelper/testhelper.h"
+
+using namespace Kerfuffle;
+
+class MoveTest : public QObject
+{
+Q_OBJECT
+
+private:
+ void addAllFormatsRows(const QString testName, const QString archiveName, QList<Archive::Entry*> entries, Archive::Entry *destination) {
+ QStringList formats = QStringList()
+ << QStringLiteral("7z")
+ << QStringLiteral("rar")
+ << QStringLiteral("tar.bz2")
+ << QStringLiteral("zip");
+
+ foreach (QString format, formats) {
+ const QString testNameWithFormat = testName + QStringLiteral(" (") + format + QStringLiteral(")");
+ QTest::newRow(testNameWithFormat.toUtf8())
+ << archiveName + QLatin1Char('.') + format
+ << entries
+ << destination;
+ }
+ }
+
+private Q_SLOTS:
+ void testMoving_data();
+ void testMoving();
+};
+
+QTEST_GUILESS_MAIN(MoveTest)
+
+void MoveTest::testMoving_data()
+{
+ QTest::addColumn<QString>("archiveName");
+ QTest::addColumn<QList<Archive::Entry*>>("files");
+ QTest::addColumn<Archive::Entry*>("destination");
+
+ addAllFormatsRows(QStringLiteral("replace a single file"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("a.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/a.txt")));
+
+ addAllFormatsRows(QStringLiteral("replace several files"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("a.txt")),
+ new Archive::Entry(this, QStringLiteral("b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("replace a root directory"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/dir/")));
+
+ addAllFormatsRows(QStringLiteral("replace a root directory 2"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir2/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/dir/")));
+
+ addAllFormatsRows(QStringLiteral("replace a directory"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/dir/")));
+
+ addAllFormatsRows(QStringLiteral("replace several directories"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir2/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("replace several entries"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("empty_dir/")));
+
+ addAllFormatsRows(QStringLiteral("move a directory to the root"),
+ QStringLiteral("test"),
+ QList<Archive::Entry*> {
+ new Archive::Entry(this, QStringLiteral("dir1/dir/")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/a.txt")),
+ new Archive::Entry(this, QStringLiteral("dir1/dir/b.txt")),
+ },
+ new Archive::Entry(this, QStringLiteral("dir/")));
+}
+
+void MoveTest::testMoving()
+{
+ QTemporaryDir temporaryDir;
+
+ QFETCH(QString, archiveName);
+ const QString archivePath = temporaryDir.path() + QLatin1Char('/') + archiveName;
+ Q_ASSERT(QFile::copy(QFINDTESTDATA(QStringLiteral("data/") + archiveName), archivePath));
+ Archive *archive = Archive::create(archivePath, this);
+ QVERIFY(archive);
+
+ if (!archive->isValid()) {
+ QSKIP("Could not find a plugin to handle the archive. Skipping test.", SkipSingle);
+ }
+
+ QFETCH(QList<Archive::Entry*>, files);
+ QFETCH(Archive::Entry*, destination);
+
+ QList<Archive::Entry*> oldEntries = TestHelper::getEntryList(archive);
+
+ CompressionOptions options = CompressionOptions();
+ options.insert(QStringLiteral("GlobalWorkDir"), QFINDTESTDATA("data"));
+ MoveJob *moveJob = archive->moveFiles(files, destination, options);
+ TestHelper::startAndWaitForResult(moveJob);
+
+ QList<Archive::Entry*> resultedEntries = TestHelper::getEntryList(archive);
+ TestHelper::verifyMovedEntriesWithDestination(files, destination, oldEntries, resultedEntries);
+
+ archive->deleteLater();
+}
+
+#include "movetest.moc"
diff --git a/autotests/plugins/cli7zplugin/CMakeLists.txt b/autotests/plugins/cli7zplugin/CMakeLists.txt
index 3b5e0bc..8d9a6ae 100644
--- a/autotests/plugins/cli7zplugin/CMakeLists.txt
+++ b/autotests/plugins/cli7zplugin/CMakeLists.txt
@@ -9,6 +9,6 @@ ecm_add_test(
cli7ztest.cpp
${CMAKE_SOURCE_DIR}/plugins/cli7zplugin/cliplugin.cpp
${CMAKE_BINARY_DIR}/plugins/cli7zplugin/ark_debug.cpp
- LINK_LIBRARIES kerfuffle Qt5::Test
+ LINK_LIBRARIES testhelper kerfuffle Qt5::Test
TEST_NAME cli7ztest
NAME_PREFIX plugins-)
diff --git a/autotests/plugins/cli7zplugin/cli7ztest.cpp b/autotests/plugins/cli7zplugin/cli7ztest.cpp
index c3eb15e..3cb3e4f 100644
--- a/autotests/plugins/cli7zplugin/cli7ztest.cpp
+++ b/autotests/plugins/cli7zplugin/cli7ztest.cpp
@@ -31,6 +31,7 @@
#include <QTextStream>
#include <KPluginLoader>
+#include <QtCore/QVariant>
QTEST_GUILESS_MAIN(Cli7zTest)
@@ -163,7 +164,7 @@ void Cli7zTest::testList()
Archive::Entry *entry = signalSpy.at(someEntryIndex).at(0).value<Archive::Entry*>();
QFETCH(QString, expectedName);
- QCOMPARE(entry->property("fullPath").toString(), expectedName);
+ QCOMPARE(entry->fullPath(), expectedName);
QFETCH(bool, isDirectory);
QCOMPARE(entry->isDir(), isDirectory);
@@ -300,7 +301,7 @@ void Cli7zTest::testExtractArgs_data()
QTest::newRow("preserve paths, encrypted")
<< QStringLiteral("/tmp/foo.7z")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QStringLiteral("1234")
@@ -308,28 +309,28 @@ void Cli7zTest::testExtractArgs_data()
QStringLiteral("x"),
QStringLiteral("-p1234"),
QStringLiteral("/tmp/foo.7z"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("preserve paths, unencrypted")
<< QStringLiteral("/tmp/foo.7z")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QString()
<< QStringList {
QStringLiteral("x"),
QStringLiteral("/tmp/foo.7z"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, encrypted")
<< QStringLiteral("/tmp/foo.7z")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QStringLiteral("1234")
@@ -337,21 +338,21 @@ void Cli7zTest::testExtractArgs_data()
QStringLiteral("e"),
QStringLiteral("-p1234"),
QStringLiteral("/tmp/foo.7z"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, unencrypted")
<< QStringLiteral("/tmp/foo.7z")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QString()
<< QStringList {
QStringLiteral("e"),
QStringLiteral("/tmp/foo.7z"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
}
@@ -371,7 +372,7 @@ void Cli7zTest::testExtractArgs()
QFETCH(bool, preservePaths);
QFETCH(QString, password);
- QStringList replacedArgs = plugin->substituteCopyVariables(extractArgs, files, preservePaths, password);
+ QStringList replacedArgs = plugin->substituteExtractVariables(extractArgs, files, preservePaths, password);
QVERIFY(replacedArgs.size() >= extractArgs.size());
QFETCH(QStringList, expectedArgs);
diff --git a/autotests/plugins/cli7zplugin/cli7ztest.h b/autotests/plugins/cli7zplugin/cli7ztest.h
index 1e55abf..86307af 100644
--- a/autotests/plugins/cli7zplugin/cli7ztest.h
+++ b/autotests/plugins/cli7zplugin/cli7ztest.h
@@ -28,6 +28,8 @@
#include "cliplugin.h"
#include "pluginmanager.h"
+#include "autotests/testhelper/testhelper.h"
+#include "kerfuffle/jobs.h"
using namespace Kerfuffle;
diff --git a/autotests/plugins/clirarplugin/CMakeLists.txt b/autotests/plugins/clirarplugin/CMakeLists.txt
index b2ec1c4..afdf46d 100644
--- a/autotests/plugins/clirarplugin/CMakeLists.txt
+++ b/autotests/plugins/clirarplugin/CMakeLists.txt
@@ -9,6 +9,6 @@ ecm_add_test(
clirartest.cpp
${CMAKE_SOURCE_DIR}/plugins/clirarplugin/cliplugin.cpp
${CMAKE_BINARY_DIR}/plugins/clirarplugin/ark_debug.cpp
- LINK_LIBRARIES kerfuffle Qt5::Test
+ LINK_LIBRARIES testhelper kerfuffle Qt5::Test
TEST_NAME clirartest
NAME_PREFIX plugins-)
diff --git a/autotests/plugins/clirarplugin/clirartest.cpp b/autotests/plugins/clirarplugin/clirartest.cpp
index 014a7af..4640e8a 100644
--- a/autotests/plugins/clirarplugin/clirartest.cpp
+++ b/autotests/plugins/clirarplugin/clirartest.cpp
@@ -192,7 +192,7 @@ void CliRarTest::testList()
Archive::Entry *entry = signalSpy.at(someEntryIndex).at(0).value<Archive::Entry*>();
QFETCH(QString, expectedName);
- QCOMPARE(entry->property("fullPath").toString(), expectedName);
+ QCOMPARE(entry->fullPath(), expectedName);
QFETCH(bool, isDirectory);
QCOMPARE(entry->isDir(), isDirectory);
@@ -334,7 +334,7 @@ void CliRarTest::testExtractArgs_data()
QTest::newRow("preserve paths, encrypted")
<< QStringLiteral("/tmp/foo.rar")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QStringLiteral("1234")
@@ -344,14 +344,14 @@ void CliRarTest::testExtractArgs_data()
QStringLiteral("x"),
QStringLiteral("-p1234"),
QStringLiteral("/tmp/foo.rar"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("preserve paths, unencrypted")
<< QStringLiteral("/tmp/foo.rar")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QString()
@@ -360,14 +360,14 @@ void CliRarTest::testExtractArgs_data()
QStringLiteral("-p-"),
QStringLiteral("x"),
QStringLiteral("/tmp/foo.rar"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, encrypted")
<< QStringLiteral("/tmp/foo.rar")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QStringLiteral("1234")
@@ -377,14 +377,14 @@ void CliRarTest::testExtractArgs_data()
QStringLiteral("e"),
QStringLiteral("-p1234"),
QStringLiteral("/tmp/foo.rar"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, unencrypted")
<< QStringLiteral("/tmp/foo.rar")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QString()
@@ -393,7 +393,7 @@ void CliRarTest::testExtractArgs_data()
QStringLiteral("-p-"),
QStringLiteral("e"),
QStringLiteral("/tmp/foo.rar"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
}
@@ -415,7 +415,7 @@ void CliRarTest::testExtractArgs()
QFETCH(bool, preservePaths);
QFETCH(QString, password);
- QStringList replacedArgs = rarPlugin->substituteCopyVariables(extractArgs, files, preservePaths, password);
+ QStringList replacedArgs = rarPlugin->substituteExtractVariables(extractArgs, files, preservePaths, password);
QVERIFY(replacedArgs.size() >= extractArgs.size());
QFETCH(QStringList, expectedArgs);
diff --git a/autotests/plugins/clirarplugin/clirartest.h b/autotests/plugins/clirarplugin/clirartest.h
index 8e53831..c319c10 100644
--- a/autotests/plugins/clirarplugin/clirartest.h
+++ b/autotests/plugins/clirarplugin/clirartest.h
@@ -29,6 +29,8 @@
#include "cliplugin.h"
#include "pluginmanager.h"
+#include "autotests/testhelper/testhelper.h"
+#include "kerfuffle/jobs.h"
using namespace Kerfuffle;
diff --git a/autotests/plugins/cliunarchiverplugin/CMakeLists.txt b/autotests/plugins/cliunarchiverplugin/CMakeLists.txt
index c28bba8..a831936 100644
--- a/autotests/plugins/cliunarchiverplugin/CMakeLists.txt
+++ b/autotests/plugins/cliunarchiverplugin/CMakeLists.txt
@@ -9,6 +9,6 @@ ecm_add_test(
cliunarchivertest.cpp
${CMAKE_SOURCE_DIR}/plugins/cliunarchiverplugin/cliplugin.cpp
${CMAKE_BINARY_DIR}/plugins/cliunarchiverplugin/ark_debug.cpp
- LINK_LIBRARIES kerfuffle Qt5::Test
+ LINK_LIBRARIES testhelper kerfuffle Qt5::Test
TEST_NAME cliunarchivertest
NAME_PREFIX plugins-)
diff --git a/autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp b/autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
index 1c7675d..eb22cfe 100644
--- a/autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
+++ b/autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
@@ -156,7 +156,7 @@ void CliUnarchiverTest::testList()
Archive::Entry *entry = signalSpy.at(someEntryIndex).at(0).value<Archive::Entry*>();
QFETCH(QString, expectedName);
- QCOMPARE(entry->property("fullPath").toString(), expectedName);
+ QCOMPARE(entry->fullPath(), expectedName);
QFETCH(bool, isDirectory);
QCOMPARE(entry->isDir(), isDirectory);
@@ -301,7 +301,7 @@ void CliUnarchiverTest::testExtraction()
QFETCH(QList<Archive::Entry*>, entriesToExtract);
QFETCH(ExtractionOptions, extractionOptions);
- auto extractionJob = archive->copyFiles(entriesToExtract, destDir.path(), extractionOptions);
+ auto extractionJob = archive->extractFiles(entriesToExtract, destDir.path(), extractionOptions);
QEventLoop eventLoop(this);
connect(extractionJob, &KJob::result, &eventLoop, &QEventLoop::quit);
@@ -374,7 +374,7 @@ void CliUnarchiverTest::testExtractArgs()
QFETCH(QList<Archive::Entry*>, files);
QFETCH(QString, password);
- QStringList replacedArgs = plugin->substituteCopyVariables(extractArgs, files, false, password);
+ QStringList replacedArgs = plugin->substituteExtractVariables(extractArgs, files, false, password);
QVERIFY(replacedArgs.size() >= extractArgs.size());
QFETCH(QStringList, expectedArgs);
diff --git a/autotests/plugins/clizipplugin/CMakeLists.txt b/autotests/plugins/clizipplugin/CMakeLists.txt
index f1a710c..9efe7bd 100644
--- a/autotests/plugins/clizipplugin/CMakeLists.txt
+++ b/autotests/plugins/clizipplugin/CMakeLists.txt
@@ -9,6 +9,6 @@ ecm_add_test(
cliziptest.cpp
${CMAKE_SOURCE_DIR}/plugins/clizipplugin/cliplugin.cpp
${CMAKE_BINARY_DIR}/plugins/clizipplugin/ark_debug.cpp
- LINK_LIBRARIES kerfuffle Qt5::Test
+ LINK_LIBRARIES testhelper kerfuffle Qt5::Test
TEST_NAME cliziptest
NAME_PREFIX plugins-)
diff --git a/autotests/plugins/clizipplugin/cliziptest.cpp b/autotests/plugins/clizipplugin/cliziptest.cpp
index 7cb1881..1ca09d7 100644
--- a/autotests/plugins/clizipplugin/cliziptest.cpp
+++ b/autotests/plugins/clizipplugin/cliziptest.cpp
@@ -125,34 +125,34 @@ void CliZipTest::testExtractArgs_data()
QTest::newRow("preserve paths, encrypted")
<< QStringLiteral("/tmp/foo.zip")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QStringLiteral("1234")
<< QStringList {
QStringLiteral("-P1234"),
QStringLiteral("/tmp/foo.zip"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("preserve paths, unencrypted")
<< QStringLiteral("/tmp/foo.zip")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QString()
<< QStringList {
QStringLiteral("/tmp/foo.zip"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, encrypted")
<< QStringLiteral("/tmp/foo.zip")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QStringLiteral("1234")
@@ -160,21 +160,21 @@ void CliZipTest::testExtractArgs_data()
QStringLiteral("-j"),
QStringLiteral("-P1234"),
QStringLiteral("/tmp/foo.zip"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, unencrypted")
<< QStringLiteral("/tmp/foo.zip")
<< QList<Archive::Entry*> {
- new Archive::Entry(this, QStringLiteral("aDir/b.txt"), QStringLiteral("aDir")),
+ new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QString()
<< QStringList {
QStringLiteral("-j"),
QStringLiteral("/tmp/foo.zip"),
- QStringLiteral("aDir/b.txt"),
+ QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
}
@@ -194,7 +194,7 @@ void CliZipTest::testExtractArgs()
QFETCH(bool, preservePaths);
QFETCH(QString, password);
- QStringList replacedArgs = plugin->substituteCopyVariables(extractArgs, files, preservePaths, password);
+ QStringList replacedArgs = plugin->substituteExtractVariables(extractArgs, files, preservePaths, password);
QFETCH(QStringList, expectedArgs);
QCOMPARE(replacedArgs, expectedArgs);
diff --git a/autotests/plugins/clizipplugin/cliziptest.h b/autotests/plugins/clizipplugin/cliziptest.h
index c61dd33..dabf899 100644
--- a/autotests/plugins/clizipplugin/cliziptest.h
+++ b/autotests/plugins/clizipplugin/cliziptest.h
@@ -27,7 +27,8 @@
#define CLIZIPTEST_H
#include "cliplugin.h"
-#include <QObject>
+#include "autotests/testhelper/testhelper.h"
+#include "kerfuffle/jobs.h"
using namespace Kerfuffle;
diff --git a/autotests/testhelper/CMakeLists.txt b/autotests/testhelper/CMakeLists.txt
new file mode 100644
index 0000000..5c44a2f
--- /dev/null
+++ b/autotests/testhelper/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+
+set(TESTHELPER_SOURCES
+ testhelper.h
+ testhelper.cpp
+)
+
+add_library(testhelper STATIC ${TESTHELPER_SOURCES})
+target_link_libraries(testhelper Qt5::Test kerfuffle)
diff --git a/autotests/testhelper/testhelper.cpp b/autotests/testhelper/testhelper.cpp
new file mode 100644
index 0000000..0f58fd8
--- /dev/null
+++ b/autotests/testhelper/testhelper.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
+
+#include "testhelper.h"
+
+QEventLoop TestHelper::m_eventLoop;
+
+void TestHelper::startAndWaitForResult(KJob *job)
+{
+ QObject::connect(job, &KJob::result, &m_eventLoop, &QEventLoop::quit);
+ job->start();
+ m_eventLoop.exec();
+}
+
+QList<Archive::Entry*> TestHelper::getEntryList(Archive *archive)
+{
+ QList<Archive::Entry*> list = QList<Archive::Entry*>();
+ ListJob *listJob = archive->list();
+ QObject::connect(listJob, &Job::newEntry, [&list](Archive::Entry* entry) { list << entry; });
+ startAndWaitForResult(listJob);
+ return list;
+}
+
+void TestHelper::verifyAddedEntriesWithDestination(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination, const QList<Archive::Entry*> &oldEntries, const QList<Archive::Entry*> &newEntries)
+{
+ QStringList expectedPaths = getExpectedNewEntryPaths(argumentEntries, destination);
+ QStringList actualPaths = ReadOnlyArchiveInterface::entryFullPaths(newEntries);
+ foreach (const QString &path, expectedPaths) {
+ QVERIFY2(actualPaths.contains(path), (QStringLiteral("No ") + path + QStringLiteral(" inside the archive (new entry)")).toUtf8());
+ }
+ foreach (const Archive::Entry *entry, oldEntries) {
+ const QString path = entry->fullPath();
+ QVERIFY2(actualPaths.contains(path), (QStringLiteral("No ") + path + QStringLiteral(" inside the archive (old entry)")).toUtf8());
+ }
+}
+
+void TestHelper::verifyMovedEntriesWithDestination(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination, const QList<Archive::Entry*> &oldEntries, const QList<Archive::Entry*> &newEntries)
+{
+ QStringList expectedPaths = getExpectedMovedEntryPaths(oldEntries, argumentEntries, destination);
+ QStringList actualPaths = ReadOnlyArchiveInterface::entryFullPaths(newEntries);
+ foreach (const QString &path, expectedPaths) {
+ QVERIFY2(actualPaths.contains(path), (QStringLiteral("No ") + path + QStringLiteral(" inside the archive")).toUtf8());
+ }
+ foreach (const QString &path, actualPaths) {
+ QVERIFY2(expectedPaths.contains(path), (QStringLiteral("Entry ") + path + QStringLiteral(" is not expected to be inside the archive")).toUtf8());
+ }
+ foreach (const Archive::Entry *entry, argumentEntries) {
+ const QString path = entry->fullPath();
+ QVERIFY2(!actualPaths.contains(path), (QStringLiteral("Entry ") + path + QStringLiteral(" is still inside the archive, when it shouldn't be")).toUtf8());
+ }
+}
+
+void TestHelper::verifyCopiedEntriesWithDestination(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination, const QList<Archive::Entry*> &oldEntries, const QList<Archive::Entry*> &newEntries)
+{
+ QStringList expectedPaths = getExpectedCopiedEntryPaths(oldEntries, argumentEntries, destination);
+ QStringList actualPaths = ReadOnlyArchiveInterface::entryFullPaths(newEntries);
+ foreach (const QString &path, expectedPaths) {
+ QVERIFY2(actualPaths.contains(path), (QStringLiteral("No ") + path + QStringLiteral(" inside the archive")).toUtf8());
+ }
+ foreach (const QString &path, actualPaths) {
+ QVERIFY2(expectedPaths.contains(path), (QStringLiteral("Entry ") + path + QStringLiteral(" is not expected to be inside the archive")).toUtf8());
+ }
+}
+
+QStringList TestHelper::getExpectedNewEntryPaths(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination)
+{
+ QStringList expectedPaths = QStringList();
+ const QString testDataPath = QFINDTESTDATA("data") + QLatin1Char('/');
+
+ foreach (const Archive::Entry *entry, argumentEntries) {
+ const QString entryPath = entry->fullPath();
+ expectedPaths << destination->fullPath() + entryPath;
+
+ if (entryPath.right(1) == QLatin1String("/")) {
+ const QString workingDirectory = testDataPath + QLatin1Char('/') + entry->fullPath(true);
+ QDirIterator it(workingDirectory, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ QString path = it.next();
+ path = destination->fullPath() + path.right(path.count() - testDataPath.count() - 1);
+ if (it.fileInfo().isDir()) {
+ path += QLatin1Char('/');
+ }
+ expectedPaths << path;
+ }
+ }
+ }
+ return expectedPaths;
+}
+
+QStringList TestHelper::getExpectedMovedEntryPaths(const QList<Archive::Entry*> &entryList, const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination)
+{
+ QStringList expectedPaths = QStringList();
+ QMap<QString, Archive::Entry*> entryMap = getEntryMap(entryList);
+ QStringList argumentPaths = ReadOnlyArchiveInterface::entryFullPaths(argumentEntries);
+ QString lastMovedFolder;
+ if (ReadOnlyArchiveInterface::entriesWithoutChildren(argumentEntries).count() > 1) {
+ // Destination path doesn't contain a target entry name, so we have to remember to include it while moving
+ // folder contents.
+ int nameLength = 0;
+ foreach (const Archive::Entry *entry, entryMap) {
+ const QString entryPath = entry->fullPath();
+ if (lastMovedFolder.count() > 0 && entryPath.startsWith(lastMovedFolder)) {
+ expectedPaths << destination->fullPath() + entryPath.right(entryPath.count() - lastMovedFolder.count() + nameLength);
+ }
+ else if (argumentPaths.contains(entryPath)) {
+ QString expectedPath = destination->fullPath() + entry->name();
+ if (entryPath.right(1) == QLatin1String("/")) {
+ expectedPath += QLatin1Char('/');
+ nameLength = entry->name().count() + 1; // plus slash
+ lastMovedFolder = entryPath;
+ }
+ else {
+ nameLength = 0;
+ lastMovedFolder = QString();
+ }
+ expectedPaths << expectedPath;
+ }
+ else {
+ expectedPaths << entryPath;
+ nameLength = 0;
+ lastMovedFolder = QString();
+ }
+ }
+ }
+ else {
+ foreach (const Archive::Entry *entry, entryMap) {
+ const QString entryPath = entry->fullPath();
+ if (lastMovedFolder.count() > 0 && entryPath.startsWith(lastMovedFolder)) {
+ expectedPaths << destination->fullPath() + entryPath.right(entryPath.count() - lastMovedFolder.count());
+ }
+ else if (argumentPaths.contains(entryPath)) {
+ if (entryPath.right(1) == QLatin1String("/")) {
+ lastMovedFolder = entryPath;
+ }
+ else if (lastMovedFolder.count() > 0) {
+ lastMovedFolder = QString();
+ }
+ expectedPaths << destination->fullPath();
+ }
+ else {
+ expectedPaths << entryPath;
+ }
+ }
+ }
+ return expectedPaths;
+}
+
+QStringList TestHelper::getExpectedCopiedEntryPaths(const QList<Archive::Entry*> &entryList, const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination)
+{
+ QStringList expectedPaths = QStringList();
+ QMap<QString, Archive::Entry*> entryMap = getEntryMap(entryList);
+ QStringList argumentPaths = ReadOnlyArchiveInterface::entryFullPaths(argumentEntries);
+ QString lastCopiedFolder;
+ // Destination path doesn't contain a target entry name, so we have to remember to include it while copying
+ // folder contents.
+ int nameLength = 0;
+ foreach (const Archive::Entry *entry, entryMap) {
+ const QString entryPath = entry->fullPath();
+ if (lastCopiedFolder.count() > 0 && entryPath.startsWith(lastCopiedFolder)) {
+ expectedPaths << destination->fullPath() + entryPath.right(entryPath.count() - lastCopiedFolder.count() + nameLength);
+ }
+ else if (argumentPaths.contains(entryPath)) {
+ QString expectedPath = destination->fullPath() + entry->name();
+ if (entryPath.right(1) == QLatin1String("/")) {
+ expectedPath += QLatin1Char('/');
+ nameLength = entry->name().count() + 1; // plus slash
+ lastCopiedFolder = entryPath;
+ }
+ else {
+ nameLength = 0;
+ lastCopiedFolder = QString();
+ }
+ expectedPaths << expectedPath;
+ }
+ else {
+ nameLength = 0;
+ lastCopiedFolder = QString();
+ }
+ expectedPaths << entryPath;
+ }
+ return expectedPaths;
+}
+
+QMap<QString, Archive::Entry*> TestHelper::getEntryMap(const QList<Archive::Entry *> entries)
+{
+ QMap<QString, Archive::Entry*> map;
+ foreach (Archive::Entry* entry, entries) {
+ map.insert(entry->fullPath(), entry);
+ }
+ return map;
+}
diff --git a/autotests/testhelper/testhelper.h b/autotests/testhelper/testhelper.h
new file mode 100644
index 0000000..e50501d
--- /dev/null
+++ b/autotests/testhelper/testhelper.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef TESTHELPER_H
+#define TESTHELPER_H
+
+#include "kerfuffle/jobs.h"
+#include "kerfuffle/archiveentry.h"
+
+#include <QTest>
+#include <QEventLoop>
+#include <QDirIterator>
+
+using namespace Kerfuffle;
+
+class TestHelper
+{
+public:
+
+ static void startAndWaitForResult(KJob *job);
+ static QList<Archive::Entry*> getEntryList(Archive *archive);
+ static void verifyAddedEntriesWithDestination(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination, const QList<Archive::Entry*> &oldEntries, const QList<Archive::Entry*> &newEntries);
+ static void verifyMovedEntriesWithDestination(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination, const QList<Archive::Entry*> &oldEntries, const QList<Archive::Entry*> &newEntries);
+ static void verifyCopiedEntriesWithDestination(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination, const QList<Archive::Entry*> &oldEntries, const QList<Archive::Entry*> &newEntries);
+
+private:
+ TestHelper() {}
+
+ static QStringList getExpectedNewEntryPaths(const QList<Archive::Entry*> &argumentEntries, const Archive::Entry *destination);
+ static QStringList getExpectedMovedEntryPaths(const QList<Archive::Entry*> &entryList, const QList<Archive::Entry*> &argumentEntries, const Archive::Entry* destination);
+ static QStringList getExpectedCopiedEntryPaths(const QList<Archive::Entry*> &entryList, const QList<Archive::Entry*> &argumentEntries, const Archive::Entry* destination);
+
+ /**
+ * Returns map of entries.
+ *
+ * It's useful when we need a sorted list of entries.
+ */
+ static QMap<QString, Archive::Entry*> getEntryMap(const QList<Archive::Entry*> entries);
+
+ static QEventLoop m_eventLoop;
+};
+
+
+#endif //TESTHELPER_H
diff --git a/kerfuffle/CMakeLists.txt b/kerfuffle/CMakeLists.txt
index 938f6e8..b545f52 100644
--- a/kerfuffle/CMakeLists.txt
+++ b/kerfuffle/CMakeLists.txt
@@ -8,6 +8,8 @@ set(kerfuffle_SRCS
previewsettingspage.cpp
settingspage.cpp
jobs.cpp
+ adddialog.cpp
+ compressionoptionswidget.cpp
createdialog.cpp
extractiondialog.cpp
propertiesdialog.cpp
@@ -29,6 +31,7 @@ ki18n_wrap_ui(kerfuffle_SRCS
extractionsettings.ui
previewsettings.ui
propertiesdialog.ui
+ compressionoptionswidget.ui
)
ecm_qt_declare_logging_category(kerfuffle_SRCS
diff --git a/kerfuffle/adddialog.cpp b/kerfuffle/adddialog.cpp
new file mode 100644
index 0000000..882f145
--- /dev/null
+++ b/kerfuffle/adddialog.cpp
@@ -0,0 +1,123 @@
+/*
+ * ark -- archiver for the KDE project
+ *
+ * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#include "adddialog.h"
+#include "ark_debug.h"
+#include "archiveformat.h"
+#include "compressionoptionswidget.h"
+#include "mimetypes.h"
+
+#include <QDialogButtonBox>
+#include <QPointer>
+#include <QPushButton>
+#include <QUrl>
+#include <QVBoxLayout>
+
+namespace Kerfuffle
+{
+
+AddDialog::AddDialog(QWidget *parent,
+ const QString &title,
+ const QUrl &startDir,
+ const QMimeType &mimeType,
+ const CompressionOptions &opts)
+ : QDialog(parent, Qt::Dialog)
+ , m_mimeType(mimeType)
+ , m_compOptions(opts)
+{
+ qCDebug(ARK) << "AddDialog loaded with options:" << m_compOptions;
+
+ setWindowTitle(title);
+
+ QVBoxLayout *vlayout = new QVBoxLayout(this);
+ m_fileWidget = new KFileWidget(startDir, this);
+ vlayout->addWidget(m_fileWidget);
+
+ QPushButton *optionsButton = new QPushButton(QIcon::fromTheme(QStringLiteral("settings-configure")),
+ i18n("Advanced Options"));
+ optionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ m_fileWidget->setCustomWidget(optionsButton);
+
+ connect(optionsButton, &QPushButton::clicked, this, &AddDialog::slotOpenOptions);
+
+ m_fileWidget->setMode(KFile::Files | KFile::Directory | KFile::LocalOnly | KFile::ExistingOnly);
+ m_fileWidget->setOperationMode(KFileWidget::Opening);
+
+ m_fileWidget->okButton()->setText(i18nc("@action:button", "Add"));
+ m_fileWidget->okButton()->show();
+ connect(m_fileWidget->okButton(), &QPushButton::clicked, m_fileWidget, &KFileWidget::slotOk);
+ connect(m_fileWidget, &KFileWidget::accepted, m_fileWidget, &KFileWidget::accept);
+ connect(m_fileWidget, &KFileWidget::accepted, this, &QDialog::accept);
+
+ m_fileWidget->cancelButton()->show();
+ connect(m_fileWidget->cancelButton(), &QPushButton::clicked, this, &QDialog::reject);
+}
+
+AddDialog::~AddDialog()
+{
+}
+
+QStringList AddDialog::selectedFiles() const
+{
+ return m_fileWidget->selectedFiles();
+}
+
+CompressionOptions AddDialog::compressionOptions() const
+{
+ return m_compOptions;
+}
+
+void AddDialog::slotOpenOptions()
+{
+ optionsDialog = new QDialog(this);
+ QVBoxLayout *vlayout = new QVBoxLayout(optionsDialog);
+ optionsDialog->setWindowTitle(i18n("Advanced Options"));
+
+ CompressionOptionsWidget *optionsWidget = new CompressionOptionsWidget(optionsDialog, m_compOptions);
+ optionsWidget->setMimeType(m_mimeType);
+ optionsWidget->setEncryptionVisible(false);
+ optionsWidget->collapsibleCompression->expand();
+ vlayout->addWidget(optionsWidget);
+
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, optionsDialog);
+ vlayout->addWidget(buttonBox);
+ connect(buttonBox, &QDialogButtonBox::accepted, optionsDialog, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, optionsDialog, &QDialog::reject);
+
+ optionsDialog->layout()->setSizeConstraint(QLayout::SetFixedSize);
+
+ connect(optionsDialog, &QDialog::finished, this, [this, optionsWidget](int result) {
+ if (result == QDialog::Accepted) {
+ m_compOptions = optionsWidget->commpressionOptions();
+ }
+ optionsDialog->deleteLater();
+ });
+
+ optionsDialog->open();
+}
+
+}
diff --git a/kerfuffle/adddialog.h b/kerfuffle/adddialog.h
new file mode 100644
index 0000000..013b93d
--- /dev/null
+++ b/kerfuffle/adddialog.h
@@ -0,0 +1,68 @@
+/*
+ * ark -- archiver for the KDE project
+ *
+ * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef ADDDIALOG_H
+#define ADDDIALOG_H
+
+#include "kerfuffle_export.h"
+#include "archive_kerfuffle.h"
+#include "compressionoptionswidget.h"
+
+#include <KFileWidget>
+
+#include <QDialog>
+#include <QMimeType>
+
+class QUrl;
+
+namespace Kerfuffle
+{
+class KERFUFFLE_EXPORT AddDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit AddDialog(QWidget *parent,
+ const QString &title,
+ const QUrl &startDir,
+ const QMimeType &mimeType,
+ const CompressionOptions &opts = QHash<QString, QVariant>());
+ virtual ~AddDialog();
+ QStringList selectedFiles() const;
+ CompressionOptions compressionOptions() const;
+ QDialog *optionsDialog;
+
+private:
+ KFileWidget *m_fileWidget;
+ QMimeType m_mimeType;
+ CompressionOptions m_compOptions;
+
+public slots:
+ void slotOpenOptions();
+};
+}
+
+#endif
diff --git a/kerfuffle/addtoarchive.cpp b/kerfuffle/addtoarchive.cpp
index 49c0aea..3df09af 100644
--- a/kerfuffle/addtoarchive.cpp
+++ b/kerfuffle/addtoarchive.cpp
@@ -208,7 +208,7 @@ void AddToArchive::slotStartJob()
const QDir stripDir(m_firstPath);
foreach (Archive::Entry *entry, m_entries) {
- entry->setFullPath(stripDir.absoluteFilePath(entry->property("fullPath").toString()));
+ entry->setFullPath(stripDir.absoluteFilePath(entry->fullPath()));
}
options[QStringLiteral( "GlobalWorkDir" )] = stripDir.path();
@@ -216,7 +216,7 @@ void AddToArchive::slotStartJob()
}
Kerfuffle::AddJob *job =
- archive->addFiles(m_entries, options);
+ archive->addFiles(m_entries, new Archive::Entry(this), options);
KIO::getJobTracker()->registerJob(job);
@@ -238,7 +238,7 @@ void AddToArchive::slotFinished(KJob *job)
QString AddToArchive::detectBaseName(const QList<Archive::Entry*> &entries) const
{
- QFileInfo fileInfo = QFileInfo(entries.first()->property("fullPath").toString());
+ QFileInfo fileInfo = QFileInfo(entries.first()->fullPath());
QDir parentDir = fileInfo.dir();
QString base = parentDir.absolutePath() + QLatin1Char('/');
diff --git a/kerfuffle/archive_kerfuffle.cpp b/kerfuffle/archive_kerfuffle.cpp
index 14cca8b..8e6cd59 100644
--- a/kerfuffle/archive_kerfuffle.cpp
+++ b/kerfuffle/archive_kerfuffle.cpp
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2009-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -316,7 +317,7 @@ DeleteJob* Archive::deleteFiles(QList<Archive::Entry*> &entries)
return newJob;
}
-AddJob* Archive::addFiles(QList<Archive::Entry*> &files, const CompressionOptions& options)
+AddJob* Archive::addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options)
{
if (!isValid()) {
return Q_NULLPTR;
@@ -330,12 +331,48 @@ AddJob* Archive::addFiles(QList<Archive::Entry*> &files, const CompressionOption
qCDebug(ARK) << "Going to add files" << files << "with options" << newOptions;
Q_ASSERT(!m_iface->isReadOnly());
- AddJob *newJob = new AddJob(files, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
+ AddJob *newJob = new AddJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
connect(newJob, &AddJob::result, this, &Archive::onAddFinished);
return newJob;
}
-ExtractJob* Archive::copyFiles(const QList<Archive::Entry*> &files, const QString& destinationDir, const ExtractionOptions& options)
+MoveJob* Archive::moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options)
+{
+ if (!isValid()) {
+ return Q_NULLPTR;
+ }
+
+ CompressionOptions newOptions = options;
+ if (encryptionType() != Unencrypted) {
+ newOptions[QStringLiteral("PasswordProtectedHint")] = true;
+ }
+
+ qCDebug(ARK) << "Going to move files" << files << "with options" << newOptions;
+ Q_ASSERT(!m_iface->isReadOnly());
+
+ MoveJob *newJob = new MoveJob(files, destination, newOptions, static_cast<ReadWriteArchiveInterface*>(m_iface));
+ return newJob;
+}
+
+CopyJob* Archive::copyFiles(const QList<Archive::Entry *> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ if (!isValid()) {
+ return Q_NULLPTR;
+ }
+
+ CompressionOptions newOptions = options;
+ if (encryptionType() != Unencrypted) {
+ newOptions[QStringLiteral("PasswordProtectedHint")] = true;
+ }
+
+ 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;
+}
+
+ExtractJob* Archive::extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDir, const ExtractionOptions &options)
{
if (!isValid()) {
return Q_NULLPTR;
@@ -445,4 +482,14 @@ void Archive::onUserQuery(Query* query)
query->execute();
}
+void Archive::setCompressionOptions(const CompressionOptions &opts)
+{
+ m_compOptions = opts;
+}
+
+CompressionOptions Archive::compressionOptions() const
+{
+ return m_compOptions;
+}
+
} // namespace Kerfuffle
diff --git a/kerfuffle/archive_kerfuffle.h b/kerfuffle/archive_kerfuffle.h
index 856b2b8..2ad3782 100644
--- a/kerfuffle/archive_kerfuffle.h
+++ b/kerfuffle/archive_kerfuffle.h
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -45,6 +46,8 @@ class ListJob;
class ExtractJob;
class DeleteJob;
class AddJob;
+class MoveJob;
+class CopyJob;
class CommentJob;
class TestJob;
class OpenJob;
@@ -111,6 +114,8 @@ public:
qulonglong unpackedSize();
qulonglong packedSize() const;
QString subfolderName();
+ void setCompressionOptions(const CompressionOptions &opts);
+ CompressionOptions compressionOptions() const;
static Archive *create(const QString &fileName, QObject *parent = 0);
static Archive *create(const QString &fileName, const QString &fixedMimeType, QObject *parent = 0);
@@ -144,16 +149,31 @@ public:
*
* GlobalWorkDir - Change to this dir before adding the new files.
* The path names should then be added relative to this directory.
+ */
+ AddJob* addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options = CompressionOptions());
+
+ /**
+ * Renames or moves entries within the archive.
*
- * TODO: find a way to actually add files to specific locations in
- * the archive
- * (not supported yet) GlobalPathInArchive - a path relative to the
- * archive root where the files will be added under
+ * @param files All the renamed or moved files and their child entries (for renaming a directory too).
+ * @param destination New entry name (for renaming) or destination folder (for moving).
+ * If ReadOnlyArchiveInterface::entriesWithoutChildren(files).count() returns 1, then it's renaming,
+ * so you must specify the resulted entry name, even if it's not going to be changed.
+ * Otherwise (if count is more than 1) it's moving, so destination must conatin only targeted folder path
+ * or be empty, if moving to the root.
+ */
+ MoveJob* moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options = CompressionOptions());
+
+ /**
+ * Copies entries within the archive.
*
+ * @param files All the renamed or moved files and their child entries (for renaming a directory too).
+ * @param destination Destination path. It must conatin only targeted folder path or be empty,
+ * if copying to the root.
*/
- AddJob* addFiles(QList<Archive::Entry*> &files, const CompressionOptions& options = CompressionOptions());
+ CopyJob* copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options = CompressionOptions());
- ExtractJob* copyFiles(const QList<Archive::Entry*> &files, const QString &destinationDir, const ExtractionOptions &options = ExtractionOptions());
+ ExtractJob* extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDir, const ExtractionOptions &options = ExtractionOptions());
PreviewJob* preview(Archive::Entry *entry);
OpenJob* open(Archive::Entry *entry);
@@ -186,6 +206,7 @@ private:
ArchiveError m_error;
EncryptionType m_encryptionType;
qulonglong m_numberOfFiles;
+ CompressionOptions m_compOptions;
QMimeType m_mimeType;
};
diff --git a/kerfuffle/archiveentry.cpp b/kerfuffle/archiveentry.cpp
index 6c03fa6..a36008f 100644
--- a/kerfuffle/archiveentry.cpp
+++ b/kerfuffle/archiveentry.cpp
@@ -1,6 +1,27 @@
-//
-// Created by mvlabat on 5/27/16.
-//
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
#include "archiveentry.h"
@@ -24,6 +45,25 @@ Archive::Entry::~Entry()
clear();
}
+void Archive::Entry::copyMetaData(const Archive::Entry *sourceEntry)
+{
+ setProperty("fullPath", sourceEntry->property("fullPath"));
+ setProperty("permissions", sourceEntry->property("permissions"));
+ setProperty("owner", sourceEntry->property("owner"));
+ setProperty("group", sourceEntry->property("group"));
+ setProperty("size", sourceEntry->property("size"));
+ setProperty("compressedSize", sourceEntry->property("compressedSize"));
+ setProperty("link", sourceEntry->property("link"));
+ setProperty("ratio", sourceEntry->property("ratio"));
+ setProperty("CRC", sourceEntry->property("CRC"));
+ setProperty("method", sourceEntry->property("method"));
+ setProperty("version", sourceEntry->property("version"));
+ setProperty("timestamp", sourceEntry->property("timestamp").toDateTime());
+ setProperty("isDirectory", sourceEntry->property("isDirectory"));
+ setProperty("comment", sourceEntry->property("comment"));
+ setProperty("isPasswordProtected", sourceEntry->property("isPasswordProtected"));
+}
+
QVector<Archive::Entry*> Archive::Entry::entries()
{
Q_ASSERT(isDir());
@@ -68,21 +108,27 @@ void Archive::Entry::setParent(Archive::Entry *parent)
void Archive::Entry::setFullPath(const QString &fullPath)
{
m_fullPath = fullPath;
+ m_fullPathWithoutTrailingSlash = fullPath;
+ if (m_fullPathWithoutTrailingSlash.right(1) == QLatin1String("/")) {
+ m_fullPathWithoutTrailingSlash.chop(1);
+ }
const QStringList pieces = m_fullPath.split(QLatin1Char( '/' ), QString::SkipEmptyParts);
m_name = pieces.isEmpty() ? QString() : pieces.last();
}
-void Archive::Entry::setIsDirectory(const bool isDirectory)
+QString Archive::Entry::fullPath(bool withoutTrailingSlash) const
{
- m_isDirectory = isDirectory;
+ return (withoutTrailingSlash) ? m_fullPathWithoutTrailingSlash : m_fullPath;
}
-int Archive::Entry::row() const
+QString Archive::Entry::name() const
{
- if (getParent()) {
- return getParent()->entries().indexOf(const_cast<Archive::Entry*>(this));
- }
- return 0;
+ return m_name;
+}
+
+void Archive::Entry::setIsDirectory(const bool isDirectory)
+{
+ m_isDirectory = isDirectory;
}
bool Archive::Entry::isDir() const
@@ -90,46 +136,48 @@ bool Archive::Entry::isDir() const
return m_isDirectory;
}
-QString Archive::Entry::name() const
+int Archive::Entry::row() const
{
- return m_name;
+ if (getParent()) {
+ return getParent()->entries().indexOf(const_cast<Archive::Entry*>(this));
+ }
+ return 0;
}
-Archive::Entry *Archive::Entry::find(const QString & name)
+Archive::Entry *Archive::Entry::find(const QString &name) const
{
- foreach(Entry *entry, m_entries) {
+ foreach (Entry *entry, m_entries) {
if (entry && (entry->name() == name)) {
return entry;
}
}
- return 0;
+ return Q_NULLPTR;
}
-Archive::Entry *Archive::Entry::findByPath(const QStringList & pieces, int index)
+Archive::Entry *Archive::Entry::findByPath(const QStringList &pieces, int index) const
{
if (index == pieces.count()) {
- return 0;
+ return Q_NULLPTR;
}
Entry *next = find(pieces.at(index));
-
if (index == pieces.count() - 1) {
return next;
}
if (next && next->isDir()) {
return next->findByPath(pieces, index + 1);
}
- return 0;
+ return Q_NULLPTR;
}
-void Archive::Entry::returnDirEntries(QList<Entry *> *store)
+void Archive::Entry::returnDirEntries(QList<Entry*> *store)
{
- foreach(Entry *entry, m_entries) {
- if (entry->isDir()) {
- store->prepend(entry);
- entry->returnDirEntries(store);
- }
+ foreach(Entry *entry, m_entries) {
+ if (entry->isDir()) {
+ store->prepend(entry);
+ entry->returnDirEntries(store);
}
+ }
}
void Archive::Entry::clear()
diff --git a/kerfuffle/archiveentry.h b/kerfuffle/archiveentry.h
index 3561780..e9779a1 100644
--- a/kerfuffle/archiveentry.h
+++ b/kerfuffle/archiveentry.h
@@ -1,6 +1,27 @@
-//
-// Created by mvlabat on 5/27/16.
-//
+/*
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
+ *
+ * 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.
+ */
#ifndef ARK_ENTRY_H
#define ARK_ENTRY_H
@@ -52,6 +73,8 @@ public:
explicit Entry(QObject *parent = Q_NULLPTR, QString fullPath = QString(), QString rootNode = QString());
~Entry();
+ void copyMetaData(const Archive::Entry *sourceEntry);
+
QVector<Entry*> entries();
const QVector<Entry*> entries() const;
void setEntryAt(int index, Entry *value);
@@ -60,13 +83,14 @@ public:
Entry *getParent() const;
void setParent(Entry *parent);
void setFullPath(const QString &fullPath);
+ QString fullPath(bool withoutTrailingSlash = false) const;
+ QString name() const;
void setIsDirectory(const bool isDirectory);
- int row() const;
bool isDir() const;
- QString name() const;
- Entry *find(const QString & name);
- Entry *findByPath(const QStringList & pieces, int index = 0);
- void returnDirEntries(QList<Entry *> *store);
+ int row() const;
+ Entry *find(const QString &name) const;
+ Entry *findByPath(const QStringList & pieces, int index = 0) const;
+ void returnDirEntries(QList<Entry*> *store);
void clear();
bool operator==(const Archive::Entry &right) const;
@@ -81,6 +105,7 @@ private:
Entry *m_parent;
QString m_fullPath;
+ QString m_fullPathWithoutTrailingSlash;
QString m_permissions;
QString m_owner;
QString m_group;
diff --git a/kerfuffle/archiveinterface.cpp b/kerfuffle/archiveinterface.cpp
index 971ec4f..c3df9ab 100644
--- a/kerfuffle/archiveinterface.cpp
+++ b/kerfuffle/archiveinterface.cpp
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -128,20 +129,96 @@ bool ReadOnlyArchiveInterface::waitForFinishedSignal()
return m_waitForFinishedSignal;
}
+int ReadOnlyArchiveInterface::moveRequiredSignals() const {
+ return 1;
+}
+
+int ReadOnlyArchiveInterface::copyRequiredSignals() const
+{
+ return 1;
+}
+
void ReadOnlyArchiveInterface::setWaitForFinishedSignal(bool value)
{
m_waitForFinishedSignal = value;
}
-QStringList ReadOnlyArchiveInterface::entryFullPaths(const QList<Archive::Entry *> &entries) const
+QStringList ReadOnlyArchiveInterface::entryFullPaths(const QList<Archive::Entry*> &entries, const bool withoutTrailingSlashes)
{
QStringList filesList;
foreach (const Archive::Entry *file, entries) {
- filesList << file->property("fullPath").toString();
+ filesList << file->fullPath(withoutTrailingSlashes);
}
return filesList;
}
+QList<Archive::Entry*> ReadOnlyArchiveInterface::entriesWithoutChildren(const QList<Archive::Entry*> &entries)
+{
+ // QMap is easy way to get entries sorted by their fullPath.
+ QMap<QString, Archive::Entry*> sortedEntries;
+ foreach (Archive::Entry *entry, entries) {
+ sortedEntries.insert(entry->fullPath(), entry);
+ }
+
+ QList<Archive::Entry*> filteredEntries;
+ QString lastFolder;
+ foreach (Archive::Entry *entry, sortedEntries) {
+ if (lastFolder.count() > 0 && entry->fullPath().startsWith(lastFolder))
+ continue;
+
+ lastFolder = (entry->fullPath().right(1) == QLatin1String("/")) ? entry->fullPath() : QString();
+ filteredEntries << entry;
+ }
+
+ return filteredEntries;
+}
+
+QStringList ReadOnlyArchiveInterface::entryPathsFromDestination(QStringList entries, const Archive::Entry *destination, int entriesWithoutChildren)
+{
+ QStringList paths = QStringList();
+ entries.sort();
+ QString lastFolder;
+ const QString destinationPath = (destination == Q_NULLPTR) ? QString() : destination->fullPath();
+
+ QString newPath;
+ int nameLength = 0;
+ foreach (const QString &entryPath, entries) {
+ if (lastFolder.count() > 0 && entryPath.startsWith(lastFolder)) {
+ // Replace last moved or copied folder path with destination path.
+ int charsCount = entryPath.count() - lastFolder.count();
+ if (entriesWithoutChildren != 1) {
+ charsCount += nameLength;
+ }
+ newPath = destinationPath + entryPath.right(charsCount);
+ }
+ else {
+ const QString name = entryPath.split(QLatin1Char('/'), QString::SkipEmptyParts).last();
+ if (entriesWithoutChildren != 1) {
+ newPath = destinationPath + name;
+ if (entryPath.right(1) == QLatin1String("/")) {
+ newPath += QLatin1Char('/');
+ }
+ }
+ else {
+ // If the mode is set to Move and there is only one passed file in the list,
+ // we have to use destination as newPath.
+ newPath = destinationPath;
+ }
+ if (entryPath.right(1) == QLatin1String("/")) {
+ nameLength = name.count() + 1; // plus slash
+ lastFolder = entryPath;
+ }
+ else {
+ nameLength = 0;
+ lastFolder = QString();
+ }
+ }
+ paths << newPath;
+ }
+
+ return paths;
+}
+
bool ReadOnlyArchiveInterface::isHeaderEncryptionEnabled() const
{
return m_isHeaderEncryptionEnabled;
diff --git a/kerfuffle/archiveinterface.h b/kerfuffle/archiveinterface.h
index ee924ca..2cb0fcc 100644
--- a/kerfuffle/archiveinterface.h
+++ b/kerfuffle/archiveinterface.h
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -97,13 +98,53 @@ public:
* @note If returning false, make sure to emit the error() signal beforewards to notify
* the user of the error condition.
*/
- virtual bool copyFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options) = 0;
+ virtual bool extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options) = 0;
bool waitForFinishedSignal();
/**
- * @return The list of filenames retrieved from the list of entries.
+ * Returns count of required finish signals for a job to be finished.
+ *
+ * These two methods are used by move and copy jobs, which in some plugins implementations have to call
+ * several processes sequentually. For instance, moving entries in zip archive is only possible if
+ * extracting the entries, deleting them, recreating destination folder structure and adding them back again.
+ */
+ virtual int moveRequiredSignals() const;
+ virtual int copyRequiredSignals() const;
+
+ /**
+ * Returns the list of filenames retrieved from the list of entries.
+ */
+ static QStringList entryFullPaths(const QList<Archive::Entry*> &entries, const bool withoutTrailingSlashes = false);
+
+ /**
+ * Returns the list of the entries, excluding their children.
+ *
+ * This method relies on entries paths so doesn't require parents to be set.
*/
- QStringList entryFullPaths(const QList<Archive::Entry *> &entries) const;
+ static QList<Archive::Entry*> entriesWithoutChildren(const QList<Archive::Entry*> &entries);
+
+ /**
+ * Returns the string list of entry paths, which will be a result of adding/moving/copying entries.
+ *
+ * @param entries The entries which will be added/moved/copied.
+ * @param destination Destination path within the archive to which entries have to be added. For renaming an entry
+ * the path has to contain a new filename too.
+ * @param entriesWithoutChildren Entries count, excluding their children. For AddJob or CopyJob 0 MUST be passed.
+ *
+ * @return For entries
+ * some/dir/
+ * some/dir/entry
+ * some/dir/some/entry
+ * some/another/entry
+ * and destination
+ * some/destination
+ * will return
+ * some/destination/dir/
+ * some/destination/dir/entry
+ * some/destination/dir/some/enty
+ * some/destination/entry
+ */
+ static QStringList entryPathsFromDestination(QStringList entries, const Archive::Entry *destination, int entriesWithoutChildren);
virtual bool doKill();
virtual bool doSuspend();
@@ -146,6 +187,10 @@ class KERFUFFLE_EXPORT ReadWriteArchiveInterface: public ReadOnlyArchiveInterfac
{
Q_OBJECT
public:
+ enum OperationMode {
+ List, Extract, Add, Move, Copy, Delete, Comment, Test
+ };
+
explicit ReadWriteArchiveInterface(QObject *parent, const QVariantList & args);
virtual ~ReadWriteArchiveInterface();
@@ -153,7 +198,9 @@ public:
//see archive.h for a list of what the compressionoptions might
//contain
- virtual bool addFiles(const QList<Archive::Entry*> &files, const CompressionOptions& options) = 0;
+ virtual bool addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options) = 0;
+ virtual bool moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options) = 0;
+ virtual bool copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options) = 0;
virtual bool deleteFiles(const QList<Archive::Entry*> &files) = 0;
virtual bool addComment(const QString &comment) = 0;
};
diff --git a/kerfuffle/cliinterface.cpp b/kerfuffle/cliinterface.cpp
index cecb263..43fc155 100644
--- a/kerfuffle/cliinterface.cpp
+++ b/kerfuffle/cliinterface.cpp
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -94,6 +95,11 @@ void CliInterface::setListEmptyLines(bool emptyLines)
m_listEmptyLines = emptyLines;
}
+int CliInterface::copyRequiredSignals() const
+{
+ return 2;
+}
+
bool CliInterface::list()
{
resetParsing();
@@ -109,14 +115,14 @@ bool CliInterface::list()
return true;
}
-bool CliInterface::copyFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options)
+bool CliInterface::extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options)
{
qCDebug(ARK) << Q_FUNC_INFO << "to" << destinationDirectory;
cacheParameterList();
- m_operationMode = Copy;
+ m_operationMode = Extract;
m_compressionOptions = options;
- m_copiedFiles = files;
+ m_extractedFiles = files;
m_extractDestDir = destinationDirectory;
const QStringList extractArgs = m_param.value(ExtractArgs).toStringList();
@@ -130,10 +136,10 @@ bool CliInterface::copyFiles(const QList<Archive::Entry*> &files, const QString
}
// Populate the argument list.
- const QStringList args = substituteCopyVariables(extractArgs,
- files,
- options.value(QStringLiteral("PreservePaths")).toBool(),
- password());
+ const QStringList args = substituteExtractVariables(extractArgs,
+ files,
+ options.value(QStringLiteral("PreservePaths")).toBool(),
+ password());
QUrl destDir = QUrl(destinationDirectory);
QDir::setCurrent(destDir.adjusted(QUrl::RemoveScheme).url());
@@ -164,7 +170,7 @@ bool CliInterface::copyFiles(const QList<Archive::Entry*> &files, const QString
return true;
}
-bool CliInterface::addFiles(const QList<Archive::Entry*> &files, const CompressionOptions& options)
+bool CliInterface::addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options)
{
cacheParameterList();
@@ -172,6 +178,49 @@ bool CliInterface::addFiles(const QList<Archive::Entry*> &files, const Compressi
const QStringList addArgs = m_param.value(AddArgs).toStringList();
+ QList<Archive::Entry*> filesToPass = QList<Archive::Entry*>();
+ // If destination path is specified, we have recreate its structure inside the temp directory
+ // and then place symlinks of targeted files there.
+ const QString destinationPath = (destination == Q_NULLPTR)
+ ? QString()
+ : destination->fullPath();
+ if (!destinationPath.isEmpty()) {
+ m_extractTempDir = new QTemporaryDir();
+ const QString absoluteDestinationPath = m_extractTempDir->path() + QLatin1Char('/') + destinationPath;
+
+ QDir qDir;
+ qDir.mkpath(absoluteDestinationPath);
+
+ QObject *preservedParent = Q_NULLPTR;
+ foreach (Archive::Entry *file, files) {
+ // The entries may have parent. We have to save and apply it to our new entry in order to prevent memory
+ // leaks.
+ if (preservedParent == Q_NULLPTR) {
+ preservedParent = file->parent();
+ }
+
+ const QString filePath = QDir::currentPath() + QLatin1Char('/') + file->fullPath(true);
+ const QString newFilePath = absoluteDestinationPath + file->fullPath(true);
+ if (QFile::link(filePath, newFilePath)) {
+ qCDebug(ARK) << "Symlink's created:" << filePath << newFilePath;
+ }
+ else {
+ qCDebug(ARK) << "Can't create symlink" << filePath << newFilePath;
+ delete m_extractTempDir;
+ m_extractTempDir = Q_NULLPTR;
+ return false;
+ }
+ }
+
+ qCDebug(ARK) << "Changing working dir again to " << m_extractTempDir->path();
+ QDir::setCurrent(m_extractTempDir->path());
+
+ filesToPass.push_back(new Archive::Entry(preservedParent, destinationPath.split(QLatin1Char('/'), QString::SkipEmptyParts).at(0)));
+ }
+ else {
+ filesToPass = files;
+ }
+
if (addArgs.contains(QStringLiteral("$PasswordSwitch")) &&
options.value(QStringLiteral("PasswordProtectedHint")).toBool() &&
password().isEmpty()) {
@@ -184,7 +233,7 @@ bool CliInterface::addFiles(const QList<Archive::Entry*> &files, const Compressi
int compLevel = options.value(QStringLiteral("CompressionLevel"), -1).toInt();
const auto args = substituteAddVariables(m_param.value(AddArgs).toStringList(),
- files,
+ filesToPass,
password(),
isHeaderEncryptionEnabled(),
compLevel);
@@ -192,6 +241,39 @@ bool CliInterface::addFiles(const QList<Archive::Entry*> &files, const Compressi
return runProcess(m_param.value(AddProgram).toStringList(), args);
}
+bool CliInterface::moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ cacheParameterList();
+ m_operationMode = Move;
+
+ m_removedFiles = files;
+ QList<Archive::Entry*> withoutChildren = entriesWithoutChildren(files);
+ setNewMovedFiles(files, destination, withoutChildren.count());
+
+ const auto moveArgs = m_param.value(MoveArgs).toStringList();
+
+ const auto args = substituteMoveVariables(moveArgs, withoutChildren, destination, password());
+
+ return runProcess(m_param.value(MoveProgram).toStringList(), args);
+}
+
+bool CliInterface::copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ m_oldWorkingDir = QDir::currentPath();
+ m_tempExtractDir = new QTemporaryDir();
+ m_tempAddDir = new QTemporaryDir();
+ QDir::setCurrent(m_tempExtractDir->path());
+ m_passedFiles = files;
+ m_passedDestination = destination;
+ m_passedOptions = options;
+ m_passedOptions[QStringLiteral("PreservePaths")] = true;
+
+ m_subOperation = Extract;
+ connect(this, &CliInterface::finished, this, &CliInterface::continueCopying);
+
+ return extractFiles(files, QDir::currentPath(), m_passedOptions);
+}
+
bool CliInterface::deleteFiles(const QList<Archive::Entry*> &files)
{
cacheParameterList();
@@ -240,6 +322,7 @@ bool CliInterface::runProcess(const QStringList& programNames, const QStringList
qCDebug(ARK) << "Executing" << programPath << arguments << "within directory" << QDir::currentPath();
#ifdef Q_OS_WIN
+
m_process = new KProcess;
#else
m_process = new KPtyProcess;
@@ -252,9 +335,13 @@ bool CliInterface::runProcess(const QStringList& programNames, const QStringList
connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(readStdout()), Qt::DirectConnection);
- if (m_operationMode == Copy) {
+ if (m_operationMode == Extract) {
// Extraction jobs need a dedicated post-processing function.
- connect(m_process, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &CliInterface::copyProcessFinished, Qt::DirectConnection);
+ connect(m_process,
+ static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished),
+ this,
+ &CliInterface::extractProcessFinished,
+ Qt::DirectConnection);
} else {
connect(m_process, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &CliInterface::processFinished, Qt::DirectConnection);
}
@@ -285,14 +372,22 @@ void CliInterface::processFinished(int exitCode, QProcess::ExitStatus exitStatus
return;
}
- if (m_operationMode == Delete) {
+ if (m_operationMode == Delete || m_operationMode == Move) {
QStringList removedFullPaths = entryFullPaths(m_removedFiles);
foreach (const QString &fullPath, removedFullPaths) {
emit entryRemoved(fullPath);
}
+ foreach (Archive::Entry *e, m_newMovedFiles) {
+ emit entry(e);
+ }
+ m_newMovedFiles.clear();
}
if (m_operationMode == Add) {
+ if (m_extractTempDir) {
+ delete m_extractTempDir;
+ m_extractTempDir = Q_NULLPTR;
+ }
list();
} else if (m_operationMode == List && isCorrupt()) {
Kerfuffle::LoadCorruptQuery query(filename());
@@ -311,9 +406,9 @@ void CliInterface::processFinished(int exitCode, QProcess::ExitStatus exitStatus
}
}
-void CliInterface::copyProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
+void CliInterface::extractProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
- Q_ASSERT(m_operationMode == Copy);
+ Q_ASSERT(m_operationMode == Extract);
m_exitCode = exitCode;
qCDebug(ARK) << "Extraction process finished, exitcode:" << exitCode << "exitstatus:" << exitStatus;
@@ -338,7 +433,7 @@ void CliInterface::copyProcessFinished(int exitCode, QProcess::ExitStatus exitSt
emit error(i18n("Extraction failed. Make sure you provided the correct password and that enough space is available."));
setPassword(QString());
}
- copyProcessCleanup();
+ cleanUpExtracting();
emit finished(false);
return;
}
@@ -348,34 +443,59 @@ void CliInterface::copyProcessFinished(int exitCode, QProcess::ExitStatus exitSt
emit error(i18ncp("@info",
"Could not move the extracted file to the destination directory.",
"Could not move the extracted files to the destination directory.",
- m_copiedFiles.size()));
- copyProcessCleanup();
+ m_extractedFiles.size()));
+ cleanUpExtracting();
emit finished(false);
return;
}
- copyProcessCleanup();
+ cleanUpExtracting();
}
}
if (m_compressionOptions.value(QStringLiteral("DragAndDrop")).toBool()) {
- if (!moveDroppedFilesToDest(m_copiedFiles, m_extractDestDir)) {
+ if (!moveDroppedFilesToDest(m_extractedFiles, m_extractDestDir)) {
emit error(i18ncp("@info",
"Could not move the extracted file to the destination directory.",
"Could not move the extracted files to the destination directory.",
- m_copiedFiles.size()));
- copyProcessCleanup();
+ m_extractedFiles.size()));
+ cleanUpExtracting();
emit finished(false);
return;
}
- copyProcessCleanup();
+ cleanUpExtracting();
}
emit progress(1.0);
emit finished(true);
}
+void CliInterface::continueCopying(bool result)
+{
+ if (!result) {
+ finishCopying(false);
+ return;
+ }
+
+ switch (m_subOperation) {
+ case Extract:
+ m_subOperation = Add;
+ m_passedFiles = entriesWithoutChildren(m_passedFiles);
+ if (!setAddedFiles() || !addFiles(m_tempAddedFiles, m_passedDestination, m_passedOptions)) {
+ finishCopying(false);
+ }
+ break;
+
+ case Add:
+ finishCopying(true);
+ break;
+
+ default:
+ Q_ASSERT(false);
+ }
+}
+
bool CliInterface::moveDroppedFilesToDest(const QList<Archive::Entry*> &files, const QString &finalDest)
{
// Move extracted files from a QTemporaryDir to the final destination.
@@ -388,8 +508,8 @@ bool CliInterface::moveDroppedFilesToDest(const QList<Archive::Entry*> &files, c
foreach (const Archive::Entry *file, files) {
- QFileInfo relEntry(file->property("fullPath").toString().remove(file->rootNode));
- QFileInfo absSourceEntry(QDir::current().absolutePath() + QLatin1Char('/') + file->property("fullPath").toString());
+ QFileInfo relEntry(file->fullPath().remove(file->rootNode));
+ QFileInfo absSourceEntry(QDir::current().absolutePath() + QLatin1Char('/') + file->fullPath());
QFileInfo absDestEntry(finalDestDir.path() + QLatin1Char('/') + relEntry.filePath());
if (absSourceEntry.isDir()) {
@@ -466,7 +586,7 @@ bool CliInterface::isEmptyDir(const QDir &dir)
return d.count() == 0;
}
-void CliInterface::copyProcessCleanup()
+void CliInterface::cleanUpExtracting()
{
if (!m_oldWorkingDir.isEmpty()) {
QDir::setCurrent(m_oldWorkingDir);
@@ -478,6 +598,14 @@ void CliInterface::copyProcessCleanup()
}
}
+void CliInterface::finishCopying(bool result)
+{
+ disconnect(this, &CliInterface::finished, this, &CliInterface::continueCopying);
+ emit progress(1.0);
+ emit finished(result);
+ cleanUp();
+}
+
bool CliInterface::moveToDestination(const QDir &tempDir, const QDir &destDir, bool preservePaths)
{
qCDebug(ARK) << "Moving extracted files from temp dir" << tempDir.path() << "to final destination" << destDir.path();
@@ -585,7 +713,7 @@ QStringList CliInterface::substituteListVariables(const QStringList &listArgs, c
return args;
}
-QStringList CliInterface::substituteCopyVariables(const QStringList &extractArgs, const QList<Archive::Entry*> &entries, bool preservePaths, const QString &password)
+QStringList CliInterface::substituteExtractVariables(const QStringList &extractArgs, const QList<Archive::Entry*> &entries, bool preservePaths, const QString &password)
{
// Required if we call this function from unit tests.
cacheParameterList();
@@ -610,7 +738,7 @@ QStringList CliInterface::substituteCopyVariables(const QStringList &extractArgs
}
if (arg == QLatin1String("$Files")) {
- args << copyFilesList(entries);
+ args << extractFilesList(entries);
continue;
}
@@ -649,7 +777,7 @@ QStringList CliInterface::substituteAddVariables(const QStringList &addArgs, con
}
if (arg == QLatin1String("$Files")) {
- args << entryFullPaths(entries);
+ args << entryFullPaths(entries, true);
continue;
}
@@ -663,6 +791,40 @@ QStringList CliInterface::substituteAddVariables(const QStringList &addArgs, con
return args;
}
+QStringList CliInterface::substituteMoveVariables(const QStringList &moveArgs, const QList<Archive::Entry*> &entriesWithoutChildren, const Archive::Entry *destination, const QString &password)
+{
+ // Required if we call this function from unit tests.
+ cacheParameterList();
+
+ QStringList args;
+ foreach (const QString& arg, moveArgs) {
+ qCDebug(ARK) << "Processing argument " << arg;
+
+ if (arg == QLatin1String("$Archive")) {
+ args << filename();
+ continue;
+ }
+
+ if (arg == QLatin1String("$PasswordSwitch")) {
+ args << passwordSwitch(password);
+ continue;
+ }
+
+ if (arg == QLatin1String("$PathPairs")) {
+ args << entryPathDestinationPairs(entriesWithoutChildren, destination);
+ continue;
+ }
+
+ // Simple argument (e.g. a in 7z), nothing to substitute, just add it to the list.
+ args << arg;
+ }
+
+ // Remove empty strings, if any.
+ args.removeAll(QString());
+
+ return args;
+}
+
QStringList CliInterface::substituteDeleteVariables(const QStringList &deleteArgs, const QList<Archive::Entry*> &entries, const QString &password)
{
cacheParameterList();
@@ -683,7 +845,7 @@ QStringList CliInterface::substituteDeleteVariables(const QStringList &deleteArg
if (arg == QLatin1String("$Files")) {
foreach (const Archive::Entry *e, entries) {
- args << escapeFileName(e->property("fullPath").toString());
+ args << escapeFileName(e->fullPath(true));
}
continue;
}
@@ -751,6 +913,53 @@ QStringList CliInterface::substituteTestVariables(const QStringList &testArgs)
return args;
}
+void CliInterface::setNewMovedFiles(const QList<Archive::Entry*> &entries, const Archive::Entry *destination, int entriesWithoutChildren)
+{
+ m_newMovedFiles.clear();
+ QMap<QString, const Archive::Entry*> entryMap;
+ foreach (const Archive::Entry* entry, entries) {
+ entryMap.insert(entry->fullPath(), entry);
+ }
+
+ QString lastFolder;
+
+ QString newPath;
+ int nameLength = 0;
+ foreach (const Archive::Entry* entry, entryMap) {
+ if (lastFolder.count() > 0 && entry->fullPath().startsWith(lastFolder)) {
+ // Replace last moved or copied folder path with destination path.
+ int charsCount = entry->fullPath().count() - lastFolder.count();
+ if (entriesWithoutChildren > 1) {
+ charsCount += nameLength;
+ }
+ newPath = destination->fullPath() + entry->fullPath().right(charsCount);
+ }
+ else {
+ if (entriesWithoutChildren > 1) {
+ newPath = destination->fullPath() + entry->name();
+ }
+ else {
+ // If there is only one passed file in the list,
+ // we have to use destination as newPath.
+ newPath = destination->fullPath(true);
+ }
+ if (entry->isDir()) {
+ newPath += QLatin1Char('/');
+ nameLength = entry->name().count() + 1; // plus slash
+ lastFolder = entry->fullPath();
+ }
+ else {
+ nameLength = 0;
+ lastFolder = QString();
+ }
+ }
+ Archive::Entry *newEntry = new Archive::Entry(Q_NULLPTR);
+ newEntry->copyMetaData(entry);
+ newEntry->setFullPath(newPath);
+ m_newMovedFiles << newEntry;
+ }
+}
+
QString CliInterface::preservePathSwitch(bool preservePaths) const
{
Q_ASSERT(m_param.contains(PreservePathSwitch));
@@ -816,11 +1025,11 @@ QString CliInterface::compressionLevelSwitch(int level) const
return compLevelSwitch;
}
-QStringList CliInterface::copyFilesList(const QList<Archive::Entry*> &entries) const
+QStringList CliInterface::extractFilesList(const QList<Archive::Entry*> &entries) const
{
QStringList filesList;
foreach (const Archive::Entry *e, entries) {
- filesList << escapeFileName(e->property("fullPath").toString());
+ filesList << escapeFileName(e->fullPath(true));
}
return filesList;
@@ -864,6 +1073,17 @@ bool CliInterface::passwordQuery()
return true;
}
+void CliInterface::cleanUp()
+{
+ qDeleteAll(m_tempAddedFiles);
+ m_tempAddedFiles.clear();
+ QDir::setCurrent(m_oldWorkingDir);
+ delete m_tempExtractDir;
+ m_tempExtractDir = Q_NULLPTR;
+ delete m_tempAddDir;
+ m_tempAddDir = Q_NULLPTR;
+}
+
void CliInterface::readStdout(bool handleAll)
{
//when hacking this function, please remember the following:
@@ -941,11 +1161,25 @@ void CliInterface::readStdout(bool handleAll)
}
}
+bool CliInterface::setAddedFiles()
+{
+ QDir::setCurrent(m_tempAddDir->path());
+ foreach (const Archive::Entry *file, m_passedFiles) {
+ const QString oldPath = m_tempExtractDir->path() + QLatin1Char('/') + file->fullPath(true);
+ const QString newPath = m_tempAddDir->path() + QLatin1Char('/') + file->name();
+ if (!QFile::rename(oldPath, newPath)) {
+ return false;
+ }
+ m_tempAddedFiles << new Archive::Entry(Q_NULLPTR, file->name());
+ }
+ return true;
+}
+
void CliInterface::handleLine(const QString& line)
{
// TODO: This should be implemented by each plugin; the way progress is
// shown by each CLI application is subject to a lot of variation.
- if ((m_operationMode == Copy || m_operationMode == Add) && m_param.contains(CaptureProgress) && m_param.value(CaptureProgress).toBool()) {
+ if ((m_operationMode == Extract || m_operationMode == Add) && m_param.contains(CaptureProgress) && m_param.value(CaptureProgress).toBool()) {
//read the percentage
int pos = line.indexOf(QLatin1Char( '%' ));
if (pos > 1) {
@@ -955,7 +1189,7 @@ void CliInterface::handleLine(const QString& line)
}
}
- if (m_operationMode == Copy) {
+ if (m_operationMode == Extract) {
if (checkForPasswordPromptMessage(line)) {
qCDebug(ARK) << "Found a password prompt";
@@ -1204,6 +1438,20 @@ QString CliInterface::escapeFileName(const QString& fileName) const
return fileName;
}
+QStringList CliInterface::entryPathDestinationPairs(const QList<Archive::Entry*> &entriesWithoutChildren, const Archive::Entry *destination)
+{
+ QStringList pairList;
+ if (entriesWithoutChildren.count() > 1) {
+ foreach (const Archive::Entry *file, entriesWithoutChildren) {
+ pairList << file->fullPath(true) << destination->fullPath() + file->name();
+ }
+ }
+ else {
+ pairList << entriesWithoutChildren.at(0)->fullPath(true) << destination->fullPath(true);
+ }
+ return pairList;
+}
+
void CliInterface::writeToProcess(const QByteArray& data)
{
Q_ASSERT(m_process);
diff --git a/kerfuffle/cliinterface.h b/kerfuffle/cliinterface.h
index c152981..072a278 100644
--- a/kerfuffle/cliinterface.h
+++ b/kerfuffle/cliinterface.h
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -31,6 +32,7 @@
#include "archiveinterface.h"
#include "archiveentry.h"
#include "kerfuffle_export.h"
+#include "part/archivemodel.h"
#include <QProcess>
#include <QRegularExpression>
@@ -237,6 +239,25 @@ enum CliInterfaceParameters {
*/
AddArgs,
+ ///////////////[ MOVE ]/////////////
+
+ /**
+ * QStringList
+ * The names to the program that will handle adding in this
+ * archive format (eg "rar"). Will be searched for in PATH
+ */
+ MoveProgram,
+ /**
+ * QStringList
+ * The arguments that are passed to the program above for
+ * moving inside the archive. Special strings that will be
+ * substituted:
+ * $Archive - the path of the archive
+ * $Files - the files selected to be moved
+ * $Destinations - new path of each file selected to be moved
+ */
+ MoveArgs,
+
///////////////[ ENCRYPT ]/////////////
/**
@@ -274,17 +295,18 @@ class KERFUFFLE_EXPORT CliInterface : public ReadWriteArchiveInterface
Q_OBJECT
public:
- enum OperationMode {
- List, Copy, Add, Delete, Comment, Test
- };
OperationMode m_operationMode;
explicit CliInterface(QObject *parent, const QVariantList & args);
virtual ~CliInterface();
+ virtual int copyRequiredSignals() const;
+
virtual bool list() Q_DECL_OVERRIDE;
- virtual bool copyFiles(const QList<Archive::Entry*> &files, const QString& destinationDirectory, const ExtractionOptions& options) Q_DECL_OVERRIDE;
- virtual bool addFiles(const QList<Archive::Entry*> &files, const CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options) Q_DECL_OVERRIDE;
+ virtual bool addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options) Q_DECL_OVERRIDE;
virtual bool deleteFiles(const QList<Archive::Entry*> &files) Q_DECL_OVERRIDE;
virtual bool addComment(const QString &comment) Q_DECL_OVERRIDE;
virtual bool testArchive() Q_DECL_OVERRIDE;
@@ -310,13 +332,19 @@ public:
bool moveToDestination(const QDir &tempDir, const QDir &destDir, bool preservePaths);
QStringList substituteListVariables(const QStringList &listArgs, const QString &password);
- QStringList substituteCopyVariables(const QStringList &extractArgs, const QList<Archive::Entry*> &entries, bool preservePaths, const QString &password);
+ QStringList substituteExtractVariables(const QStringList &extractArgs, const QList<Archive::Entry*> &entries, bool preservePaths, const QString &password);
QStringList substituteAddVariables(const QStringList &addArgs, const QList<Archive::Entry*> &entries, const QString &password, bool encryptHeader, int compLevel);
+ QStringList substituteMoveVariables(const QStringList &moveArgs, const QList<Archive::Entry*> &entriesWithoutChildren, const Archive::Entry *destination, const QString &password);
QStringList substituteDeleteVariables(const QStringList &deleteArgs, const QList<Archive::Entry*> &entries, const QString &password);
QStringList substituteCommentVariables(const QStringList &commentArgs, const QString &commentFile);
QStringList substituteTestVariables(const QStringList &testArgs);
/**
+ * @see ArchiveModel::entryPathsFromDestination
+ */
+ void setNewMovedFiles(const QList<Archive::Entry*> &entries, const Archive::Entry *destination, int entriesWithoutChildren);
+
+ /**
* @return The preserve path switch, according to the @p preservePaths extraction option.
*/
QString preservePathSwitch(bool preservePaths) const;
@@ -339,10 +367,11 @@ public:
/**
* @return The list of selected files to extract.
*/
- QStringList copyFilesList(const QList<Archive::Entry*> &files) const;
+ QStringList extractFilesList(const QList<Archive::Entry*> &files) const;
protected:
+ bool setAddedFiles();
virtual void handleLine(const QString& line);
virtual void cacheParameterList();
@@ -368,8 +397,18 @@ protected:
*/
bool passwordQuery();
+ void cleanUp();
+
+ QString m_oldWorkingDir;
ParameterList m_param;
int m_exitCode;
+ QTemporaryDir *m_tempExtractDir;
+ QTemporaryDir *m_tempAddDir;
+ OperationMode m_subOperation;
+ QList<Archive::Entry*> m_passedFiles;
+ QList<Archive::Entry*> m_tempAddedFiles;
+ Archive::Entry *m_passedDestination;
+ CompressionOptions m_passedOptions;
protected slots:
virtual void readStdout(bool handleAll = false);
@@ -405,6 +444,16 @@ private:
virtual QString escapeFileName(const QString &fileName) const;
/**
+ * Returns a list of path pairs which will be supplied to rn command.
+ * <src_file_1> <dest_file_1> [ <src_file_2> <dest_file_2> ... ]
+ * Also constructs a list of new entries resulted in moving.
+ *
+ * @param entriesWithoutChildren List of archive entries
+ * @param destination Must be a directory entry if QList contains more that one entry
+ */
+ QStringList entryPathDestinationPairs(const QList<Archive::Entry*> &entriesWithoutChildren, const Archive::Entry *destination);
+
+ /**
* Wrapper around KProcess::write() or KPtyDevice::write(), depending on
* the platform.
*/
@@ -417,7 +466,9 @@ private:
*/
bool isEmptyDir(const QDir &dir);
- void copyProcessCleanup();
+ void cleanUpExtracting();
+
+ void finishCopying(bool result);
QByteArray m_stdOutData;
QRegularExpression m_passwordPromptPattern;
@@ -430,20 +481,23 @@ private:
#endif
QList<Archive::Entry*> m_removedFiles;
+ QList<Archive::Entry*> m_newMovedFiles;
bool m_listEmptyLines;
bool m_abortingOperation;
QString m_storedFileName;
CompressionOptions m_compressionOptions;
- QString m_oldWorkingDir;
QString m_extractDestDir;
QTemporaryDir *m_extractTempDir;
QTemporaryFile *m_commentTempFile;
- QList<Archive::Entry*> m_copiedFiles;
+ QList<Archive::Entry*> m_extractedFiles;
+
+protected slots:
+ virtual void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
private slots:
- void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
- void copyProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void extractProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void continueCopying(bool result);
};
}
diff --git a/kerfuffle/compressionoptionswidget.cpp b/kerfuffle/compressionoptionswidget.cpp
new file mode 100644
index 0000000..4340009
--- /dev/null
+++ b/kerfuffle/compressionoptionswidget.cpp
@@ -0,0 +1,158 @@
+/*
+ * ark -- archiver for the KDE project
+ *
+ * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#include "compressionoptionswidget.h"
+#include "ark_debug.h"
+#include "archiveformat.h"
+#include "pluginmanager.h"
+
+#include <KColorScheme>
+#include <KPluginMetaData>
+
+#include <QMimeDatabase>
+
+namespace Kerfuffle
+{
+CompressionOptionsWidget::CompressionOptionsWidget(QWidget *parent,
+ const CompressionOptions &opts)
+ : QWidget(parent)
+ , m_opts(opts)
+{
+ setupUi(this);
+
+ KColorScheme colorScheme(QPalette::Active, KColorScheme::View);
+ pwdWidget->setBackgroundWarningColor(colorScheme.background(KColorScheme::NegativeBackground).color());
+ pwdWidget->setPasswordStrengthMeterVisible(false);
+}
+
+CompressionOptions CompressionOptionsWidget::commpressionOptions() const
+{
+ CompressionOptions opts;
+ opts[QStringLiteral("CompressionLevel")] = compLevelSlider->value();
+
+ return opts;
+}
+
+int CompressionOptionsWidget::compressionLevel() const
+{
+ return compLevelSlider->value();
+}
+
+void CompressionOptionsWidget::setEncryptionVisible(bool visible)
+{
+ collapsibleEncryption->setVisible(visible);
+}
+
+QString CompressionOptionsWidget::password() const
+{
+ return pwdWidget->password();
+}
+
+void CompressionOptionsWidget::updateWidgets()
+{
+ const KPluginMetaData metadata = PluginManager().preferredPluginFor(m_mimetype)->metaData();
+ const ArchiveFormat archiveFormat = ArchiveFormat::fromMetadata(m_mimetype, metadata);
+ Q_ASSERT(archiveFormat.isValid());
+
+ if (archiveFormat.encryptionType() != Archive::Unencrypted) {
+ collapsibleEncryption->setEnabled(true);
+ collapsibleEncryption->setToolTip(QString());
+ pwdWidget->setEnabled(true);
+
+ if (archiveFormat.encryptionType() == Archive::HeaderEncrypted) {
+ encryptHeaderCheckBox->setEnabled(true);
+ encryptHeaderCheckBox->setToolTip(QString());
+ } else {
+ encryptHeaderCheckBox->setEnabled(false);
+ // Show the tooltip only if the encryption is still enabled.
+ // This is needed because if the new filter is e.g. tar, the whole encryption group gets disabled.
+ if (collapsibleEncryption->isEnabled() && collapsibleEncryption->isExpanded()) {
+ encryptHeaderCheckBox->setToolTip(i18n("Protection of the list of files is not possible with the %1 format.",
+ m_mimetype.comment()));
+ } else {
+ encryptHeaderCheckBox->setToolTip(QString());
+ }
+ }
+
+ } else {
+ collapsibleEncryption->setEnabled(false);
+ collapsibleEncryption->setToolTip(i18n("Protection of the archive with password is not possible with the %1 format.",
+ m_mimetype.comment()));
+ pwdWidget->setEnabled(false);
+ encryptHeaderCheckBox->setToolTip(QString());
+ }
+
+
+ if (archiveFormat.maxCompressionLevel() == 0) {
+ collapsibleCompression->setEnabled(false);
+ collapsibleCompression->setToolTip(i18n("It is not possible to set compression level for the %1 format.",
+ m_mimetype.comment()));
+ } else {
+ collapsibleCompression->setEnabled(true);
+ collapsibleCompression->setToolTip(QString());
+ compLevelSlider->setMinimum(archiveFormat.minCompressionLevel());
+ compLevelSlider->setMaximum(archiveFormat.maxCompressionLevel());
+ if (m_opts.contains(QStringLiteral("CompressionLevel"))) {
+ compLevelSlider->setValue(m_opts.value(QStringLiteral("CompressionLevel")).toInt());
+ } else {
+ compLevelSlider->setValue(archiveFormat.defaultCompressionLevel());
+ }
+ }
+}
+
+void CompressionOptionsWidget::setMimeType(const QMimeType &mimeType)
+{
+ m_mimetype = mimeType;
+ updateWidgets();
+}
+
+bool CompressionOptionsWidget::isEncryptionAvailable() const
+{
+ return collapsibleEncryption->isEnabled();
+}
+
+bool CompressionOptionsWidget::isEncryptionEnabled() const
+{
+ return isEncryptionAvailable() && collapsibleEncryption->isExpanded();
+}
+
+bool CompressionOptionsWidget::isHeaderEncryptionAvailable() const
+{
+ return isEncryptionEnabled() && encryptHeaderCheckBox->isEnabled();
+}
+
+bool CompressionOptionsWidget::isHeaderEncryptionEnabled() const
+{
+ return isHeaderEncryptionAvailable() && encryptHeaderCheckBox->isChecked();
+}
+
+KNewPasswordWidget::PasswordStatus CompressionOptionsWidget::passwordStatus() const
+{
+ return pwdWidget->passwordStatus();
+}
+
+}
diff --git a/kerfuffle/compressionoptionswidget.h b/kerfuffle/compressionoptionswidget.h
new file mode 100644
index 0000000..350aa7a
--- /dev/null
+++ b/kerfuffle/compressionoptionswidget.h
@@ -0,0 +1,67 @@
+/*
+ * ark -- archiver for the KDE project
+ *
+ * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef COMPRESSIONOPTIONSWIDGET_H
+#define COMPRESSIONOPTIONSWIDGET_H
+
+#include "kerfuffle_export.h"
+#include "archive_kerfuffle.h"
+#include "ui_compressionoptionswidget.h"
+
+#include <QMimeType>
+#include <QWidget>
+
+namespace Kerfuffle
+{
+class KERFUFFLE_EXPORT CompressionOptionsWidget : public QWidget, public Ui::CompressionOptionsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit CompressionOptionsWidget(QWidget *parent = Q_NULLPTR,
+ const CompressionOptions &opts = QHash<QString, QVariant>());
+ int compressionLevel() const;
+ QString password() const;
+ CompressionOptions commpressionOptions() const;
+ bool isEncryptionAvailable() const;
+ bool isEncryptionEnabled() const;
+ bool isHeaderEncryptionAvailable() const;
+ bool isHeaderEncryptionEnabled() const;
+ KNewPasswordWidget::PasswordStatus passwordStatus() const;
+
+ void setEncryptionVisible(bool visible);
+ void setMimeType(const QMimeType &mimeType);
+
+private:
+ void updateWidgets();
+
+ QMimeType m_mimetype;
+ CompressionOptions m_opts;
+};
+}
+
+#endif
diff --git a/kerfuffle/compressionoptionswidget.ui b/kerfuffle/compressionoptionswidget.ui
new file mode 100644
index 0000000..b0a36f4
--- /dev/null
+++ b/kerfuffle/compressionoptionswidget.ui
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CompressionOptionsWidget</class>
+ <widget class="QWidget" name="CompressionOptionsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>384</width>
+ <height>62</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="KCollapsibleGroupBox" name="collapsibleCompression">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Compression</string>
+ </property>
+ <property name="expanded">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Min</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Max</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Level:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QSlider" name="compLevelSlider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>9</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="KCollapsibleGroupBox" name="collapsibleEncryption">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Password Protection</string>
+ </property>
+ <property name="expanded">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="KNewPasswordWidget" name="pwdWidget" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="encryptHeaderCheckBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Ask for password before showing the list of files in the archive</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KCollapsibleGroupBox</class>
+ <extends>QWidget</extends>
+ <header>kcollapsiblegroupbox.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KNewPasswordWidget</class>
+ <extends>QWidget</extends>
+ <header location="global">KNewPasswordWidget</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/kerfuffle/createdialog.cpp b/kerfuffle/createdialog.cpp
index ba6a475..dd21d88 100644
--- a/kerfuffle/createdialog.cpp
+++ b/kerfuffle/createdialog.cpp
@@ -35,7 +35,6 @@
#include "kerfuffle/archive_kerfuffle.h"
#include "mimetypes.h"
-#include <KColorScheme>
#include <KMessageBox>
#include <KSharedConfig>
#include <KUrlRequester>
@@ -80,17 +79,12 @@ CreateDialog::CreateDialog(QWidget *parent,
m_ui->destFolderUrlRequester->setUrl(startDir);
}
- KColorScheme colorScheme(QPalette::Active, KColorScheme::View);
- m_ui->pwdWidget->setBackgroundWarningColor(colorScheme.background(KColorScheme::NegativeBackground).color());
- m_ui->pwdWidget->setPasswordStrengthMeterVisible(false);
-
// Populate combobox with mimetypes.
foreach (const QString &type, m_supportedMimeTypes) {
m_ui->mimeComboBox->addItem(QMimeDatabase().mimeTypeForName(type).comment());
}
connect(m_ui->filenameLineEdit, &QLineEdit::textChanged, this, &CreateDialog::slotFileNameEdited);
- connect(m_ui->collapsibleEncryption, &KCollapsibleGroupBox::expandedChanged, this, &CreateDialog::slotEncryptionToggled);
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(this, &QDialog::accepted, this, &CreateDialog::slotUpdateDefaultMimeType);
@@ -99,6 +93,8 @@ CreateDialog::CreateDialog(QWidget *parent,
m_vlayout->addWidget(m_ui);
+ m_ui->optionsWidget->setMimeType(currentMimeType());
+
loadConfiguration();
layout()->setSizeConstraint(QLayout::SetFixedSize);
@@ -119,30 +115,7 @@ void CreateDialog::slotFileNameEdited(const QString &fileName)
void CreateDialog::slotUpdateWidgets(int index)
{
- const QMimeType mimeType = QMimeDatabase().mimeTypeForName(m_supportedMimeTypes.at(index));
- const KPluginMetaData metadata = m_pluginManger.preferredPluginFor(mimeType)->metaData();
- const ArchiveFormat archiveFormat = ArchiveFormat::fromMetadata(mimeType, metadata);
- Q_ASSERT(archiveFormat.isValid());
-
- if (archiveFormat.encryptionType() != Archive::Unencrypted) {
- m_ui->collapsibleEncryption->setEnabled(true);
- m_ui->collapsibleEncryption->setToolTip(QString());
- } else {
- m_ui->collapsibleEncryption->setEnabled(false);
- m_ui->collapsibleEncryption->setToolTip(i18n("Protection of the archive with password is not possible with the %1 format.",
- mimeType.comment()));
- }
-
- if (archiveFormat.maxCompressionLevel() == 0) {
- m_ui->collapsibleCompression->setEnabled(false);
- } else {
- m_ui->collapsibleCompression->setEnabled(true);
- m_ui->compLevelSlider->setMinimum(archiveFormat.minCompressionLevel());
- m_ui->compLevelSlider->setMaximum(archiveFormat.maxCompressionLevel());
- m_ui->compLevelSlider->setValue(archiveFormat.defaultCompressionLevel());
- }
-
- slotEncryptionToggled();
+ m_ui->optionsWidget->setMimeType(QMimeDatabase().mimeTypeForName(m_supportedMimeTypes.at(index)));
}
void CreateDialog::slotUpdateFilenameExtension(int index)
@@ -175,35 +148,32 @@ QUrl CreateDialog::selectedUrl() const
int CreateDialog::compressionLevel() const
{
- if (m_ui->compLevelSlider->isEnabled()) {
- return m_ui->compLevelSlider->value();
- }
- return -1;
+ return m_ui->optionsWidget->compressionLevel();
}
QString CreateDialog::password() const
{
- return m_ui->pwdWidget->password();
+ return m_ui->optionsWidget->password();
}
bool CreateDialog::isEncryptionAvailable() const
{
- return m_ui->collapsibleEncryption->isEnabled();
+ return m_ui->optionsWidget->isEncryptionAvailable();
}
bool CreateDialog::isEncryptionEnabled() const
{
- return isEncryptionAvailable() && m_ui->collapsibleEncryption->isExpanded();
+ return m_ui->optionsWidget->isEncryptionEnabled();
}
bool CreateDialog::isHeaderEncryptionAvailable() const
{
- return isEncryptionEnabled() && m_ui->encryptHeaderCheckBox->isEnabled();
+ return m_ui->optionsWidget->isHeaderEncryptionAvailable();
}
bool CreateDialog::isHeaderEncryptionEnabled() const
{
- return isHeaderEncryptionAvailable() && m_ui->encryptHeaderCheckBox->isChecked();
+ return m_ui->optionsWidget->isHeaderEncryptionEnabled();
}
void CreateDialog::accept()
@@ -213,7 +183,7 @@ void CreateDialog::accept()
return;
}
- switch (m_ui->pwdWidget->passwordStatus()) {
+ switch (m_ui->optionsWidget->passwordStatus()) {
case KNewPasswordWidget::WeakPassword:
case KNewPasswordWidget::StrongPassword:
QDialog::accept();
@@ -226,30 +196,6 @@ void CreateDialog::accept()
}
}
-void CreateDialog::slotEncryptionToggled()
-{
- const KPluginMetaData metadata = m_pluginManger.preferredPluginFor(currentMimeType())->metaData();
- const ArchiveFormat archiveFormat = ArchiveFormat::fromMetadata(currentMimeType(), metadata);
- Q_ASSERT(archiveFormat.isValid());
-
- const bool isExpanded = m_ui->collapsibleEncryption->isExpanded();
- if (isExpanded && (archiveFormat.encryptionType() == Archive::HeaderEncrypted)) {
- m_ui->encryptHeaderCheckBox->setEnabled(true);
- m_ui->encryptHeaderCheckBox->setToolTip(QString());
- } else {
- m_ui->encryptHeaderCheckBox->setEnabled(false);
- // Show the tooltip only if the encryption is still enabled.
- // This is needed because if the new filter is e.g. tar, the whole encryption group gets disabled.
- if (isEncryptionEnabled()) {
- m_ui->encryptHeaderCheckBox->setToolTip(i18n("Protection of the list of files is not possible with the %1 format.",
- currentMimeType().comment()));
- } else {
- m_ui->encryptHeaderCheckBox->setToolTip(QString());
- }
- }
- m_ui->pwdWidget->setEnabled(isExpanded);
-}
-
void CreateDialog::slotUpdateDefaultMimeType()
{
m_config.writeEntry("LastMimeType", currentMimeType().name());
diff --git a/kerfuffle/createdialog.h b/kerfuffle/createdialog.h
index f3fe49d..4ba3d40 100644
--- a/kerfuffle/createdialog.h
+++ b/kerfuffle/createdialog.h
@@ -31,6 +31,7 @@
#ifndef CREATEDIALOG_H
#define CREATEDIALOG_H
+#include "archive_kerfuffle.h"
#include "kerfuffle_export.h"
#include "pluginmanager.h"
@@ -90,11 +91,11 @@ private:
KConfigGroup m_config;
QStringList m_supportedMimeTypes;
PluginManager m_pluginManger;
+ CompressionOptions m_compOptions;
private slots:
void slotFileNameEdited(const QString &text);
void slotUpdateWidgets(int index);
- void slotEncryptionToggled();
void slotUpdateDefaultMimeType();
void slotUpdateFilenameExtension(int index);
};
diff --git a/kerfuffle/createdialog.ui b/kerfuffle/createdialog.ui
index addf276..e0eb481 100644
--- a/kerfuffle/createdialog.ui
+++ b/kerfuffle/createdialog.ui
@@ -18,205 +18,98 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <layout class="QFormLayout" name="formLayout">
- <property name="sizeConstraint">
- <enum>QLayout::SetFixedSize</enum>
- </property>
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Folder:</string>
+ <layout class="QVBoxLayout" name="vlayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
</property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="KUrlRequester" name="destFolderUrlRequester">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Filename:</string>
- </property>
- </widget>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Folder:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KUrlRequester" name="destFolderUrlRequester">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Filename:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="filenameLineEdit">
+ <property name="placeholderText">
+ <string>Type archive name...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="mimeComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Extension:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QCheckBox" name="chkAddExtension">
+ <property name="text">
+ <string notr="true">Automatically add filename extension (extension)</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="filenameLineEdit">
- <property name="placeholderText">
- <string>Type archive name...</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Type:</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QComboBox" name="mimeComboBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
+ <item>
+ <widget class="Kerfuffle::CompressionOptionsWidget" name="optionsWidget" native="true"/>
</item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>Extension:</string>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QCheckBox" name="chkAddExtension">
- <property name="text">
- <string notr="true">Automatically add filename extension (extension)</string>
- </property>
- <property name="checked">
- <bool>true</bool>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
- <item>
- <widget class="KCollapsibleGroupBox" name="collapsibleEncryption">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="title">
- <string>Password Protection</string>
- </property>
- <property name="expanded">
- <bool>false</bool>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <widget class="KNewPasswordWidget" name="pwdWidget" native="true">
- <property name="enabled">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="encryptHeaderCheckBox">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Ask for password before showing the list of files in the archive</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="KCollapsibleGroupBox" name="collapsibleCompression">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="title">
- <string>Compression</string>
- </property>
- <property name="expanded">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1">
- <widget class="QLabel" name="label_6">
- <property name="text">
- <string>Min</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>Max</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_5">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>1</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Level:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1" colspan="2">
- <widget class="QSlider" name="compLevelSlider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>3</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximum">
- <number>9</number>
- </property>
- <property name="pageStep">
- <number>1</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="tickPosition">
- <enum>QSlider::TicksBothSides</enum>
- </property>
- <property name="tickInterval">
- <number>1</number>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="Line" name="line_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
</layout>
</widget>
<customwidgets>
@@ -226,15 +119,9 @@
<header>kurlrequester.h</header>
</customwidget>
<customwidget>
- <class>KCollapsibleGroupBox</class>
- <extends>QWidget</extends>
- <header>kcollapsiblegroupbox.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>KNewPasswordWidget</class>
+ <class>Kerfuffle::CompressionOptionsWidget</class>
<extends>QWidget</extends>
- <header location="global">KNewPasswordWidget</header>
+ <header location="global">compressionoptionswidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
@@ -242,7 +129,6 @@
<tabstop>destFolderUrlRequester</tabstop>
<tabstop>filenameLineEdit</tabstop>
<tabstop>mimeComboBox</tabstop>
- <tabstop>collapsibleEncryption</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/kerfuffle/jobs.cpp b/kerfuffle/jobs.cpp
index 8a2043b..bb815b1 100644
--- a/kerfuffle/jobs.cpp
+++ b/kerfuffle/jobs.cpp
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -251,7 +252,7 @@ void ListJob::onNewEntry(const Archive::Entry *entry)
if (m_isSingleFolderArchive) {
// RPM filenames have the ./ prefix, and "." would be detected as the subfolder name, so we remove it.
- const QString fullPath = entry->property("fullPath").toString().replace(QRegularExpression(QStringLiteral("^\\./")), QString());
+ const QString fullPath = entry->fullPath().replace(QRegularExpression(QStringLiteral("^\\./")), QString());
const QString basePath = fullPath.split(QLatin1Char('/')).at(0);
if (m_basePath.isEmpty()) {
@@ -309,7 +310,7 @@ void ExtractJob::doWork()
<< "Destination dir:" << m_destinationDir
<< "Options:" << m_options;
- bool ret = archiveInterface()->copyFiles(m_entries, m_destinationDir, m_options);
+ bool ret = archiveInterface()->extractFiles(m_entries, m_destinationDir, m_options);
if (!archiveInterface()->waitForFinishedSignal()) {
onFinished(ret);
@@ -350,7 +351,7 @@ TempExtractJob::TempExtractJob(Archive::Entry *entry, bool passwordProtectedHint
QString TempExtractJob::validatedFilePath() const
{
- QString path = extractionDir() + QLatin1Char('/') + m_entry->property("fullPath").toString();
+ QString path = extractionDir() + QLatin1Char('/') + m_entry->fullPath();
// Make sure a maliciously crafted archive with parent folders named ".." do
// not cause the previewed file path to be located outside the temporary
@@ -380,7 +381,7 @@ void TempExtractJob::doWork()
qCDebug(ARK) << "Extracting:" << m_entry;
- bool ret = archiveInterface()->copyFiles({ m_entry }, extractionDir(), extractionOptions());
+ bool ret = archiveInterface()->extractFiles({m_entry}, extractionDir(), extractionOptions());
if (!archiveInterface()->waitForFinishedSignal()) {
onFinished(ret);
@@ -422,9 +423,10 @@ OpenWithJob::OpenWithJob(Archive::Entry *entry, bool passwordProtectedHint, Read
qCDebug(ARK) << "OpenWithJob started";
}
-AddJob::AddJob(QList<Archive::Entry*> &entries, const CompressionOptions& options , ReadWriteArchiveInterface *interface)
+AddJob::AddJob(const QList<Archive::Entry*> &entries, const Archive::Entry *destination, const CompressionOptions& options , ReadWriteArchiveInterface *interface)
: Job(interface)
, m_entries(entries)
+ , m_destination(destination)
, m_options(options)
{
qCDebug(ARK) << "AddJob started";
@@ -453,19 +455,18 @@ void AddJob::doWork()
foreach (Archive::Entry *entry, m_entries) {
// #191821: workDir must be used instead of QDir::current()
// so that symlinks aren't resolved automatically
- const QString &fullPath = entry->property("fullPath").toString();
+ const QString &fullPath = entry->fullPath();
QString relativePath = workDir.relativeFilePath(fullPath);
if (fullPath.endsWith(QLatin1Char('/'))) {
relativePath += QLatin1Char('/');
}
- qCDebug(ARK) << entry->property("fullPath") << entry->isDir() << relativePath;
entry->setFullPath(relativePath);
}
connectToArchiveInterfaceSignals();
- bool ret = m_writeInterface->addFiles(m_entries, m_options);
+ bool ret = m_writeInterface->addFiles(m_entries, m_destination, m_options);
if (!archiveInterface()->waitForFinishedSignal()) {
onFinished(ret);
@@ -481,7 +482,81 @@ void AddJob::onFinished(bool result)
Job::onFinished(result);
}
-DeleteJob::DeleteJob(QList<Archive::Entry*> &entries, ReadWriteArchiveInterface *interface)
+MoveJob::MoveJob(const QList<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions& options , ReadWriteArchiveInterface *interface)
+ : Job(interface)
+ , m_finishedSignalsCount(0)
+ , m_entries(entries)
+ , m_destination(destination)
+ , m_options(options)
+{
+ qCDebug(ARK) << "MoveJob started";
+}
+
+void MoveJob::doWork()
+{
+ qCDebug(ARK) << "MoveJob: going to move" << m_entries.count() << "file(s)";
+
+ emit description(this, i18np("Moving a file", "Moving %1 files", m_entries.count()));
+
+ 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);
+ }
+}
+
+CopyJob::CopyJob(const QList<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options, ReadWriteArchiveInterface *interface)
+ : Job(interface)
+ , m_finishedSignalsCount(0)
+ , m_entries(entries)
+ , m_destination(destination)
+ , m_options(options)
+{
+ qCDebug(ARK) << "CopyJob started";
+}
+
+void CopyJob::doWork()
+{
+ qCDebug(ARK) << "CopyJob: going to copy" << m_entries.count() << "file(s)";
+
+ emit description(this, i18np("Copying a file", "Copying %1 files", m_entries.count()));
+
+ 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);
+ }
+}
+
+DeleteJob::DeleteJob(const QList<Archive::Entry*> &entries, ReadWriteArchiveInterface *interface)
: Job(interface)
, m_entries(entries)
{
diff --git a/kerfuffle/jobs.h b/kerfuffle/jobs.h
index 3279bff..50f3c28 100644
--- a/kerfuffle/jobs.h
+++ b/kerfuffle/jobs.h
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -224,7 +225,7 @@ class KERFUFFLE_EXPORT AddJob : public Job
Q_OBJECT
public:
- AddJob(QList<Archive::Entry*> &files, const CompressionOptions& options, ReadWriteArchiveInterface *interface);
+ AddJob(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions& options, ReadWriteArchiveInterface *interface);
public slots:
virtual void doWork() Q_DECL_OVERRIDE;
@@ -234,7 +235,56 @@ protected slots:
private:
QString m_oldWorkingDir;
- QList<Archive::Entry*> m_entries;
+ const QList<Archive::Entry*> m_entries;
+ const Archive::Entry *m_destination;
+ CompressionOptions m_options;
+};
+
+/**
+ * This MoveJob can be used to rename or move entries withing the archive.
+ * @see Archive::moveFiles for more details.
+ */
+class KERFUFFLE_EXPORT MoveJob : public Job
+{
+Q_OBJECT
+
+public:
+ MoveJob(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options, ReadWriteArchiveInterface *interface);
+
+public slots:
+ virtual void doWork() Q_DECL_OVERRIDE;
+
+protected slots:
+ virtual void onFinished(bool result) Q_DECL_OVERRIDE;
+
+private:
+ int m_finishedSignalsCount;
+ const QList<Archive::Entry*> m_entries;
+ Archive::Entry *m_destination;
+ CompressionOptions m_options;
+};
+
+/**
+ * This CopyJob can be used to copy entries withing the archive.
+ * @see Archive::copyFiles for more details.
+ */
+class KERFUFFLE_EXPORT CopyJob : public Job
+{
+Q_OBJECT
+
+public:
+ CopyJob(const QList<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions& options, ReadWriteArchiveInterface *interface);
+
+public slots:
+ virtual void doWork() Q_DECL_OVERRIDE;
+
+protected slots:
+ virtual void onFinished(bool result) Q_DECL_OVERRIDE;
+
+private:
+ int m_finishedSignalsCount;
+ const QList<Archive::Entry*> m_entries;
+ Archive::Entry *m_destination;
CompressionOptions m_options;
};
@@ -243,7 +293,7 @@ class KERFUFFLE_EXPORT DeleteJob : public Job
Q_OBJECT
public:
- DeleteJob(QList<Archive::Entry*> &files, ReadWriteArchiveInterface *interface);
+ DeleteJob(const QList<Archive::Entry*> &files, ReadWriteArchiveInterface *interface);
public slots:
virtual void doWork() Q_DECL_OVERRIDE;
diff --git a/part/CMakeLists.txt b/part/CMakeLists.txt
index 1f17fb0..c222996 100644
--- a/part/CMakeLists.txt
+++ b/part/CMakeLists.txt
@@ -7,6 +7,7 @@ set(arkpart_PART_SRCS
archivemodel.cpp
archiveview.cpp
jobtracker.cpp
+ overwritedialog.cpp
)
ecm_qt_declare_logging_category(arkpart_PART_SRCS
@@ -30,6 +31,8 @@ configure_file(
${CMAKE_CURRENT_BINARY_DIR}/ark_part.desktop
)
+kcoreaddons_desktop_to_json(arkpart ${CMAKE_CURRENT_BINARY_DIR}/ark_part.desktop SERVICES_TYPES kpart.desktop browserview.desktop)
+
install(TARGETS arkpart DESTINATION ${KDE_INSTALL_PLUGINDIR})
########### install files ###############
diff --git a/part/archivemodel.cpp b/part/archivemodel.cpp
index 5489153..85e5389 100644
--- a/part/archivemodel.cpp
+++ b/part/archivemodel.cpp
@@ -4,6 +4,7 @@
* Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2010-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -123,8 +124,8 @@ protected:
}
EntryMetaDataType column = static_cast<EntryMetaDataType>(m_sortColumn);
- const QVariant &leftEntryMetaData = leftEntry->property(propertiesList[column].toStdString().c_str());
- const QVariant &rightEntryMetaData = rightEntry->property(propertiesList[column].toStdString().c_str());
+ const QVariant &leftEntryMetaData = leftEntry->property(propertiesList[column].toUtf8());
+ const QVariant &rightEntryMetaData = rightEntry->property(propertiesList[column].toUtf8());
switch (m_sortColumn) {
case FullPath:
@@ -211,12 +212,14 @@ QVariant ArchiveModel::data(const QModelIndex &index, int role) const
}
default:
- return entry->property(propertiesList[column].toStdString().c_str());
+ return entry->property(propertiesList[column].toUtf8());
}
}
case Qt::DecorationRole:
if (index.column() == 0) {
- return *m_entryIcons.value(entry->property("fullPath").toString());
+ const Archive::Entry *e = static_cast<Archive::Entry*>(index.internalPointer());
+ QIcon::Mode mode = (filesToMove.contains(e->fullPath())) ? QIcon::Disabled : QIcon::Normal;
+ return m_entryIcons.value(e->fullPath(true)).pixmap(IconSize(KIconLoader::Small), IconSize(KIconLoader::Small), mode);
}
return QVariant();
case Qt::FontRole: {
@@ -448,9 +451,6 @@ QMimeData *ArchiveModel::mimeData(const QModelIndexList &indexes) const
bool ArchiveModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
Q_UNUSED(action)
- Q_UNUSED(row)
- Q_UNUSED(column)
- Q_UNUSED(parent)
if (!data->hasUrls()) {
return false;
@@ -461,26 +461,16 @@ bool ArchiveModel::dropMimeData(const QMimeData * data, Qt::DropAction action, i
paths << url.toLocalFile();
}
- //for now, this code is not used because adding files to paths inside the
- //archive is not supported yet. need a solution for this later.
- QString path;
-#if 0
- if (parent.isValid()) {
- QModelIndex droppedOnto = index(row, column, parent);
- Archive::Entry *entry = entryForIndex(droppedOnto);
- if (entry->isDir()) {
- qCDebug(ARK) << "Using entry";
- path = entry->fileName.toString();
- } else {
- path = entryForIndex(parent)->fileName.toString();
+ const Archive::Entry *entry = Q_NULLPTR;
+ QModelIndex droppedOnto = index(row, column, parent);
+ if (droppedOnto.isValid()) {
+ entry = entryForIndex(droppedOnto);
+ if (!entry->isDir()) {
+ entry = entry->getParent();
}
}
- qCDebug(ARK) << "Dropped onto " << path;
-
-#endif
-
- emit droppedFiles(paths, path);
+ emit droppedFiles(paths, entry, QString());
return true;
}
@@ -504,7 +494,7 @@ QString ArchiveModel::cleanFileName(const QString& fileName)
Archive::Entry *ArchiveModel::parentFor(const Archive::Entry *entry)
{
- QStringList pieces = entry->property("fullPath").toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts);
+ QStringList pieces = entry->fullPath().split(QLatin1Char( '/' ), QString::SkipEmptyParts);
if (pieces.isEmpty()) {
return Q_NULLPTR;
}
@@ -543,13 +533,13 @@ Archive::Entry *ArchiveModel::parentFor(const Archive::Entry *entry)
entry->setProperty("fullPath", (parent == &m_rootEntry)
? piece
- : parent->property("fullPath").toString() + QLatin1Char( '/' ) + piece);
+ : parent->fullPath(true) + QLatin1Char('/') + piece);
entry->setProperty("isDirectory", true);
insertEntry(entry);
}
if (!entry->isDir()) {
Archive::Entry *e = new Archive::Entry(parent);
- copyEntryMetaData(e, entry);
+ e->copyMetaData(entry);
// Maybe we have both a file and a directory of the same name.
// We avoid removing previous entries unless necessary.
insertEntry(e);
@@ -588,7 +578,7 @@ void ArchiveModel::slotEntryRemoved(const QString & path)
Q_UNUSED(index);
beginRemoveRows(indexForEntry(parent), entry->row(), entry->row());
- delete m_entryIcons.take(parent->entries().at(entry->row())->property("fullPath").toString());
+ m_entryIcons.remove(parent->entries().at(entry->row())->fullPath(true));
parent->removeEntryAt(entry->row());
endRemoveRows();
}
@@ -615,7 +605,7 @@ void ArchiveModel::slotNewEntry(Archive::Entry *entry)
void ArchiveModel::newEntry(Archive::Entry *receivedEntry, InsertBehaviour behaviour)
{
- if (receivedEntry->property("fullPath").toString().isEmpty()) {
+ if (receivedEntry->fullPath().isEmpty()) {
qCDebug(ARK) << "Weird, received empty entry (no filename) - skipping";
return;
}
@@ -627,7 +617,7 @@ void ArchiveModel::newEntry(Archive::Entry *receivedEntry, InsertBehaviour behav
QMap<int, QString>::const_iterator i = propertiesList.begin();
while (i != propertiesList.end()) {
- if (!receivedEntry->property(i.value().toStdString().c_str()).toString().isEmpty()) {
+ if (!receivedEntry->property(i.value().toUtf8()).toString().isEmpty()) {
if (i.key() != CompressedSize || receivedEntry->compressedSizeIsSet) {
toInsert << i.key();
}
@@ -644,7 +634,7 @@ void ArchiveModel::newEntry(Archive::Entry *receivedEntry, InsertBehaviour behav
//#194241: Filenames such as "./file" should be displayed as "file"
//#241967: Entries called "/" should be ignored
//#355839: Entries called "//" should be ignored
- QString entryFileName = cleanFileName(receivedEntry->property("fullPath").toString());
+ QString entryFileName = cleanFileName(receivedEntry->fullPath());
if (entryFileName.isEmpty()) { // The entry contains only "." or "./"
return;
}
@@ -671,7 +661,7 @@ void ArchiveModel::newEntry(Archive::Entry *receivedEntry, InsertBehaviour behav
const QString name = path.last();
Archive::Entry *entry = parent->find(name);
if (entry) {
- copyEntryMetaData(entry, receivedEntry);
+ entry->copyMetaData(receivedEntry);
entry->setProperty("fullPath", entryFileName);
delete receivedEntry;
} else {
@@ -696,25 +686,6 @@ void ArchiveModel::slotLoadingFinished(KJob *job)
emit loadingFinished(job);
}
-void ArchiveModel::copyEntryMetaData(Archive::Entry *destinationEntry, const Archive::Entry *sourceEntry)
-{
- destinationEntry->setProperty("fullPath", sourceEntry->property("fullPath"));
- destinationEntry->setProperty("permissions", sourceEntry->property("permissions"));
- destinationEntry->setProperty("owner", sourceEntry->property("owner"));
- destinationEntry->setProperty("group", sourceEntry->property("group"));
- destinationEntry->setProperty("size", sourceEntry->property("size"));
- destinationEntry->setProperty("compressedSize", sourceEntry->property("compressedSize"));
- destinationEntry->setProperty("link", sourceEntry->property("link"));
- destinationEntry->setProperty("ratio", sourceEntry->property("ratio"));
- destinationEntry->setProperty("CRC", sourceEntry->property("CRC"));
- destinationEntry->setProperty("method", sourceEntry->property("method"));
- destinationEntry->setProperty("version", sourceEntry->property("version"));
- destinationEntry->setProperty("timestamp", sourceEntry->property("timestamp").toDateTime());
- destinationEntry->setProperty("isDirectory", sourceEntry->property("isDirectory"));
- destinationEntry->setProperty("comment", sourceEntry->property("comment"));
- destinationEntry->setProperty("isPasswordProtected", sourceEntry->property("isPasswordProtected"));
-}
-
void ArchiveModel::insertEntry(Archive::Entry *entry, InsertBehaviour behaviour)
{
Q_ASSERT(entry);
@@ -730,15 +701,15 @@ void ArchiveModel::insertEntry(Archive::Entry *entry, InsertBehaviour behaviour)
// Save an icon for each newly added entry.
QMimeDatabase db;
- const QPixmap *pixmap;
+ QIcon icon;
if (entry->isDir()) {
- pixmap = new QPixmap(QIcon::fromTheme(db.mimeTypeForName(QStringLiteral("inode/directory")).iconName()).pixmap(IconSize(KIconLoader::Small),
- IconSize(KIconLoader::Small)));
+ icon = QIcon::fromTheme(db.mimeTypeForName(QStringLiteral("inode/directory")).iconName()).pixmap(IconSize(KIconLoader::Small),
+ IconSize(KIconLoader::Small));
} else {
- pixmap = new QPixmap(QIcon::fromTheme(db.mimeTypeForFile(entry->property("fullPath").toString()).iconName()).pixmap(IconSize(KIconLoader::Small),
- IconSize(KIconLoader::Small)));
+ icon = QIcon::fromTheme(db.mimeTypeForFile(entry->fullPath()).iconName()).pixmap(IconSize(KIconLoader::Small),
+ IconSize(KIconLoader::Small));
}
- m_entryIcons.insert(entry->property("fullPath").toString(), pixmap);
+ m_entryIcons.insert(entry->fullPath(true), icon);
}
Kerfuffle::Archive* ArchiveModel::archive() const
@@ -785,7 +756,7 @@ ExtractJob* ArchiveModel::extractFile(Archive::Entry *file, const QString& desti
ExtractJob* ArchiveModel::extractFiles(const QList<Archive::Entry*>& files, const QString& destinationDir, const Kerfuffle::ExtractionOptions& options) const
{
Q_ASSERT(m_archive);
- ExtractJob *newJob = m_archive->copyFiles(files, destinationDir, options);
+ ExtractJob *newJob = m_archive->extractFiles(files, destinationDir, options);
connect(newJob, &ExtractJob::userQuery, this, &ArchiveModel::slotUserQuery);
return newJob;
}
@@ -814,14 +785,14 @@ OpenWithJob *ArchiveModel::openWith(Archive::Entry *file) const
return job;
}
-AddJob* ArchiveModel::addFiles(QList<Archive::Entry*> &entries, const CompressionOptions& options)
+AddJob* ArchiveModel::addFiles(QList<Archive::Entry*> &entries, const Archive::Entry *destination, const CompressionOptions& options)
{
if (!m_archive) {
return Q_NULLPTR;
}
if (!m_archive->isReadOnly()) {
- AddJob *job = m_archive->addFiles(entries, options);
+ AddJob *job = m_archive->addFiles(entries, destination, options);
connect(job, &AddJob::newEntry, this, &ArchiveModel::slotNewEntry);
connect(job, &AddJob::userQuery, this, &ArchiveModel::slotUserQuery);
@@ -831,6 +802,41 @@ AddJob* ArchiveModel::addFiles(QList<Archive::Entry*> &entries, const Compressio
return Q_NULLPTR;
}
+Kerfuffle::MoveJob *ArchiveModel::moveFiles(QList<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options)
+{
+ if (!m_archive) {
+ return Q_NULLPTR;
+ }
+
+ 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;
+ }
+ return Q_NULLPTR;
+}
+Kerfuffle::CopyJob *ArchiveModel::copyFiles(QList<Archive::Entry*> &entries, Archive::Entry *destination, const CompressionOptions &options)
+{
+ if (!m_archive) {
+ return Q_NULLPTR;
+ }
+
+ 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;
+ }
+ return Q_NULLPTR;
+}
+
DeleteJob* ArchiveModel::deleteFiles(QList<Archive::Entry*> entries)
{
Q_ASSERT(m_archive);
@@ -855,6 +861,95 @@ void ArchiveModel::encryptArchive(const QString &password, bool encryptHeader)
m_archive->encrypt(password, encryptHeader);
}
+bool ArchiveModel::conflictingEntries(QList<const Archive::Entry*> &conflictingEntries, const QStringList &entries, bool allowMerging) const
+{
+ bool error = false;
+
+ // We can't accept destination as an argument, because it can be a new entry path for renaming.
+ const Archive::Entry *destination;
+ {
+ QStringList destinationParts = entries.first().split(QLatin1Char('/'), QString::SkipEmptyParts);
+ destinationParts.removeLast();
+ if (destinationParts.count() > 0) {
+ destination = m_rootEntry.findByPath(destinationParts);
+ }
+ else {
+ destination = &m_rootEntry;
+ }
+ }
+ const Archive::Entry *lastDirEntry = destination;
+ QString skippedDirPath;
+
+ foreach (const QString &entry, entries) {
+ if (skippedDirPath.count() > 0 && entry.startsWith(skippedDirPath)) {
+ continue;
+ }
+ else {
+ skippedDirPath.clear();
+ }
+
+ while (!entry.startsWith(lastDirEntry->fullPath())) {
+ lastDirEntry = lastDirEntry->getParent();
+ }
+
+ bool isDir = entry.right(1) == QLatin1String("/");
+ const Archive::Entry *archiveEntry = lastDirEntry->find(entry.split(QLatin1Char('/'), QString::SkipEmptyParts).last());
+
+ if (archiveEntry != Q_NULLPTR) {
+ if (archiveEntry->isDir() != isDir || !allowMerging) {
+ if (isDir) {
+ skippedDirPath = lastDirEntry->fullPath();
+ }
+
+ if (!error) {
+ conflictingEntries.clear();
+ error = true;
+ }
+ conflictingEntries << archiveEntry;
+ }
+ else {
+ if (isDir) {
+ lastDirEntry = archiveEntry;
+ }
+ else if (!error) {
+ conflictingEntries << archiveEntry;
+ }
+ }
+ }
+ else if (isDir) {
+ skippedDirPath = entry;
+ }
+ }
+
+ return error;
+}
+
+bool ArchiveModel::hasDuplicatedEntries(const QStringList &entries)
+{
+ QStringList tempList;
+ foreach (const QString &entry, entries) {
+ if (tempList.contains(entry)) {
+ return true;
+ }
+ tempList << entry;
+ }
+ return false;
+}
+
+QMap<QString, Archive::Entry*> ArchiveModel::entryMap(const QList<Archive::Entry*> &entries)
+{
+ QMap<QString, Archive::Entry*> map;
+ foreach (Archive::Entry *entry, entries) {
+ map.insert(entry->fullPath(), entry);
+ }
+ return map;
+}
+
+const QHash<QString, QIcon> ArchiveModel::entryIcons() const
+{
+ return m_entryIcons;
+}
+
void ArchiveModel::slotCleanupEmptyDirs()
{
QList<QPersistentModelIndex> queue;
@@ -871,7 +966,7 @@ void ArchiveModel::slotCleanupEmptyDirs()
Archive::Entry *entry = entryForIndex(node);
if (!hasChildren(node)) {
- if (entry->property("fullPath").toString().isEmpty()) {
+ if (entry->fullPath().isEmpty()) {
nodesToDelete << node;
}
} else {
@@ -885,7 +980,7 @@ void ArchiveModel::slotCleanupEmptyDirs()
Archive::Entry *rawEntry = static_cast<Archive::Entry*>(node.internalPointer());
qCDebug(ARK) << "Delete with parent entries " << rawEntry->getParent()->entries() << " and row " << rawEntry->row();
beginRemoveRows(parent(node), rawEntry->row(), rawEntry->row());
- delete m_entryIcons.take(rawEntry->getParent()->entries().at(rawEntry->row())->property("fullPath").toString());
+ m_entryIcons.remove(rawEntry->getParent()->entries().at(rawEntry->row())->fullPath(true));
rawEntry->getParent()->removeEntryAt(rawEntry->row());
endRemoveRows();
}
diff --git a/part/archivemodel.h b/part/archivemodel.h
index 395ef80..fcca1ca 100644
--- a/part/archivemodel.h
+++ b/part/archivemodel.h
@@ -3,6 +3,7 @@
*
* Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -73,7 +74,9 @@ public:
Kerfuffle::OpenJob* open(Archive::Entry *file) const;
Kerfuffle::OpenWithJob* openWith(Archive::Entry *file) const;
- Kerfuffle::AddJob* addFiles(QList<Archive::Entry*> &entries, const Kerfuffle::CompressionOptions& options = Kerfuffle::CompressionOptions());
+ Kerfuffle::AddJob* addFiles(QList<Archive::Entry*> &entries, const Archive::Entry *destination, const Kerfuffle::CompressionOptions& options = Kerfuffle::CompressionOptions());
+ Kerfuffle::MoveJob* moveFiles(QList<Archive::Entry*> &entries, Archive::Entry *destination, const Kerfuffle::CompressionOptions& options = Kerfuffle::CompressionOptions());
+ Kerfuffle::CopyJob* copyFiles(QList<Archive::Entry*> &entries, Archive::Entry *destination, const Kerfuffle::CompressionOptions& options = Kerfuffle::CompressionOptions());
Kerfuffle::DeleteJob* deleteFiles(QList<Archive::Entry*> entries);
/**
@@ -82,12 +85,37 @@ public:
*/
void encryptArchive(const QString &password, bool encryptHeader);
+ /**
+ * Constructs a list of conflicting entries.
+ *
+ * @param conflictingEntries Reference to the empty mutable entries list, which will be constructed.
+ * If the method returns false, this list will contain only entries which produce a critical conflict.
+ * @param entries New entries paths list.
+ * @param allowMerging Boolean variable indicating whether merging is permitted.
+ * If true, existing entries won't generate an error.
+ *
+ * @return Boolean variable indicating whether conflicts are not critical (true for not critical,
+ * false for critical). For example, if there are both "some/file" (not a directory) and "some/file/" (a directory)
+ * entries for both new and existing paths, the method will return false. Also, if merging is not allowed,
+ * this method will return false for entries with the same path and types.
+ */
+ bool conflictingEntries(QList<const Archive::Entry*> &conflictingEntries, const QStringList &entries, bool allowMerging) const;
+
+ static bool hasDuplicatedEntries(const QStringList &entries);
+
+ static QMap<QString, Archive::Entry*> entryMap(const QList<Archive::Entry*> &entries);
+
+ const QHash<QString, QIcon> entryIcons() const;
+
+ QMap<QString, Kerfuffle::Archive::Entry*> filesToMove;
+ QMap<QString, Kerfuffle::Archive::Entry*> filesToCopy;
+
signals:
void loadingStarted();
void loadingFinished(KJob *);
void extractionFinished(bool success);
void error(const QString& error, const QString& details);
- void droppedFiles(const QStringList& files, const QString& path = QString());
+ void droppedFiles(const QStringList& files, const Archive::Entry*, const QString&);
private slots:
void slotNewEntryFromSetArchive(Archive::Entry *entry);
@@ -118,7 +146,6 @@ private:
* of the change.
*/
enum InsertBehaviour { NotifyViews, DoNotNotifyViews };
- void copyEntryMetaData(Archive::Entry *destinationEntry, const Archive::Entry *sourceEntry);
void insertEntry(Archive::Entry *entry, InsertBehaviour behaviour = NotifyViews);
void newEntry(Kerfuffle::Archive::Entry *receivedEntry, InsertBehaviour behaviour);
@@ -126,7 +153,7 @@ private:
QList<int> m_showColumns;
QScopedPointer<Kerfuffle::Archive> m_archive;
Archive::Entry m_rootEntry;
- QHash<QString, const QPixmap*> m_entryIcons;
+ QHash<QString, QIcon> m_entryIcons;
QString m_dbusPathName;
};
diff --git a/part/archiveview.cpp b/part/archiveview.cpp
index 7a31a86..bf5333c 100644
--- a/part/archiveview.cpp
+++ b/part/archiveview.cpp
@@ -2,6 +2,7 @@
* ark -- archiver for the KDE project
*
* Copyright (C) 2008-2009 Harald Hvaal <haraldhv (at@at) stud.ntnu.no>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -29,6 +30,7 @@
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QMouseEvent>
+#include <QLineEdit>
ArchiveView::ArchiveView(QWidget *parent)
: QTreeView(parent)
@@ -103,3 +105,64 @@ void ArchiveView::dragMoveEvent(QDragMoveEvent * event)
event->acceptProposedAction();
}
}
+
+bool ArchiveView::eventFilter(QObject *object, QEvent *event)
+{
+ if (object == m_entryEditor && event->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Escape) {
+ closeEntryEditor();
+ return true;
+ }
+ }
+ return false;
+}
+
+void ArchiveView::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (m_editorIndex.isValid()) {
+ closeEntryEditor();
+ }
+ else {
+ QTreeView::mouseReleaseEvent(event);
+ }
+}
+
+void ArchiveView::keyPressEvent(QKeyEvent *event)
+{
+ if (m_editorIndex.isValid()) {
+ switch (event->key()) {
+ case Qt::Key_Return:
+ case Qt::Key_Enter: {
+ QLineEdit* editor = static_cast<QLineEdit*>(indexWidget(m_editorIndex));
+ emit entryChanged(editor->text());
+ closeEntryEditor();
+ break;
+ }
+
+ default:
+ QTreeView::keyPressEvent(event);
+ }
+ }
+ else {
+ QTreeView::keyPressEvent(event);
+ }
+}
+
+void ArchiveView::openEntryEditor(QModelIndex index)
+{
+ m_editorIndex = index;
+ openPersistentEditor(index);
+ m_entryEditor = static_cast<QLineEdit*>(indexWidget(m_editorIndex));
+ m_entryEditor->installEventFilter(this);
+ m_entryEditor->setText(index.data().toString());
+ m_entryEditor->setFocus(Qt::OtherFocusReason);
+ m_entryEditor->selectAll();
+}
+
+void ArchiveView::closeEntryEditor()
+{
+ m_entryEditor->removeEventFilter(this);
+ closePersistentEditor(m_editorIndex);
+ m_editorIndex = QModelIndex();
+}
diff --git a/part/archiveview.h b/part/archiveview.h
index 1f0a63a..4dd164a 100644
--- a/part/archiveview.h
+++ b/part/archiveview.h
@@ -2,6 +2,7 @@
* ark -- archiver for the KDE project
*
* Copyright (C) 2008 Harald Hvaal <haraldhv (at@at) stud.ntnu.no>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -23,6 +24,7 @@
#define ARCHIVEVIEW_H
#include <QTreeView>
+#include <QtWidgets/QLineEdit>
class ArchiveView : public QTreeView
{
@@ -36,6 +38,21 @@ public:
virtual void startDrag(Qt::DropActions supportedActions) Q_DECL_OVERRIDE;
void setModel(QAbstractItemModel *model) Q_DECL_OVERRIDE;
+
+ void openEntryEditor(QModelIndex index);
+
+protected:
+ virtual bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE;
+ virtual void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
+ virtual void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
+
+signals:
+ void entryChanged(QString name);
+
+private:
+ void closeEntryEditor();
+ QModelIndex m_editorIndex;
+ QLineEdit *m_entryEditor;
};
#endif /* ARCHIVEVIEW_H */
diff --git a/part/ark_part.rc b/part/ark_part.rc
index 62c0e68..fb45306 100644
--- a/part/ark_part.rc
+++ b/part/ark_part.rc
@@ -1,11 +1,10 @@
<!DOCTYPE kpartgui>
-<kpartgui name="ark_part" version="14">
+<kpartgui name="ark_part" version="15">
<MenuBar>
<Menu name="archive">
<text>&amp;Archive</text>
<Action name="ark_file_save_as" group="file_save"/>
<Action name="add" group="archive_edit"/>
- <Action name="add-dir" group="archive_edit"/>
<Action name="edit_comment" group="archive_edit"/>
<Action name="extract_all" group="archive_extract"/>
<Action name="properties" group="archive_props"/>
@@ -17,8 +16,13 @@
<Action name="openfile"/>
<Action name="openfilewith"/>
<Separator/>
+ <Action name="rename"/>
<Action name="delete"/>
<Action name="extract"/>
+ <Separator/>
+ <Action name="cut"/>
+ <Action name="copy"/>
+ <Action name="paste"/>
</Menu>
<Menu name="settings">
<text>&amp;Settings</text>
@@ -39,7 +43,14 @@
<Action name="openfile"/>
<Action name="openfilewith"/>
<Separator/>
+ <Action name="rename"/>
<Action name="delete"/>
<Action name="extract"/>
+ <Separator/>
+ <Action name="cut"/>
+ <Action name="copy"/>
+ <Action name="paste"/>
+ <Separator/>
+ <Action name="add"/>
</Menu>
</kpartgui>
diff --git a/part/infopanel.cpp b/part/infopanel.cpp
index d46f0e1..23245cd 100644
--- a/part/infopanel.cpp
+++ b/part/infopanel.cpp
@@ -100,7 +100,7 @@ void InfoPanel::setIndex(const QModelIndex& index)
if (entry->isDir()) {
mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
} else {
- mimeType = db.mimeTypeForFile(entry->property("fullPath").toString(), QMimeDatabase::MatchExtension);
+ mimeType = db.mimeTypeForFile(entry->fullPath(), QMimeDatabase::MatchExtension);
}
iconLabel->setPixmap(getDesktopIconForName(mimeType.iconName()));
@@ -120,8 +120,8 @@ void InfoPanel::setIndex(const QModelIndex& index)
}
}
- const QStringList nameParts = entry->property("fullPath").toString().split(QLatin1Char( '/' ), QString::SkipEmptyParts);
- const QString name = (nameParts.count() > 0) ? nameParts.last() : entry->property("fullPath").toString();
+ const QStringList nameParts = entry->fullPath().split(QLatin1Char( '/' ), QString::SkipEmptyParts);
+ const QString name = (nameParts.count() > 0) ? nameParts.last() : entry->fullPath();
fileName->setText(name);
showMetaDataFor(index);
@@ -171,7 +171,7 @@ void InfoPanel::showMetaDataFor(const QModelIndex &index)
if (entry->isDir()) {
mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
} else {
- mimeType = db.mimeTypeForFile(entry->property("fullPath").toString(), QMimeDatabase::MatchExtension);
+ mimeType = db.mimeTypeForFile(entry->fullPath(), QMimeDatabase::MatchExtension);
}
m_typeLabel->setText(i18n("<b>Type:</b> %1", mimeType.comment()));
diff --git a/part/overwritedialog.cpp b/part/overwritedialog.cpp
new file mode 100644
index 0000000..e70ccd8
--- /dev/null
+++ b/part/overwritedialog.cpp
@@ -0,0 +1,74 @@
+/*
+ * ark -- archiver for the KDE project
+ *
+ * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#include "overwritedialog.h"
+
+namespace Kerfuffle
+{
+
+OverwriteDialog::OverwriteDialog(QWidget *parent, const QList<const Archive::Entry*> &entries, const QHash<QString, QIcon> &icons, bool error)
+ : QDialog(parent)
+ , m_buttonBox(Qt::Horizontal)
+ , m_okButton(i18n("OK"))
+ , m_cancelButton(i18n("Cancel"))
+{
+ m_vBoxLayout.addLayout(&m_messageLayout);
+ m_vBoxLayout.addWidget(&m_entriesList);
+ m_vBoxLayout.addWidget(&m_buttonBox);
+
+ m_messageLayout.addWidget(&m_messageIcon);
+ m_messageLayout.addWidget(&m_messageText);
+
+ m_messageIcon.setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(QSize(64, 64)));
+ if (error) {
+ m_messageText.setText(i18n("Files with the following paths already exist. Remove them if you really want to overwrite."));
+ }
+ else {
+ m_okButton.setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok")));
+ m_messageText.setText(i18n("Files with the following paths already exist. Do you want to continue overwriting them?"));
+ m_buttonBox.addButton(&m_okButton, QDialogButtonBox::AcceptRole);
+ }
+ m_cancelButton.setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel")));
+ m_buttonBox.addButton(&m_cancelButton, QDialogButtonBox::RejectRole);
+
+ connect(&m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(&m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+
+ foreach (const Archive::Entry *entry, entries) {
+ QListWidgetItem *item = new QListWidgetItem(icons.value(entry->fullPath(true)), entry->fullPath(true));
+ m_entriesList.addItem(item);
+ }
+
+ setLayout(&m_vBoxLayout);
+ setFixedSize(window()->sizeHint());
+}
+
+OverwriteDialog::~OverwriteDialog()
+{
+}
+
+}
diff --git a/part/overwritedialog.h b/part/overwritedialog.h
new file mode 100644
index 0000000..5207a39
--- /dev/null
+++ b/part/overwritedialog.h
@@ -0,0 +1,69 @@
+/*
+ * ark -- archiver for the KDE project
+ *
+ * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OVERWRITEDIALOG_H
+#define OVERWRITEDIALOG_H
+
+#include "kerfuffle_export.h"
+#include "kerfuffle/archiveentry.h"
+
+#include <KFileWidget>
+#include <KLocalizedString>
+
+#include <QDialog>
+#include <QLabel>
+#include <QListWidget>
+#include <QMessageBox>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QDialogButtonBox>
+
+class QUrl;
+
+namespace Kerfuffle
+{
+class KERFUFFLE_EXPORT OverwriteDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit OverwriteDialog(QWidget *parent, const QList<const Archive::Entry*> &entries, const QHash<QString, QIcon> &icons, bool error = false);
+ virtual ~OverwriteDialog();
+
+private:
+ QVBoxLayout m_vBoxLayout;
+ QHBoxLayout m_messageLayout;
+ QLabel m_messageIcon;
+ QLabel m_messageText;
+ QListWidget m_entriesList;
+ QDialogButtonBox m_buttonBox;
+ QPushButton m_okButton;
+ QPushButton m_cancelButton;
+};
+}
+
+#endif
diff --git a/part/part.cpp b/part/part.cpp
index e2ed178..b6a9f59 100644
--- a/part/part.cpp
+++ b/part/part.cpp
@@ -4,6 +4,7 @@
* Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -23,6 +24,8 @@
#include "part.h"
#include "ark_debug.h"
+#include "adddialog.h"
+#include "overwritedialog.h"
#include "archiveformat.h"
#include "archivemodel.h"
#include "archiveview.h"
@@ -74,7 +77,7 @@
using namespace Kerfuffle;
-K_PLUGIN_FACTORY(Factory, registerPlugin<Ark::Part>();)
+K_PLUGIN_FACTORY_WITH_JSON(Factory, "ark_part.json", registerPlugin<Ark::Part>();)
namespace Ark
{
@@ -85,6 +88,7 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args)
: KParts::ReadWritePart(parent),
m_splitter(Q_NULLPTR),
m_busy(false),
+ m_archiveIsLoaded(false),
m_jobTracker(Q_NULLPTR)
{
Q_UNUSED(args)
@@ -115,6 +119,9 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args)
vbox->addWidget(m_commentView);
m_commentBox->setLayout(vbox);
+ m_messageWidget = new KMessageWidget(parentWidget);
+ m_messageWidget->hide();
+
m_commentMsgWidget = new KMessageWidget();
m_commentMsgWidget->setText(i18n("Comment has been modified."));
m_commentMsgWidget->setMessageType(KMessageWidget::Information);
@@ -134,6 +141,7 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args)
// Configure the QVBoxLayout and add widgets
m_vlayout->setContentsMargins(0,0,0,0);
+ m_vlayout->addWidget(m_messageWidget);
m_vlayout->addWidget(m_splitter);
// Vertical QSplitter for the file view and comment field.
@@ -157,12 +165,15 @@ Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList& args)
setupView();
setupActions();
+ connect(m_view, &ArchiveView::entryChanged,
+ this, &Part::slotRenameFile);
+
connect(m_model, &ArchiveModel::loadingStarted,
this, &Part::slotLoadingStarted);
connect(m_model, &ArchiveModel::loadingFinished,
this, &Part::slotLoadingFinished);
connect(m_model, &ArchiveModel::droppedFiles,
- this, static_cast<void (Part::*)(const QStringList&, const QString&)>(&Part::slotAddFiles));
+ this, static_cast<void (Part::*)(const QStringList&, const Archive::Entry*, const QString&)>(&Part::slotAddFiles));
connect(m_model, &ArchiveModel::error,
this, &Part::slotError);
@@ -196,6 +207,10 @@ Part::~Part()
void Part::slotCommentChanged()
{
+ if (!m_model->archive()) {
+ return;
+ }
+
if (m_commentMsgWidget->isHidden() && m_commentView->toPlainText() != m_model->archive()->comment()) {
m_commentMsgWidget->animatedShow();
} else if (m_commentMsgWidget->isVisible() && m_commentView->toPlainText() == m_model->archive()->comment()) {
@@ -319,14 +334,14 @@ void Part::setupActions()
m_openFileAction->setText(i18nc("open a file with external program", "&Open"));
m_openFileAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
m_openFileAction->setToolTip(i18nc("@info:tooltip", "Click to open the selected file with the associated application"));
- connect(m_openFileAction, SIGNAL(triggered(bool)), m_signalMapper, SLOT(map()));
+ connect(m_openFileAction, &QAction::triggered, m_signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
m_signalMapper->setMapping(m_openFileAction, OpenFile);
m_openFileWithAction = actionCollection()->addAction(QStringLiteral("openfilewith"));
m_openFileWithAction->setText(i18nc("open a file with external program", "Open &With..."));
m_openFileWithAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
m_openFileWithAction->setToolTip(i18nc("@info:tooltip", "Click to open the selected file with an external program"));
- connect(m_openFileWithAction, SIGNAL(triggered(bool)), m_signalMapper, SLOT(map()));
+ connect(m_openFileWithAction, &QAction::triggered, m_signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
m_signalMapper->setMapping(m_openFileWithAction, OpenFileWith);
m_previewAction = actionCollection()->addAction(QStringLiteral("preview"));
@@ -334,7 +349,7 @@ void Part::setupActions()
m_previewAction->setIcon(QIcon::fromTheme(QStringLiteral("document-preview-archive")));
m_previewAction->setToolTip(i18nc("@info:tooltip", "Click to preview the selected file"));
actionCollection()->setDefaultShortcut(m_previewAction, Qt::CTRL + Qt::Key_P);
- connect(m_previewAction, SIGNAL(triggered(bool)), m_signalMapper, SLOT(map()));
+ connect(m_previewAction, &QAction::triggered, m_signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
m_signalMapper->setMapping(m_previewAction, Preview);
m_extractArchiveAction = actionCollection()->addAction(QStringLiteral("extract_all"));
@@ -342,46 +357,62 @@ void Part::setupActions()
m_extractArchiveAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract")));
m_extractArchiveAction->setToolTip(i18n("Click to open an extraction dialog, where you can choose how to extract all the files in the archive"));
actionCollection()->setDefaultShortcut(m_extractArchiveAction, Qt::CTRL + Qt::SHIFT + Qt::Key_E);
- connect(m_extractArchiveAction, &QAction::triggered,
- this, &Part::slotExtractArchive);
+ connect(m_extractArchiveAction, &QAction::triggered, this, &Part::slotExtractArchive);
m_extractAction = actionCollection()->addAction(QStringLiteral("extract"));
m_extractAction->setText(i18nc("@action:inmenu", "&Extract"));
m_extractAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract")));
actionCollection()->setDefaultShortcut(m_extractAction, Qt::CTRL + Qt::Key_E);
m_extractAction->setToolTip(i18n("Click to open an extraction dialog, where you can choose to extract either all files or just the selected ones"));
- connect(m_extractAction, &QAction::triggered,
- this, &Part::slotShowExtractionDialog);
+ connect(m_extractAction, &QAction::triggered, this, &Part::slotShowExtractionDialog);
m_addFilesAction = actionCollection()->addAction(QStringLiteral("add"));
m_addFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-insert")));
- m_addFilesAction->setText(i18n("Add &File..."));
+ m_addFilesAction->setText(i18n("Add &Files to..."));
m_addFilesAction->setToolTip(i18nc("@info:tooltip", "Click to add files to the archive"));
- connect(m_addFilesAction, SIGNAL(triggered(bool)),
- this, SLOT(slotAddFiles()));
+ connect(m_addFilesAction, &QAction::triggered, this, static_cast<void (Part::*)()>(&Part::slotAddFiles));
- m_addDirAction = actionCollection()->addAction(QStringLiteral("add-dir"));
- m_addDirAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-insert-directory")));
- m_addDirAction->setText(i18n("Add Fo&lder..."));
- m_addDirAction->setToolTip(i18nc("@info:tooltip", "Click to add a folder to the archive"));
- connect(m_addDirAction, &QAction::triggered,
- this, &Part::slotAddDir);
+ m_renameFileAction = actionCollection()->addAction(QStringLiteral("rename"));
+ m_renameFileAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
+ m_renameFileAction->setText(i18n("&Rename"));
+ actionCollection()->setDefaultShortcut(m_renameFileAction, Qt::Key_F2);
+ m_renameFileAction->setToolTip(i18nc("@info:tooltip", "Click to rename the selected file"));
+ connect(m_renameFileAction, &QAction::triggered, this, &Part::slotEditFileName);
m_deleteFilesAction = actionCollection()->addAction(QStringLiteral("delete"));
m_deleteFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-remove")));
m_deleteFilesAction->setText(i18n("De&lete"));
actionCollection()->setDefaultShortcut(m_deleteFilesAction, Qt::Key_Delete);
m_deleteFilesAction->setToolTip(i18nc("@info:tooltip", "Click to delete the selected files"));
- connect(m_deleteFilesAction, &QAction::triggered,
- this, &Part::slotDeleteFiles);
+ connect(m_deleteFilesAction, &QAction::triggered, this, &Part::slotDeleteFiles);
+
+ m_cutFilesAction = actionCollection()->addAction(QStringLiteral("cut"));
+ m_cutFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut")));
+ m_cutFilesAction->setText(i18nc("@action:inmenu", "C&ut"));
+ actionCollection()->setDefaultShortcut(m_cutFilesAction, Qt::CTRL + Qt::Key_X);
+ m_cutFilesAction->setToolTip(i18nc("@info:tooltip", "Click to cut the selected files"));
+ connect(m_cutFilesAction, &QAction::triggered, this, &Part::slotCutFiles);
+
+ m_copyFilesAction = actionCollection()->addAction(QStringLiteral("copy"));
+ m_copyFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
+ m_copyFilesAction->setText(i18nc("@action:inmenu", "C&opy"));
+ actionCollection()->setDefaultShortcut(m_copyFilesAction, Qt::CTRL + Qt::Key_C);
+ m_copyFilesAction->setToolTip(i18nc("@info:tooltip", "Click to copy the selected files"));
+ connect(m_copyFilesAction, &QAction::triggered, this, &Part::slotCopyFiles);
+
+ m_pasteFilesAction = actionCollection()->addAction(QStringLiteral("paste"));
+ m_pasteFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste")));
+ m_pasteFilesAction->setText(i18nc("@action:inmenu", "Pa&ste"));
+ actionCollection()->setDefaultShortcut(m_pasteFilesAction, Qt::CTRL + Qt::Key_V);
+ m_pasteFilesAction->setToolTip(i18nc("@info:tooltip", "Click to paste the files here"));
+ connect(m_pasteFilesAction, &QAction::triggered, this, static_cast<void (Part::*)()>(&Part::slotPasteFiles));
m_propertiesAction = actionCollection()->addAction(QStringLiteral("properties"));
m_propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
m_propertiesAction->setText(i18nc("@action:inmenu", "&Properties"));
actionCollection()->setDefaultShortcut(m_propertiesAction, Qt::ALT + Qt::Key_Return);
m_propertiesAction->setToolTip(i18nc("@info:tooltip", "Click to see properties for archive"));
- connect(m_propertiesAction, &QAction::triggered,
- this, &Part::slotShowProperties);
+ connect(m_propertiesAction, &QAction::triggered, this, &Part::slotShowProperties);
m_editCommentAction = actionCollection()->addAction(QStringLiteral("edit_comment"));
m_editCommentAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
@@ -415,9 +446,10 @@ void Part::updateActions()
const bool limit = ArkSettings::limitPreviewFileSize();
bool isPreviewable = (!limit || (limit && entry != Q_NULLPTR && entry->property("size").toLongLong() < maxPreviewSize));
+ const bool isDir = (entry == Q_NULLPTR) ? false : entry->isDir();
m_previewAction->setEnabled(!isBusy() &&
isPreviewable &&
- !entry->isDir() &&
+ !isDir &&
(selectedEntriesCount == 1));
m_extractArchiveAction->setEnabled(!isBusy() &&
(m_model->rowCount() > 0));
@@ -427,22 +459,34 @@ void Part::updateActions()
m_model->rowCount() > 0);
m_addFilesAction->setEnabled(!isBusy() &&
isWritable);
- m_addDirAction->setEnabled(!isBusy() &&
- isWritable);
m_deleteFilesAction->setEnabled(!isBusy() &&
isWritable &&
(selectedEntriesCount > 0));
m_openFileAction->setEnabled(!isBusy() &&
isPreviewable &&
- !entry->isDir() &&
+ !isDir &&
(selectedEntriesCount == 1));
m_openFileWithAction->setEnabled(!isBusy() &&
isPreviewable &&
- !entry->isDir() &&
+ !isDir &&
(selectedEntriesCount == 1));
m_propertiesAction->setEnabled(!isBusy() &&
m_model->archive());
+ m_renameFileAction->setEnabled(!isBusy() &&
+ isWritable &&
+ (selectedEntriesCount == 1));
+ m_cutFilesAction->setEnabled(!isBusy() &&
+ isWritable &&
+ (selectedEntriesCount > 0));
+ m_copyFilesAction->setEnabled(!isBusy() &&
+ isWritable &&
+ (selectedEntriesCount > 0));
+ m_pasteFilesAction->setEnabled(!isBusy() &&
+ isWritable &&
+ (selectedEntriesCount == 0 || (selectedEntriesCount == 1 && isDir)) &&
+ (m_model->filesToMove.count() > 0 || m_model->filesToCopy.count() > 0));
+
m_commentView->setEnabled(!isBusy());
m_commentMsgWidget->setEnabled(!isBusy());
@@ -501,6 +545,13 @@ void Part::slotTestArchive()
job->start();
}
+void Part::resetGui()
+{
+ m_messageWidget->hide();
+ m_commentView->clear();
+ m_commentBox->hide();
+}
+
void Part::slotTestingDone(KJob* job)
{
if (job->error() && job->error() != KJob::KilledJobError) {
@@ -607,6 +658,8 @@ bool Part::openFile()
{
qCDebug(ARK) << "Attempting to open archive" << localFilePath();
+ resetGui();
+
if (!isLocalFileValid()) {
return false;
}
@@ -740,6 +793,8 @@ bool Part::confirmAndDelete(const QString &targetFile)
void Part::slotLoadingStarted()
{
+ m_model->filesToMove.clear();
+ m_model->filesToCopy.clear();
}
void Part::slotLoadingFinished(KJob *job)
@@ -754,6 +809,7 @@ void Part::slotLoadingFinished(KJob *job)
// The file failed to open, so reset the open archive, info panel and caption.
m_model->setArchive(Q_NULLPTR);
+ m_archiveIsLoaded = false;
m_infoPanel->setPrettyFileName(QString());
m_infoPanel->updateWithDefaults();
@@ -761,6 +817,9 @@ void Part::slotLoadingFinished(KJob *job)
emit setWindowCaption(QString());
}
}
+ else {
+ m_archiveIsLoaded = true;
+ }
m_view->sortByColumn(0, Qt::AscendingOrder);
@@ -792,7 +851,7 @@ void Part::slotLoadingFinished(KJob *job)
displayMsgWidget(KMessageWidget::Warning, xi18nc("@info", "The archive is empty or Ark could not open its content."));
} else if (m_model->rowCount() == 1) {
if (m_model->archive()->mimeType().inherits(QStringLiteral("application/x-cd-image")) &&
- m_model->entryForIndex(m_model->index(0, 0))->property("fullPath").toString() == QLatin1String("README.TXT")) {
+ m_model->entryForIndex(m_model->index(0, 0))->fullPath() == QLatin1String("README.TXT")) {
qCWarning(ARK) << "Detected ISO image with UDF filesystem";
displayMsgWidget(KMessageWidget::Warning, xi18nc("@info", "Ark does not currently support ISO files with UDF filesystem."));
}
@@ -854,7 +913,7 @@ void Part::slotOpenEntry(int mode)
}
// Extract the entry.
- if (!entry->property("fullPath").toString().isEmpty()) {
+ if (!entry->fullPath().isEmpty()) {
m_openFileMode = static_cast<OpenFileMode>(mode);
KJob *job = Q_NULLPTR;
@@ -959,7 +1018,7 @@ void Part::slotWatchedFileModified(const QString& file)
QStringList list = QStringList() << file;
qCDebug(ARK) << "Updating file" << file << "with path" << relPath;
- slotAddFiles(list, relPath);
+ slotAddFiles(list, Q_NULLPTR, relPath);
}
// This is needed because some apps, such as Kate, delete and recreate
// files when saving.
@@ -1106,14 +1165,15 @@ QList<Kerfuffle::Archive::Entry*> Part::filesAndRootNodesForIndexes(const QModel
}
// Fetch the root node for the unselected parent.
- const QString rootFileName =
- m_model->entryForIndex(selectionRoot)->property("fullPath").toString();
+ const QString rootFileName = selectionRoot.isValid()
+ ? m_model->entryForIndex(selectionRoot)->fullPath()
+ : QString();
// Append index with root node to fileList.
QModelIndexList alist = QModelIndexList() << index;
foreach (Archive::Entry *entry, filesForIndexes(alist)) {
- const QString fullPath = entry->property("fullPath").toString();
+ const QString fullPath = entry->fullPath();
if (!fullPathsList.contains(fullPath)) {
entry->rootNode = rootFileName;
fileList.append(entry);
@@ -1157,23 +1217,44 @@ void Part::adjustColumns()
m_view->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
}
-void Part::slotAddFiles(const QStringList& filesToAdd, const QString& path)
+void Part::slotAddFiles(const QStringList& filesToAdd, const Archive::Entry *destination, const QString &relPath)
{
- if (filesToAdd.isEmpty()) {
+ if (!m_archiveIsLoaded || filesToAdd.isEmpty()) {
return;
}
- qCDebug(ARK) << "Adding " << filesToAdd << " to " << path;
-
- // Add a trailing slash to directories.
- QStringList cleanFilesToAdd(filesToAdd);
- for (int i = 0; i < cleanFilesToAdd.size(); ++i) {
- QString& file = cleanFilesToAdd[i];
+ QStringList withChildPaths;
+ foreach (const QString& file, filesToAdd) {
+ m_jobTempEntries.push_back(new Archive::Entry(Q_NULLPTR, file));
if (QFileInfo(file).isDir()) {
- if (!file.endsWith(QLatin1Char( '/' ))) {
- file += QLatin1Char( '/' );
+ withChildPaths << file + QLatin1Char('/');
+ QDirIterator it(file, QDir::AllEntries | QDir::Readable | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ QString path = it.next();
+ if (it.fileInfo().isDir()) {
+ path += QLatin1Char('/');
+ }
+ withChildPaths << path;
}
}
+ else {
+ withChildPaths << file;
+ }
+ }
+
+ withChildPaths = ReadOnlyArchiveInterface::entryPathsFromDestination(withChildPaths, destination, 0);
+ QList<const Archive::Entry*> conflictingEntries;
+ bool error = m_model->conflictingEntries(conflictingEntries, withChildPaths, true);
+
+ if (conflictingEntries.count() > 0) {
+ QPointer<OverwriteDialog> overwriteDialog = new OverwriteDialog(widget(), conflictingEntries, m_model->entryIcons(), error);
+ int ret = overwriteDialog->exec();
+ delete overwriteDialog;
+ if (ret == QDialog::Rejected) {
+ qDeleteAll(m_jobTempEntries);
+ m_jobTempEntries.clear();
+ return;
+ }
}
// GlobalWorkDir is used by AddJob and should contain the part of the
@@ -1182,13 +1263,17 @@ void Part::slotAddFiles(const QStringList& filesToAdd, const QString& path)
// Example: We add file "/home/user/somedir/somefile.txt" and want the file
// to have the relative path within the archive "somedir/somefile.txt".
// GlobalWorkDir is then: "/home/user"
- QString globalWorkDir = cleanFilesToAdd.first();
+ QString globalWorkDir = filesToAdd.first();
// path represents the path of the file within the archive. This needs to
// be removed from globalWorkDir, otherwise the files will be added to the
// root of the archive. In the example above, path would be "somedir/".
- if (!path.isEmpty()) {
- globalWorkDir.remove(path);
+ if (!relPath.isEmpty()) {
+ globalWorkDir.remove(relPath);
+ qCDebug(ARK) << "Adding" << filesToAdd << "to" << relPath;
+ }
+ else {
+ qCDebug(ARK) << "Adding " << filesToAdd << ((destination == Q_NULLPTR) ? QString() : QStringLiteral("to ") + destination->fullPath());
}
// Remove trailing slash (needed when adding dirs).
@@ -1196,22 +1281,18 @@ void Part::slotAddFiles(const QStringList& filesToAdd, const QString& path)
globalWorkDir.chop(1);
}
+ CompressionOptions options(m_model->archive()->compressionOptions());
+
// Now take the absolute path of the parent directory.
globalWorkDir = QFileInfo(globalWorkDir).dir().absolutePath();
qCDebug(ARK) << "Detected GlobalWorkDir to be " << globalWorkDir;
- CompressionOptions options;
options[QStringLiteral("GlobalWorkDir")] = globalWorkDir;
- if (arguments().metaData().contains(QStringLiteral("compressionLevel"))) {
- options[QStringLiteral("CompressionLevel")] = arguments().metaData()[QStringLiteral("compressionLevel")];
- }
-
- foreach (const QString& file, cleanFilesToAdd) {
- m_jobTempEntries.push_back(new Archive::Entry(Q_NULLPTR, file));
- }
- AddJob *job = m_model->addFiles(m_jobTempEntries, options);
+ AddJob *job = m_model->addFiles(m_jobTempEntries, destination, options);
if (!job) {
+ qDeleteAll(m_jobTempEntries);
+ m_jobTempEntries.clear();
return;
}
@@ -1223,6 +1304,31 @@ void Part::slotAddFiles(const QStringList& filesToAdd, const QString& path)
void Part::slotAddFiles()
{
+ // If compression options are already set, we don't use the values from CreateDialog.
+ CompressionOptions opts;
+ if (m_model->archive()->compressionOptions().isEmpty()) {
+ if (arguments().metaData().contains(QStringLiteral("compressionLevel"))) {
+ opts[QStringLiteral("CompressionLevel")] = arguments().metaData()[QStringLiteral("compressionLevel")];
+ }
+ m_model->archive()->setCompressionOptions(opts);
+ } else {
+ opts = m_model->archive()->compressionOptions();
+ }
+
+ QString dialogTitle = i18nc("@title:window", "Add Files");
+ const Archive::Entry *destination = Q_NULLPTR;
+ if (m_view->selectionModel()->selectedRows().count() == 1) {
+ destination = m_model->entryForIndex(m_view->selectionModel()->currentIndex());
+ if (destination->isDir()) {
+ dialogTitle = i18nc("@title:window", "Add Files to %1", destination->fullPath());;
+ }
+ else {
+ destination = Q_NULLPTR;
+ }
+ }
+
+ qCDebug(ARK) << "Opening AddDialog with opts:" << opts;
+
// #264819: passing widget() as the parent will not work as expected.
// KFileDialog will create a KFileWidget, which runs an internal
// event loop to stat the given directory. This, in turn, leads to
@@ -1233,17 +1339,182 @@ void Part::slotAddFiles()
// When KFileDialog::exec() is called, the widget is already shown
// and nothing happens.
- const QStringList filesToAdd = QFileDialog::getOpenFileNames(widget(), i18nc("@title:window", "Add Files"));
+ QPointer<AddDialog> dlg = new AddDialog(widget(),
+ dialogTitle,
+ m_lastUsedAddPath,
+ m_model->archive()->mimeType(),
+ opts);
+
+ if (dlg->exec() == QDialog::Accepted) {
+ qCDebug(ARK) << "Selected files:" << dlg->selectedFiles();
+ qCDebug(ARK) << "Options:" << dlg->compressionOptions();
+ m_model->archive()->setCompressionOptions(dlg->compressionOptions());
+ slotAddFiles(dlg->selectedFiles(), destination, QString());
+ }
+ delete dlg;
+}
+
+void Part::slotEditFileName()
+{
+ QModelIndex currentIndex = m_view->selectionModel()->currentIndex();
+ currentIndex = (currentIndex.parent().isValid())
+ ? currentIndex.parent().child(currentIndex.row(), 0)
+ : m_model->index(currentIndex.row(), 0);
+ m_view->openEntryEditor(currentIndex);
+}
+
+void Part::slotCutFiles()
+{
+ QModelIndexList selectedRows = addChildren(m_view->selectionModel()->selectedRows());
+ m_model->filesToMove = ArchiveModel::entryMap(filesForIndexes(selectedRows));
+ qCDebug(ARK) << "Entries marked to cut:" << m_model->filesToMove.values();
+ m_model->filesToCopy.clear();
+ foreach (const QModelIndex &row, m_cutIndexes) {
+ m_view->dataChanged(row, row);
+ }
+ m_cutIndexes = selectedRows;
+ foreach (const QModelIndex &row, m_cutIndexes) {
+ m_view->dataChanged(row, row);
+ }
+ updateActions();
+}
+
+void Part::slotCopyFiles()
+{
+ m_model->filesToCopy = ArchiveModel::entryMap(filesForIndexes(addChildren(m_view->selectionModel()->selectedRows())));
+ qCDebug(ARK) << "Entries marked to copy:" << m_model->filesToCopy.values();
+ foreach (const QModelIndex &row, m_cutIndexes) {
+ m_view->dataChanged(row, row);
+ }
+ m_cutIndexes.clear();
+ m_model->filesToMove.clear();
+ updateActions();
+}
+
+void Part::slotRenameFile(QString name)
+{
+ if (name == QStringLiteral(".") || name == QStringLiteral("..") || name.contains(QLatin1Char('/'))) {
+ QMessageBox messageBox(QMessageBox::Warning,
+ i18n("Invalid filename"),
+ i18n("Filename can't contain slashes and can't be equal to \".\" or \"..\""),
+ QMessageBox::Ok);
+ messageBox.exec();
+ return;
+ }
+ const Archive::Entry *entry = m_model->entryForIndex(m_view->selectionModel()->currentIndex());
+ QList<Archive::Entry*> entriesToMove = filesForIndexes(addChildren(m_view->selectionModel()->selectedRows()));
+
+ m_destination = new Archive::Entry();
+ const QString &entryPath = entry->fullPath(true);
+ const QString rootPath = entryPath.left(entryPath.count() - entry->name().count());
+ m_destination->setFullPath(rootPath + name + ((entry->isDir()) ? QLatin1Char('/') : QChar()));
- slotAddFiles(filesToAdd);
+ slotPasteFiles(entriesToMove, m_destination, 1);
}
-void Part::slotAddDir()
+void Part::slotPasteFiles()
{
- const QString dirToAdd = QFileDialog::getExistingDirectory(widget(), i18nc("@title:window", "Add Folder"));
+ m_destination = (m_view->selectionModel()->selectedRows().count() > 0)
+ ? m_model->entryForIndex(m_view->selectionModel()->currentIndex())
+ : Q_NULLPTR;
+ if (m_destination == Q_NULLPTR) {
+ m_destination = new Archive::Entry(Q_NULLPTR, QString());
+ }
+ else {
+ m_destination = new Archive::Entry(Q_NULLPTR, m_destination->fullPath());
+ }
+
+ if (m_model->filesToMove.count() > 0) {
+ // Changing destination to include new entry path if pasting only 1 entry.
+ QList<Archive::Entry*> entriesWithoutChildren = ReadOnlyArchiveInterface::entriesWithoutChildren(m_model->filesToMove.values());
+ if (entriesWithoutChildren.count() == 1) {
+ const Archive::Entry *entry = entriesWithoutChildren.first();
+ const QString nameWithSlash = entry->name() + ((entry->isDir()) ? QLatin1String("/") : QString());
+ m_destination->setFullPath(m_destination->fullPath() + nameWithSlash);
+ }
+
+ foreach (const Archive::Entry *entry, entriesWithoutChildren) {
+ if (entry->isDir() && m_destination->fullPath().startsWith(entry->fullPath())) {
+ QMessageBox messageBox(QMessageBox::Warning,
+ i18n("Moving a folder into itself"),
+ i18n("Folders can't be moved into themselves."),
+ QMessageBox::Ok);
+ messageBox.exec();
+ delete m_destination;
+ return;
+ }
+ }
+ QList<Archive::Entry*> entryList = m_model->filesToMove.values();
+ slotPasteFiles(entryList, m_destination, entriesWithoutChildren.count());
+ m_model->filesToMove.clear();
+ }
+ else {
+ QList<Archive::Entry*> entryList = m_model->filesToCopy.values();
+ slotPasteFiles(entryList, m_destination, 0);
+ m_model->filesToCopy.clear();
+ }
+ m_cutIndexes.clear();
+ updateActions();
+}
+
+void Part::slotPasteFiles(QList<Kerfuffle::Archive::Entry*> &files, Kerfuffle::Archive::Entry *destination, int entriesWithoutChildren)
+{
+ if (files.isEmpty()) {
+ delete m_destination;
+ return;
+ }
+
+ QStringList filesPaths = ReadOnlyArchiveInterface::entryFullPaths(files);
+ QStringList newPaths = ReadOnlyArchiveInterface::entryPathsFromDestination(filesPaths, destination, entriesWithoutChildren);
+
+ if (ArchiveModel::hasDuplicatedEntries(newPaths)) {
+ QMessageBox messageBox(QMessageBox::Warning,
+ i18n("Pasting entries with the same name"),
+ i18n("Entries with the same names can't be pasted to the same destination."),
+ QMessageBox::Ok);
+ messageBox.exec();
+ delete m_destination;
+ return;
+ }
+
+ QList<const Archive::Entry*> conflictingEntries;
+ bool error = m_model->conflictingEntries(conflictingEntries, newPaths, false);
- if (!dirToAdd.isEmpty()) {
- slotAddFiles(QStringList() << dirToAdd);
+ if (conflictingEntries.count() != 0) {
+ QPointer<OverwriteDialog> overwriteDialog = new OverwriteDialog(widget(), conflictingEntries, m_model->entryIcons(), error);
+ int ret = overwriteDialog->exec();
+ delete overwriteDialog;
+ if (ret == QDialog::Rejected) {
+ delete m_destination;
+ return;
+ }
+ }
+
+ if (entriesWithoutChildren > 0) {
+ qCDebug(ARK) << "Moving" << files << "to" << destination;
+ }
+ else {
+ qCDebug(ARK) << "Copying " << files << "to" << destination;
+ }
+
+ CompressionOptions options(m_model->archive()->compressionOptions());
+
+ KJob *job;
+ if (entriesWithoutChildren != 0) {
+ job = m_model->moveFiles(files, destination, options);
+ }
+ else {
+ job = m_model->copyFiles(files, destination, options);
+ }
+
+ if (job) {
+ connect(job, &KJob::result,
+ this, &Part::slotPasteFilesDone);
+ registerJob(job);
+ job->start();
+ }
+ else {
+ delete m_destination;
}
}
@@ -1253,7 +1524,23 @@ void Part::slotAddFilesDone(KJob* job)
m_jobTempEntries.clear();
if (job->error() && job->error() != KJob::KilledJobError) {
KMessageBox::error(widget(), job->errorString());
+ } else {
+ // Hide the "archive will be created as soon as you add a file" message.
+ m_messageWidget->hide();
+ }
+ m_cutIndexes.clear();
+ m_model->filesToMove.clear();
+ m_model->filesToCopy.clear();
+}
+
+void Part::slotPasteFilesDone(KJob *job)
+{
+ if (job->error() && job->error() != KJob::KilledJobError) {
+ KMessageBox::error(widget(), job->errorString());
}
+ m_cutIndexes.clear();
+ m_model->filesToMove.clear();
+ m_model->filesToCopy.clear();
}
void Part::slotDeleteFilesDone(KJob* job)
@@ -1261,6 +1548,9 @@ void Part::slotDeleteFilesDone(KJob* job)
if (job->error() && job->error() != KJob::KilledJobError) {
KMessageBox::error(widget(), job->errorString());
}
+ m_cutIndexes.clear();
+ m_model->filesToMove.clear();
+ m_model->filesToCopy.clear();
}
void Part::slotDeleteFiles()
@@ -1363,11 +1653,11 @@ void Part::slotShowContextMenu()
void Part::displayMsgWidget(KMessageWidget::MessageType type, const QString& msg)
{
- KMessageWidget *msgWidget = new KMessageWidget();
- msgWidget->setText(msg);
- msgWidget->setMessageType(type);
- m_vlayout->insertWidget(0, msgWidget);
- msgWidget->animatedShow();
+ // The widget could be already visible, so hide it.
+ m_messageWidget->hide();
+ m_messageWidget->setText(msg);
+ m_messageWidget->setMessageType(type);
+ m_messageWidget->animatedShow();
}
} // namespace Ark
diff --git a/part/part.h b/part/part.h
index a11b92d..ce62810 100644
--- a/part/part.h
+++ b/part/part.h
@@ -4,6 +4,7 @@
* Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -34,6 +35,7 @@
#include <QModelIndex>
class ArchiveModel;
+class ArchiveView;
class InfoPanel;
class KAboutData;
@@ -104,10 +106,36 @@ private slots:
void slotShowExtractionDialog();
void slotExtractionDone(KJob*);
void slotQuickExtractFiles(QAction*);
+
+ /**
+ * Creates and starts AddJob.
+ *
+ * @param files Files to add.
+ * @param destination Destination path within the archive to which entries have to be added. Is used on addto action
+ * or drag'n'drop event, for adding a watched file it has empty.
+ * @param relPath Relative path of watched entry inside the archive. Is used only for adding temporarily extracted
+ * watched file.
+ */
+ void slotAddFiles(const QStringList &files, const Kerfuffle::Archive::Entry *destination, const QString &relPath);
+
+ /**
+ * Creates and starts MoveJob or CopyJob.
+ *
+ * @param files Files to paste.
+ * @param destination Destination path within the archive to which entries have to be added. For renaming an entry
+ * the path has to contain a new filename too.
+ * @param entriesWithoutChildren Entries count, excluding their children. For CopyJob 0 MUST be passed.
+ */
+ void slotPasteFiles(QList<Kerfuffle::Archive::Entry*> &files, Kerfuffle::Archive::Entry *destination, int entriesWithoutChildren);
+
void slotAddFiles();
- void slotAddFiles(const QStringList& files, const QString& path = QString());
- void slotAddDir();
+ void slotEditFileName();
+ void slotCutFiles();
+ void slotCopyFiles();
+ void slotRenameFile(QString name);
+ void slotPasteFiles();
void slotAddFilesDone(KJob*);
+ void slotPasteFilesDone(KJob*);
void slotTestingDone(KJob*);
void slotDeleteFiles();
void slotDeleteFilesDone(KJob*);
@@ -135,6 +163,7 @@ signals:
void quit();
private:
+ void resetGui();
void setupView();
void setupActions();
bool isSingleFolderArchive() const;
@@ -146,15 +175,18 @@ private:
void displayMsgWidget(KMessageWidget::MessageType type, const QString& msg);
ArchiveModel *m_model;
- QTreeView *m_view;
+ ArchiveView *m_view;
QAction *m_previewAction;
QAction *m_openFileAction;
QAction *m_openFileWithAction;
QAction *m_extractArchiveAction;
QAction *m_extractAction;
QAction *m_addFilesAction;
- QAction *m_addDirAction;
+ QAction *m_renameFileAction;
QAction *m_deleteFilesAction;
+ QAction *m_cutFilesAction;
+ QAction *m_copyFilesAction;
+ QAction *m_pasteFilesAction;
QAction *m_saveAsAction;
QAction *m_propertiesAction;
QAction *m_editCommentAction;
@@ -164,8 +196,12 @@ private:
QSplitter *m_splitter;
QList<QTemporaryDir*> m_tmpOpenDirList;
bool m_busy;
+ bool m_archiveIsLoaded;
OpenFileMode m_openFileMode;
+ QUrl m_lastUsedAddPath;
QList<Kerfuffle::Archive::Entry*> m_jobTempEntries;
+ Kerfuffle::Archive::Entry *m_destination;
+ QModelIndexList m_cutIndexes;
KAbstractWidgetJobTracker *m_jobTracker;
KParts::StatusBarExtension *m_statusBarExtension;
@@ -176,6 +212,7 @@ private:
QGroupBox *m_commentBox;
QPlainTextEdit *m_commentView;
KMessageWidget *m_commentMsgWidget;
+ KMessageWidget *m_messageWidget;
};
} // namespace Ark
diff --git a/plugins/cli7zplugin/cliplugin.cpp b/plugins/cli7zplugin/cliplugin.cpp
index 85b5227..51837af 100644
--- a/plugins/cli7zplugin/cliplugin.cpp
+++ b/plugins/cli7zplugin/cliplugin.cpp
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -61,7 +62,7 @@ ParameterList CliPlugin::parameterList() const
if (p.isEmpty()) {
//p[CaptureProgress] = true;
- p[ListProgram] = p[ExtractProgram] = p[DeleteProgram] = p[AddProgram] = p[TestProgram] = QStringList() << QStringLiteral("7z");
+ p[ListProgram] = p[ExtractProgram] = p[DeleteProgram] = p[MoveProgram] = p[AddProgram] = p[TestProgram] = QStringList() << QStringLiteral("7z");
p[ListArgs] = QStringList() << QStringLiteral("l")
<< QStringLiteral("-slt")
<< QStringLiteral("$PasswordSwitch")
@@ -77,10 +78,15 @@ ParameterList CliPlugin::parameterList() const
p[WrongPasswordPatterns] = QStringList() << QStringLiteral("Wrong password");
p[CompressionLevelSwitch] = QStringLiteral("-mx=$CompressionLevel");
p[AddArgs] = QStringList() << QStringLiteral("a")
+ << QStringLiteral("-l")
<< QStringLiteral("$Archive")
<< QStringLiteral("$PasswordSwitch")
<< QStringLiteral("$CompressionLevelSwitch")
<< QStringLiteral("$Files");
+ p[MoveArgs] = QStringList() << QStringLiteral("rn")
+ << QStringLiteral("$PasswordSwitch")
+ << QStringLiteral("$Archive")
+ << QStringLiteral("$PathPairs");
p[DeleteArgs] = QStringList() << QStringLiteral("d")
<< QStringLiteral("$PasswordSwitch")
<< QStringLiteral("$Archive")
@@ -214,7 +220,7 @@ bool CliPlugin::readListLine(const QString& line)
m_currentArchiveEntry->setProperty("isDirectory", isDirectory);
if (isDirectory) {
const QString directoryName =
- m_currentArchiveEntry->property("fullPath").toString();
+ m_currentArchiveEntry->fullPath();
if (!directoryName.endsWith(QLatin1Char('/'))) {
const bool isPasswordProtected = (line.at(12) == QLatin1Char('+'));
m_currentArchiveEntry->setProperty("fullPath", QString(directoryName + QLatin1Char('/')));
@@ -233,7 +239,7 @@ bool CliPlugin::readListLine(const QString& line)
} else if (line.startsWith(QStringLiteral("Block = ")) ||
line.startsWith(QStringLiteral("Version = "))) {
m_isFirstInformationEntry = true;
- if (!m_currentArchiveEntry->property("fullPath").toString().isEmpty()) {
+ if (!m_currentArchiveEntry->fullPath().isEmpty()) {
emit entry(m_currentArchiveEntry);
}
else {
diff --git a/plugins/cli7zplugin/cliplugin.h b/plugins/cli7zplugin/cliplugin.h
index 806c647..c60a62d 100644
--- a/plugins/cli7zplugin/cliplugin.h
+++ b/plugins/cli7zplugin/cliplugin.h
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2010 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
diff --git a/plugins/clirarplugin/cliplugin.cpp b/plugins/clirarplugin/cliplugin.cpp
index b56ec41..d701a8b 100644
--- a/plugins/clirarplugin/cliplugin.cpp
+++ b/plugins/clirarplugin/cliplugin.cpp
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2010-2011,2014 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -59,18 +60,6 @@ void CliPlugin::resetParsing()
m_comment.clear();
}
-// #272281: the proprietary unrar program does not like trailing '/'s
-// in directories passed to it when extracting only part of
-// the files in an archive.
-QString CliPlugin::escapeFileName(const QString &fileName) const
-{
- if (fileName.endsWith(QLatin1Char('/'))) {
- return fileName.left(fileName.length() - 1);
- }
-
- return fileName;
-}
-
ParameterList CliPlugin::parameterList() const
{
static ParameterList p;
@@ -78,7 +67,7 @@ ParameterList CliPlugin::parameterList() const
if (p.isEmpty()) {
p[CaptureProgress] = true;
p[ListProgram] = p[ExtractProgram] = p[TestProgram] = QStringList() << QStringLiteral( "unrar" );
- p[DeleteProgram] = p[AddProgram] = QStringList() << QStringLiteral( "rar" );
+ p[DeleteProgram] = p[MoveProgram] = p[AddProgram] = QStringList() << QStringLiteral( "rar" );
p[ListArgs] = QStringList() << QStringLiteral("vt")
<< QStringLiteral("-v")
@@ -113,6 +102,10 @@ ParameterList CliPlugin::parameterList() const
<< QStringLiteral("$PasswordSwitch")
<< QStringLiteral("$CompressionLevelSwitch")
<< QStringLiteral( "$Files" );
+ p[MoveArgs] = QStringList() << QStringLiteral( "rn" )
+ << QStringLiteral( "$PasswordSwitch" )
+ << QStringLiteral( "$Archive" )
+ << QStringLiteral( "$PathPairs" );
p[PasswordPromptPattern] = QLatin1String("Enter password \\(will not be echoed\\) for");
p[WrongPasswordPatterns] = QStringList() << QStringLiteral("password incorrect") << QStringLiteral("wrong password");
p[ExtractionFailedPatterns] = QStringList() << QStringLiteral( "CRC failed" )
diff --git a/plugins/clirarplugin/cliplugin.h b/plugins/clirarplugin/cliplugin.h
index dd98088..dc7dbfa 100644
--- a/plugins/clirarplugin/cliplugin.h
+++ b/plugins/clirarplugin/cliplugin.h
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2010 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -34,7 +35,6 @@ public:
virtual ~CliPlugin();
virtual void resetParsing() Q_DECL_OVERRIDE;
- virtual QString escapeFileName(const QString &fileName) const Q_DECL_OVERRIDE;
virtual Kerfuffle::ParameterList parameterList() const Q_DECL_OVERRIDE;
virtual bool readListLine(const QString &line) Q_DECL_OVERRIDE;
diff --git a/plugins/cliunarchiverplugin/cliplugin.cpp b/plugins/cliunarchiverplugin/cliplugin.cpp
index 4a8ab87..3fe0c31 100644
--- a/plugins/cliunarchiverplugin/cliplugin.cpp
+++ b/plugins/cliunarchiverplugin/cliplugin.cpp
@@ -76,7 +76,7 @@ bool CliPlugin::list()
return true;
}
-bool CliPlugin::copyFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options)
+bool CliPlugin::extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options)
{
ExtractionOptions newOptions = options;
@@ -90,7 +90,7 @@ bool CliPlugin::copyFiles(const QList<Archive::Entry*> &files, const QString &de
qCDebug(ARK) << "Enabling extraction to temporary directory.";
newOptions[QStringLiteral("AlwaysUseTmpDir")] = true;
- return CliInterface::copyFiles(files, destinationDirectory, newOptions);
+ return CliInterface::extractFiles(files, destinationDirectory, newOptions);
}
void CliPlugin::resetParsing()
diff --git a/plugins/cliunarchiverplugin/cliplugin.h b/plugins/cliunarchiverplugin/cliplugin.h
index 0358771..e106679 100644
--- a/plugins/cliunarchiverplugin/cliplugin.h
+++ b/plugins/cliunarchiverplugin/cliplugin.h
@@ -35,7 +35,7 @@ public:
virtual ~CliPlugin();
virtual bool list() Q_DECL_OVERRIDE;
- virtual bool copyFiles(const QList<Kerfuffle::Archive::Entry*> &files, const QString &destinationDirectory, const Kerfuffle::ExtractionOptions &options) Q_DECL_OVERRIDE;
+ virtual bool extractFiles(const QList<Kerfuffle::Archive::Entry*> &files, const QString &destinationDirectory, const Kerfuffle::ExtractionOptions &options) Q_DECL_OVERRIDE;
virtual void resetParsing() Q_DECL_OVERRIDE;
virtual Kerfuffle::ParameterList parameterList() const Q_DECL_OVERRIDE;
virtual bool readListLine(const QString &line) Q_DECL_OVERRIDE;
diff --git a/plugins/clizipplugin/cliplugin.cpp b/plugins/clizipplugin/cliplugin.cpp
index e12b5b7..83265d1 100644
--- a/plugins/clizipplugin/cliplugin.cpp
+++ b/plugins/clizipplugin/cliplugin.cpp
@@ -3,6 +3,7 @@
*
* Copyright (C) 2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (C) 2009-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -189,5 +190,96 @@ bool CliPlugin::readListLine(const QString &line)
return true;
}
+bool CliPlugin::moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ m_oldWorkingDir = QDir::currentPath();
+ m_tempExtractDir = new QTemporaryDir();
+ m_tempAddDir = new QTemporaryDir();
+ QDir::setCurrent(m_tempExtractDir->path());
+ m_passedFiles = files;
+ m_passedDestination = destination;
+ m_passedOptions = options;
+
+ m_subOperation = Extract;
+ connect(this, &CliPlugin::finished, this, &CliPlugin::continueMoving);
+
+ return extractFiles(files, QDir::currentPath(), options);
+}
+
+int CliPlugin::moveRequiredSignals() const {
+ return 4;
+}
+
+void CliPlugin::continueMoving(bool result)
+{
+ if (!result) {
+ finishMoving(false);
+ return;
+ }
+
+ switch (m_subOperation) {
+ case Extract:
+ m_subOperation = Delete;
+ if (!deleteFiles(m_passedFiles)) {
+ finishMoving(false);
+ }
+ break;
+
+ case Delete:
+ m_subOperation = Add;
+ if (!setMovingAddedFiles() || !addFiles(m_tempAddedFiles, m_passedDestination, m_passedOptions)) {
+ finishMoving(false);
+ }
+ break;
+
+ case Add:
+ finishMoving(true);
+ break;
+
+ default:
+ Q_ASSERT(false);
+ }
+}
+
+bool CliPlugin::setMovingAddedFiles()
+{
+ m_passedFiles = entriesWithoutChildren(m_passedFiles);
+ // If there are more files being moved than 1, we have destination as a destination folder,
+ // otherwise it's new entry full path.
+ if (m_passedFiles.count() > 1) {
+ return setAddedFiles();
+ }
+
+ QDir::setCurrent(m_tempAddDir->path());
+ const Archive::Entry *file = m_passedFiles.at(0);
+ const QString oldPath = m_tempExtractDir->path() + QLatin1Char('/') + file->fullPath(true);
+ const QString newPath = m_tempAddDir->path() + QLatin1Char('/') + m_passedDestination->name();
+ if (!QFile::rename(oldPath, newPath)) {
+ return false;
+ }
+ m_tempAddedFiles << new Archive::Entry(Q_NULLPTR, m_passedDestination->name());
+
+ // We have to exclude file name from destination path in order to pass it to addFiles method.
+ const QString destinationPath = m_passedDestination->fullPath();
+ int destinationLength = destinationPath.count();
+ bool iteratedChar = false;
+ do {
+ destinationLength--;
+ if (destinationPath.at(destinationLength) != QLatin1Char('/')) {
+ iteratedChar = true;
+ }
+ } while (destinationLength > 0 && !(iteratedChar && destinationPath.at(destinationLength) == QLatin1Char('/')));
+ m_passedDestination->setProperty("fullPath", destinationPath.left(destinationLength + 1));
+
+ return true;
+}
+
+void CliPlugin::finishMoving(bool result)
+{
+ disconnect(this, &CliPlugin::finished, this, &CliPlugin::continueMoving);
+ emit progress(1.0);
+ emit finished(result);
+ cleanUp();
+}
#include "cliplugin.moc"
diff --git a/plugins/clizipplugin/cliplugin.h b/plugins/clizipplugin/cliplugin.h
index 8da8ff4..c85f73a 100644
--- a/plugins/clizipplugin/cliplugin.h
+++ b/plugins/clizipplugin/cliplugin.h
@@ -2,6 +2,7 @@
* ark -- archiver for the KDE project
*
* Copyright (C) 2011 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -23,6 +24,10 @@
#include "kerfuffle/cliinterface.h"
+#include <QTemporaryDir>
+
+using namespace Kerfuffle;
+
class KERFUFFLE_EXPORT CliPlugin : public Kerfuffle::CliInterface
{
Q_OBJECT
@@ -36,7 +41,16 @@ public:
virtual Kerfuffle::ParameterList parameterList() const Q_DECL_OVERRIDE;
virtual bool readListLine(const QString &line) Q_DECL_OVERRIDE;
+ virtual bool moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions& options) Q_DECL_OVERRIDE;
+ virtual int moveRequiredSignals() const Q_DECL_OVERRIDE;
+
+private slots:
+ void continueMoving(bool result);
+
private:
+ bool setMovingAddedFiles();
+ void finishMoving(bool result);
+
enum ParseState {
ParseStateHeader = 0,
ParseStateComment,
diff --git a/plugins/libarchive/libarchiveplugin.cpp b/plugins/libarchive/libarchiveplugin.cpp
index 4ac0659..ed81152 100644
--- a/plugins/libarchive/libarchiveplugin.cpp
+++ b/plugins/libarchive/libarchiveplugin.cpp
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2010 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -32,7 +33,7 @@
#include <QDirIterator>
-LibarchivePlugin::LibarchivePlugin(QObject *parent, const QVariantList & args)
+LibarchivePlugin::LibarchivePlugin(QObject *parent, const QVariantList &args)
: ReadWriteArchiveInterface(parent, args)
, m_archiveReadDisk(archive_read_disk_new())
, m_abortOperation(false)
@@ -52,26 +53,11 @@ bool LibarchivePlugin::list()
{
qCDebug(ARK) << "Listing archive contents";
- ArchiveRead arch_reader(archive_read_new());
-
- if (!(arch_reader.data())) {
- return false;
- }
-
- if (archive_read_support_filter_all(arch_reader.data()) != ARCHIVE_OK) {
- return false;
- }
-
- if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) {
+ if (!initializeReader()) {
return false;
}
- if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) {
- emit error(i18nc("@info", "Could not open the archive."));
- return false;
- }
-
- qDebug(ARK) << "Detected compression filter:" << archive_filter_name(arch_reader.data(), 0);
+ qDebug(ARK) << "Detected compression filter:" << archive_filter_name(m_archiveReader.data(), 0);
m_cachedArchiveEntryCount = 0;
m_extractedFilesSize = 0;
@@ -80,10 +66,10 @@ bool LibarchivePlugin::list()
int result = ARCHIVE_RETRY;
bool firstEntry = true;
- while (!m_abortOperation && (result = archive_read_next_header(arch_reader.data(), &aentry)) == ARCHIVE_OK) {
+ while (!m_abortOperation && (result = archive_read_next_header(m_archiveReader.data(), &aentry)) == ARCHIVE_OK) {
if (firstEntry) {
- qDebug(ARK) << "Detected format for first entry:" << archive_format_name(arch_reader.data());
+ qDebug(ARK) << "Detected format for first entry:" << archive_format_name(m_archiveReader.data());
firstEntry = false;
}
@@ -94,23 +80,40 @@ bool LibarchivePlugin::list()
m_extractedFilesSize += (qlonglong)archive_entry_size(aentry);
m_cachedArchiveEntryCount++;
- archive_read_data_skip(arch_reader.data());
+ archive_read_data_skip(m_archiveReader.data());
}
m_abortOperation = false;
if (result != ARCHIVE_EOF) {
- const QString errorString = QLatin1String(archive_error_string(arch_reader.data()));
+ const QString errorString = QLatin1String(archive_error_string(m_archiveReader.data()));
// FIXME: what about the other archive_error_string() calls? Do they also happen to return empty strings?
emit error(errorString.isEmpty() ? i18nc("@info", "Could not read until the end of the archive") : errorString);
return false;
}
- return archive_read_close(arch_reader.data()) == ARCHIVE_OK;
+ return archive_read_close(m_archiveReader.data()) == ARCHIVE_OK;
}
-bool LibarchivePlugin::addFiles(const QList<Archive::Entry*> &files, const CompressionOptions &options)
+bool LibarchivePlugin::addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions &options)
{
Q_UNUSED(files)
+ Q_UNUSED(destination)
+ Q_UNUSED(options)
+ return false;
+}
+
+bool LibarchivePlugin::moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ Q_UNUSED(files)
+ Q_UNUSED(destination)
+ Q_UNUSED(options)
+ return false;
+}
+
+bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ Q_UNUSED(files)
+ Q_UNUSED(destination)
Q_UNUSED(options)
return false;
}
@@ -121,7 +124,7 @@ bool LibarchivePlugin::deleteFiles(const QList<Archive::Entry*> &files)
return false;
}
-bool LibarchivePlugin::addComment(const QString& comment)
+bool LibarchivePlugin::addComment(const QString &comment)
{
Q_UNUSED(comment)
return false;
@@ -138,7 +141,7 @@ bool LibarchivePlugin::doKill()
return true;
}
-bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QString& destinationDirectory, const ExtractionOptions& options)
+bool LibarchivePlugin::extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options)
{
qCDebug(ARK) << "Changing current directory to " << destinationDirectory;
QDir::setCurrent(destinationDirectory);
@@ -153,25 +156,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
QStringList fullPaths = entryFullPaths(files);
QStringList remainingFiles = entryFullPaths(files);
- ArchiveRead arch(archive_read_new());
-
- if (!(arch.data())) {
- return false;
- }
-
- if (archive_read_support_filter_all(arch.data()) != ARCHIVE_OK) {
- return false;
- }
-
- if (archive_read_support_format_all(arch.data()) != ARCHIVE_OK) {
- return false;
- }
-
- if (archive_read_open_filename(arch.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) {
- // This error might be shown outside of a running Ark part (e.g. from a batch job).
- emit error(xi18nc("@info", "Could not open the archive <filename>%1</filename>.<nl/>"
- "Check whether you have sufficient permissions.",
- filename()));
+ if (!initializeReader()) {
return false;
}
@@ -214,7 +199,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
QString fileBeingRenamed;
// Iterate through all entries in archive.
- while (!m_abortOperation && (archive_read_next_header(arch.data(), &entry) == ARCHIVE_OK)) {
+ while (!m_abortOperation && (archive_read_next_header(m_archiveReader.data(), &entry) == ARCHIVE_OK)) {
if (!extractAll && remainingFiles.isEmpty()) {
break;
@@ -231,7 +216,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
// Skip directories if not preserving paths.
if (!preservePaths && entryIsDir) {
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
continue;
}
@@ -292,7 +277,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
// Check if the file about to be written already exists.
if (!entryIsDir && entryFI.exists()) {
if (skipAll) {
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
archive_entry_clear(entry);
continue;
} else if (!overwriteAll && !skipAll) {
@@ -301,15 +286,15 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
query.waitForResponse();
if (query.responseCancelled()) {
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
archive_entry_clear(entry);
break;
} else if (query.responseSkip()) {
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
archive_entry_clear(entry);
continue;
} else if (query.responseAutoSkip()) {
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
archive_entry_clear(entry);
skipAll = true;
continue;
@@ -331,7 +316,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
} else {
qCWarning(ARK) << "Warning, existing, but non-writable dir. skipping";
archive_entry_clear(entry);
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
continue;
}
}
@@ -342,7 +327,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
case ARCHIVE_OK:
// If the whole archive is extracted and the total filesize is
// available, we use partial progress.
- copyData(entryName, arch.data(), writer.data(), (extractAll && m_extractedFilesSize));
+ copyData(entryName, m_archiveReader.data(), writer.data(), (extractAll && m_extractedFilesSize));
break;
case ARCHIVE_FAILED:
@@ -386,7 +371,6 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
++entryNr;
emit progress(float(entryNr) / totalCount);
}
- archive_entry_clear(entry);
no_entries++;
remainingFiles.removeOne(entryName);
@@ -394,7 +378,7 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
} else {
// Archive entry not among selected files, skip it.
- archive_read_data_skip(arch.data());
+ archive_read_data_skip(m_archiveReader.data());
}
@@ -404,7 +388,34 @@ bool LibarchivePlugin::copyFiles(const QList<Archive::Entry*>& files, const QStr
qCDebug(ARK) << "Extracted" << no_entries << "entries";
- return archive_read_close(arch.data()) == ARCHIVE_OK;
+ return archive_read_close(m_archiveReader.data()) == ARCHIVE_OK;
+}
+
+bool LibarchivePlugin::initializeReader()
+{
+ m_archiveReader.reset(archive_read_new());
+
+ if (!(m_archiveReader.data())) {
+ emit error(i18n("The archive reader could not be initialized."));
+ return false;
+ }
+
+ if (archive_read_support_filter_all(m_archiveReader.data()) != ARCHIVE_OK) {
+ return false;
+ }
+
+ if (archive_read_support_format_all(m_archiveReader.data()) != ARCHIVE_OK) {
+ return false;
+ }
+
+ if (archive_read_open_filename(m_archiveReader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) {
+ emit error(xi18nc("@info", "Could not open the archive <filename>%1</filename>.<nl/>"
+ "Check whether you have sufficient permissions.",
+ filename()));
+ return false;
+ }
+
+ return true;
}
void LibarchivePlugin::emitEntryFromArchiveEntry(struct archive_entry *aentry)
diff --git a/plugins/libarchive/libarchiveplugin.h b/plugins/libarchive/libarchiveplugin.h
index 9661efa..56dda50 100644
--- a/plugins/libarchive/libarchiveplugin.h
+++ b/plugins/libarchive/libarchiveplugin.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -41,23 +42,21 @@ class LibarchivePlugin : public ReadWriteArchiveInterface
Q_OBJECT
public:
- explicit LibarchivePlugin(QObject *parent, const QVariantList& args);
+ explicit LibarchivePlugin(QObject *parent, const QVariantList &args);
virtual ~LibarchivePlugin();
virtual bool list() Q_DECL_OVERRIDE;
virtual bool doKill() Q_DECL_OVERRIDE;
- virtual bool copyFiles(const QList<Archive::Entry*>& files, const QString& destinationDirectory, const ExtractionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options) Q_DECL_OVERRIDE;
- virtual bool addFiles(const QList<Archive::Entry*>& files, const CompressionOptions& options) Q_DECL_OVERRIDE;
- virtual bool deleteFiles(const QList<Archive::Entry*>& files) Q_DECL_OVERRIDE;
- virtual bool addComment(const QString& comment) Q_DECL_OVERRIDE;
+ virtual bool addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions &options) Q_DECL_OVERRIDE;
+ virtual bool moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options) Q_DECL_OVERRIDE;
+ virtual bool copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options) Q_DECL_OVERRIDE;
+ virtual bool deleteFiles(const QList<Archive::Entry*> &files) Q_DECL_OVERRIDE;
+ virtual bool addComment(const QString &comment) Q_DECL_OVERRIDE;
virtual bool testArchive() Q_DECL_OVERRIDE;
protected:
- void emitEntryFromArchiveEntry(struct archive_entry *entry);
- void copyData(const QString& filename, struct archive *dest, bool partialprogress = true);
- void copyData(const QString& filename, struct archive *source, struct archive *dest, bool partialprogress = true);
-
struct ArchiveReadCustomDeleter
{
static inline void cleanup(struct archive *a)
@@ -81,6 +80,12 @@ protected:
typedef QScopedPointer<struct archive, ArchiveReadCustomDeleter> ArchiveRead;
typedef QScopedPointer<struct archive, ArchiveWriteCustomDeleter> ArchiveWrite;
+ bool initializeReader();
+ void emitEntryFromArchiveEntry(struct archive_entry *entry);
+ void copyData(const QString& filename, struct archive *dest, bool partialprogress = true);
+ void copyData(const QString& filename, struct archive *source, struct archive *dest, bool partialprogress = true);
+
+ ArchiveRead m_archiveReader;
ArchiveRead m_archiveReadDisk;
bool m_abortOperation;
diff --git a/plugins/libarchive/readwritelibarchiveplugin.cpp b/plugins/libarchive/readwritelibarchiveplugin.cpp
index dd13b3a..d856b9e 100644
--- a/plugins/libarchive/readwritelibarchiveplugin.cpp
+++ b/plugins/libarchive/readwritelibarchiveplugin.cpp
@@ -2,6 +2,7 @@
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
* Copyright (c) 2010 Raphael Kubo da Costa <rakuco@FreeBSD.org>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -35,7 +36,7 @@
K_PLUGIN_FACTORY_WITH_JSON(ReadWriteLibarchivePluginFactory, "kerfuffle_libarchive.json", registerPlugin<ReadWriteLibarchivePlugin>();)
-ReadWriteLibarchivePlugin::ReadWriteLibarchivePlugin(QObject *parent, const QVariantList & args)
+ReadWriteLibarchivePlugin::ReadWriteLibarchivePlugin(QObject *parent, const QVariantList &args)
: LibarchivePlugin(parent, args)
{
qCDebug(ARK) << "Loaded libarchive read-write plugin";
@@ -45,7 +46,7 @@ ReadWriteLibarchivePlugin::~ReadWriteLibarchivePlugin()
{
}
-bool ReadWriteLibarchivePlugin::addFiles(const QList<Archive::Entry*>& files, const CompressionOptions& options)
+bool ReadWriteLibarchivePlugin::addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions &options)
{
qCDebug(ARK) << "Adding" << files.size() << "entries with CompressionOptions" << options;
@@ -53,183 +54,35 @@ bool ReadWriteLibarchivePlugin::addFiles(const QList<Archive::Entry*>& files, co
m_writtenFiles.clear();
- ArchiveRead arch_reader;
- if (!creatingNewFile) {
- arch_reader.reset(archive_read_new());
- if (!arch_reader.data()) {
- emit error(i18n("The archive reader could not be initialized."));
- return false;
- }
-
- if (archive_read_support_filter_all(arch_reader.data()) != ARCHIVE_OK) {
- return false;
- }
-
- if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) {
- return false;
- }
-
- if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) {
- emit error(i18n("The source file could not be read."));
- return false;
- }
- }
-
- // |tempFile| needs to be created before |arch_writer| so that when we go
- // out of scope in a `return false' case ArchiveWriteCustomDeleter is
- // called before destructor of QSaveFile (ie. we call archive_write_close()
- // before close()'ing the file descriptor).
- QSaveFile tempFile(filename());
- if (!tempFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
- emit error(xi18nc("@info", "Failed to create a temporary file to compress <filename>%1</filename>.", filename()));
+ if (!creatingNewFile && !initializeReader()) {
return false;
}
- ArchiveWrite arch_writer(archive_write_new());
- if (!(arch_writer.data())) {
- emit error(i18n("The archive writer could not be initialized."));
- return false;
- }
-
- // pax_restricted is the libarchive default, let's go with that.
- archive_write_set_format_pax_restricted(arch_writer.data());
-
- int ret;
- bool requiresExecutable = false;
- if (creatingNewFile) {
- if (filename().right(2).toUpper() == QLatin1String("GZ")) {
- qCDebug(ARK) << "Detected gzip compression for new file";
- ret = archive_write_add_filter_gzip(arch_writer.data());
- } else if (filename().right(3).toUpper() == QLatin1String("BZ2")) {
- qCDebug(ARK) << "Detected bzip2 compression for new file";
- ret = archive_write_add_filter_bzip2(arch_writer.data());
- } else if (filename().right(2).toUpper() == QLatin1String("XZ")) {
- qCDebug(ARK) << "Detected xz compression for new file";
- ret = archive_write_add_filter_xz(arch_writer.data());
- } else if (filename().right(4).toUpper() == QLatin1String("LZMA")) {
- qCDebug(ARK) << "Detected lzma compression for new file";
- ret = archive_write_add_filter_lzma(arch_writer.data());
- } else if (filename().right(2).toUpper() == QLatin1String(".Z")) {
- qCDebug(ARK) << "Detected compress (.Z) compression for new file";
- ret = archive_write_add_filter_compress(arch_writer.data());
- } else if (filename().right(2).toUpper() == QLatin1String("LZ")) {
- qCDebug(ARK) << "Detected lzip compression for new file";
- ret = archive_write_add_filter_lzip(arch_writer.data());
- } else if (filename().right(3).toUpper() == QLatin1String("LZO")) {
- qCDebug(ARK) << "Detected lzop compression for new file";
- ret = archive_write_add_filter_lzop(arch_writer.data());
- } else if (filename().right(3).toUpper() == QLatin1String("LRZ")) {
- qCDebug(ARK) << "Detected lrzip compression for new file";
- ret = archive_write_add_filter_lrzip(arch_writer.data());
- requiresExecutable = true;
-#ifdef HAVE_LIBARCHIVE_3_2_0
- } else if (filename().right(3).toUpper() == QLatin1String("LZ4")) {
- qCDebug(ARK) << "Detected lz4 compression for new file";
- ret = archive_write_add_filter_lz4(arch_writer.data());
- requiresExecutable = true;
-#endif
- } else if (filename().right(3).toUpper() == QLatin1String("TAR")) {
- qCDebug(ARK) << "Detected no compression for new file (pure tar)";
- ret = archive_write_add_filter_none(arch_writer.data());
- } else {
- qCDebug(ARK) << "Falling back to gzip";
- ret = archive_write_add_filter_gzip(arch_writer.data());
- }
-
- // Libarchive emits a warning for lrzip due to using external executable.
- if ((requiresExecutable && ret != ARCHIVE_WARN) ||
- (!requiresExecutable && ret != ARCHIVE_OK)) {
- emit error(xi18nc("@info", "Setting the compression method failed with the following error:<nl/><message>%1</message>",
- QLatin1String(archive_error_string(arch_writer.data()))));
- return false;
- }
-
- // Set compression level if passed in CompressionOptions.
- if (options.contains(QStringLiteral("CompressionLevel"))) {
- qCDebug(ARK) << "Using compression level:" << options.value(QStringLiteral("CompressionLevel")).toString();
- ret = archive_write_set_filter_option(arch_writer.data(), NULL, "compression-level", options.value(QStringLiteral("CompressionLevel")).toString().toUtf8());
- if (ret != ARCHIVE_OK) {
- qCWarning(ARK) << "Failed to set compression level";
- emit error(xi18nc("@info", "Setting the compression level failed with the following error:<nl/><message>%1</message>",
- QLatin1String(archive_error_string(arch_writer.data()))));
- return false;
- }
- }
-
- } else {
- switch (archive_filter_code(arch_reader.data(), 0)) {
- case ARCHIVE_FILTER_GZIP:
- ret = archive_write_add_filter_gzip(arch_writer.data());
- break;
- case ARCHIVE_FILTER_BZIP2:
- ret = archive_write_add_filter_bzip2(arch_writer.data());
- break;
- case ARCHIVE_FILTER_XZ:
- ret = archive_write_add_filter_xz(arch_writer.data());
- break;
- case ARCHIVE_FILTER_LZMA:
- ret = archive_write_add_filter_lzma(arch_writer.data());
- break;
- case ARCHIVE_FILTER_COMPRESS:
- ret = archive_write_add_filter_compress(arch_writer.data());
- break;
- case ARCHIVE_FILTER_LZIP:
- ret = archive_write_add_filter_lzip(arch_writer.data());
- break;
- case ARCHIVE_FILTER_LZOP:
- ret = archive_write_add_filter_lzop(arch_writer.data());
- break;
- case ARCHIVE_FILTER_LRZIP:
- ret = archive_write_add_filter_lrzip(arch_writer.data());
- requiresExecutable = true;
- break;
-#ifdef HAVE_LIBARCHIVE_3_2_0
- case ARCHIVE_FILTER_LZ4:
- ret = archive_write_add_filter_lz4(arch_writer.data());
- requiresExecutable = true;
- break;
-#endif
- case ARCHIVE_FILTER_NONE:
- ret = archive_write_add_filter_none(arch_writer.data());
- break;
- default:
- emit error(i18n("The compression type '%1' is not supported by Ark.",
- QLatin1String(archive_filter_name(arch_reader.data(), 0))));
- return false;
- }
-
- // Libarchive emits a warning for lrzip due to using external executable.
- if ((requiresExecutable && ret != ARCHIVE_WARN) ||
- (!requiresExecutable && ret != ARCHIVE_OK)) {
- emit error(xi18nc("@info", "Setting the compression method failed with the following error:<nl/><message>%1</message>",
- QLatin1String(archive_error_string(arch_writer.data()))));
- return false;
- }
- }
-
- ret = archive_write_open_fd(arch_writer.data(), tempFile.handle());
- if (ret != ARCHIVE_OK) {
- emit error(xi18nc("@info", "Opening the archive for writing failed with the following error:<nl/><message>%1</message>",
- QLatin1String(archive_error_string(arch_writer.data()))));
+ if (!initializeWriter(creatingNewFile, options)) {
return false;
}
// First write the new files.
qCDebug(ARK) << "Writing new entries";
int no_entries = 0;
- foreach(Archive::Entry *selectedFile, files) {
+ // Recreate destination directory structure.
+ const QString destinationPath = (destination == Q_NULLPTR)
+ ? QString()
+ : destination->fullPath();
+ foreach(Archive::Entry *selectedFile, files) {
if (m_abortOperation) {
break;
}
- if (!writeFile(selectedFile->property("fullPath").toString(), arch_writer.data())) {
+ if (!writeFile(selectedFile->fullPath(), destinationPath)) {
+ finish(false);
return false;
}
no_entries++;
// For directories, write all subfiles/folders.
- const QString &fullPath = selectedFile->property("fullPath").toString();
+ const QString &fullPath = selectedFile->fullPath();
if (QFileInfo(fullPath).isDir()) {
QDirIterator it(fullPath,
QDir::AllEntries | QDir::Readable |
@@ -250,7 +103,8 @@ bool ReadWriteLibarchivePlugin::addFiles(const QList<Archive::Entry*>& files, co
path.append(QLatin1Char('/'));
}
- if (!writeFile(path, arch_writer.data())) {
+ if (!writeFile(path, destinationPath)) {
+ finish(false);
return false;
}
no_entries++;
@@ -259,202 +113,390 @@ bool ReadWriteLibarchivePlugin::addFiles(const QList<Archive::Entry*>& files, co
}
qCDebug(ARK) << "Added" << no_entries << "new entries to archive";
- struct archive_entry *entry;
-
+ bool isSuccessful = true;
// If we have old archive entries.
if (!creatingNewFile) {
-
qCDebug(ARK) << "Copying any old entries";
- no_entries = 0;
+ m_filesPaths = m_writtenFiles;
+ isSuccessful = processOldEntries(no_entries, Add);
+ if (isSuccessful) {
+ qCDebug(ARK) << "Added" << no_entries << "old entries to archive";
+ }
+ else {
+ qCDebug(ARK) << "Adding entries failed";
+ }
+ }
- // Copy old entries from previous archive to new archive.
- while (!m_abortOperation && (archive_read_next_header(arch_reader.data(), &entry) == ARCHIVE_OK)) {
+ m_abortOperation = false;
- const QString entryName = QFile::decodeName(archive_entry_pathname(entry));
+ finish(isSuccessful);
+ return isSuccessful;
+}
- if (m_writtenFiles.contains(entryName)) {
- archive_read_data_skip(arch_reader.data());
- qCDebug(ARK) << entryName << "is already present in the new archive, skipping.";
- continue;
- }
+bool ReadWriteLibarchivePlugin::moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
+ Q_UNUSED(options);
- const int returnCode = archive_write_header(arch_writer.data(), entry);
+ qCDebug(ARK) << "Moving" << files.size() << "entries";
- switch (returnCode) {
- case ARCHIVE_OK:
- // If the whole archive is extracted and the total filesize is
- // available, we use partial progress.
- copyData(QLatin1String(archive_entry_pathname(entry)), arch_reader.data(), arch_writer.data(), false);
- no_entries++;
- break;
- case ARCHIVE_FAILED:
- case ARCHIVE_FATAL:
- qCCritical(ARK) << "archive_write_header() has returned" << returnCode
- << "with errno" << archive_errno(arch_writer.data());
- emit error(xi18nc("@info", "Compression failed while processing:<nl/>"
- "<filename>%1</filename><nl/><nl/>Operation aborted.", entryName));
- QFile::remove(tempFile.fileName());
- return false;
- default:
- qCWarning(ARK) << "archive_writer_header() has returned" << returnCode
- << "which will be ignored.";
- break;
- }
- archive_entry_clear(entry);
- }
- qCDebug(ARK) << "Added" << no_entries << "old entries to archive";
+ if (!initializeReader()) {
+ return false;
}
- m_abortOperation = false;
+ if (!initializeWriter()) {
+ return false;
+ }
- // In the success case, we need to manually close the archive_writer before
- // calling QSaveFile::commit(), otherwise the latter will close() the
- // file descriptor archive_writer is still working on.
- // TODO: We need to abstract this code better so that we only deal with one
- // object that manages both QSaveFile and ArchiveWriter.
- archive_write_close(arch_writer.data());
- tempFile.commit();
+ // Copy old elements from previous archive to new archive.
+ int no_entries = 0;
+ m_filesPaths = entryFullPaths(files);
+ m_entriesWithoutChildren = entriesWithoutChildren(files).count();
+ m_destination = destination;
+ const bool isSuccessful = processOldEntries(no_entries, Move);
+ if (isSuccessful) {
+ qCDebug(ARK) << "Moved" << no_entries << "entries within archive";
+ }
+ else {
+ qCDebug(ARK) << "Moving entries failed";
+ }
- return true;
+ finish(isSuccessful);
+ return isSuccessful;
}
-bool ReadWriteLibarchivePlugin::deleteFiles(const QList<Archive::Entry*>& files)
+bool ReadWriteLibarchivePlugin::copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
{
- qCDebug(ARK) << "Deleting" << files.size() << "entries";
+ Q_UNUSED(options);
+
+ qCDebug(ARK) << "Copying" << files.size() << "entries";
- ArchiveRead arch_reader(archive_read_new());
- if (!(arch_reader.data())) {
- emit error(i18n("The archive reader could not be initialized."));
+ if (!initializeReader()) {
return false;
}
- if (archive_read_support_filter_all(arch_reader.data()) != ARCHIVE_OK) {
+ if (!initializeWriter()) {
return false;
}
- if (archive_read_support_format_all(arch_reader.data()) != ARCHIVE_OK) {
+ // Copy old elements from previous archive to new archive.
+ int no_entries = 0;
+ m_filesPaths = entryFullPaths(files);
+ m_entriesWithoutChildren = 0; // we don't care
+ m_destination = destination;
+ const bool isSuccessful = processOldEntries(no_entries, Copy);
+ if (isSuccessful) {
+ qCDebug(ARK) << "Copied" << no_entries << "entries within archive";
+ }
+ else {
+ qCDebug(ARK) << "Copying entries failed";
+ }
+
+ finish(isSuccessful);
+ return isSuccessful;
+}
+
+bool ReadWriteLibarchivePlugin::deleteFiles(const QList<Archive::Entry*> &files)
+{
+ qCDebug(ARK) << "Deleting" << files.size() << "entries";
+
+ if (!initializeReader()) {
return false;
}
- if (archive_read_open_filename(arch_reader.data(), QFile::encodeName(filename()), 10240) != ARCHIVE_OK) {
- emit error(i18n("The source file could not be read."));
+ if (!initializeWriter()) {
return false;
}
+ // Copy old elements from previous archive to new archive.
+ int no_entries = 0;
+ m_filesPaths = entryFullPaths(files);
+ const bool isSuccessful = processOldEntries(no_entries, Delete);
+ if (isSuccessful) {
+ qCDebug(ARK) << "Removed" << no_entries << "entries from archive";
+ }
+ else {
+ qCDebug(ARK) << "Removing entries failed";
+ }
+
+ finish(isSuccessful);
+ return isSuccessful;
+}
+
+bool ReadWriteLibarchivePlugin::initializeWriter(const bool creatingNewFile, const CompressionOptions &options)
+{
// |tempFile| needs to be created before |arch_writer| so that when we go
// out of scope in a `return false' case ArchiveWriteCustomDeleter is
// called before destructor of QSaveFile (ie. we call archive_write_close()
// before close()'ing the file descriptor).
- QSaveFile tempFile(filename());
- if (!tempFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
- emit error(i18nc("@info", "Failed to create a temporary file."));
+ m_tempFile.setFileName(filename());
+ if (!m_tempFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
+ emit error(xi18nc("@info", "Failed to create a temporary file to compress <filename>%1</filename>.", filename()));
return false;
}
- ArchiveWrite arch_writer(archive_write_new());
- if (!(arch_writer.data())) {
+ m_archiveWriter.reset(archive_write_new());
+ if (!(m_archiveWriter.data())) {
emit error(i18n("The archive writer could not be initialized."));
return false;
}
// pax_restricted is the libarchive default, let's go with that.
- archive_write_set_format_pax_restricted(arch_writer.data());
+ archive_write_set_format_pax_restricted(m_archiveWriter.data());
- int ret;
- switch (archive_filter_code(arch_reader.data(), 0)) {
- case ARCHIVE_FILTER_GZIP:
- ret = archive_write_add_filter_gzip(arch_writer.data());
- break;
- case ARCHIVE_FILTER_BZIP2:
- ret = archive_write_add_filter_bzip2(arch_writer.data());
- break;
- case ARCHIVE_FILTER_XZ:
- ret = archive_write_add_filter_xz(arch_writer.data());
- break;
- case ARCHIVE_FILTER_LZMA:
- ret = archive_write_add_filter_lzma(arch_writer.data());
- break;
- case ARCHIVE_FILTER_NONE:
- ret = archive_write_add_filter_none(arch_writer.data());
- break;
- default:
- emit error(i18n("The compression type '%1' is not supported by Ark.", QLatin1String(archive_filter_name(arch_reader.data(), 0))));
+ if (creatingNewFile) {
+ if (!initializeNewFileWriterFilters(options)) {
+ return false;
+ }
+ }
+ else {
+ if (!initializeWriterFilters()) {
+ return false;
+ }
+ }
+
+ if (archive_write_open_fd(m_archiveWriter.data(), m_tempFile.handle()) != ARCHIVE_OK) {
+ emit error(xi18nc("@info", "Opening the archive for writing failed with the following error:"
+ "<nl/><message>%1</message>", QLatin1String(archive_error_string(m_archiveWriter.data()))));
return false;
}
- if (ret != ARCHIVE_OK) {
- emit error(xi18nc("@info", "Setting the compression method failed with the following error:"
- "<nl/><message>%1</message>", QLatin1String(archive_error_string(arch_writer.data()))));
+ return true;
+}
+
+bool ReadWriteLibarchivePlugin::initializeWriterFilters()
+{
+ int ret;
+ bool requiresExecutable = false;
+ switch (archive_filter_code(m_archiveReader.data(), 0)) {
+ case ARCHIVE_FILTER_GZIP:
+ ret = archive_write_add_filter_gzip(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_BZIP2:
+ ret = archive_write_add_filter_bzip2(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_XZ:
+ ret = archive_write_add_filter_xz(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_LZMA:
+ ret = archive_write_add_filter_lzma(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_COMPRESS:
+ ret = archive_write_add_filter_compress(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_LZIP:
+ ret = archive_write_add_filter_lzip(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_LZOP:
+ ret = archive_write_add_filter_lzop(m_archiveWriter.data());
+ break;
+ case ARCHIVE_FILTER_LRZIP:
+ ret = archive_write_add_filter_lrzip(m_archiveWriter.data());
+ requiresExecutable = true;
+ break;
+#ifdef HAVE_LIBARCHIVE_3_2_0
+ case ARCHIVE_FILTER_LZ4:
+ ret = archive_write_add_filter_lz4(m_archiveWriter.data());
+ break;
+#endif
+ case ARCHIVE_FILTER_NONE:
+ ret = archive_write_add_filter_none(m_archiveWriter.data());
+ break;
+ default:
+ emit error(i18n("The compression type '%1' is not supported by Ark.",
+ QLatin1String(archive_filter_name(m_archiveReader.data(), 0))));
+ return false;
+ }
+
+ // Libarchive emits a warning for lrzip due to using external executable.
+ if ((requiresExecutable && ret != ARCHIVE_WARN) ||
+ (!requiresExecutable && ret != ARCHIVE_OK)) {
+ emit error(xi18nc("@info", "Setting the compression method failed with the following error:<nl/><message>%1</message>",
+ QLatin1String(archive_error_string(m_archiveWriter.data()))));
return false;
}
- ret = archive_write_open_fd(arch_writer.data(), tempFile.handle());
- if (ret != ARCHIVE_OK) {
- emit error(xi18nc("@info", "Opening the archive for writing failed with the following error:"
- "<nl/><message>%1</message>", QLatin1String(archive_error_string(arch_writer.data()))));
+ return true;
+}
+
+bool ReadWriteLibarchivePlugin::initializeNewFileWriterFilters(const CompressionOptions &options)
+{
+ int ret;
+ bool requiresExecutable = false;
+ if (filename().right(2).toUpper() == QLatin1String("GZ")) {
+ qCDebug(ARK) << "Detected gzip compression for new file";
+ ret = archive_write_add_filter_gzip(m_archiveWriter.data());
+ } else if (filename().right(3).toUpper() == QLatin1String("BZ2")) {
+ qCDebug(ARK) << "Detected bzip2 compression for new file";
+ ret = archive_write_add_filter_bzip2(m_archiveWriter.data());
+ } else if (filename().right(2).toUpper() == QLatin1String("XZ")) {
+ qCDebug(ARK) << "Detected xz compression for new file";
+ ret = archive_write_add_filter_xz(m_archiveWriter.data());
+ } else if (filename().right(4).toUpper() == QLatin1String("LZMA")) {
+ qCDebug(ARK) << "Detected lzma compression for new file";
+ ret = archive_write_add_filter_lzma(m_archiveWriter.data());
+ } else if (filename().right(2).toUpper() == QLatin1String(".Z")) {
+ qCDebug(ARK) << "Detected compress (.Z) compression for new file";
+ ret = archive_write_add_filter_compress(m_archiveWriter.data());
+ } else if (filename().right(2).toUpper() == QLatin1String("LZ")) {
+ qCDebug(ARK) << "Detected lzip compression for new file";
+ ret = archive_write_add_filter_lzip(m_archiveWriter.data());
+ } else if (filename().right(3).toUpper() == QLatin1String("LZO")) {
+ qCDebug(ARK) << "Detected lzop compression for new file";
+ ret = archive_write_add_filter_lzop(m_archiveWriter.data());
+ } else if (filename().right(3).toUpper() == QLatin1String("LRZ")) {
+ qCDebug(ARK) << "Detected lrzip compression for new file";
+ ret = archive_write_add_filter_lrzip(m_archiveWriter.data());
+ requiresExecutable = true;
+#ifdef HAVE_LIBARCHIVE_3_2_0
+ } else if (filename().right(3).toUpper() == QLatin1String("LZ4")) {
+ qCDebug(ARK) << "Detected lz4 compression for new file";
+ ret = archive_write_add_filter_lz4(m_archiveWriter.data());
+#endif
+ } else if (filename().right(3).toUpper() == QLatin1String("TAR")) {
+ qCDebug(ARK) << "Detected no compression for new file (pure tar)";
+ ret = archive_write_add_filter_none(m_archiveWriter.data());
+ } else {
+ qCDebug(ARK) << "Falling back to gzip";
+ ret = archive_write_add_filter_gzip(m_archiveWriter.data());
+ }
+
+ // Libarchive emits a warning for lrzip due to using external executable.
+ if ((requiresExecutable && ret != ARCHIVE_WARN) ||
+ (!requiresExecutable && ret != ARCHIVE_OK)) {
+ emit error(xi18nc("@info", "Setting the compression method failed with the following error:<nl/><message>%1</message>",
+ QLatin1String(archive_error_string(m_archiveWriter.data()))));
return false;
}
+ // Set compression level if passed in CompressionOptions.
+ if (options.contains(QStringLiteral("CompressionLevel"))) {
+ qCDebug(ARK) << "Using compression level:" << options.value(QStringLiteral("CompressionLevel")).toString();
+ ret = archive_write_set_filter_option(m_archiveWriter.data(), NULL, "compression-level", options.value(QStringLiteral("CompressionLevel")).toString().toUtf8());
+ if (ret != ARCHIVE_OK) {
+ qCWarning(ARK) << "Failed to set compression level";
+ emit error(xi18nc("@info", "Setting the compression level failed with the following error:<nl/><message>%1</message>",
+ QLatin1String(archive_error_string(m_archiveWriter.data()))));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ReadWriteLibarchivePlugin::finish(const bool isSuccessful)
+{
+ if (!isSuccessful) {
+ m_tempFile.cancelWriting();
+ }
+ archive_write_close(m_archiveWriter.data());
+ m_tempFile.commit();
+}
+
+bool ReadWriteLibarchivePlugin::processOldEntries(int &entriesCounter, OperationMode mode)
+{
struct archive_entry *entry;
+ entriesCounter = 0;
+ QMap<QString, QString> pathMap;
+ if (mode == Move || mode == Copy) {
+ m_filesPaths.sort();
+ QStringList resultList = entryPathsFromDestination(m_filesPaths, m_destination, m_entriesWithoutChildren);
+ const int listSize = m_filesPaths.count();
+ Q_ASSERT(listSize == resultList.count());
+ for (int i = 0; i < listSize; ++i) {
+ pathMap.insert(m_filesPaths.at(i), resultList.at(i));
+ }
+ }
- // Copy old elements from previous archive to new archive.
- int no_entries = 0;
- QStringList filePaths = entryFullPaths(files);
- while (archive_read_next_header(arch_reader.data(), &entry) == ARCHIVE_OK) {
+ while ((mode != Add || !m_abortOperation) && archive_read_next_header(m_archiveReader.data(), &entry) == ARCHIVE_OK) {
- QString file = QFile::decodeName(archive_entry_pathname(entry));
+ const QString file = QFile::decodeName(archive_entry_pathname(entry));
- if (filePaths.contains(file)) {
- archive_read_data_skip(arch_reader.data());
- emit entryRemoved(file);
- no_entries++;
+ if (mode == Move || mode == Copy) {
+ const QString newPathname = pathMap.value(file);
+ if (!newPathname.isEmpty()) {
+ if (mode == Copy) {
+ if (!writeEntry(entry)) {
+ return false;
+ }
+ }
+ else {
+ emit entryRemoved(file);
+ }
+
+ entriesCounter++;
+ archive_entry_set_pathname(entry, newPathname.toUtf8());
+ }
+ }
+ else if (m_filesPaths.contains(file)) {
+ archive_read_data_skip(m_archiveReader.data());
+ switch (mode) {
+ case Delete:
+ entriesCounter++;
+ emit entryRemoved(file);
+ break;
+
+ case Add:
+ qCDebug(ARK) << file << "is already present in the new archive, skipping.";
+ break;
+
+ default:
+ qCDebug(ARK) << "Mode" << mode << "is not considered for processing old libarchive entries";
+ Q_ASSERT(false);
+ }
continue;
}
- const int returnCode = archive_write_header(arch_writer.data(), entry);
+ if (writeEntry(entry)) {
+ if (mode == Add) {
+ entriesCounter++;
+ }
+ else if (mode == Move || mode == Copy) {
+ emitEntryFromArchiveEntry(entry);
+ }
+ }
+ else {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool ReadWriteLibarchivePlugin::writeEntry(struct archive_entry *entry) {
+ const int returnCode = archive_write_header(m_archiveWriter.data(), entry);
+ const QString file = QFile::decodeName(archive_entry_pathname(entry));
- switch (returnCode) {
+ switch (returnCode) {
case ARCHIVE_OK:
// If the whole archive is extracted and the total filesize is
// available, we use partial progress.
- copyData(QLatin1String(archive_entry_pathname(entry)), arch_reader.data(), arch_writer.data(), false);
+ copyData(QLatin1String(archive_entry_pathname(entry)), m_archiveReader.data(), m_archiveWriter.data(), false);
break;
case ARCHIVE_FAILED:
case ARCHIVE_FATAL:
qCCritical(ARK) << "archive_write_header() has returned" << returnCode
- << "with errno" << archive_errno(arch_writer.data());
+ << "with errno" << archive_errno(m_archiveWriter.data());
emit error(xi18nc("@info", "Compression failed while processing:<nl/>"
- "<filename>%1</filename><nl/><nl/>Operation aborted.", file));
+ "<filename>%1</filename><nl/><nl/>Operation aborted.", file));
return false;
default:
qCDebug(ARK) << "archive_writer_header() has returned" << returnCode
<< "which will be ignored.";
break;
- }
}
- qCDebug(ARK) << "Removed" << no_entries << "entries from archive";
-
- m_abortOperation = false;
-
- // In the success case, we need to manually close the archive_writer before
- // calling QSaveFile::commit(), otherwise the latter will close() the
- // file descriptor archive_writer is still working on.
- // TODO: We need to abstract this code better so that we only deal with one
- // object that manages both QSaveFile and ArchiveWriter.
- archive_write_close(arch_writer.data());
- tempFile.commit();
return true;
}
// TODO: if we merge this with copyData(), we can pass more data
// such as an fd to archive_read_disk_entry_from_file()
-bool ReadWriteLibarchivePlugin::writeFile(const QString& relativeName, struct archive* arch_writer)
+bool ReadWriteLibarchivePlugin::writeFile(const QString &relativeName, const QString &destination)
{
int header_response;
const QString absoluteFilename = QFileInfo(relativeName).absoluteFilePath();
+ const QString destinationFilename = destination + relativeName;
// #253059: Even if we use archive_read_disk_entry_from_file,
// libarchive may have been compiled without HAVE_LSTAT,
@@ -465,29 +507,29 @@ bool ReadWriteLibarchivePlugin::writeFile(const QString& relativeName, struct ar
lstat(QFile::encodeName(absoluteFilename).constData(), &st);
struct archive_entry *entry = archive_entry_new();
- archive_entry_set_pathname(entry, QFile::encodeName(relativeName).constData());
+ archive_entry_set_pathname(entry, QFile::encodeName(destinationFilename).constData());
archive_entry_copy_sourcepath(entry, QFile::encodeName(absoluteFilename).constData());
archive_read_disk_entry_from_file(m_archiveReadDisk.data(), entry, -1, &st);
- if ((header_response = archive_write_header(arch_writer, entry)) == ARCHIVE_OK) {
+ if ((header_response = archive_write_header(m_archiveWriter.data(), entry)) == ARCHIVE_OK) {
// If the whole archive is extracted and the total filesize is
// available, we use partial progress.
- copyData(absoluteFilename, arch_writer, false);
+ copyData(absoluteFilename, m_archiveWriter.data(), false);
} else {
qCCritical(ARK) << "Writing header failed with error code " << header_response;
- qCCritical(ARK) << "Error while writing..." << archive_error_string(arch_writer) << "(error no =" << archive_errno(arch_writer) << ')';
+ qCCritical(ARK) << "Error while writing..." << archive_error_string(m_archiveWriter.data()) << "(error no =" << archive_errno(m_archiveWriter.data()) << ')';
emit error(xi18nc("@info Error in a message box",
"Ark could not compress <filename>%1</filename>:<nl/>%2",
absoluteFilename,
- QString::fromUtf8(archive_error_string(arch_writer))));
+ QString::fromUtf8(archive_error_string(m_archiveWriter.data()))));
archive_entry_free(entry);
return false;
}
- m_writtenFiles.push_back(relativeName);
+ m_writtenFiles.push_back(destinationFilename);
emitEntryFromArchiveEntry(entry);
diff --git a/plugins/libarchive/readwritelibarchiveplugin.h b/plugins/libarchive/readwritelibarchiveplugin.h
index 1fb9938..81f87d0 100644
--- a/plugins/libarchive/readwritelibarchiveplugin.h
+++ b/plugins/libarchive/readwritelibarchiveplugin.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
* Copyright (c) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
+ * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -31,6 +32,7 @@
#include <QDir>
#include <QStringList>
+#include <QtCore/QSaveFile>
using namespace Kerfuffle;
@@ -39,16 +41,56 @@ class ReadWriteLibarchivePlugin : public LibarchivePlugin
Q_OBJECT
public:
- explicit ReadWriteLibarchivePlugin(QObject *parent, const QVariantList& args);
+ explicit ReadWriteLibarchivePlugin(QObject *parent, const QVariantList &args);
~ReadWriteLibarchivePlugin();
- bool addFiles(const QList<Archive::Entry*>& files, const CompressionOptions& options) Q_DECL_OVERRIDE;
- bool deleteFiles(const QList<Archive::Entry*>& files) Q_DECL_OVERRIDE;
+ bool addFiles(const QList<Archive::Entry*> &files, const Archive::Entry *destination, const CompressionOptions &options) Q_DECL_OVERRIDE;
+ bool moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options) Q_DECL_OVERRIDE;
+ bool copyFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options) Q_DECL_OVERRIDE;
+ bool deleteFiles(const QList<Archive::Entry*> &files) Q_DECL_OVERRIDE;
+
+protected:
+ bool initializeWriter(const bool creatingNewFile = false, const CompressionOptions &options = CompressionOptions());
+ bool initializeWriterFilters();
+ bool initializeNewFileWriterFilters(const CompressionOptions &options);
+ void finish(const bool isSuccessful);
private:
- bool writeFile(const QString& relativeName, struct archive* arch);
+ /**
+ * Processes all the existing entries and does manipulations to them
+ * based on the OperationMode (Add/Move/Copy/Delete).
+ *
+ * @param entriesCounter Counter of added/moved/copied/deleted entries.
+ *
+ * @return bool indicating whether the operation was successful.
+ */
+ bool processOldEntries(int &entriesCounter, OperationMode mode);
+
+ /**
+ * Writes entry being read into memory.
+ *
+ * @return bool indicating whether the operation was successful.
+ */
+ bool writeEntry(struct archive_entry *entry);
+
+ /**
+ * Writes entry from physical disk.
+ *
+ * @return bool indicating whether the operation was successful.
+ */
+ bool writeFile(const QString &relativeName, const QString &destination);
+ QSaveFile m_tempFile;
+ ArchiveWrite m_archiveWriter;
+
+ // New added files by addFiles methods. It's assigned to m_filesPaths
+ // and then is used by processOldEntries method (in Add mode) for skipping already written entries.
QStringList m_writtenFiles;
+
+ // Passed argument from job which is used by processOldEntries method.
+ QStringList m_filesPaths;
+ int m_entriesWithoutChildren;
+ const Archive::Entry *m_destination;
};
#endif // READWRITELIBARCHIVEPLUGIN_H
diff --git a/plugins/libsinglefileplugin/singlefileplugin.cpp b/plugins/libsinglefileplugin/singlefileplugin.cpp
index fa7d2f0..8a61f27 100644
--- a/plugins/libsinglefileplugin/singlefileplugin.cpp
+++ b/plugins/libsinglefileplugin/singlefileplugin.cpp
@@ -44,7 +44,7 @@ LibSingleFileInterface::~LibSingleFileInterface()
{
}
-bool LibSingleFileInterface::copyFiles(const QList<Kerfuffle::Archive::Entry*> &files, const QString& destinationDirectory, const Kerfuffle::ExtractionOptions& options)
+bool LibSingleFileInterface::extractFiles(const QList<Kerfuffle::Archive::Entry*> &files, const QString &destinationDirectory, const Kerfuffle::ExtractionOptions &options)
{
Q_UNUSED(files)
Q_UNUSED(options)
diff --git a/plugins/libsinglefileplugin/singlefileplugin.h b/plugins/libsinglefileplugin/singlefileplugin.h
index 29d2a92..a6ff21b 100644
--- a/plugins/libsinglefileplugin/singlefileplugin.h
+++ b/plugins/libsinglefileplugin/singlefileplugin.h
@@ -39,7 +39,7 @@ public:
virtual bool list() Q_DECL_OVERRIDE;
virtual bool testArchive() Q_DECL_OVERRIDE;
- virtual bool copyFiles(const QList<Kerfuffle::Archive::Entry*> &files, const QString& destinationDirectory, const Kerfuffle::ExtractionOptions& options) Q_DECL_OVERRIDE;
+ virtual bool extractFiles(const QList<Kerfuffle::Archive::Entry*> &files, const QString &destinationDirectory, const Kerfuffle::ExtractionOptions &options) Q_DECL_OVERRIDE;
protected:
const QString uncompressedFileName() const;