Akonadi

resourcebase.cpp
1 /*
2  SPDX-FileCopyrightText: 2006 Till Adam <[email protected]>
3  SPDX-FileCopyrightText: 2007 Volker Krause <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "resourcebase.h"
9 #include "agentbase_p.h"
10 
11 #include "akonadifull-version.h"
12 #include "collectiondeletejob.h"
13 #include "collectionsync_p.h"
14 #include "relationsync.h"
15 #include "resourceadaptor.h"
16 #include "resourcescheduler_p.h"
17 #include "tagsync.h"
18 #include "tracerinterface.h"
19 #include <QDBusConnection>
20 
21 #include "changerecorder.h"
22 #include "collectionfetchjob.h"
23 #include "collectionfetchscope.h"
24 #include "collectionmodifyjob.h"
25 #include "favoritecollectionattribute.h"
26 #include "invalidatecachejob_p.h"
27 #include "itemcreatejob.h"
28 #include "itemfetchjob.h"
29 #include "itemfetchscope.h"
30 #include "itemmodifyjob.h"
31 #include "itemmodifyjob_p.h"
32 #include "monitor_p.h"
33 #include "recursivemover_p.h"
34 #include "resourceselectjob_p.h"
35 #include "servermanager_p.h"
36 #include "session.h"
37 #include "specialcollectionattribute.h"
38 #include "tagmodifyjob.h"
39 
40 #include "akonadiagentbase_debug.h"
41 
42 #include <cstdlib>
43 #include <iterator>
44 #include <shared/akranges.h>
45 
46 #include <KAboutData>
47 #include <KLocalizedString>
48 
49 #include <QApplication>
50 #include <QHash>
51 #include <QTimer>
52 
53 using namespace Akonadi;
54 using namespace AkRanges;
55 using namespace std::chrono_literals;
56 class Akonadi::ResourceBasePrivate : public AgentBasePrivate
57 {
58  Q_OBJECT
59  Q_CLASSINFO("D-Bus Interface", "org.kde.dfaure")
60 
61 public:
62  explicit ResourceBasePrivate(ResourceBase *parent)
63  : AgentBasePrivate(parent)
64  , scheduler(nullptr)
65  , mItemSyncer(nullptr)
66  , mItemTransactionMode(ItemSync::SingleTransaction)
67  , mItemMergeMode(ItemSync::RIDMerge)
68  , mCollectionSyncer(nullptr)
69  , mTagSyncer(nullptr)
70  , mRelationSyncer(nullptr)
71  , mHierarchicalRid(false)
72  , mUnemittedProgress(0)
73  , mAutomaticProgressReporting(true)
74  , mDisableAutomaticItemDeliveryDone(false)
75  , mItemSyncBatchSize(10)
76  , mCurrentCollectionFetchJob(nullptr)
77  , mScheduleAttributeSyncBeforeCollectionSync(false)
78  {
79  Internal::setClientType(Internal::Resource);
80  mStatusMessage = defaultReadyMessage();
81  mProgressEmissionCompressor.setInterval(1000);
82  mProgressEmissionCompressor.setSingleShot(true);
83  // HACK: skip local changes of the EntityDisplayAttribute by default. Remove this for KDE5 and adjust resource implementations accordingly.
84  mKeepLocalCollectionChanges << "ENTITYDISPLAY";
85  }
86 
87  ~ResourceBasePrivate() override = default;
88 
89  Q_DECLARE_PUBLIC(ResourceBase)
90 
91  void delayedInit() override
92  {
93  const QString serviceId = ServerManager::agentServiceName(ServerManager::Resource, mId);
94  if (!QDBusConnection::sessionBus().registerService(serviceId)) {
96  if (reason.isEmpty()) {
97  reason = QStringLiteral("this service is probably running already.");
98  }
99  qCCritical(AKONADIAGENTBASE_LOG) << "Unable to register service" << serviceId << "at D-Bus:" << reason;
100 
101  if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
103  }
104  } else {
105  AgentBasePrivate::delayedInit();
106  }
107  }
108 
109  void changeProcessed() override
110  {
111  if (m_recursiveMover) {
112  m_recursiveMover->changeProcessed();
113  QTimer::singleShot(0s, m_recursiveMover.data(), &RecursiveMover::replayNext);
114  return;
115  }
116 
117  mChangeRecorder->changeProcessed();
118  if (!mChangeRecorder->isEmpty()) {
119  scheduler->scheduleChangeReplay();
120  }
121  scheduler->taskDone();
122  }
123 
124  void slotAbortRequested();
125 
126  void slotDeliveryDone(KJob *job);
127  void slotCollectionSyncDone(KJob *job);
128  void slotLocalListDone(KJob *job);
129  void slotSynchronizeCollection(const Collection &col);
130  void slotItemRetrievalCollectionFetchDone(KJob *job);
131  void slotCollectionListDone(KJob *job);
132  void slotSynchronizeCollectionAttributes(const Collection &col);
133  void slotCollectionListForAttributesDone(KJob *job);
134  void slotCollectionAttributesSyncDone(KJob *job);
135  void slotSynchronizeTags();
136  void slotSynchronizeRelations();
137  void slotAttributeRetrievalCollectionFetchDone(KJob *job);
138 
139  void slotItemSyncDone(KJob *job);
140 
141  void slotPercent(KJob *job, quint64 percent);
142  void slotDelayedEmitProgress();
143  void slotDeleteResourceCollection();
144  void slotDeleteResourceCollectionDone(KJob *job);
145  void slotCollectionDeletionDone(KJob *job);
146 
147  void slotInvalidateCache(const Akonadi::Collection &collection);
148 
149  void slotPrepareItemRetrieval(const Akonadi::Item &item);
150  void slotPrepareItemRetrievalResult(KJob *job);
151 
152  void slotPrepareItemsRetrieval(const QVector<Akonadi::Item> &item);
153  void slotPrepareItemsRetrievalResult(KJob *job);
154 
155  void changeCommittedResult(KJob *job);
156 
157  void slotRecursiveMoveReplay(RecursiveMover *mover);
158  void slotRecursiveMoveReplayResult(KJob *job);
159 
160  void slotTagSyncDone(KJob *job);
161  void slotRelationSyncDone(KJob *job);
162 
163  void slotSessionReconnected()
164  {
165  Q_Q(ResourceBase);
166 
167  new ResourceSelectJob(q->identifier());
168  }
169 
170  void createItemSyncInstanceIfMissing()
171  {
172  Q_Q(ResourceBase);
173  Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::SyncCollection,
174  "createItemSyncInstance",
175  "Calling items retrieval methods although no item retrieval is in progress");
176  if (!mItemSyncer) {
177  mItemSyncer = new ItemSync(q->currentCollection());
178  mItemSyncer->setTransactionMode(mItemTransactionMode);
179  mItemSyncer->setBatchSize(mItemSyncBatchSize);
180  mItemSyncer->setMergeMode(mItemMergeMode);
181  mItemSyncer->setDisableAutomaticDeliveryDone(mDisableAutomaticItemDeliveryDone);
182  mItemSyncer->setProperty("collection", QVariant::fromValue(q->currentCollection()));
183  connect(mItemSyncer, &KJob::percentChanged, this,
184  &ResourceBasePrivate::slotPercent); // NOLINT(google-runtime-int): ulong comes from KJob
185 
186  connect(mItemSyncer, &KJob::result, this, &ResourceBasePrivate::slotItemSyncDone);
188  }
189  Q_ASSERT(mItemSyncer);
190  }
191 
192 public Q_SLOTS:
193  // Dump the state of the scheduler
194  Q_SCRIPTABLE QString dumpToString() const
195  {
196  Q_Q(const ResourceBase);
197  return scheduler->dumpToString() + QLatin1Char('\n') + q->dumpResourceToString();
198  }
199 
200  Q_SCRIPTABLE void dump()
201  {
202  scheduler->dump();
203  }
204 
205  Q_SCRIPTABLE void clear()
206  {
207  scheduler->clear();
208  }
209 
210 protected Q_SLOTS:
211  // reimplementations from AgentbBasePrivate, containing sanity checks that only apply to resources
212  // such as making sure that RIDs are present as well as translations of cross-resource moves
213  // TODO: we could possibly add recovery code for no-RID notifications by re-enquing those to the change recorder
214  // as the corresponding Add notifications, although that contains a risk of endless fail/retry loops
215 
216  void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override
217  {
218  if (collection.remoteId().isEmpty()) {
219  changeProcessed();
220  return;
221  }
222  AgentBasePrivate::itemAdded(item, collection);
223  }
224 
225  void itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &partIdentifiers) override
226  {
227  if (item.remoteId().isEmpty()) {
228  changeProcessed();
229  return;
230  }
231  AgentBasePrivate::itemChanged(item, partIdentifiers);
232  }
233 
234  void itemsFlagsChanged(const Akonadi::Item::List &items, const QSet<QByteArray> &addedFlags, const QSet<QByteArray> &removedFlags) override
235  {
236  if (addedFlags.isEmpty() && removedFlags.isEmpty()) {
237  changeProcessed();
238  return;
239  }
240 
241  const Item::List validItems = filterValidItems(items);
242  if (validItems.isEmpty()) {
243  changeProcessed();
244  return;
245  }
246 
247  AgentBasePrivate::itemsFlagsChanged(validItems, addedFlags, removedFlags);
248  }
249 
250  void itemsTagsChanged(const Akonadi::Item::List &items, const QSet<Akonadi::Tag> &addedTags, const QSet<Akonadi::Tag> &removedTags) override
251  {
252  if (addedTags.isEmpty() && removedTags.isEmpty()) {
253  changeProcessed();
254  return;
255  }
256 
257  const Item::List validItems = filterValidItems(items);
258  if (validItems.isEmpty()) {
259  changeProcessed();
260  return;
261  }
262 
263  AgentBasePrivate::itemsTagsChanged(validItems, addedTags, removedTags);
264  }
265 
266  // TODO move the move translation code from AgentBasePrivate here, it's wrong for agents
267  void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &destination) override
268  {
269  if (item.remoteId().isEmpty() || destination.remoteId().isEmpty() || destination == source) {
270  changeProcessed();
271  return;
272  }
273  AgentBasePrivate::itemMoved(item, source, destination);
274  }
275 
276  void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) override
277  {
278  if (destination.remoteId().isEmpty() || destination == source) {
279  changeProcessed();
280  return;
281  }
282 
283  const Item::List validItems = filterValidItems(items);
284  if (validItems.isEmpty()) {
285  changeProcessed();
286  return;
287  }
288 
289  AgentBasePrivate::itemsMoved(validItems, source, destination);
290  }
291 
292  void itemRemoved(const Akonadi::Item &item) override
293  {
294  if (item.remoteId().isEmpty()) {
295  changeProcessed();
296  return;
297  }
298  AgentBasePrivate::itemRemoved(item);
299  }
300 
301  void itemsRemoved(const Akonadi::Item::List &items) override
302  {
303  const Item::List validItems = filterValidItems(items);
304  if (validItems.isEmpty()) {
305  changeProcessed();
306  return;
307  }
308 
309  AgentBasePrivate::itemsRemoved(validItems);
310  }
311 
312  void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override
313  {
314  if (parent.remoteId().isEmpty()) {
315  changeProcessed();
316  return;
317  }
318  AgentBasePrivate::collectionAdded(collection, parent);
319  }
320 
321  void collectionChanged(const Akonadi::Collection &collection) override
322  {
323  if (collection.remoteId().isEmpty()) {
324  changeProcessed();
325  return;
326  }
327  AgentBasePrivate::collectionChanged(collection);
328  }
329 
330  void collectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &partIdentifiers) override
331  {
332  if (collection.remoteId().isEmpty()) {
333  changeProcessed();
334  return;
335  }
336  AgentBasePrivate::collectionChanged(collection, partIdentifiers);
337  }
338 
339  void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination) override
340  {
341  // unknown destination or source == destination means we can't do/don't have to do anything
342  if (destination.remoteId().isEmpty() || source == destination) {
343  changeProcessed();
344  return;
345  }
346 
347  // inter-resource moves, requires we know which resources the source and destination are in though
348  if (!source.resource().isEmpty() && !destination.resource().isEmpty() && source.resource() != destination.resource()) {
349  if (source.resource() == q_ptr->identifier()) { // moved away from us
350  AgentBasePrivate::collectionRemoved(collection);
351  } else if (destination.resource() == q_ptr->identifier()) { // moved to us
352  scheduler->taskDone(); // stop change replay for now
353  auto mover = new RecursiveMover(this);
354  mover->setCollection(collection, destination);
355  scheduler->scheduleMoveReplay(collection, mover);
356  }
357  return;
358  }
359 
360  // intra-resource move, requires the moved collection to have a valid id though
361  if (collection.remoteId().isEmpty()) {
362  changeProcessed();
363  return;
364  }
365 
366  // intra-resource move, ie. something we can handle internally
367  AgentBasePrivate::collectionMoved(collection, source, destination);
368  }
369 
370  void collectionRemoved(const Akonadi::Collection &collection) override
371  {
372  if (collection.remoteId().isEmpty()) {
373  changeProcessed();
374  return;
375  }
376  AgentBasePrivate::collectionRemoved(collection);
377  }
378 
379  void tagAdded(const Akonadi::Tag &tag) override
380  {
381  if (!tag.isValid()) {
382  changeProcessed();
383  return;
384  }
385 
386  AgentBasePrivate::tagAdded(tag);
387  }
388 
389  void tagChanged(const Akonadi::Tag &tag) override
390  {
391  if (tag.remoteId().isEmpty()) {
392  changeProcessed();
393  return;
394  }
395 
396  AgentBasePrivate::tagChanged(tag);
397  }
398 
399  void tagRemoved(const Akonadi::Tag &tag) override
400  {
401  if (tag.remoteId().isEmpty()) {
402  changeProcessed();
403  return;
404  }
405 
406  AgentBasePrivate::tagRemoved(tag);
407  }
408 
409 private:
410  static Item::List filterValidItems(Item::List items)
411  {
412  items.erase(std::remove_if(items.begin(),
413  items.end(),
414  [](const auto &item) {
415  return item.remoteId().isEmpty();
416  }),
417  items.end());
418  return items;
419  }
420 
421 public:
422  // synchronize states
423  Collection currentCollection;
424 
425  ResourceScheduler *scheduler = nullptr;
426  ItemSync *mItemSyncer = nullptr;
427  ItemSync::TransactionMode mItemTransactionMode;
428  ItemSync::MergeMode mItemMergeMode;
429  CollectionSync *mCollectionSyncer = nullptr;
430  TagSync *mTagSyncer = nullptr;
431  RelationSync *mRelationSyncer = nullptr;
432  bool mHierarchicalRid;
433  QTimer mProgressEmissionCompressor;
434  int mUnemittedProgress;
435  QMap<Akonadi::Collection::Id, QVariantMap> mUnemittedAdvancedStatus;
436  bool mAutomaticProgressReporting;
437  bool mDisableAutomaticItemDeliveryDone;
438  QPointer<RecursiveMover> m_recursiveMover;
439  int mItemSyncBatchSize;
440  QSet<QByteArray> mKeepLocalCollectionChanges;
441  KJob *mCurrentCollectionFetchJob = nullptr;
442  bool mScheduleAttributeSyncBeforeCollectionSync;
443 };
444 
446  : AgentBase(new ResourceBasePrivate(this), id)
447 {
448  Q_D(ResourceBase);
449 
450  qDBusRegisterMetaType<QByteArrayList>();
451 
452  new Akonadi__ResourceAdaptor(this);
453 
454  d->scheduler = new ResourceScheduler(this);
455 
456  d->mChangeRecorder->setChangeRecordingEnabled(true);
457  d->mChangeRecorder->setCollectionMoveTranslationEnabled(false); // we deal with this ourselves
458  connect(d->mChangeRecorder, &ChangeRecorder::changesAdded, d->scheduler, &ResourceScheduler::scheduleChangeReplay);
459 
460  d->mChangeRecorder->setResourceMonitored(d->mId.toLatin1());
461  d->mChangeRecorder->fetchCollection(true);
462 
463  connect(d->scheduler, &ResourceScheduler::executeFullSync, this, &ResourceBase::retrieveCollections);
464  connect(d->scheduler, &ResourceScheduler::executeCollectionTreeSync, this, &ResourceBase::retrieveCollections);
465  connect(d->scheduler, &ResourceScheduler::executeCollectionSync, d, &ResourceBasePrivate::slotSynchronizeCollection);
466  connect(d->scheduler, &ResourceScheduler::executeCollectionAttributesSync, d, &ResourceBasePrivate::slotSynchronizeCollectionAttributes);
467  connect(d->scheduler, &ResourceScheduler::executeTagSync, d, &ResourceBasePrivate::slotSynchronizeTags);
468  connect(d->scheduler, &ResourceScheduler::executeRelationSync, d, &ResourceBasePrivate::slotSynchronizeRelations);
469  connect(d->scheduler, &ResourceScheduler::executeItemFetch, d, &ResourceBasePrivate::slotPrepareItemRetrieval);
470  connect(d->scheduler, &ResourceScheduler::executeItemsFetch, d, &ResourceBasePrivate::slotPrepareItemsRetrieval);
471  connect(d->scheduler, &ResourceScheduler::executeResourceCollectionDeletion, d, &ResourceBasePrivate::slotDeleteResourceCollection);
472  connect(d->scheduler, &ResourceScheduler::executeCacheInvalidation, d, &ResourceBasePrivate::slotInvalidateCache);
473  connect(d->scheduler, &ResourceScheduler::status, this, qOverload<int, const QString &>(&ResourceBase::status));
474  connect(d->scheduler, &ResourceScheduler::executeChangeReplay, d->mChangeRecorder, &ChangeRecorder::replayNext);
475  connect(d->scheduler, &ResourceScheduler::executeRecursiveMoveReplay, d, &ResourceBasePrivate::slotRecursiveMoveReplay);
476  connect(d->scheduler, &ResourceScheduler::fullSyncComplete, this, &ResourceBase::synchronized);
477  connect(d->scheduler, &ResourceScheduler::collectionTreeSyncComplete, this, &ResourceBase::collectionTreeSynchronized);
478  connect(d->mChangeRecorder, &ChangeRecorder::nothingToReplay, d->scheduler, &ResourceScheduler::taskDone);
479  connect(d->mChangeRecorder, &Monitor::collectionRemoved, d->scheduler, &ResourceScheduler::collectionRemoved);
480  connect(this, &ResourceBase::abortRequested, d, &ResourceBasePrivate::slotAbortRequested);
481  connect(this, &ResourceBase::synchronized, d->scheduler, &ResourceScheduler::taskDone);
482  connect(this, &ResourceBase::collectionTreeSynchronized, d->scheduler, &ResourceScheduler::taskDone);
484  connect(&d->mProgressEmissionCompressor, &QTimer::timeout, d, &ResourceBasePrivate::slotDelayedEmitProgress);
485 
486  d->scheduler->setOnline(d->mOnline);
487  if (!d->mChangeRecorder->isEmpty()) {
488  d->scheduler->scheduleChangeReplay();
489  }
490 
491  new ResourceSelectJob(identifier());
492 
493  connect(d->mChangeRecorder->session(), &Session::reconnected, d, &ResourceBasePrivate::slotSessionReconnected);
494 }
495 
496 ResourceBase::~ResourceBase() = default;
497 
499 {
500  d_func()->scheduler->scheduleFullSync();
501 }
502 
504 {
506 }
507 
509 {
510  return AgentBase::agentName();
511 }
512 
513 QString ResourceBase::parseArguments(int argc, char **argv)
514 {
515  Q_UNUSED(argc)
516 
517  QCommandLineOption identifierOption(QStringLiteral("identifier"), i18nc("@label command line option", "Resource identifier"), QStringLiteral("argument"));
518  QCommandLineParser parser;
519  parser.addOption(identifierOption);
520  parser.addHelpOption();
521  parser.addVersionOption();
522  parser.process(*qApp);
523  parser.setApplicationDescription(i18n("Akonadi Resource"));
524 
525  if (!parser.isSet(identifierOption)) {
526  qCDebug(AKONADIAGENTBASE_LOG) << "Identifier argument missing";
527  exit(1);
528  }
529 
530  const QString identifier = parser.value(identifierOption);
531 
532  if (identifier.isEmpty()) {
533  qCDebug(AKONADIAGENTBASE_LOG) << "Identifier is empty";
534  exit(1);
535  }
536 
538  QCoreApplication::setApplicationVersion(QStringLiteral(AKONADI_FULL_VERSION));
539 
540  const QFileInfo fi(QString::fromLocal8Bit(argv[0]));
541  // strip off full path and possible .exe suffix
542  const QString catalog = fi.baseName();
543 
544  auto translator = new QTranslator(qApp);
545  translator->load(catalog);
547 
548  return identifier;
549 }
550 
552 {
554  KAboutData::setApplicationData(r.aboutData());
555  return qApp->exec();
556 }
557 
558 void ResourceBasePrivate::slotAbortRequested()
559 {
560  Q_Q(ResourceBase);
561 
562  scheduler->cancelQueues();
563  q->abortActivity();
564 }
565 
567 {
568  Q_D(ResourceBase);
569  Q_ASSERT(d->scheduler->currentTask().type == ResourceScheduler::FetchItem);
570  if (!item.isValid()) {
571  d->scheduler->itemFetchDone(i18nc("@info", "Invalid item retrieved"));
572  return;
573  }
574 
575  const QSet<QByteArray> requestedParts = d->scheduler->currentTask().itemParts;
576  for (const QByteArray &part : requestedParts) {
577  if (!item.loadedPayloadParts().contains(part)) {
578  qCWarning(AKONADIAGENTBASE_LOG) << "Item does not provide part" << part;
579  }
580  }
581 
582  auto job = new ItemModifyJob(item);
583  job->d_func()->setSilent(true);
584  // FIXME: remove once the item with which we call retrieveItem() has a revision number
585  job->disableRevisionCheck();
586  connect(job, &KJob::result, d, &ResourceBasePrivate::slotDeliveryDone);
587 }
588 
589 void ResourceBasePrivate::slotDeliveryDone(KJob *job)
590 {
591  Q_Q(ResourceBase);
592  Q_ASSERT(scheduler->currentTask().type == ResourceScheduler::FetchItem);
593  if (job->error()) {
594  Q_EMIT q->error(i18nc("@info", "Error while creating item: %1", job->errorString()));
595  }
596  scheduler->itemFetchDone(QString());
597 }
598 
600 {
601  Q_D(ResourceBase);
602  Q_ASSERT(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionAttributes);
603  if (!collection.isValid()) {
604  Q_EMIT attributesSynchronized(d->scheduler->currentTask().collection.id());
605  d->scheduler->taskDone();
606  return;
607  }
608 
609  auto job = new CollectionModifyJob(collection);
610  connect(job, &KJob::result, d, &ResourceBasePrivate::slotCollectionAttributesSyncDone);
611 }
612 
613 void ResourceBasePrivate::slotCollectionAttributesSyncDone(KJob *job)
614 {
615  Q_Q(ResourceBase);
616  Q_ASSERT(scheduler->currentTask().type == ResourceScheduler::SyncCollectionAttributes);
617  if (job->error()) {
618  Q_EMIT q->error(i18nc("@info", "Error while updating collection: %1", job->errorString()));
619  }
620  Q_EMIT q->attributesSynchronized(scheduler->currentTask().collection.id());
621  scheduler->taskDone();
622 }
623 
624 void ResourceBasePrivate::slotDeleteResourceCollection()
625 {
626  Q_Q(ResourceBase);
627 
629  job->fetchScope().setResource(q->identifier());
630  connect(job, &KJob::result, this, &ResourceBasePrivate::slotDeleteResourceCollectionDone);
631 }
632 
633 void ResourceBasePrivate::slotDeleteResourceCollectionDone(KJob *job)
634 {
635  Q_Q(ResourceBase);
636  if (job->error()) {
637  Q_EMIT q->error(job->errorString());
638  scheduler->taskDone();
639  } else {
640  const auto fetchJob = static_cast<const CollectionFetchJob *>(job);
641 
642  if (!fetchJob->collections().isEmpty()) {
643  auto job = new CollectionDeleteJob(fetchJob->collections().at(0));
644  connect(job, &KJob::result, this, &ResourceBasePrivate::slotCollectionDeletionDone);
645  } else {
646  // there is no resource collection, so just ignore the request
647  scheduler->taskDone();
648  }
649  }
650 }
651 
652 void ResourceBasePrivate::slotCollectionDeletionDone(KJob *job)
653 {
654  Q_Q(ResourceBase);
655  if (job->error()) {
656  Q_EMIT q->error(job->errorString());
657  }
658 
659  scheduler->taskDone();
660 }
661 
662 void ResourceBasePrivate::slotInvalidateCache(const Akonadi::Collection &collection)
663 {
664  Q_Q(ResourceBase);
665  auto job = new InvalidateCacheJob(collection, q);
666  connect(job, &KJob::result, scheduler, &ResourceScheduler::taskDone);
667 }
668 
670 {
671  changesCommitted(Item::List() << item);
672 }
673 
675 {
676  Q_D(ResourceBase);
677  auto transaction = new TransactionSequence(this);
678  connect(transaction, &KJob::finished, d, &ResourceBasePrivate::changeCommittedResult);
679 
680  // Modify the items one-by-one, because STORE does not support mass RID change
681  for (const Item &item : items) {
682  auto job = new ItemModifyJob(item, transaction);
683  job->d_func()->setClean();
684  job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error?
685  job->setIgnorePayload(true); // we only want to reset the dirty flag and update the remote id
686  }
687 }
688 
690 {
691  Q_D(ResourceBase);
692  auto job = new CollectionModifyJob(collection);
693  connect(job, &KJob::result, d, &ResourceBasePrivate::changeCommittedResult);
694 }
695 
696 void ResourceBasePrivate::changeCommittedResult(KJob *job)
697 {
698  if (job->error()) {
699  qCWarning(AKONADIAGENTBASE_LOG) << job->errorText();
700  }
701 
702  Q_Q(ResourceBase);
703  if (qobject_cast<CollectionModifyJob *>(job)) {
704  if (job->error()) {
705  Q_EMIT q->error(i18nc("@info", "Updating local collection failed: %1.", job->errorText()));
706  }
707  mChangeRecorder->d_ptr->invalidateCache(static_cast<CollectionModifyJob *>(job)->collection());
708  } else {
709  if (job->error()) {
710  Q_EMIT q->error(i18nc("@info", "Updating local items failed: %1.", job->errorText()));
711  }
712  // Item and tag cache is invalidated by modify job
713  }
714 
715  changeProcessed();
716 }
717 
719 {
720  Q_D(ResourceBase);
721  auto job = new TagModifyJob(tag);
722  connect(job, &KJob::result, d, &ResourceBasePrivate::changeCommittedResult);
723 }
724 
725 void ResourceBase::requestItemDelivery(const QVector<qint64> &uids, const QByteArrayList &parts)
726 {
727  Q_D(ResourceBase);
728  if (!isOnline()) {
729  const QString errorMsg = i18nc("@info", "Cannot fetch item in offline mode.");
731  Q_EMIT error(errorMsg);
732  return;
733  }
734 
735  setDelayedReply(true);
736 
737  const auto items = uids | Views::transform([](const auto uid) {
738  return Item{uid};
739  })
740  | Actions::toQVector;
741 
742  const QSet<QByteArray> partSet = QSet<QByteArray>(parts.begin(), parts.end());
743  d->scheduler->scheduleItemsFetch(items, partSet, message());
744 }
745 
747 {
748  Q_D(ResourceBase);
749  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll,
750  "ResourceBase::collectionsRetrieved()",
751  "Calling collectionsRetrieved() although no collection retrieval is in progress");
752  if (!d->mCollectionSyncer) {
753  d->mCollectionSyncer = new CollectionSync(identifier());
754  d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid);
755  d->mCollectionSyncer->setKeepLocalChanges(d->mKeepLocalCollectionChanges);
756  connect(d->mCollectionSyncer, &KJob::percentChanged, d,
757  &ResourceBasePrivate::slotPercent); // NOLINT(google-runtime-int): ulong comes from KJob
758  connect(d->mCollectionSyncer, &KJob::result, d, &ResourceBasePrivate::slotCollectionSyncDone);
759  }
760  d->mCollectionSyncer->setRemoteCollections(collections);
761 }
762 
763 void ResourceBase::collectionsRetrievedIncremental(const Collection::List &changedCollections, const Collection::List &removedCollections)
764 {
765  Q_D(ResourceBase);
766  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll,
767  "ResourceBase::collectionsRetrievedIncremental()",
768  "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress");
769  if (!d->mCollectionSyncer) {
770  d->mCollectionSyncer = new CollectionSync(identifier());
771  d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid);
772  d->mCollectionSyncer->setKeepLocalChanges(d->mKeepLocalCollectionChanges);
773  connect(d->mCollectionSyncer, &KJob::percentChanged, d,
774  &ResourceBasePrivate::slotPercent); // NOLINT(google-runtime-int): ulong comes from KJob
775  connect(d->mCollectionSyncer, &KJob::result, d, &ResourceBasePrivate::slotCollectionSyncDone);
776  }
777  d->mCollectionSyncer->setRemoteCollections(changedCollections, removedCollections);
778 }
779 
781 {
782  Q_D(ResourceBase);
783  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll,
784  "ResourceBase::setCollectionStreamingEnabled()",
785  "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress");
786  if (!d->mCollectionSyncer) {
787  d->mCollectionSyncer = new CollectionSync(identifier());
788  d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid);
789  connect(d->mCollectionSyncer, &KJob::percentChanged, d,
790  &ResourceBasePrivate::slotPercent); // NOLINT(google-runtime-int): ulong comes from KJob
791  connect(d->mCollectionSyncer, &KJob::result, d, &ResourceBasePrivate::slotCollectionSyncDone);
792  }
793  d->mCollectionSyncer->setStreamingEnabled(enable);
794 }
795 
797 {
798  Q_D(ResourceBase);
799  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll,
800  "ResourceBase::collectionsRetrievalDone()",
801  "Calling collectionsRetrievalDone() although no collection retrieval is in progress");
802  // streaming enabled, so finalize the sync
803  if (d->mCollectionSyncer) {
804  d->mCollectionSyncer->retrievalDone();
805  } else {
806  // user did the sync himself, we are done now
807  // FIXME: we need the same special case for SyncAll as in slotCollectionSyncDone here!
808  d->scheduler->taskDone();
809  }
810 }
811 
813 {
814  Q_D(ResourceBase);
815  d->mKeepLocalCollectionChanges = parts;
816 }
817 
818 void ResourceBasePrivate::slotCollectionSyncDone(KJob *job)
819 {
820  Q_Q(ResourceBase);
821  mCollectionSyncer = nullptr;
822  if (job->error()) {
823  if (job->error() != Job::UserCanceled) {
824  Q_EMIT q->error(job->errorString());
825  }
826  } else {
827  if (scheduler->currentTask().type == ResourceScheduler::SyncAll) {
829  list->setFetchScope(q->changeRecorder()->collectionFetchScope());
830  list->fetchScope().fetchAttribute<SpecialCollectionAttribute>();
831  list->fetchScope().fetchAttribute<FavoriteCollectionAttribute>();
832  list->fetchScope().setResource(mId);
833  list->fetchScope().setListFilter(CollectionFetchScope::Sync);
834  connect(list, &KJob::result, this, &ResourceBasePrivate::slotLocalListDone);
835  return;
836  } else if (scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree) {
837  scheduler->scheduleCollectionTreeSyncCompletion();
838  }
839  }
840  scheduler->taskDone();
841 }
842 
843 namespace
844 {
845 bool sortCollectionsForSync(const Collection &l, const Collection &r)
846 {
848 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
849  const bool lInbox = (lType == "inbox") || (l.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0);
850 #else
851  const bool lInbox = (lType == "inbox") || (QStringView(l.remoteId()).mid(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0);
852 #endif
853  const bool lFav = l.hasAttribute<FavoriteCollectionAttribute>();
854 
856 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
857  const bool rInbox = (rType == "inbox") || (r.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0);
858 #else
859  const bool rInbox = (rType == "inbox") || (QStringView(r.remoteId()).mid(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0);
860 #endif
861  const bool rFav = r.hasAttribute<FavoriteCollectionAttribute>();
862 
863  // inbox is always first
864  if (lInbox) {
865  return true;
866  } else if (rInbox) {
867  return false;
868  }
869 
870  // favorites right after inbox
871  if (lFav) {
872  return !rInbox;
873  } else if (rFav) {
874  return lInbox;
875  }
876 
877  // trash is always last (unless it's favorite)
878  if (lType == "trash") {
879  return false;
880  } else if (rType == "trash") {
881  return true;
882  }
883 
884  // Fallback to sorting by id
885  return l.id() < r.id();
886 }
887 
888 } // namespace
889 
890 void ResourceBasePrivate::slotLocalListDone(KJob *job)
891 {
892  Q_Q(ResourceBase);
893  if (job->error()) {
894  Q_EMIT q->error(job->errorString());
895  } else {
896  Collection::List cols = static_cast<CollectionFetchJob *>(job)->collections();
897  std::sort(cols.begin(), cols.end(), sortCollectionsForSync);
898  for (const Collection &col : std::as_const(cols)) {
899  scheduler->scheduleSync(col);
900  }
901  scheduler->scheduleFullSyncCompletion();
902  }
903  scheduler->taskDone();
904 }
905 
906 void ResourceBasePrivate::slotSynchronizeCollection(const Collection &col)
907 {
908  Q_Q(ResourceBase);
909  currentCollection = col;
910  // This can happen due to FetchHelper::triggerOnDemandFetch() in the akonadi server (not an error).
911  if (!col.remoteId().isEmpty()) {
912  // check if this collection actually can contain anything
913  QStringList contentTypes = currentCollection.contentMimeTypes();
914  contentTypes.removeAll(Collection::mimeType());
915  contentTypes.removeAll(Collection::virtualMimeType());
916  if (!contentTypes.isEmpty() || col.isVirtual()) {
917  if (mAutomaticProgressReporting) {
918  Q_EMIT q->status(AgentBase::Running, i18nc("@info:status", "Syncing folder '%1'", currentCollection.displayName()));
919  }
920 
921  qCDebug(AKONADIAGENTBASE_LOG) << "Preparing collection sync of collection" << currentCollection.id() << currentCollection.displayName();
922  auto fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this);
923  fetchJob->setFetchScope(q->changeRecorder()->collectionFetchScope());
924  connect(fetchJob, &KJob::result, this, &ResourceBasePrivate::slotItemRetrievalCollectionFetchDone);
925  mCurrentCollectionFetchJob = fetchJob;
926  return;
927  }
928  }
929  scheduler->taskDone();
930 }
931 
932 void ResourceBasePrivate::slotItemRetrievalCollectionFetchDone(KJob *job)
933 {
934  Q_Q(ResourceBase);
935  mCurrentCollectionFetchJob = nullptr;
936  if (job->error()) {
937  qCWarning(AKONADIAGENTBASE_LOG) << "Failed to retrieve collection for sync: " << job->errorString();
938  q->cancelTask(i18n("Failed to retrieve collection for sync."));
939  return;
940  }
941  auto fetchJob = static_cast<Akonadi::CollectionFetchJob *>(job);
942  const Collection::List collections = fetchJob->collections();
943  if (collections.isEmpty()) {
944  qCWarning(AKONADIAGENTBASE_LOG) << "The fetch job returned empty collection set. This is unexpected.";
945  q->cancelTask(i18n("Failed to retrieve collection for sync."));
946  return;
947  }
948  q->retrieveItems(collections.at(0));
949 }
950 
952 {
953  Q_D(const ResourceBase);
954  return d->mItemSyncBatchSize;
955 }
956 
958 {
959  Q_D(ResourceBase);
960  d->mItemSyncBatchSize = batchSize;
961 }
962 
964 {
965  Q_D(ResourceBase);
966  d->mScheduleAttributeSyncBeforeCollectionSync = enable;
967 }
968 
969 void ResourceBasePrivate::slotSynchronizeCollectionAttributes(const Collection &col)
970 {
971  Q_Q(ResourceBase);
972  auto fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this);
973  fetchJob->setFetchScope(q->changeRecorder()->collectionFetchScope());
974  connect(fetchJob, &KJob::result, this, &ResourceBasePrivate::slotAttributeRetrievalCollectionFetchDone);
975  Q_ASSERT(!mCurrentCollectionFetchJob);
976  mCurrentCollectionFetchJob = fetchJob;
977 }
978 
979 void ResourceBasePrivate::slotAttributeRetrievalCollectionFetchDone(KJob *job)
980 {
981  mCurrentCollectionFetchJob = nullptr;
982  Q_Q(ResourceBase);
983  if (job->error()) {
984  qCWarning(AKONADIAGENTBASE_LOG) << "Failed to retrieve collection for attribute sync: " << job->errorString();
985  q->cancelTask(i18n("Failed to retrieve collection for attribute sync."));
986  return;
987  }
988  auto fetchJob = static_cast<Akonadi::CollectionFetchJob *>(job);
989  // FIXME: Why not call q-> directly?
990  QMetaObject::invokeMethod(q, "retrieveCollectionAttributes", Q_ARG(Akonadi::Collection, fetchJob->collections().at(0)));
991 }
992 
993 void ResourceBasePrivate::slotSynchronizeTags()
994 {
995  Q_Q(ResourceBase);
996  QMetaObject::invokeMethod(this, [q] {
997  q->retrieveTags();
998  });
999 }
1000 
1001 void ResourceBasePrivate::slotSynchronizeRelations()
1002 {
1003  Q_Q(ResourceBase);
1004  QMetaObject::invokeMethod(this, [q] {
1005  q->retrieveRelations();
1006  });
1007 }
1008 
1009 void ResourceBasePrivate::slotPrepareItemRetrieval(const Item &item)
1010 {
1011  Q_Q(ResourceBase);
1012  auto fetch = new ItemFetchJob(item, this);
1013  // we always need at least parent so we can use ItemCreateJob to merge
1014  fetch->fetchScope().setAncestorRetrieval(qMax(ItemFetchScope::Parent, q->changeRecorder()->itemFetchScope().ancestorRetrieval()));
1015  fetch->fetchScope().setCacheOnly(true);
1016  fetch->fetchScope().setFetchRemoteIdentification(true);
1017 
1018  // copy list of attributes to fetch
1019  const QSet<QByteArray> attributes = q->changeRecorder()->itemFetchScope().attributes();
1020  for (const auto &attribute : attributes) {
1021  fetch->fetchScope().fetchAttribute(attribute);
1022  }
1023 
1024  connect(fetch, &KJob::result, this, &ResourceBasePrivate::slotPrepareItemRetrievalResult);
1025 }
1026 
1027 void ResourceBasePrivate::slotPrepareItemRetrievalResult(KJob *job)
1028 {
1029  Q_Q(ResourceBase);
1030  Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::FetchItem,
1031  "ResourceBasePrivate::slotPrepareItemRetrievalResult()",
1032  "Preparing item retrieval although no item retrieval is in progress");
1033  if (job->error()) {
1034  q->cancelTask(job->errorText());
1035  return;
1036  }
1037  auto fetch = qobject_cast<ItemFetchJob *>(job);
1038  if (fetch->items().count() != 1) {
1039  q->cancelTask(i18n("The requested item no longer exists"));
1040  return;
1041  }
1042  const QSet<QByteArray> parts = scheduler->currentTask().itemParts;
1043  if (!q->retrieveItem(fetch->items().at(0), parts)) {
1044  q->cancelTask();
1045  }
1046 }
1047 
1048 void ResourceBasePrivate::slotPrepareItemsRetrieval(const QVector<Item> &items)
1049 {
1050  Q_Q(ResourceBase);
1051  auto fetch = new ItemFetchJob(items, this);
1052  // we always need at least parent so we can use ItemCreateJob to merge
1053  fetch->fetchScope().setAncestorRetrieval(qMax(ItemFetchScope::Parent, q->changeRecorder()->itemFetchScope().ancestorRetrieval()));
1054  fetch->fetchScope().setCacheOnly(true);
1055  fetch->fetchScope().setFetchRemoteIdentification(true);
1056  // It's possible that one or more items were removed before this task was
1057  // executed, so ignore it and just handle the rest.
1058  fetch->fetchScope().setIgnoreRetrievalErrors(true);
1059 
1060  // copy list of attributes to fetch
1061  const QSet<QByteArray> attributes = q->changeRecorder()->itemFetchScope().attributes();
1062  for (const auto &attribute : attributes) {
1063  fetch->fetchScope().fetchAttribute(attribute);
1064  }
1065 
1066  connect(fetch, &KJob::result, this, &ResourceBasePrivate::slotPrepareItemsRetrievalResult);
1067 }
1068 
1069 void ResourceBasePrivate::slotPrepareItemsRetrievalResult(KJob *job)
1070 {
1071  Q_Q(ResourceBase);
1072  Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::FetchItems,
1073  "ResourceBasePrivate::slotPrepareItemsRetrievalResult()",
1074  "Preparing items retrieval although no items retrieval is in progress");
1075  if (job->error()) {
1076  q->cancelTask(job->errorText());
1077  return;
1078  }
1079  auto fetch = qobject_cast<ItemFetchJob *>(job);
1080  const auto items = fetch->items();
1081  if (items.isEmpty()) {
1082  q->cancelTask();
1083  return;
1084  }
1085 
1086  const QSet<QByteArray> parts = scheduler->currentTask().itemParts;
1087  Q_ASSERT(items.first().parentCollection().isValid());
1088  if (!q->retrieveItems(items, parts)) {
1089  q->cancelTask();
1090  }
1091 }
1092 
1093 void ResourceBasePrivate::slotRecursiveMoveReplay(RecursiveMover *mover)
1094 {
1095  Q_ASSERT(mover);
1096  Q_ASSERT(!m_recursiveMover);
1097  m_recursiveMover = mover;
1098  connect(mover, &KJob::result, this, &ResourceBasePrivate::slotRecursiveMoveReplayResult);
1099  mover->start();
1100 }
1101 
1102 void ResourceBasePrivate::slotRecursiveMoveReplayResult(KJob *job)
1103 {
1104  Q_Q(ResourceBase);
1105  m_recursiveMover = nullptr;
1106 
1107  if (job->error()) {
1108  q->deferTask();
1109  return;
1110  }
1111 
1112  changeProcessed();
1113 }
1114 
1116 {
1117  Q_D(ResourceBase);
1118  // streaming enabled, so finalize the sync
1119  if (d->mItemSyncer) {
1120  d->mItemSyncer->deliveryDone();
1121  } else {
1122  if (d->scheduler->currentTask().type == ResourceScheduler::FetchItems) {
1123  d->scheduler->currentTask().sendDBusReplies(QString());
1124  }
1125  // user did the sync himself, we are done now
1126  d->scheduler->taskDone();
1127  }
1128 }
1129 
1131 {
1132  Q_D(ResourceBase);
1133  d->scheduler->scheduleResourceCollectionDeletion();
1134 }
1135 
1137 {
1138  Q_D(ResourceBase);
1139  d->scheduler->scheduleCacheInvalidation(collection);
1140 }
1141 
1143 {
1144  Q_D(const ResourceBase);
1145  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollection,
1146  "ResourceBase::currentCollection()",
1147  "Trying to access current collection although no item retrieval is in progress");
1148  return d->currentCollection;
1149 }
1150 
1152 {
1153  Q_D(const ResourceBase);
1154  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::FetchItem,
1155  "ResourceBase::currentItem()",
1156  "Trying to access current item although no item retrieval is in progress");
1157  return d->scheduler->currentTask().items[0];
1158 }
1159 
1161 {
1162  Q_D(const ResourceBase);
1163  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::FetchItems,
1164  "ResourceBase::currentItems()",
1165  "Trying to access current items although no items retrieval is in progress");
1166  return d->scheduler->currentTask().items;
1167 }
1168 
1170 {
1171  d_func()->scheduler->scheduleCollectionTreeSync();
1172 }
1173 
1175 {
1176  d_func()->scheduler->scheduleTagSync();
1177 }
1178 
1180 {
1181  d_func()->scheduler->scheduleRelationSync();
1182 }
1183 
1185 {
1186  Q_D(ResourceBase);
1187  if (d->mCurrentCollectionFetchJob) {
1188  d->mCurrentCollectionFetchJob->kill();
1189  d->mCurrentCollectionFetchJob = nullptr;
1190  }
1191  switch (d->scheduler->currentTask().type) {
1192  case ResourceScheduler::FetchItem:
1193  itemRetrieved(Item()); // sends the error reply and
1194  break;
1195  case ResourceScheduler::FetchItems:
1197  break;
1198  case ResourceScheduler::ChangeReplay:
1199  d->changeProcessed();
1200  break;
1201  case ResourceScheduler::SyncCollectionTree:
1202  case ResourceScheduler::SyncAll:
1203  if (d->mCollectionSyncer) {
1204  d->mCollectionSyncer->rollback();
1205  } else {
1206  d->scheduler->taskDone();
1207  }
1208  break;
1209  case ResourceScheduler::SyncCollection:
1210  if (d->mItemSyncer) {
1211  d->mItemSyncer->rollback();
1212  } else {
1213  d->scheduler->taskDone();
1214  }
1215  break;
1216  default:
1217  d->scheduler->taskDone();
1218  }
1219 }
1220 
1222 {
1223  cancelTask();
1224 
1225  Q_EMIT error(msg);
1226 }
1227 
1229 {
1230  Q_D(ResourceBase);
1231  qCDebug(AKONADIAGENTBASE_LOG) << "Deferring task" << d->scheduler->currentTask();
1232  // Deferring a CollectionSync is just not implemented.
1233  // We'd need to d->mItemSyncer->rollback() but also to NOT call taskDone in slotItemSyncDone() here...
1234  Q_ASSERT(!d->mItemSyncer);
1235  d->scheduler->deferTask();
1236 }
1237 
1239 {
1240  d_func()->scheduler->setOnline(state);
1241 }
1242 
1243 void ResourceBase::synchronizeCollection(qint64 collectionId)
1244 {
1245  synchronizeCollection(collectionId, false);
1246 }
1247 
1248 void ResourceBase::synchronizeCollection(qint64 collectionId, bool recursive)
1249 {
1250  Q_D(ResourceBase);
1251  auto job = new CollectionFetchJob(Collection(collectionId), recursive ? CollectionFetchJob::Recursive : CollectionFetchJob::Base);
1252  job->setFetchScope(changeRecorder()->collectionFetchScope());
1253  job->fetchScope().setResource(identifier());
1254  job->fetchScope().setListFilter(CollectionFetchScope::Sync);
1255  connect(job, &KJob::result, d, &ResourceBasePrivate::slotCollectionListDone);
1256 }
1257 
1258 void ResourceBasePrivate::slotCollectionListDone(KJob *job)
1259 {
1260  if (!job->error()) {
1261  const Collection::List list = static_cast<CollectionFetchJob *>(job)->collections();
1262  for (const Collection &collection : list) {
1263  // We also get collections that should not be synced but are part of the tree.
1264  if (collection.shouldList(Collection::ListSync)) {
1265  if (mScheduleAttributeSyncBeforeCollectionSync) {
1266  scheduler->scheduleAttributesSync(collection);
1267  }
1268  scheduler->scheduleSync(collection);
1269  }
1270  }
1271  } else {
1272  qCWarning(AKONADIAGENTBASE_LOG) << "Failed to fetch collection for collection sync: " << job->errorString();
1273  }
1274 }
1275 
1277 {
1278  Q_D(ResourceBase);
1279  d->scheduler->scheduleAttributesSync(col);
1280 }
1281 
1283 {
1284  Q_D(ResourceBase);
1285  auto job = new CollectionFetchJob(Collection(collectionId), CollectionFetchJob::Base);
1286  job->setFetchScope(changeRecorder()->collectionFetchScope());
1287  job->fetchScope().setResource(identifier());
1288  connect(job, &KJob::result, d, &ResourceBasePrivate::slotCollectionListForAttributesDone);
1289 }
1290 
1291 void ResourceBasePrivate::slotCollectionListForAttributesDone(KJob *job)
1292 {
1293  if (!job->error()) {
1294  const Collection::List list = static_cast<CollectionFetchJob *>(job)->collections();
1295  if (!list.isEmpty()) {
1296  const Collection &col = list.first();
1297  scheduler->scheduleAttributesSync(col);
1298  }
1299  }
1300  // TODO: error handling
1301 }
1302 
1304 {
1305  qCDebug(AKONADIAGENTBASE_LOG) << amount;
1306  Q_D(ResourceBase);
1308  if (d->mItemSyncer) {
1309  d->mItemSyncer->setTotalItems(amount);
1310  }
1311 }
1312 
1314 {
1315  Q_D(ResourceBase);
1316  if (d->mItemSyncer) {
1317  d->mItemSyncer->setDisableAutomaticDeliveryDone(disable);
1318  }
1319  d->mDisableAutomaticItemDeliveryDone = disable;
1320 }
1321 
1323 {
1324  Q_D(ResourceBase);
1325  d->createItemSyncInstanceIfMissing();
1326  if (d->mItemSyncer) {
1327  d->mItemSyncer->setStreamingEnabled(enable);
1328  }
1329 }
1330 
1332 {
1333  Q_D(ResourceBase);
1334  if (d->scheduler->currentTask().type == ResourceScheduler::FetchItems) {
1335  auto trx = new TransactionSequence(this);
1336  connect(trx, &KJob::result, d, &ResourceBasePrivate::slotItemSyncDone);
1337  for (const Item &item : items) {
1338  Q_ASSERT(item.parentCollection().isValid());
1339  if (item.isValid()) { // NOLINT(bugprone-branch-clone)
1340  new ItemModifyJob(item, trx);
1341  } else if (!item.remoteId().isEmpty()) {
1342  auto job = new ItemCreateJob(item, item.parentCollection(), trx);
1343  job->setMerge(ItemCreateJob::RID);
1344  } else {
1345  // This should not happen, but just to be sure...
1346  new ItemModifyJob(item, trx);
1347  }
1348  }
1349  trx->commit();
1350  } else {
1351  d->createItemSyncInstanceIfMissing();
1352  if (d->mItemSyncer) {
1353  d->mItemSyncer->setFullSyncItems(items);
1354  }
1355  }
1356 }
1357 
1358 void ResourceBase::itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems)
1359 {
1360  Q_D(ResourceBase);
1361  d->createItemSyncInstanceIfMissing();
1362  if (d->mItemSyncer) {
1363  d->mItemSyncer->setIncrementalSyncItems(changedItems, removedItems);
1364  }
1365 }
1366 
1367 void ResourceBasePrivate::slotItemSyncDone(KJob *job)
1368 {
1369  mItemSyncer = nullptr;
1370  Q_Q(ResourceBase);
1371  if (job->error() && job->error() != Job::UserCanceled) {
1372  Q_EMIT q->error(job->errorString());
1373  }
1374  if (scheduler->currentTask().type == ResourceScheduler::FetchItems) {
1375  scheduler->currentTask().sendDBusReplies((job->error() && job->error() != Job::UserCanceled) ? job->errorString() : QString());
1376  }
1377  scheduler->taskDone();
1378 }
1379 
1380 void ResourceBasePrivate::slotDelayedEmitProgress()
1381 {
1382  Q_Q(ResourceBase);
1383  if (mAutomaticProgressReporting) {
1384  Q_EMIT q->percent(mUnemittedProgress);
1385 
1386  for (const QVariantMap &statusMap : std::as_const(mUnemittedAdvancedStatus)) {
1387  Q_EMIT q->advancedStatus(statusMap);
1388  }
1389  }
1390  mUnemittedProgress = 0;
1391  mUnemittedAdvancedStatus.clear();
1392 }
1393 
1394 void ResourceBasePrivate::slotPercent(KJob *job, quint64 percent)
1395 {
1396  mUnemittedProgress = static_cast<int>(percent);
1397 
1398  const auto collection = job->property("collection").value<Collection>();
1399  if (collection.isValid()) {
1400  QVariantMap statusMap;
1401  statusMap.insert(QStringLiteral("key"), QStringLiteral("collectionSyncProgress"));
1402  statusMap.insert(QStringLiteral("collectionId"), collection.id());
1403  statusMap.insert(QStringLiteral("percent"), static_cast<unsigned int>(percent));
1404 
1405  mUnemittedAdvancedStatus[collection.id()] = statusMap;
1406  }
1407  // deliver completion right away, intermediate progress at 1s intervals
1408  if (percent == 100U) {
1409  mProgressEmissionCompressor.stop();
1410  slotDelayedEmitProgress();
1411  } else if (!mProgressEmissionCompressor.isActive()) {
1412  mProgressEmissionCompressor.start();
1413  }
1414 }
1415 
1417 {
1418  Q_D(ResourceBase);
1419  d->mHierarchicalRid = enable;
1420 }
1421 
1422 void ResourceBase::scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument, SchedulePriority priority)
1423 {
1424  Q_D(ResourceBase);
1425  d->scheduler->scheduleCustomTask(receiver, method, argument, priority);
1426 }
1427 
1429 {
1430  Q_D(ResourceBase);
1431  d->scheduler->taskDone();
1432 }
1433 
1435 {
1436  collectionAttributesRetrieved(collection);
1437 }
1438 
1440 {
1441  Q_D(ResourceBase);
1442  d->scheduler->taskDone();
1443 }
1444 
1446 {
1447  Q_D(ResourceBase);
1448  d->scheduler->taskDone();
1449 }
1450 
1452 {
1453  Q_UNUSED(item)
1454  Q_UNUSED(parts)
1455  // retrieveItem() can no longer be pure virtual, because then we could not mark
1456  // it as deprecated (i.e. implementations would still be forced to implement it),
1457  // so instead we assert here.
1458  // NOTE: Don't change to Q_ASSERT_X here: while the macro can be disabled at
1459  // compile time, we want to hit this assert *ALWAYS*.
1460  qt_assert_x("Akonadi::ResourceBase::retrieveItem()",
1461  "The base implementation of retrieveItem() must never be reached. "
1462  "You must implement either retrieveItem() or retrieveItems(Akonadi::Item::List, QSet<QByteArray>) overload "
1463  "to handle item retrieval requests.",
1464  __FILE__,
1465  __LINE__);
1466  return false;
1467 }
1468 
1470 {
1471  Q_D(ResourceBase);
1472 
1473  // If we reach this implementation of retrieveItems() then it means that the
1474  // resource is still using the deprecated retrieveItem() method, so we explode
1475  // this to a myriad of tasks in scheduler and let them be processed one by one
1476 
1477  const qint64 id = d->scheduler->currentTask().serial;
1478  for (const auto &item : items) {
1479  d->scheduler->scheduleItemFetch(item, parts, d->scheduler->currentTask().dbusMsgs, id);
1480  }
1481  taskDone();
1482  return true;
1483 }
1484 
1486 {
1487 }
1488 
1490 {
1491  Q_D(ResourceBase);
1492  d->mItemTransactionMode = mode;
1493 }
1494 
1495 void ResourceBase::setItemMergingMode(ItemSync::MergeMode mode)
1496 {
1497  Q_D(ResourceBase);
1498  d->mItemMergeMode = mode;
1499 }
1500 
1502 {
1503  Q_D(ResourceBase);
1504  d->mAutomaticProgressReporting = enabled;
1505 }
1506 
1508 {
1509  Q_D(const ResourceBase);
1510  return d->dumpNotificationListToString();
1511 }
1512 
1514 {
1515  Q_D(const ResourceBase);
1516  return d->dumpToString();
1517 }
1518 
1520 {
1521  Q_D(const ResourceBase);
1522  d->dumpMemoryInfo();
1523 }
1524 
1526 {
1527  Q_D(const ResourceBase);
1528  return d->dumpMemoryInfoToString();
1529 }
1530 
1531 void ResourceBase::tagsRetrieved(const Tag::List &tags, const QHash<QString, Item::List> &tagMembers)
1532 {
1533  Q_D(ResourceBase);
1534  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncTags || d->scheduler->currentTask().type == ResourceScheduler::SyncAll
1535  || d->scheduler->currentTask().type == ResourceScheduler::Custom,
1536  "ResourceBase::tagsRetrieved()",
1537  "Calling tagsRetrieved() although no tag retrieval is in progress");
1538  if (!d->mTagSyncer) {
1539  d->mTagSyncer = new TagSync(this);
1540  connect(d->mTagSyncer, &KJob::percentChanged, d,
1541  &ResourceBasePrivate::slotPercent); // NOLINT(google-runtime-int): ulong comes from KJob
1542  connect(d->mTagSyncer, &KJob::result, d, &ResourceBasePrivate::slotTagSyncDone);
1543  }
1544  d->mTagSyncer->setFullTagList(tags);
1545  d->mTagSyncer->setTagMembers(tagMembers);
1546 }
1547 
1548 void ResourceBasePrivate::slotTagSyncDone(KJob *job)
1549 {
1550  Q_Q(ResourceBase);
1551  mTagSyncer = nullptr;
1552  if (job->error()) {
1553  if (job->error() != Job::UserCanceled) {
1554  qCWarning(AKONADIAGENTBASE_LOG) << "TagSync failed: " << job->errorString();
1555  Q_EMIT q->error(job->errorString());
1556  }
1557  }
1558 
1559  scheduler->taskDone();
1560 }
1561 
1562 void ResourceBase::relationsRetrieved(const Relation::List &relations)
1563 {
1564  Q_D(ResourceBase);
1565  Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncRelations || d->scheduler->currentTask().type == ResourceScheduler::SyncAll
1566  || d->scheduler->currentTask().type == ResourceScheduler::Custom,
1567  "ResourceBase::relationsRetrieved()",
1568  "Calling relationsRetrieved() although no relation retrieval is in progress");
1569  if (!d->mRelationSyncer) {
1570  d->mRelationSyncer = new RelationSync(this);
1571  connect(d->mRelationSyncer, &KJob::percentChanged, d,
1572  &ResourceBasePrivate::slotPercent); // NOLINT(google-runtime-int): ulong comes from KJob
1573  connect(d->mRelationSyncer, &KJob::result, d, &ResourceBasePrivate::slotRelationSyncDone);
1574  }
1575  d->mRelationSyncer->setRemoteRelations(relations);
1576 }
1577 
1578 void ResourceBasePrivate::slotRelationSyncDone(KJob *job)
1579 {
1580  Q_Q(ResourceBase);
1581  mRelationSyncer = nullptr;
1582  if (job->error()) {
1583  if (job->error() != Job::UserCanceled) {
1584  qCWarning(AKONADIAGENTBASE_LOG) << "RelationSync failed: " << job->errorString();
1585  Q_EMIT q->error(job->errorString());
1586  }
1587  }
1588 
1589  scheduler->taskDone();
1590 }
1591 
1592 #include "moc_resourcebase.cpp"
1593 #include "resourcebase.moc"
bool isValid() const
Returns whether the item is valid.
Definition: item.cpp:88
int itemSyncBatchSize() const
Returns the batch size used during the item sync.
void scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument, SchedulePriority priority=Append)
Schedules a custom task in the internal scheduler.
T & first()
virtual void abortActivity()
Abort any activity in progress in the backend.
void synchronizeTags()
Refetches Tags.
void invalidateCache(const Collection &collection)
Call this method to invalidate all cached content in collection.
~ResourceBase() override
Destroys the base resource.
@ Sync
Only retrieve collections for synchronization, taking the local preference and enabled into account.
void synchronizeRelations()
Refetches Relations.
bool isEmpty() const const
virtual void retrieveItems(const Akonadi::Collection &collection)=0
Retrieve all (new/changed) items in collection collection.
void collectionRemoved(const Akonadi::Collection &collection)
This signal is emitted if a monitored collection has been removed from the Akonadi storage.
virtual void retrieveCollectionAttributes(const Akonadi::Collection &collection)
Retrieve the attributes of a single collection from the backend.
virtual void retrieveCollections()=0
Retrieve the collection tree from the remote server and supply it via collectionsRetrieved() or colle...
void attributesSynchronized(qlonglong collectionId)
Emitted when a collection attributes synchronization has been completed.
Job that modifies a collection in the Akonadi storage.
void finished(KJob *job)
CaseInsensitive
QVariant fromValue(const T &value)
void setAutomaticProgressReporting(bool enabled)
Enable or disable automatic progress reporting.
virtual AKONADIAGENTBASE_DEPRECATED bool retrieveItem(const Akonadi::Item &item, const QSet< QByteArray > &parts)
Retrieve a single item from the backend.
Q_EMITQ_EMIT
void result(KJob *job)
void setApplicationDescription(const QString &description)
void reconnected()
This signal is emitted whenever the session has been reconnected to the server (e....
void percentChanged(KJob *job, unsigned long percent)
int removeAll(const T &value)
void collectionsRetrievalDone()
Call this method to indicate you finished synchronizing the collection tree.
Job that creates a new item in the Akonadi storage.
Definition: itemcreatejob.h:60
void doSetOnline(bool online) override
Inherited from AgentBase.
QVector::iterator begin()
void synchronizeCollectionAttributes(qint64 id)
This method is called whenever the collection with the given id shall have its attributes synchronize...
QString message() const const
An Akonadi Tag.
Definition: tag.h:25
@ RID
Merge by remote id.
Definition: itemcreatejob.h:87
QStringRef midRef(int position, int n) const const
static void setApplicationData(const KAboutData &aboutData)
T value() const const
Item::List currentItems() const
Returns the items that are currently retrieved.
void setCollectionStreamingEnabled(bool enable)
Enable collection streaming, that is collections don't have to be delivered at once as result of a re...
bool isOnline() const
Returns whether the agent is currently online.
Definition: agentbase.cpp:974
void itemsRetrieved(const Item::List &items)
Call this method to supply the full collection listing from the remote server.
KGuiItem clear()
Clears all queries from current thread.
Definition: querycache.cpp:107
void setDelayedReply(bool enable) const const
void dumpMemoryInfo() const
Dumps memory usage information to stdout.
Represents a collection of PIM items.
Definition: collection.h:61
void setApplicationVersion(const QString &version)
void setItemMergingMode(ItemSync::MergeMode mode)
Set merge mode for item sync'ing.
void synchronized()
Emitted when a full synchronization has been completed.
void setKeepLocalCollectionChanges(const QSet< QByteArray > &parts)
Allows to keep locally changed collection parts during the collection sync.
void changesAdded()
Emitted when new changes are recorded.
void exit(int returnCode)
void nameChanged(const QString &name)
This signal is emitted whenever the name of the resource has changed.
void setTotalItems(int amount)
Call this method when you want to use the itemsRetrieved() method in streaming mode and indicate the ...
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
QByteArray collectionType() const
Returns the special collections type of the collection.
int compare(const QString &other, Qt::CaseSensitivity cs) const const
T & first()
void replayNext()
Replay the next change notification and erase the previous one from the record.
void readyForNextBatch(int remainingBatchSize)
Signals the resource that new items can be delivered.
void synchronizeCollectionTree()
Refetches the Collections.
void changesCommitted(const Item::List &items)
Resets the dirty flag of all given items and updates remote ids.
void retrieveNextItemSyncBatch(int remainingBatchSize)
Emitted when the item synchronization processed the current batch and is ready for a new one.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QVector< Item > List
Describes a list of items.
Definition: item.h:115
void itemsRetrievalDone()
Call this method to indicate you finished synchronizing the current collection.
QDBusError lastError() const const
void agentNameChanged(const QString &name)
This signal is emitted whenever the name of the agent has changed.
static QString agentServiceName(ServiceAgentType agentType, const QString &identifier)
Returns the namespaced D-Bus service name for an agent of type agentType with agent identifier identi...
QString dumpMemoryInfoToString() const
Returns a string with memory usage information.
bool shouldList(ListPurpose purpose) const
Returns whether the collection should be listed or not for the specified purpose Takes enabled state ...
Definition: collection.cpp:397
virtual void retrieveRelations()
Retrieve all relations from the backend.
Job that fetches collections from the Akonadi storage.
void synchronize()
This method is called whenever the resource should start synchronize all data.
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
Definition: collection.cpp:176
QMap::iterator insert(const Key &key, const T &value)
void deferTask()
Suspends the execution of the current task and tries again to execute it.
TransactionMode
Transaction mode used by ItemSync.
Definition: itemsync.h:125
Job that modifies an existing item in the Akonadi storage.
Definition: itemmodifyjob.h:81
QString i18n(const char *text, const TYPE &arg...)
QDBusConnection sessionBus()
QString fromLocal8Bit(const char *str, int size)
SchedulePriority
Describes the scheduling priority of a task that has been queued for execution.
Definition: resourcebase.h:757
void setScheduleAttributeSyncBeforeItemSync(bool)
Set to true to schedule an attribute sync before every item sync.
void clearCache()
Call this method to remove all items and collections of the resource from the server cache.
void collectionsRetrieved(const Collection::List &collections)
Call this to supply the full folder tree retrieved from the remote server.
bool installTranslator(QTranslator *translationFile)
void collectionAttributesRetrieved(const Collection &collection)
Call this method from retrieveCollectionAttributes() once the result is available.
void process(const QStringList &arguments)
QCommandLineOption addVersionOption()
const T & at(int i) const const
void timeout()
bool isEmpty() const const
static QString addNamespace(const QString &string)
Adds the multi-instance namespace to string if required (with '_' as separator).
QString dumpSchedulerToString() const
Dump the state of the scheduler.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
Definition: collection.cpp:161
QString errorText() const
QCoreApplication * instance()
Collection parentCollection() const
Returns the parent collection of this object.
Definition: item.cpp:153
virtual int status() const
This method returns the current status code of the agent.
Definition: agentbase.cpp:946
QThread * currentThread()
QString value(const QString &optionName) const const
static QString virtualMimeType()
Returns the mimetype used for virtual collections.
Definition: collection.cpp:297
void setAgentName(const QString &name)
This method is used to set the name of the agent.
Definition: agentbase.cpp:1249
virtual void retrieveTags()
Retrieve all tags from the backend.
bool isEmpty() const const
void itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems)
Call this method to supply incrementally retrieved items from the remote server.
@ FirstLevel
Only list direct sub-collections of the base collection.
AKONADIAGENTBASE_DEPRECATED Item currentItem() const
Returns the item that is currently retrieved.
An Attribute that stores the special collection type of a collection.
static QString mimeType()
Returns the mimetype used for collections.
Definition: collection.cpp:292
void collectionTreeSynchronized()
Emitted when a collection tree synchronization has been completed.
The base class for all Akonadi agents and resources.
Definition: agentbase.h:72
@ Base
Only fetch the base collection.
Syncs between items known to a client (usually a resource) and the Akonadi storage.
Definition: itemsync.h:38
void setApplicationName(const QString &application)
bool isSet(const QString &name) const const
const QDBusMessage & message() const const
static Collection root()
Returns the root collection.
Definition: collection.cpp:287
@ ListSync
Listing for synchronization.
Definition: collection.h:476
bool contains(const T &value) const const
void synchronizeCollection(qint64 id)
This method is called whenever the collection with the given id shall be synchronized.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
QString remoteId() const
Returns the remote id of the collection.
Definition: collection.cpp:106
The base class for all Akonadi resources.
Definition: resourcebase.h:134
QVector::iterator end()
void nothingToReplay()
Emitted when replayNext() was called, but there was no valid change to replay.
void setItemTransactionMode(ItemSync::TransactionMode mode)
Set transaction mode for item sync'ing.
void changeCommitted(const Item &item)
Resets the dirty flag of the given item and updates the remote id.
void error(const QString &message)
This signal shall be used to report errors.
bool isEmpty() const const
Job that deletes a collection in the Akonadi storage.
QString identifier() const
Returns the instance identifier of this agent.
Definition: agentbase.cpp:1244
void sendErrorReply(const QString &name, const QString &msg) const const
void setHierarchicalRemoteIdentifiersEnabled(bool enable)
Indicate the use of hierarchical remote identifiers.
ChangeRecorder * changeRecorder() const
Returns the Akonadi::ChangeRecorder object used for monitoring.
Definition: agentbase.cpp:1289
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QVector::iterator erase(QVector::iterator begin, QVector::iterator end)
void itemRetrieved(const Item &item)
Call this method from retrieveItem() once the result is available.
int compare(QStringView str, Qt::CaseSensitivity cs) const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void cancelTask()
Stops the execution of the current task and continues with the next one.
QList::iterator begin()
void setItemSyncBatchSize(int batchSize)
Set the batch size used during the item sync.
Job that fetches items from the Akonadi storage.
Definition: itemfetchjob.h:69
@ Recursive
List all sub-collections.
bool addOption(const QCommandLineOption &option)
void setItemStreamingEnabled(bool enable)
Enable item streaming, which is disabled by default.
static int init(int argc, char **argv)
Use this method in the main function of your resource application to initialize your resource subclas...
Definition: resourcebase.h:166
void abortRequested()
Emitted when another application has remotely asked the agent to abort its current operation.
virtual QString errorString() const
int error() const
QString agentName() const
Returns the name of the agent.
Definition: agentbase.cpp:1273
QSet< QByteArray > loadedPayloadParts() const
Returns the list of loaded payload parts.
Definition: item.cpp:288
QString dumpNotificationListToString() const
Dump the contents of the current ChangeReplay.
void taskDone()
Indicate that the current task is finished.
Collection currentCollection() const
Returns the collection that is currently synchronized.
QList::iterator end()
static void setApplicationDomain(const char *domain)
@ UserCanceled
The user canceled this job.
Definition: job.h:101
void setDisableAutomaticItemDeliveryDone(bool disable)
Disables the automatic completion of the item sync, based on the number of delivered items.
QCommandLineOption addHelpOption()
ResourceBase(const QString &id)
Creates a base resource.
@ Running
The agent is working on something.
Definition: agentbase.h:423
void collectionsRetrievedIncremental(const Collection::List &changedCollections, const Collection::List &removedCollections)
Call this to supply incrementally retrieved collections from the remote server.
bool isEmpty() const const
QString remoteId() const
Returns the remote id of the item.
Definition: item.cpp:73
Represents a PIM item stored in Akonadi storage.
Definition: item.h:104
Q_D(Todo)
void setName(const QString &name)
This method is used to set the name of the resource.
QVariant property(const char *name) const const
@ Parent
Only retrieve the immediate parent collection.
Helper integration between Akonadi and Qt.
QString name() const
Returns the name of the resource.
Job that modifies a tag in the Akonadi storage.
Definition: tagmodifyjob.h:21
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:01:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.