[ark/gsoc2016/master] /: [GSoC] Merge master into gsoc2016/master
Vladyslav Batyrenko
mvlabat at gmail.com
Mon Aug 15 17:05:24 UTC 2016
Git commit 7eb3304db549df12afca7e25698cdf369e6e4a7e by Vladyslav Batyrenko.
Committed on 15/08/2016 at 17:04.
Pushed by vbatyrenko into branch 'gsoc2016/master'.
[GSoC] Merge master into gsoc2016/master
M +3 -3 app/batchextract.cpp
M +3 -1 app/org.kde.ark.desktop.cmake
M +1 -0 autotests/CMakeLists.txt
M +105 -8 autotests/kerfuffle/addtoarchivetest.cpp
R +65 -18 autotests/kerfuffle/extracttest.cpp
M +5 -0 autotests/kerfuffle/jobstest.cpp
M +46 -10 autotests/plugins/cli7zplugin/cli7ztest.cpp
M +47 -15 autotests/plugins/clirarplugin/clirartest.cpp
M +7 -1 autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
M +1 -1 autotests/plugins/clizipplugin/cliziptest.cpp
A +- -- doc/create-archive.png
M +- -- doc/create-protected-archive.png
M +48 -4 doc/index.docbook
M +16 -3 doc/man-ark.1.docbook
M +3 -3 kerfuffle/addtoarchive.cpp
M +65 -9 kerfuffle/archive_kerfuffle.cpp
M +13 -2 kerfuffle/archive_kerfuffle.h
M +22 -0 kerfuffle/archiveinterface.cpp
M +7 -0 kerfuffle/archiveinterface.h
M +86 -42 kerfuffle/cliinterface.cpp
M +43 -28 kerfuffle/cliinterface.h
M +42 -29 kerfuffle/jobs.cpp
M +8 -17 kerfuffle/jobs.h
M +84 -2 kerfuffle/mime/kerfuffle.xml
M +73 -9 part/archivemodel.cpp
M +13 -0 part/archivemodel.h
M +67 -20 part/part.cpp
M +2 -2 part/part.h
M +11 -3 plugins/cli7zplugin/cliplugin.cpp
M +23 -8 plugins/clirarplugin/cliplugin.cpp
M +0 -1 plugins/clirarplugin/cliplugin.h
M +85 -27 plugins/cliunarchiverplugin/cliplugin.cpp
M +4 -1 plugins/cliunarchiverplugin/cliplugin.h
M +2 -1 plugins/clizipplugin/cliplugin.cpp
http://commits.kde.org/ark/7eb3304db549df12afca7e25698cdf369e6e4a7e
diff --cc app/org.kde.ark.desktop.cmake
index c68523b,9f61e7d..e6bede5
--- a/app/org.kde.ark.desktop.cmake
+++ b/app/org.kde.ark.desktop.cmake
@@@ -178,6 -178,6 +178,7 @@@ Comment[nl]=Mert bestandsarchieven werk
Comment[pl]=Pracuj z archiwami plików
Comment[pt]=Lidar com pacotes em ficheiros
Comment[pt_BR]=Manipulação de arquivos compactados
++Comment[ru]=Работа с архивами файлов
Comment[sk]=Práca s archívmi súborov
Comment[sl]=Delajte z datotečnimi arhivi
Comment[sr]=Рад са фајловима архива
diff --cc autotests/CMakeLists.txt
index 2ea2753,71c7995..f477472
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@@ -1,5 -1,5 +1,6 @@@
include(ECMAddTests)
+ add_subdirectory(app)
+add_subdirectory(testhelper)
add_subdirectory(kerfuffle)
add_subdirectory(plugins)
diff --cc autotests/kerfuffle/addtoarchivetest.cpp
index 410bb96,0613d6e..abaa0b4
--- a/autotests/kerfuffle/addtoarchivetest.cpp
+++ b/autotests/kerfuffle/addtoarchivetest.cpp
@@@ -38,11 -40,17 +40,18 @@@ class AddToArchiveTest : public QObjec
private Q_SLOTS:
+ void init();
void testCompressHere_data();
void testCompressHere();
+ void testCreateEncryptedArchive();
};
+ void AddToArchiveTest::init()
+ {
+ // The test needs an empty subfolder, but git doesn't support tracking of empty directories.
+ QDir(QFINDTESTDATA("data/testdirwithemptysubdir")).mkdir(QStringLiteral("emptydir"));
+ }
+
void AddToArchiveTest::testCompressHere_data()
{
QTest::addColumn<QString>("expectedSuffix");
diff --cc autotests/kerfuffle/extracttest.cpp
index ca18801,eae0ea8..02aac41
--- a/autotests/kerfuffle/extracttest.cpp
+++ b/autotests/kerfuffle/extracttest.cpp
@@@ -168,11 -171,29 +170,29 @@@ void ExtractTest::testProperties_data(
QTest::newRow("mimetype child of application/zip")
<< QFINDTESTDATA("data/test.odt")
<< QStringLiteral("test")
- << false << true << false << Archive::Unencrypted
+ << false << true << false << false << 0 << Archive::Unencrypted
<< QStringLiteral("test");
+
+ QTest::newRow("AppImage")
+ << QFINDTESTDATA("data/hello-1.0-x86_64.AppImage")
+ << QStringLiteral("hello-1.0-x86_64")
+ << true << false << false << false << 0 << Archive::Unencrypted
+ << QStringLiteral("hello-1.0-x86_64");
+
+ QTest::newRow("7z multivolume")
+ << QFINDTESTDATA("data/archive-multivolume.7z.001")
+ << QStringLiteral("archive-multivolume")
+ << true << false << false << true << 3 << Archive::Unencrypted
+ << QStringLiteral("archive-multivolume");
+
+ QTest::newRow("rar multivolume")
+ << QFINDTESTDATA("data/archive-multivolume.part1.rar")
+ << QStringLiteral("archive-multivolume")
+ << true << false << false << true << 3 << Archive::Unencrypted
+ << QStringLiteral("archive-multivolume");
}
-void ArchiveTest::testProperties()
+void ExtractTest::testProperties()
{
QFETCH(QString, archivePath);
Archive *archive = Archive::create(archivePath, this);
@@@ -506,12 -533,33 +532,33 @@@ void ExtractTest::testExtraction_data(
archivePath = QFINDTESTDATA("data/simplearchive.xar");
QTest::newRow("extract all entries from a xar archive with path")
<< archivePath
- << QVariantList()
+ << QList<Archive::Entry*>()
<< optionsPreservePaths
<< 6;
+
- archivePath = QFINDTESTDATA("data/hello-2.8-x86_64.AppImage");
++ archivePath = QFINDTESTDATA("data/hello-1.0-x86_64.AppImage");
+ QTest::newRow("extract all entries from an AppImage with path")
+ << archivePath
- << QVariantList()
++ << QList<Archive::Entry*>()
+ << optionsPreservePaths
+ << 7;
+
+ archivePath = QFINDTESTDATA("data/archive-multivolume.7z.001");
+ QTest::newRow("extract all entries from a multivolume 7z archive with path")
+ << archivePath
- << QVariantList()
++ << QList<Archive::Entry*>()
+ << optionsPreservePaths
+ << 3;
+
+ archivePath = QFINDTESTDATA("data/archive-multivolume.part1.rar");
+ QTest::newRow("extract all entries from a multivolume rar archive with path")
+ << archivePath
- << QVariantList()
++ << QList<Archive::Entry*>()
+ << optionsPreservePaths
+ << 3;
}
-void ArchiveTest::testExtraction()
+void ExtractTest::testExtraction()
{
QFETCH(QString, archivePath);
Archive *archive = Archive::create(archivePath, this);
diff --cc autotests/kerfuffle/jobstest.cpp
index 6392d8e,f20bd90..073fe3f
--- a/autotests/kerfuffle/jobstest.cpp
+++ b/autotests/kerfuffle/jobstest.cpp
@@@ -253,8 -253,10 +253,10 @@@ void JobsTest::testExtractJobAccessors(
void JobsTest::testTempExtractJob()
{
JSONArchiveInterface *iface = createArchiveInterface(QFINDTESTDATA("data/archive-malicious.json"));
- PreviewJob *job = new PreviewJob(QStringLiteral("anotherDir/../../file.txt"), false, iface);
+ PreviewJob *job = new PreviewJob(new Archive::Entry(this, QStringLiteral("anotherDir/../../file.txt")), false, iface);
+ const QString tempDirPath = job->tempDir()->path();
+ QVERIFY(QFileInfo::exists(tempDirPath));
QVERIFY(job->validatedFilePath().endsWith(QLatin1String("anotherDir/file.txt")));
QVERIFY(job->extractionOptions()[QStringLiteral("PreservePaths")].toBool());
diff --cc autotests/plugins/cli7zplugin/cli7ztest.cpp
index 3cb3e4f,727214c..655980f
--- a/autotests/plugins/cli7zplugin/cli7ztest.cpp
+++ b/autotests/plugins/cli7zplugin/cli7ztest.cpp
@@@ -159,24 -176,30 +176,30 @@@ void Cli7zTest::testList(
QCOMPARE(signalSpy.count(), expectedEntriesCount);
+ QFETCH(bool, isMultiVolume);
+ QCOMPARE(plugin->isMultiVolume(), isMultiVolume);
+
+ QFETCH(int, numberOfVolumes);
+ QCOMPARE(plugin->numberOfVolumes(), numberOfVolumes);
+
QFETCH(int, someEntryIndex);
QVERIFY(someEntryIndex < signalSpy.count());
- ArchiveEntry entry = qvariant_cast<ArchiveEntry>(signalSpy.at(someEntryIndex).at(0));
+ Archive::Entry *entry = signalSpy.at(someEntryIndex).at(0).value<Archive::Entry*>();
QFETCH(QString, expectedName);
- QCOMPARE(entry[FileName].toString(), expectedName);
+ QCOMPARE(entry->fullPath(), expectedName);
QFETCH(bool, isDirectory);
- QCOMPARE(entry[IsDirectory].toBool(), isDirectory);
+ QCOMPARE(entry->isDir(), isDirectory);
QFETCH(bool, isPasswordProtected);
- QCOMPARE(entry[IsPasswordProtected].toBool(), isPasswordProtected);
+ QCOMPARE(entry->property("isPasswordProtected").toBool(), isPasswordProtected);
QFETCH(qulonglong, expectedSize);
- QCOMPARE(entry[Size].toULongLong(), expectedSize);
+ QCOMPARE(entry->property("size").toULongLong(), expectedSize);
QFETCH(QString, expectedTimestamp);
- QCOMPARE(entry[Timestamp].toString(), expectedTimestamp);
+ QCOMPARE(entry->property("timestamp").toString(), expectedTimestamp);
plugin->deleteLater();
}
diff --cc autotests/plugins/clirarplugin/clirartest.cpp
index 4640e8a,d2988d9..5775b2b
--- a/autotests/plugins/clirarplugin/clirartest.cpp
+++ b/autotests/plugins/clirarplugin/clirartest.cpp
@@@ -187,30 -200,36 +200,36 @@@ void CliRarTest::testList(
QCOMPARE(signalSpy.count(), expectedEntriesCount);
+ QFETCH(bool, isMultiVolume);
+ QCOMPARE(rarPlugin->isMultiVolume(), isMultiVolume);
+
+ QFETCH(int, numberOfVolumes);
+ QCOMPARE(rarPlugin->numberOfVolumes(), numberOfVolumes);
+
QFETCH(int, someEntryIndex);
QVERIFY(someEntryIndex < signalSpy.count());
- ArchiveEntry entry = qvariant_cast<ArchiveEntry>(signalSpy.at(someEntryIndex).at(0));
+ Archive::Entry *entry = signalSpy.at(someEntryIndex).at(0).value<Archive::Entry*>();
QFETCH(QString, expectedName);
- QCOMPARE(entry[FileName].toString(), expectedName);
+ QCOMPARE(entry->fullPath(), expectedName);
QFETCH(bool, isDirectory);
- QCOMPARE(entry[IsDirectory].toBool(), isDirectory);
+ QCOMPARE(entry->isDir(), isDirectory);
QFETCH(bool, isPasswordProtected);
- QCOMPARE(entry[IsPasswordProtected].toBool(), isPasswordProtected);
+ QCOMPARE(entry->property("isPasswordProtected").toBool(), isPasswordProtected);
QFETCH(QString, symlinkTarget);
- QCOMPARE(entry[Link].toString(), symlinkTarget);
+ QCOMPARE(entry->property("link").toString(), symlinkTarget);
QFETCH(qulonglong, expectedSize);
- QCOMPARE(entry[Size].toULongLong(), expectedSize);
+ QCOMPARE(entry->property("size").toULongLong(), expectedSize);
QFETCH(qulonglong, expectedCompressedSize);
- QCOMPARE(entry[CompressedSize].toULongLong(), expectedCompressedSize);
+ QCOMPARE(entry->property("compressedSize").toULongLong(), expectedCompressedSize);
QFETCH(QString, expectedTimestamp);
- QCOMPARE(entry[Timestamp].toString(), expectedTimestamp);
+ QCOMPARE(entry->property("timestamp").toString(), expectedTimestamp);
rarPlugin->deleteLater();
}
diff --cc autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
index eb22cfe,3713cc4..2b2fd1d
--- a/autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
+++ b/autotests/plugins/cliunarchiverplugin/cliunarchivertest.cpp
@@@ -273,9 -273,15 +273,15 @@@ void CliUnarchiverTest::testExtraction_
QTest::newRow("rar with empty folders")
<< QFINDTESTDATA("data/empty_folders.rar")
- << QVariantList()
+ << QList<Archive::Entry*>()
<< optionsPreservePaths
<< 5;
+
+ QTest::newRow("rar with hidden folder and files")
+ << QFINDTESTDATA("data/hidden_files.rar")
- << QVariantList()
++ << QList<Archive::Entry*>()
+ << optionsPreservePaths
+ << 4;
}
// TODO: we can remove this test (which is duplicated from kerfuffle/archivetest)
diff --cc doc/create-archive.png
index 0000000,0000000..40a6bf5
new file mode 100644
Binary files differ
diff --cc doc/create-protected-archive.png
index 686d701,686d701..cf3d5f2
Binary files differ
diff --cc doc/index.docbook
index 73edbcd,da20381..6b17457
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@@ -45,7 -44,7 +44,7 @@@
<legalnotice>&FDLNotice;</legalnotice>
--<date>2016-05-05</date>
++<date>2016-08-09</date>
<releaseinfo>Applications 16.08</releaseinfo>
<abstract>
@@@ -74,7 -73,7 +73,8 @@@ archives
<command>tar</command>, <command>gzip</command>,
<command>bzip2</command>, <command>zip</command>, <command>rar</command>,
<command>7zip</command>, <command>xz</command>, <command>rpm</command>,
--<command>cab</command> and <command>deb</command> (support for certain archive formats depends on
++<command>cab</command>, <command>deb</command>, <command>xar</command>
++and <command>AppImage</command> (support for certain archive formats depends on
the appropriate command-line programs being installed).</para>
<mediaobject>
@@@ -121,6 -120,6 +121,10 @@@ For example, you can save the archive w
Archive properties such as type, size and MD5 hash can be viewed using the
<guimenuitem>Properties</guimenuitem> item.</para>
++<para>&ark; has the ability to test archives for integrity. This functionality is currently available for
++<command>zip</command>, <command>rar</command> and <command>7z</command> archives.
++The test action can be found in the <guimenu>Archive</guimenu> menu.</para>
++
</sect2>
<sect2 id="ark-archive-comments">
@@@ -263,11 -262,11 +267,20 @@@ archive.</para
<guimenuitem>New</guimenuitem> (<keycombo action="simul">&Ctrl;<keycap>N</keycap></keycombo>)
from the <guimenu>Archive</guimenu> menu.</para>
++<mediaobject>
++<imageobject>
++<imagedata fileref="create-archive.png" format="PNG"/>
++</imageobject>
++<textobject>
++<phrase>Create an archive</phrase>
++</textobject>
++</mediaobject>
++
<para>You can then type the name of the archive, with the appropriate
extension (<literal role="extension">tar.gz</literal>, <literal
role="extension">zip</literal>, <literal role="extension">7z</literal>,
&etc;) or select a supported format in the <guilabel>Filter</guilabel> combo box
--and check the <guilabel>Automatically select filename extension</guilabel> option.</para>
++and check the <guilabel>Automatically add <replaceable>filename extension</replaceable></guilabel> option.</para>
<para>To add files or folders to the new archive, choose <guimenuitem>Add
File...</guimenuitem> or <guimenuitem>Add
@@@ -278,6 -277,6 +291,17 @@@ from e.g. &dolphin; into the main &ark
be added to the current archive. Note that files added in this way will always be added
to the root directory of the archive.</para>
++<para>Additional options are presented in collapsible groups at the bottom of the dialog.
++</para>
++
++<sect2 id="ark-compression">
++<title>Compression</title>
++<para>A higher value generates smaller archives, but results in longer compression and decompression times.
++The default compression level proposed by &ark; is usually a good compromise between size and (de)compression speed.
++For most formats the minimum compression level is equivalent to just storing the files, &ie; applying no compression.
++</para>
++</sect2>
++
<sect2 id="ark-password-protection">
<title>Password Protection</title>
<para>If you create a <literal role="extension">zip</literal>, <literal
@@@ -298,8 -297,8 +322,28 @@@ This is called header encryption and i
<literal role="extension">rar</literal> and <literal role="extension">7zip</literal>
formats. Header encryption is enabled by default (when available), in order to offer
the maximum protection for novice users.</para>
++</sect2>
++<sect2 id="ark-multi-volume">
++<title>Multi-volume Archive</title>
++<para>With the <literal role="extension">zip</literal>, <literal role="extension">rar</literal>
++and <literal role="extension">7z</literal> formats you can create multi-volume archives, also
++known as multi-part or split archives.</para>
++<para>A multi-volume archive is one big compressed archive split into several files. This feature
++is useful if the maximum file size is limited, ⪚ by the capacity of a storage medium
++or the maximal size of an email with attachments.</para>
++<para>To create a multi-volume archive, check the <guilabel>Create multi-volume archive</guilabel>
++checkbox and set a maximum <guilabel>Volume size</guilabel> in the dialog. Then add all files to
++the archive and &ark; will automatically generate the required number of archive volumes.
++Depending on the selected format the files have an extension with consecutively numbering
++scheme ⪚ <filename>xxx.7z.001</filename>, <filename>xxx.7z.002</filename> or
++<filename>xxx.zip.001</filename>, <filename>xxx.zip.002</filename> or <filename>xxx.part1.rar</filename>,
++<filename>xxx.part2.rar</filename> &etc;.</para>
++<para>To extract a multi-volume archive, put all archive files into one folder and open the file
++with the lowest extension number in &ark; and all other parts of the split archive
++will be opened automatically.</para>
</sect2>
++
</sect1>
</chapter>
diff --cc doc/man-ark.1.docbook
index 9786bca,9786bca..b98cb54
--- a/doc/man-ark.1.docbook
+++ b/doc/man-ark.1.docbook
@@@ -19,8 -19,8 +19,8 @@@
<contrib>Update of &ark; man page in 2015 and 2016.</contrib>
<email>rthomsen6 at gmail.com</email></author>
--<date>2016-03-19</date><!--Update only when changing/reviewing this man page-->
--<releaseinfo>16.04</releaseinfo><!--Update only when changing/reviewing this man page-->
++<date>2016-08-09</date><!--Update only when changing/reviewing this man page-->
++<releaseinfo>16.08</releaseinfo><!--Update only when changing/reviewing this man page-->
<productname>KDE Applications</productname>
</refentryinfo>
@@@ -50,7 -50,7 +50,7 @@@ file</replaceable></group
<group choice="opt"><option>-d</option></group>
<group choice="opt"><option>-o</option> <replaceable>
directory</replaceable></group>
--<arg choice="opt">&kde; Generic Options</arg>
++<arg choice="opt">&kf5; Generic Options</arg>
<arg choice="opt">&Qt; Generic Options</arg>
</cmdsynopsis>
</refsynopsisdiv>
@@@ -175,6 -175,6 +175,19 @@@ a subfolder by the name of the archive
</refsect1>
<refsect1>
++<title>See Also</title>
++<simplelist>
++<member>More detailed user documentation is available from <ulink
++url="help:/ark">help:/ark</ulink>
++(either enter this &URL; into &konqueror;, or run
++<userinput><command>khelpcenter</command>
++<parameter>help:/ark</parameter></userinput>).</member>
++<member>kf5options(7)</member>
++<member>qt5options(7)</member>
++</simplelist>
++</refsect1>
++
++<refsect1>
<title>Examples</title>
<variablelist>
diff --cc kerfuffle/addtoarchive.cpp
index 3df09af,cd61f82..c8f8d50
--- a/kerfuffle/addtoarchive.cpp
+++ b/kerfuffle/addtoarchive.cpp
@@@ -113,12 -113,10 +113,12 @@@ bool AddToArchive::showAddDialog(
bool AddToArchive::addInput(const QUrl &url)
{
- m_inputs << url.toLocalFile();
+ Archive::Entry *entry = new Archive::Entry();
- entry->setFullPath(url.toDisplayString(QUrl::PreferLocalFile));
++ entry->setFullPath(url.toLocalFile());
+ m_entries << entry;
if (m_firstPath.isEmpty()) {
- QString firstEntry = url.toDisplayString(QUrl::PreferLocalFile);
+ QString firstEntry = url.toLocalFile();
m_firstPath = QFileInfo(firstEntry).dir().absolutePath();
}
diff --cc kerfuffle/archive_kerfuffle.cpp
index 8e6cd59,86cf419..41db04b
--- a/kerfuffle/archive_kerfuffle.cpp
+++ b/kerfuffle/archive_kerfuffle.cpp
@@@ -33,7 -32,13 +33,8 @@@
#include "mimetypes.h"
#include "pluginmanager.h"
-#include <QByteArray>
-#include <QDebug>
#include <QEventLoop>
-#include <QFile>
-#include <QFileInfo>
-#include <QMimeDatabase>
+ #include <QRegularExpression>
#include <KPluginFactory>
#include <KPluginLoader>
@@@ -256,11 -322,9 +307,9 @@@ QString Archive::subfolderName(
return m_subfolderName;
}
-void Archive::onNewEntry(const ArchiveEntry &entry)
+void Archive::onNewEntry(const Archive::Entry *entry)
{
- if (!entry->isDir()) {
- m_numberOfFiles++;
- }
- entry[IsDirectory].toBool() ? m_numberOfFolders++ : m_numberOfFiles++;
++ entry->isDir() ? m_numberOfFolders++ : m_numberOfFiles++;
}
bool Archive::isValid() const
@@@ -307,7 -375,7 +360,7 @@@ DeleteJob* Archive::deleteFiles(QList<A
return Q_NULLPTR;
}
- qCDebug(ARK) << "Going to delete entries" << entries;
- qCDebug(ARK) << "Going to delete" << files.size() << "files";
++ qCDebug(ARK) << "Going to delete" << entries.size() << "entries";
if (m_iface->isReadOnly()) {
return 0;
diff --cc kerfuffle/cliinterface.cpp
index 43fc155,b2cc09b..4793a54
--- a/kerfuffle/cliinterface.cpp
+++ b/kerfuffle/cliinterface.cpp
@@@ -231,50 -183,23 +232,54 @@@ bool CliInterface::addFiles(const QList
}
int compLevel = options.value(QStringLiteral("CompressionLevel"), -1).toInt();
+ ulong volumeSize = options.value(QStringLiteral("VolumeSize"), 0).toULongLong();
const auto args = substituteAddVariables(m_param.value(AddArgs).toStringList(),
- files,
+ filesToPass,
password(),
isHeaderEncryptionEnabled(),
- compLevel);
+ compLevel,
+ volumeSize);
- if (!runProcess(m_param.value(AddProgram).toStringList(), args)) {
- return false;
- }
+ return runProcess(m_param.value(AddProgram).toStringList(), args);
+}
- return true;
+bool CliInterface::moveFiles(const QList<Archive::Entry*> &files, Archive::Entry *destination, const CompressionOptions &options)
+{
++ Q_UNUSED(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<QVariant> & files)
+bool CliInterface::deleteFiles(const QList<Archive::Entry*> &files)
{
cacheParameterList();
m_operationMode = Delete;
@@@ -296,9 -225,13 +301,9 @@@ bool CliInterface::testArchive(
cacheParameterList();
m_operationMode = Test;
- const auto args = substituteTestVariables(m_param.value(TestArgs).toStringList());
+ const auto args = substituteTestVariables(m_param.value(TestArgs).toStringList(), password());
- if (!runProcess(m_param.value(TestProgram).toStringList(), args)) {
- return false;
- }
-
- return true;
+ return runProcess(m_param.value(TestProgram).toStringList(), args);
}
bool CliInterface::runProcess(const QStringList& programNames, const QStringList& arguments)
@@@ -333,17 -265,15 +337,15 @@@
m_process->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Unbuffered | QIODevice::Text);
m_process->setProgram(programPath, arguments);
- connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(readStdout()), Qt::DirectConnection);
+ connect(m_process, &QProcess::readyReadStandardOutput, this, [=]() {
+ readStdout();
+ });
- 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::extractProcessFinished,
- Qt::DirectConnection);
- connect(m_process, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &CliInterface::copyProcessFinished);
++ connect(m_process, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &CliInterface::extractProcessFinished);
} else {
- connect(m_process, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &CliInterface::processFinished, Qt::DirectConnection);
+ connect(m_process, static_cast<void (KPtyProcess::*)(int, QProcess::ExitStatus)>(&KPtyProcess::finished), this, &CliInterface::processFinished);
}
m_stdOutData.clear();
@@@ -372,22 -302,13 +374,22 @@@ void CliInterface::processFinished(int
return;
}
- if (m_operationMode == Delete) {
- foreach(const QVariant& v, m_removedFiles) {
- emit entryRemoved(v.toString());
+ 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_operationMode == Add && !isMultiVolume()) {
+ if (m_extractTempDir) {
+ delete m_extractTempDir;
+ m_extractTempDir = Q_NULLPTR;
+ }
list();
} else if (m_operationMode == List && isCorrupt()) {
Kerfuffle::LoadCorruptQuery query(filename());
@@@ -752,7 -640,7 +754,7 @@@ QStringList CliInterface::substituteExt
return args;
}
- QStringList CliInterface::substituteAddVariables(const QStringList &addArgs, const QList<Archive::Entry*> &entries, const QString &password, bool encryptHeader, int compLevel)
-QStringList CliInterface::substituteAddVariables(const QStringList &addArgs, const QStringList &files, const QString &password, bool encryptHeader, int compLevel, ulong volumeSize)
++QStringList CliInterface::substituteAddVariables(const QStringList &addArgs, const QList<Archive::Entry*> &entries, const QString &password, bool encryptHeader, int compLevel, ulong volumeSize)
{
// Required if we call this function from unit tests.
cacheParameterList();
@@@ -776,8 -664,13 +778,13 @@@
continue;
}
+ if (arg == QLatin1String("$MultiVolumeSwitch")) {
+ args << multiVolumeSwitch(volumeSize);
+ continue;
+ }
+
if (arg == QLatin1String("$Files")) {
- args << files;
+ args << entryFullPaths(entries, true);
continue;
}
@@@ -1025,11 -842,29 +1037,29 @@@ QString CliInterface::compressionLevelS
return compLevelSwitch;
}
+ QString CliInterface::multiVolumeSwitch(ulong volumeSize) const
+ {
+ // The maximum value we allow in the QDoubleSpinBox is 1000MB. Converted to
+ // KB this is 1024000.
+ if (volumeSize <= 0 || volumeSize > 1024000) {
+ return QString();
+ }
+
+ Q_ASSERT(m_param.contains(MultiVolumeSwitch));
+
+ QString multiVolumeSwitch = m_param.value(MultiVolumeSwitch).toString();
+ Q_ASSERT(!multiVolumeSwitch.isEmpty());
+
+ multiVolumeSwitch.replace(QLatin1String("$VolumeSize"), QString::number(volumeSize));
+
+ return multiVolumeSwitch;
+ }
+
-QStringList CliInterface::copyFilesList(const QVariantList& files) const
+QStringList CliInterface::extractFilesList(const QList<Archive::Entry*> &entries) const
{
QStringList filesList;
- foreach (const QVariant& f, files) {
- filesList << escapeFileName(f.value<fileRootNodePair>().file);
+ foreach (const Archive::Entry *e, entries) {
+ filesList << escapeFileName(e->fullPath(true));
}
return filesList;
@@@ -1161,21 -988,7 +1194,21 @@@ void CliInterface::readStdout(bool hand
}
}
+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)
+ bool 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.
diff --cc kerfuffle/cliinterface.h
index 072a278,af33005..c4ad91e
--- a/kerfuffle/cliinterface.h
+++ b/kerfuffle/cliinterface.h
@@@ -332,19 -311,13 +334,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 QVariantList &files, bool preservePaths, const QString &password);
- QStringList substituteAddVariables(const QStringList &addArgs, const QStringList &files, const QString &password, bool encryptHeader, int compLevel, ulong volumeSize);
- QStringList substituteDeleteVariables(const QStringList &deleteArgs, const QVariantList &files, 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 substituteAddVariables(const QStringList &addArgs, const QList<Archive::Entry*> &entries, const QString &password, bool encryptHeader, int compLevel, ulong volumeSize);
+ 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);
+ QStringList substituteTestVariables(const QStringList &testArgs, const QString &password);
/**
+ * @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;
@@@ -367,12 -342,19 +371,36 @@@
/**
* @return The list of selected files to extract.
*/
- QStringList copyFilesList(const QVariantList& files) const;
+ QStringList extractFilesList(const QList<Archive::Entry*> &files) const;
+ QString multiVolumeName() const Q_DECL_OVERRIDE;
+
protected:
+ bool setAddedFiles();
- virtual void handleLine(const QString& line);
++
+ /**
+ * Handles the given @p line.
+ * @return True if the line is ok. False if the line contains/triggers a "fatal" error
+ * or a canceled user query. If false is returned, the caller is supposed to call killProcess().
+ */
+ virtual bool handleLine(const QString& line);
+
++ bool checkForErrorMessage(const QString& line, int parameterIndex);
++
++ /**
++ * Checks whether a line of the program's output is a password prompt.
++ *
++ * It uses the regular expression in the @c PasswordPromptPattern parameter
++ * for the check.
++ *
++ * @param line A line of the program's output.
++ *
++ * @return @c true if the given @p line is a password prompt, @c false
++ * otherwise.
++ */
++ bool checkForPasswordPromptMessage(const QString& line);
++
virtual void cacheParameterList();
/**
@@@ -397,19 -379,33 +425,27 @@@
*/
bool passwordQuery();
- /**
- * Checks whether a line of the program's output is a password prompt.
- *
- * It uses the regular expression in the @c PasswordPromptPattern parameter
- * for the check.
- *
- * @param line A line of the program's output.
- *
- * @return @c true if the given @p line is a password prompt, @c false
- * otherwise.
- */
-
- bool checkForPasswordPromptMessage(const QString& line);
- bool checkForErrorMessage(const QString& line, int parameterIndex);
+ 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;
+ ParameterList m_param;
+
+ #ifdef Q_OS_WIN
+ KProcess *m_process;
+ #else
+ KPtyProcess *m_process;
+ #endif
+
+ bool m_abortingOperation;
+
-
protected slots:
virtual void readStdout(bool handleAll = false);
@@@ -474,16 -443,9 +495,10 @@@ private
QRegularExpression m_passwordPromptPattern;
QHash<int, QList<QRegularExpression> > m_patternCache;
- #ifdef Q_OS_WIN
- KProcess *m_process;
- #else
- KPtyProcess *m_process;
- #endif
-
- QVariantList m_removedFiles;
+ QList<Archive::Entry*> m_removedFiles;
+ QList<Archive::Entry*> m_newMovedFiles;
+ int m_exitCode;
bool m_listEmptyLines;
- bool m_abortingOperation;
QString m_storedFileName;
CompressionOptions m_compressionOptions;
diff --cc kerfuffle/jobs.cpp
index bb815b1,bedc3bf..8b9aa02
--- a/kerfuffle/jobs.cpp
+++ b/kerfuffle/jobs.cpp
@@@ -31,6 -29,6 +31,7 @@@
#include "ark_debug.h"
#include <QDir>
++#include <QDirIterator>
#include <QFileInfo>
#include <QRegularExpression>
#include <QThread>
@@@ -305,8 -300,7 +306,8 @@@ void ExtractJob::doWork(
connectToArchiveInterfaceSignals();
- qCDebug(ARK) << "Starting extraction with selected files:"
- qCDebug(ARK) << "Starting extraction with" << m_files.count() << "selected files."
++ qCDebug(ARK) << "Starting extraction with" << m_entries.count() << "selected files."
+ << m_entries
<< "Destination dir:" << m_destinationDir
<< "Options:" << m_options;
@@@ -341,17 -335,17 +342,17 @@@ ExtractionOptions ExtractJob::extractio
return m_options;
}
-TempExtractJob::TempExtractJob(const QString &file, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
+TempExtractJob::TempExtractJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
: Job(interface)
- , m_file(file)
+ , m_entry(entry)
, m_passwordProtectedHint(passwordProtectedHint)
{
+ m_tmpExtractDir = new QTemporaryDir();
}
-
QString TempExtractJob::validatedFilePath() const
{
- QString path = extractionDir() + QLatin1Char('/') + m_file;
+ 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
@@@ -388,37 -387,25 +394,25 @@@ void TempExtractJob::doWork(
}
}
- PreviewJob::PreviewJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
- : TempExtractJob(entry, passwordProtectedHint, interface)
+ QString TempExtractJob::extractionDir() const
{
- qCDebug(ARK) << "PreviewJob started";
+ return m_tmpExtractDir->path();
}
- QString PreviewJob::extractionDir() const
-PreviewJob::PreviewJob(const QString& file, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
- : TempExtractJob(file, passwordProtectedHint, interface)
++PreviewJob::PreviewJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
++ : TempExtractJob(entry, passwordProtectedHint, interface)
{
- return m_tmpExtractDir.path();
+ qCDebug(ARK) << "PreviewJob started";
}
-OpenJob::OpenJob(const QString& file, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
- : TempExtractJob(file, passwordProtectedHint, interface)
+OpenJob::OpenJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
+ : TempExtractJob(entry, passwordProtectedHint, interface)
{
qCDebug(ARK) << "OpenJob started";
-
- m_tmpExtractDir = new QTemporaryDir();
- }
-
- QTemporaryDir *OpenJob::tempDir() const
- {
- return m_tmpExtractDir;
- }
-
- QString OpenJob::extractionDir() const
- {
- return m_tmpExtractDir->path();
}
-OpenWithJob::OpenWithJob(const QString& file, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
- : OpenJob(file, passwordProtectedHint, interface)
+OpenWithJob::OpenWithJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface)
+ : OpenJob(entry, passwordProtectedHint, interface)
{
qCDebug(ARK) << "OpenWithJob started";
}
@@@ -434,15 -420,15 +428,7 @@@ AddJob::AddJob(const QList<Archive::Ent
void AddJob::doWork()
{
- qCDebug(ARK) << "AddJob: going to add" << m_entries.count() << "file(s)";
- qCDebug(ARK) << "AddJob: going to add" << m_files.count() << "file(s)";
--
- emit description(this, i18np("Adding a file", "Adding %1 files", m_entries.count()));
- emit description(this, i18np("Adding a file", "Adding %1 files", m_files.count()));
--
-- ReadWriteArchiveInterface *m_writeInterface =
-- qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());
--
-- Q_ASSERT(m_writeInterface);
--
++ // Set current dir.
const QString globalWorkDir = m_options.value(QStringLiteral("GlobalWorkDir")).toString();
const QDir workDir = globalWorkDir.isEmpty() ? QDir::current() : QDir(globalWorkDir);
if (!globalWorkDir.isEmpty()) {
@@@ -451,14 -437,14 +437,41 @@@
QDir::setCurrent(globalWorkDir);
}
++ // Count total number of entries to be added.
++ qulonglong totalCount = 0;
++ QElapsedTimer timer;
++ timer.start();
++ foreach (const Archive::Entry* entry, m_entries) {
++ totalCount++;
++ if (QFileInfo(entry->fullPath()).isDir()) {
++ QDirIterator it(entry->fullPath(), QDir::AllEntries | QDir::Readable | QDir::Hidden |
++ QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
++ while (it.hasNext()) {
++ it.next();
++ totalCount++;
++ }
++ }
++ }
++ qCDebug(ARK) << "Counted" << totalCount << "entries in" << timer.elapsed() << "ms";
++
++ m_options[QStringLiteral("NumberOfEntries")] = totalCount;
++
++ qCDebug(ARK) << "AddJob: going to add" << totalCount << "entries";
++ emit description(this, i18np("Adding a file", "Adding %1 files", totalCount));
++
++ ReadWriteArchiveInterface *m_writeInterface =
++ qobject_cast<ReadWriteArchiveInterface*>(archiveInterface());
++
++ Q_ASSERT(m_writeInterface);
++
// The file paths must be relative to GlobalWorkDir.
- QStringList relativeFiles;
- foreach (const QString& file, m_files) {
+ foreach (Archive::Entry *entry, m_entries) {
// #191821: workDir must be used instead of QDir::current()
// so that symlinks aren't resolved automatically
- QString relativePath = workDir.relativeFilePath(file);
+ const QString &fullPath = entry->fullPath();
+ QString relativePath = workDir.relativeFilePath(fullPath);
- if (file.endsWith(QLatin1Char('/'))) {
+ if (fullPath.endsWith(QLatin1Char('/'))) {
relativePath += QLatin1Char('/');
}
diff --cc kerfuffle/jobs.h
index 50f3c28,0fdd7be..196b0bf
--- a/kerfuffle/jobs.h
+++ b/kerfuffle/jobs.h
@@@ -166,9 -169,10 +172,10 @@@ public slots
virtual void doWork() Q_DECL_OVERRIDE;
private:
- virtual QString extractionDir() const = 0;
+ QString extractionDir() const;
- QString m_file;
+ Archive::Entry *m_entry;
+ QTemporaryDir *m_tmpExtractDir;
bool m_passwordProtectedHint;
};
@@@ -181,12 -185,7 +188,7 @@@ class KERFUFFLE_EXPORT PreviewJob : pub
Q_OBJECT
public:
- PreviewJob(const QString& file, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface);
+ PreviewJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface);
-
- private:
- QString extractionDir() const Q_DECL_OVERRIDE;
-
- QTemporaryDir m_tmpExtractDir;
};
/**
@@@ -198,18 -197,7 +200,7 @@@ class KERFUFFLE_EXPORT OpenJob : publi
Q_OBJECT
public:
- OpenJob(const QString& file, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface);
+ OpenJob(Archive::Entry *entry, bool passwordProtectedHint, ReadOnlyArchiveInterface *interface);
-
- /**
- * @return The temporary dir used for the extraction.
- * It is safe to delete this pointer in order to remove the directory.
- */
- QTemporaryDir *tempDir() const;
-
- private:
- QString extractionDir() const Q_DECL_OVERRIDE;
-
- QTemporaryDir *m_tmpExtractDir;
};
class KERFUFFLE_EXPORT OpenWithJob : public OpenJob
diff --cc kerfuffle/mime/kerfuffle.xml
index 00b1101,c7b2824..c02d807
--- a/kerfuffle/mime/kerfuffle.xml
+++ b/kerfuffle/mime/kerfuffle.xml
@@@ -15,7 -48,51 +48,56 @@@
</mime-type>
<mime-type type="application/x-lz4-compressed-tar">
<comment>Tar archive (LZ4-compressed)</comment>
- <comment xml:lang="en">Tar archive (LZ4-compressed)</comment>
+ <comment xml:lang="ca">Arxiu tar (comprimit amb LZ4)</comment>
+ <comment xml:lang="ca at valencia">Arxiu tar (comprimit amb LZ4)</comment>
+ <comment xml:lang="cs">Archiv Tar (komprimovaný LZ4)</comment>
+ <comment xml:lang="de">Tar-Archiv (LZ4-komprimiert)</comment>
+ <comment xml:lang="en_GB">Tar archive (LZ4-compressed)</comment>
+ <comment xml:lang="es">Archivo comprimido Tar (comprimido con LZ4)</comment>
+ <comment xml:lang="nl">Tar-archief (lz4-gecomprimeerd)</comment>
+ <comment xml:lang="pl">Archiwum Tar (kompresja LZ4)</comment>
+ <comment xml:lang="pt">Pacote TAR (comprimido com LZ4)</comment>
+ <comment xml:lang="sk">Tar archív (LZ4 komprimovaný)</comment>
+ <comment xml:lang="sl">Arhiv tar (stisnjen z LZ4)</comment>
+ <comment xml:lang="sr">тар архива (ЛЗ4‑компресована)</comment>
+ <comment xml:lang="sr at ijekavian">тар архива (ЛЗ4‑компресована)</comment>
+ <comment xml:lang="sr at ijekavianlatin">tar arhiva (LZ4-kompresovana)</comment>
+ <comment xml:lang="sr at latin">tar arhiva (LZ4-kompresovana)</comment>
+ <comment xml:lang="sv">Tar-arkiv (LZ4-komprimerat)</comment>
+ <comment xml:lang="uk">архів Tar (стиснутий LZ4)</comment>
<glob pattern="*.tar.lz4"/>
</mime-type>
+ <mime-type type="application/x-iso9660-appimage">
+ <comment>AppImage application bundle</comment>
+ <comment xml:lang="ca">Paquet d'aplicació «AppImage»</comment>
+ <comment xml:lang="ca at valencia">Paquet d'aplicació «AppImage»</comment>
++ <comment xml:lang="de">AppImage-Anwendungspaket</comment>
+ <comment xml:lang="en_GB">AppImage application bundle</comment>
+ <comment xml:lang="es">Paquete de aplicación AppImage</comment>
+ <comment xml:lang="nl">Toepassingsbundel AppImage</comment>
+ <comment xml:lang="pl">Pęk aplikacji AppImage</comment>
+ <comment xml:lang="pt">Pacote de aplicação AppImage</comment>
+ <comment xml:lang="sk">Balík aplikácie AppImage</comment>
+ <comment xml:lang="sl">Zbirka programov AppImage</comment>
++ <comment xml:lang="sr">ап‑имејџ пакет програма</comment>
++ <comment xml:lang="sr at ijekavian">ап‑имејџ пакет програма</comment>
++ <comment xml:lang="sr at ijekavianlatin">AppImage paket programa</comment>
++ <comment xml:lang="sr at latin">AppImage paket programa</comment>
+ <comment xml:lang="sv">AppImage programpacke</comment>
+ <comment xml:lang="uk">пакунок програми AppImage</comment>
+ <sub-class-of type="application/x-executable"/>
+ <sub-class-of type="application/x-cd-image"/>
+ <generic-icon name="application-x-executable"/>
+ <magic priority="50">
+ <match value="ELF" type="string" offset="1" >
+ <match value="0x41" type="byte" offset="8">
+ <match value="0x49" type="byte" offset="9">
+ <match value="0x01" type="byte" offset="10"/>
+ </match>
+ </match>
+ </match>
+ </magic>
+ <glob pattern="*.AppImage"/>
+ </mime-type>
</mime-info>
+
diff --cc part/archivemodel.cpp
index 85e5389,3587a77..4bdc6d0
--- a/part/archivemodel.cpp
+++ b/part/archivemodel.cpp
@@@ -28,8 -29,13 +28,9 @@@
#include <KLocalizedString>
#include <kio/global.h>
-#include <QDateTime>
#include <QDBusConnection>
+ #include <QElapsedTimer>
#include <QMimeData>
-#include <QMimeDatabase>
-#include <QPersistentModelIndex>
#include <QRegularExpression>
#include <QUrl>
@@@ -147,12 -272,21 +148,14 @@@ private
Qt::SortOrder m_sortOrder;
};
-int ArchiveNode::row() const
-{
- if (parent()) {
- return parent()->entries().indexOf(const_cast<ArchiveNode*>(this));
- }
- return 0;
-}
-
ArchiveModel::ArchiveModel(const QString &dbusPathName, QObject *parent)
: QAbstractItemModel(parent)
- , m_rootNode(new ArchiveDirNode(0, ArchiveEntry()))
+ , m_rootEntry()
, m_dbusPathName(dbusPathName)
+ , m_numberOfFiles(0)
+ , m_numberOfFolders(0)
{
+ m_rootEntry.setProperty("isDirectory", true);
}
ArchiveModel::~ArchiveModel()
@@@ -672,16 -843,20 +683,20 @@@ void ArchiveModel::newEntry(Archive::En
void ArchiveModel::slotLoadingFinished(KJob *job)
{
- int i = 0;
- foreach(Archive::Entry *entry, m_newArchiveEntries) {
- newEntry(entry, DoNotNotifyViews);
- i++;
- }
- beginResetModel();
- endResetModel();
- m_newArchiveEntries.clear();
+ if (!job->error()) {
+ QElapsedTimer timer;
+ timer.start();
+ int i = 0;
- foreach(const ArchiveEntry &entry, m_newArchiveEntries) {
++ foreach(Archive::Entry *entry, m_newArchiveEntries) {
+ newEntry(entry, DoNotNotifyViews);
+ i++;
+ }
+ beginResetModel();
+ endResetModel();
+ m_newArchiveEntries.clear();
- qCDebug(ARK) << "Added" << i << "entries to model";
+ qCDebug(ARK) << "Added" << i << "entries to model in" << timer.elapsed() << "ms";
+ }
emit loadingFinished(job);
}
@@@ -985,3 -1023,52 +1000,52 @@@ void ArchiveModel::slotCleanupEmptyDirs
endRemoveRows();
}
}
+
+ void ArchiveModel::countEntriesAndSize() {
+
+ // This function is used to count the number of folders/files and
+ // the total compressed size. This is needed for PropertiesDialog
+ // to update the corresponding values after adding/deleting files.
+
+ // When ArchiveModel has been properly fixed, this code can likely
+ // be removed.
+
+ m_numberOfFiles = 0;
+ m_numberOfFolders = 0;
+ m_uncompressedSize = 0;
+
+ QElapsedTimer timer;
+ timer.start();
+
- traverseAndCountDirNode(m_rootNode);
++ traverseAndCountDirNode(&m_rootEntry);
+
+ qCDebug(ARK) << "Time to count entries and size:" << timer.elapsed() << "ms";
+ }
+
-void ArchiveModel::traverseAndCountDirNode(ArchiveDirNode *dir)
++void ArchiveModel::traverseAndCountDirNode(Archive::Entry *dir)
+ {
- foreach(ArchiveNode *node, dir->entries()) {
- if (node->isDir()) {
- traverseAndCountDirNode(dynamic_cast<ArchiveDirNode*>(node));
++ foreach(Archive::Entry *entry, dir->entries()) {
++ if (entry->isDir()) {
++ traverseAndCountDirNode(entry);
+ m_numberOfFolders++;
+ } else {
+ m_numberOfFiles++;
- m_uncompressedSize += node->entry()[Size].toULongLong();
++ m_uncompressedSize += entry->property("size").toULongLong();
+ }
+ }
+ }
+
+ qulonglong ArchiveModel::numberOfFiles() const
+ {
+ return m_numberOfFiles;
+ }
+
+ qulonglong ArchiveModel::numberOfFolders() const
+ {
+ return m_numberOfFolders;
+ }
+
+ qulonglong ArchiveModel::uncompressedSize() const
+ {
+ return m_uncompressedSize;
+ }
diff --cc part/archivemodel.h
index fcca1ca,5baffaf..0290d8f
--- a/part/archivemodel.h
+++ b/part/archivemodel.h
@@@ -26,10 -25,11 +26,11 @@@
#include <QAbstractItemModel>
#include <QScopedPointer>
+ #include <KMessageWidget>
#include <kjobtrackerinterface.h>
-#include "kerfuffle/archive_kerfuffle.h"
+#include "kerfuffle/archiveentry.h"
-using Kerfuffle::ArchiveEntry;
+using Kerfuffle::Archive;
namespace Kerfuffle
{
@@@ -85,41 -86,22 +86,47 @@@ public
*/
void encryptArchive(const QString &password, bool encryptHeader);
+ void countEntriesAndSize();
+ qulonglong numberOfFiles() const;
+ qulonglong numberOfFolders() const;
+ qulonglong uncompressedSize() const;
+
+ /**
+ * 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&);
+ void messageWidget(KMessageWidget::MessageType type, const QString& msg);
private slots:
- void slotNewEntryFromSetArchive(const ArchiveEntry& entry);
- void slotNewEntry(const ArchiveEntry& entry);
+ void slotNewEntryFromSetArchive(Archive::Entry *entry);
+ void slotNewEntry(Archive::Entry *entry);
void slotLoadingFinished(KJob *job);
void slotEntryRemoved(const QString & path);
void slotUserQuery(Kerfuffle::Query *query);
@@@ -146,16 -128,21 +153,22 @@@ private
* of the change.
*/
enum InsertBehaviour { NotifyViews, DoNotNotifyViews };
- void insertNode(ArchiveNode *node, InsertBehaviour behaviour = NotifyViews);
- void newEntry(const Kerfuffle::ArchiveEntry& entry, InsertBehaviour behaviour);
+ void insertEntry(Archive::Entry *entry, InsertBehaviour behaviour = NotifyViews);
+ void newEntry(Kerfuffle::Archive::Entry *receivedEntry, InsertBehaviour behaviour);
- void traverseAndCountDirNode(ArchiveDirNode *dir);
++ void traverseAndCountDirNode(Archive::Entry *dir);
+
- QList<Kerfuffle::ArchiveEntry> m_newArchiveEntries; // holds entries from opening a new archive until it's totally open
+ QList<Kerfuffle::Archive::Entry*> m_newArchiveEntries; // holds entries from opening a new archive until it's totally open
QList<int> m_showColumns;
QScopedPointer<Kerfuffle::Archive> m_archive;
- ArchiveDirNode *m_rootNode;
+ Archive::Entry m_rootEntry;
+ QHash<QString, QIcon> m_entryIcons;
QString m_dbusPathName;
+
+ qulonglong m_numberOfFiles;
+ qulonglong m_numberOfFolders;
+ qulonglong m_uncompressedSize;
};
#endif // ARCHIVEMODEL_H
diff --cc part/part.cpp
index b6a9f59,3a71679..24ccb9e
--- a/part/part.cpp
+++ b/part/part.cpp
@@@ -173,9 -168,11 +173,11 @@@ Part::Part(QWidget *parentWidget, QObje
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);
+ connect(m_model, &ArchiveModel::messageWidget,
+ this, &Part::displayMsgWidget);
connect(this, &Part::busy,
this, &Part::setBusyGui);
@@@ -368,16 -367,11 +370,17 @@@ void Part::setupActions(
m_addFilesAction = actionCollection()->addAction(QStringLiteral("add"));
m_addFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-insert")));
- m_addFilesAction->setText(i18n("Add &Files..."));
+ m_addFilesAction->setText(i18n("Add &Files to..."));
m_addFilesAction->setToolTip(i18nc("@info:tooltip", "Click to add files to the archive"));
+ actionCollection()->setDefaultShortcut(m_addFilesAction, Qt::ALT + Qt::Key_A);
- connect(m_addFilesAction, &QAction::triggered,
- this, static_cast<void (Part::*)()>(&Part::slotAddFiles));
+ connect(m_addFilesAction, &QAction::triggered, this, static_cast<void (Part::*)()>(&Part::slotAddFiles));
+
+ 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")));
@@@ -438,9 -413,28 +441,28 @@@
void Part::updateActions()
{
bool isWritable = m_model->archive() && !m_model->archive()->isReadOnly();
- bool isDirectory = m_model->entryForIndex(m_view->selectionModel()->currentIndex())[IsDirectory].toBool();
+ const Archive::Entry *entry = m_model->entryForIndex(m_view->selectionModel()->currentIndex());
int selectedEntriesCount = m_view->selectionModel()->selectedRows().count();
+ // We disable adding files if the archive is encrypted but the password is
+ // unknown (this happens when opening existing non-he password-protected
+ // archives). If we added files they would not get encrypted resulting in an
+ // archive with a mixture of encrypted and unencrypted files.
+ const bool isEncryptedButUnknownPassword = m_model->archive() &&
+ m_model->archive()->encryptionType() != Archive::Unencrypted &&
+ m_model->archive()->password().isEmpty();
+
+ if (isEncryptedButUnknownPassword) {
+ m_addFilesAction->setToolTip(xi18nc("@info:tooltip",
+ "Adding files to existing password-protected archives with no header-encryption is currently not supported."
+ "<nl/><nl/>Extract the files and create a new archive if you want to add files."));
+ m_testArchiveAction->setToolTip(xi18nc("@info:tooltip",
+ "Testing password-protected archives with no header-encryption is currently not supported."));
+ } else {
+ m_addFilesAction->setToolTip(i18nc("@info:tooltip", "Click to add files to the archive"));
+ m_testArchiveAction->setToolTip(i18nc("@info:tooltip", "Click to test the archive for integrity"));
+ }
+
// Figure out if entry size is larger than preview size limit.
const int maxPreviewSize = ArkSettings::previewFileSizeLimit() * 1024 * 1024;
const bool limit = ArkSettings::limitPreviewFileSize();
@@@ -1527,20 -1293,18 +1559,31 @@@ void Part::slotAddFilesDone(KJob* job
} else {
// Hide the "archive will be created as soon as you add a file" message.
m_messageWidget->hide();
+
+ // For multi-volume archive, we need to re-open the archive after adding files
+ // because the name changes from e.g name.rar to name.part1.rar.
+ if (m_model->archive()->isMultiVolume()) {
+ qCDebug(ARK) << "Multi-volume archive detected, re-opening...";
+ KParts::OpenUrlArguments args = arguments();
+ args.metaData()[QStringLiteral("createNewArchive")] = QStringLiteral("false");
+ setArguments(args);
+
+ openUrl(QUrl::fromLocalFile(m_model->archive()->multiVolumeName()));
+ }
}
+ 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)
@@@ -1653,11 -1418,11 +1700,11 @@@ void Part::slotShowContextMenu(
void Part::displayMsgWidget(KMessageWidget::MessageType type, const QString& msg)
{
-- // The widget could be already visible, so hide it.
-- m_messageWidget->hide();
-- m_messageWidget->setText(msg);
-- m_messageWidget->setMessageType(type);
-- m_messageWidget->animatedShow();
++ KMessageWidget *msgWidget = new KMessageWidget();
++ msgWidget->setText(msg);
++ msgWidget->setMessageType(type);
++ m_vlayout->insertWidget(0, msgWidget);
++ msgWidget->animatedShow();
}
} // namespace Ark
diff --cc part/part.h
index ce62810,00b5a54..9fa850a
--- a/part/part.h
+++ b/part/part.h
@@@ -168,14 -139,13 +169,13 @@@ private
void setupActions();
bool isSingleFolderArchive() const;
QString detectSubfolder() const;
- QList<QVariant> filesForIndexes(const QModelIndexList& list) const;
- QList<QVariant> filesAndRootNodesForIndexes(const QModelIndexList& list) const;
+ QList<Kerfuffle::Archive::Entry*> filesForIndexes(const QModelIndexList& list) const;
+ QList<Kerfuffle::Archive::Entry*> filesAndRootNodesForIndexes(const QModelIndexList& list) const;
QModelIndexList addChildren(const QModelIndexList &list) const;
void registerJob(KJob *job);
- 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;
@@@ -194,14 -160,10 +194,14 @@@
KToggleAction *m_showInfoPanelAction;
InfoPanel *m_infoPanel;
QSplitter *m_splitter;
- QList<QTemporaryDir*> m_tmpOpenDirList;
+ QList<QTemporaryDir*> m_tmpExtractDirList;
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;
diff --cc plugins/cli7zplugin/cliplugin.cpp
index 51837af,4f30e33..ae385ac
--- a/plugins/cli7zplugin/cliplugin.cpp
+++ b/plugins/cli7zplugin/cliplugin.cpp
@@@ -82,11 -80,8 +83,12 @@@ ParameterList CliPlugin::parameterList(
<< QStringLiteral("$Archive")
<< QStringLiteral("$PasswordSwitch")
<< QStringLiteral("$CompressionLevelSwitch")
+ << QStringLiteral("$MultiVolumeSwitch")
<< QStringLiteral("$Files");
+ p[MoveArgs] = QStringList() << QStringLiteral("rn")
+ << QStringLiteral("$PasswordSwitch")
+ << QStringLiteral("$Archive")
+ << QStringLiteral("$PathPairs");
p[DeleteArgs] = QStringList() << QStringLiteral("d")
<< QStringLiteral("$PasswordSwitch")
<< QStringLiteral("$Archive")
diff --cc plugins/clirarplugin/cliplugin.cpp
index d701a8b,f345211..5e132ca
--- a/plugins/clirarplugin/cliplugin.cpp
+++ b/plugins/clirarplugin/cliplugin.cpp
@@@ -58,8 -57,21 +57,9 @@@ void CliPlugin::resetParsing(
m_parseState = ParseStateTitle;
m_remainingIgnoreLines = 1;
m_comment.clear();
+ m_numberOfVolumes = 0;
}
-// #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;
@@@ -101,11 -114,8 +102,12 @@@
<< QStringLiteral( "$Archive" )
<< QStringLiteral("$PasswordSwitch")
<< QStringLiteral("$CompressionLevelSwitch")
+ << QStringLiteral("$MultiVolumeSwitch")
<< 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 --cc plugins/cliunarchiverplugin/cliplugin.cpp
index 3fe0c31,cdda7e8..a973419
--- a/plugins/cliunarchiverplugin/cliplugin.cpp
+++ b/plugins/cliunarchiverplugin/cliplugin.cpp
@@@ -21,6 -21,9 +21,7 @@@
*/
#include "cliplugin.h"
-#include "ark_debug.h"
-#include "kerfuffle_export.h"
+ #include "queries.h"
#include <QJsonArray>
#include <QJsonParseError>
@@@ -49,34 -53,10 +51,10 @@@ bool CliPlugin::list(
m_operationMode = List;
const auto args = substituteListVariables(m_param.value(ListArgs).toStringList(), password());
-
- if (!runProcess(m_param.value(ListProgram).toStringList(), args)) {
- return false;
- }
-
- if (!password().isEmpty()) {
-
- // lsar -json exits with error code 1 if the archive is header-encrypted and the password is wrong.
- if (m_exitCode == 1) {
- qCWarning(ARK) << "Wrong password, list() aborted";
- emit error(i18n("Wrong password."));
- emit finished(false);
- killProcess();
- setPassword(QString());
- return false;
- }
-
- // lsar -json exits with error code 2 if the archive is header-encrypted and no password is given as argument.
- // At this point we have already asked a password to the user, so we can just list() again.
- if (m_exitCode == 2) {
- return CliPlugin::list();
- }
- }
-
- return true;
+ return runProcess(m_param.value(ListProgram).toStringList(), args);
}
-bool CliPlugin::copyFiles(const QList<QVariant> &files, const QString &destinationDirectory, const ExtractionOptions &options)
+bool CliPlugin::extractFiles(const QList<Archive::Entry*> &files, const QString &destinationDirectory, const ExtractionOptions &options)
{
ExtractionOptions newOptions = options;
More information about the kde-doc-english
mailing list