diff --git a/autotests/commandlauncherjobtest.h b/autotests/commandlauncherjobtest.h --- a/autotests/commandlauncherjobtest.h +++ b/autotests/commandlauncherjobtest.h @@ -32,8 +32,14 @@ private Q_SLOTS: void initTestCase(); - void startProcess_data(); - void startProcess(); + void startProcessAsCommand_data(); + void startProcessAsCommand(); + + void startProcessWithArgs_data(); + void startProcessWithArgs(); + + void startProcessWithSpacesInExecutablePath_data(); + void startProcessWithSpacesInExecutablePath(); void doesNotFailOnNonExistingExecutable(); void shouldDoNothingOnEmptyCommand(); diff --git a/autotests/commandlauncherjobtest.cpp b/autotests/commandlauncherjobtest.cpp --- a/autotests/commandlauncherjobtest.cpp +++ b/autotests/commandlauncherjobtest.cpp @@ -44,18 +44,18 @@ { QFile srcFile(path); QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString())); - srcFile.write("Hello world\n"); + QVERIFY(srcFile.write("Hello world\n") > 0); } -void CommandLauncherJobTest::startProcess_data() +void CommandLauncherJobTest::startProcessAsCommand_data() { QTest::addColumn("useExec"); QTest::newRow("exec") << true; QTest::newRow("waitForStarted") << false; } -void CommandLauncherJobTest::startProcess() +void CommandLauncherJobTest::startProcessAsCommand() { QFETCH(bool, useExec); @@ -89,8 +89,120 @@ QVERIFY(pid != 0); const QString dest = srcDir + "/destfile"; QTRY_VERIFY2(QFile::exists(dest), qPrintable(dest)); - QVERIFY(QFile::exists(srcDir + "/srcfile")); - QVERIFY(QFile::remove(dest)); // cleanup + QVERIFY(QFile::remove(srcFile)); // cleanup + QVERIFY(QFile::remove(dest)); + + // Just to make sure + QTRY_COMPARE(KProcessRunner::instanceCount(), 0); +} + +void CommandLauncherJobTest::startProcessWithArgs_data() +{ + QTest::addColumn("srcName"); + QTest::addColumn("destName"); + + QTest::newRow("path without spaces") << QStringLiteral("srcfile") << QStringLiteral("destfile"); + QTest::newRow("path with spaces") << QStringLiteral("Source File") << QStringLiteral("Destination File"); +} + +void CommandLauncherJobTest::startProcessWithArgs() +{ + QFETCH(QString, srcName); + QFETCH(QString, destName); + + QTemporaryDir tempDir; + const QString srcDir = tempDir.path(); + const QString srcPath = srcDir + '/' + srcName; + const QString destPath = srcDir + '/' + destName; + + createSrcFile(srcPath); + QVERIFY(QFileInfo::exists(srcPath)); + +#ifdef Q_OS_WIN + const QString executable = "copy.exe"; +#else + const QString executable = "cp"; +#endif + + auto *job = new KIO::CommandLauncherJob(executable, { + srcPath, destName + }, this); + job->setWorkingDirectory(srcDir); + + job->start(); + QVERIFY(job->waitForStarted()); + + const qint64 pid = job->pid(); + + // Then the service should be executed (which copies the source file to "dest") + QVERIFY(pid != 0); + QTRY_VERIFY2(QFileInfo::exists(destPath), qPrintable(destPath)); + QVERIFY(QFile::remove(srcPath)); + QVERIFY(QFile::remove(destPath)); // cleanup + + // Just to make sure + QTRY_COMPARE(KProcessRunner::instanceCount(), 0); +} + +void CommandLauncherJobTest::startProcessWithSpacesInExecutablePath_data() +{ + QTest::addColumn("srcName"); + QTest::addColumn("destName"); + + QTest::newRow("path without spaces") << QStringLiteral("srcfile") << QStringLiteral("destfile"); + QTest::newRow("path with spaces") << QStringLiteral("Source File") << QStringLiteral("Destination File"); +} + +void CommandLauncherJobTest::startProcessWithSpacesInExecutablePath() +{ + QFETCH(QString, srcName); + QFETCH(QString, destName); + + QTemporaryDir tempDir; + + const QString srcDir = tempDir.filePath("folder with spaces"); + QVERIFY(QDir().mkpath(srcDir)); + + const QString srcPath = srcDir + '/' + srcName; + const QString destPath = srcDir + '/' + destName; + + createSrcFile(srcPath); + QVERIFY(QFileInfo::exists(srcPath)); + + // Copy the executable into the folder with spaces in its path +#ifdef Q_OS_WIN + const QString executableName = "copy"; // QStandardPaths appends extension as necessary +#else + const QString executableName = "cp"; +#endif + + const QString executablePath = QStandardPaths::findExecutable(executableName); + QVERIFY(!executablePath.isEmpty()); + QFileInfo fi(executablePath); + // Needed since it could be .exe or .bat on Windows + const QString executableFileName = fi.fileName(); + + const QString executable = srcDir + '/' + executableFileName; + QVERIFY(QFile::copy(executablePath, executable)); + + auto *job = new KIO::CommandLauncherJob(executable, { + srcPath, destName + }, this); + job->setWorkingDirectory(srcDir); + + job->start(); + QVERIFY(job->waitForStarted()); + + const qint64 pid = job->pid(); + + // Then the service should be executed (which copies the source file to "dest") + QVERIFY(pid != 0); + QTRY_VERIFY2(QFileInfo::exists(destPath), qPrintable(destPath)); + + // cleanup + QVERIFY(QFile::remove(destPath)); + QVERIFY(QFile::remove(srcPath)); + QVERIFY(QFile::remove(executable)); // Just to make sure QTRY_COMPARE(KProcessRunner::instanceCount(), 0); diff --git a/src/gui/commandlauncherjob.h b/src/gui/commandlauncherjob.h --- a/src/gui/commandlauncherjob.h +++ b/src/gui/commandlauncherjob.h @@ -63,6 +63,16 @@ */ explicit CommandLauncherJob(const QString &command, QObject *parent = nullptr); + /** + * @brief Creates a CommandLauncherJob + * @param executable the name of the executable + * @param args the commandline arguments to pass to the executable + * @param parent the parent QObject + * + * Please consider also calling setExecutable and setIcon for better startup notification. + */ + explicit CommandLauncherJob(const QString &executable, const QStringList &args, QObject *parent = nullptr); + /** * Destructor * Note that jobs auto-delete themselves after emitting result diff --git a/src/gui/commandlauncherjob.cpp b/src/gui/commandlauncherjob.cpp --- a/src/gui/commandlauncherjob.cpp +++ b/src/gui/commandlauncherjob.cpp @@ -27,9 +27,6 @@ class KIO::CommandLauncherJobPrivate { public: - explicit CommandLauncherJobPrivate(const QString &command) - : m_command(command) {} - QString m_command; QString m_desktopName; QString m_executable; @@ -41,8 +38,16 @@ }; KIO::CommandLauncherJob::CommandLauncherJob(const QString &command, QObject *parent) - : KJob(parent), d(new CommandLauncherJobPrivate(command)) + : KJob(parent), d(new CommandLauncherJobPrivate()) { + d->m_command = command; +} + +KIO::CommandLauncherJob::CommandLauncherJob(const QString &executable, const QStringList &args, QObject *parent) + : KJob(parent), d(new CommandLauncherJobPrivate()) +{ + d->m_executable = executable; + d->m_command = KShell::quoteArg(executable) + QLatin1Char(' ') + KShell::joinArgs(args); } KIO::CommandLauncherJob::~CommandLauncherJob()