diff --git a/autotests/kerfuffle/addtest.cpp b/autotests/kerfuffle/addtest.cpp index 7086c2ae..264c2b2e 100644 --- a/autotests/kerfuffle/addtest.cpp +++ b/autotests/kerfuffle/addtest.cpp @@ -1,133 +1,133 @@ /* * Copyright (c) 2010-2011 Raphael Kubo da Costa * Copyright (c) 2016 Elvis Angelaccio * * 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 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.toStdString().c_str()) + 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("archiveName"); QTest::addColumn>("files"); QTest::addColumn("destination"); addAllFormatsRows(QStringLiteral("without destination"), QStringLiteral("test"), QList { 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 { 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 { new Archive::Entry(this, QStringLiteral("testdir/")), }, new Archive::Entry(this, QStringLiteral("empty_dir/"))); addAllFormatsRows(QStringLiteral("without destination, directory 2"), QStringLiteral("test"), QList { new Archive::Entry(this, QStringLiteral("testdir2/")), }, new Archive::Entry(this)); addAllFormatsRows(QStringLiteral("with destination, directory 2"), QStringLiteral("test"), QList { 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, files); QFETCH(Archive::Entry*, destination); QList oldEntries = TestHelper::getEntryList(archive); CompressionOptions options = CompressionOptions(); options.insert(QStringLiteral("GlobalWorkDir"), QFINDTESTDATA("data")); AddJob *addJob = archive->addFiles(files, destination, options); TestHelper::startAndWaitForResult(addJob); QList resultedEntries = TestHelper::getEntryList(archive); TestHelper::verifyAddedEntriesWithDestination(files, destination, oldEntries, resultedEntries); archive->deleteLater(); } #include "addtest.moc" diff --git a/autotests/kerfuffle/copytest.cpp b/autotests/kerfuffle/copytest.cpp index e70d1b1a..54c88bf1 100644 --- a/autotests/kerfuffle/copytest.cpp +++ b/autotests/kerfuffle/copytest.cpp @@ -1,200 +1,200 @@ /* * Copyright (c) 2010-2011 Raphael Kubo da Costa * Copyright (c) 2016 Elvis Angelaccio * * 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 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.toStdString().c_str()) + 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("archiveName"); QTest::addColumn>("files"); QTest::addColumn("destination"); addAllFormatsRows(QStringLiteral("copy a single file"), QStringLiteral("test"), QList { new Archive::Entry(this, QStringLiteral("a.txt")), }, new Archive::Entry(this, QStringLiteral("empty_dir/"))); addAllFormatsRows(QStringLiteral("copy several files"), QStringLiteral("test"), QList { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, files); QFETCH(Archive::Entry*, destination); const QList oldEntries = TestHelper::getEntryList(archive); CompressionOptions options = CompressionOptions(); options.insert(QStringLiteral("GlobalWorkDir"), QFINDTESTDATA("data")); CopyJob *copyJob = archive->copyFiles(files, destination, options); TestHelper::startAndWaitForResult(copyJob); QList resultedEntries = TestHelper::getEntryList(archive); TestHelper::verifyCopiedEntriesWithDestination(files, destination, oldEntries, resultedEntries); archive->deleteLater(); } #include "copytest.moc" diff --git a/autotests/kerfuffle/movetest.cpp b/autotests/kerfuffle/movetest.cpp index a22b0773..f1433226 100644 --- a/autotests/kerfuffle/movetest.cpp +++ b/autotests/kerfuffle/movetest.cpp @@ -1,178 +1,178 @@ /* * Copyright (c) 2010-2011 Raphael Kubo da Costa * Copyright (c) 2016 Elvis Angelaccio * * 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 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.toStdString().c_str()) + 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("archiveName"); QTest::addColumn>("files"); QTest::addColumn("destination"); addAllFormatsRows(QStringLiteral("replace a single file"), QStringLiteral("test"), QList { new Archive::Entry(this, QStringLiteral("a.txt")), }, new Archive::Entry(this, QStringLiteral("empty_dir/a.txt"))); addAllFormatsRows(QStringLiteral("replace several files"), QStringLiteral("test"), QList { 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 { 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 { 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 { 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 { 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 { 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 { 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, files); QFETCH(Archive::Entry*, destination); QList oldEntries = TestHelper::getEntryList(archive); CompressionOptions options = CompressionOptions(); options.insert(QStringLiteral("GlobalWorkDir"), QFINDTESTDATA("data")); MoveJob *moveJob = archive->moveFiles(files, destination, options); TestHelper::startAndWaitForResult(moveJob); QList resultedEntries = TestHelper::getEntryList(archive); TestHelper::verifyMovedEntriesWithDestination(files, destination, oldEntries, resultedEntries); archive->deleteLater(); } #include "movetest.moc" diff --git a/autotests/testhelper/testhelper.cpp b/autotests/testhelper/testhelper.cpp index abbd139f..5cff7c77 100644 --- a/autotests/testhelper/testhelper.cpp +++ b/autotests/testhelper/testhelper.cpp @@ -1,188 +1,188 @@ #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 TestHelper::getEntryList(Archive *archive) { QList list = QList(); ListJob *listJob = archive->list(); QObject::connect(listJob, &Job::newEntry, [&list](Archive::Entry* entry) { list << entry; }); startAndWaitForResult(listJob); return list; } void TestHelper::verifyAddedEntriesWithDestination(const QList &argumentEntries, const Archive::Entry *destination, const QList &oldEntries, const QList &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)")).toStdString().c_str()); + 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)")).toStdString().c_str()); + QVERIFY2(actualPaths.contains(path), (QStringLiteral("No ") + path + QStringLiteral(" inside the archive (old entry)")).toUtf8()); } } void TestHelper::verifyMovedEntriesWithDestination(const QList &argumentEntries, const Archive::Entry *destination, const QList &oldEntries, const QList &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")).toStdString().c_str()); + 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")).toStdString().c_str()); + 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")).toStdString().c_str()); + QVERIFY2(!actualPaths.contains(path), (QStringLiteral("Entry ") + path + QStringLiteral(" is still inside the archive, when it shouldn't be")).toUtf8()); } } void TestHelper::verifyCopiedEntriesWithDestination(const QList &argumentEntries, const Archive::Entry *destination, const QList &oldEntries, const QList &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")).toStdString().c_str()); + 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")).toStdString().c_str()); + QVERIFY2(expectedPaths.contains(path), (QStringLiteral("Entry ") + path + QStringLiteral(" is not expected to be inside the archive")).toUtf8()); } } QStringList TestHelper::getExpectedNewEntryPaths(const QList &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 &entryList, const QList &argumentEntries, const Archive::Entry *destination) { QStringList expectedPaths = QStringList(); QMap 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 &entryList, const QList &argumentEntries, const Archive::Entry *destination) { QStringList expectedPaths = QStringList(); QMap 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 TestHelper::getEntryMap(const QList entries) { QMap map; foreach (Archive::Entry* entry, entries) { map.insert(entry->fullPath(), entry); } return map; } diff --git a/part/archivemodel.cpp b/part/archivemodel.cpp index 841b0717..fb81b001 100644 --- a/part/archivemodel.cpp +++ b/part/archivemodel.cpp @@ -1,892 +1,892 @@ /* * ark -- archiver for the KDE project * * Copyright (C) 2007 Henrique Pinto * Copyright (C) 2008-2009 Harald Hvaal * Copyright (C) 2010-2012 Raphael Kubo da Costa * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "archivemodel.h" #include "kerfuffle/jobs.h" #include #include #include #include #include #include using namespace Kerfuffle; //used to speed up the loading of large archives static Archive::Entry *s_previousMatch = Q_NULLPTR; Q_GLOBAL_STATIC(QStringList, s_previousPieces) /** * Meta data related to one entry in a compressed archive. * * This is used for indexing entry properties as numbers * and for determining data displaying order in part's view. */ enum EntryMetaDataType { FullPath, /**< The entry's file name */ Size, /**< The entry's original size */ CompressedSize, /**< The compressed size for the entry */ Permissions, /**< The entry's permissions */ Owner, /**< The user the entry belongs to */ Group, /**< The user group the entry belongs to */ Ratio, /**< The compression ratio for the entry */ CRC, /**< The entry's CRC */ Method, /**< The compression method used on the entry */ Version, /**< The archiver version needed to extract the entry */ Timestamp, /**< The timestamp for the current entry */ Comment, }; /** * Mappings between column indexes and entry properties. */ static QMap initializePropertiesList() { QMap propertiesList = QMap(); propertiesList.insert(FullPath, QStringLiteral("fullPath")); propertiesList.insert(Size, QStringLiteral("size")); propertiesList.insert(CompressedSize, QStringLiteral("compressedSize")); propertiesList.insert(Permissions, QStringLiteral("permissions")); propertiesList.insert(Owner, QStringLiteral("owner")); propertiesList.insert(Group, QStringLiteral("group")); propertiesList.insert(Ratio, QStringLiteral("ratio")); propertiesList.insert(CRC, QStringLiteral("CRC")); propertiesList.insert(Method, QStringLiteral("method")); propertiesList.insert(Version, QStringLiteral("version")); propertiesList.insert(Timestamp, QStringLiteral("timestamp")); propertiesList.insert(Comment, QStringLiteral("comment")); return propertiesList; } static const QMap propertiesList = initializePropertiesList(); /** * Helper functor used by qStableSort. * * It always sorts folders before files. * * @internal */ class ArchiveModelSorter { public: ArchiveModelSorter(int column, Qt::SortOrder order) : m_sortColumn(column) , m_sortOrder(order) { } virtual ~ArchiveModelSorter() { } inline bool operator()(const QPair &left, const QPair &right) const { if (m_sortOrder == Qt::AscendingOrder) { return lessThan(left, right); } else { return !lessThan(left, right); } } protected: bool lessThan(const QPair &left, const QPair &right) const { const Archive::Entry * const leftEntry = left.first; const Archive::Entry * const rightEntry = right.first; // #234373: sort folders before files if ((leftEntry->isDir()) && (!rightEntry->isDir())) { return (m_sortOrder == Qt::AscendingOrder); } else if ((!leftEntry->isDir()) && (rightEntry->isDir())) { return !(m_sortOrder == Qt::AscendingOrder); } EntryMetaDataType column = static_cast(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: return leftEntry->name() < rightEntry->name(); case Size: case CompressedSize: return leftEntryMetaData.toInt() < rightEntryMetaData.toInt(); default: return leftEntryMetaData.toString() < rightEntryMetaData.toString(); } // We should not get here. Q_ASSERT(false); return false; } private: int m_sortColumn; Qt::SortOrder m_sortOrder; }; ArchiveModel::ArchiveModel(const QString &dbusPathName, QObject *parent) : QAbstractItemModel(parent) , m_rootEntry() , m_dbusPathName(dbusPathName) { m_rootEntry.setProperty("isDirectory", true); } ArchiveModel::~ArchiveModel() { } QVariant ArchiveModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { Archive::Entry *entry = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: { //TODO: complete the columns int column = m_showColumns.at(index.column()); switch (column) { case FullPath: return entry->name(); case Size: if (entry->isDir()) { int dirs; int files; const int children = childCount(index, dirs, files); return KIO::itemsSummaryString(children, files, dirs, 0, false); } else if (!entry->property("link").toString().isEmpty()) { return QVariant(); } else { return KIO::convertSize(entry->property("size").toULongLong()); } case CompressedSize: if (entry->isDir() || !entry->property("link").toString().isEmpty()) { return QVariant(); } else { qulonglong compressedSize = entry->property("compressedSize").toULongLong(); if (compressedSize != 0) { return KIO::convertSize(compressedSize); } else { return QVariant(); } } case Ratio: // TODO: Use entry->metaData()[Ratio] when available if (entry->isDir() || !entry->property("link").toString().isEmpty()) { return QVariant(); } else { qulonglong compressedSize = entry->property("compressedSize").toULongLong(); qulonglong size = entry->property("size").toULongLong(); if (compressedSize == 0 || size == 0) { return QVariant(); } else { int ratio = int(100 * ((double)size - compressedSize) / size); return QString(QString::number(ratio) + QStringLiteral(" %")); } } case Timestamp: { const QDateTime timeStamp = entry->property("timestamp").toDateTime(); return QLocale().toString(timeStamp, QLocale::ShortFormat); } 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->fullPath()); } return QVariant(); case Qt::FontRole: { QFont f; f.setItalic(entry->property("isPasswordProtected").toBool()); return f; } default: return QVariant(); } } return QVariant(); } Qt::ItemFlags ArchiveModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); if (index.isValid()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags; } return 0; } QVariant ArchiveModel::headerData(int section, Qt::Orientation, int role) const { if (role == Qt::DisplayRole) { if (section >= m_showColumns.size()) { qCDebug(ARK) << "WEIRD: showColumns.size = " << m_showColumns.size() << " and section = " << section; return QVariant(); } int columnId = m_showColumns.at(section); switch (columnId) { case FullPath: return i18nc("Name of a file inside an archive", "Name"); case Size: return i18nc("Uncompressed size of a file inside an archive", "Size"); case CompressedSize: return i18nc("Compressed size of a file inside an archive", "Compressed"); case Ratio: return i18nc("Compression rate of file", "Rate"); case Owner: return i18nc("File's owner username", "Owner"); case Group: return i18nc("File's group", "Group"); case Permissions: return i18nc("File permissions", "Mode"); case CRC: return i18nc("CRC hash code", "CRC"); case Method: return i18nc("Compression method", "Method"); case Version: //TODO: what exactly is a file version? return i18nc("File version", "Version"); case Timestamp: return i18nc("Timestamp", "Date"); case Comment: return i18nc("File comment", "Comment"); default: return i18nc("Unnamed column", "??"); } } return QVariant(); } QModelIndex ArchiveModel::index(int row, int column, const QModelIndex &parent) const { if (hasIndex(row, column, parent)) { const Archive::Entry *parentEntry = parent.isValid() ? static_cast(parent.internalPointer()) : &m_rootEntry; Q_ASSERT(parentEntry->isDir()); const Archive::Entry *item = parentEntry->entries().value(row, Q_NULLPTR); if (item != Q_NULLPTR) { return createIndex(row, column, const_cast(item)); } } return QModelIndex(); } QModelIndex ArchiveModel::parent(const QModelIndex &index) const { if (index.isValid()) { Archive::Entry *item = static_cast(index.internalPointer()); Q_ASSERT(item); if (item->getParent() && (item->getParent() != &m_rootEntry)) { return createIndex(item->getParent()->row(), 0, item->getParent()); } } return QModelIndex(); } Archive::Entry *ArchiveModel::entryForIndex(const QModelIndex &index) { if (index.isValid()) { Archive::Entry *item = static_cast(index.internalPointer()); Q_ASSERT(item); return item; } return Q_NULLPTR; } int ArchiveModel::childCount(const QModelIndex &index, int &dirs, int &files) const { if (index.isValid()) { dirs = files = 0; Archive::Entry *item = static_cast(index.internalPointer()); Q_ASSERT(item); if (item->isDir()) { const QVector entries = item->entries(); foreach(const Archive::Entry *entry, entries) { if (entry->isDir()) { dirs++; } else { files++; } } return entries.count(); } return 0; } return -1; } int ArchiveModel::rowCount(const QModelIndex &parent) const { if (parent.column() <= 0) { const Archive::Entry *parentEntry = parent.isValid() ? static_cast(parent.internalPointer()) : &m_rootEntry; if (parentEntry && parentEntry->isDir()) { return parentEntry->entries().count(); } } return 0; } int ArchiveModel::columnCount(const QModelIndex &parent) const { return m_showColumns.size(); } void ArchiveModel::sort(int column, Qt::SortOrder order) { if (m_showColumns.size() <= column) { return; } emit layoutAboutToBeChanged(); QList dirEntries; m_rootEntry.returnDirEntries(&dirEntries); dirEntries.append(&m_rootEntry); const ArchiveModelSorter modelSorter(m_showColumns.at(column), order); foreach(Archive::Entry *dir, dirEntries) { QVector < QPair > sorting(dir->entries().count()); for (int i = 0; i < dir->entries().count(); ++i) { Archive::Entry *item = dir->entries().at(i); sorting[i].first = item; sorting[i].second = i; } qStableSort(sorting.begin(), sorting.end(), modelSorter); QModelIndexList fromIndexes; QModelIndexList toIndexes; for (int r = 0; r < sorting.count(); ++r) { Archive::Entry *item = sorting.at(r).first; toIndexes.append(createIndex(r, 0, item)); fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first)); dir->setEntryAt(r, sorting.at(r).first); } changePersistentIndexList(fromIndexes, toIndexes); emit dataChanged( index(0, 0, indexForEntry(dir)), index(dir->entries().size() - 1, 0, indexForEntry(dir))); } emit layoutChanged(); } Qt::DropActions ArchiveModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList ArchiveModel::mimeTypes() const { QStringList types; // MIME types we accept for dragging (eg. Dolphin -> Ark). types << QStringLiteral("text/uri-list") << QStringLiteral("text/plain") << QStringLiteral("text/x-moz-url"); // MIME types we accept for dropping (eg. Ark -> Dolphin). types << QStringLiteral("application/x-kde-ark-dndextract-service") << QStringLiteral("application/x-kde-ark-dndextract-path"); return types; } QMimeData *ArchiveModel::mimeData(const QModelIndexList &indexes) const { Q_UNUSED(indexes) QMimeData *mimeData = new QMimeData; mimeData->setData(QStringLiteral("application/x-kde-ark-dndextract-service"), QDBusConnection::sessionBus().baseService().toUtf8()); mimeData->setData(QStringLiteral("application/x-kde-ark-dndextract-path"), m_dbusPathName.toUtf8()); return mimeData; } 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; } QStringList paths; foreach(const QUrl &url, data->urls()) { 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(); } } qCDebug(ARK) << "Dropped onto " << path; #endif emit droppedFiles(paths, path); return true; } // For a rationale, see bugs #194241, #241967 and #355839 QString ArchiveModel::cleanFileName(const QString& fileName) { // Skip entries with filename "/" or "//" or "." // "." is present in ISO files QRegularExpression pattern(QStringLiteral("/+|\\.")); QRegularExpressionMatch match; if (fileName.contains(pattern, &match) && match.captured() == fileName) { qCDebug(ARK) << "Skipping entry with filename" << fileName; return QString(); } else if (fileName.startsWith(QLatin1String("./"))) { return fileName.mid(2); } return fileName; } Archive::Entry *ArchiveModel::parentFor(const Archive::Entry *entry) { QStringList pieces = entry->fullPath().split(QLatin1Char( '/' ), QString::SkipEmptyParts); if (pieces.isEmpty()) { return Q_NULLPTR; } pieces.removeLast(); if (s_previousMatch) { //the number of path elements must be the same for the shortcut //to work if (s_previousPieces->count() == pieces.count()) { bool equal = true; //make sure all the pieces match up for (int i = 0; i < s_previousPieces->count(); ++i) { if (s_previousPieces->at(i) != pieces.at(i)) { equal = false; break; } } //if match return it if (equal) { return s_previousMatch; } } } Archive::Entry *parent = &m_rootEntry; foreach(const QString &piece, pieces) { Archive::Entry *entry = parent->find(piece); if (!entry) { // Directory entry will be traversed later (that happens for some archive formats, 7z for instance). // We have to create one before, in order to construct tree from its children, // and then delete the existing one (see ArchiveModel::newEntry). entry = new Archive::Entry(parent); entry->setProperty("fullPath", (parent == &m_rootEntry) ? piece : parent->fullPath() + QLatin1Char( '/' ) + piece); entry->setProperty("isDirectory", true); insertEntry(entry); } if (!entry->isDir()) { Archive::Entry *e = new Archive::Entry(parent); copyEntryMetaData(e, entry); // Maybe we have both a file and a directory of the same name. // We avoid removing previous entries unless necessary. insertEntry(e); } parent = entry; } s_previousMatch = parent; *s_previousPieces = pieces; return parent; } QModelIndex ArchiveModel::indexForEntry(Archive::Entry *entry) { Q_ASSERT(entry); if (entry != &m_rootEntry) { Q_ASSERT(entry->getParent()); Q_ASSERT(entry->getParent()->isDir()); return createIndex(entry->row(), 0, entry); } return QModelIndex(); } void ArchiveModel::slotEntryRemoved(const QString & path) { const QString entryFileName(cleanFileName(path)); if (entryFileName.isEmpty()) { return; } Archive::Entry *entry = m_rootEntry.findByPath(entryFileName.split(QLatin1Char( '/' ), QString::SkipEmptyParts)); if (entry) { Archive::Entry *parent = entry->getParent(); QModelIndex index = indexForEntry(entry); Q_UNUSED(index); beginRemoveRows(indexForEntry(parent), entry->row(), entry->row()); delete m_entryIcons.take(parent->entries().at(entry->row())->fullPath()); parent->removeEntryAt(entry->row()); endRemoveRows(); } } void ArchiveModel::slotUserQuery(Kerfuffle::Query *query) { query->execute(); } void ArchiveModel::slotNewEntryFromSetArchive(Archive::Entry *entry) { // we cache all entries that appear when opening a new archive // so we can all them together once it's done, this is a huge // performance improvement because we save from doing lots of // begin/endInsertRows m_newArchiveEntries.push_back(entry); } void ArchiveModel::slotNewEntry(Archive::Entry *entry) { newEntry(entry, NotifyViews); } void ArchiveModel::newEntry(Archive::Entry *receivedEntry, InsertBehaviour behaviour) { if (receivedEntry->fullPath().isEmpty()) { qCDebug(ARK) << "Weird, received empty entry (no filename) - skipping"; return; } //if there are no addidional columns registered, then have a look at the //entry and populate some if (m_showColumns.isEmpty()) { QList toInsert; QMap::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(); } } ++i; } beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1); m_showColumns << toInsert; endInsertColumns(); qCDebug(ARK) << "Showing columns: " << m_showColumns; } //#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->fullPath()); if (entryFileName.isEmpty()) { // The entry contains only "." or "./" return; } receivedEntry->setProperty("fullPath", entryFileName); /// 1. Skip already created entries Archive::Entry *existing = m_rootEntry.findByPath(entryFileName.split(QLatin1Char( '/' ))); if (existing) { qCDebug(ARK) << "Refreshing entry for" << entryFileName; existing->setProperty("fullPath", entryFileName); // Multi-volume files are repeated at least in RAR archives. // In that case, we need to sum the compressed size for each volume qulonglong currentCompressedSize = existing->property("compressedSize").toULongLong(); existing->setProperty("compressedSize", currentCompressedSize + receivedEntry->property("compressedSize").toULongLong()); return; } /// 2. Find Parent Entry, creating missing direcotry ArchiveEntries in the process Archive::Entry *parent = parentFor(receivedEntry); /// 3. Create an Archive::Entry const QStringList path = entryFileName.split(QLatin1Char('/'), QString::SkipEmptyParts); const QString name = path.last(); Archive::Entry *entry = parent->find(name); if (entry) { copyEntryMetaData(entry, receivedEntry); entry->setProperty("fullPath", entryFileName); delete receivedEntry; } else { receivedEntry->setParent(parent); insertEntry(receivedEntry, behaviour); } } void ArchiveModel::slotLoadingFinished(KJob *job) { int i = 0; foreach(Archive::Entry *entry, m_newArchiveEntries) { newEntry(entry, DoNotNotifyViews); i++; } beginResetModel(); endResetModel(); m_newArchiveEntries.clear(); qCDebug(ARK) << "Added" << i << "entries to model"; 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); Archive::Entry *parent = entry->getParent(); Q_ASSERT(parent); if (behaviour == NotifyViews) { beginInsertRows(indexForEntry(parent), parent->entries().count(), parent->entries().count()); } parent->appendEntry(entry); if (behaviour == NotifyViews) { endInsertRows(); } // Save an icon for each newly added entry. QMimeDatabase db; const QPixmap *pixmap; if (entry->isDir()) { pixmap = new QPixmap(QIcon::fromTheme(db.mimeTypeForName(QStringLiteral("inode/directory")).iconName()).pixmap(IconSize(KIconLoader::Small), IconSize(KIconLoader::Small))); } else { pixmap = new QPixmap(QIcon::fromTheme(db.mimeTypeForFile(entry->fullPath()).iconName()).pixmap(IconSize(KIconLoader::Small), IconSize(KIconLoader::Small))); } m_entryIcons.insert(entry->fullPath(), pixmap); } Kerfuffle::Archive* ArchiveModel::archive() const { return m_archive.data(); } KJob* ArchiveModel::setArchive(Kerfuffle::Archive *archive) { m_archive.reset(archive); m_rootEntry.clear(); s_previousMatch = Q_NULLPTR; s_previousPieces->clear(); Kerfuffle::ListJob *job = Q_NULLPTR; m_newArchiveEntries.clear(); if (m_archive) { job = m_archive->list(); // TODO: call "open" or "create"? if (job) { connect(job, &Kerfuffle::ListJob::newEntry, this, &ArchiveModel::slotNewEntryFromSetArchive); connect(job, &Kerfuffle::ListJob::result, this, &ArchiveModel::slotLoadingFinished); connect(job, &Kerfuffle::ListJob::userQuery, this, &ArchiveModel::slotUserQuery); emit loadingStarted(); // TODO: make sure if it's ok to not have calls to beginRemoveColumns here m_showColumns.clear(); } } beginResetModel(); endResetModel(); return job; } ExtractJob* ArchiveModel::extractFile(Archive::Entry *file, const QString& destinationDir, const Kerfuffle::ExtractionOptions& options) const { QList files; files << file; return extractFiles(files, destinationDir, options); } ExtractJob* ArchiveModel::extractFiles(const QList& files, const QString& destinationDir, const Kerfuffle::ExtractionOptions& options) const { Q_ASSERT(m_archive); ExtractJob *newJob = m_archive->extractFiles(files, destinationDir, options); connect(newJob, &ExtractJob::userQuery, this, &ArchiveModel::slotUserQuery); return newJob; } Kerfuffle::PreviewJob *ArchiveModel::preview(Archive::Entry *file) const { Q_ASSERT(m_archive); PreviewJob *job = m_archive->preview(file); connect(job, &Job::userQuery, this, &ArchiveModel::slotUserQuery); return job; } OpenJob *ArchiveModel::open(Archive::Entry *file) const { Q_ASSERT(m_archive); OpenJob *job = m_archive->open(file); connect(job, &Job::userQuery, this, &ArchiveModel::slotUserQuery); return job; } OpenWithJob *ArchiveModel::openWith(Archive::Entry *file) const { Q_ASSERT(m_archive); OpenWithJob *job = m_archive->openWith(file); connect(job, &Job::userQuery, this, &ArchiveModel::slotUserQuery); return job; } AddJob* ArchiveModel::addFiles(QList &entries, const Archive::Entry *destination, const CompressionOptions& options) { if (!m_archive) { return Q_NULLPTR; } if (!m_archive->isReadOnly()) { AddJob *job = m_archive->addFiles(entries, destination, options); connect(job, &AddJob::newEntry, this, &ArchiveModel::slotNewEntry); connect(job, &AddJob::userQuery, this, &ArchiveModel::slotUserQuery); return job; } return Q_NULLPTR; } DeleteJob* ArchiveModel::deleteFiles(QList entries) { Q_ASSERT(m_archive); if (!m_archive->isReadOnly()) { DeleteJob *job = m_archive->deleteFiles(entries); connect(job, &DeleteJob::entryRemoved, this, &ArchiveModel::slotEntryRemoved); connect(job, &DeleteJob::finished, this, &ArchiveModel::slotCleanupEmptyDirs); connect(job, &DeleteJob::userQuery, this, &ArchiveModel::slotUserQuery); return job; } return Q_NULLPTR; } void ArchiveModel::encryptArchive(const QString &password, bool encryptHeader) { if (!m_archive) { return; } m_archive->encrypt(password, encryptHeader); } void ArchiveModel::slotCleanupEmptyDirs() { QList queue; QList nodesToDelete; //add root nodes for (int i = 0; i < rowCount(); ++i) { queue.append(QPersistentModelIndex(index(i, 0))); } //breadth-first traverse while (!queue.isEmpty()) { QPersistentModelIndex node = queue.takeFirst(); Archive::Entry *entry = entryForIndex(node); if (!hasChildren(node)) { if (entry->fullPath().isEmpty()) { nodesToDelete << node; } } else { for (int i = 0; i < rowCount(node); ++i) { queue.append(QPersistentModelIndex(index(i, 0, node))); } } } foreach(const QPersistentModelIndex& node, nodesToDelete) { Archive::Entry *rawEntry = static_cast(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())->fullPath()); rawEntry->getParent()->removeEntryAt(rawEntry->row()); endRemoveRows(); } } diff --git a/plugins/libarchive/readwritelibarchiveplugin.cpp b/plugins/libarchive/readwritelibarchiveplugin.cpp index d68ca73a..b176304d 100644 --- a/plugins/libarchive/readwritelibarchiveplugin.cpp +++ b/plugins/libarchive/readwritelibarchiveplugin.cpp @@ -1,558 +1,558 @@ /* * Copyright (c) 2007 Henrique Pinto * Copyright (c) 2008-2009 Harald Hvaal * Copyright (c) 2010 Raphael Kubo da Costa * * 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 "readwritelibarchiveplugin.h" #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(ReadWriteLibarchivePluginFactory, "kerfuffle_libarchive.json", registerPlugin();) ReadWriteLibarchivePlugin::ReadWriteLibarchivePlugin(QObject *parent, const QVariantList &args) : LibarchivePlugin(parent, args) { qCDebug(ARK) << "Loaded libarchive read-write plugin"; } ReadWriteLibarchivePlugin::~ReadWriteLibarchivePlugin() { } bool ReadWriteLibarchivePlugin::addFiles(const QList &files, const Archive::Entry *destination, const CompressionOptions &options) { qCDebug(ARK) << "Adding" << files.size() << "entries with CompressionOptions" << options; const bool creatingNewFile = !QFileInfo::exists(filename()); m_writtenFiles.clear(); if (!creatingNewFile && !initializeReader()) { return false; } if (!initializeWriter(creatingNewFile, options)) { return false; } // First write the new files. qCDebug(ARK) << "Writing new entries"; int no_entries = 0; // 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->fullPath(), destinationPath)) { finish(false); return false; } no_entries++; // For directories, write all subfiles/folders. const QString &fullPath = selectedFile->fullPath(); if (QFileInfo(fullPath).isDir()) { QDirIterator it(fullPath, QDir::AllEntries | QDir::Readable | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (!m_abortOperation && it.hasNext()) { QString path = it.next(); if ((it.fileName() == QLatin1String("..")) || (it.fileName() == QLatin1String("."))) { continue; } const bool isRealDir = it.fileInfo().isDir() && !it.fileInfo().isSymLink(); if (isRealDir) { path.append(QLatin1Char('/')); } if (!writeFile(path, destinationPath)) { finish(false); return false; } no_entries++; } } } qCDebug(ARK) << "Added" << no_entries << "new entries to archive"; bool isSuccessful = true; // If we have old archive entries. if (!creatingNewFile) { qCDebug(ARK) << "Copying any old entries"; m_filePaths = m_writtenFiles; isSuccessful = processOldEntries(no_entries, Add); if (isSuccessful) { qCDebug(ARK) << "Added" << no_entries << "old entries to archive"; } else { qCDebug(ARK) << "Adding entries failed"; } } m_abortOperation = false; finish(isSuccessful); return isSuccessful; } bool ReadWriteLibarchivePlugin::moveFiles(const QList &files, Archive::Entry *destination, const CompressionOptions &options) { Q_UNUSED(options); qCDebug(ARK) << "Moving" << files.size() << "entries"; if (!initializeReader()) { return false; } if (!initializeWriter()) { return false; } // Copy old elements from previous archive to new archive. int no_entries = 0; m_filePaths = entryFullPaths(entriesWithoutChildren(files)); 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"; } finish(isSuccessful); return isSuccessful; } bool ReadWriteLibarchivePlugin::copyFiles(const QList &files, Archive::Entry *destination, const CompressionOptions &options) { Q_UNUSED(options); qCDebug(ARK) << "Copying" << files.size() << "entries"; if (!initializeReader()) { return false; } if (!initializeWriter()) { return false; } // Copy old elements from previous archive to new archive. int no_entries = 0; m_filePaths = entryFullPaths(entriesWithoutChildren(files)); 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 &files) { qCDebug(ARK) << "Deleting" << files.size() << "entries"; if (!initializeReader()) { return false; } if (!initializeWriter()) { return false; } // Copy old elements from previous archive to new archive. int no_entries = 0; m_filePaths = 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). m_tempFile.setFileName(filename()); if (!m_tempFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) { emit error(xi18nc("@info", "Failed to create a temporary file to compress %1.", filename())); return false; } 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(m_archiveWriter.data()); 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:" "%1", QLatin1String(archive_error_string(m_archiveWriter.data())))); return false; } 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:%1", QLatin1String(archive_error_string(m_archiveWriter.data())))); return false; } 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:%1", 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:%1", 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; m_lastMovedFolder = QString(); entriesCounter = 0; // If destination path doesn't contain a target entry name, we have to remember to include it // while moving or copying folder contents. int nameLength = 0; while ((mode != Add || !m_abortOperation) && archive_read_next_header(m_archiveReader.data(), &entry) == ARCHIVE_OK) { const QString file = QFile::decodeName(archive_entry_pathname(entry)); if (mode == Move || mode == Copy) { QString newPathname; bool found = true; if (m_lastMovedFolder.count() > 0 && file.startsWith(m_lastMovedFolder)) { // Replace last moved or copied folder path with destination path. int charsCount = file.count() - m_lastMovedFolder.count(); if (mode == Copy || m_filePaths.count() > 1) { charsCount += nameLength; } newPathname = m_destination->fullPath() + file.right(charsCount); } else if (m_filePaths.contains(file)) { const QString name = file.split(QLatin1Char('/'), QString::SkipEmptyParts).last(); if (mode == Copy || m_filePaths.count() > 1) { newPathname = m_destination->fullPath() + name; } else { // If the mode is set to Move and there is only one passed file in the list, // we have to use destination as newPathname. newPathname = m_destination->fullPath(); } if (file.right(1) == QLatin1String("/")) { nameLength = name.count() + 1; // plus slash m_lastMovedFolder = file; } else { nameLength = 0; // plus slash m_lastMovedFolder = QString(); } } else { found = false; m_lastMovedFolder = QString(); } if (found) { if (mode == Copy) { writeEntry(entry); } else { emit entryRemoved(file); } entriesCounter++; - archive_entry_set_pathname(entry, newPathname.toStdString().c_str()); + archive_entry_set_pathname(entry, newPathname.toUtf8()); } } else if (m_filePaths.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; } if (writeEntry(entry)) { if (mode == Add) { entriesCounter++; } } 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) { 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)), 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(m_archiveWriter.data()); emit error(xi18nc("@info", "Compression failed while processing:" "%1Operation aborted.", file)); return false; default: qCDebug(ARK) << "archive_writer_header() has returned" << returnCode << "which will be ignored."; break; } 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, 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, // or something may have caused it to follow symlinks, in // which case stat() will be called. To avoid this, we // call lstat() ourselves. struct stat st; lstat(QFile::encodeName(absoluteFilename).constData(), &st); struct archive_entry *entry = archive_entry_new(); 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(m_archiveWriter.data(), entry)) == ARCHIVE_OK) { // If the whole archive is extracted and the total filesize is // available, we use partial progress. 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(m_archiveWriter.data()) << "(error no =" << archive_errno(m_archiveWriter.data()) << ')'; emit error(xi18nc("@info Error in a message box", "Ark could not compress %1:%2", absoluteFilename, QString::fromUtf8(archive_error_string(m_archiveWriter.data())))); archive_entry_free(entry); return false; } m_writtenFiles.push_back(destinationFilename); emitEntryFromArchiveEntry(entry); archive_entry_free(entry); return true; } #include "readwritelibarchiveplugin.moc"