Akonadi

collectionfetchjob.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "collectionfetchjob.h"
8
9#include "collection_p.h"
10#include "collectionfetchscope.h"
11#include "collectionutils.h"
12#include "job_p.h"
13#include "private/protocol_p.h"
14#include "protocolhelper_p.h"
15
16#include "akonadicore_debug.h"
17
18#include <KLocalizedString>
19
20#include <QHash>
21#include <QObject>
22#include <QTimer>
23
24using namespace Akonadi;
25
26class Akonadi::CollectionFetchJobPrivate : public JobPrivate
27{
28public:
29 explicit CollectionFetchJobPrivate(CollectionFetchJob *parent)
30 : JobPrivate(parent)
31 {
32 mEmitTimer.setSingleShot(true);
33 mEmitTimer.setInterval(std::chrono::milliseconds{100});
34 }
35
36 void init()
37 {
38 QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() {
39 timeout();
40 });
41 }
42
43 Q_DECLARE_PUBLIC(CollectionFetchJob)
44
46 Collection mBase;
47 Collection::List mBaseList;
48 Collection::List mCollections;
50 Collection::List mPendingCollections;
51 QTimer mEmitTimer;
52 bool mBasePrefetch = false;
53 Collection::List mPrefetchList;
54
55 void aboutToFinish() override
56 {
57 timeout();
58 }
59
60 void timeout()
61 {
63
64 mEmitTimer.stop(); // in case we are called by result()
65 if (!mPendingCollections.isEmpty()) {
66 if (!q->error() || mScope.ignoreRetrievalErrors()) {
67 Q_EMIT q->collectionsReceived(mPendingCollections);
68 }
69 mPendingCollections.clear();
70 }
71 }
72
73 void subJobCollectionReceived(const Akonadi::Collection::List &collections)
74 {
75 mPendingCollections += collections;
76 if (!mEmitTimer.isActive()) {
77 mEmitTimer.start();
78 }
79 }
80
81 QString jobDebuggingString() const override
82 {
83 if (mBase.isValid()) {
84 return QStringLiteral("Collection Id %1").arg(mBase.id());
85 } else if (CollectionUtils::hasValidHierarchicalRID(mBase)) {
86 // return QLatin1StringView("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1StringView(", ")) + QLatin1Char(')');
87 return QStringLiteral("HRID chain");
88 } else {
89 return QStringLiteral("Collection RemoteId %1").arg(mBase.remoteId());
90 }
91 }
92
93 bool jobFailed(KJob *job)
94 {
96 if (mScope.ignoreRetrievalErrors()) {
97 int error = job->error();
98 if (error && !q->error()) {
99 q->setError(error);
100 q->setErrorText(job->errorText());
101 }
102
104 } else {
105 return job->error();
106 }
107 }
108};
109
111 : Job(new CollectionFetchJobPrivate(this), parent)
112{
114 d->init();
115
116 d->mBase = collection;
117 d->mType = type;
118}
119
121 : Job(new CollectionFetchJobPrivate(this), parent)
122{
124 d->init();
125
126 Q_ASSERT(!cols.isEmpty());
127 if (cols.size() == 1) {
128 d->mBase = cols.first();
129 } else {
130 d->mBaseList = cols;
131 }
132 d->mType = CollectionFetchJob::Base;
133}
134
136 : Job(new CollectionFetchJobPrivate(this), parent)
137{
139 d->init();
140
141 Q_ASSERT(!cols.isEmpty());
142 if (cols.size() == 1) {
143 d->mBase = cols.first();
144 } else {
145 d->mBaseList = cols;
146 }
147 d->mType = type;
148}
149
151 : Job(new CollectionFetchJobPrivate(this), parent)
152{
154 d->init();
155
156 Q_ASSERT(!cols.isEmpty());
157 if (cols.size() == 1) {
158 d->mBase = Collection(cols.first());
159 } else {
160 for (Collection::Id id : cols) {
161 d->mBaseList.append(Collection(id));
162 }
163 }
164 d->mType = type;
165}
166
168
170{
171 Q_D(const CollectionFetchJob);
172
173 return d->mCollections;
174}
175
177{
179
180 if (!d->mBaseList.isEmpty()) {
181 if (d->mType == Recursive) {
182 // Because doStart starts several subjobs and @p cols could contain descendants of
183 // other elements in the list, if type is Recursive, we could end up with duplicates in the result.
184 // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors,
185 // Iterate over that result removing intersections and then perform the Recursive fetch on
186 // the remainder.
187 d->mBasePrefetch = true;
188 // No need to connect to the collectionsReceived signal here. This job is internal. The
189 // result needs to be filtered through filterDescendants before it is useful.
190 new CollectionFetchJob(d->mBaseList, NonOverlappingRoots, this);
191 } else if (d->mType == NonOverlappingRoots) {
192 for (const Collection &col : std::as_const(d->mBaseList)) {
193 // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated)
194 // result needs to be filtered through filterDescendants before it is useful.
195 auto subJob = new CollectionFetchJob(col, Base, this);
196 subJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All);
197 }
198 } else {
199 for (const Collection &col : std::as_const(d->mBaseList)) {
200 auto subJob = new CollectionFetchJob(col, d->mType, this);
202 d->subJobCollectionReceived(cols);
203 });
204 subJob->setFetchScope(fetchScope());
205 }
206 }
207 return;
208 }
209
210 if (!d->mBase.isValid() && d->mBase.remoteId().isEmpty()) {
212 setErrorText(i18n("Invalid collection given."));
213 emitResult();
214 return;
215 }
216
217 const auto cmd = Protocol::FetchCollectionsCommandPtr::create(ProtocolHelper::entityToScope(d->mBase));
218 switch (d->mType) {
219 case Base:
220 cmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection);
221 break;
223 cmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection);
224 break;
226 cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections);
227 break;
228 default:
229 Q_ASSERT(false);
230 }
231 cmd->setResource(d->mScope.resource());
232 cmd->setMimeTypes(d->mScope.contentMimeTypes());
233
234 switch (d->mScope.listFilter()) {
236 cmd->setDisplayPref(true);
237 break;
239 cmd->setSyncPref(true);
240 break;
242 cmd->setIndexPref(true);
243 break;
245 cmd->setEnabled(true);
246 break;
248 break;
249 default:
250 Q_ASSERT(false);
251 }
252
253 cmd->setFetchStats(d->mScope.includeStatistics());
254 switch (d->mScope.ancestorRetrieval()) {
256 cmd->setAncestorsDepth(Protocol::Ancestor::NoAncestor);
257 break;
259 cmd->setAncestorsDepth(Protocol::Ancestor::ParentAncestor);
260 break;
262 cmd->setAncestorsDepth(Protocol::Ancestor::AllAncestors);
263 break;
264 }
265 if (d->mScope.ancestorRetrieval() != CollectionFetchScope::None) {
266 cmd->setAncestorsAttributes(d->mScope.ancestorFetchScope().attributes());
267 }
268
269 d->sendCommand(cmd);
270}
271
273{
275
276 if (d->mBasePrefetch || d->mType == NonOverlappingRoots) {
277 return false;
278 }
279
280 if (!response->isResponse() || response->type() != Protocol::Command::FetchCollections) {
281 return Job::doHandleResponse(tag, response);
282 }
283
284 const auto &resp = Protocol::cmdCast<Protocol::FetchCollectionsResponse>(response);
285 // Invalid response (no ID) means this was the last response
286 if (resp.id() == -1) {
287 return true;
288 }
289
290 Collection collection = ProtocolHelper::parseCollection(resp, true);
291 if (!collection.isValid()) {
292 return false;
293 }
294
295 collection.d_ptr->resetChangeLog();
296 d->mCollections.append(collection);
297 d->mPendingCollections.append(collection);
298 if (!d->mEmitTimer.isActive()) {
299 d->mEmitTimer.start();
300 }
301
302 return false;
303}
304
305static Collection::List filterDescendants(const Collection::List &list)
306{
307 Collection::List result;
308
310 ids.reserve(list.count());
311 for (const Collection &collection : list) {
312 QList<Collection::Id> ancestors;
313 Collection parent = collection.parentCollection();
314 ancestors << parent.id();
315 if (parent != Collection::root()) {
316 while (parent.parentCollection() != Collection::root()) {
317 parent = parent.parentCollection();
318 QList<Collection::Id>::iterator i = std::lower_bound(ancestors.begin(), ancestors.end(), parent.id());
319 ancestors.insert(i, parent.id());
320 }
321 }
322 ids << ancestors;
323 }
324
325 QSet<Collection::Id> excludeList;
326 for (const Collection &collection : list) {
327 int i = 0;
328 for (const QList<Collection::Id> &ancestors : std::as_const(ids)) {
329 if (std::binary_search(ancestors.cbegin(), ancestors.cend(), collection.id())) {
330 excludeList.insert(list.at(i).id());
331 }
332 ++i;
333 }
334 }
335
336 for (const Collection &collection : list) {
337 if (!excludeList.contains(collection.id())) {
338 result.append(collection);
339 }
340 }
341
342 return result;
343}
344
346{
348
350 Q_ASSERT(job);
351
352 if (d->mType == NonOverlappingRoots) {
353 d->mPrefetchList += list->collections();
354 } else if (!d->mBasePrefetch) {
355 d->mCollections += list->collections();
356 }
357
358 if (d_ptr->mCurrentSubJob == job && !d->jobFailed(job)) {
359 if (job->error()) {
360 qCWarning(AKONADICORE_LOG) << "Error during CollectionFetchJob: " << job->errorString();
361 }
362 d_ptr->mCurrentSubJob = nullptr;
363 removeSubjob(job);
364 QTimer::singleShot(0, this, [d]() {
365 d->startNext();
366 });
367 } else {
368 Job::slotResult(job);
369 }
370
371 if (d->mBasePrefetch) {
372 d->mBasePrefetch = false;
373 const Collection::List roots = list->collections();
375 if (!job->error()) {
376 for (const Collection &col : roots) {
377 auto subJob = new CollectionFetchJob(col, d->mType, this);
379 d->subJobCollectionReceived(cols);
380 });
381 subJob->setFetchScope(fetchScope());
382 }
383 }
384 // No result yet.
385 } else if (d->mType == NonOverlappingRoots) {
386 if (!d->jobFailed(job) && !hasSubjobs()) {
387 const Collection::List result = filterDescendants(d->mPrefetchList);
388 d->mPendingCollections += result;
389 d->mCollections = result;
390 d->delayedEmitResult();
391 }
392 } else {
393 if (!d->jobFailed(job) && !hasSubjobs()) {
394 d->delayedEmitResult();
395 }
396 }
397}
398
400{
402 d->mScope = scope;
403}
404
410
411#include "moc_collectionfetchjob.cpp"
Job that fetches collections from the Akonadi storage.
void setFetchScope(const CollectionFetchScope &fetchScope)
Sets the collection fetch scope.
bool doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) override
This method should be reimplemented in the concrete jobs in case you want to handle incoming data.
void doStart() override
This method must be reimplemented in the concrete jobs.
CollectionFetchScope & fetchScope()
Returns the collection fetch scope.
Type
Describes the type of fetch depth.
@ Recursive
List all sub-collections.
@ FirstLevel
Only list direct sub-collections of the base collection.
@ NonOverlappingRoots
List the roots of a list of fetched collections.
@ Base
Only fetch the base collection.
CollectionFetchJob(const Collection &collection, Type type=FirstLevel, QObject *parent=nullptr)
Creates a new collection fetch job.
void collectionsReceived(const Akonadi::Collection::List &collections)
This signal is emitted whenever the job has received collections.
~CollectionFetchJob() override
Destroys the collection fetch job.
Collection::List collections() const
Returns the list of fetched collection.
Specifies which parts of a collection should be fetched from the Akonadi storage.
@ NoFilter
No filtering, retrieve all collections.
@ Index
Only retrieve collections for indexing, taking the local preference and enabled into account.
@ Display
Only retrieve collections for display, taking the local preference and enabled into account.
@ Sync
Only retrieve collections for synchronization, taking the local preference and enabled into account.
@ Enabled
Only retrieve enabled collections, ignoring the local preference.
bool ignoreRetrievalErrors() const
Returns whether retrieval errors should be ignored.
@ Parent
Only retrieve the immediate parent collection.
@ All
Retrieve all ancestors, up to Collection::root()
@ None
No ancestor retrieval at all (the default)
Represents a collection of PIM items.
Definition collection.h:62
qint64 Id
Describes the unique id type.
Definition collection.h:79
static Collection root()
Returns the root collection.
Collection parentCollection() const
Returns the parent collection of this object.
QString remoteId() const
Returns the remote id of the collection.
Base class for all actions in the Akonadi storage.
Definition job.h:81
virtual bool doHandleResponse(qint64 tag, const Protocol::CommandPtr &response)
This method should be reimplemented in the concrete jobs in case you want to handle incoming data.
Definition job.cpp:381
@ Unknown
Unknown error.
Definition job.h:102
@ ProtocolVersionMismatch
The server protocol version is too old or too new.
Definition job.h:100
@ ConnectionFailed
The connection to the Akonadi server failed.
Definition job.h:99
@ UserCanceled
The user canceled this job.
Definition job.h:101
bool removeSubjob(KJob *job) override
Removes the given subjob of this job.
Definition job.cpp:369
bool hasSubjobs() const
virtual void slotResult(KJob *job)
void setErrorText(const QString &errorText)
virtual QString errorString() const
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
QString errorText() const
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
A glue between Qt and the standard library.
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
const_iterator cbegin() const const
const_iterator cend() const const
void clear()
qsizetype count() const const
iterator end()
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
void reserve(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
QString arg(Args &&... args) const const
void setInterval(int msec)
bool isActive() const const
void setSingleShot(bool singleShot)
void start()
void stop()
void timeout()
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.