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 | |
26 | using namespace Akonadi; |
27 | |
28 | RecursiveMover::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 | |
37 | void 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 | |
47 | void 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 | |
54 | void 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 | |
91 | void 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 | |
114 | void 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 | |
133 | void 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 | |
153 | void 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 | |
179 | void 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 | |
196 | void 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 | |
212 | void 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 | |