File indexing completed on 2025-01-05 04:46:24

0001 /*
0002     SPDX-FileCopyrightText: 2012 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "collectionfetchjob.h"
0008 #include "collectionfetchscope.h"
0009 #include "itemfetchjob.h"
0010 #include "itemfetchscope.h"
0011 #include "recursivemover_p.h"
0012 
0013 using namespace Akonadi;
0014 
0015 RecursiveMover::RecursiveMover(AgentBasePrivate *parent)
0016     : KCompositeJob(parent)
0017     , m_agentBase(parent)
0018     , m_currentAction(None)
0019 {
0020 }
0021 
0022 void RecursiveMover::start()
0023 {
0024     Q_ASSERT(receivers(SIGNAL(result(KJob *))));
0025 
0026     auto job = new CollectionFetchJob(m_movedCollection, CollectionFetchJob::Recursive, this);
0027     connect(job, &CollectionFetchJob::finished, this, &RecursiveMover::collectionListResult);
0028     addSubjob(job);
0029     ++m_runningJobs;
0030 }
0031 
0032 void RecursiveMover::setCollection(const Collection &collection, const Collection &parentCollection)
0033 {
0034     m_movedCollection = collection;
0035     m_collections.insert(collection.id(), m_movedCollection);
0036     m_collections.insert(parentCollection.id(), parentCollection);
0037 }
0038 
0039 void RecursiveMover::collectionListResult(KJob *job)
0040 {
0041     Q_ASSERT(m_pendingCollections.isEmpty());
0042     --m_runningJobs;
0043 
0044     if (job->error()) {
0045         return; // error handling is in the base class
0046     }
0047 
0048     // build a parent -> children map for the following topological sorting
0049     // while we are iterating anyway, also fill m_collections here
0050     auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
0051     QHash<Collection::Id, Collection::List> colTree;
0052     const Akonadi::Collection::List lstCol = fetchJob->collections();
0053     for (const Collection &col : lstCol) {
0054         colTree[col.parentCollection().id()] << col;
0055         m_collections.insert(col.id(), col);
0056     }
0057 
0058     // topological sort; BFS traversal of the tree
0059     m_pendingCollections.push_back(m_movedCollection);
0060     QQueue<Collection> toBeProcessed;
0061     toBeProcessed.enqueue(m_movedCollection);
0062     while (!toBeProcessed.isEmpty()) {
0063         const Collection col = toBeProcessed.dequeue();
0064         const Collection::List children = colTree.value(col.id());
0065         if (children.isEmpty()) {
0066             continue;
0067         }
0068         m_pendingCollections += children;
0069         for (const Collection &child : children) {
0070             toBeProcessed.enqueue(child);
0071         }
0072     }
0073 
0074     replayNextCollection();
0075 }
0076 
0077 void RecursiveMover::collectionFetchResult(KJob *job)
0078 {
0079     Q_ASSERT(m_currentCollection.isValid());
0080     --m_runningJobs;
0081 
0082     if (job->error()) {
0083         return; // error handling is in the base class
0084     }
0085 
0086     auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
0087     if (fetchJob->collections().size() == 1) {
0088         m_currentCollection = fetchJob->collections().at(0);
0089         m_currentCollection.setParentCollection(m_collections.value(m_currentCollection.parentCollection().id()));
0090         m_collections.insert(m_currentCollection.id(), m_currentCollection);
0091     } else {
0092         // already deleted, move on
0093     }
0094 
0095     if (!m_runningJobs && m_pendingReplay) {
0096         replayNext();
0097     }
0098 }
0099 
0100 void RecursiveMover::itemListResult(KJob *job)
0101 {
0102     --m_runningJobs;
0103 
0104     if (job->error()) {
0105         return; // error handling is in the base class
0106     }
0107     const Akonadi::Item::List lstItems = qobject_cast<ItemFetchJob *>(job)->items();
0108     for (const Item &item : lstItems) {
0109         if (item.remoteId().isEmpty()) {
0110             m_pendingItems.push_back(item);
0111         }
0112     }
0113 
0114     if (!m_runningJobs && m_pendingReplay) {
0115         replayNext();
0116     }
0117 }
0118 
0119 void RecursiveMover::itemFetchResult(KJob *job)
0120 {
0121     Q_ASSERT(m_currentAction == None);
0122     --m_runningJobs;
0123 
0124     if (job->error()) {
0125         return; // error handling is in the base class
0126     }
0127 
0128     auto fetchJob = qobject_cast<ItemFetchJob *>(job);
0129     if (fetchJob->items().size() == 1) {
0130         m_currentAction = AddItem;
0131         m_agentBase->itemAdded(fetchJob->items().at(0), m_currentCollection);
0132     } else {
0133         // deleted since we started, skip
0134         m_currentItem = Item();
0135         replayNextItem();
0136     }
0137 }
0138 
0139 void RecursiveMover::replayNextCollection()
0140 {
0141     if (!m_pendingCollections.isEmpty()) {
0142         m_currentCollection = m_pendingCollections.takeFirst();
0143         auto job = new ItemFetchJob(m_currentCollection, this);
0144         connect(job, &ItemFetchJob::result, this, &RecursiveMover::itemListResult);
0145         addSubjob(job);
0146         ++m_runningJobs;
0147 
0148         if (m_currentCollection.remoteId().isEmpty()) {
0149             Q_ASSERT(m_currentAction == None);
0150             m_currentAction = AddCollection;
0151             m_agentBase->collectionAdded(m_currentCollection, m_collections.value(m_currentCollection.parentCollection().id()));
0152             return;
0153         } else {
0154             // replayNextItem(); - but waiting for the fetch job to finish first
0155             m_pendingReplay = true;
0156             return;
0157         }
0158     } else {
0159         // nothing left to do
0160         emitResult();
0161     }
0162 }
0163 
0164 void RecursiveMover::replayNextItem()
0165 {
0166     Q_ASSERT(m_currentCollection.isValid());
0167     if (m_pendingItems.isEmpty()) {
0168         replayNextCollection(); // all items processed here
0169         return;
0170     } else {
0171         Q_ASSERT(m_currentAction == None);
0172         m_currentItem = m_pendingItems.takeFirst();
0173         auto job = new ItemFetchJob(m_currentItem, this);
0174         job->fetchScope().fetchFullPayload();
0175         connect(job, &ItemFetchJob::result, this, &RecursiveMover::itemFetchResult);
0176         addSubjob(job);
0177         ++m_runningJobs;
0178     }
0179 }
0180 
0181 void RecursiveMover::changeProcessed()
0182 {
0183     Q_ASSERT(m_currentAction != None);
0184 
0185     if (m_currentAction == AddCollection) {
0186         Q_ASSERT(m_currentCollection.isValid());
0187         auto job = new CollectionFetchJob(m_currentCollection, CollectionFetchJob::Base, this);
0188         job->fetchScope().setAncestorRetrieval(CollectionFetchScope::All);
0189         connect(job, &CollectionFetchJob::result, this, &RecursiveMover::collectionFetchResult);
0190         addSubjob(job);
0191         ++m_runningJobs;
0192     }
0193 
0194     m_currentAction = None;
0195 }
0196 
0197 void RecursiveMover::replayNext()
0198 {
0199     // wait for runnings jobs to finish before actually doing the replay
0200     if (m_runningJobs) {
0201         m_pendingReplay = true;
0202         return;
0203     }
0204 
0205     m_pendingReplay = false;
0206 
0207     if (m_currentCollection.isValid()) {
0208         replayNextItem();
0209     } else {
0210         replayNextCollection();
0211     }
0212 }
0213 
0214 #include "moc_recursivemover_p.cpp"