File indexing completed on 2024-05-12 05:12:40

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 "collectionresolvejob.h"
0020 
0021 #include <Akonadi/CollectionFetchJob>
0022 #include <Akonadi/CollectionFetchScope>
0023 #include <Akonadi/CollectionPathResolver>
0024 
0025 #include <KLocalizedString>
0026 
0027 #include "errorreporter.h"
0028 
0029 using namespace Akonadi;
0030 
0031 HackedCollectionPathResolver::HackedCollectionPathResolver(const QString &path, QObject *parent)
0032     : CollectionPathResolver(path, parent)
0033 {}
0034 
0035 HackedCollectionPathResolver::HackedCollectionPathResolver(const Collection &col, QObject *parent)
0036     : CollectionPathResolver(col, parent)
0037 {}
0038 
0039 bool HackedCollectionPathResolver::addSubjob(KJob *job)
0040 {
0041     if (auto akjob = qobject_cast<Akonadi::Job*>(job)) {
0042         connect(akjob, &Job::aboutToStart,
0043                 [](Akonadi::Job *subjob) {
0044                     if (auto fetchJob = qobject_cast<CollectionFetchJob*>(subjob)) {
0045                         fetchJob->fetchScope().setListFilter(CollectionFetchScope::NoFilter);
0046                     }
0047                 });
0048     }
0049     return CollectionPathResolver::addSubjob(job);
0050 }
0051 
0052 
0053 CollectionResolveJob::CollectionResolveJob(const QString &userInput, QObject *parent)
0054     : KCompositeJob(parent),
0055       mUserInput(userInput),
0056       mHadSlash(false)
0057 {
0058     setAutoDelete(false);
0059 
0060     // A collection path ending with a '/' is accepted by a
0061     // CollectionPathResolver.  However, we strip any slash here
0062     // so that an ID or a URL can also be parsed (but, obviously,
0063     // not from a single slash meaning the root).  Note whether
0064     // any slash was removed, so that the caller can act on it
0065     // if required.
0066 
0067     QString in = userInput;
0068     if (in.length() > 1 && in.endsWith(QLatin1Char('/'))) {
0069         in.chop(1);
0070         mHadSlash = true;
0071     }
0072 
0073     mCollection = parseCollection(in);
0074 }
0075 
0076 void CollectionResolveJob::start()
0077 {
0078     if (!hasUsableInput()) {
0079         emitResult();
0080         return;
0081     }
0082 
0083     if (mCollection.isValid()) {
0084         fetchBase();
0085     } else {
0086         CollectionPathResolver *resolver = new HackedCollectionPathResolver(mUserInput, this);
0087         addSubjob(resolver);
0088         resolver->start();
0089     }
0090 }
0091 
0092 bool CollectionResolveJob::hasUsableInput()
0093 {
0094     if (mCollection.isValid() || mUserInput.startsWith(CollectionPathResolver::pathDelimiter())) {
0095         return true;
0096     }
0097 
0098     setError(Akonadi::Job::UserError);
0099     setErrorText(i18nc("@info:shell", "Unknown Akonadi collection format '%1'", mUserInput));
0100     return false;
0101 }
0102 
0103 void CollectionResolveJob::fetchBase()
0104 {
0105     if (mCollection == Collection::root()) {
0106         emitResult();
0107         return;
0108     }
0109 
0110     CollectionFetchJob *job = new CollectionFetchJob(mCollection, CollectionFetchJob::Base, this);
0111     job->fetchScope().setListFilter(CollectionFetchScope::NoFilter);
0112     addSubjob(job);
0113 }
0114 
0115 void CollectionResolveJob::slotResult(KJob *job)
0116 {
0117     if (job->error() == 0) {
0118         CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
0119         if (fetchJob != nullptr) {
0120             mCollection = fetchJob->collections().first();
0121         } else {
0122             CollectionPathResolver *resolver = qobject_cast<CollectionPathResolver *>(job);
0123             mCollection = Collection(resolver->collection());
0124             fetchBase();
0125         }
0126     }
0127 
0128     bool willEmitResult = (job->error() && !error());
0129     // If willEmitResult is true, then emitResult() will be
0130     // done inside the call of KCompositeJob::slotResult() below.
0131     // So there will be no need for us to do it again.
0132     KCompositeJob::slotResult(job);
0133 
0134     if (!hasSubjobs() && !willEmitResult) {
0135         emitResult();
0136     }
0137 }
0138 
0139 QString CollectionResolveJob::formattedCollectionName() const
0140 {
0141     if (mCollection == Collection::root()) {
0142         return (i18nc("@info:shell 1=collection ID",
0143                       "%1 (root)", QString::number(mCollection.id())));
0144     } else {
0145         return (i18nc("@info:shell 1=collection ID, 2=collection name",
0146                       "%1 (\"%2\")",
0147                       QString::number(mCollection.id()), mCollection.name()));
0148     }
0149 }
0150 
0151 Akonadi::Item CollectionResolveJob::parseItem(const QString &userInput, bool verbose)
0152 {
0153     Item item;
0154 
0155     // See if user input is a valid integer as an item ID
0156     bool ok;
0157     unsigned int id = userInput.toUInt(&ok);
0158     if (ok) {
0159         item = Item(id);    // conversion succeeded
0160     } else {
0161         // Otherwise check if we have an Akonadi URL
0162         const QUrl url = QUrl::fromUserInput(userInput);
0163         if (url.isValid() && url.scheme() == QLatin1String("akonadi")) {
0164             // valid Akonadi URL
0165             item = Item::fromUrl(url);
0166         }
0167     }
0168     // Otherwise return an invalid Item
0169 
0170     if (!item.isValid() && verbose) {         // report error if required
0171         ErrorReporter::error(i18nc("@info:shell", "Invalid item syntax '%1'", userInput));
0172     }
0173 
0174     return (item);
0175 }
0176 
0177 Akonadi::Collection CollectionResolveJob::parseCollection(const QString &userInput)
0178 {
0179     Collection coll;
0180 
0181     // First see if user input is a valid integer as a collection ID
0182     bool ok;
0183     unsigned int id = userInput.toUInt(&ok);
0184     if (ok) {                     // conversion succeeded
0185         if (id == 0) {
0186             coll = Collection::root();    // the root collection
0187         } else {
0188             coll = Collection(id);    // the specified collection
0189         }
0190     } else {
0191         // Then quickly check for a path of "/", meaning the root
0192         if (userInput == QLatin1String("/")) {
0193             coll = Collection::root();
0194         } else {
0195             // Next check if we have an Akonadi URL
0196             const QUrl url = QUrl::fromUserInput(userInput);
0197             if (url.isValid() && url.scheme() == QLatin1String("akonadi")) {
0198                 // valid Akonadi URL
0199                 coll = Collection::fromUrl(url);
0200             }
0201         }
0202     }
0203     // If none of these applied, assume that we have a path
0204     // and return an invalid Collection.
0205 
0206     return (coll);
0207 }