Akonadi

collectionfetchjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2006-2007 Volker Krause <[email protected]>
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 
24 using namespace Akonadi;
25 
26 class Akonadi::CollectionFetchJobPrivate : public JobPrivate
27 {
28 public:
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;
49  CollectionFetchScope mScope;
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  {
62  Q_Q(CollectionFetchJob);
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 QLatin1String("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1String(", ")) + 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  {
95  Q_Q(CollectionFetchJob);
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);
201  connect(subJob, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) {
202  d->subJobCollectionReceived(cols);
203  });
204  subJob->setFetchScope(fetchScope());
205  }
206  }
207  return;
208  }
209 
210  if (!d->mBase.isValid() && d->mBase.remoteId().isEmpty()) {
211  setError(Unknown);
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 
305 static 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 
345 void CollectionFetchJob::slotResult(KJob *job)
346 {
348 
349  auto list = qobject_cast<CollectionFetchJob *>(job);
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();
374  Q_ASSERT(!hasSubjobs());
375  if (!job->error()) {
376  for (const Collection &col : roots) {
377  auto subJob = new CollectionFetchJob(col, d->mType, this);
378  connect(subJob, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) {
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 
406 {
408  return d->mScope;
409 }
410 
411 #include "moc_collectionfetchjob.cpp"
T & first()
@ Parent
Only retrieve the immediate parent collection.
@ Sync
Only retrieve collections for synchronization, taking the local preference and enabled into account.
bool isEmpty() const const
bool isActive() const const
CollectionFetchScope & fetchScope()
Returns the collection fetch scope.
@ Unknown
Unknown error.
Definition: job.h:102
void setErrorText(const QString &errorText)
void result(KJob *job)
@ All
Retrieve all ancestors, up to Collection::root()
Type
Describes the type of fetch depth.
QCA_EXPORT void init()
int count(const T &value) const const
void doStart() override
This method must be reimplemented in the concrete jobs.
@ ProtocolVersionMismatch
The server protocol version is too old or too new.
Definition: job.h:100
void append(const T &value)
Represents a collection of PIM items.
Definition: collection.h:61
@ Display
Only retrieve collections for display, taking the local preference and enabled into account.
@ NoFilter
No filtering, retrieve all collections.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
@ ConnectionFailed
The connection to the Akonadi server failed.
Definition: job.h:99
T & first()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Job that fetches collections from the Akonadi storage.
void collectionsReceived(const Akonadi::Collection::List &collections)
This signal is emitted whenever the job has received collections.
Collection::List collections() const
Returns the list of fetched collection.
bool removeSubjob(KJob *job) override
Removes the given subjob of this job.
Definition: job.cpp:369
CollectionFetchJob(const Collection &collection, Type type=FirstLevel, QObject *parent=nullptr)
Creates a new collection fetch job.
int size() const const
void start(int msec)
Specifies which parts of a collection should be fetched from the Akonadi storage.
bool ignoreRetrievalErrors() const
Returns whether retrieval errors should be ignored.
void clear()
QString i18n(const char *text, const TYPE &arg...)
void timeout()
void setFetchScope(const CollectionFetchScope &fetchScope)
Sets the collection fetch scope.
@ Enabled
Only retrieve enabled collections, ignoring the local preference.
QString errorText() const
const T & at(int i) const const
bool isEmpty() const const
@ Index
Only retrieve collections for indexing, taking the local preference and enabled into account.
@ FirstLevel
Only list direct sub-collections of the base collection.
QList::const_iterator cend() const const
@ Base
Only fetch the base collection.
Base class for all actions in the Akonadi storage.
Definition: job.h:80
void reserve(int size)
static Collection root()
Returns the root collection.
Definition: collection.cpp:287
~CollectionFetchJob() override
Destroys the collection fetch job.
void insert(int i, const T &value)
bool contains(const T &value) const const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString remoteId() const
Returns the remote id of the collection.
Definition: collection.cpp:106
Collection parentCollection() const
Returns the parent collection of this object.
Definition: collection.cpp:187
@ NonOverlappingRoots
List the roots of a list of fetched collections.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
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
void stop()
QList::const_iterator cbegin() const const
QSet::iterator insert(const T &value)
QList::iterator begin()
int size() const const
@ Recursive
List all sub-collections.
void emitResult()
virtual QString errorString() const
@ None
No ancestor retrieval at all (the default)
int error() const
bool hasSubjobs() const
QList::iterator end()
@ UserCanceled
The user canceled this job.
Definition: job.h:101
qint64 Id
Describes the unique id type.
Definition: collection.h:79
void setError(int errorCode)
Q_D(Todo)
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.
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Jun 4 2023 03:52:46 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.