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()) {
 
  396        if (!(localCollection.cachePolicy() == remoteCollection.cachePolicy())) {
 
  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.
 
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 Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
 
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)