[multimedia/kid3] /: kid3-cli: Command 'config' to query and set configuration options

Urs Fleisch null at kde.org
Thu Dec 3 18:48:54 GMT 2020


Git commit ed81d235d3f0e3aeaa2bbf797c09dc67ba50770b by Urs Fleisch.
Committed on 03/12/2020 at 18:35.
Pushed by ufleisch into branch 'master'.

kid3-cli: Command 'config' to query and set configuration options

M  +24   -0    doc/en/index.docbook
M  +297  -0    src/app/cli/clicommand.cpp
M  +11   -0    src/app/cli/clicommand.h
M  +2    -1    src/app/cli/kid3cli.cpp

https://invent.kde.org/multimedia/kid3/commit/ed81d235d3f0e3aeaa2bbf797c09dc67ba50770b

diff --git a/doc/en/index.docbook b/doc/en/index.docbook
index 92f2acaf..80fefd0f 100644
--- a/doc/en/index.docbook
+++ b/doc/en/index.docbook
@@ -3210,6 +3210,30 @@ command</link>.
 </para>
 </sect2>
 
+<sect2 id="cli-config">
+<title>Configure Kid3</title>
+<cmdsynopsis>
+<command>config</command>
+<arg><replaceable>OPTION</replaceable></arg>
+<arg><replaceable>VALUE</replaceable></arg>
+</cmdsynopsis>
+<para>Query or set a configuration option.
+</para>
+<para>The <replaceable>OPTION</replaceable> consists of a group name and a
+property name separated by a dot. When no <replaceable>OPTION</replaceable> is
+given, all available groups are displayed. If only a group name is given, all
+available properties of the group are displayed. For a given group and property,
+the currently configured value is displayed. To change the setting, the new
+value can be passed as a second argument.
+</para>
+<para>If the value of a setting is a list, all list elements have to be given
+as arguments. This means that to append an element to an existing list of
+elements, all existing elements have to be passed followed by the new element.
+In such a situation, it is easier to use the JSON mode, where the current
+list can be copied with the new element appended.
+</para>
+</sect2>
+
 </sect1>
 
 <sect1 id="kid3-cli-examples">
diff --git a/src/app/cli/clicommand.cpp b/src/app/cli/clicommand.cpp
index ada52c95..e3e60132 100644
--- a/src/app/cli/clicommand.cpp
+++ b/src/app/cli/clicommand.cpp
@@ -25,11 +25,13 @@
  */
 
 #include "clicommand.h"
+#include <functional>
 #include <QStringList>
 #include <QStringBuilder>
 #include <QTimer>
 #include <QDir>
 #include <QItemSelectionModel>
+#include <QMetaProperty>
 #include "kid3cli.h"
 #include "kid3application.h"
 #include "fileproxymodel.h"
@@ -41,6 +43,11 @@
 #include "fileconfig.h"
 #include "rendirconfig.h"
 #include "batchimportconfig.h"
+#include "formatconfig.h"
+#include "networkconfig.h"
+#include "numbertracksconfig.h"
+#include "playlistconfig.h"
+#include "tagconfig.h"
 #include "batchimporter.h"
 #include "downloadclient.h"
 #include "dirrenamer.h"
@@ -50,6 +57,206 @@ namespace {
 /** Default command timeout in milliseconds. */
 const int DEFAULT_TIMEOUT_MS = 3000;
 
+/**
+ * Available names for groups in config command.
+ * If this list is modified, adapt also the cfgFuncs in getConfig().
+ */
+const QStringList configNames{
+  QLatin1String("BatchImport"),
+  QLatin1String("Export"),
+  QLatin1String("File"),
+  QLatin1String("FilenameFormat"),
+  QLatin1String("Filter"),
+  QLatin1String("Import"),
+  QLatin1String("Network"),
+  QLatin1String("NumberTracks"),
+  QLatin1String("Playlist"),
+  QLatin1String("RenameFolder"),
+  QLatin1String("Tag"),
+  QLatin1String("TagFormat")
+};
+
+/** Properties which shall not be displayed as config options. */
+const QStringList excludedConfigPropertyNames{
+  QLatin1String("objectName"),
+  QLatin1String("windowGeometry"),
+  QLatin1String("exportWindowGeometry"),
+  QLatin1String("importServer"),
+  QLatin1String("importVisibleColumns"),
+  QLatin1String("importWindowGeometry"),
+  QLatin1String("browseCoverArtWindowGeometry"),
+  QLatin1String("quickAccessFrames"),
+  QLatin1String("quickAccessFrameOrder"),
+  QLatin1String("taggedFileFeatures")
+};
+
+/**
+ * Get a configuration object for a given group name.
+ * @param name group name
+ * @return QObject with configuration options as properties.
+ */
+GeneralConfig* getConfig(const QString& name)
+{
+  int idx = configNames.indexOf(name);
+  if (idx == -1) {
+    return nullptr;
+  }
+
+  // Change this list together with configNames.
+  static const std::function<GeneralConfig*(void)> cfgFuncs[] = {
+    []() { return &BatchImportConfig::instance(); },
+    []() { return &ExportConfig::instance(); },
+    []() { return &FileConfig::instance(); },
+    []() { return &FilenameFormatConfig::instance(); },
+    []() { return &FilterConfig::instance(); },
+    []() { return &ImportConfig::instance(); },
+    []() { return &NetworkConfig::instance(); },
+    []() { return &NumberTracksConfig::instance(); },
+    []() { return &PlaylistConfig::instance(); },
+    []() { return &RenDirConfig::instance(); },
+    []() { return &TagConfig::instance(); },
+    []() { return &TagFormatConfig::instance(); }
+  };
+  return cfgFuncs[idx]();
+}
+
+/**
+ * Convert an integer value to the corresponding enum name string.
+ * @param group config group
+ * @param option config option
+ * @param value enum value as integer
+ * @return enum value as string, original int value if invalid.
+ */
+QVariant configIntToEnumName(const QString& group, const QString& option,
+                             const QVariant& value)
+{
+  const int enumVal = value.toInt();
+  if (option == QLatin1String("importDest") ||
+      option == QLatin1String("exportSource") ||
+      option == QLatin1String("numberTracksDestination")) {
+    QString tagMaskStr;
+    for (Frame::TagNumber tagNr :
+         Frame::tagNumbersFromMask(Frame::tagVersionCast(enumVal))) {
+      tagMaskStr += Frame::tagNumberToString(tagNr);
+    }
+    return tagMaskStr;
+  } else if (option == QLatin1String("caseConversion")) {
+    const QMetaObject metaObj = FormatConfig::staticMetaObject;
+    const char* key = metaObj.enumerator(
+          metaObj.indexOfEnumerator("CaseConversion")).valueToKey(enumVal);
+    if (key) {
+      return QString::fromLatin1(key);
+    }
+  } else if (group == QLatin1String("Playlist") &&
+             option == QLatin1String("location")) {
+    const QMetaObject metaObj = PlaylistConfig::staticMetaObject;
+    const char* key = metaObj.enumerator(
+          metaObj.indexOfEnumerator("PlaylistLocation")).valueToKey(enumVal);
+    if (key) {
+      return QString::fromLatin1(key);
+    }
+  } else if (group == QLatin1String("Playlist") &&
+             option == QLatin1String("format")) {
+    const QMetaObject metaObj = PlaylistConfig::staticMetaObject;
+    const char* key = metaObj.enumerator(
+          metaObj.indexOfEnumerator("PlaylistFormat")).valueToKey(enumVal);
+    if (key) {
+      return QString::fromLatin1(key);
+    }
+  } else if (group == QLatin1String("Tag") &&
+             option == QLatin1String("id3v2Version")) {
+    const QMetaObject metaObj = TagConfig::staticMetaObject;
+    const char* key = metaObj.enumerator(
+          metaObj.indexOfEnumerator("Id3v2Version")).valueToKey(enumVal);
+    if (key) {
+      return QString::fromLatin1(key);
+    }
+  } else if (group == QLatin1String("Tag") &&
+             option == QLatin1String("textEncoding")) {
+    const QMetaObject metaObj = TagConfig::staticMetaObject;
+    const char* key = metaObj.enumerator(
+          metaObj.indexOfEnumerator("TextEncoding")).valueToKey(enumVal);
+    if (key) {
+      return QString::fromLatin1(key);
+    }
+  }
+  return value;
+}
+
+/**
+ * Convert an enum value name to the corresponding integer value.
+ * @param group config group
+ * @param option config option
+ * @param enumName enum value as string
+ * @return enum value as integer, original string value if invalid.
+ */
+QVariant configIntFromEnumName(const QString& group, const QString& option,
+                               const QVariant& value)
+{
+  const QString enumName = value.toString();
+  int val;
+  bool ok;
+  if (option == QLatin1String("importDest") ||
+      option == QLatin1String("exportSource") ||
+      option == QLatin1String("numberTracksDestination")) {
+    val = 0;
+    if (!enumName.isEmpty() && enumName.at(0).isDigit()) {
+      FOR_ALL_TAGS(tagNr) {
+        if (enumName.contains(Frame::tagNumberToString(tagNr))) {
+          val |= Frame::tagVersionFromNumber(tagNr);
+        }
+      }
+      if (val != 0) {
+        return val;
+      }
+    }
+  } else if (option == QLatin1String("caseConversion")) {
+    const QMetaObject metaObj = FormatConfig::staticMetaObject;
+    val = metaObj.enumerator(metaObj.indexOfEnumerator("CaseConversion"))
+        .keyToValue(enumName.toLatin1(), &ok);
+    if (ok) {
+      return val;
+    }
+  } else if (group == QLatin1String("Playlist") &&
+             option == QLatin1String("location")) {
+    const QMetaObject metaObj = PlaylistConfig::staticMetaObject;
+    val = metaObj.enumerator(metaObj.indexOfEnumerator("PlaylistLocation"))
+        .keyToValue(enumName.toLatin1(), &ok);
+    if (ok) {
+      return val;
+    }
+  } else if (group == QLatin1String("Playlist") &&
+             option == QLatin1String("format")) {
+    const QMetaObject metaObj = PlaylistConfig::staticMetaObject;
+    val = metaObj.enumerator(metaObj.indexOfEnumerator("PlaylistFormat"))
+        .keyToValue(enumName.toLatin1(), &ok);
+    if (ok) {
+      return val;
+    }
+  } else if (group == QLatin1String("Tag") &&
+             option == QLatin1String("id3v2Version")) {
+    const QMetaObject metaObj = TagConfig::staticMetaObject;
+    val = metaObj.enumerator(metaObj.indexOfEnumerator("Id3v2Version"))
+        .keyToValue(enumName.toLatin1(), &ok);
+    if (ok) {
+      return val;
+    }
+  } else if (group == QLatin1String("Tag") &&
+             option == QLatin1String("textEncoding")) {
+    const QMetaObject metaObj = TagConfig::staticMetaObject;
+    val = metaObj.enumerator(metaObj.indexOfEnumerator("TextEncoding"))
+        .keyToValue(enumName.toLatin1(), &ok);
+    if (ok) {
+      return val;
+    }
+  }
+  val = enumName.toInt(&ok);
+  if (ok) {
+    return val;
+  }
+  return QVariant();
+}
+
 }
 
 /**
@@ -1079,3 +1286,93 @@ void RemoveCommand::startCommand()
   Frame::TagVersion tagMask = getTagMaskParameter(1);
   cli()->app()->removeTags(tagMask);
 }
+
+
+ConfigCommand::ConfigCommand(Kid3Cli* processor)
+  : CliCommand(processor, QLatin1String("config"), tr("Configure Kid3"),
+               QLatin1String("[S]\nS = Group.Option Value"))
+{
+}
+
+void ConfigCommand::startCommand()
+{
+  int numArgs = args().size();
+  QString group, option;
+  GeneralConfig* cfg = nullptr;
+  QVariant value;
+  if (numArgs > 1) {
+    const QString& groupOption = args().at(1);
+    int dotPos = groupOption.indexOf(QLatin1Char('.'));
+    if (dotPos > 0) {
+      group = groupOption.left(dotPos);
+      option = groupOption.mid(dotPos + 1);
+    } else {
+      group = groupOption;
+    }
+    cfg = getConfig(group);
+    if (!cfg) {
+      setError(tr("%1 does not exist").arg(group));
+      return;
+    }
+    if (!option.isNull()) {
+      value = cfg->property(option.toLatin1());
+      if (!value.isValid()) {
+        setError(tr("%1 does not exist").arg(option));
+        return;
+      }
+    }
+  }
+  if (numArgs > 2) {
+    const QMetaObject* metaObj = nullptr;
+    int propIdx = -1;
+    if (!option.isNull() && (metaObj = cfg->metaObject()) != nullptr &&
+        (propIdx = metaObj->indexOfProperty(option.toLatin1())) >= 0) {
+      QVariant::Type propType = metaObj->property(propIdx).type();
+      if (propType == QVariant::StringList) {
+        value = QVariant(args().mid(2));
+      } else if (propType == QVariant::Int) {
+        value = configIntFromEnumName(group, option, args().at(2));
+      } else if (propType == QVariant::Bool) {
+        value = QVariant(args().at(2)).toBool();
+      } else {
+        value = args().at(2);
+      }
+      if (value.type() == propType) {
+        cfg->setProperty(option.toLatin1(), value);
+        cli()->app()->applyChangedConfiguration();
+        // The value is read back and will be displayed.
+        value = cfg->property(option.toLatin1());
+      } else {
+        setError(tr("Invalid value %1").arg(value.toString()));
+        return;
+      }
+    }
+  }
+  if (numArgs > 1) {
+    if (option.isNull()) {
+      if (auto metaObj = cfg->metaObject()) {
+        QStringList propertyNames;
+        for (int i = 0; i < metaObj->propertyCount(); ++i) {
+          QString propertyName = QString::fromLatin1(metaObj->property(i).name());
+          if (!excludedConfigPropertyNames.contains(propertyName)) {
+            propertyNames.append(propertyName);
+          }
+        }
+        cli()->writeResult(propertyNames);
+      }
+    } else {
+      if (value.type() == QVariant::StringList) {
+        cli()->writeResult(value.toStringList());
+      } else if (value.type() == QVariant::Map) {
+        cli()->writeResult(value.toMap());
+      } else if (value.type() == QVariant::Int) {
+        value = configIntToEnumName(group, option, value);
+        cli()->writeResult(value.toString());
+      } else {
+        cli()->writeResult(value.toString());
+      }
+    }
+  } else {
+    cli()->writeResult(configNames);
+  }
+}
diff --git a/src/app/cli/clicommand.h b/src/app/cli/clicommand.h
index 47afa43f..68383756 100644
--- a/src/app/cli/clicommand.h
+++ b/src/app/cli/clicommand.h
@@ -593,3 +593,14 @@ public:
 protected:
   virtual void startCommand() override;
 };
+
+/** Get or set configuration options. */
+class ConfigCommand : public CliCommand {
+  Q_OBJECT
+public:
+  /** Constructor. */
+  explicit ConfigCommand(Kid3Cli* processor);
+
+protected:
+  virtual void startCommand() override;
+};
diff --git a/src/app/cli/kid3cli.cpp b/src/app/cli/kid3cli.cpp
index bf81dfa9..bedf4b10 100644
--- a/src/app/cli/kid3cli.cpp
+++ b/src/app/cli/kid3cli.cpp
@@ -216,7 +216,8 @@ Kid3Cli::Kid3Cli(Kid3Application* app,
          << new TagToOtherTagCommand(this)
          << new CopyCommand(this)
          << new PasteCommand(this)
-         << new RemoveCommand(this);
+         << new RemoveCommand(this)
+         << new ConfigCommand(this);
   connect(m_app, &Kid3Application::fileSelectionUpdateRequested,
           this, &Kid3Cli::updateSelectedFiles);
   connect(m_app, &Kid3Application::selectedFilesUpdated,



More information about the kde-doc-english mailing list