File indexing completed on 2024-05-19 04:55:49
0001 /** 0002 * \file clicommand.cpp 0003 * Command line interface commands. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 11 Aug 2013 0008 * 0009 * Copyright (C) 2013-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "clicommand.h" 0028 #include <functional> 0029 #include <QStringList> 0030 #include <QStringBuilder> 0031 #include <QTimer> 0032 #include <QDir> 0033 #include <QItemSelectionModel> 0034 #include <QMetaProperty> 0035 #include "kid3cli.h" 0036 #include "kid3application.h" 0037 #include "fileproxymodel.h" 0038 #include "frametablemodel.h" 0039 #include "filefilter.h" 0040 #include "importconfig.h" 0041 #include "exportconfig.h" 0042 #include "filterconfig.h" 0043 #include "fileconfig.h" 0044 #include "rendirconfig.h" 0045 #include "batchimportconfig.h" 0046 #include "formatconfig.h" 0047 #include "networkconfig.h" 0048 #include "numbertracksconfig.h" 0049 #include "playlistconfig.h" 0050 #include "tagconfig.h" 0051 #include "batchimporter.h" 0052 #include "downloadclient.h" 0053 #include "dirrenamer.h" 0054 0055 namespace { 0056 0057 /** Default command timeout in milliseconds. */ 0058 constexpr int DEFAULT_TIMEOUT_MS = 3000; 0059 0060 /** 0061 * Available names for groups in config command. 0062 * If this list is modified, adapt also the cfgFuncs in getConfig(). 0063 */ 0064 const QStringList configNames{ 0065 QLatin1String("BatchImport"), 0066 QLatin1String("Export"), 0067 QLatin1String("File"), 0068 QLatin1String("FilenameFormat"), 0069 QLatin1String("Filter"), 0070 QLatin1String("Import"), 0071 QLatin1String("Network"), 0072 QLatin1String("NumberTracks"), 0073 QLatin1String("Playlist"), 0074 QLatin1String("RenameFolder"), 0075 QLatin1String("Tag"), 0076 QLatin1String("TagFormat") 0077 }; 0078 0079 /** Properties which shall not be displayed as config options. */ 0080 const QStringList excludedConfigPropertyNames{ 0081 QLatin1String("objectName"), 0082 QLatin1String("windowGeometry"), 0083 QLatin1String("exportWindowGeometry"), 0084 QLatin1String("importServer"), 0085 QLatin1String("importVisibleColumns"), 0086 QLatin1String("importWindowGeometry"), 0087 QLatin1String("browseCoverArtWindowGeometry"), 0088 QLatin1String("quickAccessFrames"), 0089 QLatin1String("quickAccessFrameOrder"), 0090 QLatin1String("taggedFileFeatures") 0091 }; 0092 0093 /** 0094 * Get a configuration object for a given group name. 0095 * @param name group name 0096 * @return QObject with configuration options as properties. 0097 */ 0098 GeneralConfig* getConfig(const QString& name) 0099 { 0100 int idx = configNames.indexOf(name); 0101 if (idx == -1) { 0102 return nullptr; 0103 } 0104 0105 // Change this list together with configNames. 0106 static const std::function<GeneralConfig*()> cfgFuncs[] = { 0107 [] { return &BatchImportConfig::instance(); }, 0108 [] { return &ExportConfig::instance(); }, 0109 [] { return &FileConfig::instance(); }, 0110 [] { return &FilenameFormatConfig::instance(); }, 0111 [] { return &FilterConfig::instance(); }, 0112 [] { return &ImportConfig::instance(); }, 0113 [] { return &NetworkConfig::instance(); }, 0114 [] { return &NumberTracksConfig::instance(); }, 0115 [] { return &PlaylistConfig::instance(); }, 0116 [] { return &RenDirConfig::instance(); }, 0117 [] { return &TagConfig::instance(); }, 0118 [] { return &TagFormatConfig::instance(); } 0119 }; 0120 return cfgFuncs[idx](); 0121 } 0122 0123 /** 0124 * Convert an integer value to the corresponding enum name string. 0125 * @param group config group 0126 * @param option config option 0127 * @param value enum value as integer 0128 * @return enum value as string, original int value if invalid. 0129 */ 0130 QVariant configIntToEnumName(const QString& group, const QString& option, 0131 const QVariant& value) 0132 { 0133 const int enumVal = value.toInt(); 0134 if (option == QLatin1String("importDest") || 0135 option == QLatin1String("exportSource") || 0136 option == QLatin1String("numberTracksDestination")) { 0137 QString tagMaskStr; 0138 for (Frame::TagNumber tagNr : 0139 Frame::tagNumbersFromMask(Frame::tagVersionCast(enumVal))) { 0140 tagMaskStr += Frame::tagNumberToString(tagNr); 0141 } 0142 return tagMaskStr; 0143 } 0144 if (option == QLatin1String("caseConversion")) { 0145 const QMetaObject metaObj = FormatConfig::staticMetaObject; 0146 if (const char* key = metaObj.enumerator( 0147 metaObj.indexOfEnumerator("CaseConversion")).valueToKey(enumVal)) { 0148 return QString::fromLatin1(key); 0149 } 0150 } else if (group == QLatin1String("Playlist") && 0151 option == QLatin1String("location")) { 0152 const QMetaObject metaObj = PlaylistConfig::staticMetaObject; 0153 if (const char* key = metaObj.enumerator( 0154 metaObj.indexOfEnumerator("PlaylistLocation")).valueToKey(enumVal)) { 0155 return QString::fromLatin1(key); 0156 } 0157 } else if (group == QLatin1String("Playlist") && 0158 option == QLatin1String("format")) { 0159 const QMetaObject metaObj = PlaylistConfig::staticMetaObject; 0160 if (const char* key = metaObj.enumerator( 0161 metaObj.indexOfEnumerator("PlaylistFormat")).valueToKey(enumVal)) { 0162 return QString::fromLatin1(key); 0163 } 0164 } else if (group == QLatin1String("Tag") && 0165 option == QLatin1String("id3v2Version")) { 0166 const QMetaObject metaObj = TagConfig::staticMetaObject; 0167 if (const char* key = metaObj.enumerator( 0168 metaObj.indexOfEnumerator("Id3v2Version")).valueToKey(enumVal)) { 0169 return QString::fromLatin1(key); 0170 } 0171 } else if (group == QLatin1String("Tag") && 0172 option == QLatin1String("textEncoding")) { 0173 const QMetaObject metaObj = TagConfig::staticMetaObject; 0174 if (const char* key = metaObj.enumerator( 0175 metaObj.indexOfEnumerator("TextEncoding")).valueToKey(enumVal)) { 0176 return QString::fromLatin1(key); 0177 } 0178 } 0179 return value; 0180 } 0181 0182 /** 0183 * Convert an enum value name to the corresponding integer value. 0184 * @param group config group 0185 * @param option config option 0186 * @param value enum name as variant 0187 * @return enum value as integer, original string value if invalid. 0188 */ 0189 QVariant configIntFromEnumName(const QString& group, const QString& option, 0190 const QVariant& value) 0191 { 0192 const QString enumName = value.toString(); 0193 int val; 0194 bool ok; 0195 if (option == QLatin1String("importDest") || 0196 option == QLatin1String("exportSource") || 0197 option == QLatin1String("numberTracksDestination")) { 0198 val = 0; 0199 if (!enumName.isEmpty() && enumName.at(0).isDigit()) { 0200 FOR_ALL_TAGS(tagNr) { 0201 if (enumName.contains(Frame::tagNumberToString(tagNr))) { 0202 val |= Frame::tagVersionFromNumber(tagNr); 0203 } 0204 } 0205 if (val != 0) { 0206 return val; 0207 } 0208 } 0209 } else if (option == QLatin1String("caseConversion")) { 0210 const QMetaObject metaObj = FormatConfig::staticMetaObject; 0211 val = metaObj.enumerator(metaObj.indexOfEnumerator("CaseConversion")) 0212 .keyToValue(enumName.toLatin1(), &ok); 0213 if (ok) { 0214 return val; 0215 } 0216 } else if (group == QLatin1String("Playlist") && 0217 option == QLatin1String("location")) { 0218 const QMetaObject metaObj = PlaylistConfig::staticMetaObject; 0219 val = metaObj.enumerator(metaObj.indexOfEnumerator("PlaylistLocation")) 0220 .keyToValue(enumName.toLatin1(), &ok); 0221 if (ok) { 0222 return val; 0223 } 0224 } else if (group == QLatin1String("Playlist") && 0225 option == QLatin1String("format")) { 0226 const QMetaObject metaObj = PlaylistConfig::staticMetaObject; 0227 val = metaObj.enumerator(metaObj.indexOfEnumerator("PlaylistFormat")) 0228 .keyToValue(enumName.toLatin1(), &ok); 0229 if (ok) { 0230 return val; 0231 } 0232 } else if (group == QLatin1String("Tag") && 0233 option == QLatin1String("id3v2Version")) { 0234 const QMetaObject metaObj = TagConfig::staticMetaObject; 0235 val = metaObj.enumerator(metaObj.indexOfEnumerator("Id3v2Version")) 0236 .keyToValue(enumName.toLatin1(), &ok); 0237 if (ok) { 0238 return val; 0239 } 0240 } else if (group == QLatin1String("Tag") && 0241 option == QLatin1String("textEncoding")) { 0242 const QMetaObject metaObj = TagConfig::staticMetaObject; 0243 val = metaObj.enumerator(metaObj.indexOfEnumerator("TextEncoding")) 0244 .keyToValue(enumName.toLatin1(), &ok); 0245 if (ok) { 0246 return val; 0247 } 0248 } 0249 val = enumName.toInt(&ok); 0250 if (ok) { 0251 return val; 0252 } 0253 return QVariant(); 0254 } 0255 0256 } 0257 0258 /** 0259 * Constructor. 0260 * @param processor command line processor 0261 * @param name name with which command is invoked 0262 * @param help help text for command 0263 * @param argspec argument specification 0264 */ 0265 CliCommand::CliCommand(Kid3Cli* processor, 0266 const QString& name, const QString& help, 0267 const QString& argspec) 0268 : QObject(processor), m_processor(processor), m_name(name), m_help(help), 0269 m_argspec(argspec), m_timerId(0), m_timeoutMs(DEFAULT_TIMEOUT_MS), m_result(0) 0270 { 0271 } 0272 0273 /** 0274 * Reset state to defaults. 0275 */ 0276 void CliCommand::clear() 0277 { 0278 if (m_timerId != 0) { 0279 killTimer(m_timerId); 0280 m_timerId = 0; 0281 } 0282 cli()->finishWriting(); 0283 m_errorMsg.clear(); 0284 m_args.clear(); 0285 m_result = 0; 0286 } 0287 0288 /** 0289 * Execute command. 0290 */ 0291 void CliCommand::execute() 0292 { 0293 if (m_timerId != 0) { 0294 killTimer(m_timerId); 0295 m_timerId = 0; 0296 } 0297 int msec = m_processor->getTimeout(); 0298 if (msec == 0) { 0299 msec = getTimeout(); 0300 } 0301 if (msec > 0) { 0302 m_timerId = startTimer(msec); 0303 } 0304 connectResultSignal(); 0305 startCommand(); 0306 } 0307 0308 /** 0309 * Terminate command. 0310 */ 0311 void CliCommand::terminate() { 0312 if (m_timerId != 0) { 0313 killTimer(m_timerId); 0314 m_timerId = 0; 0315 } 0316 disconnectResultSignal(); 0317 emit finished(); 0318 } 0319 0320 /** 0321 * Connect signals used to emit finished(). 0322 * This method is called after startCommand(). The default implementation 0323 * invokes terminate() in the event loop. It can be overridden to connect 0324 * signals connected to terminate() to signal termination of the command. 0325 */ 0326 void CliCommand::connectResultSignal() 0327 { 0328 QTimer::singleShot(0, this, &CliCommand::terminate); 0329 } 0330 0331 /** 0332 * Disconnect signals used to emit finished(). 0333 * This method is called from terminate(). The default implementation 0334 * does nothing. It can be overridden to disconnect signals connected 0335 * in connectResultSignal(). 0336 */ 0337 void CliCommand::disconnectResultSignal() 0338 { 0339 } 0340 0341 /** 0342 * Called on timeout. 0343 */ 0344 void CliCommand::timerEvent(QTimerEvent*) { 0345 setError(tr("Timeout")); 0346 terminate(); 0347 } 0348 0349 /** 0350 * Get parameter for task mask. 0351 * @param nr index in args() 0352 * @param useDefault if true use cli()->tagMask() if no parameter found 0353 * @return tag versions. 0354 */ 0355 Frame::TagVersion CliCommand::getTagMaskParameter(int nr, 0356 bool useDefault) const 0357 { 0358 int tagMask = 0; 0359 if (m_args.size() > nr) { 0360 if (const QString& tagStr = m_args.at(nr); 0361 !tagStr.isEmpty() && tagStr.at(0).isDigit()) { 0362 FOR_ALL_TAGS(tagNr) { 0363 if (tagStr.contains(Frame::tagNumberToString(tagNr))) { 0364 tagMask |= Frame::tagVersionFromNumber(tagNr); 0365 } 0366 } 0367 if (tagMask == 0) 0368 tagMask = tagStr.toInt(); 0369 } 0370 } 0371 if (tagMask == 0 && useDefault) { 0372 tagMask = m_processor->tagMask(); 0373 } 0374 return Frame::tagVersionCast(tagMask); 0375 } 0376 0377 /** 0378 * Show usage of command. 0379 */ 0380 void CliCommand::showUsage() 0381 { 0382 cli()->writeHelp(name(), true); 0383 setError(QLatin1String("_Usage")); 0384 } 0385 0386 0387 0388 HelpCommand::HelpCommand(Kid3Cli* processor) 0389 : CliCommand(processor, QLatin1String("help"), tr("Help"), 0390 QLatin1String("[S]\nS = ") + tr("Command name")) 0391 { 0392 } 0393 0394 void HelpCommand::startCommand() 0395 { 0396 cli()->writeHelp(args().size() > 1 ? args().at(1) : QString()); 0397 } 0398 0399 0400 TimeoutCommand::TimeoutCommand(Kid3Cli* processor) 0401 : CliCommand(processor, QLatin1String("timeout"), tr("Overwrite timeout"), 0402 QLatin1String("[S]\nS = \"default\" | \"off\" | ") + tr("Time") + 0403 QLatin1String(" [ms]")) 0404 { 0405 } 0406 0407 void TimeoutCommand::startCommand() 0408 { 0409 int cliTimeout = cli()->getTimeout(); 0410 if (args().size() > 1) { 0411 if (const QString& val = args().at(1); val == QLatin1String("off")) { 0412 cliTimeout = -1; 0413 } else if (val == QLatin1String("default")) { 0414 cliTimeout = 0; 0415 } else { 0416 QString msStr = val; 0417 if (msStr.endsWith(QLatin1String("ms"))) { 0418 msStr.truncate(msStr.length() - 2); 0419 } 0420 bool ok; 0421 if (int ms = msStr.toInt(&ok); ok && ms > 0) { 0422 cliTimeout = ms; 0423 } 0424 } 0425 cli()->setTimeout(cliTimeout); 0426 } 0427 QString value; 0428 if (cliTimeout < 0) { 0429 value = QLatin1String("off"); 0430 } else if (cliTimeout == 0) { 0431 value = QLatin1String("default"); 0432 } else { 0433 value = QString::number(cliTimeout); 0434 value += QLatin1String(" ms"); 0435 } 0436 cli()->writeResult(QVariantMap{{QLatin1String("timeout"), value}}); 0437 } 0438 0439 0440 QuitCommand::QuitCommand(Kid3Cli* processor) 0441 : CliCommand(processor, QLatin1String("exit"), tr("Quit application"), 0442 QLatin1String("[S]\nS = \"force\"")) 0443 { 0444 } 0445 0446 void QuitCommand::startCommand() 0447 { 0448 if (cli()->app()->isModified() && !cli()->app()->getDirName().isEmpty()) { 0449 if (!(args().size() > 1 && args().at(1) == QLatin1String("force"))) { 0450 cli()->writeResult(tr("The current folder has been modified.") % 0451 QLatin1Char('\n') % 0452 tr("Type 'exit force' to quit.")); 0453 terminate(); 0454 return; 0455 } 0456 } 0457 disconnect(this, &CliCommand::finished, cli(), &Kid3Cli::onCommandFinished); 0458 cli()->terminate(); 0459 } 0460 0461 void QuitCommand::connectResultSignal() 0462 { 0463 // Do not signal finished() to avoid printing prompt. 0464 } 0465 0466 0467 CdCommand::CdCommand(Kid3Cli* processor) 0468 : CliCommand(processor, QLatin1String("cd"), tr("Change folder"), 0469 QLatin1String("[P]")) 0470 { 0471 } 0472 0473 void CdCommand::startCommand() 0474 { 0475 QStringList paths; 0476 if (args().size() > 1) { 0477 paths = args().mid(1); 0478 } else { 0479 paths.append(QDir::homePath()); 0480 } 0481 if (!cli()->openDirectory(Kid3Cli::expandWildcards(paths))) { 0482 setError(tr("%1 does not exist").arg(paths.join(QLatin1String(", ")))); 0483 terminate(); 0484 } 0485 } 0486 0487 void CdCommand::connectResultSignal() 0488 { 0489 connect(cli()->app(), &Kid3Application::directoryOpened, 0490 this, &CdCommand::terminate); 0491 } 0492 0493 void CdCommand::disconnectResultSignal() 0494 { 0495 disconnect(cli()->app(), &Kid3Application::directoryOpened, 0496 this, &CdCommand::terminate); 0497 } 0498 0499 0500 PwdCommand::PwdCommand(Kid3Cli* processor) 0501 : CliCommand(processor, QLatin1String("pwd"), 0502 tr("Print the filename of the current folder")) 0503 { 0504 } 0505 0506 void PwdCommand::startCommand() 0507 { 0508 QString path = cli()->app()->getDirPath(); 0509 if (path.isNull()) { 0510 path = QDir::currentPath(); 0511 cli()->app()->openDirectory({path}); 0512 } 0513 cli()->writeResult(path); 0514 } 0515 0516 0517 LsCommand::LsCommand(Kid3Cli* processor) 0518 : CliCommand(processor, QLatin1String("ls"), tr("Folder list")) 0519 { 0520 setTimeout(10000); 0521 } 0522 0523 void LsCommand::startCommand() 0524 { 0525 cli()->writeFileList(); 0526 } 0527 0528 0529 SaveCommand::SaveCommand(Kid3Cli* processor) 0530 : CliCommand(processor, QLatin1String("save"), tr("Saves the changed files")) 0531 { 0532 } 0533 0534 void SaveCommand::startCommand() 0535 { 0536 QStringList errorDescriptions; 0537 if (const QStringList errorFiles = cli()->app()->saveDirectory(&errorDescriptions); 0538 errorFiles.isEmpty()) { 0539 cli()->updateSelection(); 0540 } else { 0541 setError(tr("Error while writing file:\n") + 0542 Kid3Application::mergeStringLists(errorFiles, errorDescriptions, 0543 QLatin1String(": ")) 0544 .join(QLatin1String("\n"))); 0545 } 0546 } 0547 0548 0549 SelectCommand::SelectCommand(Kid3Cli* processor) 0550 : CliCommand(processor, QLatin1String("select"), tr("Select file"), 0551 QLatin1String("[P|S]\n" 0552 "S = \"all\" | \"none\" | \"first\" | \"previous\" | \"next\"")) 0553 { 0554 } 0555 0556 void SelectCommand::startCommand() 0557 { 0558 if (args().size() > 1) { 0559 if (const QString& param = args().at(1); param == QLatin1String("all")) { 0560 cli()->app()->selectAllFiles(); 0561 } else if (param == QLatin1String("none")) { 0562 cli()->app()->deselectAllFiles(); 0563 } else if (param == QLatin1String("first")) { 0564 setResult(cli()->app()->firstFile(true) ? 0 : 1); 0565 } else if (param == QLatin1String("previous")) { 0566 setResult(cli()->app()->previousFile(true) ? 0 : 1); 0567 } else if (param == QLatin1String("next")) { 0568 setResult(cli()->app()->nextFile(true) ? 0 : 1); 0569 } else { 0570 if (QStringList paths = args().mid(1); 0571 !cli()->selectFile(Kid3Cli::expandWildcards(paths))) { 0572 setError(tr("%1 not found").arg(paths.join(QLatin1String(", ")))); 0573 } 0574 } 0575 } else { 0576 cli()->updateSelection(); 0577 } 0578 } 0579 0580 0581 TagCommand::TagCommand(Kid3Cli* processor) 0582 : CliCommand(processor, QLatin1String("tag"), tr("Select tag"), 0583 QLatin1String("[T]")) 0584 { 0585 } 0586 0587 void TagCommand::startCommand() 0588 { 0589 if (Frame::TagVersion tagMask = getTagMaskParameter(1, false); 0590 tagMask != Frame::TagNone) { 0591 cli()->setTagMask(tagMask); 0592 } else { 0593 cli()->writeTagMask(); 0594 } 0595 } 0596 0597 0598 GetCommand::GetCommand(Kid3Cli* processor) 0599 : CliCommand(processor, QLatin1String("get"), tr("Get tag frame"), 0600 QLatin1String("[N|S] [T]\nS = \"all\"")) 0601 { 0602 } 0603 0604 void GetCommand::startCommand() 0605 { 0606 int numArgs = args().size(); 0607 QString name = numArgs > 1 0608 ? Frame::getNameForTranslatedFrameName(args().at(1)) 0609 : QLatin1String("all"); 0610 Frame::TagVersion tagMask = getTagMaskParameter(2); 0611 if (name == QLatin1String("all")) { 0612 cli()->writeFileInformation(tagMask); 0613 } else { 0614 for (Frame::TagNumber tagNr : Frame::tagNumbersFromMask(tagMask)) { 0615 if (QString value = cli()->app()->getFrame( 0616 Frame::tagVersionFromNumber(tagNr), name); 0617 !(tagNr == Frame::Tag_1 ? value.isEmpty() : value.isNull())) { 0618 cli()->writeResult(value); 0619 break; 0620 } 0621 } 0622 } 0623 } 0624 0625 0626 SetCommand::SetCommand(Kid3Cli* processor) 0627 : CliCommand(processor, QLatin1String("set"), tr("Set tag frame"), 0628 QLatin1String("N V [T]")) 0629 { 0630 } 0631 0632 void SetCommand::startCommand() 0633 { 0634 if (int numArgs = args().size(); numArgs > 2) { 0635 QString name = Frame::getNameForTranslatedFrameName(args().at(1)); 0636 const QString& value = args().at(2); 0637 if (Frame::TagVersion tagMask = getTagMaskParameter(3); 0638 cli()->app()->setFrame(tagMask, name, value)) { 0639 if (!name.endsWith(QLatin1String(".selected"))) { 0640 cli()->updateSelectedFiles(); 0641 cli()->updateSelection(); 0642 } 0643 } else if (!value.isEmpty()) { 0644 setError(tr("Could not set \"%1\" for %2").arg(value, name)); 0645 } 0646 } else { 0647 showUsage(); 0648 } 0649 } 0650 0651 0652 RevertCommand::RevertCommand(Kid3Cli* processor) 0653 : CliCommand(processor, QLatin1String("revert"), 0654 tr("Revert")) 0655 { 0656 } 0657 0658 void RevertCommand::startCommand() 0659 { 0660 cli()->app()->revertFileModifications(); 0661 } 0662 0663 0664 ImportCommand::ImportCommand(Kid3Cli* processor) 0665 : CliCommand(processor, QLatin1String("import"), 0666 tr("Import from file"), 0667 QLatin1String("P S [T]\nP S = ") + 0668 tr("File path") + QLatin1Char(' ') + tr("Format name") + 0669 QLatin1String(" | tags ") + tr("Source") + QLatin1Char(' ') + 0670 tr("Extraction")) 0671 { 0672 } 0673 0674 void ImportCommand::startCommand() 0675 { 0676 if (int numArgs = args().size(); 0677 numArgs > 3 && args().at(1).startsWith(QLatin1String("tags"))) { 0678 const QString& source = args().at(2); 0679 const QString& extraction = args().at(3); 0680 Frame::TagVersion tagMask = getTagMaskParameter(4); 0681 if (args().at(1).contains(QLatin1String("sel"))) { 0682 if (QStringList returnValues = 0683 cli()->app()->importFromTagsToSelection(tagMask, source, extraction); 0684 !returnValues.isEmpty()) { 0685 cli()->writeResult(returnValues); 0686 } 0687 } else { 0688 cli()->app()->importFromTags(tagMask, source, extraction); 0689 } 0690 } else if (numArgs > 2) { 0691 const QString& path = args().at(1); 0692 const QString& fmtName = args().at(2); 0693 bool ok; 0694 int fmtIdx = fmtName.toInt(&ok); 0695 if (!ok) { 0696 fmtIdx = ImportConfig::instance().importFormatNames().indexOf(fmtName); 0697 if (fmtIdx == -1) { 0698 QString errMsg = tr("%1 not found.").arg(fmtName); 0699 errMsg += QLatin1Char('\n'); 0700 errMsg += tr("Available"); 0701 errMsg += QLatin1String(": "); 0702 errMsg += ImportConfig::instance().importFormatNames().join( 0703 QLatin1String(", ")); 0704 errMsg += QLatin1Char('.'); 0705 setError(errMsg); 0706 return; 0707 } 0708 } 0709 if (Frame::TagVersion tagMask = getTagMaskParameter(3); 0710 !cli()->app()->importTags(tagMask, path, fmtIdx)) { 0711 setError(tr("Error")); 0712 } 0713 } else { 0714 showUsage(); 0715 } 0716 } 0717 0718 0719 BatchImportCommand::BatchImportCommand(Kid3Cli* processor) 0720 : CliCommand(processor, QLatin1String("autoimport"), 0721 tr("Automatic import"), QLatin1String("[S] [T]\nS = ") + 0722 tr("Profile name")) 0723 { 0724 setTimeout(60000); 0725 } 0726 0727 void BatchImportCommand::startCommand() 0728 { 0729 int numArgs = args().size(); 0730 const QString& profileName = numArgs > 1 0731 ? args().at(1) : QLatin1String("All"); 0732 if (Frame::TagVersion tagMask = getTagMaskParameter(2); 0733 !cli()->app()->batchImport(profileName, tagMask)) { 0734 QString errMsg = tr("%1 not found.").arg(profileName); 0735 errMsg += QLatin1Char('\n'); 0736 errMsg += tr("Available"); 0737 errMsg += QLatin1String(": "); 0738 errMsg += BatchImportConfig::instance().profileNames().join( 0739 QLatin1String(", ")); 0740 errMsg += QLatin1Char('.'); 0741 setError(errMsg); 0742 terminate(); 0743 } 0744 } 0745 0746 void BatchImportCommand::connectResultSignal() 0747 { 0748 BatchImporter* importer = cli()->app()->getBatchImporter(); 0749 connect(importer, &BatchImporter::reportImportEvent, 0750 this, &BatchImportCommand::onReportImportEvent); 0751 connect(importer, &BatchImporter::finished, 0752 this, &BatchImportCommand::terminate); 0753 } 0754 0755 void BatchImportCommand::disconnectResultSignal() 0756 { 0757 BatchImporter* importer = cli()->app()->getBatchImporter(); 0758 disconnect(importer, &BatchImporter::reportImportEvent, 0759 this, &BatchImportCommand::onReportImportEvent); 0760 disconnect(importer, &BatchImporter::finished, 0761 this, &BatchImportCommand::terminate); 0762 } 0763 0764 void BatchImportCommand::onReportImportEvent(int type, const QString& text) 0765 { 0766 QString typeStr; 0767 switch (type) { 0768 case BatchImporter::ReadingDirectory: 0769 typeStr = QLatin1String("readingDirectory"); 0770 break; 0771 case BatchImporter::Started: 0772 typeStr = QLatin1String("started"); 0773 break; 0774 case BatchImporter::SourceSelected: 0775 typeStr = QLatin1String("source"); 0776 break; 0777 case BatchImporter::QueryingAlbumList: 0778 typeStr = QLatin1String("querying"); 0779 break; 0780 case BatchImporter::FetchingTrackList: 0781 case BatchImporter::FetchingCoverArt: 0782 typeStr = QLatin1String("fetching"); 0783 break; 0784 case BatchImporter::TrackListReceived: 0785 typeStr = QLatin1String("data received"); 0786 break; 0787 case BatchImporter::CoverArtReceived: 0788 typeStr = QLatin1String("cover"); 0789 break; 0790 case BatchImporter::Finished: 0791 typeStr = QLatin1String("finished"); 0792 break; 0793 case BatchImporter::Aborted: 0794 typeStr = QLatin1String("aborted"); 0795 break; 0796 case BatchImporter::Error: 0797 typeStr = QLatin1String("error"); 0798 } 0799 QVariantMap event{{QLatin1String("type"), typeStr}}; 0800 if (!text.isEmpty()) { 0801 event.insert(QLatin1String("data"), text); 0802 } 0803 cli()->writeResult(QVariantMap{{QLatin1String("event"), event}}); 0804 } 0805 0806 0807 AlbumArtCommand::AlbumArtCommand(Kid3Cli* processor) 0808 : CliCommand(processor, QLatin1String("albumart"), 0809 tr("Download album cover artwork"), 0810 QLatin1String("U [S]\nS = \"all\"")) 0811 { 0812 setTimeout(10000); 0813 } 0814 0815 void AlbumArtCommand::startCommand() 0816 { 0817 if (int numArgs = args().size(); numArgs > 1) { 0818 const QString& url = args().at(1); 0819 cli()->app()->downloadImage(url, 0820 numArgs > 2 && args().at(2) == QLatin1String("all")); 0821 } else { 0822 showUsage(); 0823 terminate(); 0824 } 0825 } 0826 0827 void AlbumArtCommand::connectResultSignal() 0828 { 0829 DownloadClient* downloadClient = cli()->app()->getDownloadClient(); 0830 connect(downloadClient, &DownloadClient::downloadFinished, 0831 this, &AlbumArtCommand::onDownloadFinished); 0832 } 0833 0834 void AlbumArtCommand::disconnectResultSignal() 0835 { 0836 DownloadClient* downloadClient = cli()->app()->getDownloadClient(); 0837 disconnect(downloadClient, 0838 &DownloadClient::downloadFinished, 0839 this, &AlbumArtCommand::onDownloadFinished); 0840 } 0841 0842 void AlbumArtCommand::onDownloadFinished( 0843 const QByteArray& data, const QString& mimeType, const QString& url) 0844 { 0845 cli()->app()->imageDownloaded(data, mimeType, url); 0846 terminate(); 0847 } 0848 0849 0850 ExportCommand::ExportCommand(Kid3Cli* processor) 0851 : CliCommand(processor, QLatin1String("export"), 0852 tr("Export to file"), 0853 QLatin1String("P S [T]\nS = ") + tr("Format name")) 0854 { 0855 } 0856 0857 void ExportCommand::startCommand() 0858 { 0859 if (int numArgs = args().size(); numArgs > 2) { 0860 const QString& path = args().at(1); 0861 const QString& fmtName = args().at(2); 0862 bool ok; 0863 int fmtIdx = fmtName.toInt(&ok); 0864 if (!ok) { 0865 fmtIdx = ExportConfig::instance().exportFormatNames().indexOf(fmtName); 0866 if (fmtIdx == -1) { 0867 QString errMsg = tr("%1 not found.").arg(fmtName); 0868 errMsg += QLatin1Char('\n'); 0869 errMsg += tr("Available"); 0870 errMsg += QLatin1String(": "); 0871 errMsg += ExportConfig::instance().exportFormatNames().join( 0872 QLatin1String(", ")); 0873 errMsg += QLatin1Char('.'); 0874 setError(errMsg); 0875 return; 0876 } 0877 } 0878 if (Frame::TagVersion tagMask = getTagMaskParameter(3); 0879 !cli()->app()->exportTags(tagMask, path, fmtIdx)) { 0880 setError(tr("Error")); 0881 } 0882 } else { 0883 showUsage(); 0884 } 0885 } 0886 0887 0888 PlaylistCommand::PlaylistCommand(Kid3Cli* processor) 0889 : CliCommand(processor, QLatin1String("playlist"), tr("Create playlist")) 0890 { 0891 } 0892 0893 void PlaylistCommand::startCommand() 0894 { 0895 if (!cli()->app()->writePlaylist()) { 0896 setError(tr("Error")); 0897 } 0898 } 0899 0900 0901 FilenameFormatCommand::FilenameFormatCommand(Kid3Cli* processor) 0902 : CliCommand(processor, QLatin1String("filenameformat"), 0903 tr("Apply filename format")) 0904 { 0905 } 0906 0907 void FilenameFormatCommand::startCommand() 0908 { 0909 cli()->app()->applyFilenameFormat(); 0910 } 0911 0912 0913 TagFormatCommand::TagFormatCommand(Kid3Cli* processor) 0914 : CliCommand(processor, QLatin1String("tagformat"), tr("Apply tag format")) 0915 { 0916 } 0917 0918 void TagFormatCommand::startCommand() 0919 { 0920 cli()->app()->applyTagFormat(); 0921 } 0922 0923 0924 TextEncodingCommand::TextEncodingCommand(Kid3Cli* processor) 0925 : CliCommand(processor, QLatin1String("textencoding"), 0926 tr("Apply text encoding")) 0927 { 0928 } 0929 0930 void TextEncodingCommand::startCommand() 0931 { 0932 cli()->app()->applyTextEncoding(); 0933 } 0934 0935 0936 RenameDirectoryCommand::RenameDirectoryCommand(Kid3Cli* processor) 0937 : CliCommand(processor, QLatin1String("renamedir"), tr("Rename folder"), 0938 QLatin1String("[F] [S] [T]\nS = \"create\" | \"rename\" | \"dryrun\"")), 0939 m_dryRun(false) 0940 { 0941 } 0942 0943 void RenameDirectoryCommand::startCommand() 0944 { 0945 Frame::TagVersion tagMask = Frame::TagNone; 0946 QString format; 0947 bool create = false; 0948 m_dryRun = false; 0949 for (int i = 1; i < args().size(); ++i) { 0950 bool ok = false; 0951 if (tagMask == Frame::TagNone) { 0952 tagMask = getTagMaskParameter(i, false); 0953 ok = tagMask != Frame::TagNone; 0954 } 0955 if (!ok) { 0956 if (const QString& param = args().at(i); 0957 param == QLatin1String("create")) { 0958 create = true; 0959 } else if (param == QLatin1String("rename")) { 0960 create = false; 0961 } else if (param == QLatin1String("dryrun")) { 0962 m_dryRun = true; 0963 } else if (format.isEmpty()) { 0964 format = param; 0965 } 0966 } 0967 } 0968 if (tagMask == Frame::TagNone) { 0969 tagMask = cli()->tagMask(); 0970 } 0971 if (format.isEmpty()) { 0972 format = RenDirConfig::instance().dirFormat(); 0973 } 0974 0975 if (!cli()->app()->renameDirectory(tagMask, format, create)) { 0976 terminate(); 0977 } 0978 } 0979 0980 void RenameDirectoryCommand::connectResultSignal() 0981 { 0982 DirRenamer* renamer = cli()->app()->getDirRenamer(); 0983 connect(renamer, &DirRenamer::actionScheduled, 0984 this, &RenameDirectoryCommand::onActionScheduled); 0985 connect(cli()->app(), &Kid3Application::renameActionsScheduled, 0986 this, &RenameDirectoryCommand::onRenameActionsScheduled); 0987 } 0988 0989 void RenameDirectoryCommand::disconnectResultSignal() 0990 { 0991 DirRenamer* renamer = cli()->app()->getDirRenamer(); 0992 disconnect(renamer, &DirRenamer::actionScheduled, 0993 this, &RenameDirectoryCommand::onActionScheduled); 0994 disconnect(cli()->app(), &Kid3Application::renameActionsScheduled, 0995 this, &RenameDirectoryCommand::onRenameActionsScheduled); 0996 } 0997 0998 void RenameDirectoryCommand::onActionScheduled(const QStringList& actionStrs) 0999 { 1000 QVariantMap event{{QLatin1String("type"), actionStrs.at(0)}}; 1001 QVariantMap data; 1002 if (actionStrs.size() > 1) { 1003 data.insert(QLatin1String("source"), actionStrs.at(1)); 1004 } 1005 if (actionStrs.size() > 2) { 1006 data.insert(QLatin1String("destination"), actionStrs.at(2)); 1007 } 1008 if (!data.isEmpty()) { 1009 event.insert(QLatin1String("data"), data); 1010 } 1011 cli()->writeResult(QVariantMap{{QLatin1String("event"), event}}); 1012 } 1013 1014 void RenameDirectoryCommand::onRenameActionsScheduled() 1015 { 1016 if (!m_dryRun) { 1017 if (QString errMsg = cli()->app()->performRenameActions(); errMsg.isEmpty()) { 1018 cli()->app()->deselectAllFiles(); 1019 } else { 1020 setError(errMsg); 1021 } 1022 } 1023 terminate(); 1024 } 1025 1026 1027 NumberTracksCommand::NumberTracksCommand(Kid3Cli* processor) 1028 : CliCommand(processor, QLatin1String("numbertracks"), tr("Number tracks"), 1029 QLatin1String("[S] [T]\nS = ") + tr("Track number")) 1030 { 1031 } 1032 1033 void NumberTracksCommand::startCommand() 1034 { 1035 int numArgs = args().size(); 1036 int firstTrackNr = 1; 1037 bool ok = false; 1038 if (numArgs > 1) { 1039 firstTrackNr = args().at(1).toInt(&ok); 1040 } 1041 if (!ok) { 1042 firstTrackNr = 1; 1043 } 1044 Frame::TagVersion tagMask = getTagMaskParameter(2); 1045 Kid3Application::NumberTrackOptions options; 1046 options |= Kid3Application::NumberTracksEnabled; 1047 options |= Kid3Application::NumberTracksResetCounterForEachDirectory; 1048 cli()->app()->numberTracks(firstTrackNr, 0, tagMask, options); 1049 } 1050 1051 1052 FilterCommand::FilterCommand(Kid3Cli* processor) 1053 : CliCommand(processor, QLatin1String("filter"), tr("Filter"), 1054 QLatin1String("F|S\nS = ") + tr("Filter name")) 1055 { 1056 setTimeout(60000); 1057 } 1058 1059 void FilterCommand::startCommand() 1060 { 1061 if (args().size() > 1) { 1062 QString expression = args().at(1); 1063 if (int fltIdx = FilterConfig::instance().filterNames().indexOf(expression); 1064 fltIdx != -1) { 1065 expression = FilterConfig::instance().filterExpressions().at(fltIdx); 1066 } else if (!expression.isEmpty() && 1067 !expression.contains(QLatin1Char('%'))) { 1068 // Probably an invalid expression 1069 QString errMsg = tr("%1 not found.").arg(expression); 1070 errMsg += QLatin1Char('\n'); 1071 errMsg += tr("Available"); 1072 errMsg += QLatin1String(": "); 1073 errMsg += FilterConfig::instance().filterNames().join( 1074 QLatin1String(", ")); 1075 errMsg += QLatin1Char('.'); 1076 setError(errMsg); 1077 terminate(); 1078 return; 1079 } 1080 cli()->app()->applyFilter(expression); 1081 } else { 1082 showUsage(); 1083 terminate(); 1084 } 1085 } 1086 1087 void FilterCommand::connectResultSignal() 1088 { 1089 connect(cli()->app(), &Kid3Application::fileFiltered, 1090 this, &FilterCommand::onFileFiltered); 1091 } 1092 1093 void FilterCommand::disconnectResultSignal() 1094 { 1095 cli()->app()->abortFilter(); 1096 disconnect(cli()->app(), &Kid3Application::fileFiltered, 1097 this, &FilterCommand::onFileFiltered); 1098 } 1099 1100 void FilterCommand::onFileFiltered(int type, const QString& fileName) 1101 { 1102 QString typeStr; 1103 QString data; 1104 bool finish = false; 1105 switch (type) { 1106 case FileFilter::Started: 1107 typeStr = QLatin1String("started"); 1108 break; 1109 case FileFilter::Directory: 1110 typeStr = QLatin1String("filterEntered"); 1111 data = fileName; 1112 break; 1113 case FileFilter::ParseError: 1114 typeStr = QLatin1String("parseError"); 1115 break; 1116 case FileFilter::FilePassed: 1117 typeStr = QLatin1String("filterPassed"); 1118 data = fileName; 1119 break; 1120 case FileFilter::FileFilteredOut: 1121 typeStr = QLatin1String("filteredOut"); 1122 data = fileName; 1123 break; 1124 case FileFilter::Finished: 1125 typeStr = QLatin1String("finished"); 1126 finish = true; 1127 break; 1128 case FileFilter::Aborted: 1129 typeStr = QLatin1String("aborted"); 1130 finish = true; 1131 break; 1132 } 1133 QVariantMap event{{QLatin1String("type"), typeStr}}; 1134 if (!data.isEmpty()) { 1135 event.insert(QLatin1String("data"), data); 1136 } 1137 cli()->writeResult(QVariantMap{{QLatin1String("event"), event}}); 1138 if (finish) { 1139 terminate(); 1140 } 1141 } 1142 1143 1144 ToId3v24Command::ToId3v24Command(Kid3Cli* processor) 1145 : CliCommand(processor, QLatin1String("to24"), tr("Convert ID3v2.3 to ID3v2.4")) 1146 { 1147 } 1148 1149 void ToId3v24Command::startCommand() 1150 { 1151 cli()->app()->convertToId3v24(); 1152 } 1153 1154 1155 ToId3v23Command::ToId3v23Command(Kid3Cli* processor) 1156 : CliCommand(processor, QLatin1String("to23"), tr("Convert ID3v2.4 to ID3v2.3")) 1157 { 1158 } 1159 1160 void ToId3v23Command::startCommand() 1161 { 1162 cli()->app()->convertToId3v23(); 1163 } 1164 1165 1166 TagToFilenameCommand::TagToFilenameCommand(Kid3Cli* processor) 1167 : CliCommand(processor, QLatin1String("fromtag"), tr("Filename from tag"), 1168 QLatin1String("[F] [T]")) 1169 { 1170 } 1171 1172 void TagToFilenameCommand::startCommand() 1173 { 1174 Frame::TagVersion tagMask = Frame::TagNone; 1175 QString format; 1176 for (int i = 1; i < qMin(args().size(), 3); ++i) { 1177 bool ok = false; 1178 if (tagMask == Frame::TagNone) { 1179 tagMask = getTagMaskParameter(i, false); 1180 ok = tagMask != Frame::TagNone; 1181 } 1182 if (!ok && format.isEmpty()) { 1183 format = args().at(i); 1184 } 1185 } 1186 if (tagMask == Frame::TagNone) { 1187 tagMask = cli()->tagMask(); 1188 } 1189 if (!format.isEmpty()) { 1190 FileConfig::instance().setToFilenameFormat(format); 1191 } 1192 cli()->app()->getFilenameFromTags(tagMask); 1193 } 1194 1195 1196 FilenameToTagCommand::FilenameToTagCommand(Kid3Cli* processor) 1197 : CliCommand(processor, QLatin1String("totag"), tr("Tag from filename"), 1198 QLatin1String("[F] [T]")) 1199 { 1200 } 1201 1202 void FilenameToTagCommand::startCommand() 1203 { 1204 Frame::TagVersion tagMask = Frame::TagNone; 1205 QString format; 1206 for (int i = 1; i < qMin(args().size(), 3); ++i) { 1207 bool ok = false; 1208 if (tagMask == Frame::TagNone) { 1209 tagMask = getTagMaskParameter(i, false); 1210 ok = tagMask != Frame::TagNone; 1211 } 1212 if (!ok && format.isEmpty()) { 1213 format = args().at(i); 1214 } 1215 } 1216 if (tagMask == Frame::TagNone) { 1217 tagMask = cli()->tagMask(); 1218 } 1219 if (!format.isEmpty()) { 1220 FileConfig::instance().setFromFilenameFormat(format); 1221 } 1222 cli()->app()->getTagsFromFilename(tagMask); 1223 } 1224 1225 1226 TagToOtherTagCommand::TagToOtherTagCommand(Kid3Cli* processor) 1227 : CliCommand(processor, QLatin1String("syncto"), tr("Tag to other tag"), 1228 QLatin1String("T")) 1229 { 1230 } 1231 1232 void TagToOtherTagCommand::startCommand() 1233 { 1234 if (Frame::TagVersion tagMask = getTagMaskParameter(1, false); 1235 tagMask != Frame::TagNone) { 1236 cli()->app()->copyToOtherTag(tagMask); 1237 } else { 1238 showUsage(); 1239 } 1240 } 1241 1242 1243 CopyCommand::CopyCommand(Kid3Cli* processor) 1244 : CliCommand(processor, QLatin1String("copy"), tr("Copy"), 1245 QLatin1String("[T]")) 1246 { 1247 } 1248 1249 void CopyCommand::startCommand() 1250 { 1251 Frame::TagVersion tagMask = getTagMaskParameter(1); 1252 cli()->app()->copyTags(tagMask); 1253 } 1254 1255 1256 PasteCommand::PasteCommand(Kid3Cli* processor) 1257 : CliCommand(processor, QLatin1String("paste"), tr("Paste"), 1258 QLatin1String("[T]")) 1259 { 1260 } 1261 1262 void PasteCommand::startCommand() 1263 { 1264 Frame::TagVersion tagMask = getTagMaskParameter(1); 1265 cli()->app()->pasteTags(tagMask); 1266 } 1267 1268 1269 RemoveCommand::RemoveCommand(Kid3Cli* processor) 1270 : CliCommand(processor, QLatin1String("remove"), tr("Remove"), 1271 QLatin1String("[T]")) 1272 { 1273 } 1274 1275 void RemoveCommand::startCommand() 1276 { 1277 Frame::TagVersion tagMask = getTagMaskParameter(1); 1278 cli()->app()->removeTags(tagMask); 1279 } 1280 1281 1282 ConfigCommand::ConfigCommand(Kid3Cli* processor) 1283 : CliCommand(processor, QLatin1String("config"), tr("Configure Kid3"), 1284 QLatin1String("[S]\nS = ") + tr("Group.Option Value")) 1285 { 1286 } 1287 1288 void ConfigCommand::startCommand() 1289 { 1290 int numArgs = args().size(); 1291 QString group, option; 1292 GeneralConfig* cfg = nullptr; 1293 QVariant value; 1294 if (numArgs > 1) { 1295 const QString& groupOption = args().at(1); 1296 if (int dotPos = groupOption.indexOf(QLatin1Char('.')); dotPos > 0) { 1297 group = groupOption.left(dotPos); 1298 option = groupOption.mid(dotPos + 1); 1299 } else { 1300 group = groupOption; 1301 } 1302 cfg = getConfig(group); 1303 if (!cfg) { 1304 setError(tr("%1 does not exist").arg(group)); 1305 return; 1306 } 1307 if (!option.isNull()) { 1308 value = cfg->property(option.toLatin1()); 1309 if (!value.isValid()) { 1310 setError(tr("%1 does not exist").arg(option)); 1311 return; 1312 } 1313 } 1314 } 1315 if (numArgs > 2) { 1316 const QMetaObject* metaObj = nullptr; 1317 if (int propIdx = -1; 1318 !option.isNull() && (metaObj = cfg->metaObject()) != nullptr && 1319 (propIdx = metaObj->indexOfProperty(option.toLatin1())) >= 0) { 1320 #if QT_VERSION >= 0x060000 1321 auto propType = metaObj->property(propIdx).typeId(); 1322 if (propType == QMetaType::QStringList) { 1323 value = QVariant(args().mid(2)); 1324 } else if (propType == QMetaType::Int) { 1325 value = configIntFromEnumName(group, option, args().at(2)); 1326 } else if (propType == QMetaType::Bool) { 1327 value = QVariant(args().at(2)).toBool(); 1328 } else { 1329 value = args().at(2); 1330 } 1331 if (value.typeId() == propType) { 1332 cfg->setProperty(option.toLatin1(), value); 1333 cli()->app()->applyChangedConfiguration(); 1334 // The value is read back and will be displayed. 1335 value = cfg->property(option.toLatin1()); 1336 } else { 1337 setError(tr("Invalid value %1").arg(value.toString())); 1338 return; 1339 } 1340 #else 1341 QVariant::Type propType = metaObj->property(propIdx).type(); 1342 if (propType == QVariant::StringList) { 1343 value = QVariant(args().mid(2)); 1344 } else if (propType == QVariant::Int) { 1345 value = configIntFromEnumName(group, option, args().at(2)); 1346 } else if (propType == QVariant::Bool) { 1347 value = QVariant(args().at(2)).toBool(); 1348 } else { 1349 value = args().at(2); 1350 } 1351 if (value.type() == propType) { 1352 cfg->setProperty(option.toLatin1(), value); 1353 cli()->app()->applyChangedConfiguration(); 1354 // The value is read back and will be displayed. 1355 value = cfg->property(option.toLatin1()); 1356 } else { 1357 setError(tr("Invalid value %1").arg(value.toString())); 1358 return; 1359 } 1360 #endif 1361 } 1362 } 1363 if (numArgs > 1) { 1364 if (option.isNull()) { 1365 if (auto metaObj = cfg->metaObject()) { 1366 QStringList propertyNames; 1367 for (int i = 0; i < metaObj->propertyCount(); ++i) { 1368 if (QString propertyName = QString::fromLatin1(metaObj->property(i).name()); 1369 !excludedConfigPropertyNames.contains(propertyName)) { 1370 propertyNames.append(propertyName); 1371 } 1372 } 1373 cli()->writeResult(propertyNames); 1374 } 1375 } else { 1376 #if QT_VERSION >= 0x060000 1377 if (value.typeId() == QMetaType::QStringList) { 1378 cli()->writeResult(value.toStringList()); 1379 } else if (value.typeId() == QMetaType::QVariantMap) { 1380 cli()->writeResult(value.toMap()); 1381 } else if (value.typeId() == QMetaType::Int) { 1382 value = configIntToEnumName(group, option, value); 1383 cli()->writeResult(value.toString()); 1384 } else if (value.typeId() == QMetaType::Bool) { 1385 cli()->writeResult(value.toBool()); 1386 } else { 1387 cli()->writeResult(value.toString()); 1388 } 1389 #else 1390 if (value.type() == QVariant::StringList) { 1391 cli()->writeResult(value.toStringList()); 1392 } else if (value.type() == QVariant::Map) { 1393 cli()->writeResult(value.toMap()); 1394 } else if (value.type() == QVariant::Int) { 1395 value = configIntToEnumName(group, option, value); 1396 cli()->writeResult(value.toString()); 1397 } else if (value.type() == QVariant::Bool) { 1398 cli()->writeResult(value.toBool()); 1399 } else { 1400 cli()->writeResult(value.toString()); 1401 } 1402 #endif 1403 } 1404 } else { 1405 cli()->writeResult(configNames); 1406 } 1407 } 1408 1409 1410 ExecuteCommand::ExecuteCommand(Kid3Cli* processor) 1411 : CliCommand(processor, QLatin1String("execute"), tr("Execute command"), 1412 QLatin1String("S\nS = [@qml] ") + tr("Executable [arguments]")) 1413 { 1414 setTimeout(-1); 1415 } 1416 1417 void ExecuteCommand::setCaption(const QString& title) 1418 { 1419 Q_UNUSED(title) 1420 } 1421 1422 void ExecuteCommand::append(const QString& text) 1423 { 1424 cli()->writeLine(text); 1425 } 1426 1427 void ExecuteCommand::scrollToBottom() 1428 { 1429 } 1430 1431 void ExecuteCommand::startCommand() 1432 { 1433 if (args().size() > 1) { 1434 QString command = args().at(1); 1435 if (!m_process) { 1436 m_process.reset(new ExternalProcess(cli()->app(), this)); 1437 connectResultSignal(); 1438 } 1439 m_process->setOutputViewer(this); 1440 if (!m_process->launchCommand(command, args().mid(1), true)) { 1441 setError(tr("Could not execute ") + args().mid(1).join(QLatin1String(" "))); 1442 terminate(); 1443 } 1444 } else { 1445 showUsage(); 1446 terminate(); 1447 } 1448 } 1449 1450 void ExecuteCommand::connectResultSignal() 1451 { 1452 if (m_process) { 1453 connect(m_process.data(), &ExternalProcess::finished, 1454 this, &ExecuteCommand::terminate, Qt::UniqueConnection); 1455 } 1456 } 1457 1458 void ExecuteCommand::disconnectResultSignal() 1459 { 1460 if (m_process) { 1461 disconnect(m_process.data(), &ExternalProcess::finished, 1462 this, &ExecuteCommand::terminate); 1463 // Avoid segfault when m_process is deleted at program termination 1464 m_process.reset(); 1465 } 1466 }