[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