[multimedia/kid3] /: kid3-cli: Command 'execute' to run QML scripts
Urs Fleisch
null at kde.org
Fri Oct 29 19:41:17 BST 2021
Git commit febdd5710abc440b746a89d5dceb4d73db2164d0 by Urs Fleisch.
Committed on 29/10/2021 at 15:57.
Pushed by ufleisch into branch 'master'.
kid3-cli: Command 'execute' to run QML scripts
M +29 -0 doc/en/index.docbook
M +60 -1 src/app/cli/clicommand.cpp
M +23 -0 src/app/cli/clicommand.h
M +2 -1 src/app/cli/kid3cli.cpp
M +9 -0 src/app/kde/kdeplatformtools.cpp
M +6 -0 src/app/kde/kdeplatformtools.h
M +9 -0 src/app/qt/platformtools.cpp
M +6 -0 src/app/qt/platformtools.h
M +11 -3 src/core/model/externalprocess.cpp
M +7 -0 src/core/model/externalprocess.h
M +2 -2 src/core/model/iusercommandprocessor.h
M +9 -0 src/core/model/kid3application.cpp
M +6 -0 src/core/model/kid3application.h
M +9 -0 src/core/utils/icoreplatformtools.cpp
M +6 -0 src/core/utils/icoreplatformtools.h
M +2 -0 src/plugins/qmlcommand/qmlcommandplugin.cpp
M +6 -0 src/plugins/qmlcommand/qmlcommandplugin.h
M +3 -3 src/qml/script/ExportCsv.qml
M +4 -4 src/qml/script/ImportCsv.qml
https://invent.kde.org/multimedia/kid3/commit/febdd5710abc440b746a89d5dceb4d73db2164d0
diff --git a/doc/en/index.docbook b/doc/en/index.docbook
index 66381602..4b32e95a 100644
--- a/doc/en/index.docbook
+++ b/doc/en/index.docbook
@@ -3251,6 +3251,35 @@ list can be copied with the new element appended.
</para>
</sect2>
+<sect2 id="cli-execute">
+<title>Execute program or QML script</title>
+<cmdsynopsis>
+<command>execute</command>
+<arg choice="opt">@qml</arg>
+<arg choice="req"><replaceable>FILE</replaceable></arg>
+<arg choice="opt"><replaceable>ARGS</replaceable></arg>
+</cmdsynopsis>
+<para>Execute a QML script or an executable.
+</para>
+<para>
+Without <option>@qml</option> a program is executed with arguments.
+When <option>@qml</option> is given as the first argument, the following
+arguments are the QML script and its arguments. For example, the tags
+of a folder can be exported to the file <filename>export.csv</filename> with
+the following command.
+</para>
+<screen width="65"><userinput>kid3-cli -c "execute @qml
+/usr/share/kid3/qml/script/ExportCsv.qml export.csv"
+/path/to/folder/</userinput></screen>
+<para>
+Here <option>export.csv</option> is the argument for the
+<filename>ExportCsv.qml</filename> script, whereas
+<option>/path/to/folder/</option> is the
+<option><replaceable>FILE</replaceable></option> argument for
+<command>kid3-cli</command>.
+</para>
+</sect2>
+
</sect1>
<sect1 id="kid3-cli-examples">
diff --git a/src/app/cli/clicommand.cpp b/src/app/cli/clicommand.cpp
index e89ed304..b4e307e7 100644
--- a/src/app/cli/clicommand.cpp
+++ b/src/app/cli/clicommand.cpp
@@ -1290,7 +1290,7 @@ void RemoveCommand::startCommand()
ConfigCommand::ConfigCommand(Kid3Cli* processor)
: CliCommand(processor, QLatin1String("config"), tr("Configure Kid3"),
- QLatin1String("[S]\nS = Group.Option Value"))
+ QLatin1String("[S]\nS = ") + tr("Group.Option Value"))
{
}
@@ -1415,3 +1415,62 @@ void ConfigCommand::startCommand()
cli()->writeResult(configNames);
}
}
+
+
+ExecuteCommand::ExecuteCommand(Kid3Cli* processor)
+ : CliCommand(processor, QLatin1String("execute"), tr("Execute command"),
+ QLatin1String("S\nS = [@qml] ") + tr("Executable [arguments]"))
+{
+ setTimeout(-1);
+}
+
+void ExecuteCommand::setCaption(const QString& title)
+{
+ Q_UNUSED(title)
+}
+
+void ExecuteCommand::append(const QString& text)
+{
+ cli()->writeLine(text);
+}
+
+void ExecuteCommand::scrollToBottom()
+{
+}
+
+void ExecuteCommand::startCommand()
+{
+ if (args().size() > 1) {
+ QString command = args().at(1);
+ if (!m_process) {
+ m_process.reset(new ExternalProcess(cli()->app(), this));
+ connectResultSignal();
+ }
+ m_process->setOutputViewer(this);
+ if (!m_process->launchCommand(command, args().mid(1), true)) {
+ setError(tr("Could not execute ") + args().mid(1).join(QLatin1String(" ")));
+ terminate();
+ }
+ } else {
+ showUsage();
+ terminate();
+ }
+}
+
+void ExecuteCommand::connectResultSignal()
+{
+ if (m_process) {
+ connect(m_process.data(), &ExternalProcess::finished,
+ this, &ExecuteCommand::terminate, Qt::UniqueConnection);
+ }
+}
+
+void ExecuteCommand::disconnectResultSignal()
+{
+ if (m_process) {
+ disconnect(m_process.data(), &ExternalProcess::finished,
+ this, &ExecuteCommand::terminate);
+ // Avoid segfault when m_process is deleted at program termination
+ m_process.reset();
+ }
+}
diff --git a/src/app/cli/clicommand.h b/src/app/cli/clicommand.h
index 68383756..d7398aa8 100644
--- a/src/app/cli/clicommand.h
+++ b/src/app/cli/clicommand.h
@@ -27,7 +27,9 @@
#pragma once
#include <QObject>
+#include <QScopedPointer>
#include "frame.h"
+#include "externalprocess.h"
class QModelIndex;
class Kid3Cli;
@@ -604,3 +606,24 @@ public:
protected:
virtual void startCommand() override;
};
+
+/** Execute command. */
+class ExecuteCommand : public CliCommand,
+ public ExternalProcess::IOutputViewer {
+ Q_OBJECT
+public:
+ /** Constructor. */
+ explicit ExecuteCommand(Kid3Cli* processor);
+
+ virtual void setCaption(const QString& title) override;
+ virtual void append(const QString& text) override;
+ virtual void scrollToBottom() override;
+
+protected:
+ virtual void startCommand() override;
+ virtual void connectResultSignal() override;
+ virtual void disconnectResultSignal() override;
+
+private:
+ QScopedPointer<ExternalProcess> m_process;
+};
diff --git a/src/app/cli/kid3cli.cpp b/src/app/cli/kid3cli.cpp
index 334836b8..6fc00df0 100644
--- a/src/app/cli/kid3cli.cpp
+++ b/src/app/cli/kid3cli.cpp
@@ -219,7 +219,8 @@ Kid3Cli::Kid3Cli(Kid3Application* app,
<< new CopyCommand(this)
<< new PasteCommand(this)
<< new RemoveCommand(this)
- << new ConfigCommand(this);
+ << new ConfigCommand(this)
+ << new ExecuteCommand(this);
connect(m_app, &Kid3Application::fileSelectionUpdateRequested,
this, &Kid3Cli::updateSelectedFiles);
connect(m_app, &Kid3Application::selectedFilesUpdated,
diff --git a/src/app/kde/kdeplatformtools.cpp b/src/app/kde/kdeplatformtools.cpp
index 62e0b6cc..5efa6faa 100644
--- a/src/app/kde/kdeplatformtools.cpp
+++ b/src/app/kde/kdeplatformtools.cpp
@@ -295,6 +295,15 @@ QString KdePlatformTools::getExistingDirectory(QWidget* parent,
: QFileDialog::ShowDirsOnly);
}
+/**
+ * Check if platform has a graphical user interface.
+ * @return true if platform has GUI.
+ */
+bool KdePlatformTools::hasGui() const
+{
+ return true;
+}
+
/**
* Display warning dialog.
* @param parent parent widget
diff --git a/src/app/kde/kdeplatformtools.h b/src/app/kde/kdeplatformtools.h
index 973cb0f8..4eaf3c73 100644
--- a/src/app/kde/kdeplatformtools.h
+++ b/src/app/kde/kdeplatformtools.h
@@ -198,6 +198,12 @@ public:
virtual QString getExistingDirectory(QWidget* parent,
const QString& caption, const QString& startDir) override;
+ /**
+ * Check if platform has a graphical user interface.
+ * @return true if platform has GUI.
+ */
+ virtual bool hasGui() const override;
+
/**
* Display warning dialog.
* @param parent parent widget
diff --git a/src/app/qt/platformtools.cpp b/src/app/qt/platformtools.cpp
index 05e8ea3f..5865b5ae 100644
--- a/src/app/qt/platformtools.cpp
+++ b/src/app/qt/platformtools.cpp
@@ -285,6 +285,15 @@ QString PlatformTools::getExistingDirectory(QWidget* parent,
: QFileDialog::ShowDirsOnly);
}
+/**
+ * Check if platform has a graphical user interface.
+ * @return true if platform has GUI.
+ */
+bool PlatformTools::hasGui() const
+{
+ return true;
+}
+
/**
* Display warning dialog.
* @param parent parent widget
diff --git a/src/app/qt/platformtools.h b/src/app/qt/platformtools.h
index 0349f234..045a8c33 100644
--- a/src/app/qt/platformtools.h
+++ b/src/app/qt/platformtools.h
@@ -201,6 +201,12 @@ public:
virtual QString getExistingDirectory(QWidget* parent,
const QString& caption, const QString& startDir) override;
+ /**
+ * Check if platform has a graphical user interface.
+ * @return true if platform has GUI.
+ */
+ virtual bool hasGui() const override;
+
/**
* Display warning dialog.
* @param parent parent widget
diff --git a/src/core/model/externalprocess.cpp b/src/core/model/externalprocess.cpp
index 433195b4..c617bbf3 100644
--- a/src/core/model/externalprocess.cpp
+++ b/src/core/model/externalprocess.cpp
@@ -90,6 +90,9 @@ bool ExternalProcess::launchCommand(const QString& name, const QStringList& args
if (m_process->state() != QProcess::NotRunning) {
m_process = new QProcess(parent());
}
+ connect(m_process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
+ &QProcess::finished),
+ this, &ExternalProcess::finished, Qt::UniqueConnection);
if (showOutput && m_outputViewer) {
m_process->setProcessChannelMode(QProcess::MergedChannels);
@@ -108,9 +111,14 @@ bool ExternalProcess::launchCommand(const QString& name, const QStringList& args
program = program.mid(1);
const auto userCommandProcessors = m_app->getUserCommandProcessors();
for (IUserCommandProcessor* userCommandProcessor : userCommandProcessors) {
- if (userCommandProcessor->userCommandKeys().contains(program) &&
- userCommandProcessor->startUserCommand(program, arguments, showOutput))
- return true;
+ if (userCommandProcessor->userCommandKeys().contains(program)) {
+ connect(userCommandProcessor->qobject(), SIGNAL(finished(int)),
+ this, SIGNAL(finished(int)), Qt::UniqueConnection);
+ if (userCommandProcessor->startUserCommand(program, arguments,
+ showOutput)) {
+ return true;
+ }
+ }
}
}
m_process->start(program, arguments);
diff --git a/src/core/model/externalprocess.h b/src/core/model/externalprocess.h
index a0e67479..f575d88e 100644
--- a/src/core/model/externalprocess.h
+++ b/src/core/model/externalprocess.h
@@ -103,6 +103,13 @@ public:
bool launchCommand(const QString& name, const QStringList& args,
bool showOutput = false);
+signals:
+ /**
+ * Emitted when the process finishes.
+ * @param exitCode exit code of process
+ */
+ void finished(int exitCode);
+
private slots:
/**
* Read data from standard output and display it in the output viewer.
diff --git a/src/core/model/iusercommandprocessor.h b/src/core/model/iusercommandprocessor.h
index a8d6155a..588eceea 100644
--- a/src/core/model/iusercommandprocessor.h
+++ b/src/core/model/iusercommandprocessor.h
@@ -71,14 +71,14 @@ public:
*
* @remarks If @a showOutput is true, command output is emitted using a signal
* "void commandOutput(QString)". Objects implementing this interface have to
- * be QObjects providing such a signal.
+ * be QObjects providing such a signal and a signal "void finished(int)".
* @see qobject()
*/
virtual bool startUserCommand(
const QString& key, const QStringList& arguments, bool showOutput) = 0;
/**
- * Return object which emits commandOutput() signal.
+ * Return object which emits commandOutput() and finished() signals.
*
* @return object which emits signals.
*/
diff --git a/src/core/model/kid3application.cpp b/src/core/model/kid3application.cpp
index d06ffc4d..d8eb6571 100644
--- a/src/core/model/kid3application.cpp
+++ b/src/core/model/kid3application.cpp
@@ -3990,3 +3990,12 @@ QString Kid3Application::selectDirName(const QString& caption,
{
return m_platformTools->getExistingDirectory(nullptr, caption, dir);
}
+
+/**
+ * Check if application is running with a graphical user interface.
+ * @return true if application has a GUI.
+ */
+bool Kid3Application::hasGui() const
+{
+ return m_platformTools->hasGui();
+}
diff --git a/src/core/model/kid3application.h b/src/core/model/kid3application.h
index 9e5372ca..c3c59264 100644
--- a/src/core/model/kid3application.h
+++ b/src/core/model/kid3application.h
@@ -889,6 +889,12 @@ public:
Q_INVOKABLE QString selectDirName(
const QString& caption = QString(), const QString& dir = QString());
+ /**
+ * Check if application is running with a graphical user interface.
+ * @return true if application has a GUI.
+ */
+ Q_INVOKABLE bool hasGui() const;
+
/**
* Notify the tagged file factories about the changed configuration.
*/
diff --git a/src/core/utils/icoreplatformtools.cpp b/src/core/utils/icoreplatformtools.cpp
index 53cdabdb..07cdb002 100644
--- a/src/core/utils/icoreplatformtools.cpp
+++ b/src/core/utils/icoreplatformtools.cpp
@@ -96,6 +96,15 @@ QString ICorePlatformTools::getExistingDirectory(QWidget* parent,
return QString();
}
+/**
+ * Check if platform has a graphical user interface.
+ * @return true if platform has GUI.
+ */
+bool ICorePlatformTools::hasGui() const
+{
+ return false;
+}
+
/**
* Construct a name filter string suitable for file dialogs.
* This function can be used to implement fileDialogNameFilter()
diff --git a/src/core/utils/icoreplatformtools.h b/src/core/utils/icoreplatformtools.h
index 9cfb61d4..8630f2dd 100644
--- a/src/core/utils/icoreplatformtools.h
+++ b/src/core/utils/icoreplatformtools.h
@@ -148,6 +148,12 @@ public:
virtual QString getExistingDirectory(QWidget* parent,
const QString& caption, const QString& startDir);
+ /**
+ * Check if platform has a graphical user interface.
+ * @return true if platform has GUI.
+ */
+ virtual bool hasGui() const;
+
protected:
/**
* Construct a name filter string suitable for file dialogs.
diff --git a/src/plugins/qmlcommand/qmlcommandplugin.cpp b/src/plugins/qmlcommand/qmlcommandplugin.cpp
index 326ecbd3..e782a5cd 100644
--- a/src/plugins/qmlcommand/qmlcommandplugin.cpp
+++ b/src/plugins/qmlcommand/qmlcommandplugin.cpp
@@ -150,6 +150,7 @@ bool QmlCommandPlugin::startUserCommand(
}
}
m_qmlEngine->clearComponentCache();
+ onEngineFinished();
}
return true;
}
@@ -256,6 +257,7 @@ void QmlCommandPlugin::onEngineFinished()
qInstallMessageHandler(nullptr);
s_messageHandlerInstance = nullptr;
}
+ QTimer::singleShot(0, this, [this]() { emit finished(0); });
}
/**
diff --git a/src/plugins/qmlcommand/qmlcommandplugin.h b/src/plugins/qmlcommand/qmlcommandplugin.h
index acdb4808..185a2e85 100644
--- a/src/plugins/qmlcommand/qmlcommandplugin.h
+++ b/src/plugins/qmlcommand/qmlcommandplugin.h
@@ -99,6 +99,12 @@ signals:
*/
void commandOutput(const QString& msg);
+ /**
+ * Emitted when the command finishes.
+ * @param exitCode exit code of command
+ */
+ void finished(int exitCode);
+
private slots:
void onEngineError(const QList<QQmlError>& errors);
void onQmlViewClosing();
diff --git a/src/qml/script/ExportCsv.qml b/src/qml/script/ExportCsv.qml
index be623e5f..c1a3186e 100644
--- a/src/qml/script/ExportCsv.qml
+++ b/src/qml/script/ExportCsv.qml
@@ -1,12 +1,12 @@
/**
- * \file ExportAll.qml
+ * \file ExportCsv.qml
* Export all tags of all files to a CSV file.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 06 Mar 2015
*
- * Copyright (C) 2015 Urs Fleisch
+ * Copyright (C) 2015-2021 Urs Fleisch
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -128,7 +128,7 @@ Kid3Script {
doWork()
}
- if (!isStandalone()) {
+ if (!isStandalone() && app.hasGui()) {
console.log("Expanding file list")
app.expandFileListFinished.connect(startWork)
app.requestExpandFileList()
diff --git a/src/qml/script/ImportCsv.qml b/src/qml/script/ImportCsv.qml
index ec8c3c62..4852b48e 100644
--- a/src/qml/script/ImportCsv.qml
+++ b/src/qml/script/ImportCsv.qml
@@ -1,12 +1,12 @@
/**
- * \file ExportAll.qml
- * Export all tags of all files to a CSV file.
+ * \file ImportCsv.qml
+ * Import all tags of all files from a CSV file.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 06 Mar 2015
*
- * Copyright (C) 2015 Urs Fleisch
+ * Copyright (C) 2015-2021 Urs Fleisch
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -160,7 +160,7 @@ Kid3Script {
files = undefined
}
- if (!isStandalone()) {
+ if (!isStandalone() && app.hasGui()) {
console.log("Expanding file list")
app.expandFileListFinished.connect(startWork)
app.requestExpandFileList()
More information about the kde-doc-english
mailing list