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 }