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()
