File indexing completed on 2024-05-12 05:12:39
0001 /* 0002 Copyright (C) 2012 Kevin Krammer <krammer@kde.org> 0003 0004 This program is free software; you can redistribute it and/or modify 0005 it under the terms of the GNU General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or 0007 (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 GNU General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License along 0015 with this program; if not, write to the Free Software Foundation, Inc., 0016 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0017 */ 0018 0019 #include "abstractcommand.h" 0020 0021 #include <KLocalizedString> 0022 0023 #include <QCommandLineParser> 0024 0025 #include "collectionresolvejob.h" 0026 #include "errorreporter.h" 0027 #include "commandshell.h" 0028 0029 #include <iostream> 0030 0031 #define ENV_VAR_DANGEROUS "AKONADICLIENT_DANGEROUS" 0032 #define ENV_VAL_DANGEROUS "enabled" 0033 0034 AbstractCommand::AbstractCommand(QObject *parent) 0035 : QObject(parent), 0036 mDryRun(false), 0037 mWantCollection(false), 0038 mWantItem(false), 0039 mSetDryRun(false), 0040 mSetCollectionItem(false), 0041 mResolveJob(nullptr), 0042 mProcessLoopSlot(nullptr) 0043 { 0044 } 0045 0046 int AbstractCommand::init(const QStringList &parsedArgs, bool showHelp) 0047 { 0048 QCommandLineParser parser; 0049 parser.addPositionalArgument(name(), i18nc("@info:shell", "The name of the command"), name()); 0050 setupCommandOptions(&parser); // set options for command 0051 0052 const bool ok = parser.parse(parsedArgs); // just parse, do not do actions 0053 if (!ok) 0054 { 0055 ErrorReporter::fatal(parser.errorText()); 0056 return (InvalidUsage); 0057 } 0058 0059 if (showHelp) { // do this here, while the 0060 QString s = parser.helpText(); // parser is still available 0061 0062 // This substitutes the first "[options]" (between the "akonadiclient" 0063 // and the comand name) with blank, because it is not relevant and 0064 // would be confused with the command options (following the command 0065 // name). Cannot use QString::replace() with a regular expression 0066 // here, as that would substitute any subsequent "[optional]" help 0067 // text supplied by the command as well. 0068 int idx1 = s.indexOf(" ["); 0069 int idx2 = s.indexOf("] "); 0070 if (idx1!=-1 && idx2!=-1) s = s.left(idx1)+s.mid(idx2+1); 0071 0072 // If the command shell is active, then the executable name also needs 0073 // to be removed. Again, cannot use a regular expression replacement 0074 // because only the first instance needs to be changed. 0075 if (CommandShell::isActive()) { 0076 idx1 = s.indexOf(": "); 0077 if (idx1!=-1) { 0078 idx2 = s.indexOf(' ', idx1+2); 0079 if (idx2!=-1) s = s.left(idx1+1)+s.mid(idx2); 0080 } 0081 } 0082 0083 std::cout << qPrintable(s) << std::endl; 0084 return (NoError); 0085 } 0086 0087 return (initCommand(&parser)); // read command arguments 0088 } 0089 0090 void AbstractCommand::addOptionsOption(QCommandLineParser *parser) 0091 { 0092 parser->addPositionalArgument("options", i18nc("@info:shell", "Options for command"), i18nc("@info:shell", "[options]")); 0093 } 0094 0095 void AbstractCommand::addCollectionItemOptions(QCommandLineParser *parser) 0096 { 0097 parser->addOption(QCommandLineOption((QStringList() << "c" << "collection"), 0098 i18nc("@info:shell", "Assume that a collection is specified"))); 0099 0100 parser->addOption(QCommandLineOption((QStringList() << "i" << "item"), 0101 i18nc("@info:shell", "Assume that an item is specified"))); 0102 mSetCollectionItem = true; 0103 } 0104 0105 void AbstractCommand::addDryRunOption(QCommandLineParser *parser) 0106 { 0107 parser->addOption(QCommandLineOption((QStringList() << "n" << "dryrun"), 0108 i18nc("@info:shell", "Run without making any actual changes"))); 0109 mSetDryRun = true; 0110 } 0111 0112 bool AbstractCommand::getCommonOptions(QCommandLineParser *parser) 0113 { 0114 if (mSetDryRun) { 0115 mDryRun = parser->isSet("dryrun"); 0116 } 0117 0118 if (mSetCollectionItem) 0119 { 0120 mWantCollection = parser->isSet("collection"); 0121 mWantItem = parser->isSet("item"); 0122 0123 if (mWantItem && mWantCollection) { 0124 emit error(i18nc("@info:shell", "Cannot specify as both an item and a collection")); 0125 return (false); 0126 } 0127 } 0128 0129 return (true); 0130 } 0131 0132 bool AbstractCommand::getResolveJob(const QString &arg) 0133 { 0134 if (mResolveJob != nullptr) delete mResolveJob; 0135 mResolveJob = new CollectionResolveJob(arg, this); 0136 // TODO: does this work for ITEMs specified as an Akonadi URL? 0137 // "akonadiclient info 10175" works, 0138 // but "akonadiclient info 'akonadi://?item=10175'" doesn't 0139 0140 if (mResolveJob->hasUsableInput()) return (true); 0141 0142 error(i18nc("@info:shell", "Invalid collection/item argument '%1', %2", arg, mResolveJob->errorString())); 0143 delete mResolveJob; 0144 mResolveJob = nullptr; 0145 return (false); 0146 } 0147 0148 bool AbstractCommand::checkArgCount(const QStringList &args, int min, const QString &errorText) 0149 { 0150 if (args.count()>=min) return (true); // enough arguments provided 0151 emitErrorSeeHelp(errorText); 0152 return (false); 0153 } 0154 0155 void AbstractCommand::emitErrorSeeHelp(const QString &msg) 0156 { 0157 QString s; 0158 if (CommandShell::isActive()) { 0159 s = i18nc("@info:shell %1 is subcommand name, %2 is error message", 0160 "%2. See 'help %1'", 0161 this->name(), 0162 msg); 0163 } else { 0164 s = i18nc("@info:shell %1 is application name, %2 is subcommand name, %3 is error message", 0165 "%3. See '%1 help %2'", 0166 QCoreApplication::applicationName(), 0167 this->name(), 0168 msg); 0169 } 0170 0171 emit error(s); 0172 } 0173 0174 bool AbstractCommand::allowDangerousOperation() const 0175 { 0176 if (qgetenv(ENV_VAR_DANGEROUS) == ENV_VAL_DANGEROUS) { 0177 // check set in environment 0178 return true; 0179 } 0180 0181 ErrorReporter::error(i18nc("@info:shell", "Dangerous or destructive operations are not allowed")); 0182 ErrorReporter::error(i18nc("@info:shell", "Set %1=\"%2\" in environment", 0183 QLatin1String(ENV_VAR_DANGEROUS), 0184 QLatin1String(ENV_VAL_DANGEROUS))); 0185 return false; 0186 } 0187 0188 bool AbstractCommand::checkJobResult(KJob *job, const QString &message) 0189 { 0190 if (job->error() != 0) { 0191 emit error(!message.isEmpty() ? message : job->errorString()); 0192 // This will work even if the process loop is not in use 0193 // (in which case mProcessLoopArgs will be empty and finished() 0194 // will use the return code set by the error() signal above). 0195 processNext(); 0196 return (false); 0197 } 0198 0199 return (true); 0200 } 0201 0202 void AbstractCommand::initProcessLoop(const QStringList &args, const QString &finishedMessage) 0203 { 0204 mProcessLoopArgs = args; 0205 mFinishedLoopMessage = finishedMessage; 0206 } 0207 0208 void AbstractCommand::startProcessLoop(const char *slot) 0209 { 0210 mProcessLoopSlot = slot; 0211 processNext(); 0212 } 0213 0214 void AbstractCommand::processNext() 0215 { 0216 if (mProcessLoopArgs.isEmpty()) // all arguments processed, 0217 { // loop is finished 0218 if (!mFinishedLoopMessage.isEmpty()) { 0219 ErrorReporter::progress(mFinishedLoopMessage); 0220 } 0221 0222 emit finished(); // with accumulated error code 0223 return; 0224 } 0225 0226 Q_ASSERT(mProcessLoopSlot!=nullptr); 0227 mCurrentArg = mProcessLoopArgs.takeFirst(); 0228 QMetaObject::invokeMethod(this, mProcessLoopSlot, Qt::QueuedConnection); 0229 }