Akonadi

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

KDE's Doxygen guidelines are available online.