7#include "akonadicore_debug.h"
9#include "collectioncreatejob.h"
10#include "collectiondeletejob.h"
11#include "collectionfetchjob.h"
12#include "collectionfetchscope.h"
13#include "collectionmodifyjob.h"
14#include "collectionmovejob.h"
15#include "collectionsync_p.h"
17#include "cachepolicy.h"
19#include <KLocalizedString>
27static const char CONTENTMIMETYPES[] =
"CONTENTMIMETYPES";
29static const char ROOTPARENTRID[] =
"AKONADI_ROOT_COLLECTION";
38 explicit inline RemoteId(
const QStringList &ridChain)
43 explicit inline RemoteId(
const QString &rid)
48 inline bool isAbsolute()
const
53 inline bool isEmpty()
const
55 return ridChain.isEmpty();
58 inline bool operator==(
const RemoteId &other)
const
60 return ridChain == other.ridChain;
65 static RemoteId rootRid;
70Q_DECLARE_METATYPE(RemoteId)
72size_t qHash(
const RemoteId &rid,
size_t seed = 0) noexcept
77inline bool operator<(
const RemoteId &r1,
const RemoteId &r2)
84 if ((*it1) == (*it2)) {
89 return (*it1) < (*it2);
99 s.
nospace() <<
"RemoteId(" << rid.ridChain <<
")";
106class Akonadi::CollectionSyncPrivate
109 explicit CollectionSyncPrivate(CollectionSync *parent)
113 , currentTransaction(nullptr)
116 , hierarchicalRIDs(false)
117 , localListDone(false)
118 , deliveryDone(false)
119 , akonadiRootCollection(Collection::root())
120 , resultEmitted(false)
124 ~CollectionSyncPrivate()
128 RemoteId remoteIdForCollection(
const Collection &collection)
const
131 return RemoteId::rootRid;
134 if (!hierarchicalRIDs) {
135 return RemoteId(collection.
remoteId());
139 Collection parent = collection;
142 if (prid.
isEmpty() && parent.isValid()) {
143 prid = uidRidMap.value(parent.id());
148 rid.ridChain.
append(prid);
150 if (parent == akonadiRootCollection) {
158 void addRemoteColection(
const Collection &collection,
bool removed =
false)
160 QHash<RemoteId, Collection::List> &
map = (removed ? removedRemoteCollections : remoteCollections);
162 if (parentCollection.
remoteId() == akonadiRootCollection.remoteId() || parentCollection.id() == akonadiRootCollection.id()) {
163 Collection c2(collection);
164 c2.setParentCollection(akonadiRootCollection);
165 map[RemoteId::rootRid].append(c2);
168 map[remoteIdForCollection(parentCollection)].append(collection);
175 bool matchLocalAndRemoteCollection(
const Collection &local,
const Collection &remote)
180 return local.name() == remote.name();
186 for (
const Akonadi::Collection &collection : localCols) {
187 const RemoteId parentRid = remoteIdForCollection(collection.
parentCollection());
188 localCollections[parentRid] += collection;
192 void processCollections(
const RemoteId &parentRid)
199 for (
auto localIter = localChildren.
begin(), localEnd = localChildren.
end(); localIter != localEnd;) {
200 const Collection localCollection = *localIter;
201 bool matched =
false;
202 uidRidMap.insert(localIter->id(), localIter->remoteId());
205 for (
auto removedIter = removedChildren.
begin(), removedEnd = removedChildren.
end(); removedIter != removedEnd;) {
206 Collection removedCollection = *removedIter;
208 if (matchLocalAndRemoteCollection(localCollection, removedCollection)) {
211 localCollectionsToRemove.append(localCollection);
215 removedIter = removedChildren.
erase(removedIter);
216 removedEnd = removedChildren.
end();
227 localIter = localChildren.
erase(localIter);
228 localEnd = localChildren.
end();
233 for (
auto remoteIter = remoteChildren.
begin(), remoteEnd = remoteChildren.
end(); !matched && remoteIter != remoteEnd;) {
234 Collection remoteCollection = *remoteIter;
237 if (matchLocalAndRemoteCollection(localCollection, remoteCollection)) {
242 if (localCollection.isVirtual() != remoteCollection.isVirtual()) {
244 QList<QPair<Collection , Collection >> parents = {{localCollection, remoteCollection}};
245 while (!parents.empty()) {
246 auto parent = parents.takeFirst();
247 qCDebug(AKONADICORE_LOG) <<
"Local collection " << parent.first.name() <<
" will be recreated";
248 localCollectionsToRemove.
push_back(parent.first);
249 remoteCollectionsToCreate.push_back(parent.second);
250 for (
auto it = localChildren.
begin(), end = localChildren.
end(); it != end;) {
251 if (it->parentCollection() == parent.first) {
252 Collection remoteParent;
253 auto remoteIt = std::find_if(
254 remoteChildren.
begin(),
255 remoteChildren.
end(),
256 std::bind(&CollectionSyncPrivate::matchLocalAndRemoteCollection,
this, parent.first, std::placeholders::_1));
257 if (remoteIt != remoteChildren.
end()) {
258 remoteParent = *remoteIt;
259 remoteEnd = remoteChildren.
erase(remoteIt);
261 parents.push_back({*it, remoteParent});
262 it = localChildren.
erase(it);
263 localEnd =
end = localChildren.
end();
269 }
else if (collectionNeedsUpdate(localCollection, remoteCollection)) {
272 remoteCollectionsToUpdate.append(qMakePair(localCollection, remoteCollection));
280 remoteIter = remoteChildren.
erase(remoteIter);
281 remoteEnd = remoteChildren.
end();
293 localIter = localChildren.
erase(localIter);
294 localEnd = localChildren.
end();
300 if (!removedChildren.
isEmpty()) {
301 removedRemoteCollections[parentRid] = removedChildren;
303 removedRemoteCollections.remove(parentRid);
306 if (!remoteChildren.
isEmpty()) {
307 remoteCollections[parentRid] = remoteChildren;
309 remoteCollections.remove(parentRid);
312 if (!localChildren.
isEmpty()) {
313 localCollections[parentRid] = localChildren;
315 localCollections.remove(parentRid);
319 void processLocalCollections(
const RemoteId &parentRid,
const Collection &parentCollection)
322 processCollections(parentRid);
328 if (!remoteChildren.
isEmpty()) {
329 for (Collection c : remoteChildren) {
330 c.setParentCollection(parentCollection);
331 remoteCollectionsToCreate.append(c);
335 if (!localChildren.
isEmpty() && !incremental) {
336 for (
const auto &c : localChildren) {
337 if (!c.remoteId().isEmpty()) {
338 localCollectionsToRemove.push_back(c);
344 for (
const Collection &c : originalChildren) {
345 processLocalCollections(remoteIdForCollection(c), c);
349 void localCollectionFetchResult(KJob *job)
355 processLocalCollections(RemoteId::rootRid, akonadiRootCollection);
356 localListDone =
true;
360 bool ignoreAttributeChanges(
const Akonadi::Collection &col,
const QByteArray &attribute)
const
368 bool collectionNeedsUpdate(
const Collection &localCollection,
const Collection &remoteCollection)
const
370 if (!ignoreAttributeChanges(remoteCollection, CONTENTMIMETYPES)) {
371 if (localCollection.contentMimeTypes().
size() != remoteCollection.contentMimeTypes().
size()) {
374 for (qsizetype i = 0, total = remoteCollection.contentMimeTypes().
size(); i < total; ++i) {
375 const auto contentMimeTypes = remoteCollection.contentMimeTypes();
376 const QString
mimeType = contentMimeTypes.
at(i);
377 if (!localCollection.contentMimeTypes().
contains(mimeType)) {
387 if (localCollection.name() != remoteCollection.name()) {
393 if (localCollection.remoteRevision() != remoteCollection.remoteRevision()) {
399 if (localCollection.enabled() != remoteCollection.enabled()) {
405 for (
const Attribute *attr : lstAttr) {
406 const Attribute *localAttr = localCollection.
attribute(attr->type());
407 if (localAttr && ignoreAttributeChanges(remoteCollection, attr->type())) {
411 if (!localAttr || localAttr->
serialized() != attr->serialized()) {
419 void createLocalCollections()
421 if (remoteCollectionsToCreate.isEmpty()) {
422 updateLocalCollections();
426 for (
auto iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end;) {
427 const Collection col = *iter;
430 if (parentCollection == akonadiRootCollection || parentCollection.id() > 0) {
432 auto create =
new CollectionCreateJob(col, currentTransaction);
434 createLocalCollectionResult(job);
439 if (pendingJobs % 100 == 0) {
440 currentTransaction->commit();
444 iter = remoteCollectionsToCreate.erase(iter);
445 end = remoteCollectionsToCreate.end();
454 void createLocalCollectionResult(KJob *job)
463 const Collection newLocal =
static_cast<CollectionCreateJob *
>(job)->collection();
464 uidRidMap.insert(newLocal.id(), newLocal.
remoteId());
465 const RemoteId newLocalRID = remoteIdForCollection(newLocal);
469 for (
auto iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end; ++iter) {
471 if (parentCollection != akonadiRootCollection && parentCollection.id() <= 0) {
472 const RemoteId remoteRID = remoteIdForCollection(*iter);
473 if (remoteRID.isAbsolute()) {
474 if (newLocalRID == remoteIdForCollection(*iter)) {
475 iter->setParentCollection(newLocal);
477 }
else if (!hierarchicalRIDs) {
479 iter->setParentCollection(newLocal);
488 if (collectionsToCreate.
isEmpty() && !hierarchicalRIDs) {
489 collectionsToCreate = remoteCollections.take(RemoteId(newLocal.
remoteId()));
491 for (Collection col : std::as_const(collectionsToCreate)) {
493 remoteCollectionsToCreate.append(col);
498 if (!remoteCollectionsToCreate.isEmpty()) {
499 createLocalCollections();
500 }
else if (pendingJobs == 0) {
501 Q_ASSERT(remoteCollectionsToCreate.isEmpty());
502 if (!remoteCollections.isEmpty()) {
503 currentTransaction->rollback();
504 q->setError(CollectionSync::Unknown);
505 q->setErrorText(
i18n(
"Found unresolved orphan collections"));
506 qCWarning(AKONADICORE_LOG) <<
"found unresolved orphan collection";
511 currentTransaction->commit();
515 updateLocalCollections();
531 void updateLocalCollections()
533 if (remoteCollectionsToUpdate.isEmpty()) {
534 deleteLocalCollections();
538 using CollectionPair = QPair<Collection, Collection>;
539 for (
const CollectionPair &pair : std::as_const(remoteCollectionsToUpdate)) {
540 const Collection local = pair.first;
541 const Collection remote = pair.second;
542 Collection upd(remote);
544 Q_ASSERT(!upd.remoteId().isEmpty());
545 Q_ASSERT(currentTransaction);
546 upd.setId(local.id());
547 if (ignoreAttributeChanges(remote, CONTENTMIMETYPES)) {
548 upd.setContentMimeTypes(local.contentMimeTypes());
550 const auto remoteAttributes = upd.attributes();
551 for (Attribute *remoteAttr : remoteAttributes) {
552 if (ignoreAttributeChanges(remote, remoteAttr->type()) && local.
hasAttribute(remoteAttr->type())) {
554 const Attribute *localAttr = local.
attribute(remoteAttr->type());
555 upd.removeAttribute(localAttr->
type());
556 upd.addAttribute(localAttr->
clone());
565 auto mod =
new CollectionModifyJob(c, currentTransaction);
567 updateLocalCollectionResult(job);
571 if (!hierarchicalRIDs) {
576 updateLocalCollectionResult(job);
583 void updateLocalCollectionResult(KJob *job)
589 if (qobject_cast<CollectionModifyJob *>(job)) {
594 if (pendingJobs == 0) {
595 currentTransaction->commit();
598 deleteLocalCollections();
602 void deleteLocalCollections()
604 if (localCollectionsToRemove.isEmpty()) {
609 for (
const Collection &col : std::as_const(localCollectionsToRemove)) {
613 Q_ASSERT(currentTransaction);
614 auto job =
new CollectionDeleteJob(col, currentTransaction);
616 deleteLocalCollectionsResult(job);
623 currentTransaction->setIgnoreJobFailure(job);
627 void deleteLocalCollectionsResult(KJob * )
632 if (pendingJobs == 0) {
633 currentTransaction->commit();
634 currentTransaction =
nullptr;
642 if (currentTransaction) {
644 currentTransaction->commit();
645 currentTransaction =
nullptr;
648 if (!remoteCollections.isEmpty()) {
649 q->setError(CollectionSync::Unknown);
650 q->setErrorText(
i18n(
"Found unresolved orphan collections"));
658 Q_ASSERT(!resultEmitted);
659 if (!resultEmitted) {
660 if (q->hasSubjobs()) {
664 KJob *subjob = q->subjobs().at(0);
674 resultEmitted =
true;
680 void createTransaction()
682 currentTransaction =
new TransactionSequence(q);
683 currentTransaction->setAutomaticCommittingEnabled(
false);
685 transactionSequenceResult(job);
690 void transactionSequenceResult(KJob *job)
698 if (job == currentTransaction) {
699 currentTransaction =
nullptr;
708 qCDebug(AKONADICORE_LOG) <<
"localListDone: " << localListDone <<
" deliveryDone: " << deliveryDone;
709 if (!localListDone && !deliveryDone) {
713 if (!localListDone && deliveryDone) {
714 Job *parent = (currentTransaction ?
static_cast<Job *
>(currentTransaction) :
static_cast<Job *
>(q));
716 job->fetchScope().setResource(resourceId);
720 localCollectionsReceived(cols);
723 localCollectionFetchResult(job);
729 if (!currentTransaction) {
731 if (remoteCollectionsToCreate.isEmpty() && remoteCollectionsToUpdate.isEmpty() && localCollectionsToRemove.isEmpty()) {
732 qCDebug(AKONADICORE_LOG) <<
"Nothing to do";
740 createLocalCollections();
743 CollectionSync *
const q;
750 TransactionSequence *currentTransaction;
754 bool hierarchicalRIDs;
760 QSet<QByteArray> keepLocalChanges;
768 QList<QPair<Collection , Collection >> remoteCollectionsToUpdate;
769 QHash<Collection::Id, QString> uidRidMap;
774 Collection akonadiRootCollection;
779CollectionSync::CollectionSync(
const QString &resourceId,
QObject *parent)
781 , d(new CollectionSyncPrivate(this))
783 d->resourceId = resourceId;
787CollectionSync::~CollectionSync() =
default;
789void CollectionSync::setRemoteCollections(
const Collection::List &remoteCollections)
792 for (
const Collection &c : remoteCollections) {
793 d->addRemoteColection(c);
797 d->deliveryDone =
true;
805 d->incremental =
true;
806 for (
const Collection &c : changedCollections) {
807 d->addRemoteColection(c);
809 for (
const Collection &c : removedCollections) {
810 d->addRemoteColection(c,
true);
814 d->deliveryDone =
true;
819void CollectionSync::doStart()
823void CollectionSync::setStreamingEnabled(
bool streaming)
825 d->streaming = streaming;
828void CollectionSync::retrievalDone()
830 d->deliveryDone =
true;
834void CollectionSync::setHierarchicalRemoteIds(
bool hierarchical)
836 d->hierarchicalRIDs = hierarchical;
839void CollectionSync::rollback()
841 if (d->currentTransaction) {
842 d->currentTransaction->rollback();
844 setError(UserCanceled);
851 d->keepLocalChanges = parts;
854#include "moc_collectionsync_p.cpp"
virtual QByteArray serialized() const =0
Returns a QByteArray representation of the attribute which will be storaged.
virtual Attribute * clone() const =0
Creates a copy of this attribute.
virtual QByteArray type() const =0
Returns the type of the attribute.
QList< Attribute * > List
Describes a list of attributes.
@ Recursive
List all sub-collections.
void collectionsReceived(const Akonadi::Collection::List &collections)
This signal is emitted whenever the job has received collections.
@ NoFilter
No filtering, retrieve all collections.
@ All
Retrieve all ancestors, up to Collection::root()
Represents a collection of PIM items.
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
static Collection root()
Returns the root collection.
Attribute::List attributes() const
Returns a list of all attributes of the collection.
CachePolicy cachePolicy() const
Returns the cache policy of the collection.
Collection parentCollection() const
Returns the parent collection of this object.
QSet< QByteArray > keepLocalChanges() const
Returns what parts are only default values.
QList< Collection > List
Describes a list of collections.
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
QString remoteId() const
Returns the remote id of the collection.
Base class for all actions in the Akonadi storage.
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT CopyJob * move(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
qsizetype length() const const
qsizetype size() const const
bool startsWith(parameter_type value) const const
T value(qsizetype i) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool contains(const QSet< T > &other) const const
const QChar at(qsizetype position) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)