[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