1/*
2 Copyright (c) 2012 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "recursivemover_p.h"
21#include "collectionfetchjob.h"
22#include "itemfetchjob.h"
23#include "itemfetchscope.h"
24#include "collectionfetchscope.h"
25
26using namespace Akonadi;
27
28RecursiveMover::RecursiveMover(AgentBasePrivate *parent)
29 : KCompositeJob(parent)
30 , m_agentBase(parent)
31 , m_currentAction(None)
32 , m_runningJobs(0)
33 , m_pendingReplay(false)
34{
35}
36
37void RecursiveMover::start()
38{
39 Q_ASSERT(receivers(SIGNAL(result(KJob*))));
40
41 CollectionFetchJob *job = new CollectionFetchJob(m_movedCollection, CollectionFetchJob::Recursive, this);
42 connect(job, SIGNAL(finished(KJob*)), SLOT(collectionListResult(KJob*)));
43 addSubjob(job);
44 ++m_runningJobs;
45}
46
47void RecursiveMover::setCollection(const Collection &collection, const Collection &parentCollection)
48{
49 m_movedCollection = collection;
50 m_collections.insert(collection.id(), m_movedCollection);
51 m_collections.insert(parentCollection.id(), parentCollection);
52}
53
54void RecursiveMover::collectionListResult(KJob *job)
55{
56 Q_ASSERT(m_pendingCollections.isEmpty());
57 --m_runningJobs;
58
59 if (job->error()) {
60 return; // error handling is in the base class
61 }
62
63 // build a parent -> children map for the following topological sorting
64 // while we are iterating anyway, also fill m_collections here
65 CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
66 QHash<Collection::Id, Collection::List> colTree;
67 foreach (const Collection &col, fetchJob->collections()) {
68 colTree[col.parentCollection().id()] << col;
69 m_collections.insert(col.id(), col);
70 }
71
72 // topological sort; BFS traversal of the tree
73 m_pendingCollections.push_back(m_movedCollection);
74 QQueue<Collection> toBeProcessed;
75 toBeProcessed.enqueue(m_movedCollection);
76 while (!toBeProcessed.isEmpty()) {
77 const Collection col = toBeProcessed.dequeue();
78 const Collection::List children = colTree.value(col.id());
79 if (children.isEmpty()) {
80 continue;
81 }
82 m_pendingCollections.append(children);
83 foreach (const Collection &child, children) {
84 toBeProcessed.enqueue(child);
85 }
86 }
87
88 replayNextCollection();
89}
90
91void RecursiveMover::collectionFetchResult(KJob *job)
92{
93 Q_ASSERT(m_currentCollection.isValid());
94 --m_runningJobs;
95
96 if (job->error()) {
97 return; // error handling is in the base class
98 }
99
100 CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
101 if (fetchJob->collections().size() == 1) {
102 m_currentCollection = fetchJob->collections().first();
103 m_currentCollection.setParentCollection(m_collections.value(m_currentCollection.parentCollection().id()));
104 m_collections.insert(m_currentCollection.id(), m_currentCollection);
105 } else {
106 // already deleted, move on
107 }
108
109 if (!m_runningJobs && m_pendingReplay) {
110 replayNext();
111 }
112}
113
114void RecursiveMover::itemListResult(KJob *job)
115{
116 --m_runningJobs;
117
118 if (job->error()) {
119 return; // error handling is in the base class
120 }
121
122 foreach (const Item &item, qobject_cast<ItemFetchJob *>(job)->items()) {
123 if (item.remoteId().isEmpty()) {
124 m_pendingItems.push_back(item);
125 }
126 }
127
128 if (!m_runningJobs && m_pendingReplay) {
129 replayNext();
130 }
131}
132
133void RecursiveMover::itemFetchResult(KJob *job)
134{
135 Q_ASSERT(m_currentAction == None);
136 --m_runningJobs;
137
138 if (job->error()) {
139 return; // error handling is in the base class
140 }
141
142 ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
143 if (fetchJob->items().size() == 1) {
144 m_currentAction = AddItem;
145 m_agentBase->itemAdded(fetchJob->items().first(), m_currentCollection);
146 } else {
147 // deleted since we started, skip
148 m_currentItem = Item();
149 replayNextItem();
150 }
151}
152
153void RecursiveMover::replayNextCollection()
154{
155 if (!m_pendingCollections.isEmpty()) {
156
157 m_currentCollection = m_pendingCollections.takeFirst();
158 ItemFetchJob *job = new ItemFetchJob(m_currentCollection, this);
159 connect(job, SIGNAL(result(KJob*)), SLOT(itemListResult(KJob*)));
160 addSubjob(job);
161 ++m_runningJobs;
162
163 if (m_currentCollection.remoteId().isEmpty()) {
164 Q_ASSERT(m_currentAction == None);
165 m_currentAction = AddCollection;
166 m_agentBase->collectionAdded(m_currentCollection, m_collections.value(m_currentCollection.parentCollection().id()));
167 return;
168 } else {
169 //replayNextItem(); - but waiting for the fetch job to finish first
170 m_pendingReplay = true;
171 return;
172 }
173 } else {
174 // nothing left to do
175 emitResult();
176 }
177}
178
179void RecursiveMover::replayNextItem()
180{
181 Q_ASSERT(m_currentCollection.isValid());
182 if (m_pendingItems.isEmpty()) {
183 replayNextCollection(); // all items processed here
184 return;
185 } else {
186 Q_ASSERT(m_currentAction == None);
187 m_currentItem = m_pendingItems.takeFirst();
188 ItemFetchJob *job = new ItemFetchJob(m_currentItem, this);
189 job->fetchScope().fetchFullPayload();
190 connect(job, SIGNAL(result(KJob*)), SLOT(itemFetchResult(KJob*)));
191 addSubjob(job);
192 ++m_runningJobs;
193 }
194}
195
196void RecursiveMover::changeProcessed()
197{
198 Q_ASSERT(m_currentAction != None);
199
200 if (m_currentAction == AddCollection) {
201 Q_ASSERT(m_currentCollection.isValid());
202 CollectionFetchJob *job = new CollectionFetchJob(m_currentCollection, CollectionFetchJob::Base, this);
203 job->fetchScope().setAncestorRetrieval(CollectionFetchScope::All);
204 connect(job, SIGNAL(result(KJob*)), SLOT(collectionFetchResult(KJob*)));
205 addSubjob(job);
206 ++m_runningJobs;
207 }
208
209 m_currentAction = None;
210}
211
212void RecursiveMover::replayNext()
213{
214 // wait for runnings jobs to finish before actually doing the replay
215 if (m_runningJobs) {
216 m_pendingReplay = true;
217 return;
218 }
219
220 m_pendingReplay = false;
221
222 if (m_currentCollection.isValid()) {
223 replayNextItem();
224 } else {
225 replayNextCollection();
226 }
227}
228
229#include "moc_recursivemover_p.cpp"
230