Commit 01d0a2dd authored by Ilya Pominov's avatar Ilya Pominov
Browse files

Add cliarj plugin to support arj archives

parent 25121a53
Pipeline #242406 passed with stage
in 1 minute and 34 seconds
Test data for the kerfuffle's unit tests.
* The password for archivetest_encrypted.zip is 'ark' (without quotes).
* The password for test_encrypted.arj is 'ark' (without quotes).
......@@ -407,6 +407,45 @@ void ExtractTest::testExtraction_data()
<< optionsPreservePaths
<< 2;
archivePath = QFINDTESTDATA("data/one_toplevel_folder.arj");
QTest::newRow("extract the whole one_toplevel_folder.arj")
<< archivePath
<< QVector<Archive::Entry*>()
<< optionsPreservePaths
<< 9;
archivePath = QFINDTESTDATA("data/one_toplevel_folder.arj");
QTest::newRow("extract selected entries from a arj, without paths")
<< archivePath
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A")),
new Archive::Entry(this, QStringLiteral("A/B/test1.txt"), QStringLiteral("A/B"))
}
<< optionsNoPaths
<< 2;
archivePath = QFINDTESTDATA("data/one_toplevel_folder.arj");
QTest::newRow("extract selected entries from a arj, preserve paths")
<< archivePath
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A")),
new Archive::Entry(this, QStringLiteral("A/B/test1.txt"), QStringLiteral("A/B"))
}
<< optionsPreservePaths
<< 4;
archivePath = QFINDTESTDATA("data/one_toplevel_folder.arj");
QTest::newRow("extract selected entries from a arj, drag-and-drop")
<< archivePath
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("A/test2.txt"), QStringLiteral("A/")),
new Archive::Entry(this, QStringLiteral("A/B/C/"), QStringLiteral("A/B/")),
new Archive::Entry(this, QStringLiteral("A/B/C/test1.txt"), QStringLiteral("A/B/")),
new Archive::Entry(this, QStringLiteral("A/B/C/test2.txt"), QStringLiteral("A/B/"))
}
<< dragAndDropOptions
<< 4;
m_expectedWorkingDir = QDir::currentPath();
}
......
......@@ -222,6 +222,20 @@ void LoadTest::testProperties_data()
} else {
qDebug() << "zstd executable not found in path. Skipping zstd test.";
}
QTest::newRow("arj unencrypted archive with comment")
<< QFINDTESTDATA("data/test.arj")
<< QStringLiteral("test")
<< false << false << false << false << false << 0 << Archive::Unencrypted
<< QStringLiteral("test")
<< QStringLiteral("Arj archive");
QTest::newRow("arj encrypted archive")
<< QFINDTESTDATA("data/test_encrypted.arj")
<< QStringLiteral("test_encrypted")
<< false << false << false << false << false << 0 << Archive::Encrypted
<< QStringLiteral("test_encrypted")
<< QString();
}
void LoadTest::testProperties()
......
......@@ -2,3 +2,4 @@ add_subdirectory(clizipplugin)
add_subdirectory(cli7zplugin)
add_subdirectory(clirarplugin)
add_subdirectory(cliunarchiverplugin)
add_subdirectory(cliarjplugin)
set(RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/plugins/cliarjplugin/)
file(COPY ${CMAKE_BINARY_DIR}/plugins/cliarjplugin/kerfuffle_cliarj.json
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
ecm_add_test(
cliarjtest.cpp
${CMAKE_SOURCE_DIR}/plugins/cliarjplugin/cliplugin.cpp
${CMAKE_BINARY_DIR}/plugins/cliarjplugin/ark_debug.cpp
LINK_LIBRARIES testhelper kerfuffle Qt::Test
TEST_NAME cliarjtest
NAME_PREFIX plugins-)
/*
SPDX-FileCopyrightText: 2022 Ilya Pominov <ipominov@astralinux.ru>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cliarjtest.h"
#include "cliplugin.h"
#include <QTest>
QTEST_GUILESS_MAIN(CliArjTest)
using namespace Kerfuffle;
void CliArjTest::initTestCase()
{
m_plugin = new Plugin(this);
const auto plugins = m_pluginManger.availablePlugins();
for (Plugin *plugin : plugins) {
if (plugin->metaData().pluginId() == QLatin1String("kerfuffle_cliarj")) {
m_plugin = plugin;
return;
}
}
}
void CliArjTest::testListArgs_data()
{
QTest::addColumn<QString>("archiveName");
QTest::addColumn<QString>("password");
QTest::addColumn<QStringList>("expectedArgs");
QTest::newRow("fake arj")
<< QStringLiteral("/tmp/foo.arj")
<< QString()
<< QStringList {
QStringLiteral("v"),
QStringLiteral("/tmp/foo.arj")
};
QTest::newRow("fake encrypted arj")
<< QStringLiteral("/tmp/foo.arj")
<< QStringLiteral("1234")
<< QStringList {
QStringLiteral("v"),
QStringLiteral("/tmp/foo.arj")
};
}
void CliArjTest::testListArgs()
{
if (!m_plugin->isValid()) {
QSKIP("cliarj plugin not available. Skipping test.", SkipSingle);
}
QFETCH(QString, archiveName);
CliPlugin *plugin = new CliPlugin(this, {QVariant(archiveName),
QVariant::fromValue(m_plugin->metaData())});
QVERIFY(plugin);
QFETCH(QString, password);
const auto replacedArgs = plugin->cliProperties()->listArgs(archiveName, password);
QFETCH(QStringList, expectedArgs);
QCOMPARE(replacedArgs, expectedArgs);
plugin->deleteLater();
}
void CliArjTest::testAddArgs_data()
{
QTest::addColumn<QString>("archiveName");
QTest::addColumn<QString>("password");
QTest::addColumn<int>("compressionLevel");
QTest::addColumn<QString>("compressionMethod");
QTest::addColumn<QStringList>("expectedArgs");
QTest::newRow("unencrypted")
<< QStringLiteral("/tmp/foo.arj")
<< QString() << -1 << QString()
<< QStringList {
QStringLiteral("a"),
QStringLiteral("-r"),
QStringLiteral("/tmp/foo.arj")
};
QTest::newRow("encrypted")
<< QStringLiteral("/tmp/foo.arj")
<< QStringLiteral("1234") << -1 << QString()
<< QStringList {
QStringLiteral("a"),
QStringLiteral("-r"),
QStringLiteral("-g1234"),
QStringLiteral("/tmp/foo.arj")
};
QTest::newRow("comp-method-good")
<< QStringLiteral("/tmp/foo.arj")
<< QString() << -1 << QStringLiteral("Good (default)")
<< QStringList {
QStringLiteral("a"),
QStringLiteral("-r"),
QStringLiteral("-m1"),
QStringLiteral("/tmp/foo.arj")
};
}
void CliArjTest::testAddArgs()
{
if (!m_plugin->isValid()) {
QSKIP("cliarj plugin not available. Skipping test.", SkipSingle);
}
QFETCH(QString, archiveName);
CliPlugin *plugin = new CliPlugin(this, {QVariant(archiveName),
QVariant::fromValue(m_plugin->metaData())});
QVERIFY(plugin);
QFETCH(QString, password);
QFETCH(int, compressionLevel);
QFETCH(QString, compressionMethod);
const auto replacedArgs = plugin->cliProperties()->addArgs(archiveName, {}, password, false, compressionLevel, compressionMethod, QString(), 0);
QFETCH(QStringList, expectedArgs);
QCOMPARE(replacedArgs, expectedArgs);
plugin->deleteLater();
}
void CliArjTest::testExtractArgs_data()
{
QTest::addColumn<QString>("archiveName");
QTest::addColumn<QVector<Archive::Entry*>>("files");
QTest::addColumn<bool>("preservePaths");
QTest::addColumn<QString>("password");
QTest::addColumn<QStringList>("expectedArgs");
QTest::newRow("preserve paths, encrypted")
<< QStringLiteral("/tmp/foo.arj")
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QStringLiteral("1234")
<< QStringList {
QStringLiteral("x"),
QStringLiteral("-p1"),
QStringLiteral("-jyc"),
QStringLiteral("-g1234"),
QStringLiteral("/tmp/foo.arj"),
QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("preserve paths, unencrypted")
<< QStringLiteral("/tmp/foo.arj")
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< true << QString()
<< QStringList {
QStringLiteral("x"),
QStringLiteral("-p1"),
QStringLiteral("-jyc"),
QStringLiteral("/tmp/foo.arj"),
QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, encrypted")
<< QStringLiteral("/tmp/foo.arj")
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QStringLiteral("1234")
<< QStringList {
QStringLiteral("e"),
QStringLiteral("-g1234"),
QStringLiteral("/tmp/foo.arj"),
QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
QTest::newRow("without paths, unencrypted")
<< QStringLiteral("/tmp/foo.arj")
<< QVector<Archive::Entry*> {
new Archive::Entry(this, QStringLiteral("aDir/textfile2.txt"), QStringLiteral("aDir")),
new Archive::Entry(this, QStringLiteral("c.txt"), QString())
}
<< false << QString()
<< QStringList {
QStringLiteral("e"),
QStringLiteral("/tmp/foo.arj"),
QStringLiteral("aDir/textfile2.txt"),
QStringLiteral("c.txt"),
};
}
void CliArjTest::testExtractArgs()
{
if (!m_plugin->isValid()) {
QSKIP("cliarj plugin not available. Skipping test.", SkipSingle);
}
QFETCH(QString, archiveName);
CliPlugin *plugin = new CliPlugin(this, {QVariant(archiveName),
QVariant::fromValue(m_plugin->metaData())});
QVERIFY(plugin);
QFETCH(QVector<Archive::Entry*>, files);
QStringList filesList;
for (const Archive::Entry *e : std::as_const(files)) {
filesList << e->fullPath(NoTrailingSlash);
}
QFETCH(bool, preservePaths);
QFETCH(QString, password);
const auto replacedArgs = plugin->cliProperties()->extractArgs(archiveName, filesList, preservePaths, password);
QFETCH(QStringList, expectedArgs);
QCOMPARE(replacedArgs, expectedArgs);
plugin->deleteLater();
}
/*
SPDX-FileCopyrightText: 2022 Ilya Pominov <ipominov@astralinux.ru>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef CLIARJTEST_H
#define CLIARJTEST_H
#include "pluginmanager.h"
using namespace Kerfuffle;
class CliArjTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testListArgs_data();
void testListArgs();
void testAddArgs_data();
void testAddArgs();
void testExtractArgs_data();
void testExtractArgs();
private:
PluginManager m_pluginManger;
Plugin *m_plugin;
};
#endif
......@@ -929,6 +929,15 @@ bool CliInterface::handleLine(const QString& line)
}
}
if (m_operationMode == Move && isNewMovedFileNamesMsg(line)) {
QString fNames;
for (auto entry : qAsConst(m_newMovedFiles)) {
fNames += QStringLiteral("%1\n").arg(entry->fullPath(NoTrailingSlash));
}
writeToProcess(fNames.toLocal8Bit());
return true;
}
return true;
}
......@@ -1126,4 +1135,10 @@ bool CliInterface::isFileExistsFileName(const QString &line)
return false;
}
bool CliInterface::isNewMovedFileNamesMsg(const QString &line)
{
Q_UNUSED(line);
return false;
}
}
......@@ -55,6 +55,7 @@ public:
virtual bool isDiskFullMsg(const QString &line);
virtual bool isFileExistsMsg(const QString &line);
virtual bool isFileExistsFileName(const QString &line);
virtual bool isNewMovedFileNamesMsg(const QString &line);
bool doKill() override;
/**
......
......@@ -127,10 +127,16 @@ QStringList CliProperties::moveArgs(const QString &archive, const QVector<Archiv
args << archive;
if (entries.count() > 1) {
for (const Archive::Entry *file : entries) {
args << file->fullPath(NoTrailingSlash) << destination->fullPath() + file->name();
args << file->fullPath(NoTrailingSlash);
if (destination) {
args << destination->fullPath() + file->name();
}
}
} else {
args << entries.at(0)->fullPath(NoTrailingSlash) << destination->fullPath(NoTrailingSlash);
args << entries.at(0)->fullPath(NoTrailingSlash);
if (destination) {
args << destination->fullPath(NoTrailingSlash);
}
}
args.removeAll(QString());
......
......@@ -14,6 +14,7 @@ add_subdirectory(cliunarchiverplugin)
if(LibZip_FOUND)
add_subdirectory(libzipplugin)
endif(LibZip_FOUND)
add_subdirectory( cliarjplugin )
set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}" PARENT_SCOPE)
set(INSTALLED_KERFUFFLE_PLUGINS "${INSTALLED_KERFUFFLE_PLUGINS}" PARENT_SCOPE)
########### next target ###############
set(SUPPORTED_CLIARJ_MIMETYPES "application/x-arj;application/arj;")
set(kerfuffle_cliarj_SRCS cliplugin.cpp)
ecm_qt_declare_logging_category(kerfuffle_cliarj_SRCS
HEADER ark_debug.h
IDENTIFIER ARK
CATEGORY_NAME ark.cliarj DESCRIPTION "Ark Arj" EXPORT ARK)
# NOTE: the first double-quotes of the first mime and the last
# double-quotes of the last mime must NOT be escaped.
set(SUPPORTED_MIMETYPES
"application/x-arj\",
\"application/arj")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_cliarj.json.cmake
${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_cliarj.json)
kerfuffle_add_plugin(kerfuffle_cliarj ${kerfuffle_cliarj_SRCS})
set(SUPPORTED_ARK_MIMETYPES "${SUPPORTED_ARK_MIMETYPES}${SUPPORTED_CLIARJ_MIMETYPES}" PARENT_SCOPE)
set(INSTALLED_KERFUFFLE_PLUGINS "${INSTALLED_KERFUFFLE_PLUGINS}kerfuffle_cliarj;" PARENT_SCOPE)
find_program(ARJ arj)
if(ARJ)
message(STATUS "Found arj executable: ${ARJ}")
else()
message(WARNING "Could not find the arj executable. Ark won't be able to handle the arj archive format.")
endif()
/*
SPDX-FileCopyrightText: 2022 Ilya Pominov <ipominov@astralinux.ru>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cliplugin.h"
#include "ark_debug.h"
#include <QDateTime>
#include <QFileInfo>
#include <KLocalizedString>
#include <KPluginFactory>
using namespace Kerfuffle;
K_PLUGIN_CLASS_WITH_JSON(CliPlugin, "kerfuffle_cliarj.json")
struct ArjFileEntry
{
enum EncryptedMethod {
// https://sourceforge.net/p/arj/git/ci/master/tree/defines.h#l104
EncryptedMethodArjOld = 0,
EncryptedMethodArjStd = 1,
EncryptedMethodGost256 = 2,
EncryptedMethodGost256L = 3,
EncryptedMethodGost40bit = 4,
EncryptedMethodUnkown = 16
};
QString fileName() const {
return QFileInfo(m_path).fileName();
}
bool isExecutable() const {
return m_attributes.contains(QLatin1Char('x'));
}
int m_currentEntryNumber = 0;
QString m_path;
QStringList m_comments;
bool m_commentsEnd = false;
int m_version = 0;
qulonglong m_origSize = 0;
qulonglong m_compressedSize = 0;
double m_ratio = 0.;
QDateTime m_timeStamp;
QString m_attributes;
bool m_encrypted = false;
EncryptedMethod m_encryptedMethod = EncryptedMethodUnkown;
};
CliPlugin::CliPlugin(QObject *parent, const QVariantList & args)
: CliInterface(parent, args)
{
qCDebug(ARK) << "Loaded cli_arj plugin";
setupCliProperties();
}
CliPlugin::~CliPlugin() = default;
bool CliPlugin::addFiles(const QVector<Kerfuffle::Archive::Entry*> &files, const Kerfuffle::Archive::Entry *destination, const Kerfuffle::CompressionOptions& options, uint numberOfEntriesToAdd)
{
auto opt = options;
if (opt.compressionMethod() == QStringLiteral("Standard")) {
opt.setCompressionMethod(QString());
} else if (opt.compressionMethod() == QStringLiteral("GOST 40-bit")) {
opt.setCompressionMethod(QStringLiteral("!"));
}
return CliInterface::addFiles(files, destination, opt, numberOfEntriesToAdd);
}
bool CliPlugin::moveFiles(const QVector<Archive::Entry *> &files, Archive::Entry *destination, const CompressionOptions &options)
{
Q_UNUSED(options);
m_operationMode = Move;
QVector<Archive::Entry*> withoutChildren = entriesWithoutChildren(files);
m_renamedFiles = files;
setNewMovedFiles(files, destination, withoutChildren.count());
QStringList args = cliProperties()->moveArgs(filename(),
withoutChildren,
nullptr,
password());
return runProcess(cliProperties()->property("moveProgram").toString(), args);
}
void CliPlugin::resetParsing()
{
m_comment.clear();
m_numberOfVolumes = 0;
m_parseState = ParseStateTitle;
m_remainingIgnoreLines = 0;
m_headerComment.clear();
m_currentParsedFile.reset(new ArjFileEntry());
m_testPassed = true;
m_renamedFiles.clear();
}
bool CliPlugin::readListLine(const QString& line)
{
auto res = readLine(line);
if (m_parseState == ParseStateEntryTotal && res) {
m_comment = m_headerComment.join(QLatin1Char('\n'));
}
return res;
}
bool CliPlugin::readExtractLine(const QString &line)
{
Q_UNUSED(line);
return true;
}
bool CliPlugin::isFileExistsMsg(const QString &line)
{
return line.contains(QStringLiteral("is same or newer, Overwrite?"));
}
bool CliPlugin::isFileExistsFileName(const QString &line)
{
return line.contains(QStringLiteral("is same or newer, Overwrite?"));
}
bool CliPlugin::isNewMovedFileNamesMsg(const QString &line)
{
return line.startsWith(QStringLiteral("Current filename:"));
}
bool CliPlugin::handleLine(const QString& line)