diff --git a/autotests/server/fakedatastore.cpp b/autotests/server/fakedatastore.cpp index d800cd2a2..59bce5faa 100644 --- a/autotests/server/fakedatastore.cpp +++ b/autotests/server/fakedatastore.cpp @@ -1,313 +1,305 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fakedatastore.h" #include "dbpopulator.h" #include "storage/dbconfig.h" #include "storage/notificationcollector.h" #include "akonadischema.h" #include "storage/dbinitializer.h" #include using namespace Akonadi::Server; Q_DECLARE_METATYPE(PimItem) Q_DECLARE_METATYPE(PimItem::List) Q_DECLARE_METATYPE(Collection) Q_DECLARE_METATYPE(Flag) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(Tag) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(MimeType) Q_DECLARE_METATYPE(QList) Akonadi::Server::FakeDataStore::FakeDataStore() : DataStore() , mPopulateDb(true) { notificationCollector(); } FakeDataStore::~FakeDataStore() { } -NotificationCollector *FakeDataStore::notificationCollector() -{ - if (!mNotificationCollector) { - mNotificationCollector = new NotificationCollector(this); - } - return mNotificationCollector; -} - DataStore *FakeDataStore::self() { if (!sInstances.hasLocalData()) { sInstances.setLocalData(new FakeDataStore()); } return sInstances.localData(); } bool FakeDataStore::init() { if (!DataStore::init()) { return false; } if (mPopulateDb) { DbPopulator dbPopulator; if (!dbPopulator.run()) { qWarning() << "Failed to populate database"; return false; } } return true; } bool FakeDataStore::setItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged, const Collection &col, bool silent) { mChanges.insert(QStringLiteral("setItemsFlags"), QVariantList() << QVariant::fromValue(items) << QVariant::fromValue(flags) << QVariant::fromValue(col) << silent); return DataStore::setItemsFlags(items, flags, flagsChanged, col, silent); } bool FakeDataStore::appendItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged, bool checkIfExists, const Collection &col, bool silent) { mChanges.insert(QStringLiteral("appendItemsFlags"), QVariantList() << QVariant::fromValue(items) << QVariant::fromValue(flags) << checkIfExists << QVariant::fromValue(col) << silent); return DataStore::appendItemsFlags(items, flags, flagsChanged, checkIfExists, col, silent); } bool FakeDataStore::removeItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged, const Collection &col, bool silent) { mChanges.insert(QStringLiteral("removeItemsFlags"), QVariantList() << QVariant::fromValue(items) << QVariant::fromValue(flags) << QVariant::fromValue(col) << silent); return DataStore::removeItemsFlags(items, flags, flagsChanged, col, silent); } bool FakeDataStore::setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool silent) { mChanges.insert(QStringLiteral("setItemsTags"), QVariantList() << QVariant::fromValue(items) << QVariant::fromValue(tags) << silent); return DataStore::setItemsTags(items, tags, tagsChanged, silent); } bool FakeDataStore::appendItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool checkIfExists, const Collection &col, bool silent) { mChanges.insert(QStringLiteral("appendItemsTags"), QVariantList() << QVariant::fromValue(items) << QVariant::fromValue(tags) << checkIfExists << QVariant::fromValue(col) << silent); return DataStore::appendItemsTags(items, tags, tagsChanged, checkIfExists, col, silent); } bool FakeDataStore::removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool silent) { mChanges.insert(QStringLiteral("removeItemsTags"), QVariantList() << QVariant::fromValue(items) << QVariant::fromValue(tags) << silent); return DataStore::removeItemsTags(items, tags, tagsChanged, silent); } bool FakeDataStore::removeItemParts(const PimItem &item, const QSet &parts) { mChanges.insert(QStringLiteral("remoteItemParts"), QVariantList() << QVariant::fromValue(item) << QVariant::fromValue(parts)); return DataStore::removeItemParts(item, parts); } bool FakeDataStore::invalidateItemCache(const PimItem &item) { mChanges.insert(QStringLiteral("invalidateItemCache"), QVariantList() << QVariant::fromValue(item)); return DataStore::invalidateItemCache(item); } bool FakeDataStore::appendCollection(Collection &collection) { mChanges.insert(QStringLiteral("appendCollection"), QVariantList() << QVariant::fromValue(collection)); return DataStore::appendCollection(collection); } bool FakeDataStore::cleanupCollection(Collection &collection) { mChanges.insert(QStringLiteral("cleanupCollection"), QVariantList() << QVariant::fromValue(collection)); return DataStore::cleanupCollection(collection); } bool FakeDataStore::cleanupCollection_slow(Collection &collection) { mChanges.insert(QStringLiteral("cleanupCollection_slow"), QVariantList() << QVariant::fromValue(collection)); return DataStore::cleanupCollection_slow(collection); } bool FakeDataStore::moveCollection(Collection &collection, const Collection &newParent) { mChanges.insert(QStringLiteral("moveCollection"), QVariantList() << QVariant::fromValue(collection) << QVariant::fromValue(newParent)); return DataStore::moveCollection(collection, newParent); } bool FakeDataStore::appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes) { mChanges.insert(QStringLiteral("appendMimeTypeForCollection"), QVariantList() << collectionId << QVariant::fromValue(mimeTypes)); return DataStore::appendMimeTypeForCollection(collectionId, mimeTypes); } void FakeDataStore::activeCachePolicy(Collection &col) { mChanges.insert(QStringLiteral("activeCachePolicy"), QVariantList() << QVariant::fromValue(col)); return DataStore::activeCachePolicy(col); } bool FakeDataStore::appendPimItem(QVector &parts, const QVector &flags, const MimeType &mimetype, const Collection &collection, const QDateTime &dateTime, const QString &remote_id, const QString &remoteRevision, const QString &gid, PimItem &pimItem) { mChanges.insert(QStringLiteral("appendPimItem"), QVariantList() << QVariant::fromValue(mimetype) << QVariant::fromValue(collection) << dateTime << remote_id << remoteRevision << gid); return DataStore::appendPimItem(parts, flags, mimetype, collection, dateTime, remote_id, remoteRevision, gid, pimItem); } bool FakeDataStore::cleanupPimItems(const PimItem::List &items) { mChanges.insert(QStringLiteral("cleanupPimItems"), QVariantList() << QVariant::fromValue(items)); return DataStore::cleanupPimItems(items); } bool FakeDataStore::unhidePimItem(PimItem &pimItem) { mChanges.insert(QStringLiteral("unhidePimItem"), QVariantList() << QVariant::fromValue(pimItem)); return DataStore::unhidePimItem(pimItem); } bool FakeDataStore::unhideAllPimItems() { mChanges.insert(QStringLiteral("unhideAllPimItems"), QVariantList()); return DataStore::unhideAllPimItems(); } bool FakeDataStore::addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value) { mChanges.insert(QStringLiteral("addCollectionAttribute"), QVariantList() << QVariant::fromValue(col) << key << value); return DataStore::addCollectionAttribute(col, key, value); } bool FakeDataStore::removeCollectionAttribute(const Collection &col, const QByteArray &key) { mChanges.insert(QStringLiteral("removeCollectionAttribute"), QVariantList() << QVariant::fromValue(col) << key); return DataStore::removeCollectionAttribute(col, key); } bool FakeDataStore::beginTransaction(const QString &name) { mChanges.insert(QStringLiteral("beginTransaction"), QVariantList() << name); return DataStore::beginTransaction(name); } bool FakeDataStore::commitTransaction() { mChanges.insert(QStringLiteral("commitTransaction"), QVariantList()); return DataStore::commitTransaction(); } bool FakeDataStore::rollbackTransaction() { mChanges.insert(QStringLiteral("rollbackTransaction"), QVariantList()); return DataStore::rollbackTransaction(); } void FakeDataStore::setPopulateDb(bool populate) { mPopulateDb = populate; } diff --git a/autotests/server/fakedatastore.h b/autotests/server/fakedatastore.h index b6f88287c..baea35563 100644 --- a/autotests/server/fakedatastore.h +++ b/autotests/server/fakedatastore.h @@ -1,136 +1,134 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SERVER_FAKEDATASTORE_H #define AKONADI_SERVER_FAKEDATASTORE_H #include "storage/datastore.h" namespace Akonadi { namespace Server { class FakeDataStore : public DataStore { Q_OBJECT public: ~FakeDataStore() override; static DataStore *self(); bool init() override; QMap changes() const { return mChanges; } virtual bool setItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, const Collection &col = Collection(), bool silent = false) override; virtual bool appendItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false) override; virtual bool removeItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, const Collection &col = Collection(), bool silent = false) override; virtual bool setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false) override; virtual bool appendItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false) override; virtual bool removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false) override; virtual bool removeItemParts(const PimItem &item, const QSet &parts) override; virtual bool invalidateItemCache(const PimItem &item) override; virtual bool appendCollection(Collection &collection) override; virtual bool cleanupCollection(Collection &collection) override; virtual bool cleanupCollection_slow(Collection &collection) override; virtual bool moveCollection(Collection &collection, const Collection &newParent) override; virtual bool appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes) override; virtual void activeCachePolicy(Collection &col) override; virtual bool appendPimItem(QVector &parts, const QVector &flags, const MimeType &mimetype, const Collection &collection, const QDateTime &dateTime, const QString &remote_id, const QString &remoteRevision, const QString &gid, PimItem &pimItem) override; virtual bool cleanupPimItems(const PimItem::List &items) override; virtual bool unhidePimItem(PimItem &pimItem) override; virtual bool unhideAllPimItems() override; virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value) override; virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key) override; virtual bool beginTransaction(const QString &name = QString()) override; virtual bool rollbackTransaction() override; virtual bool commitTransaction() override; - virtual NotificationCollector *notificationCollector() override; - void setPopulateDb(bool populate); protected: FakeDataStore(); QMap mChanges; private: bool populateDatabase(); bool mPopulateDb; }; } } #endif // AKONADI_SERVER_FAKEDATASTORE_H diff --git a/src/agentbase/resourcebase.cpp b/src/agentbase/resourcebase.cpp index 4668a4240..e0a378a42 100644 --- a/src/agentbase/resourcebase.cpp +++ b/src/agentbase/resourcebase.cpp @@ -1,1654 +1,1654 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "resourcebase.h" #include "agentbase_p.h" #include "resourceadaptor.h" #include "collectiondeletejob.h" #include "collectionsync_p.h" #include "KDBusConnectionPool" #include "itemsync.h" #include "akonadi_version.h" #include "tagsync.h" #include "relationsync.h" #include "resourcescheduler_p.h" #include "tracerinterface.h" #include "changerecorder.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmodifyjob.h" #include "invalidatecachejob_p.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "itemcreatejob.h" #include "session.h" #include "resourceselectjob_p.h" #include "monitor_p.h" #include "servermanager_p.h" #include "recursivemover_p.h" #include "tagmodifyjob.h" #include "specialcollectionattribute.h" #include "favoritecollectionattribute.h" #include "akonadiagentbase_debug.h" #include #include #include #include using namespace Akonadi; class Akonadi::ResourceBasePrivate : public AgentBasePrivate { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.dfaure") public: ResourceBasePrivate(ResourceBase *parent) : AgentBasePrivate(parent) , scheduler(nullptr) , mItemSyncer(nullptr) , mItemSyncFetchScope(nullptr) , mItemTransactionMode(ItemSync::SingleTransaction) , mItemMergeMode(ItemSync::RIDMerge) , mCollectionSyncer(nullptr) , mTagSyncer(nullptr) , mRelationSyncer(nullptr) , mHierarchicalRid(false) , mUnemittedProgress(0) , mAutomaticProgressReporting(true) , mDisableAutomaticItemDeliveryDone(false) , mItemSyncBatchSize(10) , mCurrentCollectionFetchJob(nullptr) , mScheduleAttributeSyncBeforeCollectionSync(false) { Internal::setClientType(Internal::Resource); mStatusMessage = defaultReadyMessage(); mProgressEmissionCompressor.setInterval(1000); mProgressEmissionCompressor.setSingleShot(true); // HACK: skip local changes of the EntityDisplayAttribute by default. Remove this for KDE5 and adjust resource implementations accordingly. mKeepLocalCollectionChanges << "ENTITYDISPLAY"; } ~ResourceBasePrivate() override { delete mItemSyncFetchScope; } Q_DECLARE_PUBLIC(ResourceBase) void delayedInit() override { const QString serviceId = ServerManager::agentServiceName(ServerManager::Resource, mId); if (!KDBusConnectionPool::threadConnection().registerService(serviceId)) { QString reason = KDBusConnectionPool::threadConnection().lastError().message(); if (reason.isEmpty()) { reason = QStringLiteral("this service is probably running already."); } qCCritical(AKONADIAGENTBASE_LOG) << "Unable to register service" << serviceId << "at D-Bus:" << reason; if (QThread::currentThread() == QCoreApplication::instance()->thread()) { QCoreApplication::instance()->exit(1); } } else { AgentBasePrivate::delayedInit(); } } void changeProcessed() override { if (m_recursiveMover) { m_recursiveMover->changeProcessed(); QTimer::singleShot(0, m_recursiveMover.data(), &RecursiveMover::replayNext); return; } mChangeRecorder->changeProcessed(); if (!mChangeRecorder->isEmpty()) { scheduler->scheduleChangeReplay(); } scheduler->taskDone(); } void slotAbortRequested(); void slotDeliveryDone(KJob *job); void slotCollectionSyncDone(KJob *job); void slotLocalListDone(KJob *job); void slotSynchronizeCollection(const Collection &col); void slotItemRetrievalCollectionFetchDone(KJob *job); void slotCollectionListDone(KJob *job); void slotSynchronizeCollectionAttributes(const Collection &col); void slotCollectionListForAttributesDone(KJob *job); void slotCollectionAttributesSyncDone(KJob *job); void slotSynchronizeTags(); void slotSynchronizeRelations(); void slotAttributeRetrievalCollectionFetchDone(KJob *job); void slotItemSyncDone(KJob *job); void slotPercent(KJob *job, unsigned long percent); void slotDelayedEmitProgress(); void slotDeleteResourceCollection(); void slotDeleteResourceCollectionDone(KJob *job); void slotCollectionDeletionDone(KJob *job); void slotInvalidateCache(const Akonadi::Collection &collection); void slotPrepareItemRetrieval(const Akonadi::Item &item); void slotPrepareItemRetrievalResult(KJob *job); void slotPrepareItemsRetrieval(const QVector &item); void slotPrepareItemsRetrievalResult(KJob *job); void changeCommittedResult(KJob *job); void slotRecursiveMoveReplay(RecursiveMover *mover); void slotRecursiveMoveReplayResult(KJob *job); void slotTagSyncDone(KJob *job); void slotRelationSyncDone(KJob *job); void slotSessionReconnected() { Q_Q(ResourceBase); new ResourceSelectJob(q->identifier()); } void createItemSyncInstanceIfMissing() { Q_Q(ResourceBase); Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::SyncCollection, "createItemSyncInstance", "Calling items retrieval methods although no item retrieval is in progress"); if (!mItemSyncer) { mItemSyncer = new ItemSync(q->currentCollection()); mItemSyncer->setTransactionMode(mItemTransactionMode); mItemSyncer->setBatchSize(mItemSyncBatchSize); mItemSyncer->setMergeMode(mItemMergeMode); if (mItemSyncFetchScope) { mItemSyncer->setFetchScope(*mItemSyncFetchScope); } mItemSyncer->setDisableAutomaticDeliveryDone(mDisableAutomaticItemDeliveryDone); mItemSyncer->setProperty("collection", QVariant::fromValue(q->currentCollection())); connect(mItemSyncer, SIGNAL(percent(KJob*,ulong)), q, SLOT(slotPercent(KJob*,ulong))); connect(mItemSyncer, SIGNAL(result(KJob*)), q, SLOT(slotItemSyncDone(KJob*))); connect(mItemSyncer, &ItemSync::readyForNextBatch, q, &ResourceBase::retrieveNextItemSyncBatch); } Q_ASSERT(mItemSyncer); } public Q_SLOTS: // Dump the state of the scheduler Q_SCRIPTABLE QString dumpToString() const { Q_Q(const ResourceBase); return scheduler->dumpToString() + QLatin1Char('\n') + q->dumpResourceToString(); } Q_SCRIPTABLE void dump() { scheduler->dump(); } Q_SCRIPTABLE void clear() { scheduler->clear(); } protected Q_SLOTS: // reimplementations from AgentbBasePrivate, containing sanity checks that only apply to resources // such as making sure that RIDs are present as well as translations of cross-resource moves // TODO: we could possibly add recovery code for no-RID notifications by re-enquing those to the change recorder // as the corresponding Add notifications, although that contains a risk of endless fail/retry loops void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemAdded(item, collection); } void itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers) override { if (item.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemChanged(item, partIdentifiers); } void itemsFlagsChanged(const Item::List &items, const QSet &addedFlags, const QSet &removedFlags) override { if (addedFlags.isEmpty() && removedFlags.isEmpty()) { changeProcessed(); return; } Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsFlagsChanged(validItems, addedFlags, removedFlags); } void itemsTagsChanged(const Item::List &items, const QSet &addedTags, const QSet &removedTags) override { if (addedTags.isEmpty() && removedTags.isEmpty()) { changeProcessed(); return; } Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsTagsChanged(validItems, addedTags, removedTags); } // TODO move the move translation code from AgentBasePrivate here, it's wrong for agents void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &destination) override { if (item.remoteId().isEmpty() || destination.remoteId().isEmpty() || destination == source) { changeProcessed(); return; } AgentBasePrivate::itemMoved(item, source, destination); } void itemsMoved(const Item::List &items, const Collection &source, const Collection &destination) override { if (destination.remoteId().isEmpty() || destination == source) { changeProcessed(); return; } Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsMoved(validItems, source, destination); } void itemRemoved(const Akonadi::Item &item) override { if (item.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemRemoved(item); } void itemsRemoved(const Item::List &items) override { Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsRemoved(validItems); } void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override { if (parent.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionAdded(collection, parent); } void collectionChanged(const Akonadi::Collection &collection) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionChanged(collection); } void collectionChanged(const Akonadi::Collection &collection, const QSet &partIdentifiers) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionChanged(collection, partIdentifiers); } void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination) override { // unknown destination or source == destination means we can't do/don't have to do anything if (destination.remoteId().isEmpty() || source == destination) { changeProcessed(); return; } // inter-resource moves, requires we know which resources the source and destination are in though if (!source.resource().isEmpty() && !destination.resource().isEmpty() && source.resource() != destination.resource()) { if (source.resource() == q_ptr->identifier()) { // moved away from us AgentBasePrivate::collectionRemoved(collection); } else if (destination.resource() == q_ptr->identifier()) { // moved to us scheduler->taskDone(); // stop change replay for now RecursiveMover *mover = new RecursiveMover(this); mover->setCollection(collection, destination); scheduler->scheduleMoveReplay(collection, mover); } return; } // intra-resource move, requires the moved collection to have a valid id though if (collection.remoteId().isEmpty()) { changeProcessed(); return; } // intra-resource move, ie. something we can handle internally AgentBasePrivate::collectionMoved(collection, source, destination); } void collectionRemoved(const Akonadi::Collection &collection) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionRemoved(collection); } void tagAdded(const Akonadi::Tag &tag) override { if (!tag.isValid()) { changeProcessed(); return; } AgentBasePrivate::tagAdded(tag); } void tagChanged(const Akonadi::Tag &tag) override { if (tag.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::tagChanged(tag); } void tagRemoved(const Akonadi::Tag &tag) override { if (tag.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::tagRemoved(tag); } public: // synchronize states Collection currentCollection; ResourceScheduler *scheduler = nullptr; ItemSync *mItemSyncer = nullptr; ItemFetchScope *mItemSyncFetchScope = nullptr; ItemSync::TransactionMode mItemTransactionMode; ItemSync::MergeMode mItemMergeMode; CollectionSync *mCollectionSyncer = nullptr; TagSync *mTagSyncer = nullptr; RelationSync *mRelationSyncer = nullptr; bool mHierarchicalRid; QTimer mProgressEmissionCompressor; int mUnemittedProgress; QMap mUnemittedAdvancedStatus; bool mAutomaticProgressReporting; bool mDisableAutomaticItemDeliveryDone; QPointer m_recursiveMover; int mItemSyncBatchSize; QSet mKeepLocalCollectionChanges; KJob *mCurrentCollectionFetchJob = nullptr; bool mScheduleAttributeSyncBeforeCollectionSync; }; ResourceBase::ResourceBase(const QString &id) : AgentBase(new ResourceBasePrivate(this), id) { Q_D(ResourceBase); qDBusRegisterMetaType(); new Akonadi__ResourceAdaptor(this); d->scheduler = new ResourceScheduler(this); d->mChangeRecorder->setChangeRecordingEnabled(true); d->mChangeRecorder->setCollectionMoveTranslationEnabled(false); // we deal with this ourselves connect(d->mChangeRecorder, &ChangeRecorder::changesAdded, d->scheduler, &ResourceScheduler::scheduleChangeReplay); d->mChangeRecorder->setResourceMonitored(d->mId.toLatin1()); d->mChangeRecorder->fetchCollection(true); connect(d->scheduler, &ResourceScheduler::executeFullSync, this, &ResourceBase::retrieveCollections); connect(d->scheduler, &ResourceScheduler::executeCollectionTreeSync, this, &ResourceBase::retrieveCollections); connect(d->scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection)), SLOT(slotSynchronizeCollection(Akonadi::Collection))); connect(d->scheduler, SIGNAL(executeCollectionAttributesSync(Akonadi::Collection)), SLOT(slotSynchronizeCollectionAttributes(Akonadi::Collection))); connect(d->scheduler, SIGNAL(executeTagSync()), SLOT(slotSynchronizeTags())); connect(d->scheduler, SIGNAL(executeRelationSync()), SLOT(slotSynchronizeRelations())); connect(d->scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet)), SLOT(slotPrepareItemRetrieval(Akonadi::Item))); connect(d->scheduler, SIGNAL(executeItemsFetch(QVector,QSet)), SLOT(slotPrepareItemsRetrieval(QVector))); connect(d->scheduler, SIGNAL(executeResourceCollectionDeletion()), SLOT(slotDeleteResourceCollection())); connect(d->scheduler, SIGNAL(executeCacheInvalidation(Akonadi::Collection)), SLOT(slotInvalidateCache(Akonadi::Collection))); connect(d->scheduler, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); connect(d->scheduler, &ResourceScheduler::executeChangeReplay, d->mChangeRecorder, &ChangeRecorder::replayNext); connect(d->scheduler, SIGNAL(executeRecursiveMoveReplay(RecursiveMover*)), SLOT(slotRecursiveMoveReplay(RecursiveMover*))); connect(d->scheduler, &ResourceScheduler::fullSyncComplete, this, &ResourceBase::synchronized); connect(d->scheduler, &ResourceScheduler::collectionTreeSyncComplete, this, &ResourceBase::collectionTreeSynchronized); connect(d->mChangeRecorder, &ChangeRecorder::nothingToReplay, d->scheduler, &ResourceScheduler::taskDone); connect(d->mChangeRecorder, &Monitor::collectionRemoved, d->scheduler, &ResourceScheduler::collectionRemoved); connect(this, SIGNAL(abortRequested()), this, SLOT(slotAbortRequested())); connect(this, &ResourceBase::synchronized, d->scheduler, &ResourceScheduler::taskDone); connect(this, &ResourceBase::collectionTreeSynchronized, d->scheduler, &ResourceScheduler::taskDone); connect(this, &AgentBase::agentNameChanged, this, &ResourceBase::nameChanged); connect(&d->mProgressEmissionCompressor, SIGNAL(timeout()), this, SLOT(slotDelayedEmitProgress())); d->scheduler->setOnline(d->mOnline); if (!d->mChangeRecorder->isEmpty()) { d->scheduler->scheduleChangeReplay(); } new ResourceSelectJob(identifier()); connect(d->mChangeRecorder->session(), SIGNAL(reconnected()), SLOT(slotSessionReconnected())); } ResourceBase::~ResourceBase() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName(const QString &name) { AgentBase::setAgentName(name); } QString ResourceBase::name() const { return AgentBase::agentName(); } QString ResourceBase::parseArguments(int argc, char **argv) { Q_UNUSED(argc); QCommandLineOption identifierOption(QStringLiteral("identifier"), i18nc("@label command line option", "Resource identifier"), QStringLiteral("argument")); QCommandLineParser parser; parser.addOption(identifierOption); parser.addHelpOption(); parser.addVersionOption(); parser.process(*qApp); parser.setApplicationDescription(i18n("Akonadi Resource")); if (!parser.isSet(identifierOption)) { qCDebug(AKONADIAGENTBASE_LOG) << "Identifier argument missing"; exit(1); } const QString identifier = parser.value(identifierOption); if (identifier.isEmpty()) { qCDebug(AKONADIAGENTBASE_LOG) << "Identifier is empty"; exit(1); } QCoreApplication::setApplicationName(ServerManager::addNamespace(identifier)); QCoreApplication::setApplicationVersion(QStringLiteral(AKONADI_VERSION_STRING)); const QFileInfo fi(QString::fromLocal8Bit(argv[0])); // strip off full path and possible .exe suffix const QString catalog = fi.baseName(); QTranslator *translator = new QTranslator(); translator->load(catalog); QCoreApplication::installTranslator(translator); return identifier; } int ResourceBase::init(ResourceBase *r) { int rv = qApp->exec(); delete r; return rv; } void ResourceBasePrivate::slotAbortRequested() { Q_Q(ResourceBase); scheduler->cancelQueues(); q->abortActivity(); } void ResourceBase::itemRetrieved(const Item &item) { Q_D(ResourceBase); Q_ASSERT(d->scheduler->currentTask().type == ResourceScheduler::FetchItem); if (!item.isValid()) { d->scheduler->itemFetchDone(i18nc("@info", "Invalid item retrieved")); return; } Item i(item); const QSet requestedParts = d->scheduler->currentTask().itemParts; for (const QByteArray &part : requestedParts) { if (!item.loadedPayloadParts().contains(part)) { qCWarning(AKONADIAGENTBASE_LOG) << "Item does not provide part" << part; } } ItemModifyJob *job = new ItemModifyJob(i); job->d_func()->setSilent(true); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->disableRevisionCheck(); connect(job, SIGNAL(result(KJob*)), SLOT(slotDeliveryDone(KJob*))); } void ResourceBasePrivate::slotDeliveryDone(KJob *job) { Q_Q(ResourceBase); Q_ASSERT(scheduler->currentTask().type == ResourceScheduler::FetchItem); if (job->error()) { emit q->error(i18nc("@info", "Error while creating item: %1", job->errorString())); } scheduler->itemFetchDone(QString()); } void ResourceBase::collectionAttributesRetrieved(const Collection &collection) { Q_D(ResourceBase); Q_ASSERT(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionAttributes); if (!collection.isValid()) { emit attributesSynchronized(d->scheduler->currentTask().collection.id()); d->scheduler->taskDone(); return; } CollectionModifyJob *job = new CollectionModifyJob(collection); connect(job, SIGNAL(result(KJob*)), SLOT(slotCollectionAttributesSyncDone(KJob*))); } void ResourceBasePrivate::slotCollectionAttributesSyncDone(KJob *job) { Q_Q(ResourceBase); Q_ASSERT(scheduler->currentTask().type == ResourceScheduler::SyncCollectionAttributes); if (job->error()) { emit q->error(i18nc("@info", "Error while updating collection: %1", job->errorString())); } emit q->attributesSynchronized(scheduler->currentTask().collection.id()); scheduler->taskDone(); } void ResourceBasePrivate::slotDeleteResourceCollection() { Q_Q(ResourceBase); CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel); job->fetchScope().setResource(q->identifier()); connect(job, SIGNAL(result(KJob*)), q, SLOT(slotDeleteResourceCollectionDone(KJob*))); } void ResourceBasePrivate::slotDeleteResourceCollectionDone(KJob *job) { Q_Q(ResourceBase); if (job->error()) { emit q->error(job->errorString()); scheduler->taskDone(); } else { const CollectionFetchJob *fetchJob = static_cast(job); if (!fetchJob->collections().isEmpty()) { CollectionDeleteJob *job = new CollectionDeleteJob(fetchJob->collections().at(0)); connect(job, SIGNAL(result(KJob*)), q, SLOT(slotCollectionDeletionDone(KJob*))); } else { // there is no resource collection, so just ignore the request scheduler->taskDone(); } } } void ResourceBasePrivate::slotCollectionDeletionDone(KJob *job) { Q_Q(ResourceBase); if (job->error()) { emit q->error(job->errorString()); } scheduler->taskDone(); } void ResourceBasePrivate::slotInvalidateCache(const Akonadi::Collection &collection) { Q_Q(ResourceBase); InvalidateCacheJob *job = new InvalidateCacheJob(collection, q); connect(job, &KJob::result, scheduler, &ResourceScheduler::taskDone); } void ResourceBase::changeCommitted(const Item &item) { changesCommitted(Item::List() << item); } void ResourceBase::changesCommitted(const Item::List &items) { TransactionSequence *transaction = new TransactionSequence(this); connect(transaction, SIGNAL(finished(KJob*)), this, SLOT(changeCommittedResult(KJob*))); // Modify the items one-by-one, because STORE does not support mass RID change for (const Item &item : items) { ItemModifyJob *job = new ItemModifyJob(item, transaction); job->d_func()->setClean(); job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error? job->setIgnorePayload(true); // we only want to reset the dirty flag and update the remote id } } void ResourceBase::changeCommitted(const Collection &collection) { CollectionModifyJob *job = new CollectionModifyJob(collection); connect(job, SIGNAL(result(KJob*)), SLOT(changeCommittedResult(KJob*))); } void ResourceBasePrivate::changeCommittedResult(KJob *job) { if (job->error()) { qCWarning(AKONADIAGENTBASE_LOG) << job->errorText(); } Q_Q(ResourceBase); if (qobject_cast(job)) { if (job->error()) { emit q->error(i18nc("@info", "Updating local collection failed: %1.", job->errorText())); } mChangeRecorder->d_ptr->invalidateCache(static_cast(job)->collection()); } else { if (job->error()) { emit q->error(i18nc("@info", "Updating local items failed: %1.", job->errorText())); } // Item and tag cache is invalidated by modify job } changeProcessed(); } void ResourceBase::changeCommitted(const Tag &tag) { TagModifyJob *job = new TagModifyJob(tag); connect(job, SIGNAL(result(KJob*)), SLOT(changeCommittedResult(KJob*))); } QString ResourceBase::requestItemDelivery(const QList &uids, const QByteArrayList &parts) { Q_D(ResourceBase); if (!isOnline()) { const QString errorMsg = i18nc("@info", "Cannot fetch item in offline mode."); emit error(errorMsg); return errorMsg; } setDelayedReply(true); Item::List items; items.reserve(uids.size()); for (auto uid : uids) { items.push_back(Item(uid)); } d->scheduler->scheduleItemsFetch(items, QSet::fromList(parts), message()); return QString(); } void ResourceBase::collectionsRetrieved(const Collection::List &collections) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrieved()", "Calling collectionsRetrieved() although no collection retrieval is in progress"); if (!d->mCollectionSyncer) { d->mCollectionSyncer = new CollectionSync(identifier()); d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid); d->mCollectionSyncer->setKeepLocalChanges(d->mKeepLocalCollectionChanges); connect(d->mCollectionSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*))); } d->mCollectionSyncer->setRemoteCollections(collections); } void ResourceBase::collectionsRetrievedIncremental(const Collection::List &changedCollections, const Collection::List &removedCollections) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievedIncremental()", "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress"); if (!d->mCollectionSyncer) { d->mCollectionSyncer = new CollectionSync(identifier()); d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid); d->mCollectionSyncer->setKeepLocalChanges(d->mKeepLocalCollectionChanges); connect(d->mCollectionSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*))); } d->mCollectionSyncer->setRemoteCollections(changedCollections, removedCollections); } void ResourceBase::setCollectionStreamingEnabled(bool enable) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::setCollectionStreamingEnabled()", "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress"); if (!d->mCollectionSyncer) { d->mCollectionSyncer = new CollectionSync(identifier()); d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid); connect(d->mCollectionSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*))); } d->mCollectionSyncer->setStreamingEnabled(enable); } void ResourceBase::collectionsRetrievalDone() { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievalDone()", "Calling collectionsRetrievalDone() although no collection retrieval is in progress"); // streaming enabled, so finalize the sync if (d->mCollectionSyncer) { d->mCollectionSyncer->retrievalDone(); } else { // user did the sync himself, we are done now // FIXME: we need the same special case for SyncAll as in slotCollectionSyncDone here! d->scheduler->taskDone(); } } void ResourceBase::setKeepLocalCollectionChanges(const QSet &parts) { Q_D(ResourceBase); d->mKeepLocalCollectionChanges = parts; } void ResourceBasePrivate::slotCollectionSyncDone(KJob *job) { Q_Q(ResourceBase); mCollectionSyncer = nullptr; if (job->error()) { if (job->error() != Job::UserCanceled) { emit q->error(job->errorString()); } } else { if (scheduler->currentTask().type == ResourceScheduler::SyncAll) { CollectionFetchJob *list = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); list->setFetchScope(q->changeRecorder()->collectionFetchScope()); list->fetchScope().fetchAttribute(); list->fetchScope().fetchAttribute(); list->fetchScope().setResource(mId); list->fetchScope().setListFilter(CollectionFetchScope::Sync); q->connect(list, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*))); return; } else if (scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree) { scheduler->scheduleCollectionTreeSyncCompletion(); } } scheduler->taskDone(); } namespace { bool sortCollectionsForSync(const Collection &l, const Collection &r) { const auto lType = l.hasAttribute() ? l.attribute()->collectionType() : QByteArray(); const bool lInbox = (lType == "inbox") || (l.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0); const bool lFav = l.hasAttribute(); const auto rType = r.hasAttribute() ? r.attribute()->collectionType() : QByteArray(); const bool rInbox = (rType == "inbox") || (r.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0); const bool rFav = r.hasAttribute(); // inbox is always first if (lInbox) { return true; } else if (rInbox) { return false; } // favorites right after inbox if (lFav) { return !rInbox; } else if (rFav) { return lInbox; } // trash is always last (unless it's favorite) if (lType == "trash") { return false; } else if (rType == "trash") { return true; } // Fallback to sorting by id return l.id() < r.id(); } } void ResourceBasePrivate::slotLocalListDone(KJob *job) { Q_Q(ResourceBase); if (job->error()) { emit q->error(job->errorString()); } else { Collection::List cols = static_cast(job)->collections(); std::sort(cols.begin(), cols.end(), sortCollectionsForSync); for (const Collection &col : qAsConst(cols)) { scheduler->scheduleSync(col); } scheduler->scheduleFullSyncCompletion(); } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection(const Collection &col) { Q_Q(ResourceBase); currentCollection = col; // This can happen due to FetchHelper::triggerOnDemandFetch() in the akonadi server (not an error). if (!col.remoteId().isEmpty()) { // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentMimeTypes(); contentTypes.removeAll(Collection::mimeType()); contentTypes.removeAll(Collection::virtualMimeType()); if (!contentTypes.isEmpty() || col.isVirtual()) { if (mAutomaticProgressReporting) { emit q->status(AgentBase::Running, i18nc("@info:status", "Syncing folder '%1'", currentCollection.displayName())); } Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this); fetchJob->setFetchScope(q->changeRecorder()->collectionFetchScope()); connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(slotItemRetrievalCollectionFetchDone(KJob*))); mCurrentCollectionFetchJob = fetchJob; return; } } scheduler->taskDone(); } void ResourceBasePrivate::slotItemRetrievalCollectionFetchDone(KJob *job) { Q_Q(ResourceBase); mCurrentCollectionFetchJob = nullptr; if (job->error()) { qCWarning(AKONADIAGENTBASE_LOG) << "Failed to retrieve collection for sync: " << job->errorString(); q->cancelTask(i18n("Failed to retrieve collection for sync.")); return; } Akonadi::CollectionFetchJob *fetchJob = static_cast(job); const Collection::List collections = fetchJob->collections(); if (collections.isEmpty()) { qCWarning(AKONADIAGENTBASE_LOG) << "The fetch job returned empty collection set. This is unexpected."; q->cancelTask(i18n("Failed to retrieve collection for sync.")); return; } q->retrieveItems(collections.at(0)); } int ResourceBase::itemSyncBatchSize() const { Q_D(const ResourceBase); return d->mItemSyncBatchSize; } void ResourceBase::setItemSyncBatchSize(int batchSize) { Q_D(ResourceBase); d->mItemSyncBatchSize = batchSize; } void ResourceBase::setScheduleAttributeSyncBeforeItemSync(bool enable) { Q_D(ResourceBase); d->mScheduleAttributeSyncBeforeCollectionSync = enable; } void ResourceBasePrivate::slotSynchronizeCollectionAttributes(const Collection &col) { Q_Q(ResourceBase); Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this); fetchJob->setFetchScope(q->changeRecorder()->collectionFetchScope()); connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(slotAttributeRetrievalCollectionFetchDone(KJob*))); Q_ASSERT(!mCurrentCollectionFetchJob); mCurrentCollectionFetchJob = fetchJob; } void ResourceBasePrivate::slotAttributeRetrievalCollectionFetchDone(KJob *job) { mCurrentCollectionFetchJob = nullptr; Q_Q(ResourceBase); if (job->error()) { qCWarning(AKONADIAGENTBASE_LOG) << "Failed to retrieve collection for attribute sync: " << job->errorString(); q->cancelTask(i18n("Failed to retrieve collection for attribute sync.")); return; } Akonadi::CollectionFetchJob *fetchJob = static_cast(job); QMetaObject::invokeMethod(q, "retrieveCollectionAttributes", Q_ARG(Akonadi::Collection, fetchJob->collections().at(0))); } void ResourceBasePrivate::slotSynchronizeTags() { Q_Q(ResourceBase); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QMetaObject::invokeMethod(this, [this, q] { q->retrieveTags(); }); + QMetaObject::invokeMethod(this, [q] { q->retrieveTags(); }); #else QMetaObject::invokeMethod(q, "retrieveTags"); #endif } void ResourceBasePrivate::slotSynchronizeRelations() { Q_Q(ResourceBase); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QMetaObject::invokeMethod(this, [this, q] { q->retrieveRelations(); }); + QMetaObject::invokeMethod(this, [q] { q->retrieveRelations(); }); #else QMetaObject::invokeMethod(q, "retrieveRelations"); #endif } void ResourceBasePrivate::slotPrepareItemRetrieval(const Item &item) { Q_Q(ResourceBase); auto fetch = new ItemFetchJob(item, this); // we always need at least parent so we can use ItemCreateJob to merge fetch->fetchScope().setAncestorRetrieval(qMax(ItemFetchScope::Parent, q->changeRecorder()->itemFetchScope().ancestorRetrieval())); fetch->fetchScope().setCacheOnly(true); fetch->fetchScope().setFetchRemoteIdentification(true); // copy list of attributes to fetch const QSet attributes = q->changeRecorder()->itemFetchScope().attributes(); for (const auto &attribute : attributes) { fetch->fetchScope().fetchAttribute(attribute); } q->connect(fetch, SIGNAL(result(KJob*)), SLOT(slotPrepareItemRetrievalResult(KJob*))); } void ResourceBasePrivate::slotPrepareItemRetrievalResult(KJob *job) { Q_Q(ResourceBase); Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::FetchItem, "ResourceBasePrivate::slotPrepareItemRetrievalResult()", "Preparing item retrieval although no item retrieval is in progress"); if (job->error()) { q->cancelTask(job->errorText()); return; } ItemFetchJob *fetch = qobject_cast(job); if (fetch->items().count() != 1) { q->cancelTask(i18n("The requested item no longer exists")); return; } const QSet parts = scheduler->currentTask().itemParts; if (!q->retrieveItem(fetch->items().at(0), parts)) { q->cancelTask(); } } void ResourceBasePrivate::slotPrepareItemsRetrieval(const QVector &items) { Q_Q(ResourceBase); ItemFetchJob *fetch = new ItemFetchJob(items, this); // we always need at least parent so we can use ItemCreateJob to merge fetch->fetchScope().setAncestorRetrieval(qMax(ItemFetchScope::Parent, q->changeRecorder()->itemFetchScope().ancestorRetrieval())); fetch->fetchScope().setCacheOnly(true); fetch->fetchScope().setFetchRemoteIdentification(true); // It's possible that one or more items were removed before this task was // executed, so ignore it and just handle the rest. fetch->fetchScope().setIgnoreRetrievalErrors(true); // copy list of attributes to fetch const QSet attributes = q->changeRecorder()->itemFetchScope().attributes(); for (const auto &attribute : attributes) { fetch->fetchScope().fetchAttribute(attribute); } q->connect(fetch, SIGNAL(result(KJob*)), SLOT(slotPrepareItemsRetrievalResult(KJob*))); } void ResourceBasePrivate::slotPrepareItemsRetrievalResult(KJob *job) { Q_Q(ResourceBase); Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::FetchItems, "ResourceBasePrivate::slotPrepareItemsRetrievalResult()", "Preparing items retrieval although no items retrieval is in progress"); if (job->error()) { q->cancelTask(job->errorText()); return; } ItemFetchJob *fetch = qobject_cast(job); const auto items = fetch->items(); if (items.isEmpty()) { q->cancelTask(); return; } const QSet parts = scheduler->currentTask().itemParts; Q_ASSERT(items.first().parentCollection().isValid()); if (!q->retrieveItems(items, parts)) { q->cancelTask(); } } void ResourceBasePrivate::slotRecursiveMoveReplay(RecursiveMover *mover) { Q_Q(ResourceBase); Q_ASSERT(mover); Q_ASSERT(!m_recursiveMover); m_recursiveMover = mover; connect(mover, SIGNAL(result(KJob*)), q, SLOT(slotRecursiveMoveReplayResult(KJob*))); mover->start(); } void ResourceBasePrivate::slotRecursiveMoveReplayResult(KJob *job) { Q_Q(ResourceBase); m_recursiveMover = nullptr; if (job->error()) { q->deferTask(); return; } changeProcessed(); } void ResourceBase::itemsRetrievalDone() { Q_D(ResourceBase); // streaming enabled, so finalize the sync if (d->mItemSyncer) { d->mItemSyncer->deliveryDone(); } else { if (d->scheduler->currentTask().type == ResourceScheduler::FetchItems) { d->scheduler->currentTask().sendDBusReplies(QString()); } // user did the sync himself, we are done now d->scheduler->taskDone(); } } void ResourceBase::clearCache() { Q_D(ResourceBase); d->scheduler->scheduleResourceCollectionDeletion(); } void ResourceBase::invalidateCache(const Collection &collection) { Q_D(ResourceBase); d->scheduler->scheduleCacheInvalidation(collection); } Collection ResourceBase::currentCollection() const { Q_D(const ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress"); return d->currentCollection; } Item ResourceBase::currentItem() const { Q_D(const ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::FetchItem, "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress"); return d->scheduler->currentTask().items[0]; } Item::List ResourceBase::currentItems() const { Q_D(const ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::FetchItems, "ResourceBase::currentItems()", "Trying to access current items although no items retrieval is in progress"); return d->scheduler->currentTask().items; } void ResourceBase::synchronizeCollectionTree() { d_func()->scheduler->scheduleCollectionTreeSync(); } void ResourceBase::synchronizeTags() { d_func()->scheduler->scheduleTagSync(); } void ResourceBase::synchronizeRelations() { d_func()->scheduler->scheduleRelationSync(); } void ResourceBase::cancelTask() { Q_D(ResourceBase); if (d->mCurrentCollectionFetchJob) { d->mCurrentCollectionFetchJob->kill(); d->mCurrentCollectionFetchJob = nullptr; } switch (d->scheduler->currentTask().type) { case ResourceScheduler::FetchItem: itemRetrieved(Item()); // sends the error reply and break; case ResourceScheduler::FetchItems: itemsRetrieved(Item::List()); break; case ResourceScheduler::ChangeReplay: d->changeProcessed(); break; case ResourceScheduler::SyncCollectionTree: case ResourceScheduler::SyncAll: if (d->mCollectionSyncer) { d->mCollectionSyncer->rollback(); } else { d->scheduler->taskDone(); } break; case ResourceScheduler::SyncCollection: if (d->mItemSyncer) { d->mItemSyncer->rollback(); } else { d->scheduler->taskDone(); } break; default: d->scheduler->taskDone(); } } void ResourceBase::cancelTask(const QString &msg) { cancelTask(); emit error(msg); } void ResourceBase::deferTask() { Q_D(ResourceBase); d->scheduler->deferTask(); } void ResourceBase::doSetOnline(bool state) { d_func()->scheduler->setOnline(state); } void ResourceBase::synchronizeCollection(qint64 collectionId) { synchronizeCollection(collectionId, false); } void ResourceBase::synchronizeCollection(qint64 collectionId, bool recursive) { CollectionFetchJob *job = new CollectionFetchJob(Collection(collectionId), recursive ? CollectionFetchJob::Recursive : CollectionFetchJob::Base); job->setFetchScope(changeRecorder()->collectionFetchScope()); job->fetchScope().setResource(identifier()); job->fetchScope().setListFilter(CollectionFetchScope::Sync); connect(job, SIGNAL(result(KJob*)), SLOT(slotCollectionListDone(KJob*))); } void ResourceBasePrivate::slotCollectionListDone(KJob *job) { if (!job->error()) { const Collection::List list = static_cast(job)->collections(); for (const Collection &collection : list) { //We also get collections that should not be synced but are part of the tree. if (collection.shouldList(Collection::ListSync) || collection.referenced()) { if (mScheduleAttributeSyncBeforeCollectionSync) { scheduler->scheduleAttributesSync(collection); } scheduler->scheduleSync(collection); } } } else { qCWarning(AKONADIAGENTBASE_LOG) << "Failed to fetch collection for collection sync: " << job->errorString(); } } void ResourceBase::synchronizeCollectionAttributes(const Akonadi::Collection &col) { Q_D(ResourceBase); d->scheduler->scheduleAttributesSync(col); } void ResourceBase::synchronizeCollectionAttributes(qint64 collectionId) { CollectionFetchJob *job = new CollectionFetchJob(Collection(collectionId), CollectionFetchJob::Base); job->setFetchScope(changeRecorder()->collectionFetchScope()); job->fetchScope().setResource(identifier()); connect(job, SIGNAL(result(KJob*)), SLOT(slotCollectionListForAttributesDone(KJob*))); } void ResourceBasePrivate::slotCollectionListForAttributesDone(KJob *job) { if (!job->error()) { const Collection::List list = static_cast(job)->collections(); if (!list.isEmpty()) { const Collection col = list.first(); scheduler->scheduleAttributesSync(col); } } // TODO: error handling } void ResourceBase::setTotalItems(int amount) { qCDebug(AKONADIAGENTBASE_LOG) << amount; Q_D(ResourceBase); setItemStreamingEnabled(true); if (d->mItemSyncer) { d->mItemSyncer->setTotalItems(amount); } } void ResourceBase::setDisableAutomaticItemDeliveryDone(bool disable) { Q_D(ResourceBase); if (d->mItemSyncer) { d->mItemSyncer->setDisableAutomaticDeliveryDone(disable); } d->mDisableAutomaticItemDeliveryDone = disable; } void ResourceBase::setItemStreamingEnabled(bool enable) { Q_D(ResourceBase); d->createItemSyncInstanceIfMissing(); if (d->mItemSyncer) { d->mItemSyncer->setStreamingEnabled(enable); } } void ResourceBase::itemsRetrieved(const Item::List &items) { Q_D(ResourceBase); if (d->scheduler->currentTask().type == ResourceScheduler::FetchItems) { auto trx = new TransactionSequence(this); connect(trx, SIGNAL(result(KJob*)), this, SLOT(slotItemSyncDone(KJob*))); for (const Item &item : items) { Q_ASSERT(item.parentCollection().isValid()); if (item.isValid()) { new ItemModifyJob(item, trx); } else if (!item.remoteId().isEmpty()) { auto job = new ItemCreateJob(item, item.parentCollection(), trx); job->setMerge(ItemCreateJob::RID); } else { // This should not happen, but just to be sure... new ItemModifyJob(item, trx); } } trx->commit(); } else { d->createItemSyncInstanceIfMissing(); if (d->mItemSyncer) { d->mItemSyncer->setFullSyncItems(items); } } } void ResourceBase::itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems) { Q_D(ResourceBase); d->createItemSyncInstanceIfMissing(); if (d->mItemSyncer) { d->mItemSyncer->setIncrementalSyncItems(changedItems, removedItems); } } void ResourceBasePrivate::slotItemSyncDone(KJob *job) { mItemSyncer = nullptr; Q_Q(ResourceBase); if (job->error() && job->error() != Job::UserCanceled) { emit q->error(job->errorString()); } if (scheduler->currentTask().type == ResourceScheduler::FetchItems) { scheduler->currentTask().sendDBusReplies((job->error() && job->error() != Job::UserCanceled) ? job->errorString() : QString()); } scheduler->taskDone(); } void ResourceBasePrivate::slotDelayedEmitProgress() { Q_Q(ResourceBase); if (mAutomaticProgressReporting) { emit q->percent(mUnemittedProgress); for (const QVariantMap &statusMap : qAsConst(mUnemittedAdvancedStatus)) { emit q->advancedStatus(statusMap); } } mUnemittedProgress = 0; mUnemittedAdvancedStatus.clear(); } void ResourceBasePrivate::slotPercent(KJob *job, unsigned long percent) { mUnemittedProgress = percent; const Collection collection = job->property("collection").value(); if (collection.isValid()) { QVariantMap statusMap; statusMap.insert(QStringLiteral("key"), QStringLiteral("collectionSyncProgress")); statusMap.insert(QStringLiteral("collectionId"), collection.id()); statusMap.insert(QStringLiteral("percent"), static_cast(percent)); mUnemittedAdvancedStatus[collection.id()] = statusMap; } // deliver completion right away, intermediate progress at 1s intervals if (percent == 100) { mProgressEmissionCompressor.stop(); slotDelayedEmitProgress(); } else if (!mProgressEmissionCompressor.isActive()) { mProgressEmissionCompressor.start(); } } void ResourceBase::setHierarchicalRemoteIdentifiersEnabled(bool enable) { Q_D(ResourceBase); d->mHierarchicalRid = enable; } void ResourceBase::scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument, SchedulePriority priority) { Q_D(ResourceBase); d->scheduler->scheduleCustomTask(receiver, method, argument, priority); } void ResourceBase::taskDone() { Q_D(ResourceBase); d->scheduler->taskDone(); } void ResourceBase::retrieveCollectionAttributes(const Collection &collection) { collectionAttributesRetrieved(collection); } void ResourceBase::retrieveTags() { Q_D(ResourceBase); d->scheduler->taskDone(); } void ResourceBase::retrieveRelations() { Q_D(ResourceBase); d->scheduler->taskDone(); } bool ResourceBase::retrieveItem(const Akonadi::Item &item, const QSet &parts) { Q_UNUSED(item); Q_UNUSED(parts); // retrieveItem() can no longer be pure virtual, because then we could not mark // it as deprecated (i.e. implementations would still be forced to implement it), // so instead we assert here. // NOTE: Don't change to Q_ASSERT_X here: while the macro can be disabled at // compile time, we want to hit this assert *ALWAYS*. qt_assert_x("Akonadi::ResourceBase::retrieveItem()", "The base implementation of retrieveItem() must never be reached. " "You must implement either retrieveItem() or retrieveItems(Akonadi::Item::List, QSet) overload " "to handle item retrieval requests.", __FILE__, __LINE__); return false; } bool ResourceBase::retrieveItems(const Akonadi::Item::List &items, const QSet &parts) { Q_D(ResourceBase); // If we reach this implementation of retrieveItems() then it means that the // resource is still using the deprecated retrieveItem() method, so we explode // this to a myriad of tasks in scheduler and let them be processed one by one const qint64 id = d->scheduler->currentTask().serial; for (const auto &item : items) { d->scheduler->scheduleItemFetch(item, parts, d->scheduler->currentTask().dbusMsgs, id); } taskDone(); return true; } void Akonadi::ResourceBase::abortActivity() { } void ResourceBase::setItemTransactionMode(ItemSync::TransactionMode mode) { Q_D(ResourceBase); d->mItemTransactionMode = mode; } void ResourceBase::setItemSynchronizationFetchScope(const ItemFetchScope &fetchScope) { Q_D(ResourceBase); if (!d->mItemSyncFetchScope) { d->mItemSyncFetchScope = new ItemFetchScope; } *(d->mItemSyncFetchScope) = fetchScope; } void ResourceBase::setItemMergingMode(ItemSync::MergeMode mode) { Q_D(ResourceBase); d->mItemMergeMode = mode; } void ResourceBase::setAutomaticProgressReporting(bool enabled) { Q_D(ResourceBase); d->mAutomaticProgressReporting = enabled; } QString ResourceBase::dumpNotificationListToString() const { Q_D(const ResourceBase); return d->dumpNotificationListToString(); } QString ResourceBase::dumpSchedulerToString() const { Q_D(const ResourceBase); return d->dumpToString(); } void ResourceBase::dumpMemoryInfo() const { Q_D(const ResourceBase); return d->dumpMemoryInfo(); } QString ResourceBase::dumpMemoryInfoToString() const { Q_D(const ResourceBase); return d->dumpMemoryInfoToString(); } void ResourceBase::tagsRetrieved(const Tag::List &tags, const QHash &tagMembers) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncTags || d->scheduler->currentTask().type == ResourceScheduler::SyncAll || d->scheduler->currentTask().type == ResourceScheduler::Custom, "ResourceBase::tagsRetrieved()", "Calling tagsRetrieved() although no tag retrieval is in progress"); if (!d->mTagSyncer) { d->mTagSyncer = new TagSync(this); connect(d->mTagSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mTagSyncer, SIGNAL(result(KJob*)), SLOT(slotTagSyncDone(KJob*))); } d->mTagSyncer->setFullTagList(tags); d->mTagSyncer->setTagMembers(tagMembers); } void ResourceBasePrivate::slotTagSyncDone(KJob *job) { Q_Q(ResourceBase); mTagSyncer = nullptr; if (job->error()) { if (job->error() != Job::UserCanceled) { qCWarning(AKONADIAGENTBASE_LOG) << "TagSync failed: " << job->errorString(); emit q->error(job->errorString()); } } scheduler->taskDone(); } void ResourceBase::relationsRetrieved(const Relation::List &relations) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncRelations || d->scheduler->currentTask().type == ResourceScheduler::SyncAll || d->scheduler->currentTask().type == ResourceScheduler::Custom, "ResourceBase::relationsRetrieved()", "Calling relationsRetrieved() although no relation retrieval is in progress"); if (!d->mRelationSyncer) { d->mRelationSyncer = new RelationSync(this); connect(d->mRelationSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mRelationSyncer, SIGNAL(result(KJob*)), SLOT(slotRelationSyncDone(KJob*))); } d->mRelationSyncer->setRemoteRelations(relations); } void ResourceBasePrivate::slotRelationSyncDone(KJob *job) { Q_Q(ResourceBase); mRelationSyncer = nullptr; if (job->error()) { if (job->error() != Job::UserCanceled) { qCWarning(AKONADIAGENTBASE_LOG) << "RelationSync failed: " << job->errorString(); emit q->error(job->errorString()); } } scheduler->taskDone(); } #include "resourcebase.moc" #include "moc_resourcebase.cpp" diff --git a/src/akonadicontrol/agentinstance.h b/src/akonadicontrol/agentinstance.h index d45aa5e02..b39b29637 100644 --- a/src/akonadicontrol/agentinstance.h +++ b/src/akonadicontrol/agentinstance.h @@ -1,192 +1,192 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADICONTROL_AGENTINSTANCE_H #define AKONADICONTROL_AGENTINSTANCE_H #include "controlinterface.h" #include "statusinterface.h" #include "resourceinterface.h" #include "preprocessorinterface.h" #include "searchinterface.h" #include #include #include #include class AgentManager; class AgentType; /** * Represents one agent instance and takes care of communication with it. * * The agent exposes multiple D-Bus interfaces. The Control and the Status * interfaces are implemented by all the agents. The Resource and Preprocessor * interfaces are obviously implemented only by the agents impersonating resources or * preprocessors. */ class AgentInstance : public QObject { Q_OBJECT public: typedef QSharedPointer Ptr; explicit AgentInstance(AgentManager *manager); virtual ~AgentInstance() { } /** Set/get the unique identifier of this AgentInstance */ Q_REQUIRED_RESULT QString identifier() const { return mIdentifier; } void setIdentifier(const QString &identifier) { mIdentifier = identifier; } Q_REQUIRED_RESULT QString agentType() const { return mType; } Q_REQUIRED_RESULT int status() const { return mStatus; } Q_REQUIRED_RESULT QString statusMessage() const { return mStatusMessage; } Q_REQUIRED_RESULT int progress() const { return mPercent; } Q_REQUIRED_RESULT bool isOnline() const { return mOnline; } Q_REQUIRED_RESULT QString resourceName() const { return mResourceName; } virtual bool start(const AgentType &agentInfo) = 0; virtual void quit(); virtual void cleanup(); virtual void restartWhenIdle() = 0; virtual void configure(qlonglong windowId) = 0; Q_REQUIRED_RESULT bool hasResourceInterface() const { return mResourceInterface; } Q_REQUIRED_RESULT bool hasAgentInterface() const { return mAgentControlInterface && mAgentStatusInterface; } Q_REQUIRED_RESULT bool hasPreprocessorInterface() const { return mPreprocessorInterface; } org::freedesktop::Akonadi::Agent::Control *controlInterface() const { return mAgentControlInterface; } org::freedesktop::Akonadi::Agent::Status *statusInterface() const { return mAgentStatusInterface; } org::freedesktop::Akonadi::Agent::Search *searchInterface() const { return mSearchInterface; } org::freedesktop::Akonadi::Resource *resourceInterface() const { return mResourceInterface; } org::freedesktop::Akonadi::Preprocessor *preProcessorInterface() const { return mPreprocessorInterface; } - Q_REQUIRED_RESULT bool obtainAgentInterface(); - Q_REQUIRED_RESULT bool obtainResourceInterface(); - Q_REQUIRED_RESULT bool obtainPreprocessorInterface(); + bool obtainAgentInterface(); + bool obtainResourceInterface(); + bool obtainPreprocessorInterface(); protected Q_SLOTS: void statusChanged(int status, const QString &statusMsg); void advancedStatusChanged(const QVariantMap &status); void statusStateChanged(int status); void statusMessageChanged(const QString &msg); void percentChanged(int percent); void warning(const QString &msg); void error(const QString &msg); void onlineChanged(bool state); void resourceNameChanged(const QString &name); void refreshAgentStatus(); void refreshResourceStatus(); void errorHandler(const QDBusError &error); private: template T *findInterface(Akonadi::DBus::AgentType agentType, const char *path = nullptr); protected: void setAgentType(const QString &agentType) { mType = agentType; } private: QString mIdentifier; QString mType; AgentManager *mManager = nullptr; org::freedesktop::Akonadi::Agent::Control *mAgentControlInterface = nullptr; org::freedesktop::Akonadi::Agent::Status *mAgentStatusInterface = nullptr; org::freedesktop::Akonadi::Agent::Search *mSearchInterface = nullptr; org::freedesktop::Akonadi::Resource *mResourceInterface = nullptr; org::freedesktop::Akonadi::Preprocessor *mPreprocessorInterface = nullptr; int mStatus = 0; QString mStatusMessage; int mPercent = 0; QString mResourceName; bool mOnline = false; bool mPendingQuit = false; }; #endif diff --git a/src/core/changerecorder_p.cpp b/src/core/changerecorder_p.cpp index 9357c6a48..244a439bb 100644 --- a/src/core/changerecorder_p.cpp +++ b/src/core/changerecorder_p.cpp @@ -1,1242 +1,1241 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "changerecorder_p.h" #include "akonadicore_debug.h" #include #include #include #include #include using namespace Akonadi; ChangeRecorderPrivate::ChangeRecorderPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, ChangeRecorder *parent) : MonitorPrivate(dependenciesFactory_, parent) , settings(nullptr) , enableChangeRecording(true) , m_lastKnownNotificationsCount(0) , m_startOffset(0) , m_needFullSave(true) { } int ChangeRecorderPrivate::pipelineSize() const { if (enableChangeRecording) { return 0; // we fill the pipeline ourselves when using change recording } return MonitorPrivate::pipelineSize(); } void ChangeRecorderPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) { Q_Q(ChangeRecorder); const int oldChanges = pendingNotifications.size(); // with change recording disabled this will automatically take care of dispatching notification messages and saving MonitorPrivate::slotNotify(msg); if (enableChangeRecording && pendingNotifications.size() != oldChanges) { emit q->changesAdded(); } } // The QSettings object isn't actually used anymore, except for migrating old data // and it gives us the base of the filename to use. This is all historical. QString ChangeRecorderPrivate::notificationsFileName() const { return settings->fileName() + QStringLiteral("_changes.dat"); } void ChangeRecorderPrivate::loadNotifications() { pendingNotifications.clear(); Q_ASSERT(pipeline.isEmpty()); pipeline.clear(); const QString changesFileName = notificationsFileName(); /** * In an older version we recorded changes inside the settings object, however * for performance reasons we changed that to store them in a separated file. * If this file doesn't exists, it means we run the new version the first time, * so we have to read in the legacy list of changes first. */ if (!QFile::exists(changesFileName)) { QStringList list; settings->beginGroup(QStringLiteral("ChangeRecorder")); const int size = settings->beginReadArray(QStringLiteral("change")); for (int i = 0; i < size; ++i) { settings->setArrayIndex(i); Protocol::ChangeNotificationPtr msg; switch (static_cast(settings->value(QStringLiteral("type")).toInt())) { case Item: msg = loadItemNotification(settings); break; case Collection: msg = loadCollectionNotification(settings); break; case Tag: case Relation: case InvalidType: qWarning() << "Unexpected notification type in legacy store"; continue; } if (msg->isValid()) { pendingNotifications << msg; } } settings->endArray(); // save notifications to the new file... saveNotifications(); // ...delete the legacy list... settings->remove(QString()); settings->endGroup(); // ...and continue as usually } QFile file(changesFileName); if (file.open(QIODevice::ReadOnly)) { m_needFullSave = false; pendingNotifications = loadFrom(&file, m_needFullSave); } else { m_needFullSave = true; } notificationsLoaded(); } static const quint64 s_currentVersion = Q_UINT64_C(0x000700000000); static const quint64 s_versionMask = Q_UINT64_C(0xFFFF00000000); static const quint64 s_sizeMask = Q_UINT64_C(0x0000FFFFFFFF); QQueue ChangeRecorderPrivate::loadFrom(QFile *device, bool &needsFullSave) const { QDataStream stream(device); stream.setVersion(QDataStream::Qt_4_6); QByteArray sessionId; int type; QQueue list; quint64 sizeAndVersion; stream >> sizeAndVersion; const quint64 size = sizeAndVersion & s_sizeMask; const quint64 version = (sizeAndVersion & s_versionMask) >> 32; quint64 startOffset = 0; if (version >= 1) { stream >> startOffset; } // If we skip the first N items, then we'll need to rewrite the file on saving. // Also, if the file is old, it needs to be rewritten. needsFullSave = startOffset > 0 || version == 0; for (quint64 i = 0; i < size && !stream.atEnd(); ++i) { Protocol::ChangeNotificationPtr msg; stream >> sessionId; stream >> type; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting. Corrupt file:" << device->fileName(); break; } switch (static_cast(type)) { case Item: msg = loadItemNotification(stream, version); break; case Collection: msg = loadCollectionNotification(stream, version); break; case Tag: msg = loadTagNotification(stream, version); break; case Relation: msg = loadRelationNotification(stream, version); break; default: qCWarning(AKONADICORE_LOG) << "Unknown notification type"; break; } if (i < startOffset) { continue; } if (msg && msg->isValid()) { msg->setSessionId(sessionId); list << msg; } } return list; } QString ChangeRecorderPrivate::dumpNotificationListToString() const { if (!settings) { return QStringLiteral("No settings set in ChangeRecorder yet."); } const QString changesFileName = notificationsFileName(); QFile file(changesFileName); if (!file.open(QIODevice::ReadOnly)) { return QLatin1String("Error reading ") + changesFileName; } QString result; bool dummy; const QQueue notifications = loadFrom(&file, dummy); for (const Protocol::ChangeNotificationPtr &n : notifications) { result += Protocol::debugString(n) + QLatin1Char('\n'); } return result; } void ChangeRecorderPrivate::addToStream(QDataStream &stream, const Protocol::ChangeNotificationPtr &msg) { // We deliberately don't use Factory::serialize(), because the internal // serialization format could change at any point stream << msg->sessionId(); stream << int(mapToLegacyType(msg->type())); switch (msg->type()) { case Protocol::Command::ItemChangeNotification: saveItemNotification(stream, Protocol::cmdCast(msg)); break; case Protocol::Command::CollectionChangeNotification: saveCollectionNotification(stream, Protocol::cmdCast(msg)); break; case Protocol::Command::TagChangeNotification: saveTagNotification(stream, Protocol::cmdCast(msg)); break; case Protocol::Command::RelationChangeNotification: saveRelationNotification(stream, Protocol::cmdCast(msg)); break; default: qCWarning(AKONADICORE_LOG) << "Unexpected type?"; return; } } void ChangeRecorderPrivate::writeStartOffset() { if (!settings) { return; } QFile file(notificationsFileName()); if (!file.open(QIODevice::ReadWrite)) { qCWarning(AKONADICORE_LOG) << "Could not update notifications in file" << file.fileName(); return; } // Skip "countAndVersion" file.seek(8); //qCDebug(AKONADICORE_LOG) << "Writing start offset=" << m_startOffset; QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_6); stream << static_cast(m_startOffset); // Everything else stays unchanged } void ChangeRecorderPrivate::saveNotifications() { if (!settings) { return; } QFile file(notificationsFileName()); QFileInfo info(file); if (!QFile::exists(info.absolutePath())) { QDir dir; dir.mkpath(info.absolutePath()); } if (!file.open(QIODevice::WriteOnly)) { qCWarning(AKONADICORE_LOG) << "Could not save notifications to file" << file.fileName(); return; } saveTo(&file); m_needFullSave = false; m_startOffset = 0; } void ChangeRecorderPrivate::saveTo(QIODevice *device) { // Version 0 of this file format was writing a quint64 count, followed by the notifications. // Version 1 bundles a version number into that quint64, to be able to detect a version number at load time. const quint64 countAndVersion = static_cast(pendingNotifications.count()) | s_currentVersion; QDataStream stream(device); stream.setVersion(QDataStream::Qt_4_6); stream << countAndVersion; stream << quint64(0); // no start offset //qCDebug(AKONADICORE_LOG) << "Saving" << pendingNotifications.count() << "notifications (full save)"; for (int i = 0; i < pendingNotifications.count(); ++i) { const Protocol::ChangeNotificationPtr msg = pendingNotifications.at(i); addToStream(stream, msg); } } void ChangeRecorderPrivate::notificationsEnqueued(int count) { // Just to ensure the contract is kept, and these two methods are always properly called. if (enableChangeRecording) { m_lastKnownNotificationsCount += count; if (m_lastKnownNotificationsCount != pendingNotifications.count()) { qCWarning(AKONADICORE_LOG) << this << "The number of pending notifications changed without telling us! Expected" << m_lastKnownNotificationsCount << "but got" << pendingNotifications.count() << "Caller just added" << count; Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount); } saveNotifications(); } } void ChangeRecorderPrivate::dequeueNotification() { if (pendingNotifications.isEmpty()) { return; } pendingNotifications.dequeue(); if (enableChangeRecording) { Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount - 1); --m_lastKnownNotificationsCount; if (m_needFullSave || pendingNotifications.isEmpty()) { saveNotifications(); } else { ++m_startOffset; writeStartOffset(); } } } void ChangeRecorderPrivate::notificationsErased() { if (enableChangeRecording) { m_lastKnownNotificationsCount = pendingNotifications.count(); m_needFullSave = true; saveNotifications(); } } void ChangeRecorderPrivate::notificationsLoaded() { m_lastKnownNotificationsCount = pendingNotifications.count(); m_startOffset = 0; } bool ChangeRecorderPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) { const bool someoneWasListening = MonitorPrivate::emitNotification(msg); if (!someoneWasListening && enableChangeRecording) { //If no signal was emitted (e.g. because no one was connected to it), no one is going to call changeProcessed, so we help ourselves. dequeueNotification(); QMetaObject::invokeMethod(q_ptr, "replayNext", Qt::QueuedConnection); } return someoneWasListening; } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadItemNotification(QSettings *settings) const { auto msg = Protocol::ItemChangeNotificationPtr::create(); msg->setSessionId(settings->value(QStringLiteral("sessionId")).toByteArray()); msg->setOperation(mapItemOperation(static_cast(settings->value(QStringLiteral("op")).toInt()))); auto item = Protocol::FetchItemsResponsePtr::create(); item->setId(settings->value(QStringLiteral("uid")).toLongLong()); item->setRemoteId(settings->value(QStringLiteral("rid")).toString()); item->setMimeType(settings->value(QStringLiteral("mimeType")).toString()); msg->setItems({ item }); msg->addMetadata("FETCH_ITEM"); msg->setResource(settings->value(QStringLiteral("resource")).toByteArray()); msg->setParentCollection(settings->value(QStringLiteral("parentCol")).toLongLong()); msg->setParentDestCollection(settings->value(QStringLiteral("parentDestCol")).toLongLong()); const QStringList list = settings->value(QStringLiteral("itemParts")).toStringList(); QSet itemParts; for (const QString &entry : list) { itemParts.insert(entry.toLatin1()); } msg->setItemParts(itemParts); return msg; } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadCollectionNotification(QSettings *settings) const { auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setSessionId(settings->value(QStringLiteral("sessionId")).toByteArray()); msg->setOperation(mapCollectionOperation(static_cast(settings->value(QStringLiteral("op")).toInt()))); auto collection = Protocol::FetchCollectionsResponsePtr::create(); collection->setId(settings->value(QStringLiteral("uid")).toLongLong()); collection->setRemoteId(settings->value(QStringLiteral("rid")).toString()); msg->setCollection(collection); msg->addMetadata("FETCH_COLLECTION"); msg->setResource(settings->value(QStringLiteral("resource")).toByteArray()); msg->setParentCollection(settings->value(QStringLiteral("parentCol")).toLongLong()); msg->setParentDestCollection(settings->value(QStringLiteral("parentDestCol")).toLongLong()); const QStringList list = settings->value(QStringLiteral("itemParts")).toStringList(); QSet changedParts; for (const QString &entry : list) { changedParts.insert(entry.toLatin1()); } msg->setChangedParts(changedParts); return msg; } QSet ChangeRecorderPrivate::extractRelations(QSet &flags) const { QSet relations; auto iter = flags.begin(); while (iter != flags.end()) { if (iter->startsWith("RELATION")) { const QByteArrayList parts = iter->split(' '); Q_ASSERT(parts.size() == 4); Protocol::ItemChangeNotification::Relation relation; relation.type = QString::fromLatin1(parts[1]); relation.leftId = parts[2].toLongLong(); relation.rightId = parts[3].toLongLong(); relations.insert(relation); iter = flags.erase(iter); } else { ++iter; } } return relations; } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadItemNotification(QDataStream &stream, quint64 version) const { QByteArray resource, destinationResource; int operation, entityCnt; qint64 uid, parentCollection, parentDestCollection; QString remoteId, mimeType, remoteRevision; QSet itemParts, addedFlags, removedFlags; QSet addedTags, removedTags; QVector items; auto msg = Protocol::ItemChangeNotificationPtr::create(); if (version == 1) { stream >> operation; stream >> uid; stream >> remoteId; stream >> resource; stream >> parentCollection; stream >> parentDestCollection; stream >> mimeType; stream >> itemParts; auto item = Protocol::FetchItemsResponsePtr::create(); item->setId(uid); item->setRemoteId(remoteId); item->setMimeType(mimeType); items << item; msg->addMetadata("FETCH_ITEM"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { QByteArray ba; qint64 i64; int i; QDateTime dt; QString str; QVector bav; QVector i64v; QMap babaMap; int cnt; for (int j = 0; j < entityCnt; ++j) { auto item = Protocol::FetchItemsResponsePtr::create(); stream >> i64; item->setId(i64); stream >> i; item->setRevision(i); stream >> i64; item->setParentId(i64); stream >> str; item->setRemoteId(str); stream >> str; item->setRemoteRevision(str); stream >> str; item->setGid(str); stream >> i64; item->setSize(i64); stream >> str; item->setMimeType(str); stream >> dt; item->setMTime(dt); stream >> bav; item->setFlags(bav); stream >> cnt; QVector tags; for (int k = 0; k < cnt; ++k) { Protocol::FetchTagsResponse tag; stream >> i64; tag.setId(i64); stream >> i64; tag.setParentId(i64); stream >> ba; tag.setGid(ba); stream >> ba; tag.setType(ba); stream >> ba; tag.setRemoteId(ba); stream >> babaMap; tag.setAttributes(babaMap); tags << tag; } item->setTags(tags); stream >> i64v; item->setVirtualReferences(i64v); stream >> cnt; QVector relations; for (int k = 0; k < cnt; ++k) { Protocol::FetchRelationsResponse relation; stream >> i64; relation.setLeft(i64); stream >> ba; relation.setLeftMimeType(ba); stream >> i64; relation.setRight(i64); stream >> ba; relation.setRightMimeType(ba); stream >> ba; relation.setType(ba); stream >> ba; relation.setRemoteId(ba); relations << relation; } item->setRelations(relations); stream >> cnt; QVector ancestors; for (int k = 0; k < cnt; ++k) { Protocol::Ancestor ancestor; stream >> i64; ancestor.setId(i64); stream >> str; ancestor.setRemoteId(str); stream >> str; ancestor.setName(str); stream >> babaMap; ancestor.setAttributes(babaMap); ancestors << ancestor; } item->setAncestors(ancestors); stream >> cnt; QVector parts; for (int k = 0; k < cnt; ++k) { Protocol::StreamPayloadResponse part; stream >> ba; part.setPayloadName(ba); Protocol::PartMetaData metaData; stream >> ba; metaData.setName(ba); stream >> i64; metaData.setSize(i64); stream >> i; metaData.setVersion(i); stream >> i; metaData.setStorageType(static_cast(i)); part.setMetaData(metaData); stream >> ba; part.setData(ba); parts << part; } item->setParts(parts); stream >> bav; item->setCachedParts(bav); items << item; } } else { for (int j = 0; j < entityCnt; ++j) { stream >> uid; stream >> remoteId; stream >> remoteRevision; stream >> mimeType; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } auto item = Protocol::FetchItemsResponsePtr::create(); item->setId(uid); item->setRemoteId(remoteId); item->setRemoteRevision(remoteRevision); item->setMimeType(mimeType); items << item; } msg->addMetadata("FETCH_ITEM"); } stream >> resource; stream >> destinationResource; stream >> parentCollection; stream >> parentDestCollection; stream >> itemParts; stream >> addedFlags; stream >> removedFlags; if (version >= 3) { stream >> addedTags; stream >> removedTags; } } else { qCWarning(AKONADICORE_LOG) << "Error version is not correct here" << version; return msg; } if (version >= 5) { msg->setOperation(static_cast(operation)); } else { msg->setOperation(mapItemOperation(static_cast(operation))); } msg->setItems(items); msg->setResource(resource); msg->setDestinationResource(destinationResource); msg->setParentCollection(parentCollection); msg->setParentDestCollection(parentDestCollection); msg->setItemParts(itemParts); msg->setAddedRelations(extractRelations(addedFlags)); msg->setAddedFlags(addedFlags); msg->setRemovedRelations(extractRelations(removedFlags)); msg->setRemovedFlags(removedFlags); msg->setAddedTags(addedTags); msg->setRemovedTags(removedTags); return msg; } QSet ChangeRecorderPrivate::encodeRelations(const QSet &relations) const { QSet rv; for (const auto &rel : relations) { rv.insert("RELATION " + rel.type.toLatin1() + ' ' + QByteArray::number(rel.leftId) + ' ' + QByteArray::number(rel.rightId)); } return rv; } void ChangeRecorderPrivate::saveItemNotification(QDataStream &stream, const Protocol::ItemChangeNotification &msg) { // Version 7 stream << int(msg.operation()); const auto items = msg.items(); stream << items.count(); for (const auto &item : items) { stream << item->id() << item->revision() << item->parentId() << item->remoteId() << item->remoteRevision() << item->gid() << item->size() << item->mimeType() << item->mTime() << item->flags(); const auto tags = item->tags(); stream << tags.count(); for (const auto &tag : tags) { stream << tag.id() << tag.parentId() << tag.gid() << tag.type() << tag.remoteId() << tag.attributes(); } stream << item->virtualReferences(); const auto relations = item->relations(); stream << relations.count(); for (const auto &relation : relations) { stream << relation.left() << relation.leftMimeType() << relation.right() << relation.rightMimeType() << relation.type() << relation.remoteId(); } const auto ancestors = item->ancestors(); stream << ancestors.count(); for (const auto &ancestor : ancestors) { stream << ancestor.id() << ancestor.remoteId() << ancestor.name() << ancestor.attributes(); } const auto parts = item->parts(); stream << parts.count(); for (const auto &part : parts) { const auto metaData = part.metaData(); stream << part.payloadName() << metaData.name() << metaData.size() << metaData.version() << static_cast(metaData.storageType()) << part.data(); } stream << item->cachedParts(); } stream << msg.resource(); stream << msg.destinationResource(); stream << quint64(msg.parentCollection()); stream << quint64(msg.parentDestCollection()); stream << msg.itemParts(); stream << msg.addedFlags() + encodeRelations(msg.addedRelations()); stream << msg.removedFlags() + encodeRelations(msg.removedRelations()); stream << msg.addedTags(); stream << msg.removedTags(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadCollectionNotification(QDataStream &stream, quint64 version) const { QByteArray resource, destinationResource; int operation, entityCnt; quint64 uid, parentCollection, parentDestCollection; QString remoteId, remoteRevision, dummyString; QSet changedParts, dummyBa; QSet dummyIv; auto msg = Protocol::CollectionChangeNotificationPtr::create(); if (version == 1) { stream >> operation; stream >> uid; stream >> remoteId; stream >> resource; stream >> parentCollection; stream >> parentDestCollection; stream >> dummyString; stream >> changedParts; auto collection = Protocol::FetchCollectionsResponsePtr::create(); collection->setId(uid); collection->setRemoteId(remoteId); msg->setCollection(collection); msg->addMetadata("FETCH_COLLECTION"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { QString str; QStringList stringList; qint64 i64; QVector vb; QMap attrs; bool b; int i; Tristate tristate; auto collection = Protocol::FetchCollectionsResponsePtr::create(); stream >> uid; collection->setId(uid); stream >> uid; collection->setParentId(uid); stream >> str; collection->setName(str); stream >> stringList; collection->setMimeTypes(stringList); stream >> str; collection->setRemoteId(str); stream >> str; collection->setRemoteRevision(str); stream >> str; collection->setResource(str); Protocol::FetchCollectionStatsResponse stats; stream >> i64; stats.setCount(i64); stream >> i64; stats.setUnseen(i64); stream >> i64; stats.setSize(i64); collection->setStatistics(stats); stream >> str; collection->setSearchQuery(str); stream >> vb; collection->setSearchCollections(vb); stream >> entityCnt; QVector ancestors; for (int i = 0; i < entityCnt; ++i) { Protocol::Ancestor ancestor; stream >> i64; ancestor.setId(i64); stream >> str; ancestor.setRemoteId(str); stream >> str; ancestor.setName(str); stream >> attrs; ancestor.setAttributes(attrs); ancestors.push_back(ancestor); if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Erorr reading saved notifications! Aborting"; return msg; } } collection->setAncestors(ancestors); Protocol::CachePolicy cachePolicy; stream >> b; cachePolicy.setInherit(b); stream >> i; cachePolicy.setCheckInterval(i); stream >> i; cachePolicy.setCacheTimeout(i); stream >> b; cachePolicy.setSyncOnDemand(b); stream >> stringList; cachePolicy.setLocalParts(stringList); collection->setCachePolicy(cachePolicy); stream >> attrs; collection->setAttributes(attrs); stream >> b; collection->setEnabled(b); stream >> reinterpret_cast(tristate); collection->setDisplayPref(tristate); stream >> reinterpret_cast(tristate); collection->setSyncPref(tristate); stream >> reinterpret_cast(tristate); collection->setIndexPref(tristate); stream >> b; collection->setReferenced(b); stream >> b; collection->setIsVirtual(b); msg->setCollection(collection); } else { for (int j = 0; j < entityCnt; ++j) { stream >> uid; stream >> remoteId; stream >> remoteRevision; stream >> dummyString; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } auto collection = Protocol::FetchCollectionsResponsePtr::create(); collection->setId(uid); collection->setRemoteId(remoteId); collection->setRemoteRevision(remoteRevision); msg->setCollection(collection); msg->addMetadata("FETCH_COLLECTION"); } } stream >> resource; stream >> destinationResource; stream >> parentCollection; stream >> parentDestCollection; stream >> changedParts; stream >> dummyBa; stream >> dummyBa; if (version >= 3) { stream >> dummyIv; stream >> dummyIv; } } else { qCWarning(AKONADICORE_LOG) << "Error version is not correct here" << version; return msg; } if (version >= 5) { msg->setOperation(static_cast(operation)); } else { msg->setOperation(mapCollectionOperation(static_cast(operation))); } msg->setResource(resource); msg->setDestinationResource(destinationResource); msg->setParentCollection(parentCollection); msg->setParentDestCollection(parentDestCollection); msg->setChangedParts(changedParts); return msg; } void Akonadi::ChangeRecorderPrivate::saveCollectionNotification(QDataStream &stream, const Protocol::CollectionChangeNotification &msg) { // Version 7 const auto col = msg.collection(); stream << int(msg.operation()); stream << int(1); stream << col->id(); stream << col->parentId(); stream << col->name(); stream << col->mimeTypes(); stream << col->remoteId(); stream << col->remoteRevision(); stream << col->resource(); const auto stats = col->statistics(); stream << stats.count(); stream << stats.unseen(); stream << stats.size(); stream << col->searchQuery(); stream << col->searchCollections(); const auto ancestors = col->ancestors(); stream << ancestors.count(); for (const auto &ancestor : ancestors) { stream << ancestor.id() << ancestor.remoteId() << ancestor.name() << ancestor.attributes(); } const auto cachePolicy = col->cachePolicy(); stream << cachePolicy.inherit(); stream << cachePolicy.checkInterval(); stream << cachePolicy.cacheTimeout(); stream << cachePolicy.syncOnDemand(); stream << cachePolicy.localParts(); stream << col->attributes(); stream << col->enabled(); stream << static_cast(col->displayPref()); stream << static_cast(col->syncPref()); stream << static_cast(col->indexPref()); stream << col->referenced(); stream << col->isVirtual(); stream << msg.resource(); stream << msg.destinationResource(); stream << quint64(msg.parentCollection()); stream << quint64(msg.parentDestCollection()); stream << msg.changedParts(); stream << QSet(); stream << QSet(); stream << QSet(); stream << QSet(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadTagNotification(QDataStream &stream, quint64 version) const { QByteArray resource, dummyBa; int operation, entityCnt; quint64 uid, dummyI; QString remoteId, dummyString; QSet dummyBaV; QSet dummyIv; auto msg = Protocol::TagChangeNotificationPtr::create(); if (version == 1) { stream >> operation; stream >> uid; stream >> remoteId; stream >> dummyBa; stream >> dummyI; stream >> dummyI; stream >> dummyString; stream >> dummyBaV; auto tag = Protocol::FetchTagsResponsePtr::create(); tag->setId(uid); tag->setRemoteId(remoteId.toLatin1()); msg->setTag(tag); msg->addMetadata("FETCH_TAG"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { QByteArray ba; QMap attrs; auto tag = Protocol::FetchTagsResponsePtr::create(); stream >> uid; tag->setId(uid); stream >> ba; tag->setParentId(uid); stream >> attrs; tag->setGid(ba); stream >> ba; tag->setType(ba); stream >> uid; tag->setRemoteId(ba); stream >> ba; tag->setAttributes(attrs); msg->setTag(tag); stream >> resource; } else { for (int j = 0; j < entityCnt; ++j) { stream >> uid; stream >> remoteId; stream >> dummyString; stream >> dummyString; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } auto tag = Protocol::FetchTagsResponsePtr::create(); tag->setId(uid); tag->setRemoteId(remoteId.toLatin1()); msg->setTag(tag); msg->addMetadata("FETCH_TAG"); } stream >> resource; stream >> dummyBa; stream >> dummyI; stream >> dummyI; stream >> dummyBaV; stream >> dummyBaV; stream >> dummyBaV; if (version >= 3) { stream >> dummyIv; stream >> dummyIv; } } - } - if (version >= 5) { - msg->setOperation(static_cast(operation)); - } else { - msg->setOperation(mapTagOperation(static_cast(operation))); + if (version >= 5) { + msg->setOperation(static_cast(operation)); + } else { + msg->setOperation(mapTagOperation(static_cast(operation))); + } } msg->setResource(resource); return msg; } void Akonadi::ChangeRecorderPrivate::saveTagNotification(QDataStream &stream, const Protocol::TagChangeNotification &msg) { const auto tag = msg.tag(); stream << int(msg.operation()); stream << int(1); stream << tag->id(); stream << tag->parentId(); stream << tag->gid(); stream << tag->type(); stream << tag->remoteId(); stream << tag->attributes(); stream << msg.resource(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadRelationNotification(QDataStream &stream, quint64 version) const { QByteArray dummyBa; int operation, entityCnt; quint64 dummyI; QString dummyString; QSet itemParts, dummyBaV; QSet dummyIv; auto msg = Protocol::RelationChangeNotificationPtr::create(); if (version == 1) { qCWarning(AKONADICORE_LOG) << "Invalid version of relation notification"; return msg; } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { auto relation = Protocol::FetchRelationsResponsePtr::create(); qint64 i64; QByteArray ba; stream >> i64; relation->setLeft(i64); stream >> ba; relation->setLeftMimeType(ba); stream >> i64; relation->setRight(i64); stream >>ba; relation->setRightMimeType(ba); stream >> ba; relation->setRemoteId(ba); stream >> ba; relation->setType(ba); msg->setRelation(relation); } else { for (int j = 0; j < entityCnt; ++j) { stream >> dummyI; stream >> dummyString; stream >> dummyString; stream >> dummyString; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } } stream >> dummyBa; if (version == 5) { // there was a bug in version 5 serializer that serialized this // field as qint64 (8 bytes) instead of empty QByteArray (which is // 4 bytes) stream >> dummyI; } else { stream >> dummyBa; } stream >> dummyI; stream >> dummyI; stream >> itemParts; stream >> dummyBaV; stream >> dummyBaV; if (version >= 3) { stream >> dummyIv; stream >> dummyIv; } auto relation = Protocol::FetchRelationsResponsePtr::create(); for (const QByteArray &part : qAsConst(itemParts)) { const QByteArrayList p = part.split(' '); if (p.size() < 2) { continue; } if (p[0] == "LEFT") { relation->setLeft(p[1].toLongLong()); } else if (p[0] == "RIGHT") { relation->setRight(p[1].toLongLong()); } else if (p[0] == "RID") { relation->setRemoteId(p[1]); } else if (p[0] == "TYPE") { relation->setType(p[1]); } } msg->setRelation(relation); } - } - - if (version >= 5) { - msg->setOperation(static_cast(operation)); - } else { - msg->setOperation(mapRelationOperation(static_cast(operation))); + if (version >= 5) { + msg->setOperation(static_cast(operation)); + } else { + msg->setOperation(mapRelationOperation(static_cast(operation))); + } } return msg; } void Akonadi::ChangeRecorderPrivate::saveRelationNotification(QDataStream &stream, const Protocol::RelationChangeNotification &msg) { const auto rel = msg.relation(); stream << int(msg.operation()); stream << int(0); stream << rel->left(); stream << rel->leftMimeType(); stream << rel->right(); stream << rel->rightMimeType(); stream << rel->remoteId(); stream << rel->type(); } Protocol::ItemChangeNotification::Operation ChangeRecorderPrivate::mapItemOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::ItemChangeNotification::Add; case Modify: return Protocol::ItemChangeNotification::Modify; case Move: return Protocol::ItemChangeNotification::Move; case Remove: return Protocol::ItemChangeNotification::Remove; case Link: return Protocol::ItemChangeNotification::Link; case Unlink: return Protocol::ItemChangeNotification::Unlink; case ModifyFlags: return Protocol::ItemChangeNotification::ModifyFlags; case ModifyTags: return Protocol::ItemChangeNotification::ModifyTags; case ModifyRelations: return Protocol::ItemChangeNotification::ModifyRelations; default: qWarning() << "Unexpected operation type in item notification"; return Protocol::ItemChangeNotification::InvalidOp; } } Protocol::CollectionChangeNotification::Operation ChangeRecorderPrivate::mapCollectionOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::CollectionChangeNotification::Add; case Modify: return Protocol::CollectionChangeNotification::Modify; case Move: return Protocol::CollectionChangeNotification::Move; case Remove: return Protocol::CollectionChangeNotification::Remove; case Subscribe: return Protocol::CollectionChangeNotification::Subscribe; case Unsubscribe: return Protocol::CollectionChangeNotification::Unsubscribe; default: qCWarning(AKONADICORE_LOG) << "Unexpected operation type in collection notification"; return Protocol::CollectionChangeNotification::InvalidOp; } } Protocol::TagChangeNotification::Operation ChangeRecorderPrivate::mapTagOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::TagChangeNotification::Add; case Modify: return Protocol::TagChangeNotification::Modify; case Remove: return Protocol::TagChangeNotification::Remove; default: qCWarning(AKONADICORE_LOG) << "Unexpected operation type in tag notification"; return Protocol::TagChangeNotification::InvalidOp; } } Protocol::RelationChangeNotification::Operation ChangeRecorderPrivate::mapRelationOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::RelationChangeNotification::Add; case Remove: return Protocol::RelationChangeNotification::Remove; default: qCWarning(AKONADICORE_LOG) << "Unexpected operation type in relation notification"; return Protocol::RelationChangeNotification::InvalidOp; } } ChangeRecorderPrivate::LegacyType ChangeRecorderPrivate::mapToLegacyType(Protocol::Command::Type type) const { switch (type) { case Protocol::Command::ItemChangeNotification: return Item; case Protocol::Command::CollectionChangeNotification: return Collection; case Protocol::Command::TagChangeNotification: return Tag; case Protocol::Command::RelationChangeNotification: return Relation; default: qCWarning(AKONADICORE_LOG) << "Unexpected notification type"; return InvalidType; } } diff --git a/src/core/itemserializer.cpp b/src/core/itemserializer.cpp index c30f83939..8ca2c88b6 100644 --- a/src/core/itemserializer.cpp +++ b/src/core/itemserializer.cpp @@ -1,223 +1,223 @@ /* Copyright (c) 2007 Till Adam Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemserializer_p.h" #include "item.h" #include "itemserializerplugin.h" #include "typepluginloader_p.h" #include "protocolhelper_p.h" #include "private/externalpartstorage_p.h" #include "akonadicore_debug.h" // Qt #include #include #include #include #include Q_DECLARE_METATYPE(std::string) namespace Akonadi { DefaultItemSerializerPlugin::DefaultItemSerializerPlugin() { Item::addToLegacyMapping(QStringLiteral("application/octet-stream")); } bool DefaultItemSerializerPlugin::deserialize(Item &item, const QByteArray &label, QIODevice &data, int) { if (label != Item::FullPayload) { return false; } item.setPayload(data.readAll()); return true; } void DefaultItemSerializerPlugin::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version) { Q_UNUSED(version) Q_ASSERT(label == Item::FullPayload); Q_UNUSED(label); data.write(item.payload()); } bool StdStringItemSerializerPlugin::deserialize(Item &item, const QByteArray &label, QIODevice &data, int) { if (label != Item::FullPayload) { return false; } std::string str; { const QByteArray ba = data.readAll(); str.assign(ba.data(), ba.size()); } item.setPayload(str); return true; } void StdStringItemSerializerPlugin::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version) { Q_UNUSED(version) Q_ASSERT(label == Item::FullPayload); Q_UNUSED(label); const std::string str = item.payload(); data.write(QByteArray::fromRawData(str.data(), str.size())); } /*static*/ void ItemSerializer::deserialize(Item &item, const QByteArray &label, const QByteArray &data, int version, PayloadStorage storage) { if (storage == Internal) { QBuffer buffer; buffer.setData(data); buffer.open(QIODevice::ReadOnly); buffer.seek(0); deserialize(item, label, buffer, version); buffer.close(); } else { QFile file; if (storage == External) { file.setFileName(ExternalPartStorage::resolveAbsolutePath(data)); } else { file.setFileName(QString::fromUtf8(data)); } if (file.open(QIODevice::ReadOnly)) { deserialize(item, label, file, version); file.close(); } else { qCWarning(AKONADICORE_LOG) << "Failed to open" << ((storage == External) ? "external" : "foreign") << "payload:" << file.fileName() << file.errorString(); } } } /*static*/ void ItemSerializer::deserialize(Item &item, const QByteArray &label, QIODevice &data, int version) { if (!TypePluginLoader::defaultPluginForMimeType(item.mimeType())->deserialize(item, label, data, version)) { qCWarning(AKONADICORE_LOG) << "Unable to deserialize payload part:" << label << "in item" << item.id() << "collection" << item.parentCollection().id(); data.seek(0); qCWarning(AKONADICORE_LOG) << "Payload data was: " << data.readAll(); } } /*static*/ void ItemSerializer::serialize(const Item &item, const QByteArray &label, QByteArray &data, int &version) { QBuffer buffer; buffer.setBuffer(&data); buffer.open(QIODevice::WriteOnly); buffer.seek(0); serialize(item, label, buffer, version); buffer.close(); } /*static*/ void ItemSerializer::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version) { if (!item.hasPayload()) { return; } ItemSerializerPlugin *plugin = TypePluginLoader::pluginForMimeTypeAndClass(item.mimeType(), item.availablePayloadMetaTypeIds()); plugin->serialize(item, label, data, version); } void ItemSerializer::apply(Item &item, const Item &other) { if (!other.hasPayload()) { return; } ItemSerializerPlugin *plugin = TypePluginLoader::pluginForMimeTypeAndClass(item.mimeType(), item.availablePayloadMetaTypeIds()); plugin->apply(item, other); } QSet ItemSerializer::parts(const Item &item) { if (!item.hasPayload()) { return QSet(); } return TypePluginLoader::pluginForMimeTypeAndClass(item.mimeType(), item.availablePayloadMetaTypeIds())->parts(item); } QSet ItemSerializer::availableParts(const Item &item) { if (!item.hasPayload()) { return QSet(); } ItemSerializerPlugin *plugin = TypePluginLoader::pluginForMimeTypeAndClass(item.mimeType(), item.availablePayloadMetaTypeIds()); return plugin->availableParts(item); } QSet ItemSerializer::allowedForeignParts(const Item &item) { if (!item.hasPayload()) { return QSet(); } ItemSerializerPlugin *plugin = TypePluginLoader::pluginForMimeTypeAndClass(item.mimeType(), item.availablePayloadMetaTypeIds()); return plugin->allowedForeignParts(item); } Item ItemSerializer::convert(const Item &item, int mtid) { // qCDebug(AKONADICORE_LOG) << "asked to convert a" << item.mimeType() << "item to format" << ( mtid ? QMetaType::typeName( mtid ) : "" ); if (!item.hasPayload()) { qCDebug(AKONADICORE_LOG) << " -> but item has no payload!"; return Item(); } if (ItemSerializerPlugin *const plugin = TypePluginLoader::pluginForMimeTypeAndClass(item.mimeType(), QVector(1, mtid), TypePluginLoader::NoDefault)) { qCDebug(AKONADICORE_LOG) << " -> found a plugin that feels responsible, trying serialising the payload"; QBuffer buffer; buffer.open(QIODevice::ReadWrite); - int version; + int version = 0; serialize(item, Item::FullPayload, buffer, version); buffer.seek(0); qCDebug(AKONADICORE_LOG) << " -> serialized payload into" << buffer.size() << "bytes" << endl << " -> going to deserialize"; Item newItem; if (plugin->deserialize(newItem, Item::FullPayload, buffer, version)) { qCDebug(AKONADICORE_LOG) << " -> conversion successful"; return newItem; } else { qCDebug(AKONADICORE_LOG) << " -> conversion FAILED"; } } else { // qCDebug(AKONADICORE_LOG) << " -> found NO plugin that feels responsible"; } return Item(); } void ItemSerializer::overridePluginLookup(QObject *p) { TypePluginLoader::overridePluginLookup(p); } } diff --git a/src/core/itemsync.cpp b/src/core/itemsync.cpp index ce7acb263..d01db1a80 100644 --- a/src/core/itemsync.cpp +++ b/src/core/itemsync.cpp @@ -1,557 +1,555 @@ /* Copyright (c) 2007 Tobias Koenig Copyright (c) 2007 Volker Krause Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemsync.h" #include "job_p.h" #include "collection.h" #include "item.h" #include "item_p.h" #include "itemcreatejob.h" #include "itemdeletejob.h" #include "itemfetchjob.h" #include "itemmodifyjob.h" #include "transactionsequence.h" #include "itemfetchscope.h" #include "akonadicore_debug.h" using namespace Akonadi; /** * @internal */ class Akonadi::ItemSyncPrivate : public JobPrivate { public: ItemSyncPrivate(ItemSync *parent) : JobPrivate(parent) , mTransactionMode(ItemSync::SingleTransaction) , mCurrentTransaction(nullptr) , mTransactionJobs(0) , mPendingJobs(0) , mProgress(0) , mTotalItems(-1) , mTotalItemsProcessed(0) , mStreaming(false) , mIncremental(false) , mDeliveryDone(false) , mFinished(false) , mFullListingDone(false) , mProcessingBatch(false) , mDisableAutomaticDeliveryDone(false) , mBatchSize(10) , mMergeMode(Akonadi::ItemSync::RIDMerge) { // we want to fetch all data by default mFetchScope.fetchFullPayload(); mFetchScope.fetchAllAttributes(); } void createOrMerge(const Item &item); void checkDone(); void slotItemsReceived(const Item::List &items); void slotLocalListDone(KJob *job); void slotLocalDeleteDone(KJob *job); void slotLocalChangeDone(KJob *job); void execute(); void processItems(); void processBatch(); void deleteItems(const Item::List &items); void slotTransactionResult(KJob *job); void requestTransaction(); Job *subjobParent() const; void fetchLocalItemsToDelete(); QString jobDebuggingString() const override; bool allProcessed() const; Q_DECLARE_PUBLIC(ItemSync) Collection mSyncCollection; QSet mListedItems; ItemSync::TransactionMode mTransactionMode; TransactionSequence *mCurrentTransaction; int mTransactionJobs; // fetch scope for initial item listing ItemFetchScope mFetchScope; Akonadi::Item::List mRemoteItemQueue; Akonadi::Item::List mRemovedRemoteItemQueue; Akonadi::Item::List mCurrentBatchRemoteItems; Akonadi::Item::List mCurrentBatchRemovedRemoteItems; Akonadi::Item::List mItemsToDelete; // create counter int mPendingJobs; int mProgress; int mTotalItems; int mTotalItemsProcessed; bool mStreaming; bool mIncremental; bool mDeliveryDone; bool mFinished; bool mFullListingDone; bool mProcessingBatch; bool mDisableAutomaticDeliveryDone; int mBatchSize; Akonadi::ItemSync::MergeMode mMergeMode; }; void ItemSyncPrivate::createOrMerge(const Item &item) { Q_Q(ItemSync); // don't try to do anything in error state if (q->error()) { return; } mPendingJobs++; ItemCreateJob *create = new ItemCreateJob(item, mSyncCollection, subjobParent()); ItemCreateJob::MergeOptions merge = ItemCreateJob::Silent; if (mMergeMode == ItemSync::GIDMerge && !item.gid().isEmpty()) { merge |= ItemCreateJob::GID; } else { merge |= ItemCreateJob::RID; } create->setMerge(merge); q->connect(create, &ItemCreateJob::result, q, [this](KJob *job) {slotLocalChangeDone(job);}); } bool ItemSyncPrivate::allProcessed() const { return mDeliveryDone && mCurrentBatchRemoteItems.isEmpty() && mRemoteItemQueue.isEmpty() && mRemovedRemoteItemQueue.isEmpty() && mCurrentBatchRemovedRemoteItems.isEmpty(); } void ItemSyncPrivate::checkDone() { Q_Q(ItemSync); q->setProcessedAmount(KJob::Bytes, mProgress); if (mPendingJobs > 0) { return; } if (mTransactionJobs > 0) { //Commit the current transaction if we're in batch processing mode or done //and wait until the transaction is committed to process the next batch if (mTransactionMode == ItemSync::MultipleTransactions || (mDeliveryDone && mRemoteItemQueue.isEmpty())) { if (mCurrentTransaction) { q->emit transactionCommitted(); mCurrentTransaction->commit(); mCurrentTransaction = nullptr; } return; } } mProcessingBatch = false; if (!mRemoteItemQueue.isEmpty()) { execute(); //We don't have enough items, request more if (!mProcessingBatch) { q->emit readyForNextBatch(mBatchSize - mRemoteItemQueue.size()); } return; } q->emit readyForNextBatch(mBatchSize); if (allProcessed() && !mFinished) { // prevent double result emission, can happen since checkDone() is called from all over the place qCDebug(AKONADICORE_LOG) << "finished"; mFinished = true; q->emitResult(); } } ItemSync::ItemSync(const Collection &collection, QObject *parent) : Job(new ItemSyncPrivate(this), parent) { Q_D(ItemSync); d->mSyncCollection = collection; } ItemSync::~ItemSync() { } void ItemSync::setFullSyncItems(const Item::List &items) { /* * We received a list of items from the server: * * fetch all local id's + rid's only * * check each full sync item whether it's locally available * * if it is modify the item * * if it's not create it * * delete all superfluous items */ Q_D(ItemSync); Q_ASSERT(!d->mIncremental); if (!d->mStreaming) { d->mDeliveryDone = true; } d->mRemoteItemQueue += items; d->mTotalItemsProcessed += items.count(); qCDebug(AKONADICORE_LOG) << "Received: " << items.count() << "In total: " << d->mTotalItemsProcessed << " Wanted: " << d->mTotalItems; if (!d->mDisableAutomaticDeliveryDone && (d->mTotalItemsProcessed == d->mTotalItems)) { d->mDeliveryDone = true; } d->execute(); } void ItemSync::setTotalItems(int amount) { Q_D(ItemSync); Q_ASSERT(!d->mIncremental); Q_ASSERT(amount >= 0); setStreamingEnabled(true); qCDebug(AKONADICORE_LOG) << amount; d->mTotalItems = amount; setTotalAmount(KJob::Bytes, amount); if (!d->mDisableAutomaticDeliveryDone && (d->mTotalItems == 0)) { d->mDeliveryDone = true; d->execute(); } } void ItemSync::setDisableAutomaticDeliveryDone(bool disable) { Q_D(ItemSync); d->mDisableAutomaticDeliveryDone = disable; } void ItemSync::setIncrementalSyncItems(const Item::List &changedItems, const Item::List &removedItems) { /* * We received an incremental listing of items: * * for each changed item: * ** If locally available => modify * ** else => create * * removed items can be removed right away */ Q_D(ItemSync); d->mIncremental = true; if (!d->mStreaming) { d->mDeliveryDone = true; } d->mRemoteItemQueue += changedItems; d->mRemovedRemoteItemQueue += removedItems; d->mTotalItemsProcessed += changedItems.count() + removedItems.count(); qCDebug(AKONADICORE_LOG) << "Received: " << changedItems.count() << "Removed: " << removedItems.count() << "In total: " << d->mTotalItemsProcessed << " Wanted: " << d->mTotalItems; if (!d->mDisableAutomaticDeliveryDone && (d->mTotalItemsProcessed == d->mTotalItems)) { d->mDeliveryDone = true; } d->execute(); } void ItemSync::setFetchScope(ItemFetchScope &fetchScope) { Q_D(ItemSync); d->mFetchScope = fetchScope; } ItemFetchScope &ItemSync::fetchScope() { Q_D(ItemSync); return d->mFetchScope; } void ItemSync::doStart() { } void ItemSyncPrivate::fetchLocalItemsToDelete() { Q_Q(ItemSync); if (mIncremental) { qFatal("This must not be called while in incremental mode"); return; } ItemFetchJob *job = new ItemFetchJob(mSyncCollection, subjobParent()); job->fetchScope().setFetchRemoteIdentification(true); job->fetchScope().setFetchModificationTime(false); job->setDeliveryOption(ItemFetchJob::EmitItemsIndividually); // we only can fetch parts already in the cache, otherwise this will deadlock job->fetchScope().setCacheOnly(true); QObject::connect(job, &ItemFetchJob::itemsReceived, q, [this](const Akonadi::Item::List &lst) { slotItemsReceived(lst); }); QObject::connect(job, &ItemFetchJob::result, q, [this](KJob *job) { slotLocalListDone(job); }); mPendingJobs++; } void ItemSyncPrivate::slotItemsReceived(const Item::List &items) { for (const Akonadi::Item &item : items) { //Don't delete items that have not yet been synchronized if (item.remoteId().isEmpty()) { continue; } if (!mListedItems.contains(item.remoteId())) { mItemsToDelete << Item(item.id()); } } } void ItemSyncPrivate::slotLocalListDone(KJob *job) { mPendingJobs--; if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); } deleteItems(mItemsToDelete); checkDone(); } QString ItemSyncPrivate::jobDebuggingString() const { // TODO: also print out mIncremental and mTotalItemsProcessed, but they are set after the job // started, so this requires passing jobDebuggingString to jobEnded(). return QStringLiteral("Collection %1 (%2)").arg(mSyncCollection.id()).arg(mSyncCollection.name()); } void ItemSyncPrivate::execute() { - Q_Q(ItemSync); //shouldn't happen if (mFinished) { qCWarning(AKONADICORE_LOG) << "Call to execute() on finished job."; Q_ASSERT(false); return; } //not doing anything, start processing if (!mProcessingBatch) { if (mRemoteItemQueue.size() >= mBatchSize || mDeliveryDone) { //we have a new batch to process const int num = qMin(mBatchSize, mRemoteItemQueue.size()); mCurrentBatchRemoteItems.reserve(mBatchSize); std::move(mRemoteItemQueue.begin(), mRemoteItemQueue.begin() + num, std::back_inserter(mCurrentBatchRemoteItems)); mRemoteItemQueue.erase(mRemoteItemQueue.begin(), mRemoteItemQueue.begin() + num); mCurrentBatchRemovedRemoteItems += mRemovedRemoteItemQueue; mRemovedRemoteItemQueue.clear(); } else { //nothing to do, let's wait for more data return; } mProcessingBatch = true; processBatch(); return; } checkDone(); } //process the current batch of items void ItemSyncPrivate::processBatch() { - Q_Q(ItemSync); if (mCurrentBatchRemoteItems.isEmpty() && !mDeliveryDone) { return; } //request a transaction, there are items that require processing requestTransaction(); processItems(); // removed if (!mIncremental && allProcessed()) { //the full listing is done and we know which items to remove fetchLocalItemsToDelete(); } else { deleteItems(mCurrentBatchRemovedRemoteItems); mCurrentBatchRemovedRemoteItems.clear(); } checkDone(); } void ItemSyncPrivate::processItems() { Q_Q(ItemSync); // added / updated for (const Item &remoteItem : qAsConst(mCurrentBatchRemoteItems)) { if (remoteItem.remoteId().isEmpty()) { qCWarning(AKONADICORE_LOG) << "Item " << remoteItem.id() << " does not have a remote identifier"; continue; } if (!mIncremental) { mListedItems << remoteItem.remoteId(); } createOrMerge(remoteItem); } mCurrentBatchRemoteItems.clear(); } void ItemSyncPrivate::deleteItems(const Item::List &itemsToDelete) { Q_Q(ItemSync); // if in error state, better not change anything anymore if (q->error()) { return; } if (itemsToDelete.isEmpty()) { return; } mPendingJobs++; ItemDeleteJob *job = new ItemDeleteJob(itemsToDelete, subjobParent()); q->connect(job, &ItemDeleteJob::result, q, [this](KJob *job) { slotLocalDeleteDone(job); }); // It can happen that the groupware servers report us deleted items // twice, in this case this item delete job will fail on the second try. // To avoid a rollback of the complete transaction we gracefully allow the job // to fail :) TransactionSequence *transaction = qobject_cast(subjobParent()); if (transaction) { transaction->setIgnoreJobFailure(job); } } void ItemSyncPrivate::slotLocalDeleteDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Deleting items from the akonadi database failed:" << job->errorString(); } mPendingJobs--; mProgress++; checkDone(); } void ItemSyncPrivate::slotLocalChangeDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Creating/updating items from the akonadi database failed:" << job->errorString(); } mPendingJobs--; mProgress++; checkDone(); } void ItemSyncPrivate::slotTransactionResult(KJob *job) { --mTransactionJobs; if (mCurrentTransaction == job) { mCurrentTransaction = nullptr; } checkDone(); } void ItemSyncPrivate::requestTransaction() { Q_Q(ItemSync); //we never want parallel transactions, single transaction just makes one big transaction, and multi transaction uses multiple transaction sequentially if (!mCurrentTransaction) { ++mTransactionJobs; mCurrentTransaction = new TransactionSequence(q); mCurrentTransaction->setAutomaticCommittingEnabled(false); QObject::connect(mCurrentTransaction, &TransactionSequence::result, q, [this](KJob *job) { slotTransactionResult(job); }); } } Job *ItemSyncPrivate::subjobParent() const { Q_Q(const ItemSync); if (mCurrentTransaction && mTransactionMode != ItemSync::NoTransaction) { return mCurrentTransaction; } return const_cast(q); } void ItemSync::setStreamingEnabled(bool enable) { Q_D(ItemSync); d->mStreaming = enable; } void ItemSync::deliveryDone() { Q_D(ItemSync); Q_ASSERT(d->mStreaming); d->mDeliveryDone = true; d->execute(); } void ItemSync::slotResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error during ItemSync: " << job->errorString(); // pretent there were no errors Akonadi::Job::removeSubjob(job); // propagate the first error we got but continue, we might still be fed with stuff from a resource if (!error()) { setError(job->error()); setErrorText(job->errorText()); } } else { Akonadi::Job::slotResult(job); } } void ItemSync::rollback() { Q_D(ItemSync); qCWarning(AKONADICORE_LOG) << "The item sync is being rolled-back."; setError(UserCanceled); if (d->mCurrentTransaction) { d->mCurrentTransaction->rollback(); } d->mDeliveryDone = true; // user wont deliver more data d->execute(); // end this in an ordered way, since we have an error set no real change will be done } void ItemSync::setTransactionMode(ItemSync::TransactionMode mode) { Q_D(ItemSync); d->mTransactionMode = mode; } int ItemSync::batchSize() const { Q_D(const ItemSync); return d->mBatchSize; } void ItemSync::setBatchSize(int size) { Q_D(ItemSync); d->mBatchSize = size; } ItemSync::MergeMode ItemSync::mergeMode() const { Q_D(const ItemSync); return d->mMergeMode; } void ItemSync::setMergeMode(MergeMode mergeMode) { Q_D(ItemSync); d->mMergeMode = mergeMode; } #include "moc_itemsync.cpp" diff --git a/src/core/jobs/collectionfetchjob.cpp b/src/core/jobs/collectionfetchjob.cpp index d104a5d00..7f07b54ef 100644 --- a/src/core/jobs/collectionfetchjob.cpp +++ b/src/core/jobs/collectionfetchjob.cpp @@ -1,427 +1,427 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "collectionfetchjob.h" #include "job_p.h" #include "protocolhelper_p.h" #include "collection_p.h" #include "collectionfetchscope.h" #include "collectionutils.h" #include "protocolhelper_p.h" #include "private/protocol_p.h" #include "akonadicore_debug.h" #include #include #include #include using namespace Akonadi; class Akonadi::CollectionFetchJobPrivate : public JobPrivate { public: CollectionFetchJobPrivate(CollectionFetchJob *parent) : JobPrivate(parent) , mType(CollectionFetchJob::Base) { } void init() { mEmitTimer = new QTimer(q_ptr); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q_ptr->connect(mEmitTimer, SIGNAL(timeout()), q_ptr, SLOT(timeout())); } Q_DECLARE_PUBLIC(CollectionFetchJob) CollectionFetchJob::Type mType; Collection mBase; Collection::List mBaseList; Collection::List mCollections; CollectionFetchScope mScope; Collection::List mPendingCollections; QTimer *mEmitTimer = nullptr; bool mBasePrefetch = false; Collection::List mPrefetchList; void aboutToFinish() override { timeout(); } void timeout() { Q_Q(CollectionFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingCollections.isEmpty()) { if (!q->error() || mScope.ignoreRetrievalErrors()) { emit q->collectionsReceived(mPendingCollections); } mPendingCollections.clear(); } } void subJobCollectionReceived(const Akonadi::Collection::List &collections) { mPendingCollections += collections; if (!mEmitTimer->isActive()) { mEmitTimer->start(); } } QString jobDebuggingString() const override { if (mBase.isValid()) { return QStringLiteral("Collection Id %1").arg(mBase.id()); } else if (CollectionUtils::hasValidHierarchicalRID(mBase)) { //return QLatin1String("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1String(", ")) + QLatin1String(")"); return QStringLiteral("HRID chain"); } else { return QStringLiteral("Collection RemoteId %1").arg(mBase.remoteId()); } } bool jobFailed(KJob *job) { Q_Q(CollectionFetchJob); if (mScope.ignoreRetrievalErrors()) { int error = job->error(); if (error && !q->error()) { q->setError(error); q->setErrorText(job->errorText()); } if (error == Job::ConnectionFailed || error == Job::ProtocolVersionMismatch || error == Job::UserCanceled) { return true; } return false; } else { return job->error(); } } }; CollectionFetchJob::CollectionFetchJob(const Collection &collection, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); d->mBase = collection; d->mType = type; } CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = cols.first(); } else { d->mBaseList = cols; } d->mType = CollectionFetchJob::Base; } CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = cols.first(); } else { d->mBaseList = cols; } d->mType = type; } CollectionFetchJob::CollectionFetchJob(const QList &cols, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = Collection(cols.first()); } else { for (Collection::Id id : cols) { d->mBaseList.append(Collection(id)); } } d->mType = type; } CollectionFetchJob::~CollectionFetchJob() { } Akonadi::Collection::List CollectionFetchJob::collections() const { Q_D(const CollectionFetchJob); return d->mCollections; } void CollectionFetchJob::doStart() { Q_D(CollectionFetchJob); if (!d->mBaseList.isEmpty()) { if (d->mType == Recursive) { // Because doStart starts several subjobs and @p cols could contain descendants of // other elements in the list, if type is Recusrive, we could end up with duplicates in the result. // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors, // Iterate over that result removing intersections and then perform the Recursive fetch on // the remainder. d->mBasePrefetch = true; // No need to connect to the collectionsReceived signal here. This job is internal. The // result needs to be filtered through filterDescendants before it is useful. new CollectionFetchJob(d->mBaseList, NonOverlappingRoots, this); } else if (d->mType == NonOverlappingRoots) { for (const Collection &col : qAsConst(d->mBaseList)) { // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated) // result needs to be filtered through filterDescendants before it is useful. CollectionFetchJob *subJob = new CollectionFetchJob(col, Base, this); subJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); } } else { for (const Collection &col : qAsConst(d->mBaseList)) { CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this); connect(subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List))); subJob->setFetchScope(fetchScope()); } } return; } if (!d->mBase.isValid() && d->mBase.remoteId().isEmpty()) { setError(Unknown); setErrorText(i18n("Invalid collection given.")); emitResult(); return; } const auto cmd = Protocol::FetchCollectionsCommandPtr::create(ProtocolHelper::entityToScope(d->mBase)); switch (d->mType) { case Base: cmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection); break; case Akonadi::CollectionFetchJob::FirstLevel: cmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection); break; case Akonadi::CollectionFetchJob::Recursive: cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections); break; default: Q_ASSERT(false); } cmd->setResource(d->mScope.resource()); cmd->setMimeTypes(d->mScope.contentMimeTypes()); switch (d->mScope.listFilter()) { case CollectionFetchScope::Display: cmd->setDisplayPref(true); break; case CollectionFetchScope::Sync: cmd->setSyncPref(true); break; case CollectionFetchScope::Index: cmd->setIndexPref(true); break; case CollectionFetchScope::Enabled: cmd->setEnabled(true); break; case CollectionFetchScope::NoFilter: break; default: Q_ASSERT(false); } cmd->setFetchStats(d->mScope.includeStatistics()); switch (d->mScope.ancestorRetrieval()) { case CollectionFetchScope::None: cmd->setAncestorsDepth(Protocol::Ancestor::NoAncestor); break; case CollectionFetchScope::Parent: cmd->setAncestorsDepth(Protocol::Ancestor::ParentAncestor); break; case CollectionFetchScope::All: cmd->setAncestorsDepth(Protocol::Ancestor::AllAncestors); break; } if (d->mScope.ancestorRetrieval() != CollectionFetchScope::None) { cmd->setAncestorsAttributes(d->mScope.ancestorFetchScope().attributes()); } d->sendCommand(cmd); } bool CollectionFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(CollectionFetchJob); if (d->mBasePrefetch || d->mType == NonOverlappingRoots) { return false; } if (!response->isResponse() || response->type() != Protocol::Command::FetchCollections) { return Job::doHandleResponse(tag, response); } const auto &resp = Protocol::cmdCast(response); // Invalid response (no ID) means this was the last response if (resp.id() == -1) { return true; } Collection collection = ProtocolHelper::parseCollection(resp, true); if (!collection.isValid()) { return false; } collection.d_ptr->resetChangeLog(); d->mCollections.append(collection); d->mPendingCollections.append(collection); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } return false; } static Collection::List filterDescendants(const Collection::List &list) { Collection::List result; QVector > ids; ids.reserve(list.count()); for (const Collection &collection : list) { QList ancestors; Collection parent = collection.parentCollection(); ancestors << parent.id(); if (parent != Collection::root()) { while (parent.parentCollection() != Collection::root()) { parent = parent.parentCollection(); QList::iterator i = std::lower_bound(ancestors.begin(), ancestors.end(), parent.id()); ancestors.insert(i, parent.id()); } } ids << ancestors; } QSet excludeList; for (const Collection &collection : list) { int i = 0; for (const QList &ancestors : qAsConst(ids)) { if (qBinaryFind(ancestors, collection.id()) != ancestors.end()) { excludeList.insert(list.at(i).id()); } ++i; } } for (const Collection &collection : list) { if (!excludeList.contains(collection.id())) { result.append(collection); } } return result; } void CollectionFetchJob::slotResult(KJob *job) { Q_D(CollectionFetchJob); CollectionFetchJob *list = qobject_cast(job); Q_ASSERT(job); if (d->mType == NonOverlappingRoots) { d->mPrefetchList += list->collections(); } else if (!d->mBasePrefetch) { d->mCollections += list->collections(); } if (d_ptr->mCurrentSubJob == job && !d->jobFailed(job)) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error during CollectionFetchJob: " << job->errorString(); } d_ptr->mCurrentSubJob = nullptr; removeSubjob(job); - QTimer::singleShot(0, this, [this, d]() { d->startNext(); }); + QTimer::singleShot(0, this, [d]() { d->startNext(); }); } else { Job::slotResult(job); } if (d->mBasePrefetch) { d->mBasePrefetch = false; const Collection::List roots = list->collections(); Q_ASSERT(!hasSubjobs()); if (!job->error()) { for (const Collection &col : roots) { CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this); connect(subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List))); subJob->setFetchScope(fetchScope()); } } // No result yet. } else if (d->mType == NonOverlappingRoots) { if (!d->jobFailed(job) && !hasSubjobs()) { const Collection::List result = filterDescendants(d->mPrefetchList); d->mPendingCollections += result; d->mCollections = result; d->delayedEmitResult(); } } else { if (!d->jobFailed(job) && !hasSubjobs()) { d->delayedEmitResult(); } } } void CollectionFetchJob::setFetchScope(const CollectionFetchScope &scope) { Q_D(CollectionFetchJob); d->mScope = scope; } CollectionFetchScope &CollectionFetchJob::fetchScope() { Q_D(CollectionFetchJob); return d->mScope; } #include "moc_collectionfetchjob.cpp" diff --git a/src/core/jobs/itemmodifyjob.cpp b/src/core/jobs/itemmodifyjob.cpp index 9e1a47173..fa3a1182d 100644 --- a/src/core/jobs/itemmodifyjob.cpp +++ b/src/core/jobs/itemmodifyjob.cpp @@ -1,431 +1,431 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "akonadicore_debug.h" #include "changemediator_p.h" #include "collection.h" #include "conflicthandler_p.h" #include "item_p.h" #include "itemserializer_p.h" #include "job_p.h" #include "protocolhelper_p.h" #include "gidextractor_p.h" #include #include using namespace Akonadi; ItemModifyJobPrivate::ItemModifyJobPrivate(ItemModifyJob *parent) : JobPrivate(parent) , mRevCheck(true) , mIgnorePayload(false) , mAutomaticConflictHandlingEnabled(true) , mSilent(false) { } void ItemModifyJobPrivate::setClean() { mOperations.insert(Dirty); } Protocol::PartMetaData ItemModifyJobPrivate::preparePart(const QByteArray &partName) { ProtocolHelper::PartNamespace ns; // dummy const QByteArray partLabel = ProtocolHelper::decodePartIdentifier(partName, ns); if (!mParts.remove(partLabel)) { // Error? return Protocol::PartMetaData(); } mPendingData.clear(); int version = 0; const auto item = mItems.first(); if (mForeignParts.contains(partLabel)) { mPendingData = item.d_ptr->mPayloadPath.toUtf8(); const auto size = QFile(item.d_ptr->mPayloadPath).size(); return Protocol::PartMetaData(partName, size, version, Protocol::PartMetaData::Foreign); } else { ItemSerializer::serialize(mItems.first(), partLabel, mPendingData, version); return Protocol::PartMetaData(partName, mPendingData.size(), version); } } void ItemModifyJobPrivate::conflictResolved() { Q_Q(ItemModifyJob); q->setError(KJob::NoError); q->setErrorText(QString()); q->emitResult(); } void ItemModifyJobPrivate::conflictResolveError(const QString &message) { Q_Q(ItemModifyJob); q->setErrorText(q->errorText() + message); q->emitResult(); } void ItemModifyJobPrivate::doUpdateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision) { auto it = std::find_if(mItems.begin(), mItems.end(), [&itemId](const Item & item) -> bool { return item.id() == itemId; }); if (it != mItems.end() && (*it).revision() == oldRevision) { (*it).setRevision(newRevision); } } QString ItemModifyJobPrivate::jobDebuggingString() const { try { return Protocol::debugString(fullCommand()); } catch (const Exception &e) { return QString::fromUtf8(e.what()); } } void ItemModifyJobPrivate::setSilent(bool silent) { mSilent = silent; } ItemModifyJob::ItemModifyJob(const Item &item, QObject *parent) : Job(new ItemModifyJobPrivate(this), parent) { Q_D(ItemModifyJob); d->mItems.append(item); d->mParts = item.loadedPayloadParts(); d->mOperations.insert(ItemModifyJobPrivate::RemoteId); d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision); if (!item.payloadPath().isEmpty()) { d->mForeignParts = ItemSerializer::allowedForeignParts(item); } } ItemModifyJob::ItemModifyJob(const Akonadi::Item::List &items, QObject *parent) : Job(new ItemModifyJobPrivate(this), parent) { Q_ASSERT(!items.isEmpty()); Q_D(ItemModifyJob); d->mItems = items; // same as single item ctor if (d->mItems.size() == 1) { d->mParts = items.first().loadedPayloadParts(); d->mOperations.insert(ItemModifyJobPrivate::RemoteId); d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision); } else { d->mIgnorePayload = true; d->mRevCheck = false; } } ItemModifyJob::~ItemModifyJob() { } Protocol::ModifyItemsCommandPtr ItemModifyJobPrivate::fullCommand() const { auto cmd = Protocol::ModifyItemsCommandPtr::create(); const Akonadi::Item item = mItems.first(); for (int op : qAsConst(mOperations)) { switch (op) { case ItemModifyJobPrivate::RemoteId: if (!item.remoteId().isNull()) { cmd->setRemoteId(item.remoteId()); } break; case ItemModifyJobPrivate::Gid: { const QString gid = GidExtractor::getGid(item); if (!gid.isNull()) { cmd->setGid(gid); } break; } case ItemModifyJobPrivate::RemoteRevision: if (!item.remoteRevision().isNull()) { cmd->setRemoteRevision(item.remoteRevision()); } break; case ItemModifyJobPrivate::Dirty: cmd->setDirty(false); break; } } if (item.d_ptr->mClearPayload) { cmd->setInvalidateCache(true); } if (mSilent) { cmd->setNotify(true); } if (item.d_ptr->mFlagsOverwritten) { cmd->setFlags(item.flags()); } else { const auto addedFlags = ItemChangeLog::instance()->addedFlags(item.d_ptr); if (!addedFlags.isEmpty()) { cmd->setAddedFlags(addedFlags); } const auto deletedFlags = ItemChangeLog::instance()->deletedFlags(item.d_ptr); if (!deletedFlags.isEmpty()) { cmd->setRemovedFlags(deletedFlags); } } if (item.d_ptr->mTagsOverwritten) { cmd->setTags(ProtocolHelper::entitySetToScope(item.tags())); } else { const auto addedTags = ItemChangeLog::instance()->addedTags(item.d_ptr); if (!addedTags.isEmpty()) { cmd->setAddedTags(ProtocolHelper::entitySetToScope(addedTags)); } const auto deletedTags = ItemChangeLog::instance()->deletedTags(item.d_ptr); if (!deletedTags.isEmpty()) { cmd->setRemovedTags(ProtocolHelper::entitySetToScope(deletedTags)); } } if (!mParts.isEmpty()) { QSet parts; parts.reserve(mParts.size()); for (const QByteArray &part : qAsConst(mParts)) { parts.insert(ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, part)); } cmd->setParts(parts); } const auto deletedAttributes = ItemChangeLog::instance()->deletedAttributes(item.d_ptr); if (!deletedAttributes.isEmpty()) { QSet removedParts; removedParts.reserve(deletedAttributes.size()); for (const QByteArray &part : deletedAttributes) { removedParts.insert("ATR:" + part); } cmd->setRemovedParts(removedParts); } // nothing to do if (cmd->modifiedParts() == Protocol::ModifyItemsCommand::None && mParts.isEmpty() && item.attributes().isEmpty() && !cmd->invalidateCache()) { return cmd; } cmd->setItems(ProtocolHelper::entitySetToScope(mItems)); if (mRevCheck && item.revision() >= 0) { cmd->setOldRevision(item.revision()); } if (item.d_ptr->mSizeChanged) { cmd->setItemSize(item.size()); } cmd->setAttributes(ProtocolHelper::attributesToProtocol(item)); return cmd; } void ItemModifyJob::doStart() { Q_D(ItemModifyJob); Protocol::ModifyItemsCommandPtr command; try { command = d->fullCommand(); } catch (const Exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); emitResult(); return; } if (command->modifiedParts() == Protocol::ModifyItemsCommand::None) { emitResult(); return; } d->sendCommand(command); } bool ItemModifyJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(ItemModifyJob); if (!response->isResponse() && response->type() == Protocol::Command::StreamPayload) { const auto &streamCmd = Protocol::cmdCast(response); auto streamResp = Protocol::StreamPayloadResponsePtr::create(); if (streamCmd.request() == Protocol::StreamPayloadCommand::MetaData) { streamResp->setMetaData(d->preparePart(streamCmd.payloadName())); } else { if (streamCmd.destination().isEmpty()) { streamResp->setData(d->mPendingData); } else { QByteArray error; if (!ProtocolHelper::streamPayloadToFile(streamCmd.destination(), d->mPendingData, error)) { // TODO: Error? } } } d->sendCommand(tag, streamResp); return false; } if (response->isResponse() && response->type() == Protocol::Command::ModifyItems) { const auto &resp = Protocol::cmdCast(response); if (resp.errorCode()) { setError(Unknown); setErrorText(resp.errorMessage()); return true; } if (resp.errorMessage().contains(QLatin1String("[LLCONFLICT]"))) { if (d->mAutomaticConflictHandlingEnabled) { ConflictHandler *handler = new ConflictHandler(ConflictHandler::LocalLocalConflict, this); handler->setConflictingItems(d->mItems.first(), d->mItems.first()); - connect(handler, &ConflictHandler::conflictResolved, this, [this, d]() { d->conflictResolved(); }); - connect(handler, &ConflictHandler::error, this, [this, d](const QString &str) { d->conflictResolveError(str); }); + connect(handler, &ConflictHandler::conflictResolved, this, [d]() { d->conflictResolved(); }); + connect(handler, &ConflictHandler::error, this, [d](const QString &str) { d->conflictResolveError(str); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(handler, &ConflictHandler::start, Qt::QueuedConnection); #else QMetaObject::invokeMethod(handler, "start", Qt::QueuedConnection); #endif return true; } } if (resp.modificationDateTime().isValid()) { Item &item = d->mItems.first(); item.setModificationTime(resp.modificationDateTime()); item.d_ptr->resetChangeLog(); } else if (resp.id() > -1) { auto it = std::find_if(d->mItems.begin(), d->mItems.end(), [&resp](const Item & item) -> bool { return item.id() == resp.id(); }); if (it == d->mItems.end()) { qCDebug(AKONADICORE_LOG) << "Received STORE response for an item we did not modify: " << tag << Protocol::debugString(response); return true; } const int newRev = resp.newRevision(); const int oldRev = (*it).revision(); if (newRev >= oldRev && newRev >= 0) { d->itemRevisionChanged((*it).id(), oldRev, newRev); (*it).setRevision(newRev); } // There will be more responses, either for other modified items, // or the final response with invalid ID, but with modification datetime return false; } for (const Item &item : qAsConst(d->mItems)) { ChangeMediator::invalidateItem(item); } return true; } return Job::doHandleResponse(tag, response); } void ItemModifyJob::setIgnorePayload(bool ignore) { Q_D(ItemModifyJob); if (d->mIgnorePayload == ignore) { return; } d->mIgnorePayload = ignore; if (d->mIgnorePayload) { d->mParts = QSet(); } else { Q_ASSERT(!d->mItems.first().mimeType().isEmpty()); d->mParts = d->mItems.first().loadedPayloadParts(); } } bool ItemModifyJob::ignorePayload() const { Q_D(const ItemModifyJob); return d->mIgnorePayload; } void ItemModifyJob::setUpdateGid(bool update) { Q_D(ItemModifyJob); if (update && !updateGid()) { d->mOperations.insert(ItemModifyJobPrivate::Gid); } else { d->mOperations.remove(ItemModifyJobPrivate::Gid); } } bool ItemModifyJob::updateGid() const { Q_D(const ItemModifyJob); return d->mOperations.contains(ItemModifyJobPrivate::Gid); } void ItemModifyJob::disableRevisionCheck() { Q_D(ItemModifyJob); d->mRevCheck = false; } void ItemModifyJob::disableAutomaticConflictHandling() { Q_D(ItemModifyJob); d->mAutomaticConflictHandlingEnabled = false; } Item ItemModifyJob::item() const { Q_D(const ItemModifyJob); Q_ASSERT(d->mItems.size() == 1); return d->mItems.first(); } Item::List ItemModifyJob::items() const { Q_D(const ItemModifyJob); return d->mItems; } #include "moc_itemmodifyjob.cpp" diff --git a/src/core/models/collectionmodel.cpp b/src/core/models/collectionmodel.cpp index d8213208d..6c63ef455 100644 --- a/src/core/models/collectionmodel.cpp +++ b/src/core/models/collectionmodel.cpp @@ -1,318 +1,318 @@ /* Copyright (c) 2006 - 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "collectionmodel.h" #include "collectionmodel_p.h" #include "collectionutils.h" #include "collectionmodifyjob.h" #include "entitydisplayattribute.h" #include "monitor.h" #include "pastehelper_p.h" #include "session.h" #include #include #include using namespace Akonadi; CollectionModel::CollectionModel(QObject *parent) : QAbstractItemModel(parent) , d_ptr(new CollectionModelPrivate(this)) { Q_D(CollectionModel); d->init(); } //@cond PRIVATE CollectionModel::CollectionModel(CollectionModelPrivate *d, QObject *parent) : QAbstractItemModel(parent) , d_ptr(d) { d->init(); } //@endcond CollectionModel::~CollectionModel() { Q_D(CollectionModel); d->childCollections.clear(); d->collections.clear(); delete d->monitor; d->monitor = nullptr; delete d; } int CollectionModel::columnCount(const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) { return 0; } return 1; } QVariant CollectionModel::data(const QModelIndex &index, int role) const { Q_D(const CollectionModel); if (!index.isValid()) { return QVariant(); } const Collection col = d->collections.value(index.internalId()); if (!col.isValid()) { return QVariant(); } if (index.column() == 0 && (role == Qt::DisplayRole || role == Qt::EditRole)) { return col.displayName(); } switch (role) { case Qt::DecorationRole: if (index.column() == 0) { return d->iconForCollection(col); } break; case CollectionIdRole: return col.id(); case CollectionRole: return QVariant::fromValue(col); } return QVariant(); } QModelIndex CollectionModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const CollectionModel); if (column >= columnCount() || column < 0) { return QModelIndex(); } QVector list; if (!parent.isValid()) { list = d->childCollections.value(Collection::root().id()); } else { if (parent.column() > 0) { return QModelIndex(); } list = d->childCollections.value(parent.internalId()); } if (row < 0 || row >= list.size()) { return QModelIndex(); } if (!d->collections.contains(list.at(row))) { return QModelIndex(); } return createIndex(row, column, reinterpret_cast(d->collections.value(list.at(row)).id())); } QModelIndex CollectionModel::parent(const QModelIndex &index) const { Q_D(const CollectionModel); if (!index.isValid()) { return QModelIndex(); } const Collection col = d->collections.value(index.internalId()); if (!col.isValid()) { return QModelIndex(); } const Collection parentCol = d->collections.value(col.parentCollection().id()); if (!parentCol.isValid()) { return QModelIndex(); } const QVector list = d->childCollections.value(parentCol.parentCollection().id()); int parentRow = list.indexOf(parentCol.id()); if (parentRow < 0) { return QModelIndex(); } return createIndex(parentRow, 0, reinterpret_cast(parentCol.id())); } int CollectionModel::rowCount(const QModelIndex &parent) const { const Q_D(CollectionModel); QVector list; if (parent.isValid()) { list = d->childCollections.value(parent.internalId()); } else { list = d->childCollections.value(Collection::root().id()); } return list.size(); } QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, int role) const { const Q_D(CollectionModel); if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return d->headerContent; } return QAbstractItemModel::headerData(section, orientation, role); } bool CollectionModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { Q_D(CollectionModel); if (section == 0 && orientation == Qt::Horizontal && role == Qt::EditRole) { d->headerContent = value.toString(); return true; } return false; } bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_D(CollectionModel); if (index.column() == 0 && role == Qt::EditRole) { // rename collection Collection col = d->collections.value(index.internalId()); if (!col.isValid() || value.toString().isEmpty()) { return false; } col.setName(value.toString()); CollectionModifyJob *job = new CollectionModifyJob(col, d->session); - connect(job, &CollectionModifyJob::result, this, [this, d](KJob* job) { d->editDone(job); }); + connect(job, &CollectionModifyJob::result, this, [d](KJob* job) { d->editDone(job); }); return true; } return QAbstractItemModel::setData(index, value, role); } Qt::ItemFlags CollectionModel::flags(const QModelIndex &index) const { Q_D(const CollectionModel); // Pass modeltest. if (!index.isValid()) { return 0; } Qt::ItemFlags flags = QAbstractItemModel::flags(index); flags = flags | Qt::ItemIsDragEnabled; Collection col; if (index.isValid()) { col = d->collections.value(index.internalId()); Q_ASSERT(col.isValid()); } else { return flags | Qt::ItemIsDropEnabled; // HACK Workaround for a probable bug in Qt } if (col.isValid()) { if (col.rights() & (Collection::CanChangeCollection | Collection::CanCreateCollection | Collection::CanDeleteCollection | Collection::CanCreateItem)) { if (index.column() == 0) { flags = flags | Qt::ItemIsEditable; } flags = flags | Qt::ItemIsDropEnabled; } } return flags; } Qt::DropActions CollectionModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList CollectionModel::mimeTypes() const { return {QStringLiteral("text/uri-list")}; } QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QList urls; for (const QModelIndex &index : indexes) { if (index.column() != 0) { continue; } urls << Collection(index.internalId()).url(); } data->setUrls(urls); return data; } bool CollectionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_D(CollectionModel); if (!(action & supportedDropActions())) { return false; } // handle drops onto items as well as drops between items QModelIndex idx; if (row >= 0 && column >= 0) { idx = index(row, column, parent); } else { idx = parent; } if (!idx.isValid()) { return false; } const Collection parentCol = d->collections.value(idx.internalId()); if (!parentCol.isValid()) { return false; } KJob *job = PasteHelper::paste(data, parentCol, action != Qt::MoveAction); connect(job, SIGNAL(result(KJob*)), SLOT(dropResult(KJob*))); return true; } Collection CollectionModel::collectionForId(Collection::Id id) const { Q_D(const CollectionModel); return d->collections.value(id); } void CollectionModel::fetchCollectionStatistics(bool enable) { Q_D(CollectionModel); d->fetchStatistics = enable; d->monitor->fetchCollectionStatistics(enable); } void CollectionModel::includeUnsubscribed(bool include) { Q_D(CollectionModel); d->unsubscribed = include; } #include "moc_collectionmodel.cpp" diff --git a/src/core/models/entitytreemodel.h b/src/core/models/entitytreemodel.h index a7ed13207..12e8a6d75 100644 --- a/src/core/models/entitytreemodel.h +++ b/src/core/models/entitytreemodel.h @@ -1,740 +1,740 @@ /* Copyright (c) 2008 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_ENTITYTREEMODEL_H #define AKONADI_ENTITYTREEMODEL_H #include "akonadicore_export.h" #include "collection.h" #include "collectionfetchscope.h" #include "item.h" #include #include namespace Akonadi { class CollectionStatistics; class Item; class ItemFetchScope; class Monitor; class Session; class EntityTreeModelPrivate; /** * @short A model for collections and items together. * * Akonadi models and views provide a high level way to interact with the akonadi server. * Most applications will use these classes. * * Models provide an interface for viewing, updating, deleting and moving Items and Collections. * Additionally, the models are updated automatically if another application changes the * data or inserts of deletes items etc. * * @note The EntityTreeModel should be used with the EntityTreeView or the EntityListView class * either directly or indirectly via proxy models. * *

Retrieving Collections and Items from the model

* * If you want to retrieve and Item or Collection from the model, and already have a valid * QModelIndex for the correct row, the Collection can be retrieved like this: * * @code * Collection col = index.data( EntityTreeModel::CollectionRole ).value(); * @endcode * * And similarly for Items. This works even if there is a proxy model between the calling code * and the EntityTreeModel. * * If you want to retrieve a Collection for a particular Collection::Id and you do not yet * have a valid QModelIndex, use modelIndexForCollection. * *

Using EntityTreeModel in your application

* * The responsibilities which fall to the application developer are * - Configuring the Monitor and EntityTreeModel * - Making use of this class via proxy models * - Subclassing for type specific display information * *

Creating and configuring the EntityTreeModel

* * This class is a wrapper around a Akonadi::Monitor object. The model represents a * part of the collection and item tree configured in the Monitor. The structure of the * model mirrors the structure of Collections and Items on the %Akonadi server. * * The following code creates a model which fetches items and collections relevant to * addressees (contacts), and automatically manages keeping the items up to date. * * @code * * Monitor *monitor = new Monitor( this ); * monitor->setCollectionMonitored( Collection::root() ); * monitor->setMimeTypeMonitored( KContacts::addresseeMimeType() ); * monitor->setSession( session ); * * EntityTreeModel *model = new EntityTreeModel( monitor, this ); * * EntityTreeView *view = new EntityTreeView( this ); * view->setModel( model ); * * @endcode * * The EntityTreeModel will show items of a different type by changing the line * * @code * monitor->setMimeTypeMonitored( KContacts::addresseeMimeType() ); * @endcode * * to a different mimetype. KContacts::addresseeMimeType() is an alias for "text/directory". If changed to KMime::Message::mimeType() * (an alias for "message/rfc822") the model would instead contain emails. The model can be configured to contain items of any mimetype * known to %Akonadi. * * @note The EntityTreeModel does some extra configuration on the Monitor, such as setting itemFetchScope() and collectionFetchScope() * to retrieve all ancestors. This is necessary for proper function of the model. * * @see Akonadi::ItemFetchScope::AncestorRetrieval. * * @see akonadi-mimetypes. * * The EntityTreeModel can be further configured for certain behaviours such as fetching of collections and items. * * The model can be configured to not fetch items into the model (ie, fetch collections only) by setting * * @code * entityTreeModel->setItemPopulationStrategy( EntityTreeModel::NoItemPopulation ); * @endcode * * The items may be fetched lazily, i.e. not inserted into the model until request by the user for performance reasons. * * The Collection tree is always built immediately if Collections are to be fetched. * * @code * entityTreeModel->setItemPopulationStrategy( EntityTreeModel::LazyPopulation ); * @endcode * * This will typically be used with a EntityMimeTypeFilterModel in a configuration such as KMail4.5 or AkonadiConsole. * * The CollectionFetchStrategy determines how the model will be populated with Collections. That is, if FetchNoCollections is set, * no collections beyond the root of the model will be fetched. This can be used in combination with setting a particular Collection to monitor. * * @code * // Get an collection id from a config file. * Collection::Id id; * monitor->setCollectionMonitored( Collection( id ) ); * // ... Other initialization code. * entityTree->setCollectionFetchStrategy( FetchNoCollections ); * @endcode * * This has the effect of creating a model of only a list of Items, and not collections. This is similar in behaviour and aims to the ItemModel. * By using FetchFirstLevelCollections instead, a mixed list of entities can be created. * * @note It is important that you set only one Collection to be monitored in the monitor object. This one collection will be the root of the tree. * If you need a model with a more complex structure, consider monitoring a common ancestor and using a SelectionProxyModel. * * @see lazy-model-population * * It is also possible to show the root Collection as part of the selectable model: * * @code * entityTree->setIncludeRootCollection( true ); * @endcode * * * By default the displayed name of the root collection is '[*]', because it doesn't require i18n, and is generic. It can be changed too. * * @code * entityTree->setIncludeRootCollection( true ); * entityTree->setRootCollectionDisplayName( i18nc( "Name of top level for all addressbooks in the application", "[All AddressBooks]" ) ) * @endcode * * This feature is used in KAddressBook. * * If items are to be fetched by the model, it is necessary to specify which parts of the items * are to be fetched, using the ItemFetchScope class. By default, only the basic metadata is * fetched. To fetch all item data, including all attributes: * * @code * monitor->itemFetchScope().fetchFullPayload(); * monitor->itemFetchScope().fetchAllAttributes(); * @endcode * *

Using EntityTreeModel with Proxy models

* * An Akonadi::SelectionProxyModel can be used to simplify managing selection in one view through multiple proxy models to a representation in another view. * The selectionModel of the initial view is used to create a proxied model which filters out anything not related to the current selection. * * @code * // ... create an EntityTreeModel * * collectionTree = new EntityMimeTypeFilterModel( this ); * collectionTree->setSourceModel( entityTreeModel ); * * // Include only collections in this proxy model. * collectionTree->addMimeTypeInclusionFilter( Collection::mimeType() ); * collectionTree->setHeaderGroup( EntityTreeModel::CollectionTreeHeaders ); * * treeview->setModel(collectionTree); * * // SelectionProxyModel can handle complex selections: * treeview->setSelectionMode( QAbstractItemView::ExtendedSelection ); * * SelectionProxyModel *selProxy = new SelectionProxyModel( treeview->selectionModel(), this ); * selProxy->setSourceModel( entityTreeModel ); * * itemList = new EntityMimeTypeFilterModel( this ); * itemList->setSourceModel( selProxy ); * * // Filter out collections. Show only items. * itemList->addMimeTypeExclusionFilter( Collection::mimeType() ); * itemList->setHeaderGroup( EntityTreeModel::ItemListHeaders ); * * EntityTreeView *itemView = new EntityTreeView( splitter ); * itemView->setModel( itemList ); * @endcode * * The SelectionProxyModel can handle complex selections. * * See the KSelectionProxyModel documentation for the valid configurations of a Akonadi::SelectionProxyModel. * * Obviously, the SelectionProxyModel may be used in a view, or further processed with other proxy models. Typically, the result * from this model will be further filtered to remove collections from the item list as in the above example. * * There are several advantages of using EntityTreeModel with the SelectionProxyModel, namely the items can be fetched and cached * instead of being fetched many times, and the chain of proxies from the core model to the view is automatically handled. There is * no need to manage all the mapToSource and mapFromSource calls manually. * * A KDescendantsProxyModel can be used to represent all descendants of a model as a flat list. * For example, to show all descendant items in a selected Collection in a list: * @code * collectionTree = new EntityMimeTypeFilterModel( this ); * collectionTree->setSourceModel( entityTreeModel ); * * // Include only collections in this proxy model. * collectionTree->addMimeTypeInclusionFilter( Collection::mimeType() ); * collectionTree->setHeaderGroup( EntityTreeModel::CollectionTreeHeaders ); * * treeview->setModel( collectionTree ); * * SelectionProxyModel *selProxy = new SelectionProxyModel( treeview->selectionModel(), this ); * selProxy->setSourceModel( entityTreeModel ); * * descendedList = new DescendantEntitiesProxyModel( this ); * descendedList->setSourceModel( selProxy ); * * itemList = new EntityMimeTypeFilterModel( this ); * itemList->setSourceModel( descendedList ); * * // Exclude collections from the list view. * itemList->addMimeTypeExclusionFilter( Collection::mimeType() ); * itemList->setHeaderGroup( EntityTreeModel::ItemListHeaders ); * * listView = new EntityTreeView( this ); * listView->setModel( itemList ); * @endcode * * * Note that it is important in this case to use the DescendantEntitesProxyModel before the EntityMimeTypeFilterModel. * Otherwise, by filtering out the collections first, you would also be filtering out their child items. * * This pattern is used in KAddressBook. * * It would not make sense to use a KDescendantsProxyModel with LazyPopulation. * *

Subclassing EntityTreeModel

* * Usually an application will create a subclass of an EntityTreeModel and use that in several views via proxy models. * * The subclassing is necessary in order for the data in the model to have type-specific representation in applications * * For example, the headerData for an EntityTreeModel will be different depending on whether it is in a view showing only Collections * in which case the header data should be "AddressBooks" for example, or only Items, in which case the headerData would be * for example "Family Name", "Given Name" and "Email addres" for contacts or "Subject", "Sender", "Date" in the case of emails. * * Additionally, the actual data shown in the rows of the model should be type specific. * * In summary, it must be possible to have different numbers of columns, different data in hte rows of those columns, and different * titles for each column depending on the contents of the view. * * The way this is accomplished is by using the EntityMimeTypeFilterModel for splitting the model into a "CollectionTree" and an "Item List" * as in the above example, and using a type-specific EntityTreeModel subclass to return the type-specific data, typically for only one type (for example, contacts or emails). * * The following protected virtual methods should be implemented in the subclass: * - int entityColumnCount( HeaderGroup headerGroup ) const; * -- Implement to return the number of columns for a HeaderGroup. If the HeaderGroup is CollectionTreeHeaders, return the number of columns to display for the * Collection tree, and if it is ItemListHeaders, return the number of columns to display for the item. In the case of addressee, this could be for example, * two (for given name and family name) or for emails it could be three (for subject, sender, date). This is a decision of the subclass implementor. * - QVariant entityHeaderData( int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup ) const; * -- Implement to return the data for each section for a HeaderGroup. For example, if the header group is CollectionTreeHeaders in a contacts model, * the string "Address books" might be returned for column 0, whereas if the headerGroup is ItemListHeaders, the strings "Given Name", "Family Name", * "Email Address" might be returned for the columns 0, 1, and 2. * - QVariant entityData( const Collection &collection, int column, int role = Qt::DisplayRole ) const; * -- Implement to return data for a particular Collection. Typically this will be the name of the collection or the EntityDisplayAttribute. * - QVariant entityData( const Item &item, int column, int role = Qt::DisplayRole ) const; * -- Implement to return the data for a particular item and column. In the case of email for example, this would be the actual subject, sender and date of the email. * * @note The entityData methods are just for convenience. the QAbstractItemMOdel::data method can be overridden if required. * * The application writer must then properly configure proxy models for the views, so that the correct data is shown in the correct view. * That is the purpose of these lines in the above example * * @code * collectionTree->setHeaderGroup( EntityTreeModel::CollectionTreeHeaders ); * itemList->setHeaderGroup( EntityTreeModel::ItemListHeaders ); * @endcode * *

Progress reporting

* * The EntityTreeModel uses asynchronous Akonadi::Job instances to fill and update itself. * For example, a job is run to fetch the contents of collections (that is, list the items in it). * Additionally, individual Akonadi::Items can be fetched in different parts at different times. * * To indicate that such a job is underway, the EntityTreeModel makes the FetchState available. The * FetchState returned from a QModelIndex representing a Akonadi::Collection will be FetchingState if a * listing of the items in that collection is underway, otherwise the state is IdleState. * * @author Stephen Kelly * @since 4.4 */ class AKONADICORE_EXPORT EntityTreeModel : public QAbstractItemModel { Q_OBJECT public: /** * Describes the roles for items. Roles for collections are defined by the superclass. */ enum Roles { //sebsauer, 2009-05-07; to be able here to keep the akonadi_next EntityTreeModel compatible with //the akonadi_old ItemModel and CollectionModel, we need to use the same int-values for //ItemRole, ItemIdRole and MimeTypeRole like the Akonadi::ItemModel is using and the same //CollectionIdRole and CollectionRole like the Akonadi::CollectionModel is using. ItemIdRole = Qt::UserRole + 1, ///< The item id ItemRole = Qt::UserRole + 2, ///< The Item MimeTypeRole = Qt::UserRole + 3, ///< The mimetype of the entity CollectionIdRole = Qt::UserRole + 10, ///< The collection id. CollectionRole = Qt::UserRole + 11, ///< The collection. RemoteIdRole, ///< The remoteId of the entity CollectionChildOrderRole, ///< Ordered list of child items if available ParentCollectionRole, ///< The parent collection of the entity ColumnCountRole, ///< @internal Used by proxies to determine the number of columns for a header group. LoadedPartsRole, ///< Parts available in the model for the item AvailablePartsRole, ///< Parts available in the Akonadi server for the item SessionRole, ///< @internal The Session used by this model CollectionRefRole, ///< @internal Used to increase the reference count on a Collection CollectionDerefRole, ///< @internal Used to decrease the reference count on a Collection PendingCutRole, ///< Used to indicate items which are to be cut EntityUrlRole, ///< The akonadi:/ Url of the entity as a string. Item urls will contain the mimetype. UnreadCountRole, ///< Returns the number of unread items in a collection. @since 4.5 FetchStateRole, ///< Returns the FetchState of a particular item. @since 4.5 IsPopulatedRole, ///< Returns whether a Collection has been populated, i.e. whether its items have been fetched. @since 4.10 OriginalCollectionNameRole, ///< Returns original name for collection @since 4.14 UserRole = Qt::UserRole + 500, ///< First role for user extensions. TerminalUserRole = 2000, ///< Last role for user extensions. Don't use a role beyond this or headerData will break. EndRole = 65535 }; /** * Describes the state of fetch jobs related to particular collections. * * @code * QModelIndex collectionIndex = getIndex(); * if (collectionIndex.data(EntityTreeModel::FetchStateRole).toLongLong() == FetchingState) { * // There is a fetch underway * } else { * // There is no fetch underway. * } * @endcode * * @since 4.5 */ enum FetchState { IdleState, ///< There is no fetch of items in this collection in progress. FetchingState ///< There is a fetch of items in this collection in progress. // TODO: Change states for reporting of fetching payload parts of items. }; /** * Describes what header information the model shall return. */ enum HeaderGroup { EntityTreeHeaders, ///< Header information for a tree with collections and items CollectionTreeHeaders, ///< Header information for a collection-only tree ItemListHeaders, ///< Header information for a list of items UserHeaders = 10, ///< Last header information for submodel extensions EndHeaderGroup = 32 ///< Last headergroup role. Don't use a role beyond this or headerData will break. // Note that we're splitting up available roles for the header data hack and int(EndRole / TerminalUserRole) == 32 }; /** * Creates a new entity tree model. * * @param monitor The Monitor whose entities should be represented in the model. * @param parent The parent object. */ explicit EntityTreeModel(Monitor *monitor, QObject *parent = nullptr); /** * Destroys the entity tree model. */ ~EntityTreeModel() override; /** * Describes how the model should populated its items. */ enum ItemPopulationStrategy { NoItemPopulation, ///< Do not include items in the model. ImmediatePopulation, ///< Retrieve items immediately when their parent is in the model. This is the default. LazyPopulation ///< Fetch items only when requested (using canFetchMore/fetchMore) }; /** * Some Entities are hidden in the model, but exist for internal purposes, for example, custom object * directories in groupware resources. * They are hidden by default, but can be shown by setting @p show to true. * @param show enabled displaying of hidden entities if set as @c true * Most applications will not need to use this feature. */ void setShowSystemEntities(bool show); /** * Returns @c true if internal system entities are shown, and @c false otherwise. */ Q_REQUIRED_RESULT bool systemEntitiesShown() const; /** * Returns the currently used listfilter. * * @since 4.14 */ Q_REQUIRED_RESULT Akonadi::CollectionFetchScope::ListFilter listFilter() const; /** * Sets the currently used listfilter. * * @since 4.14 */ void setListFilter(Akonadi::CollectionFetchScope::ListFilter filter); /** * Monitors the specified collections and resets the model. * * @since 4.14 */ void setCollectionsMonitored(const Akonadi::Collection::List &collections); /** * Adds or removes a specific collection from the monitored set without resetting the model. * Only call this if you're monitoring specific collections (not mimetype/resources/items). * * @since 4.14 * @see setCollectionsMonitored() */ void setCollectionMonitored(const Akonadi::Collection &col, bool monitored = true); /** * References a collection and starts to monitor it. * * Use this to temporarily include a collection that is not enabled. * * @since 4.14 */ void setCollectionReferenced(const Akonadi::Collection &col, bool referenced = true); /** * Sets the item population @p strategy of the model. */ void setItemPopulationStrategy(ItemPopulationStrategy strategy); /** * Returns the item population strategy of the model. */ Q_REQUIRED_RESULT ItemPopulationStrategy itemPopulationStrategy() const; /** * Sets whether the root collection shall be provided by the model. * @param include enables root collection if set as @c true * @see setRootCollectionDisplayName() */ void setIncludeRootCollection(bool include); /** * Returns whether the root collection is provided by the model. */ Q_REQUIRED_RESULT bool includeRootCollection() const; /** * Sets the display @p name of the root collection of the model. * The default display name is "[*]". * @param name the name to display for the root collection * @note The display name for the root collection is only used if * the root collection has been included with setIncludeRootCollection(). */ void setRootCollectionDisplayName(const QString &name); /** * Returns the display name of the root collection. */ Q_REQUIRED_RESULT QString rootCollectionDisplayName() const; /** * Describes what collections shall be fetched by and represent in the model. */ enum CollectionFetchStrategy { FetchNoCollections, ///< Fetches nothing. This creates an empty model. FetchFirstLevelChildCollections, ///< Fetches first level collections in the root collection. FetchCollectionsRecursive, ///< Fetches collections in the root collection recursively. This is the default. InvisibleCollectionFetch ///< Fetches collections, but does not put them in the model. This can be used to create a list of items in all collections. The ParentCollectionRole can still be used to retrieve the parent collection of an Item. @since 4.5 }; /** * Sets the collection fetch @p strategy of the model. */ void setCollectionFetchStrategy(CollectionFetchStrategy strategy); /** * Returns the collection fetch strategy of the model. */ - CollectionFetchStrategy collectionFetchStrategy() const; + Q_REQUIRED_RESULT CollectionFetchStrategy collectionFetchStrategy() const; Q_REQUIRED_RESULT QHash roleNames() const override; Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; Q_REQUIRED_RESULT QStringList mimeTypes() const override; Q_REQUIRED_RESULT Qt::DropActions supportedDropActions() const override; Q_REQUIRED_RESULT QMimeData *mimeData(const QModelIndexList &indexes) const override; - Q_REQUIRED_RESULT bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - Q_REQUIRED_RESULT bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &index) const override; // TODO: Review the implementations of these. I think they could be better. Q_REQUIRED_RESULT bool canFetchMore(const QModelIndex &parent) const override; void fetchMore(const QModelIndex &parent) override; Q_REQUIRED_RESULT bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; /** * Returns whether the collection tree has been fetched at initialisation. * * @see collectionTreeFetched * @since 4.10 */ Q_REQUIRED_RESULT bool isCollectionTreeFetched() const; /** * Returns whether the collection has been populated. * * @see collectionPopulated * @since 4.12 */ Q_REQUIRED_RESULT bool isCollectionPopulated(Akonadi::Collection::Id) const; /** * Returns whether the model is fully populated. * * Returns true once the collection tree has been fetched and all collections have been populated. * * @see isCollectionPopulated * @see isCollectionTreeFetched * @since 4.14 */ Q_REQUIRED_RESULT bool isFullyPopulated() const; /** * Reimplemented to handle the AmazingCompletionRole. */ Q_REQUIRED_RESULT QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; /** * Returns a QModelIndex in @p model which points to @p collection. * This method can be used through proxy models if @p model is a proxy model. * @code * EntityTreeModel *model = getEntityTreeModel(); * QSortFilterProxyModel *proxy1 = new QSortFilterProxyModel; * proxy1->setSourceModel(model); * QSortFilterProxyModel *proxy2 = new QSortFilterProxyModel; * proxy2->setSourceModel(proxy1); * * ... * * QModelIndex idx = EntityTreeModel::modelIndexForCollection(proxy2, Collection(colId)); * if (!idx.isValid()) * // Collection with id colId is not in the proxy2. * // Maybe it is filtered out if proxy 2 is only showing items? Make sure you use the correct proxy. * return; * * Collection collection = idx.data( EntityTreeModel::CollectionRole ).value(); * // collection has the id colId, and all other attributes already fetched by the model such as name, remoteId, Akonadi::Attributes etc. * * @endcode * * This can be useful for example if an id is stored in a config file and needs to be used in the application. * * Note however, that to restore view state such as scrolling, selection and expansion of items in trees, the ETMViewStateSaver can be used for convenience. * * @see modelIndexesForItem * @since 4.5 */ static QModelIndex modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection); /** * Returns a QModelIndex in @p model which points to @p item. * This method can be used through proxy models if @p model is a proxy model. * @param model the model to query for the item * @param item the item to look for * @see modelIndexForCollection * @since 4.5 */ static QModelIndexList modelIndexesForItem(const QAbstractItemModel *model, const Item &item); /** * Returns an Akonadi::Collection from the @p model based on given @p collectionId. * * This is faster and simpler than retrieving a full Collection from the ETM * by using modelIndexForCollection() and then querying for the index data. */ static Collection updatedCollection(const QAbstractItemModel *model, qint64 collectionId); static Collection updatedCollection(const QAbstractItemModel *model, const Collection &col); Q_SIGNALS: /** * Signal emitted when the collection tree has been fetched for the first time. * @param collections list of collections which have been fetched * * @see isCollectionTreeFetched, collectionPopulated * @since 4.10 */ void collectionTreeFetched(const Akonadi::Collection::List &collections); /** * Signal emitted when a collection has been populated, i.e. its items have been fetched. * @param collectionId id of the collection which has been populated * * @see collectionTreeFetched * @since 4.10 */ void collectionPopulated(Akonadi::Collection::Id collectionId); /** * Emitted once a collection has been fetched for the very first time. * This is like a dataChanged(), but specific to the initial loading, in order to update * the GUI (window caption, state of actions). * Usually, the GUI uses Akonadi::Monitor to be notified of further changes to the collections. * @param collectionId the identifier of the fetched collection * @since 4.9.3 */ void collectionFetched(int collectionId); protected: /** * Clears and resets the model. Always call this instead of the reset method in the superclass. * Using the reset method will not reliably clear or refill the model. */ void clearAndReset(); /** * Provided for convenience of subclasses. */ virtual QVariant entityData(const Item &item, int column, int role = Qt::DisplayRole) const; /** * Provided for convenience of subclasses. */ virtual QVariant entityData(const Collection &collection, int column, int role = Qt::DisplayRole) const; /** * Reimplement this to provide different header data. This is needed when using one model * with multiple proxies and views, and each should show different header data. */ virtual QVariant entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const; virtual int entityColumnCount(HeaderGroup headerGroup) const; protected: //@cond PRIVATE Q_DECLARE_PRIVATE(EntityTreeModel) EntityTreeModelPrivate *d_ptr; EntityTreeModel(Monitor *monitor, EntityTreeModelPrivate *d, QObject *parent = nullptr); //@endcond private: //@cond PRIVATE // Make these private, they shouldn't be called by applications bool insertRows(int row, int count, const QModelIndex &index = QModelIndex()) override; bool insertColumns(int column, int count, const QModelIndex &index = QModelIndex()) override; bool removeColumns(int column, int count, const QModelIndex &index = QModelIndex()) override; bool removeRows(int row, int count, const QModelIndex &index = QModelIndex()) override; Q_PRIVATE_SLOT(d_func(), void monitoredCollectionStatisticsChanged(Akonadi::Collection::Id, const Akonadi::CollectionStatistics &)) Q_PRIVATE_SLOT(d_func(), void startFirstListJob()) Q_PRIVATE_SLOT(d_func(), void serverStarted()) Q_PRIVATE_SLOT(d_func(), void itemFetchJobDone(KJob *job)) Q_PRIVATE_SLOT(d_func(), void collectionFetchJobDone(KJob *job)) Q_PRIVATE_SLOT(d_func(), void rootFetchJobDone(KJob *job)) Q_PRIVATE_SLOT(d_func(), void pasteJobDone(KJob *job)) Q_PRIVATE_SLOT(d_func(), void updateJobDone(KJob *job)) Q_PRIVATE_SLOT(d_func(), void itemsFetched(Akonadi::Item::List)) Q_PRIVATE_SLOT(d_func(), void collectionsFetched(Akonadi::Collection::List)) Q_PRIVATE_SLOT(d_func(), void collectionListFetched(Akonadi::Collection::List)) Q_PRIVATE_SLOT(d_func(), void topLevelCollectionsFetched(Akonadi::Collection::List)) Q_PRIVATE_SLOT(d_func(), void ancestorsFetched(Akonadi::Collection::List)) Q_PRIVATE_SLOT(d_func(), void monitoredMimeTypeChanged(const QString &, bool)) Q_PRIVATE_SLOT(d_func(), void monitoredCollectionsChanged(const Akonadi::Collection &, bool)) Q_PRIVATE_SLOT(d_func(), void monitoredItemsChanged(const Akonadi::Item &, bool)) Q_PRIVATE_SLOT(d_func(), void monitoredResourcesChanged(const QByteArray &, bool)) Q_PRIVATE_SLOT(d_func(), void monitoredCollectionAdded(const Akonadi::Collection &, const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredCollectionRemoved(const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredCollectionChanged(const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredCollectionMoved(const Akonadi::Collection &, const Akonadi::Collection &, const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredItemAdded(const Akonadi::Item &, const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredItemRemoved(const Akonadi::Item &)) Q_PRIVATE_SLOT(d_func(), void monitoredItemChanged(const Akonadi::Item &, const QSet &)) Q_PRIVATE_SLOT(d_func(), void monitoredItemMoved(const Akonadi::Item &, const Akonadi::Collection &, const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredItemLinked(const Akonadi::Item &, const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void monitoredItemUnlinked(const Akonadi::Item &, const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void changeFetchState(const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void agentInstanceRemoved(Akonadi::AgentInstance)) Q_PRIVATE_SLOT(d_func(), void monitoredItemsRetrieved(KJob *job)) //@endcond }; } // namespace #endif diff --git a/src/core/servermanager.h b/src/core/servermanager.h index f7314141f..a8f505d99 100644 --- a/src/core/servermanager.h +++ b/src/core/servermanager.h @@ -1,244 +1,244 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SERVERMANAGER_H #define AKONADI_SERVERMANAGER_H #include "akonadicore_export.h" #include #include namespace Akonadi { class ServerManagerPrivate; /** * @short Provides methods to control the Akonadi server process. * * Asynchronous, low-level control of the Akonadi server. * Akonadi::Control provides a synchronous interface to some of the methods in here. * * @author Volker Krause * @see Akonadi::Control * @since 4.2 */ class AKONADICORE_EXPORT ServerManager : public QObject { Q_OBJECT public: /** * Enum for the various states the server can be in. * @since 4.5 */ enum State { NotRunning, ///< Server is not running, could be no one started it yet or it failed to start. Starting, ///< Server was started but is not yet running. Running, ///< Server is running and operational. Stopping, ///< Server is shutting down. Broken, ///< Server is not operational and an error has been detected. Upgrading ///< Server is performing a database upgrade as part of a new startup. }; /** * Starts the server. This method returns imediately and does not wait * until the server is actually up and running. * @return @c true if the start was possible (which not necessarily means * the server is really running though) and @c false if an immediate error occurred. * @see Akonadi::Control::start() */ - Q_REQUIRED_RESULT static bool start(); + static bool start(); /** * Stops the server. This methods returns immediately after the shutdown * command has been send and does not wait until the server is actually * shut down. * @return @c true if the shutdown command was sent successfully, @c false * otherwise */ - Q_REQUIRED_RESULT static bool stop(); + static bool stop(); /** * Shows the Akonadi self test dialog, which tests Akonadi for various problems * and reports these to the user if. * @param parent the parent widget for the dialog */ static void showSelfTestDialog(QWidget *parent); /** * Checks if the server is available currently. For more detailed status information * see state(). * @see state() */ Q_REQUIRED_RESULT static bool isRunning(); /** * Returns the state of the server. * @since 4.5 */ Q_REQUIRED_RESULT static State state(); /** * Returns the reason why the Server is broken, if known. * * If state() is @p Broken, then you can use this method to obtain a more * detailed description of the problem and present it to users. Note that * the message can be empty if the reason is not known. * * @since 5.6 */ Q_REQUIRED_RESULT static QString brokenReason(); /** * Returns the identifier of the Akonadi instance we are connected to. This is usually * an empty string (representing the default instance), unless you have explicitly set * the AKONADI_INSTANCE environment variable to connect to a different one. * @since 4.10 */ Q_REQUIRED_RESULT static QString instanceIdentifier(); /** * Returns @c true if we are connected to a non-default Akonadi server instance. * @since 4.10 */ Q_REQUIRED_RESULT static bool hasInstanceIdentifier(); /** * Types of known D-Bus services. * @since 4.10 */ enum ServiceType { Server, Control, ControlLock, UpgradeIndicator }; /** * Returns the namespaced D-Bus service name for @p serviceType. * Use this rather the raw service name strings in order to support usage of a non-default * instance of the Akonadi server. * @param serviceType the service type for which to return the D-Bus name * @since 4.10 */ static QString serviceName(ServiceType serviceType); /** * Known agent types. * @since 4.10 */ enum ServiceAgentType { Agent, Resource, Preprocessor }; /** * Returns the namespaced D-Bus service name for an agent of type @p agentType with agent * identifier @p identifier. * @param agentType the agent type to use for D-Bus base name * @param identifier the agent identifier to include in the D-Bus name * @since 4.10 */ Q_REQUIRED_RESULT static QString agentServiceName(ServiceAgentType agentType, const QString &identifier); /** * Adds the multi-instance namespace to @p string if required (with '_' as separator). * Use whenever a multi-instance safe name is required (configfiles, identifiers, ...). * @param string the string to adapt * @since 4.10 */ Q_REQUIRED_RESULT static QString addNamespace(const QString &string); /** * Returns the singleton instance of this class, for connecting to its * signals */ static ServerManager *self(); enum OpenMode { ReadOnly, ReadWrite }; /** * Returns absolute path to akonadiserverrc file with Akonadi server * configuration. */ Q_REQUIRED_RESULT static QString serverConfigFilePath(OpenMode openMode); /** * Returns absolute path to configuration file of an agent identified by * given @p identifier. */ Q_REQUIRED_RESULT static QString agentConfigFilePath(const QString &identifier); /** * Returns current Akonadi database generation identifier * * Generation is guaranteed to never change unless as long as the database * backend is not removed and re-created. In such case it is guaranteed that * the new generation number will be higher than the previous one. * * Generation can be used by applications to detect when Akonadi database * has been recreated and thus some of the configuration (for example * collection IDs stored in a config file) must be invalidated. * * @note Note that the generation number is only available if the server * is running. If this function is called before the server starts it will * return 0. * * @since 5.4 */ Q_REQUIRED_RESULT static uint generation(); Q_SIGNALS: /** * Emitted whenever the server becomes fully operational. */ void started(); /** * Emitted whenever the server becomes unavailable. */ void stopped(); /** * Emitted whenever the server state changes. * @param state the new server state * @since 4.5 */ void stateChanged(Akonadi::ServerManager::State state); private: //@cond PRIVATE friend class ServerManagerPrivate; ServerManager(ServerManagerPrivate *dd); ServerManagerPrivate *const d; Q_PRIVATE_SLOT(d, void serviceOwnerChanged(const QString &, const QString &, const QString &)) Q_PRIVATE_SLOT(d, void checkStatusChanged()) Q_PRIVATE_SLOT(d, void timeout()) //@endcond }; } Q_DECLARE_METATYPE(Akonadi::ServerManager::State) #endif diff --git a/src/core/specialcollections.h b/src/core/specialcollections.h index 63a6f061d..80d60b938 100644 --- a/src/core/specialcollections.h +++ b/src/core/specialcollections.h @@ -1,174 +1,174 @@ /* Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SPECIALCOLLECTIONS_H #define AKONADI_SPECIALCOLLECTIONS_H #include "akonadicore_export.h" #include "collection.h" #include "item.h" #include class KCoreConfigSkeleton; class KJob; namespace Akonadi { class AgentInstance; class SpecialCollectionsPrivate; /** @short An interface to special collections. This class is the central interface to special collections like inbox or outbox in a mail resource or recent contacts in a contacts resource. The class is not meant to be used directly, but to inherit the a type specific special collections class from it (e.g. SpecialMailCollections). To check whether a special collection is available, simply use the hasCollection() and hasDefaultCollection() methods. Available special collections are accessible through the collection() and defaultCollection() methods. To create a special collection, use a SpecialCollectionsRequestJob. This will create the special collections you request and automatically register them with SpecialCollections, so that it now knows they are available. This class monitors all special collections known to it, and removes it from the known list if they are deleted. Note that this class does not automatically rebuild the collections that disappeared. The defaultCollectionsChanged() and collectionsChanged() signals are emitted when the special collections for a resource change (i.e. some became available or some become unavailable). @author Constantin Berzan @since 4.4 */ class AKONADICORE_EXPORT SpecialCollections : public QObject { Q_OBJECT public: /** * Destroys the special collections object. */ ~SpecialCollections(); /** * Returns whether the given agent @p instance has a special collection of * the given @p type. */ Q_REQUIRED_RESULT bool hasCollection(const QByteArray &type, const AgentInstance &instance) const; /** * Returns the special collection of the given @p type in the given agent * @p instance, or an invalid collection if such a collection is unknown. */ Q_REQUIRED_RESULT Akonadi::Collection collection(const QByteArray &type, const AgentInstance &instance) const; /** * Registers the given @p collection as a special collection * with the given @p type. * @param type the special type of @c collection * @param collection the given collection to register * The collection must be owned by a valid resource. * Registering a new collection of a previously registered type forgets the * old collection. */ - Q_REQUIRED_RESULT bool registerCollection(const QByteArray &type, const Akonadi::Collection &collection); + bool registerCollection(const QByteArray &type, const Akonadi::Collection &collection); /** * Unregisters the given @p collection as a special collection. * @param type the special type of @c collection * @since 4.12 */ - Q_REQUIRED_RESULT bool unregisterCollection(const Collection &collection); + bool unregisterCollection(const Collection &collection); /** * unsets the special collection attribute which marks @p collection as being a special * collection. * @since 4.12 */ static void unsetSpecialCollection(const Akonadi::Collection &collection); /** * Sets the special collection attribute which marks @p collection as being a special * collection of type @p type. * This is typically used by configuration dialog for resources, when the user can choose * a specific special collection (ex: IMAP trash). * * @since 4.11 */ static void setSpecialCollectionType(const QByteArray &type, const Akonadi::Collection &collection); /** * Returns whether the default resource has a special collection of * the given @p type. */ Q_REQUIRED_RESULT bool hasDefaultCollection(const QByteArray &type) const; /** * Returns the special collection of given @p type in the default * resource, or an invalid collection if such a collection is unknown. */ Q_REQUIRED_RESULT Akonadi::Collection defaultCollection(const QByteArray &type) const; Q_SIGNALS: /** * Emitted when the special collections for a resource have been changed * (for example, some become available, or some become unavailable). * * @param instance The instance of the resource the collection belongs to. */ void collectionsChanged(const Akonadi::AgentInstance &instance); /** * Emitted when the special collections for the default resource have * been changed (for example, some become available, or some become unavailable). */ void defaultCollectionsChanged(); protected: /** * Creates a new special collections object. * * @param config The configuration skeleton that provides the default resource id. * @param parent The parent object. */ explicit SpecialCollections(KCoreConfigSkeleton *config, QObject *parent = nullptr); private: //@cond PRIVATE friend class SpecialCollectionsRequestJob; friend class SpecialCollectionsRequestJobPrivate; friend class SpecialCollectionsPrivate; #ifdef BUILD_TESTING friend class SpecialMailCollectionsTesting; friend class LocalFoldersTest; #endif SpecialCollectionsPrivate *const d; //@endcond }; } // namespace Akonadi #endif // AKONADI_SPECIALCOLLECTIONS_H diff --git a/src/server/aggregatedfetchscope.cpp b/src/server/aggregatedfetchscope.cpp index ac485ca63..e2c8be358 100644 --- a/src/server/aggregatedfetchscope.cpp +++ b/src/server/aggregatedfetchscope.cpp @@ -1,603 +1,602 @@ /* Copyright (c) 2017 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "aggregatedfetchscope.h" #include #include #include #include #define LOCKED_D(name) \ Q_D(name); \ QMutexLocker lock(&d->lock); namespace Akonadi { namespace Server { class AggregatedFetchScopePrivate { public: AggregatedFetchScopePrivate() : lock(QMutex::Recursive) // recursive so that we can call our own getters/setters {} inline void addToSet(const QByteArray &value, QSet &set, QHash &count) { auto it = count.find(value); if (it == count.end()) { it = count.insert(value, 0); set.insert(value); } ++(*it); } inline void removeFromSet(const QByteArray &value, QSet &set, QHash &count) { auto it = count.find(value); if (it == count.end()) { return; } if (--(*it) == 0) { count.erase(it); set.remove(value); } } inline void updateBool(bool newValue, int &store) { store += newValue ? 1 : -1; } inline void applySet(const QSet &oldSet, const QSet &newSet, QSet &set, QHash &count) { const auto added = newSet - oldSet; for (const auto &value : added) { addToSet(value, set, count); } const auto removed = oldSet - newSet; for (const auto &value : removed) { removeFromSet(value, set, count); } } public: mutable QMutex lock; }; class AggregatedCollectionFetchScopePrivate : public AggregatedFetchScopePrivate { public: QSet attrs; QHash attrsCount; int fetchIdOnly = 0; int fetchStats = 0; }; class AggregatedTagFetchScopePrivate : public AggregatedFetchScopePrivate { public: QSet attrs; QHash attrsCount; int fetchIdOnly = 0; }; class AggregatedItemFetchScopePrivate : public AggregatedFetchScopePrivate { public: mutable Protocol::ItemFetchScope mCachedScope; mutable bool mCachedScopeValid = false; // use std::optional for mCachedScope QSet parts; QHash partsCount; QSet tags; QHash tagsCount; int ancestors[3] = { 0, 0, 0 }; // 3 = size of AncestorDepth enum int cacheOnly = 0; int fullPayload = 0; int allAttributes = 0; int fetchSize = 0; int fetchMTime = 0; int fetchRRev = 0; int ignoreErrors = 0; int fetchFlags = 0; int fetchRID = 0; int fetchGID = 0; int fetchTags = 0; int fetchRelations = 0; int fetchVRefs = 0; }; } // namespace Server } // namespace Akonadi using namespace Akonadi; using namespace Akonadi::Protocol; using namespace Akonadi::Server; AggregatedCollectionFetchScope::AggregatedCollectionFetchScope() : d_ptr(new AggregatedCollectionFetchScopePrivate) { } AggregatedCollectionFetchScope::~AggregatedCollectionFetchScope() { delete d_ptr; } void AggregatedCollectionFetchScope::apply(const Protocol::CollectionFetchScope &oldScope, const Protocol::CollectionFetchScope &newScope) { LOCKED_D(AggregatedCollectionFetchScope) if (newScope.includeStatistics() != oldScope.includeStatistics()) { setFetchStatistics(newScope.includeStatistics()); } if (newScope.fetchIdOnly() != oldScope.fetchIdOnly()) { setFetchIdOnly(newScope.fetchIdOnly()); } if (newScope.attributes() != oldScope.attributes()) { d->applySet(oldScope.attributes(), newScope.attributes(), d->attrs, d->attrsCount); } } QSet AggregatedCollectionFetchScope::attributes() const { LOCKED_D(const AggregatedCollectionFetchScope) return d->attrs; } void AggregatedCollectionFetchScope::addAttribute(const QByteArray &attribute) { LOCKED_D(AggregatedCollectionFetchScope) d->addToSet(attribute, d->attrs, d->attrsCount); } void AggregatedCollectionFetchScope::removeAttribute(const QByteArray &attribute) { LOCKED_D(AggregatedCollectionFetchScope) d->removeFromSet(attribute, d->attrs, d->attrsCount); } bool AggregatedCollectionFetchScope::fetchIdOnly() const { LOCKED_D(const AggregatedCollectionFetchScope) // Aggregation: we can return true only if everyone wants fetchIdOnly, // otherwise there's at least one subscriber who wants everything return d->fetchIdOnly == 0; } void AggregatedCollectionFetchScope::setFetchIdOnly(bool fetchIdOnly) { LOCKED_D(AggregatedCollectionFetchScope) d->updateBool(fetchIdOnly, d->fetchIdOnly); } bool AggregatedCollectionFetchScope::fetchStatistics() const { LOCKED_D(const AggregatedCollectionFetchScope); // Aggregation: return true if at least one subscriber wants stats return d->fetchStats > 0; } void AggregatedCollectionFetchScope::setFetchStatistics(bool fetchStats) { LOCKED_D(AggregatedCollectionFetchScope); d->updateBool(fetchStats, d->fetchStats); } AggregatedItemFetchScope::AggregatedItemFetchScope() : d_ptr(new AggregatedItemFetchScopePrivate) { } AggregatedItemFetchScope::~AggregatedItemFetchScope() { delete d_ptr; } void AggregatedItemFetchScope::apply(const Protocol::ItemFetchScope &oldScope, const Protocol::ItemFetchScope &newScope) { LOCKED_D(AggregatedItemFetchScope); const auto newParts = vectorToSet(newScope.requestedParts()); const auto oldParts = vectorToSet(oldScope.requestedParts()); if (newParts != oldParts) { d->applySet(oldParts, newParts, d->parts, d->partsCount); } if (newScope.tagFetchScope() != oldScope.tagFetchScope()) { d->applySet(oldScope.tagFetchScope(), newScope.tagFetchScope(), d->tags, d->tagsCount); } if (newScope.ancestorDepth() != oldScope.ancestorDepth()) { updateAncestorDepth(oldScope.ancestorDepth(), newScope.ancestorDepth()); } if (newScope.cacheOnly() != oldScope.cacheOnly()) { setCacheOnly(newScope.cacheOnly()); } if (newScope.fullPayload() != oldScope.fullPayload()) { setFullPayload(newScope.fullPayload()); } if (newScope.allAttributes() != oldScope.allAttributes()) { setAllAttributes(newScope.allAttributes()); } if (newScope.fetchSize() != oldScope.fetchSize()) { setFetchSize(newScope.fetchSize()); } if (newScope.fetchMTime() != oldScope.fetchMTime()) { setFetchMTime(newScope.fetchMTime()); } if (newScope.fetchRemoteRevision() != oldScope.fetchRemoteRevision()) { setFetchRemoteRevision(newScope.fetchRemoteRevision()); } if (newScope.ignoreErrors() != oldScope.ignoreErrors()) { setIgnoreErrors(newScope.ignoreErrors()); } if (newScope.fetchFlags() != oldScope.fetchFlags()) { setFetchFlags(newScope.fetchFlags()); } if (newScope.fetchRemoteId() != oldScope.fetchRemoteId()) { setFetchRemoteId(newScope.fetchRemoteId()); } if (newScope.fetchGID() != oldScope.fetchGID()) { setFetchGID(newScope.fetchGID()); } if (newScope.fetchTags() != oldScope.fetchTags()) { setFetchTags(newScope.fetchTags()); } if (newScope.fetchRelations() != oldScope.fetchRelations()) { setFetchRelations(newScope.fetchRelations()); } if (newScope.fetchVirtualReferences() != oldScope.fetchVirtualReferences()) { setFetchVirtualReferences(newScope.fetchVirtualReferences()); } d->mCachedScopeValid = false; } ItemFetchScope AggregatedItemFetchScope::toFetchScope() const { LOCKED_D(const AggregatedItemFetchScope); if (d->mCachedScopeValid) { return d->mCachedScope; } d->mCachedScope = ItemFetchScope(); d->mCachedScope.setRequestedParts(setToVector(d->parts)); d->mCachedScope.setTagFetchScope(d->tags); d->mCachedScope.setAncestorDepth(ancestorDepth()); - ItemFetchScope::FetchFlags flags; d->mCachedScope.setFetch(ItemFetchScope::CacheOnly, cacheOnly()); d->mCachedScope.setFetch(ItemFetchScope::FullPayload, fullPayload()); d->mCachedScope.setFetch(ItemFetchScope::AllAttributes, allAttributes()); d->mCachedScope.setFetch(ItemFetchScope::Size, fetchSize()); d->mCachedScope.setFetch(ItemFetchScope::MTime, fetchMTime()); d->mCachedScope.setFetch(ItemFetchScope::RemoteRevision, fetchRemoteRevision()); d->mCachedScope.setFetch(ItemFetchScope::IgnoreErrors, ignoreErrors()); d->mCachedScope.setFetch(ItemFetchScope::Flags, fetchFlags()); d->mCachedScope.setFetch(ItemFetchScope::RemoteID, fetchRemoteId()); d->mCachedScope.setFetch(ItemFetchScope::GID, fetchGID()); d->mCachedScope.setFetch(ItemFetchScope::Tags, fetchTags()); d->mCachedScope.setFetch(ItemFetchScope::Relations, fetchRelations()); d->mCachedScope.setFetch(ItemFetchScope::VirtReferences, fetchVirtualReferences()); d->mCachedScopeValid = true; return d->mCachedScope; } QSet AggregatedItemFetchScope::requestedParts() const { LOCKED_D(const AggregatedItemFetchScope) return d->parts; } void AggregatedItemFetchScope::addRequestedPart(const QByteArray &part) { LOCKED_D(AggregatedItemFetchScope) d->addToSet(part, d->parts, d->partsCount); } void AggregatedItemFetchScope::removeRequestedPart(const QByteArray &part) { LOCKED_D(AggregatedItemFetchScope) d->removeFromSet(part, d->parts, d->partsCount); } QSet AggregatedItemFetchScope::tagFetchScope() const { LOCKED_D(const AggregatedItemFetchScope) return d->tags; } void AggregatedItemFetchScope::addTag(const QByteArray &tag) { LOCKED_D(AggregatedItemFetchScope) d->addToSet(tag, d->tags, d->tagsCount); } void AggregatedItemFetchScope::removeTag(const QByteArray &tag) { LOCKED_D(AggregatedItemFetchScope) d->removeFromSet(tag, d->tags, d->tagsCount); } ItemFetchScope::AncestorDepth AggregatedItemFetchScope::ancestorDepth() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return the largest depth with at least one subscriber if (d->ancestors[ItemFetchScope::AllAncestors] > 0) { return ItemFetchScope::AllAncestors; } else if (d->ancestors[ItemFetchScope::ParentAncestor] > 0) { return ItemFetchScope::ParentAncestor; } else { return ItemFetchScope::NoAncestor; } } void AggregatedItemFetchScope::updateAncestorDepth(ItemFetchScope::AncestorDepth oldDepth, ItemFetchScope::AncestorDepth newDepth) { LOCKED_D(AggregatedItemFetchScope) if (d->ancestors[oldDepth] > 0) { --d->ancestors[oldDepth]; } ++d->ancestors[newDepth]; } bool AggregatedItemFetchScope::cacheOnly() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: we can return true only if everyone wants cached data only, // otherwise there's at least one subscriber who wants uncached data return d->cacheOnly == 0; } void AggregatedItemFetchScope::setCacheOnly(bool cacheOnly) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(cacheOnly, d->cacheOnly); } bool AggregatedItemFetchScope::fullPayload() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants the // full payload return d->fullPayload > 0; } void AggregatedItemFetchScope::setFullPayload(bool fullPayload) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fullPayload, d->fullPayload); } bool AggregatedItemFetchScope::allAttributes() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants // all attributes return d->allAttributes > 0; } void AggregatedItemFetchScope::setAllAttributes(bool allAttributes) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(allAttributes, d->allAttributes); } bool AggregatedItemFetchScope::fetchSize() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants size return d->fetchSize > 0; } void AggregatedItemFetchScope::setFetchSize(bool fetchSize) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchSize, d->fetchSize); } bool AggregatedItemFetchScope::fetchMTime() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants mtime return d->fetchMTime > 0; } void AggregatedItemFetchScope::setFetchMTime(bool fetchMTime) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchMTime, d->fetchMTime); } bool AggregatedItemFetchScope::fetchRemoteRevision() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants rrev return d->fetchRRev > 0; } void AggregatedItemFetchScope::setFetchRemoteRevision(bool remoteRevision) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(remoteRevision, d->fetchRRev); } bool AggregatedItemFetchScope::ignoreErrors() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true only if everyone wants to ignore errors, otherwise // there's at least one subscriber who does not want to ignore them return d->ignoreErrors == 0; } void AggregatedItemFetchScope::setIgnoreErrors(bool ignoreErrors) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(ignoreErrors, d->ignoreErrors); } bool AggregatedItemFetchScope::fetchFlags() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants flags return d->fetchFlags > 0; } void AggregatedItemFetchScope::setFetchFlags(bool fetchFlags) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchFlags, d->fetchFlags); } bool AggregatedItemFetchScope::fetchRemoteId() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants RID return d->fetchRID > 0; } void AggregatedItemFetchScope::setFetchRemoteId(bool fetchRemoteId) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchRemoteId, d->fetchRID); } bool AggregatedItemFetchScope::fetchGID() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants GID return d->fetchGID > 0; } void AggregatedItemFetchScope::setFetchGID(bool fetchGid) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchGid, d->fetchGID); } bool AggregatedItemFetchScope::fetchTags() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants tags return d->fetchTags > 0; } void AggregatedItemFetchScope::setFetchTags(bool fetchTags) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchTags, d->fetchTags); } bool AggregatedItemFetchScope::fetchRelations() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants relations return d->fetchRelations > 0; } void AggregatedItemFetchScope::setFetchRelations(bool fetchRelations) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchRelations, d->fetchRelations); } bool AggregatedItemFetchScope::fetchVirtualReferences() const { LOCKED_D(const AggregatedItemFetchScope) // Aggregation: return true if there's at least one subscriber who wants vrefs return d->fetchVRefs > 0; } void AggregatedItemFetchScope::setFetchVirtualReferences(bool fetchVRefs) { LOCKED_D(AggregatedItemFetchScope) d->updateBool(fetchVRefs, d->fetchVRefs); } AggregatedTagFetchScope::AggregatedTagFetchScope() : d_ptr(new AggregatedTagFetchScopePrivate) { } AggregatedTagFetchScope::~AggregatedTagFetchScope() { delete d_ptr; } void AggregatedTagFetchScope::apply(const Protocol::TagFetchScope &oldScope, const Protocol::TagFetchScope &newScope) { LOCKED_D(AggregatedTagFetchScope) if (newScope.fetchIdOnly() != oldScope.fetchIdOnly()) { setFetchIdOnly(newScope.fetchIdOnly()); } if (newScope.attributes() != oldScope.attributes()) { d->applySet(oldScope.attributes(), newScope.attributes(), d->attrs, d->attrsCount); } } bool AggregatedTagFetchScope::fetchIdOnly() const { LOCKED_D(const AggregatedTagFetchScope) // Aggregation: we can return true only if everyone wants fetchIdOnly, // otherwise there's at least one subscriber who wants everything return d->fetchIdOnly == 0; } void AggregatedTagFetchScope::setFetchIdOnly(bool fetchIdOnly) { LOCKED_D(AggregatedTagFetchScope) d->updateBool(fetchIdOnly, d->fetchIdOnly); } QSet AggregatedTagFetchScope::attributes() const { LOCKED_D(const AggregatedTagFetchScope) return d->attrs; } void AggregatedTagFetchScope::addAttribute(const QByteArray &attribute) { LOCKED_D(AggregatedTagFetchScope) d->addToSet(attribute, d->attrs, d->attrsCount); } void AggregatedTagFetchScope::removeAttribute(const QByteArray &attribute) { LOCKED_D(AggregatedTagFetchScope) d->removeFromSet(attribute, d->attrs, d->attrsCount); } #undef LOCKED_D diff --git a/src/server/handler/akappend.h b/src/server/handler/akappend.h index 81931ded7..4a2b37c62 100644 --- a/src/server/handler/akappend.h +++ b/src/server/handler/akappend.h @@ -1,68 +1,70 @@ /*************************************************************************** * Copyright (C) 2007 by Robert Zwerus * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_AKAPPEND_H #define AKONADI_AKAPPEND_H #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the X-AKAPPEND command. This command is used to append an item with multiple parts. */ class AkAppend : public Handler { Q_OBJECT public: + ~AkAppend() override = default; + bool parseStream() override; private: bool buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, Collection &parentCollection); bool insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, const Collection &parentCollection); bool mergeItem(const Protocol::CreateItemCommand &cmd, PimItem &newItem, PimItem ¤tItem, const Collection &parentCollection); bool sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes); bool notify(const PimItem &item, bool seen, const Collection &collection); bool notify(const PimItem &item, const Collection &collection, const QSet &changedParts); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/colcopy.h b/src/server/handler/colcopy.h index 55b1ec78b..38ac352ea 100644 --- a/src/server/handler/colcopy.h +++ b/src/server/handler/colcopy.h @@ -1,62 +1,64 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_COLCOPY_H #define AKONADI_COLCOPY_H #include "handler/copy.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COLCOPY command. This command is used to copy a single collection into another collection, including all sub-collections and their content. The copied items differ in the following points from the originals: - new unique id - empty remote id - possible located in a different collection (and thus resource) The copied collections differ in the following points from the originals: - new unique id - empty remote id - owning resource is the same as the one of the target collection */ class ColCopy : public Copy { Q_OBJECT public: + ~ColCopy() override = default; + bool parseStream() override; private: bool copyCollection(const Collection &source, const Collection &target); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/colmove.h b/src/server/handler/colmove.h index a1c990379..b27d35443 100644 --- a/src/server/handler/colmove.h +++ b/src/server/handler/colmove.h @@ -1,48 +1,50 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_COLMOVE_H #define AKONADI_COLMOVE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COLMOVE command. This command is used to move a set of collections into another collection, including all sub-collections and their content. */ class ColMove : public Handler { Q_OBJECT public: + ~ColMove() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/copy.h b/src/server/handler/copy.h index bd281d294..b91ccda1f 100644 --- a/src/server/handler/copy.h +++ b/src/server/handler/copy.h @@ -1,78 +1,79 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_COPY_H #define AKONADI_COPY_H #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COPY command. This command is used to copy a set of items into the specific collection. It is syntactically identical to the IMAP COPY command. The copied items differ in the following points from the originals: - new unique id - empty remote id - possible located in a different collection (and thus resource)

Syntax

Request: @verbatim request = tag " COPY " sequence-set " " collection-id @endverbatim There is only the usual status response indicating success or failure of the COPY command */ class Copy : public Handler { Q_OBJECT public: + ~Copy() override = default; bool parseStream() override; protected: /** Copy the given item and all its parts into the @p target. The changes mentioned above are applied. */ bool copyItem(const PimItem &item, const Collection &target); private Q_SLOTS: void itemsRetrieved(const QList &ids); private: Collection mTargetCollection; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/create.h b/src/server/handler/create.h index 2dba1c800..52fc03cd9 100644 --- a/src/server/handler/create.h +++ b/src/server/handler/create.h @@ -1,48 +1,50 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADICREATE_H #define AKONADICREATE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the CREATE command. CREATE is backward-compatible with RFC 3051, except recursive collection creation. Response: A untagged response identical to AkList is sent for every created collection. */ class Create : public Handler { Q_OBJECT public: + ~Create() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/delete.h b/src/server/handler/delete.h index 8a4156f5d..df43de00c 100644 --- a/src/server/handler/delete.h +++ b/src/server/handler/delete.h @@ -1,54 +1,56 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_DELETE_H #define AKONADI_DELETE_H #include "handler.h" namespace Akonadi { namespace Server { class Collection; /** @ingroup akonadi_server_handler Handler for the collection deletion command. This commands deletes the selected collections including all their content and that of any child collection. */ class Delete : public Handler { Q_OBJECT public: + ~Delete() override = default; + bool parseStream() override; private: bool deleteRecursive(Collection &col); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/fetch.h b/src/server/handler/fetch.h index a355568af..eb94660b8 100644 --- a/src/server/handler/fetch.h +++ b/src/server/handler/fetch.h @@ -1,45 +1,47 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIFETCH_H #define AKONADIFETCH_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the fetch command. */ class Fetch : public Handler { Q_OBJECT public: + ~Fetch() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/fetchhelper.h b/src/server/handler/fetchhelper.h index e06a04198..f3cce0eaa 100644 --- a/src/server/handler/fetchhelper.h +++ b/src/server/handler/fetchhelper.h @@ -1,106 +1,107 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_FETCHHELPER_H #define AKONADI_FETCHHELPER_H #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include "commandcontext.h" #include #include #include class FetchHelperTest; namespace Akonadi { namespace Server { class AggregatedItemFetchScope; class Connection; class FetchHelper : public QObject { Q_OBJECT public: class ResponseCollectorInterface { public: virtual ~ResponseCollectorInterface() {}; virtual void addResponse(const Protocol::CommandPtr &response) = 0; }; FetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); FetchHelper(ResponseCollectorInterface *collector, Connection *connection, CommandContext *context, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); + ~FetchHelper() override = default; bool fetchItems(); private: enum ItemQueryColumns { ItemQueryPimItemIdColumn, ItemQueryPimItemRidColumn, ItemQueryMimeTypeIdColumn, ItemQueryRevColumn, ItemQueryRemoteRevisionColumn, ItemQuerySizeColumn, ItemQueryDatetimeColumn, ItemQueryCollectionIdColumn, ItemQueryPimItemGidColumn, ItemQueryColumnCount }; void updateItemAccessTime(); void triggerOnDemandFetch(); QSqlQuery buildItemQuery(); QSqlQuery buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs); QSqlQuery buildFlagQuery(); QSqlQuery buildTagQuery(); QSqlQuery buildVRefQuery(); QVector ancestorsForItem(Collection::Id parentColId); static bool needsAccessTimeUpdate(const QVector &parts); QVariant extractQueryResult(const QSqlQuery &query, ItemQueryColumns column) const; bool isScopeLocal(const Scope &scope); DataStore *storageBackend() const; static QByteArray tagsToByteArray(const Tag::List &tags); static QByteArray relationsToByteArray(const Relation::List &relations); private: ResponseCollectorInterface *mCollector = nullptr; Connection *mConnection = nullptr; CommandContext *mContext = nullptr; QHash> mAncestorCache; Scope mScope; Protocol::ItemFetchScope mFetchScope; int mItemQueryColumnMap[ItemQueryColumnCount]; friend class ::FetchHelperTest; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/link.h b/src/server/handler/link.h index d65a77919..d8ceb1559 100644 --- a/src/server/handler/link.h +++ b/src/server/handler/link.h @@ -1,48 +1,50 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_LINK_H #define AKONADI_LINK_H #include "handler.h" namespace Akonadi { namespace Server { /** * @ingroup akonadi_server_handler * * Handler for the LINK and UNLINK commands. * * These commands are used to add and remove references of a set of items to a * virtual collection. */ class Link : public Handler { Q_OBJECT public: + ~Link() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/list.h b/src/server/handler/list.h index bddbe5a00..03885adad 100644 --- a/src/server/handler/list.h +++ b/src/server/handler/list.h @@ -1,127 +1,128 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_LIST_H #define AKONADI_LIST_H #include "entities.h" #include "handler.h" template class QStack; namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the LIST command. This command is used to get a (limited) listing of the available collections. It is different from the LIST command and is more similar to FETCH.

Syntax

Request: @verbatim request = tag " " command " " collection-id " " depth " (" filter-list ")" " (" option-list ")" command = "LIST" | "LSUB" | "RID LIST" | "RID LSUB" depth = number | "INF" filter-list = *(filter-key " " filter-value) filter-key = "RESOURCE" | "MIMETYPE" | "ENABLED" | "SYNC" | "DISPLAY" | "INDEX" option-list = *(option-key " " option-value) option-key = "STATISTICS" @endverbatim @c LIST will include all known collections, @c LSUB only those that are subscribed or contains subscribed collections (cf. RFC 3501, LIST vs. LSUB). The @c RID command prefix indicates that @c collection-id is a remote identifier instead of a unique identifier. In this case a resource context has to be specified previously using the @c RESSELECT command. @c depths chooses between recursive (@c INF), flat (1) and local (0, ie. just the base collection) listing, 0 indicates the root collection. The @c filter-list is used to restrict the listing to collection of a specific resource or content type. The @c option-list allows to specify the response content to some extend: - @c STATISTICS (boolean) allows to include the collection statistics (see Status) - @c ANCESTORDEPTH (numeric) allows you to specify the number of ancestor nodes that should be included additionally to the @c parent-id included anyway. Possible values are @c 0 (the default), @c 1 for the direct parent node and @c INF for all, terminating with the root collection. Response: @verbatim response = "*" collection-id " " parent-id " ("attribute-list")" attribute-list = *(attribute-identifier " " attribute-value) attribute-identifier = "NAME" | "MIMETYPE" | "REMOTEID" | "REMOTEREVISION" | "RESOURCE" | "VIRTUAL" | "MESSAGES" | "UNSEEN" | "SIZE" | "ANCESTORS" | "custom-attr-identifier @endverbatim The name is encoded as an quoted UTF-8 string. There is no order defined for the single responses. The ancestors property is encoded as a list of UID/RID pairs. */ class List : public Handler { Q_OBJECT public: List(); + ~List() override = default; bool parseStream() override; private: void listCollection(const Collection &root, const QStack &ancestors, const QStringList &mimeTypes, const CollectionAttribute::List &attributes); QStack ancestorsForCollection(const Collection &col); void retrieveCollections(const Collection &topParent, int depth); bool checkFilterCondition(const Collection &col) const; bool checkChildrenForMimeTypes(const QHash &collectionsMap, const QHash &parentMap, const Collection &col); CollectionAttribute::List getAttributes(const Collection &colId, const QSet &filter = QSet()); void retrieveAttributes(const QVariantList &collectionIds); Resource mResource; QVector mMimeTypes; int mAncestorDepth; bool mIncludeStatistics; bool mEnabledCollections; bool mCollectionsToDisplay; bool mCollectionsToSynchronize; bool mCollectionsToIndex; QSet mAncestorAttributes; QMap mCollections; QHash mAncestors; QMultiHash mCollectionAttributes; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/login.h b/src/server/handler/login.h index c3a7d6dc5..12f2a0814 100644 --- a/src/server/handler/login.h +++ b/src/server/handler/login.h @@ -1,44 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADILOGIN_H #define AKONADILOGIN_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the login command. */ class Login : public Handler { Q_OBJECT public: + ~Login() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/logout.h b/src/server/handler/logout.h index a08aa0ab3..6c0558233 100644 --- a/src/server/handler/logout.h +++ b/src/server/handler/logout.h @@ -1,45 +1,47 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADILOGOUT_H #define AKONADILOGOUT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the logout command. */ class Logout : public Handler { Q_OBJECT public: + ~Logout() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/modify.h b/src/server/handler/modify.h index 0a1d8b61f..b23a16355 100644 --- a/src/server/handler/modify.h +++ b/src/server/handler/modify.h @@ -1,48 +1,50 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_MODIFY_H #define AKONADI_MODIFY_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the MODIFY command (not in RFC 3501). This command is used to modify collections. Its syntax is similar to the STORE command. */ class Modify : public Handler { Q_OBJECT public: + ~Modify() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/move.h b/src/server/handler/move.h index d044a4fcc..97bad3558 100644 --- a/src/server/handler/move.h +++ b/src/server/handler/move.h @@ -1,60 +1,62 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_MOVE_H #define AKONADI_MOVE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item move command.

Semantics

Moves the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a global uid set (UID) - based on a list of remote identifiers within the currently selected collection (RID) Destination is a collection id. */ -class Move : public Handler +class Move : public Handler { Q_OBJECT public: + ~Move() override = default; + bool parseStream() override; private Q_SLOTS: void itemsRetrieved(const QList &ids); private: Collection mDestination; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/relationfetch.h b/src/server/handler/relationfetch.h index 4af26b79f..94d8fd7d2 100644 --- a/src/server/handler/relationfetch.h +++ b/src/server/handler/relationfetch.h @@ -1,45 +1,47 @@ /*************************************************************************** * Copyright (C) 2014 by Christian Mollekopf * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIFETCHRELATION_H #define AKONADIFETCHRELATION_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the RELATIONFETCH command. */ class RelationFetch : public Handler { Q_OBJECT public: + ~RelationFetch() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/relationremove.h b/src/server/handler/relationremove.h index b813f8e7f..5d898f09a 100644 --- a/src/server/handler/relationremove.h +++ b/src/server/handler/relationremove.h @@ -1,40 +1,42 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RELATIONREMOVE_H #define AKONADI_RELATIONREMOVE_H #include "handler.h" namespace Akonadi { namespace Server { class RelationRemove : public Handler { Q_OBJECT public: + ~RelationRemove() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_RELATIONREMOVE_H diff --git a/src/server/handler/relationstore.h b/src/server/handler/relationstore.h index 7bd202630..e453a3395 100644 --- a/src/server/handler/relationstore.h +++ b/src/server/handler/relationstore.h @@ -1,46 +1,48 @@ /* Copyright (c) 2014 Christian Mollekop This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RELATIONSTORE_H #define AKONADI_RELATIONSTORE_H #include "handler.h" namespace Akonadi { namespace Server { class Relation; class RelationStore : public Handler { Q_OBJECT public: + ~RelationStore() override = default; + bool parseStream() override; private: Relation fetchRelation(qint64 leftId, qint64 rightId, qint64 typeId); }; } // namespace Server } // namespace Akonadi #endif // AKONADI_RELATIONSTORE_H diff --git a/src/server/handler/remove.h b/src/server/handler/remove.h index 4312975ac..92b25df79 100644 --- a/src/server/handler/remove.h +++ b/src/server/handler/remove.h @@ -1,59 +1,61 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_REMOVE_H #define AKONADI_REMOVE_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item deletion command.

Syntax

One of the following three: @verbatim REMOVE UID REMOVE RID REMOVE @endverbatim

Semantics

Removes the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a global uid set (UID) - based on a remote identifier within the currently selected collection (RID) */ -class Remove : public Handler +class Remove : public Handler { Q_OBJECT public: + ~Remove() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/resourceselect.h b/src/server/handler/resourceselect.h index 1a2f0f68f..006349f6f 100644 --- a/src/server/handler/resourceselect.h +++ b/src/server/handler/resourceselect.h @@ -1,50 +1,52 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RESOURCESELECT_H #define AKONADI_RESOURCESELECT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the resource selection command.

Semantics

Limits the scope of remote id based operations. Remote ids of collections are only guaranteed to be unique per resource, so this command should be issued before running any RID based collection commands. */ -class ResourceSelect : public Handler +class ResourceSelect : public Handler { Q_OBJECT public: + ~ResourceSelect() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/search.h b/src/server/handler/search.h index 8133d7f26..8cab46bdc 100644 --- a/src/server/handler/search.h +++ b/src/server/handler/search.h @@ -1,55 +1,57 @@ /*************************************************************************** * Copyright (C) 2009 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISEARCH_H #define AKONADISEARCH_H #include "handler.h" #include namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search commands. */ class Search : public Handler { Q_OBJECT public: + ~Search() override = default; + bool parseStream() override; private Q_SLOTS: void slotResultsAvailable(const QSet &results); private: Protocol::ItemFetchScope mFetchScope; QSet mAllResults; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/searchpersistent.h b/src/server/handler/searchpersistent.h index fd6c74780..abe1ae26b 100644 --- a/src/server/handler/searchpersistent.h +++ b/src/server/handler/searchpersistent.h @@ -1,45 +1,47 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISEARCHPERSISTENT_H #define AKONADISEARCHPERSISTENT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search_store search_delete commands. */ class SearchPersistent : public Handler { Q_OBJECT public: + ~SearchPersistent() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/searchresult.h b/src/server/handler/searchresult.h index f72f03667..5129c5874 100644 --- a/src/server/handler/searchresult.h +++ b/src/server/handler/searchresult.h @@ -1,48 +1,50 @@ /* * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef AKONADI_SEARCHRESULT_H #define AKONADI_SEARCHRESULT_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search_result command */ class SearchResult : public Handler { Q_OBJECT public: + ~SearchResult() override = default; + bool parseStream() override; private: bool fail(const QByteArray &searchId, const QString &error); }; } // namespace Server } // namespace Akonadi #endif // AKONADI_SEARCHRESULT_H diff --git a/src/server/handler/status.h b/src/server/handler/status.h index 1e56fb863..9c6e8a6d0 100644 --- a/src/server/handler/status.h +++ b/src/server/handler/status.h @@ -1,44 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISTATUS_H #define AKONADISTATUS_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the STATUS command. */ class Status : public Handler { Q_OBJECT public: + ~Status() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/store.h b/src/server/handler/store.h index fd2535c75..483ccae6f 100644 --- a/src/server/handler/store.h +++ b/src/server/handler/store.h @@ -1,115 +1,117 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * Copyright (C) 2009 by Volker Krause * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISTORE_H #define AKONADISTORE_H #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item modification command.

Syntax

One of the following three: @verbatim STORE UID MOVE [] RID MOVE [] @endverbatim @c revision-check is one of the following and allowed iff one item was selected for modification: @verbatim NOREV REV @endverbatim @c modifcations is a parenthesized list containing any of the following: @verbatim SIZE [+-]FLAGS REMOTEID REMOTEREVISION GID DIRTY false INVALIDATECACHE @endverbatim

Semantics

Modifies the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a uid set (UID) - based on a list of remote identifiers within the currently selected collection (RID) The following item properties can be modified: - the remote identifier (@c REMOTEID) - the remote revision (@c REMOTEREVISION) - the global identifier (@c GID) - resetting the dirty flag indication local changes not yet replicated to the backend (@c DIRTY) - adding/deleting/setting item flags (@c FLAGS) - setting the item size hint (@c SIZE) - changing item attributes - changing item payload parts If multiple items are selected only the following operations are valid: - adding flags - removing flags - settings flags The following operations are only allowed by resources: - resetting the dirty flag - invalidating the cache - modifying the remote identifier - modifying the remote revision Conflict detection: - only available when modifying a single item - requires the previous item revision to be provided (@c REV) */ class Store : public Handler { Q_OBJECT public: + ~Store() override = default; + bool parseStream() override; private: bool replaceFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool addFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool deleteFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool replaceTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); bool addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); bool deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/tagappend.h b/src/server/handler/tagappend.h index 0bc5d8e88..d95c29987 100644 --- a/src/server/handler/tagappend.h +++ b/src/server/handler/tagappend.h @@ -1,41 +1,43 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TAGAPPEND_H #define AKONADI_TAGAPPEND_H #include "handler.h" namespace Akonadi { namespace Server { class TagAppend : public Handler { Q_OBJECT public: + ~TagAppend() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGAPPEND_H diff --git a/src/server/handler/tagfetch.h b/src/server/handler/tagfetch.h index fda0224a6..984a1abec 100644 --- a/src/server/handler/tagfetch.h +++ b/src/server/handler/tagfetch.h @@ -1,45 +1,47 @@ /*************************************************************************** * Copyright (C) 2014 by Daniel Vrátil * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIFETCHTAG_H #define AKONADIFETCHTAG_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the FETCHTAG command. */ class TagFetch : public Handler { Q_OBJECT public: + ~TagFetch() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/tagfetchhelper.h b/src/server/handler/tagfetchhelper.h index 0aa2eda60..16dc617b3 100644 --- a/src/server/handler/tagfetchhelper.h +++ b/src/server/handler/tagfetchhelper.h @@ -1,60 +1,61 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TAGFETCHHELPER_H #define AKONADI_TAGFETCHHELPER_H #include #include #include namespace Akonadi { namespace Server { class Connection; class TagFetchHelper : public QObject { Q_OBJECT public: TagFetchHelper(Connection *connection, const Scope &scope); + ~TagFetchHelper() override = default; bool fetchTags(); static QMap fetchTagAttributes(qint64 tagId); private: QSqlQuery buildTagQuery(); QSqlQuery buildAttributeQuery() const; static QSqlQuery buildAttributeQuery(qint64 id); private: Connection *mConnection = nullptr; Scope mScope; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGFETCHHELPER_H diff --git a/src/server/handler/tagremove.h b/src/server/handler/tagremove.h index 3f64edf5e..434210cae 100644 --- a/src/server/handler/tagremove.h +++ b/src/server/handler/tagremove.h @@ -1,40 +1,42 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TAGREMOVE_H #define AKONADI_TAGREMOVE_H #include "handler.h" namespace Akonadi { namespace Server { class TagRemove : public Handler { Q_OBJECT public: + ~TagRemove() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGREMOVE_H diff --git a/src/server/handler/tagstore.h b/src/server/handler/tagstore.h index 6f674c6a3..6734982df 100644 --- a/src/server/handler/tagstore.h +++ b/src/server/handler/tagstore.h @@ -1,41 +1,43 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TAGSTORE_H #define AKONADI_TAGSTORE_H #include "handler.h" namespace Akonadi { namespace Server { class TagStore : public Handler { Q_OBJECT public: + ~TagStore() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGSTORE_H diff --git a/src/server/handler/transaction.h b/src/server/handler/transaction.h index ce85c4f53..67ad70b67 100644 --- a/src/server/handler/transaction.h +++ b/src/server/handler/transaction.h @@ -1,47 +1,48 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TRANSACTION_HANDLER_H #define AKONADI_TRANSACTION_HANDLER_H #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for transaction commands (BEGIN, COMMIT, ROLLBACK). */ class TransactionHandler : public Handler { Q_OBJECT - Q_ENUMS(Mode) public: + ~TransactionHandler() override = default; + bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/notificationsubscriber.cpp b/src/server/notificationsubscriber.cpp index 472da0a43..aad646f30 100644 --- a/src/server/notificationsubscriber.cpp +++ b/src/server/notificationsubscriber.cpp @@ -1,737 +1,733 @@ /* Copyright (c) 2015 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "notificationsubscriber.h" #include "akonadiserver_debug.h" #include "notificationmanager.h" #include "collectionreferencemanager.h" #include "aggregatedfetchscope.h" #include "storage/querybuilder.h" #include "utils.h" #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; QMimeDatabase NotificationSubscriber::sMimeDatabase; #define TRACE_NTF(x) //#define TRACE_NTF(x) qCDebug(AKONADISERVER_LOG) << mSubscriber << x NotificationSubscriber::NotificationSubscriber(NotificationManager *manager) : QObject() , mManager(manager) , mSocket(nullptr) , mAllMonitored(false) , mExclusive(false) , mNotificationDebugging(false) { } NotificationSubscriber::NotificationSubscriber(NotificationManager *manager, quintptr socketDescriptor) : NotificationSubscriber(manager) { mSocket = new QLocalSocket(this); connect(mSocket, &QLocalSocket::readyRead, this, &NotificationSubscriber::handleIncomingData); connect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->setSocketDescriptor(socketDescriptor); const SchemaVersion schema = SchemaVersion::retrieveAll().first(); auto hello = Protocol::HelloResponsePtr::create(); hello->setServerName(QStringLiteral("Akonadi")); hello->setMessage(QStringLiteral("Not really IMAP server")); hello->setProtocolVersion(Protocol::version()); hello->setGeneration(schema.generation()); writeCommand(0, hello); } NotificationSubscriber::~NotificationSubscriber() { QMutexLocker locker(&mLock); if (mNotificationDebugging) { Q_EMIT notificationDebuggingChanged(false); } } void NotificationSubscriber::handleIncomingData() { while (mSocket->bytesAvailable() > (int) sizeof(qint64)) { QDataStream stream(mSocket); // Ignored atm qint64 tag = -1; stream >> tag; Protocol::CommandPtr cmd; try { cmd = Protocol::deserialize(mSocket); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); disconnectSubscriber(); return; } catch (const std::exception &e) { qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); disconnectSubscriber(); return; } if (cmd->type() == Protocol::Command::Invalid) { qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; disconnectSubscriber(); return; } switch (cmd->type()) { case Protocol::Command::CreateSubscription: registerSubscriber(Protocol::cmdCast(cmd)); writeCommand(tag, Protocol::CreateSubscriptionResponsePtr::create()); break; case Protocol::Command::ModifySubscription: if (mSubscriber.isEmpty()) { qCWarning(AKONADISERVER_LOG) << "Received ModifySubscription command before RegisterSubscriber"; disconnectSubscriber(); return; } modifySubscription(Protocol::cmdCast(cmd)); writeCommand(tag, Protocol::ModifySubscriptionResponsePtr::create()); break; case Protocol::Command::Logout: disconnectSubscriber(); break; default: qCWarning(AKONADISERVER_LOG) << "Invalid command" << cmd->type() << "received by NotificationSubscriber" << mSubscriber; disconnectSubscriber(); break; } } } void NotificationSubscriber::socketDisconnected() { qCDebug(AKONADISERVER_LOG) << "Subscriber" << mSubscriber << "disconnected"; disconnectSubscriber(); } void NotificationSubscriber::disconnectSubscriber() { QMutexLocker locker(&mLock); - if (mManager) { - auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); - changeNtf->setSubscriber(mSubscriber); - changeNtf->setSessionId(mSession); - changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); - mManager->slotNotify({ changeNtf }); - } + auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); + changeNtf->setSubscriber(mSubscriber); + changeNtf->setSessionId(mSession); + changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); + mManager->slotNotify({ changeNtf }); disconnect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->close(); // Unregister ourselves from the aggergated collection fetch scope auto cfs = mManager->collectionFetchScope(); if (mCollectionFetchScope.fetchIdOnly()) { cfs->setFetchIdOnly(false); } if (mCollectionFetchScope.includeStatistics()) { cfs->setFetchStatistics(false); } const auto attrs = mCollectionFetchScope.attributes(); for (const auto &attr : attrs) { cfs->removeAttribute(attr); } mManager->forgetSubscriber(this); deleteLater(); } void NotificationSubscriber::registerSubscriber(const Protocol::CreateSubscriptionCommand &command) { QMutexLocker locker(&mLock); qCDebug(AKONADISERVER_LOG) << "Subscriber" << this << "identified as" << command.subscriberName(); mSubscriber = command.subscriberName(); mSession = command.session(); - if (mManager) { - auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); - changeNtf->setSubscriber(mSubscriber); - changeNtf->setSessionId(mSession); - changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Add); - mManager->slotNotify({ changeNtf }); - } + auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); + changeNtf->setSubscriber(mSubscriber); + changeNtf->setSessionId(mSession); + changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Add); + mManager->slotNotify({ changeNtf }); } void NotificationSubscriber::modifySubscription(const Protocol::ModifySubscriptionCommand &command) { QMutexLocker locker(&mLock); const auto modifiedParts = command.modifiedParts(); #define START_MONITORING(type) \ (modifiedParts & Protocol::ModifySubscriptionCommand::ModifiedParts( \ Protocol::ModifySubscriptionCommand::type | Protocol::ModifySubscriptionCommand::Add)) #define STOP_MONITORING(type) \ (modifiedParts & Protocol::ModifySubscriptionCommand::ModifiedParts( \ Protocol::ModifySubscriptionCommand::type | Protocol::ModifySubscriptionCommand::Remove)) #define APPEND(set, newItems) \ Q_FOREACH (const auto &entity, newItems) { \ set.insert(entity); \ } #define REMOVE(set, items) \ Q_FOREACH (const auto &entity, items) { \ set.remove(entity); \ } if (START_MONITORING(Types)) { APPEND(mMonitoredTypes, command.startMonitoringTypes()) } if (STOP_MONITORING(Types)) { REMOVE(mMonitoredTypes, command.stopMonitoringTypes()) } if (START_MONITORING(Collections)) { APPEND(mMonitoredCollections, command.startMonitoringCollections()) } if (STOP_MONITORING(Collections)) { REMOVE(mMonitoredCollections, command.stopMonitoringCollections()) } if (START_MONITORING(Items)) { APPEND(mMonitoredItems, command.startMonitoringItems()) } if (STOP_MONITORING(Items)) { REMOVE(mMonitoredItems, command.stopMonitoringItems()) } if (START_MONITORING(Tags)) { APPEND(mMonitoredTags, command.startMonitoringTags()) } if (STOP_MONITORING(Tags)) { REMOVE(mMonitoredTags, command.stopMonitoringTags()) } if (START_MONITORING(Resources)) { APPEND(mMonitoredResources, command.startMonitoringResources()) } if (STOP_MONITORING(Resources)) { REMOVE(mMonitoredResources, command.stopMonitoringResources()) } if (START_MONITORING(MimeTypes)) { APPEND(mMonitoredMimeTypes, command.startMonitoringMimeTypes()) } if (STOP_MONITORING(MimeTypes)) { REMOVE(mMonitoredMimeTypes, command.stopMonitoringMimeTypes()) } if (START_MONITORING(Sessions)) { APPEND(mIgnoredSessions, command.startIgnoringSessions()) } if (STOP_MONITORING(Sessions)) { REMOVE(mIgnoredSessions, command.stopIgnoringSessions()) } if (modifiedParts & Protocol::ModifySubscriptionCommand::AllFlag) { mAllMonitored = command.allMonitored(); } if (modifiedParts & Protocol::ModifySubscriptionCommand::ExclusiveFlag) { mExclusive = command.isExclusive(); } if (modifiedParts & Protocol::ModifySubscriptionCommand::ItemFetchScope) { const auto newScope = command.itemFetchScope(); mManager->itemFetchScope()->apply(mItemFetchScope, newScope); mItemFetchScope = newScope; } if (modifiedParts & Protocol::ModifySubscriptionCommand::CollectionFetchScope) { const auto newScope = command.collectionFetchScope(); mManager->collectionFetchScope()->apply(mCollectionFetchScope, newScope); mCollectionFetchScope = newScope; } if (modifiedParts & Protocol::ModifySubscriptionCommand::TagFetchScope) { const auto newScope = command.tagFetchScope(); mManager->tagFetchScope()->apply(mTagFetchScope, newScope); mTagFetchScope = newScope; } if (mManager) { if (modifiedParts & Protocol::ModifySubscriptionCommand::Types) { // Did the caller just subscribed to subscription changes? if (command.startMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::SubscriptionChanges)) { // If yes, then send them list of all existing subscribers Q_FOREACH (const NotificationSubscriber *subscriber, mManager->mSubscribers) { // Send them back to caller if (subscriber) { QMetaObject::invokeMethod(this, "notify", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, subscriber->toChangeNotification())); } } } if (command.startMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::ChangeNotifications)) { if (!mNotificationDebugging) { mNotificationDebugging = true; Q_EMIT notificationDebuggingChanged(true); } } else if (command.stopMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::ChangeNotifications)) { if (mNotificationDebugging) { mNotificationDebugging = false; Q_EMIT notificationDebuggingChanged(false); } } } // Emit subscription change notification auto changeNtf = toChangeNotification(); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Modify); mManager->slotNotify({ changeNtf }); } #undef START_MONITORING #undef STOP_MONITORING #undef APPEND #undef REMOVE } Protocol::SubscriptionChangeNotificationPtr NotificationSubscriber::toChangeNotification() const { // Assumes mLock being locked by caller auto ntf = Protocol::SubscriptionChangeNotificationPtr::create(); ntf->setSessionId(mSession); ntf->setSubscriber(mSubscriber); ntf->setOperation(Protocol::SubscriptionChangeNotification::Add); ntf->setCollections(mMonitoredCollections); ntf->setItems(mMonitoredItems); ntf->setTags(mMonitoredTags); ntf->setTypes(mMonitoredTypes); ntf->setMimeTypes(mMonitoredMimeTypes); ntf->setResources(mMonitoredResources); ntf->setIgnoredSessions(mIgnoredSessions); ntf->setAllMonitored(mAllMonitored); ntf->setExclusive(mExclusive); ntf->setItemFetchScope(mItemFetchScope); ntf->setCollectionFetchScope(mCollectionFetchScope); return ntf; } bool NotificationSubscriber::isCollectionMonitored(Entity::Id id) const { // Assumes mLock being locked by caller if (id < 0) { return false; } else if (mMonitoredCollections.contains(id)) { return true; } else if (mMonitoredCollections.contains(0)) { return true; } return false; } bool NotificationSubscriber::isMimeTypeMonitored(const QString &mimeType) const { // Assumes mLock being locked by caller const QMimeType mt = sMimeDatabase.mimeTypeForName(mimeType); if (mMonitoredMimeTypes.contains(mimeType)) { return true; } const QStringList lst = mt.aliases(); for (const QString &alias : lst) { if (mMonitoredMimeTypes.contains(alias)) { return true; } } return false; } bool NotificationSubscriber::isMoveDestinationResourceMonitored(const Protocol::ItemChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.operation() != Protocol::ItemChangeNotification::Move) { return false; } return mMonitoredResources.contains(msg.destinationResource()); } bool NotificationSubscriber::isMoveDestinationResourceMonitored(const Protocol::CollectionChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.operation() != Protocol::CollectionChangeNotification::Move) { return false; } return mMonitoredResources.contains(msg.destinationResource()); } bool NotificationSubscriber::acceptsItemNotification(const Protocol::ItemChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.items().isEmpty()) { return false; } if (CollectionReferenceManager::instance()->isReferenced(msg.parentCollection())) { //We always want notifications that affect the parent resource (like an item added to a referenced collection) const bool notificationForParentResource = (mSession == msg.resource()); const bool accepts = mExclusive || isCollectionMonitored(msg.parentCollection()) || isMoveDestinationResourceMonitored(msg) || notificationForParentResource; TRACE_NTF("ACCEPTS ITEM: parent col referenced" << "exclusive:" << mExclusive << "," << "parent monitored:" << isCollectionMonitored(msg.parentCollection()) << "," << "destination monitored:" << isMoveDestinationResourceMonitored(msg) << "," << "ntf for parent resource:" << notificationForParentResource << ":" << "ACCEPTED:" << accepts); return accepts; } if (mAllMonitored) { TRACE_NTF("ACCEPTS ITEM: all monitored"); return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::ItemChanges)) { TRACE_NTF("ACCEPTS ITEM: REJECTED - Item changes not monitored"); return false; } // we have a resource or mimetype filter if (!mMonitoredResources.isEmpty() || !mMonitoredMimeTypes.isEmpty()) { if (mMonitoredResources.contains(msg.resource())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - resource monitored"); return true; } if (isMoveDestinationResourceMonitored(msg)) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: move destination monitored"); return true; } Q_FOREACH (const auto &item, msg.items()) { if (isMimeTypeMonitored(item->mimeType())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - mimetype monitored"); return true; } } TRACE_NTF("ACCEPTS ITEM: REJECTED: resource nor mimetype monitored"); return false; } // we explicitly monitor that item or the collections it's in Q_FOREACH (const auto &item, msg.items()) { if (mMonitoredItems.contains(item->id())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: item explicitly monitored"); return true; } } if (isCollectionMonitored(msg.parentCollection())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: parent collection monitored"); return true; } if (isCollectionMonitored(msg.parentDestCollection())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: destination collection monitored"); return true; } TRACE_NTF("ACCEPTS ITEM: REJECTED"); return false; } bool NotificationSubscriber::acceptsCollectionNotification(const Protocol::CollectionChangeNotification &msg) const { // Assumes mLock being locked by caller const auto collection = msg.collection(); if (!collection || collection->id() < 0) { return false; } // HACK: We need to dispatch notifications about disabled collections to SOME // agents (that's what we have the exclusive subscription for) - but because // querying each Collection from database would be expensive, we use the // metadata hack to transfer this information from NotificationCollector if (msg.metadata().contains("DISABLED") && (msg.operation() != Protocol::CollectionChangeNotification::Unsubscribe) && !msg.changedParts().contains("ENABLED")) { // Exclusive subscriber always gets it if (mExclusive) { return true; } //Deliver the notification if referenced from this session if (CollectionReferenceManager::instance()->isReferenced(collection->id(), mSession)) { return true; } //Exclusive subscribers still want the notification if (mExclusive && CollectionReferenceManager::instance()->isReferenced(collection->id())) { return true; } //The session belonging to this monitor referenced or dereferenced the collection. We always want this notification. //The referencemanager no longer holds a reference, so we have to check this way. if (msg.changedParts().contains(AKONADI_PARAM_REFERENCED) && mSession == msg.sessionId()) { return true; } // If the collection is not referenced, monitored or the subscriber is not // exclusive (i.e. if we got here), then the subscriber does not care about // this one, so drop it return false; } if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::CollectionChanges)) { return false; } // we have a resource filter if (!mMonitoredResources.isEmpty()) { const bool resourceMatches = mMonitoredResources.contains(msg.resource()) || isMoveDestinationResourceMonitored(msg); // a bit hacky, but match the behaviour from the item case, // if resource is the only thing we are filtering on, stop here, and if the resource filter matched, of course if (mMonitoredMimeTypes.isEmpty() || resourceMatches) { return resourceMatches; } // else continue } // we explicitly monitor that colleciton, or all of them if (isCollectionMonitored(collection->id())) { return true; } return isCollectionMonitored(msg.parentCollection()) || isCollectionMonitored(msg.parentDestCollection()); } bool NotificationSubscriber::acceptsTagNotification(const Protocol::TagChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.tag()->id() < 0) { return false; } // Special handling for Tag removal notifications: When a Tag is removed, // a notification is emitted for each Resource that owns the tag (i.e. // each resource that owns a Tag RID - Tag RIDs are resource-specific). // Additionally then we send one more notification without any RID that is // destined for regular applications (which don't know anything about Tag RIDs) if (msg.operation() == Protocol::TagChangeNotification::Remove) { // HACK: Since have no way to determine which resource this NotificationSource // belongs to, we are abusing the fact that each resource ignores it's own // main session, which is called the same name as the resource. // If there are any ignored sessions, but this notification does not have // a specific resource set, then we ignore it, as this notification is // for clients, not resources (does not have tag RID) if (!mIgnoredSessions.isEmpty() && msg.resource().isEmpty()) { return false; } // If this source ignores a session (i.e. we assume it is a resource), // but this notification is for another resource, then we ignore it if (!msg.resource().isEmpty() && !mIgnoredSessions.contains(msg.resource())) { return false; } // Now we got here, which means that this notification either has empty // resource, i.e. it is destined for a client applications, or it's // destined for resource that we *think* (see the hack above) this // NotificationSource belongs too. Which means we approve this notification, // but it can still be discarded in the generic Tag notification filter // below } if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::TagChanges)) { return false; } if (mMonitoredTags.isEmpty()) { return true; } if (mMonitoredTags.contains(msg.tag()->id())) { return true; } return true; } bool NotificationSubscriber::acceptsRelationNotification(const Protocol::RelationChangeNotification &msg) const { // Assumes mLock being locked by caller Q_UNUSED(msg); if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::RelationChanges)) { return false; } return true; } bool NotificationSubscriber::acceptsSubscriptionNotification(const Protocol::SubscriptionChangeNotification &msg) const { // Assumes mLock being locked by caller Q_UNUSED(msg); // Unlike other types, subscription notifications must be explicitly enabled // by caller and are excluded from "monitor all" as well return mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::SubscriptionChanges); } bool NotificationSubscriber::acceptsDebugChangeNotification(const Protocol::DebugChangeNotification &msg) const { // Assumes mLock being locked by caller // We should never end up sending debug notification about a debug notification. // This could get very messy very quickly... Q_ASSERT(msg.notification()->type() != Protocol::Command::DebugChangeNotification); if (msg.notification()->type() == Protocol::Command::DebugChangeNotification) { return false; } // Unlike other types, debug change notifications must be explicitly enabled // by caller and are excluded from "monitor all" as well return mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::ChangeNotifications); } bool NotificationSubscriber::acceptsNotification(const Protocol::ChangeNotification &msg) const { // Assumes mLock being locked // Uninitialized subscriber gets nothing if (mSubscriber.isEmpty()) { return false; } // session is ignored // TODO: Should this afect SubscriptionChangeNotification and DebugChangeNotification? if (mIgnoredSessions.contains(msg.sessionId())) { return false; } switch (msg.type()) { case Protocol::Command::ItemChangeNotification: return acceptsItemNotification(static_cast(msg)); case Protocol::Command::CollectionChangeNotification: return acceptsCollectionNotification(static_cast(msg)); case Protocol::Command::TagChangeNotification: return acceptsTagNotification(static_cast(msg)); case Protocol::Command::RelationChangeNotification: return acceptsRelationNotification(static_cast(msg)); case Protocol::Command::SubscriptionChangeNotification: return acceptsSubscriptionNotification(static_cast(msg)); case Protocol::Command::DebugChangeNotification: return acceptsDebugChangeNotification(static_cast(msg)); default: qCDebug(AKONADISERVER_LOG) << "Received invalid change notification!"; return false; } } Protocol::CollectionChangeNotificationPtr NotificationSubscriber::customizeCollection(const Protocol::CollectionChangeNotificationPtr &ntf) { const bool isReferencedFromSession = CollectionReferenceManager::instance()->isReferenced(ntf->collection()->id(), mSession); if (isReferencedFromSession != ntf->collection()->referenced()) { auto copy = Protocol::CollectionChangeNotificationPtr::create(*ntf); copy->setCollection(Protocol::FetchCollectionsResponsePtr::create(*ntf->collection())); copy->collection()->setReferenced(isReferencedFromSession); return copy; } return ntf; } bool NotificationSubscriber::notify(const Protocol::ChangeNotificationPtr ¬ification) { // Guard against this object being deleted while we are waiting for the lock QPointer ptr(this); QMutexLocker locker(&mLock); if (!ptr) { return false; } if (acceptsNotification(*notification)) { auto ntf = notification; if (ntf->type() == Protocol::Command::CollectionChangeNotification) { ntf = customizeCollection(notification.staticCast()); } QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, ntf)); return true; } return false; } void NotificationSubscriber::writeNotification(const Protocol::ChangeNotificationPtr ¬ification) { // tag chosen by fair dice roll writeCommand(4, notification); } void NotificationSubscriber::writeCommand(qint64 tag, const Protocol::CommandPtr &cmd) { Q_ASSERT(QThread::currentThread() == thread()); QDataStream stream(mSocket); stream << tag; try { Protocol::serialize(mSocket, cmd); if (!mSocket->waitForBytesWritten()) { if (mSocket->state() == QLocalSocket::ConnectedState) { qCWarning(AKONADISERVER_LOG) << "Notification socket write timeout!"; } else { // client has disconnected, just discard the message } } } catch (const ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "Notification protocol exception:" << e.what(); } } diff --git a/src/server/storage/datastore.h b/src/server/storage/datastore.h index 13005e823..d7d29bdab 100644 --- a/src/server/storage/datastore.h +++ b/src/server/storage/datastore.h @@ -1,377 +1,377 @@ /*************************************************************************** * Copyright (C) 2006 by Andreas Gungl * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef DATASTORE_H #define DATASTORE_H #include #include #include #include #include class QSqlQuery; class QTimer; #include "entities.h" #include "notificationcollector.h" namespace Akonadi { namespace Server { class NotificationCollector; /** This class handles all the database access.

Database configuration

You can select between various database backends during runtime using the @c $HOME/.config/akonadi/akonadiserverrc configuration file. Example: @verbatim [%General] Driver=QMYSQL [QMYSQL_EMBEDDED] Name=akonadi Options=SERVER_DATADIR=/home/foo/.local/share/akonadi/db_data [QMYSQL] Name=akonadi Host=localhost User=foo Password=***** #Options=UNIX_SOCKET=/home/foo/.local/share/akonadi/socket-bar/mysql.socket StartServer=true ServerPath=/usr/sbin/mysqld [QSQLITE] Name=/home/foo/.local/share/akonadi/akonadi.db @endverbatim Use @c General/Driver to select the QSql driver to use for database access. The following drivers are currently supported, other might work but are untested: - QMYSQL - QMYSQL_EMBEDDED - QSQLITE The options for each driver are read from the corresponding group. The following options are supported, dependent on the driver not all of them might have an effect: - Name: Database name, for sqlite that's the file name of the database. - Host: Hostname of the database server - User: Username for the database server - Password: Password for the database server - Options: Additional options, format is driver-dependent - StartServer: Start the database locally just for Akonadi instead of using an existing one - ServerPath: Path to the server executable */ class DataStore : public QObject { Q_OBJECT public: /** Closes the database connection and destroys the DataStore object. */ ~DataStore() override; /** Opens the database connection. */ virtual void open(); /** Closes the database connection. */ - virtual void close(); + void close(); /** Initializes the database. Should be called during startup by the main thread. */ virtual bool init(); /** Per thread singleton. */ static DataStore *self(); /** * Returns whether per thread DataStore has been created. */ static bool hasDataStore(); /* --- ItemFlags ----------------------------------------------------- */ virtual bool setItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, const Collection &col = Collection(), bool silent = false); virtual bool appendItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false); virtual bool removeItemsFlags(const PimItem::List &items, const QVector &flags, bool *tagsChanged = nullptr, const Collection &collection = Collection(), bool silent = false); /* --- ItemTags ----------------------------------------------------- */ virtual bool setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false); virtual bool appendItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false); virtual bool removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false); virtual bool removeTags(const Tag::List &tags, bool silent = false); /* --- ItemParts ----------------------------------------------------- */ virtual bool removeItemParts(const PimItem &item, const QSet &parts); // removes all payload parts for this item. virtual bool invalidateItemCache(const PimItem &item); /* --- Collection ------------------------------------------------------ */ virtual bool appendCollection(Collection &collection); /// removes the given collection and all its content virtual bool cleanupCollection(Collection &collection); /// same as the above but for database backends without working referential actions on foreign keys virtual bool cleanupCollection_slow(Collection &collection); /// moves the collection @p collection to @p newParent. virtual bool moveCollection(Collection &collection, const Collection &newParent); virtual bool appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes); static QString collectionDelimiter() { return QStringLiteral("/"); } /** Determines the active cache policy for this Collection. The active cache policy is set in the corresponding Collection fields. */ virtual void activeCachePolicy(Collection &col); /// Returns all virtual collections the @p item is linked to QVector virtualCollections(const PimItem &item); QMap< Server::Entity::Id, QList< PimItem > > virtualCollections(const Akonadi::Server::PimItem::List &items); /* --- PimItem ------------------------------------------------------- */ virtual bool appendPimItem(QVector &parts, const QVector &flags, const MimeType &mimetype, const Collection &collection, const QDateTime &dateTime, const QString &remote_id, const QString &remoteRevision, const QString &gid, PimItem &pimItem); /** * Removes the pim item and all referenced data ( e.g. flags ) */ virtual bool cleanupPimItems(const PimItem::List &items); /** * Unhides the specified PimItem. Emits the itemAdded() notification as * the hidden flag is assumed to have been set by appendPimItem() before * pushing the item to the preprocessor chain. The hidden item had his * notifications disabled until now (so for the clients the "unhide" operation * is actually a new item arrival). * * This function does NOT verify if the item was *really* hidden: this is * responsibility of the caller. */ virtual bool unhidePimItem(PimItem &pimItem); /** * Unhides all the items which have the "hidden" flag set. * This function doesn't emit any notification about the items * being unhidden so it's meant to be called only in rare circumstances. * The most notable call to this function is at server startup * when we attempt to restore a clean state of the database. */ virtual bool unhideAllPimItems(); /* --- Collection attributes ------------------------------------------ */ virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value); /** * Removes the given collection attribute for @p col. * @throws HandlerException on database errors * @returns @c true if the attribute existed, @c false otherwise */ virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key); /* --- Helper functions ---------------------------------------------- */ /** Begins a transaction. No changes will be written to the database and no notification signal will be emitted unless you call commitTransaction(). @return @c true if successful. */ virtual bool beginTransaction(const QString &name); /** Reverts all changes within the current transaction. */ virtual bool rollbackTransaction(); /** Commits all changes within the current transaction and emits all collected notfication signals. If committing fails, the transaction will be rolled back. */ virtual bool commitTransaction(); /** Returns true if there is a transaction in progress. */ - virtual bool inTransaction() const; + bool inTransaction() const; /** Returns the notification collector of this DataStore object. Use this to listen to change notification signals. */ - virtual NotificationCollector *notificationCollector(); + NotificationCollector *notificationCollector(); /** Returns the QSqlDatabase object. Use this for generating queries yourself. Will [re-]open the database, if it is closed. */ QSqlDatabase database(); /** Sets the current session id. */ void setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } /** Returns if the database is currently open */ bool isOpened() const { return m_dbOpened; } Q_SIGNALS: /** Emitted if a transaction has been successfully committed. */ void transactionCommitted(); /** Emitted if a transaction has been aborted. */ void transactionRolledBack(); protected: /** Creates a new DataStore object and opens it. */ DataStore(); void debugLastDbError(const char *actionDescription) const; void debugLastQueryError(const QSqlQuery &query, const char *actionDescription) const; private: bool doAppendItemsFlag(const PimItem::List &items, const Flag &flag, const QSet &existing, const Collection &col, bool silent); bool doAppendItemsTag(const PimItem::List &items, const Tag &tag, const QSet &existing, const Collection &col, bool silent); /** Converts the given date/time to the database format, i.e. "YYYY-MM-DD HH:MM:SS". @param dateTime the date/time in UTC @return the date/time in database format @see dateTimeToQDateTime */ static QString dateTimeFromQDateTime(const QDateTime &dateTime); /** Converts the given date/time from database format to QDateTime. @param dateTime the date/time in database format @return the date/time as QDateTime @see dateTimeFromQDateTime */ static QDateTime dateTimeToQDateTime(const QByteArray &dateTime); /** * Adds the @p query to current transaction, so that it can be replayed in * case the transaction deadlocks or timeouts. * * When DataStore is not in transaction or SQLite is configured, this method * does nothing. * * All queries will automatically be removed when transaction is committed. * * This method should only be used by QueryBuilder. */ void addQueryToTransaction(const QString &statement, const QVector &bindValues, bool isBatch); /** * Tries to execute all queries from last transaction again. If any of the * queries fails, the entire transaction is rolled back and fails. * * This method can only be used by QueryBuilder when database rolls back * transaction due to deadlock or timeout. * * @return Returns an invalid query when error occurs, or the last replayed * query on success. */ QSqlQuery retryLastTransaction(bool rollbackFirst); private Q_SLOTS: void sendKeepAliveQuery(); protected: static QThreadStorage sInstances; QString m_connectionName; QSqlDatabase m_database; bool m_dbOpened; uint m_transactionLevel; struct TransactionQuery { QString query; QVector boundValues; bool isBatch; }; QVector m_transactionQueries; QByteArray mSessionId; NotificationCollector *mNotificationCollector = nullptr; QTimer *m_keepAliveTimer = nullptr; static bool s_hasForeignKeyConstraints; // Gives QueryBuilder access to addQueryToTransaction() and retryLastTransaction() friend class QueryBuilder; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/partstreamer.cpp b/src/server/storage/partstreamer.cpp index d5fc1fc08..724c18728 100644 --- a/src/server/storage/partstreamer.cpp +++ b/src/server/storage/partstreamer.cpp @@ -1,424 +1,424 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "partstreamer.h" #include "parthelper.h" #include "parttypehelper.h" #include "selectquerybuilder.h" #include "dbconfig.h" #include "connection.h" #include "capabilities_p.h" #include "akonadiserver_debug.h" #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include using namespace Akonadi; using namespace Akonadi::Server; PartStreamer::PartStreamer(Connection *connection, const PimItem &pimItem, QObject *parent) : QObject(parent) , mConnection(connection) , mItem(pimItem) { // Make sure the file_db_data path exists StandardDirs::saveDir("data", QStringLiteral("file_db_data")); } PartStreamer::~PartStreamer() { } QString PartStreamer::error() const { return mError; } Protocol::PartMetaData PartStreamer::requestPartMetaData(const QByteArray &partName) { { auto resp = Protocol::StreamPayloadCommandPtr::create(); resp->setPayloadName(partName); resp->setRequest(Protocol::StreamPayloadCommand::MetaData); Q_EMIT responseAvailable(resp); } const auto cmd = mConnection->readCommand(); if (!cmd->isValid() || Protocol::cmdCast(cmd).isError()) { mError = QStringLiteral("Client failed to provide part metadata"); return Protocol::PartMetaData(); } return Protocol::cmdCast(cmd).metaData(); } bool PartStreamer::streamPayload(Part &part, const QByteArray &partName) { Protocol::PartMetaData metaPart = requestPartMetaData(partName); if (metaPart.name().isEmpty()) { mError = QStringLiteral("Part name is empty"); return false; } part.setVersion(metaPart.version()); if (part.datasize() != metaPart.size()) { part.setDatasize(metaPart.size()); // Shortcut: if sizes differ, we don't need to compare data later no in order // to detect whether the part has changed mDataChanged = mDataChanged || (metaPart.size() != part.datasize()); } if (metaPart.storageType() == Protocol::PartMetaData::Foreign) { return streamForeignPayload(part, metaPart); } else if (part.datasize() > DbConfig::configuredDatabase()->sizeThreshold()) { //actual case when streaming storage is used: external payload is enabled, // data is big enough in a literal return streamPayloadToFile(part, metaPart); } else { return streamPayloadData(part, metaPart); } } bool PartStreamer::streamPayloadData(Part &part, const Protocol::PartMetaData &metaPart) { // If the part WAS external previously, remove data file if (part.storage() == Part::External) { ExternalPartStorage::self()->removePartFile( ExternalPartStorage::resolveAbsolutePath(part.data())); } // Request the actual data { auto resp = Protocol::StreamPayloadCommandPtr::create(); resp->setPayloadName(metaPart.name()); resp->setRequest(Protocol::StreamPayloadCommand::Data); Q_EMIT responseAvailable(resp); } const auto cmd = mConnection->readCommand(); const auto &response = Protocol::cmdCast(cmd); if (!response.isValid() || response.isError()) { mError = QStringLiteral("Client failed to provide payload data"); qCCritical(AKONADISERVER_LOG) << mError; return false; } const QByteArray newData = response.data(); // only use the data size with intenral payload parts, for foreign parts // we use the size reported by client const auto newSize = (metaPart.storageType() == Protocol::PartMetaData::Internal) ? newData.size() : metaPart.size(); if (newSize != metaPart.size()) { mError = QStringLiteral("Payload size mismatch"); return false; } if (part.isValid()) { if (!mDataChanged) { mDataChanged = mDataChanged || (newData != part.data()); } PartHelper::update(&part, newData, newSize); } else { part.setData(newData); part.setDatasize(newSize); if (!part.insert()) { mError = QStringLiteral("Failed to insert part to database"); return false; } } return true; } bool PartStreamer::streamPayloadToFile(Part &part, const Protocol::PartMetaData &metaPart) { QByteArray origData; if (!mDataChanged && mCheckChanged) { origData = PartHelper::translateData(part); } QByteArray filename; if (part.isValid()) { if (part.storage() == Part::External) { // Part was external and is still external filename = part.data(); if (!filename.isEmpty()) { ExternalPartStorage::self()->removePartFile( ExternalPartStorage::resolveAbsolutePath(filename)); filename = ExternalPartStorage::updateFileNameRevision(filename); } else { // recover from data corruption filename = ExternalPartStorage::nameForPartId(part.id()); } } else { // Part wasn't external, but is now filename = ExternalPartStorage::nameForPartId(part.id()); } QFileInfo finfo(QString::fromUtf8(filename)); if (finfo.isAbsolute()) { filename = finfo.fileName().toUtf8(); } } part.setStorage(Part::External); part.setDatasize(metaPart.size()); part.setData(filename); if (part.isValid()) { if (!part.update()) { mError = QStringLiteral("Failed to update part in database"); return false; } } else { if (!part.insert()) { mError = QStringLiteral("Failed to insert part into database"); return false; } filename = ExternalPartStorage::nameForPartId(part.id()); part.setData(filename); if (!part.update()) { mError = QStringLiteral("Failed to update part in database"); return false; } } { auto cmd = Protocol::StreamPayloadCommandPtr::create(); cmd->setPayloadName(metaPart.name()); cmd->setRequest(Protocol::StreamPayloadCommand::Data); cmd->setDestination(QString::fromUtf8(filename)); Q_EMIT responseAvailable(cmd); } const auto cmd = mConnection->readCommand(); const auto &response = Protocol::cmdCast(cmd); if (!response.isValid() || response.isError()) { mError = QStringLiteral("Client failed to store payload into file"); qCCritical(AKONADISERVER_LOG) << mError; return false; } QFile file(ExternalPartStorage::resolveAbsolutePath(filename), this); if (!file.exists()) { mError = QStringLiteral("External payload file does not exist"); qCCritical(AKONADISERVER_LOG) << mError; return false; } if (file.size() != metaPart.size()) { mError = QStringLiteral("Payload size mismatch"); qCDebug(AKONADISERVER_LOG) << mError << ", client advertised" << metaPart.size() << "bytes, but the file is" << file.size() << "bytes!"; return false; } if (mCheckChanged && !mDataChanged) { // This is invoked only when part already exists, data sizes match and // caller wants to know whether parts really differ mDataChanged = (origData != PartHelper::translateData(part)); } return true; } bool PartStreamer::streamForeignPayload(Part &part, const Protocol::PartMetaData &metaPart) { QByteArray origData; if (!mDataChanged && mCheckChanged) { origData = PartHelper::translateData(part); } { auto cmd = Protocol::StreamPayloadCommandPtr::create(); cmd->setPayloadName(metaPart.name()); cmd->setRequest(Protocol::StreamPayloadCommand::Data); Q_EMIT responseAvailable(cmd); } const auto cmd = mConnection->readCommand(); const auto response = Protocol::cmdCast(cmd); if (!response.isValid() || response.isError()) { mError = QStringLiteral("Client failed to store payload into file"); qCCritical(AKONADISERVER_LOG) << mError; return false; } // If the part was previously external, clean up the data if (part.storage() == Part::External) { const QString filename = QString::fromUtf8(part.data()); ExternalPartStorage::self()->removePartFile( ExternalPartStorage::resolveAbsolutePath(filename)); } part.setStorage(Part::Foreign); part.setData(response.data()); if (part.isValid()) { if (!part.update()) { mError = QStringLiteral("Failed to update part in database"); return false; } } else { if (!part.insert()) { mError = QStringLiteral("Failed to insert part into database"); return false; } } const QString filename = QString::fromUtf8(response.data()); QFile file(filename); if (!file.exists()) { mError = QStringLiteral("Foreign payload file does not exist"); qCCritical(AKONADISERVER_LOG) << mError; return false; } if (file.size() != metaPart.size()) { mError = QStringLiteral("Payload size mismatch"); qCCritical(AKONADISERVER_LOG) << mError << ", client advertised" << metaPart.size() << "bytes, but the file size is" << file.size() << "bytes!"; return false; } if (mCheckChanged && !mDataChanged) { // This is invoked only when part already exists, data sizes match and // caller wants to know whether parts really differ mDataChanged = (origData != PartHelper::translateData(part)); } return true; } bool PartStreamer::preparePart(bool checkExists, const QByteArray &partName, Part &part) { mError.clear(); mDataChanged = false; const PartType partType = PartTypeHelper::fromFqName(partName); if (checkExists || mCheckChanged) { SelectQueryBuilder qb; qb.addValueCondition(Part::pimItemIdColumn(), Query::Equals, mItem.id()); qb.addValueCondition(Part::partTypeIdColumn(), Query::Equals, partType.id()); if (!qb.exec()) { mError = QStringLiteral("Unable to check item part existence"); return false; } const Part::List result = qb.result(); if (!result.isEmpty()) { part = result.at(0); } } // Shortcut: newly created parts are always "changed" if (!part.isValid()) { mDataChanged = true; } part.setPartType(partType); part.setPimItemId(mItem.id()); return true; } bool PartStreamer::stream(bool checkExists, const QByteArray &partName, qint64 &partSize, bool *changed) { mCheckChanged = (changed != nullptr); if (changed != nullptr) { *changed = false; } Part part; if (!preparePart(checkExists, partName, part)) { return false; } bool ok = streamPayload(part, partName); - if (ok && mCheckChanged) { + if (changed && ok && mCheckChanged) { *changed = mDataChanged; } partSize = part.datasize(); return ok; } bool PartStreamer::streamAttribute(bool checkExists, const QByteArray &_partName, const QByteArray &value, bool *changed) { mCheckChanged = (changed != nullptr); if (changed != nullptr) { *changed = false; } QByteArray partName; if (!_partName.startsWith("ATR:")) { partName = "ATR:" + _partName; } else { partName = _partName; } Part part; if (!preparePart(checkExists, partName, part)) { return false; } if (part.isValid()) { if (mCheckChanged) { if (PartHelper::translateData(part) != value) { mDataChanged = true; } } PartHelper::update(&part, value, value.size()); } else { const bool storeInFile = value.size() > DbConfig::configuredDatabase()->sizeThreshold(); part.setDatasize(value.size()); part.setVersion(0); if (storeInFile) { if (!part.insert()) { mError = QStringLiteral("Failed to store part in database"); return false; } PartHelper::update(&part, value, value.size()); } else { part.setData(value); if (!part.insert()) { mError = QStringLiteral("Failed to store part in database"); return false; } } } if (mCheckChanged) { *changed = mDataChanged; } return true; } diff --git a/src/shared/akremotelog.cpp b/src/shared/akremotelog.cpp index d3383f8e2..c7f0ee3b5 100644 --- a/src/shared/akremotelog.cpp +++ b/src/shared/akremotelog.cpp @@ -1,215 +1,216 @@ /* Copyright (c) 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akremotelog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define AKONADICONSOLE_SERVICE "org.kde.akonadiconsole" #define AKONADICONSOLE_LOGGER_PATH "/logger" #define AKONADICONSOLE_LOGGER_INTERFACE "org.kde.akonadiconsole.logger" namespace { class RemoteLogger : public QObject { Q_OBJECT public: explicit RemoteLogger() : mWatcher(akonadiConsoleServiceName(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration) { connect(qApp, &QCoreApplication::aboutToQuit, this, &RemoteLogger::deleteLater); sInstance = this; connect(&mWatcher, &QDBusServiceWatcher::serviceRegistered, this, &RemoteLogger::serviceRegistered); connect(&mWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &RemoteLogger::serviceUnregistered); mOldHandler = qInstallMessageHandler(dbusLogger); } ~RemoteLogger() { sInstance = nullptr; QLoggingCategory::installFilter(mOldFilter); qInstallMessageHandler(mOldHandler); mEnabled = false; delete mAkonadiConsoleInterface; } static RemoteLogger *self() { return sInstance; } private Q_SLOTS: void serviceRegistered(const QString &service) { delete mAkonadiConsoleInterface; mAkonadiConsoleInterface = new QDBusInterface(service, QStringLiteral(AKONADICONSOLE_LOGGER_PATH), QStringLiteral(AKONADICONSOLE_LOGGER_INTERFACE), QDBusConnection::sessionBus(), this); if (!mAkonadiConsoleInterface->isValid()) { delete mAkonadiConsoleInterface; + return; } connect(mAkonadiConsoleInterface, SIGNAL(enabledChanged(bool)), this, SLOT(onAkonadiConsoleLoggingEnabled(bool))); QTimer::singleShot(0, this, [this]() { auto watcher = new QDBusPendingCallWatcher(mAkonadiConsoleInterface->asyncCall(QStringLiteral("enabled"))); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { watcher->deleteLater(); QDBusPendingReply reply = *watcher; if (reply.isError()) { return; } onAkonadiConsoleLoggingEnabled(reply.argumentAt<0>()); }); }); } void serviceUnregistered(const QString &) { onAkonadiConsoleLoggingEnabled(false); delete mAkonadiConsoleInterface; mAkonadiConsoleInterface = nullptr; } void onAkonadiConsoleLoggingEnabled(bool enabled) { if (mEnabled == enabled) { return; } mEnabled = enabled; if (mEnabled) { // FIXME: Qt calls our categoryFilter from installFilter() but at that // point we cannot refer to mOldFilter yet (as we only receive it after // this call returns. So we set our category filter twice: once to get // the original Qt filter and second time to force our category filter // to be called when we already know the old filter. mOldFilter = QLoggingCategory::installFilter(categoryFilter); QLoggingCategory::installFilter(categoryFilter); } else { QLoggingCategory::installFilter(mOldFilter); mOldFilter = nullptr; } } private: QString akonadiConsoleServiceName() { QString service = QStringLiteral(AKONADICONSOLE_SERVICE); if (Akonadi::Instance::hasIdentifier()) { service += QStringLiteral("-%1").arg(Akonadi::Instance::identifier()); } return service; } static void categoryFilter(QLoggingCategory *cat) { const auto that = self(); if (!that) { return; } if (qstrncmp(cat->categoryName(), "org.kde.pim.", 12) == 0) { cat->setEnabled(QtDebugMsg, true); cat->setEnabled(QtInfoMsg, true); cat->setEnabled(QtWarningMsg, true); cat->setEnabled(QtCriticalMsg, true); } else if (that->mOldFilter) { that->mOldFilter(cat); } } static void dbusLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) { const auto that = self(); if (!that) { return; } // Log to previous logger that->mOldHandler(type, ctx, msg); if (that->mEnabled) { that->mAkonadiConsoleInterface->asyncCallWithArgumentList( QStringLiteral("message"), QList{ QDateTime::currentMSecsSinceEpoch(), qAppName(), qApp->applicationPid(), static_cast(type), QString::fromUtf8(ctx.category), QString::fromUtf8(ctx.file), QString::fromUtf8(ctx.function), ctx.line, ctx.version, msg }); } } private: QDBusServiceWatcher mWatcher; QLoggingCategory::CategoryFilter mOldFilter = nullptr; QtMessageHandler mOldHandler = nullptr; QDBusInterface *mAkonadiConsoleInterface = nullptr; bool mEnabled = false; static RemoteLogger *sInstance; }; RemoteLogger *RemoteLogger::sInstance = nullptr; } void akInitRemoteLog() { Q_ASSERT(qApp->thread() == QThread::currentThread()); if (!RemoteLogger::self()) { new RemoteLogger(); } } #include "akremotelog.moc" diff --git a/src/xml/xmldocument.h b/src/xml/xmldocument.h index cf813a9ed..8eb9d674d 100644 --- a/src/xml/xmldocument.h +++ b/src/xml/xmldocument.h @@ -1,138 +1,138 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_XMLDOCUMENT_H #define AKONADI_XMLDOCUMENT_H #include "akonadi-xml_export.h" #include "collection.h" #include #include namespace Akonadi { class XmlDocumentPrivate; /** Represents a document of the KNUT XML serialization format for Akonadi objects. */ class AKONADI_XML_EXPORT XmlDocument { public: /** Creates an empty document. */ XmlDocument(); /** Creates a new XmlDocument object and calls loadFile(). @see loadFile() */ explicit XmlDocument(const QString &fileName); ~XmlDocument(); /** Parses the given XML file and validates it. In case of an error, isValid() will return @c false and lastError() will return an error message. @see isValid(), lastError() */ - Q_REQUIRED_RESULT bool loadFile(const QString &fileName); + bool loadFile(const QString &fileName); /** Writes the current document into the given file. */ - Q_REQUIRED_RESULT bool writeToFile(const QString &fileName) const; + bool writeToFile(const QString &fileName) const; /** Returns true if the document could be parsed successfully. @see lastError() */ Q_REQUIRED_RESULT bool isValid() const; /** Returns the last error occurred during file loading/parsing. Empty if isValid() returns @c true. @see isValid() */ Q_REQUIRED_RESULT QString lastError() const; /** Returns the DOM document for this XML document. */ QDomDocument &document() const; /** Returns the DOM element representing @p collection. */ Q_REQUIRED_RESULT QDomElement collectionElement(const Collection &collection) const; /** Returns the DOM element representing the item with the given remote id */ Q_REQUIRED_RESULT QDomElement itemElementByRemoteId(const QString &rid) const; /** * Returns the DOM element representing the collection with the given remote id */ Q_REQUIRED_RESULT QDomElement collectionElementByRemoteId(const QString &rid) const; /** Returns the collection with the given remote id. */ Q_REQUIRED_RESULT Collection collectionByRemoteId(const QString &rid) const; /** Returns the item with the given remote id. */ Q_REQUIRED_RESULT Item itemByRemoteId(const QString &rid, bool includePayload = true) const; /** Returns the collections defined in this document. */ Q_REQUIRED_RESULT Collection::List collections() const; /** Returns the tags defined in this document. */ Q_REQUIRED_RESULT Tag::List tags() const; /** Returns the immediate child collections of @p parentCollection. */ Q_REQUIRED_RESULT Collection::List childCollections(const Collection &parentCollection) const; /** Returns the items in the given collection. */ Q_REQUIRED_RESULT Item::List items(const Collection &collection, bool includePayload = true) const; private: Q_DISABLE_COPY(XmlDocument) XmlDocumentPrivate *const d; }; } #endif diff --git a/src/xml/xmlwriter.h b/src/xml/xmlwriter.h index b61e6f2e0..60f77be7d 100644 --- a/src/xml/xmlwriter.h +++ b/src/xml/xmlwriter.h @@ -1,79 +1,79 @@ /* Copyright (c) 2009 Volker Krause Copyright (c) 2009 Igor Trindade Oliveira This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_XMLWRITER_H #define AKONADI_XMLWRITER_H #include "akonadi-xml_export.h" #include namespace Akonadi { class Attribute; class Collection; class Item; /** Low-level methods to serialize Akonadi objects into XML. @see Akonadi::XmlDocument */ namespace XmlWriter { /** Creates an attribute element for the given document. */ Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement attributeToElement(Attribute *attr, QDomDocument &document); /** Serializes all attributes of the given Akonadi object into the given parent element. */ AKONADI_XML_EXPORT void writeAttributes(const Item &entity, QDomElement &parentElem); /** Serializes all attributes of the given Akonadi object into the given parent element. */ AKONADI_XML_EXPORT void writeAttributes(const Collection &entity, QDomElement &parentElem); /** Creates a collection element for the given document, not yet attached to the DOM tree. */ Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement collectionToElement(const Collection &collection, QDomDocument &document); /** Serializes the given collection into a DOM element with the given parent. */ -Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement writeCollection(const Collection &collection, QDomElement &parentElem); +AKONADI_XML_EXPORT QDomElement writeCollection(const Collection &collection, QDomElement &parentElem); /** Creates an item element for the given document, not yet attached to the DOM tree */ Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement itemToElement(const Item &item, QDomDocument &document); /** Serializes the given item into a DOM element and attaches it to the given item. */ -Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement writeItem(const Item &item, QDomElement &parentElem); +AKONADI_XML_EXPORT QDomElement writeItem(const Item &item, QDomElement &parentElem); } } #endif