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
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)
120 , resultEmitted(false)
124 ~CollectionSyncPrivate()
128 RemoteId remoteIdForCollection(
const Collection &collection)
const
131 return RemoteId::rootRid;
134 if (!hierarchicalRIDs) {
135 return RemoteId(collection.
remoteId());
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)
162 if (parentCollection.
remoteId() == akonadiRootCollection.
remoteId() || parentCollection.id() == akonadiRootCollection.id()) {
164 c2.setParentCollection(akonadiRootCollection);
165 map[RemoteId::rootRid].append(c2);
168 map[remoteIdForCollection(parentCollection)].append(collection);
180 return local.name() == remote.name();
187 const RemoteId parentRid = remoteIdForCollection(collection.
parentCollection());
188 localCollections[parentRid] += collection;
192 void processCollections(
const RemoteId &parentRid)
195 Collection::List removedChildren = removedRemoteCollections.value(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;) {
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;) {
237 if (matchLocalAndRemoteCollection(localCollection, remoteCollection)) {
242 if (localCollection.isVirtual() != remoteCollection.isVirtual()) {
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) {
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)
321 const Collection::List originalChildren = localCollections.value(parentRid);
322 processCollections(parentRid);
328 if (!remoteChildren.
isEmpty()) {
330 c.setParentCollection(parentCollection);
331 remoteCollectionsToCreate.
append(c);
335 if (!localChildren.
isEmpty() && !incremental) {
336 for (
const auto &c : localChildren) {
337 if (!c.remoteId().isEmpty()) {
344 for (
const Collection &c : originalChildren) {
345 processLocalCollections(remoteIdForCollection(c), c);
349 void localCollectionFetchResult(
KJob *job)
355 processLocalCollections(RemoteId::rootRid, akonadiRootCollection);
356 localListDone =
true;
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();
377 if (!localCollection.contentMimeTypes().
contains(mimeType)) {
387 if (localCollection.name() != remoteCollection.name()) {
393 if (localCollection.remoteRevision() != remoteCollection.remoteRevision()) {
399 if (localCollection.enabled() != remoteCollection.enabled()) {
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;) {
430 if (parentCollection == akonadiRootCollection || parentCollection.id() > 0) {
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)
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()) {
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)) {
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())) {
555 upd.removeAttribute(localAttr->
type());
556 upd.addAttribute(localAttr->
clone());
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);
616 deleteLocalCollectionsResult(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()
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;
754 bool hierarchicalRIDs;
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"
Provides interface for custom attributes for Entity.
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.
Job that creates a new collection in the Akonadi storage.
Job that deletes a collection in the Akonadi storage.
Job that fetches collections from the Akonadi storage.
@ 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()
Job that modifies a collection in the Akonadi storage.
Job that moves a collection in the Akonadi storage to a new parent collection.
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.
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.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
void commit()
Commits the transaction as soon as all pending sub-jobs finished successfully.
void setIgnoreJobFailure(KJob *job)
Sets which job of the sequence might fail without rolling back the complete transaction.
void rollback()
Rolls back the current transaction as soon as possible.
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
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 Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
const QList< QKeySequence > & end()
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
bool operator<(const PosRange< Trait > &l, const PosRange< Trait > &r)
QDebug operator<<(QDebug dbg, const PerceptualColor::MultiSpinBoxSection &value)
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
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
void push_back(parameter_type value)
qsizetype size() const const
bool startsWith(parameter_type value) 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)