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

0001 /*
0002  * Copyright (C) 2014  Bhaskar Kandiyal <bkandiyal@gmail.com>
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 
0020 #include "importcommand.h"
0021 
0022 #include <Akonadi/XmlWriteJob>
0023 #include <Akonadi/XmlDocument>
0024 #include <Akonadi/CollectionCreateJob>
0025 #include <Akonadi/CollectionFetchJob>
0026 #include <Akonadi/CollectionFetchScope>
0027 #include <Akonadi/ItemCreateJob>
0028 
0029 #include <klocalizedstring.h>
0030 
0031 #include <qfile.h>
0032 
0033 #include "commandfactory.h"
0034 #include "errorreporter.h"
0035 #include "collectionresolvejob.h"
0036 
0037 using namespace Akonadi;
0038 
0039 DEFINE_COMMAND("import", ImportCommand, I18N_NOOP("Import an XML file"));
0040 
0041 ImportCommand::ImportCommand(QObject *parent)
0042     : AbstractCommand(parent),
0043       mDocument(nullptr)
0044 {
0045 }
0046 
0047 ImportCommand::~ImportCommand()
0048 {
0049     delete mDocument;
0050 }
0051 
0052 void ImportCommand::setupCommandOptions(QCommandLineParser *parser)
0053 {
0054     addOptionsOption(parser);
0055     addDryRunOption(parser);
0056 
0057     parser->addPositionalArgument("parent", i18nc("@info:shell", "The parent collection under which to import the file"));
0058     parser->addPositionalArgument("file", i18nc("@info:shell", "The file to import"));
0059 }
0060 
0061 int ImportCommand::initCommand(QCommandLineParser *parser)
0062 {
0063     const QStringList args = parser->positionalArguments();
0064     if (!checkArgCount(args, 1, i18nc("@info:shell", "No parent collection specified"))) return InvalidUsage;
0065     if (!checkArgCount(args, 2, i18nc("@info:shell", "No import file specified"))) return InvalidUsage;
0066 
0067     if (!getCommonOptions(parser)) return InvalidUsage;
0068 
0069     if (!getResolveJob(args.first())) return InvalidUsage;
0070 
0071     const QString fileArg = args.at(1);
0072     mDocument = new XmlDocument(fileArg);
0073     if (!mDocument->isValid()) {
0074         emit error(i18nc("@info:shell", "Invalid XML file, %1", mDocument->lastError()));
0075         return InvalidUsage;
0076     }
0077 
0078     mCollections = mDocument->collections();
0079 
0080     return NoError;
0081 }
0082 
0083 void ImportCommand::start()
0084 {
0085     connect(resolveJob(), &KJob::result, this, &ImportCommand::onParentFetched);
0086     resolveJob()->start();
0087 }
0088 
0089 void ImportCommand::onParentFetched(KJob *job)
0090 {
0091     if (!checkJobResult(job, i18nc("@info:shell", "Unable to fetch parent collection, %1", job->errorString()))) return;
0092 
0093     mParentCollection = resolveJob()->collection();
0094     QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0095 }
0096 
0097 void ImportCommand::onChildrenFetched(KJob *job)
0098 {
0099     if (!checkJobResult(job, i18nc("@info:shell", "Unable to fetch children of parent collection, %1", job->errorString()))) return;
0100 
0101     QString rid = job->property("rid").toString();
0102     Collection parent = job->property("parent").value<Collection>();
0103     Collection collection = mDocument->collectionByRemoteId(rid);
0104     Collection newCol;
0105     Collection::List collections = qobject_cast<CollectionFetchJob *>(job)->collections();
0106     bool found = false;
0107 
0108     Q_FOREACH (const Collection &col, collections) {
0109         if (collection.name() == col.name()) {
0110             found = true;
0111             newCol = col;
0112             break;
0113         }
0114     }
0115 
0116     if (found) {
0117         ErrorReporter::progress(i18nc("@info:shell", "Collection '%1' already exists", collection.name()));
0118         mCollectionMap.insert(rid, newCol);
0119         QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0120     } else {
0121         ErrorReporter::progress(i18nc("@info:shell", "Creating collection '%1'", collection.name()));
0122         collection.setParentCollection(parent);
0123         if (!isDryRun()) {
0124             CollectionCreateJob *createJob = new CollectionCreateJob(collection, this);
0125             createJob->setProperty("rid", rid);
0126             connect(createJob, &KJob::result, this, &ImportCommand::onCollectionCreated);
0127         } else {
0128             QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0129         }
0130     }
0131 }
0132 
0133 void ImportCommand::processNextCollection()
0134 {
0135     if (mCollections.isEmpty()) {
0136         processNextCollectionFromMap();
0137         return;
0138     }
0139 
0140     Collection collection = mCollections.takeFirst();
0141     Collection parent;
0142 
0143     ErrorReporter::progress(i18nc("@info:shell", "Processing collection '%1'", collection.name()));
0144 
0145     if (collection.parentCollection().remoteId().isEmpty()) {
0146         parent = mParentCollection;
0147     } else {
0148         parent = mCollectionMap.value(collection.parentCollection().remoteId());
0149         if (!parent.isValid() && !isDryRun()) {
0150             ErrorReporter::warning(i18nc("@info:shell", "Invalid parent for collection with remote ID '%1'",
0151                                          collection.remoteId()));
0152             QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0153         }
0154     }
0155 
0156     if (!isDryRun()) {
0157         CollectionFetchJob *fetchJob = new CollectionFetchJob(parent, CollectionFetchJob::FirstLevel, this);
0158         fetchJob->fetchScope().setListFilter(CollectionFetchScope::NoFilter);
0159         fetchJob->setProperty("rid", collection.remoteId());
0160         fetchJob->setProperty("parent", QVariant::fromValue<Collection>(parent));
0161         connect(fetchJob, &KJob::result, this, &ImportCommand::onChildrenFetched);
0162     } else {
0163         QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0164     }
0165 }
0166 
0167 void ImportCommand::onCollectionFetched(KJob *job)
0168 {
0169     Collection collection = job->property("collection").value<Collection>();
0170 
0171     if (job->error() != 0) {
0172         ErrorReporter::warning(i18nc("@info:shell", "Unable to fetch collection with remote ID '%1', %2",
0173                                      collection.remoteId(), job->errorString()));
0174 
0175         if (!isDryRun()) {
0176             CollectionCreateJob *createJob = new CollectionCreateJob(collection, this);
0177             createJob->setProperty("rid", collection.remoteId());
0178             connect(createJob, &KJob::result, this, &ImportCommand::onCollectionCreated);
0179         } else {
0180             QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0181         }
0182     } else {
0183         CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
0184         mCollectionMap.insert(collection.remoteId(), fetchJob->collections().first());
0185         QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0186     }
0187 }
0188 
0189 void ImportCommand::onCollectionCreated(KJob *job)
0190 {
0191     if (!checkJobResult(job, i18nc("@info:shell", "Unable to create collection with remote ID '%1'", job->property("rid").toString()))) return;
0192     CollectionCreateJob *createJob = qobject_cast<CollectionCreateJob *>(job);
0193     mCollectionMap.insert(job->property("rid").toString(), createJob->collection());
0194     QMetaObject::invokeMethod(this, "processNextCollection", Qt::QueuedConnection);
0195 }
0196 
0197 void ImportCommand::processNextCollectionFromMap()
0198 {
0199     if (mCollectionMap.isEmpty()) {
0200         emit finished(NoError);
0201         return;
0202     }
0203 
0204     QString rid = mCollectionMap.keys().at(0);
0205     Collection newCollection = mCollectionMap.take(rid);
0206     Collection oldCollection = mDocument->collectionByRemoteId(rid);
0207 
0208     ErrorReporter::progress(i18nc("@info:shell", "Processing items for '%1'", newCollection.name()));
0209 
0210     mItemQueue = mDocument->items(oldCollection, true);
0211     mCurrentCollection = newCollection;
0212 
0213     QMetaObject::invokeMethod(this, "processNextItemFromQueue", Qt::QueuedConnection);
0214 }
0215 
0216 void ImportCommand::processNextItemFromQueue()
0217 {
0218     if (mItemQueue.isEmpty()) {
0219         QMetaObject::invokeMethod(this, "processNextCollectionFromMap", Qt::QueuedConnection);
0220         return;
0221     }
0222 
0223     Item item = mItemQueue.takeFirst();
0224     ItemCreateJob *createJob = new ItemCreateJob(item, mCurrentCollection, this);
0225     connect(createJob, &KJob::result, this, &ImportCommand::onItemCreated);
0226 }
0227 
0228 void ImportCommand::onItemCreated(KJob *job)
0229 {
0230     if (!checkJobResult(job, i18nc("@info:shell", "Error creating item, %1", job->errorString()))) return;
0231     ItemCreateJob *itemCreateJob = qobject_cast<ItemCreateJob *>(job);
0232     ErrorReporter::progress(i18nc("@info:shell", "Created item '%1'", itemCreateJob->item().remoteId()));
0233     QMetaObject::invokeMethod(this, "processNextItemFromQueue", Qt::QueuedConnection);
0234 }