diff --git a/autotests/server/handlertest.cpp b/autotests/server/handlertest.cpp index 0dde702ff..05337bc67 100644 --- a/autotests/server/handlertest.cpp +++ b/autotests/server/handlertest.cpp @@ -1,197 +1,196 @@ /* Copyright (c) 2011 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 #include #include #include #include #include "handler.h" #include "handler/collectioncreatehandler.h" #include "handler/collectionfetchhandler.h" #include "handler/collectionstatsfetchhandler.h" #include "handler/collectiondeletehandler.h" #include "handler/collectionmodifyhandler.h" #include "handler/collectioncopyhandler.h" -#include "handler/collectiondeletehandler.h" #include "handler/collectionmovehandler.h" #include "handler/searchcreatehandler.h" #include "handler/searchhandler.h" #include "handler/itemfetchhandler.h" #include "handler/itemdeletehandler.h" #include "handler/itemmodifyhandler.h" #include "handler/itemcreatehandler.h" #include "handler/itemcopyhandler.h" #include "handler/itemlinkhandler.h" #include "handler/itemmovehandler.h" #include "handler/resourceselecthandler.h" #include "handler/transactionhandler.h" #include "handler/loginhandler.h" #include "handler/logouthandler.h" using namespace Akonadi; using namespace Akonadi::Server; #define MAKE_CMD_ROW( command, class ) QTest::newRow(#command) << command << QByteArray(typeid(Akonadi::Server::class).name()); class HandlerTest : public QObject { Q_OBJECT private: void setupTestData() { QTest::addColumn("command"); QTest::addColumn("className"); } void addAuthCommands() { MAKE_CMD_ROW(Protocol::Command::CreateCollection, CollectionCreateHandler) MAKE_CMD_ROW(Protocol::Command::FetchCollections, CollectionFetchHandler) MAKE_CMD_ROW(Protocol::Command::StoreSearch, SearchCreateHandler) MAKE_CMD_ROW(Protocol::Command::Search, SearchHandler) MAKE_CMD_ROW(Protocol::Command::FetchItems, ItemFetchHandler) MAKE_CMD_ROW(Protocol::Command::ModifyItems, ItemModifyHandler) MAKE_CMD_ROW(Protocol::Command::FetchCollectionStats, CollectionStatsFetchHandler) MAKE_CMD_ROW(Protocol::Command::DeleteCollection, CollectionDeleteHandler) MAKE_CMD_ROW(Protocol::Command::ModifyCollection, CollectionModifyHandler) MAKE_CMD_ROW(Protocol::Command::Transaction, TransactionHandler) MAKE_CMD_ROW(Protocol::Command::CreateItem, ItemCreateHandler) MAKE_CMD_ROW(Protocol::Command::CopyItems, ItemCopyHandler) MAKE_CMD_ROW(Protocol::Command::CopyCollection, CollectionCopyHandler) MAKE_CMD_ROW(Protocol::Command::LinkItems, ItemLinkHandler) MAKE_CMD_ROW(Protocol::Command::SelectResource, ResourceSelectHandler) MAKE_CMD_ROW(Protocol::Command::DeleteItems, ItemDeleteHandler) MAKE_CMD_ROW(Protocol::Command::MoveItems, ItemMoveHandler) MAKE_CMD_ROW(Protocol::Command::MoveCollection, CollectionMoveHandler) } void addNonAuthCommands() { MAKE_CMD_ROW(Protocol::Command::Login, LoginHandler) } void addAlwaysCommands() { MAKE_CMD_ROW(Protocol::Command::Logout, LogoutHandler) } void addInvalidCommands() { //MAKE_CMD_ROW(Protocol::Command::Invalid, UnknownCommandHandler) } template QByteArray typeName(const T &t) { const auto &v = *t.get(); return typeid(v).name(); } private Q_SLOTS: void testFindAuthenticatedCommand_data() { setupTestData(); addAuthCommands(); } void testFindAuthenticatedCommand() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); const auto handler = Handler::findHandlerForCommandAuthenticated(command); QVERIFY(handler); QCOMPARE(typeName(handler), className); } void testFindAuthenticatedCommandNegative_data() { setupTestData(); addNonAuthCommands(); addAlwaysCommands(); addInvalidCommands(); } void testFindAuthenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); const auto handler = Handler::findHandlerForCommandAuthenticated(command); QVERIFY(!handler); } void testFindNonAutenticatedCommand_data() { setupTestData(); addNonAuthCommands(); } void testFindNonAutenticatedCommand() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); auto handler = Handler::findHandlerForCommandNonAuthenticated(command); QVERIFY(handler); QCOMPARE(typeName(handler), className); } void testFindAlwaysCommand_data() { setupTestData(); addAlwaysCommands(); } void testFindAlwaysCommand() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); const auto handler = Handler::findHandlerForCommandAlwaysAllowed(command); QVERIFY(handler); QCOMPARE(typeName(handler), className); } void testFindAlwaysCommandNegative_data() { setupTestData(); addAuthCommands(); addNonAuthCommands(); addInvalidCommands(); } void testFindAlwaysCommandNegative() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); const auto handler = Handler::findHandlerForCommandAlwaysAllowed(command); QVERIFY(!handler); } }; AKTEST_MAIN(HandlerTest) #include "handlertest.moc" diff --git a/src/agentbase/agentfactory.h b/src/agentbase/agentfactory.h index 804f0ed14..a8b283559 100644 --- a/src/agentbase/agentfactory.h +++ b/src/agentbase/agentfactory.h @@ -1,107 +1,107 @@ /* This file is part of akonadiresources. 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. */ #ifndef AKONADI_AGENTFACTORY_H #define AKONADI_AGENTFACTORY_H #include "akonadiagentbase_export.h" #include "agentbase.h" #include namespace Akonadi { class AgentFactoryBasePrivate; /** * @short A factory base class for in-process agents. * * @see AKONADI_AGENT_FACTORY() * @internal * @since 4.6 */ class AKONADIAGENTBASE_EXPORT AgentFactoryBase : public QObject { Q_OBJECT public: /** * Creates a new agent factory. * Executed in the main thread, performs KDE infrastructure setup. * * @param catalogName The translation catalog of this resource. * @param parent The parent object. */ explicit AgentFactoryBase(const char *catalogName, QObject *parent = nullptr); ~AgentFactoryBase() override; public Q_SLOTS: /** - * Creates a new agent instace with the given identifier. + * Creates a new agent instance with the given identifier. */ virtual QObject *createInstance(const QString &identifier) const = 0; protected: void createComponentData(const QString &identifier) const; private: AgentFactoryBasePrivate *const d; }; /** * @short A factory for in-process agents. * * @see AKONADI_AGENT_FACTORY() * @internal * @since 4.6 */ template class AgentFactory : public AgentFactoryBase { public: /** reimplemented */ explicit AgentFactory(const char *catalogName, QObject *parent = nullptr) : AgentFactoryBase(catalogName, parent) { } QObject *createInstance(const QString &identifier) const override { createComponentData(identifier); T *instance = new T(identifier); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Akonadi::AgentBase::Observer *observer = dynamic_cast(instance); if (observer != nullptr) { instance->registerObserver(observer); } return instance; } }; } #endif diff --git a/src/agentbase/resourcebase.h b/src/agentbase/resourcebase.h index 5e699703e..30dcfa43c 100644 --- a/src/agentbase/resourcebase.h +++ b/src/agentbase/resourcebase.h @@ -1,900 +1,900 @@ /* This file is part of akonadiresources. 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. */ #ifndef AKONADI_RESOURCEBASE_H #define AKONADI_RESOURCEBASE_H #include "akonadiagentbase_export.h" #include "agentbase.h" #include "collection.h" #include "item.h" #include "itemsync.h" class KJob; class Akonadi__ResourceAdaptor; class ResourceState; namespace Akonadi { class ResourceBasePrivate; /** * @short The base class for all Akonadi resources. * * This class should be used as a base class by all resource agents, * because it encapsulates large parts of the protocol between * resource agent, agent manager and the Akonadi storage. * * It provides many convenience methods to make implementing a * new Akonadi resource agent as simple as possible. * *

How to write a resource

* * The following provides an overview of what you need to do to implement * your own Akonadi resource. In the following, the term 'backend' refers * to the entity the resource connects with Akonadi, be it a single file * or a remote server. * * @todo Complete this (online/offline state management) * *
Basic %Resource Framework
* * The following is needed to create a new resource: * - A new class deriving from Akonadi::ResourceBase, implementing at least all * pure-virtual methods, see below for further details. * - call init() in your main() function. * - a .desktop file similar to the following example * \code * [Desktop Entry] * Name=My Akonadi Resource * Type=AkonadiResource * Exec=akonadi_my_resource * Icon=my-icon * * X-Akonadi-MimeTypes= * X-Akonadi-Capabilities=Resource * X-Akonadi-Identifier=akonadi_my_resource * \endcode * *
Handling PIM Items
* * To follow item changes in the backend, the following steps are necessary: * - Implement retrieveItems() to synchronize all items in the given * collection. If the backend supports incremental retrieval, * implementing support for that is recommended to improve performance. * - Convert the items provided by the backend to Akonadi items. * This typically happens either in retrieveItems() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Item objects for every retrieved * item. It's very important that every object has its remote identifier set. * - Call itemsRetrieved() or itemsRetrievedIncremental() respectively * with the item objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any item in the Akonadi storage manually. * * To fetch item data on demand, the method retrieveItem() needs to be * reimplemented. Fetch the requested data there and call itemRetrieved() * with the result item. * * To write local changes back to the backend, you need to re-implement * the following three methods: * - itemAdded() * - itemChanged() * - itemRemoved() * * Note that these three functions don't get the full payload of the items by default, * you need to change the item fetch scope of the change recorder to fetch the full * payload. This can be expensive with big payloads, though.
* Once you have handled changes in itemAdded() and itemChanged(), call changeCommitted(). * Once you have handled changes in itemRemoved(), call changeProcessed(); * These methods are called whenever a local item related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * *
Handling Collections
* * To follow collection changes in the backend, the following steps are necessary: * - Implement retrieveCollections() to retrieve collections from the backend. * If the backend supports incremental collections updates, implementing * support for that is recommended to improve performance. * - Convert the collections of the backend to Akonadi collections. * This typically happens either in retrieveCollections() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Collection objects for every retrieved * collection. It's very important that every object has its remote identifier * and its parent remote identifier set. * - Call collectionsRetrieved() or collectionsRetrievedIncremental() respectively * with the collection objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any collection in the Akonadi storage manually. * * * To write local collection changes back to the backend, you need to re-implement * the following three methods: * - collectionAdded() * - collectionChanged() * - collectionRemoved() * Once you have handled changes in collectionAdded() and collectionChanged(), call changeCommitted(). * Once you have handled changes in collectionRemoved(), call changeProcessed(); * These methods are called whenever a local collection related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * * @todo Convenience base class for collection-less resources */ // FIXME_API: API dox need to be updated for Observer approach (kevin) class AKONADIAGENTBASE_EXPORT ResourceBase : public AgentBase { Q_OBJECT public: /** * Use this method in the main function of your resource * application to initialize your resource subclass. * This method also takes care of creating a KApplication * object and parsing command line arguments. * * @note In case the given class is also derived from AgentBase::Observer * it gets registered as its own observer (see AgentBase::Observer), e.g. * resourceInstance->registerObserver( resourceInstance ); * * @code * * class MyResource : public ResourceBase * { * ... * }; * * int main( int argc, char **argv ) * { * return ResourceBase::init( argc, argv ); * } * * @endcode * * @param argc number of arguments * @param argv string arguments */ template static int init(int argc, char **argv) { // Disable session management qunsetenv("SESSION_MANAGER"); QApplication app(argc, argv); debugAgent(argc, argv); const QString id = parseArguments(argc, argv); T r(id); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Observer *observer = dynamic_cast(&r); if (observer != nullptr) { r.registerObserver(observer); } return init(r); } /** * This method is used to set the name of the resource. */ void setName(const QString &name); /** * Returns the name of the resource. */ Q_REQUIRED_RESULT QString name() const; /** * Enable or disable automatic progress reporting. By default, it is enabled. * When enabled, the resource will automatically emit the signals percent() and status() * while syncing items or collections. * * The automatic progress reporting is done on a per item / per collection basis, so if a * finer granularity is desired, automatic reporting should be disabled and the subclass should * emit the percent() and status() signals itself. * * @param enabled Whether or not automatic emission of the signals is enabled. * @since 4.7 */ void setAutomaticProgressReporting(bool enabled); Q_SIGNALS: /** * This signal is emitted whenever the name of the resource has changed. * * @param name The new name of the resource. */ void nameChanged(const QString &name); /** * Emitted when a full synchronization has been completed. */ void synchronized(); /** * Emitted when a collection attributes synchronization has been completed. * * @param collectionId The identifier of the collection whose attributes got synchronized. * @since 4.6 */ void attributesSynchronized(qlonglong collectionId); /** * Emitted when a collection tree synchronization has been completed. * * @since 4.8 */ void collectionTreeSynchronized(); /** * Emitted when the item synchronization processed the current batch and is ready for a new one. * Use this to throttle the delivery to not overload Akonadi. * * Throttling can be used during item retrieval (retrieveItems(Akonadi::Collection)) in streaming mode. * To throttle only deliver itemSyncBatchSize() items, and wait for this signal, then again deliver * @param remainingBatchSize items. * * By always only providing the number of items required to process the batch, the items don't pile * up in memory and items are only provided as fast as Akonadi can process them. * * @see batchSize() * * @since 4.14 */ void retrieveNextItemSyncBatch(int remainingBatchSize); protected Q_SLOTS: /** * Retrieve the collection tree from the remote server and supply it via * collectionsRetrieved() or collectionsRetrievedIncremental(). * @see collectionsRetrieved(), collectionsRetrievedIncremental() */ virtual void retrieveCollections() = 0; /** * Retrieve all tags from the backend * @see tagsRetrieved() */ virtual void retrieveTags(); /** * Retrieve all relations from the backend * @see relationsRetrieved() */ virtual void retrieveRelations(); /** * Retrieve the attributes of a single collection from the backend. The * collection to retrieve attributes for is provided as @p collection. * Add the attributes parts and call collectionAttributesRetrieved() * when done. * * @param collection The collection whose attributes should be retrieved. * @see collectionAttributesRetrieved() * @since 4.6 */ virtual void retrieveCollectionAttributes(const Akonadi::Collection &collection); /** * Retrieve all (new/changed) items in collection @p collection. * It is recommended to use incremental retrieval if the backend supports that * and provide the result by calling itemsRetrievedIncremental(). * If incremental retrieval is not possible, provide the full listing by calling * itemsRetrieved( const Item::List& ). * In any case, ensure that all items have a correctly set remote identifier * to allow synchronizing with items already existing locally. * In case you don't want to use the built-in item syncing code, store the retrieved * items manually and call itemsRetrieved() once you are done. * @param collection The collection whose items to retrieve. * @see itemsRetrieved( const Item::List& ), itemsRetrievedIncremental(), itemsRetrieved(), currentCollection(), batchSize() */ virtual void retrieveItems(const Akonadi::Collection &collection) = 0; /** * Returns the batch size used during the item sync. * * This can be used to throttle the item delivery. * * @see retrieveNextItemSyncBatch(int), retrieveItems(Akonadi::Collection) * @since 4.14 */ int itemSyncBatchSize() const; /** * Set the batch size used during the item sync. * The default is 10. * * @see retrieveNextItemSyncBatch(int) * @since 4.14 */ void setItemSyncBatchSize(int batchSize); /** - * Set to true to scheudle an attribute sync before every item sync. + * Set to true to schedule an attribute sync before every item sync. * The default is false. * * @since 4.15 */ void setScheduleAttributeSyncBeforeItemSync(bool); /** * Retrieve a single item from the backend. The item to retrieve is provided as @p item. * Add the requested payload parts and call itemRetrieved() when done. * @param item The empty item whose payload should be retrieved. Use this object when delivering * the result instead of creating a new item to ensure conflict detection will work. * @param parts The item parts that should be retrieved. * @return false if there is an immediate error when retrieving the item. * @see itemRetrieved() * @deprecated Use retrieveItems(const Akonadi::Item::List &, const QSet &) instead. */ AKONADIAGENTBASE_DEPRECATED virtual bool retrieveItem(const Akonadi::Item &item, const QSet &parts); /** * Retrieve given @p items from the backend. * Add the requested payload parts and call itemsRetrieved() when done. * It is guaranteed that all @p items in the list belong to the same Collection. * * @param items The items whose payload should be retrieved. Use those objects * when delivering the result instead of creating new items to ensure conflict * detection will work. * @param parts The item parts that should be retrieved. * @return false if there is an immediate error when retrieving the items. * @see itemsRetrieved() * @since 5.4 * * @todo: Make this method pure virtual once retrieveItem() is gone */ virtual bool retrieveItems(const Akonadi::Item::List &items, const QSet &parts); /** * Abort any activity in progress in the backend. By default this method does nothing. * * @since 4.6 */ virtual void abortActivity(); /** * Dump resource internals, for debugging. * @since 4.9 */ virtual QString dumpResourceToString() const { return QString(); } protected: /** * Creates a base resource. * * @param id The instance id of the resource. */ ResourceBase(const QString &id); /** * Destroys the base resource. */ ~ResourceBase() override; /** * Call this method from retrieveItem() once the result is available. * * @param item The retrieved item. */ void itemRetrieved(const Item &item); /** * Call this method from retrieveCollectionAttributes() once the result is available. * * @param collection The collection whose attributes got retrieved. * @since 4.6 */ void collectionAttributesRetrieved(const Collection &collection); /** * Resets the dirty flag of the given item and updates the remote id. * * Call whenever you have successfully written changes back to the server. * This implicitly calls changeProcessed(). * @param item The changed item. */ void changeCommitted(const Item &item); /** * Resets the dirty flag of all given items and updates remote ids. * * Call whenever you have successfully written changes back to the server. * This implicitly calls changeProcessed(). * @param items Changed items * * @since 4.11 */ void changesCommitted(const Item::List &items); /** * Resets the dirty flag of the given tag and updates the remote id. * * Call whenever you have successfully written changes back to the server. * This implicitly calls changeProcessed(). * @param tag Changed tag. * * @since 4.13 */ void changeCommitted(const Tag &tag); /** * Call whenever you have successfully handled or ignored a collection * change notification. * * This will update the remote identifier of @p collection if necessary, * as well as any other collection attributes. * This implicitly calls changeProcessed(). * @param collection The collection which changes have been handled. */ void changeCommitted(const Collection &collection); /** * Call this to supply the full folder tree retrieved from the remote server. * * @param collections A list of collections. * @see collectionsRetrievedIncremental() */ void collectionsRetrieved(const Collection::List &collections); void tagsRetrieved(const Tag::List &tags, const QHash &tagMembers); void relationsRetrieved(const Relation::List &relations); /** * Call this to supply incrementally retrieved collections from the remote server. * * @param changedCollections Collections that have been added or changed. * @param removedCollections Collections that have been deleted. * @see collectionsRetrieved() */ void collectionsRetrievedIncremental(const Collection::List &changedCollections, const Collection::List &removedCollections); /** * Enable collection streaming, that is collections don't have to be delivered at once * as result of a retrieveCollections() call but can be delivered by multiple calls * to collectionsRetrieved() or collectionsRetrievedIncremental(). When all collections * have been retrieved, call collectionsRetrievalDone(). * @param enable @c true if collection streaming should be enabled, @c false by default */ void setCollectionStreamingEnabled(bool enable); /** * Call this method to indicate you finished synchronizing the collection tree. * * This is not needed if you use the built in syncing without collection streaming * and call collectionsRetrieved() or collectionRetrievedIncremental() instead. * If collection streaming is enabled, call this method once all collections have been delivered * using collectionsRetrieved() or collectionsRetrievedIncremental(). */ void collectionsRetrievalDone(); /** * Allows to keep locally changed collection parts during the collection sync. * * This is useful for resources to be able to provide default values during the collection * sync, while preserving eventual more up-to date values. * * Valid values are attribute types and "CONTENTMIMETYPE" for the collections content mimetypes. * * By default this is enabled for the EntityDisplayAttribute. * * @param parts A set parts for which local changes should be preserved. * @since 4.14 */ void setKeepLocalCollectionChanges(const QSet &parts); /** * Call this method to supply the full collection listing from the remote server. Items not present in the list * will be dropped from the Akonadi database. * * If the remote server supports incremental listing, it's strongly * recommended to use itemsRetrievedIncremental() instead. * @param items A list of items. * @see itemsRetrievedIncremental(). */ void itemsRetrieved(const Item::List &items); /** * Call this method when you want to use the itemsRetrieved() method * in streaming mode and indicate the amount of items that will arrive * that way. * * @warning By default this will end the item sync automatically once * sufficient items were delivered. To disable this and only make use * of the progress reporting, use setDisableAutomaticItemDeliveryDone() * * @note The recommended way is therefore: * @code * setDisableAutomaticItemDeliveryDone(true); * setItemStreamingEnabled(true); * setTotalItems(X); // X = sum of all items in all batches * while (...) { * itemsRetrievedIncremental(...); * // or itemsRetrieved(...); * } * itemsRetrievalDone(); * @endcode * * @param amount number of items that will arrive in streaming mode * @see setDisableAutomaticItemDeliveryDone(bool) * @see setItemStreamingEnabled(bool) */ void setTotalItems(int amount); /** * Disables the automatic completion of the item sync, * based on the number of delivered items. * * This ensures that the item sync only finishes once itemsRetrieved() * is called, while still making it possible to use the automatic progress * reporting based on setTotalItems(). * * @note This needs to be called once, before the item sync is started. * * @see setTotalItems(int) * @since 4.14 */ void setDisableAutomaticItemDeliveryDone(bool disable); /** * Enable item streaming, which is disabled by default. * Item streaming means that the resource can call setTotalItems(), * and then itemsRetrieved() or itemsRetrievedIncremental() multiple times, * in chunks. When all is done, the resource should call itemsRetrievalDone(). * @param enable @c true if items are delivered in chunks rather in one big block. * @see setTotalItems(int) */ void setItemStreamingEnabled(bool enable); /** * Set transaction mode for item sync'ing. * @param mode item transaction mode * @see Akonadi::ItemSync::TransactionMode * @since 4.6 */ void setItemTransactionMode(ItemSync::TransactionMode mode); /** * Set merge mode for item sync'ing. * * Default merge mode is RIDMerge. * * @note This method must be called before first call to itemRetrieved(), * itemsRetrieved() or itemsRetrievedIncremental(). * * @param mode Item merging mode (see ItemCreateJob for details on item merging) * @see Akonadi::ItemSync::MergeMode * @since 4.14.11 */ void setItemMergingMode(ItemSync::MergeMode mode); /** * Set the fetch scope applied for item synchronization. * By default, the one set on the changeRecorder() is used. However, it can make sense * to specify a specialized fetch scope for synchronization to improve performance. * The rule of thumb is to remove anything from this fetch scope that does not provide * additional information regarding whether and item has changed or not. This is primarily * relevant for backends not supporting incremental retrieval. * @param fetchScope The fetch scope to use by the internal Akonadi::ItemSync instance. * @see Akonadi::ItemSync * @since 4.6 */ void setItemSynchronizationFetchScope(const ItemFetchScope &fetchScope); /** * Call this method to supply incrementally retrieved items from the remote server. * * @param changedItems Items changed in the backend. * @param removedItems Items removed from the backend. */ void itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems); /** * Call this method to indicate you finished synchronizing the current collection. * * This is not needed if you use the built in syncing without item streaming * and call itemsRetrieved() or itemsRetrievedIncremental() instead. * If item streaming is enabled, call this method once all items have been delivered * using itemsRetrieved() or itemsRetrievedIncremental(). * @see retrieveItems() */ void itemsRetrievalDone(); /** * Call this method to remove all items and collections of the resource from the * server cache. * * The method should not be used anymore * * @see invalidateCache() * @since 4.3 */ void clearCache(); /** * Call this method to invalidate all cached content in @p collection. * * The method should be used when the backend indicated that the cached content * is no longer valid. * * @param collection parent of the content to be invalidated in cache * @since 4.8 */ void invalidateCache(const Collection &collection); /** * Returns the collection that is currently synchronized. * @note Calling this method is only allowed during a collection synchronization task, that * is directly or indirectly from retrieveItems(). */ Collection currentCollection() const; /** * Returns the item that is currently retrieved. * @note Calling this method is only allowed during fetching a single item, that * is directly or indirectly from retrieveItem(). */ AKONADIAGENTBASE_DEPRECATED Item currentItem() const; /** * Returns the items that are currently retrieved. * @note Calling this method is only allowed during item fetch, that is * directly or indirectly from retrieveItems(Akonadi::Item::List,QSet) */ Item::List currentItems() const; /** * This method is called whenever the resource should start synchronize all data. */ void synchronize(); /** * This method is called whenever the collection with the given @p id * shall be synchronized. */ void synchronizeCollection(qint64 id); /** * This method is called whenever the collection with the given @p id * shall be synchronized. * @param recursive if true, a recursive synchronization is done */ void synchronizeCollection(qint64 id, bool recursive); /** * This method is called whenever the collection with the given @p id * shall have its attributes synchronized. * * @param id The id of the collection to synchronize * @since 4.6 */ void synchronizeCollectionAttributes(qint64 id); /** * Synchronizes the collection attributes. * * @param col The id of the collection to synchronize * @since 4.15 */ void synchronizeCollectionAttributes(const Akonadi::Collection &col); /** * Refetches the Collections. */ void synchronizeCollectionTree(); /** * Refetches Tags. */ void synchronizeTags(); /** * Refetches Relations. */ void synchronizeRelations(); /** * Stops the execution of the current task and continues with the next one. */ void cancelTask(); /** * Stops the execution of the current task and continues with the next one. * Additionally an error message is emitted. * @param error additional error message to be emitted */ void cancelTask(const QString &error); /** * Suspends the execution of the current task and tries again to execute it. * * This can be used to delay the task processing until the resource has reached a safe * state, e.g. login to a server succeeded. * * @note This does not change the order of tasks so if there is no task with higher priority * e.g. a custom task added with #Prepend the deferred task will be processed again. * * @since 4.3 */ void deferTask(); /** * Inherited from AgentBase. * * When going offline, the scheduler aborts the current task, so you should * do the same in your resource, if the task implementation is asynchronous. */ void doSetOnline(bool online) override; /** * Indicate the use of hierarchical remote identifiers. * * This means that it is possible to have two different items with the same * remoteId in different Collections. * * This should be called in the resource constructor as needed. * * @param enable whether to enable use of hierarchical remote identifiers * @since 4.4 */ void setHierarchicalRemoteIdentifiersEnabled(bool enable); friend class ResourceScheduler; friend class ::ResourceState; /** * Describes the scheduling priority of a task that has been queued * for execution. * * @see scheduleCustomTask * @since 4.4 */ enum SchedulePriority { Prepend, ///< The task will be executed as soon as the current task has finished. AfterChangeReplay, ///< The task is scheduled after the last ChangeReplay task in the queue Append ///< The task will be executed after all tasks currently in the queue are finished }; /** * Schedules a custom task in the internal scheduler. It will be queued with * all other tasks such as change replays and retrieval requests and eventually * executed by calling the specified method. With the priority parameter the * time of execution of the Task can be influenced. @see SchedulePriority * @param receiver The object the slot should be called on. * @param method The name of the method (and only the name, not signature, not SLOT(...) macro), * that should be called to execute this task. The method has to be a slot and take a QVariant as * argument. * @param argument A QVariant argument passed to the method specified above. Use this to pass task * parameters. * @param priority Priority of the task. Use this to influence the position in * the execution queue. * @since 4.4 */ void scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument, SchedulePriority priority = Append); /** * Indicate that the current task is finished. Use this method from the slot called via scheduleCustomTaks(). * As with all the other callbacks, make sure to either call taskDone() or cancelTask()/deferTask() on all * exit paths, otherwise the resource will hang. * @since 4.4 */ void taskDone(); /** * Dump the contents of the current ChangeReplay * @since 4.8.1 */ QString dumpNotificationListToString() const; /** * Dumps memory usage information to stdout. * For now it outputs the result of glibc's mallinfo(). * This is useful to check if your memory problems are due to poor management by glibc. * Look for a high value on fsmblks when interpreting results. * man mallinfo for more details. * @since 4.11 */ void dumpMemoryInfo() const; /** * Returns a string with memory usage information. * @see dumpMemoryInfo() * * @since 4.11 */ QString dumpMemoryInfoToString() const; /** * Dump the state of the scheduler * @since 4.8.1 */ QString dumpSchedulerToString() const; private: static QString parseArguments(int argc, char **argv); static int init(ResourceBase &r); // dbus resource interface friend class ::Akonadi__ResourceAdaptor; QString requestItemDelivery(const QList &uids, const QByteArrayList &parts); private: Q_DECLARE_PRIVATE(ResourceBase) Q_PRIVATE_SLOT(d_func(), void slotAbortRequested()) Q_PRIVATE_SLOT(d_func(), void slotDeliveryDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotCollectionSyncDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotDeleteResourceCollection()) Q_PRIVATE_SLOT(d_func(), void slotDeleteResourceCollectionDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotCollectionDeletionDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotInvalidateCache(const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void slotLocalListDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotSynchronizeCollection(const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void slotCollectionListDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotSynchronizeCollectionAttributes(const Akonadi::Collection &)) Q_PRIVATE_SLOT(d_func(), void slotCollectionListForAttributesDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotCollectionAttributesSyncDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotItemSyncDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotPercent(KJob *, unsigned long)) Q_PRIVATE_SLOT(d_func(), void slotDelayedEmitProgress()) Q_PRIVATE_SLOT(d_func(), void slotPrepareItemRetrieval(const Akonadi::Item &items)) Q_PRIVATE_SLOT(d_func(), void slotPrepareItemRetrievalResult(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotPrepareItemsRetrieval(const QVector &items)) Q_PRIVATE_SLOT(d_func(), void slotPrepareItemsRetrievalResult(KJob *)) Q_PRIVATE_SLOT(d_func(), void changeCommittedResult(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotSessionReconnected()) Q_PRIVATE_SLOT(d_func(), void slotRecursiveMoveReplay(RecursiveMover *)) Q_PRIVATE_SLOT(d_func(), void slotRecursiveMoveReplayResult(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotTagSyncDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotRelationSyncDone(KJob *job)) Q_PRIVATE_SLOT(d_func(), void slotSynchronizeTags()) Q_PRIVATE_SLOT(d_func(), void slotSynchronizeRelations()) Q_PRIVATE_SLOT(d_func(), void slotItemRetrievalCollectionFetchDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void slotAttributeRetrievalCollectionFetchDone(KJob *)) }; } #ifndef AKONADI_RESOURCE_MAIN /** * Convenience Macro for the most common main() function for Akonadi resources. */ #define AKONADI_RESOURCE_MAIN( resourceClass ) \ int main( int argc, char **argv ) \ { \ return Akonadi::ResourceBase::init( argc, argv ); \ } #endif #endif diff --git a/src/core/agentconfigurationmanager_p.h b/src/core/agentconfigurationmanager_p.h index 2c6a62ddc..9726af31c 100644 --- a/src/core/agentconfigurationmanager_p.h +++ b/src/core/agentconfigurationmanager_p.h @@ -1,54 +1,54 @@ /* 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. */ -#ifndef AKONADI_AGENTCONFIGURATIOMANAGER_H -#define AKONADI_AGENTCONFIGURATIOMANAGER_H +#ifndef AKONADI_AGENTCONFIGURATIOMANAGER_P_H +#define AKONADI_AGENTCONFIGURATIOMANAGER_P_H #include #include "akonadicore_export.h" namespace Akonadi { class AKONADICORE_EXPORT AgentConfigurationManager : public QObject { Q_OBJECT public: static AgentConfigurationManager *self(); ~AgentConfigurationManager() override; bool registerInstanceConfiguration(const QString &instance); void unregisterInstanceConfiguration(const QString &instance); bool isInstanceRegistered(const QString &instance) const; QString findConfigPlugin(const QString &type) const; private: AgentConfigurationManager(QObject *parent = nullptr); class Private; friend class Private; QScopedPointer const d; static AgentConfigurationManager *sInstance; }; } #endif diff --git a/src/core/changerecorderjournal_p.h b/src/core/changerecorderjournal_p.h index a2e104c42..5b2c5d6f6 100644 --- a/src/core/changerecorderjournal_p.h +++ b/src/core/changerecorderjournal_p.h @@ -1,100 +1,100 @@ /* Copyright (c) 2007 Volker Krause 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. */ -#ifndef CHANGERACORDERJOURNAL_P_H -#define CHANGERACORDERJOURNAL_P_H +#ifndef CHANGERECORDERJOURNAL_P_H +#define CHANGERECORDERJOURNAL_P_H #include "private/protocol_p.h" #include "akonaditests_export.h" class QSettings; class QFile; namespace Akonadi { class AKONADI_TESTS_EXPORT ChangeRecorderJournalReader { public: enum LegacyType { InvalidType, Item, Collection, Tag, Relation }; // Ancient QSettings legacy store static Protocol::ChangeNotificationPtr loadQSettingsNotification(QSettings *settings); static QQueue loadFrom(QFile *device, bool &needsFullSave); private: enum LegacyOp { InvalidOp, Add, Modify, Move, Remove, Link, Unlink, Subscribe, Unsubscribe, ModifyFlags, ModifyTags, ModifyRelations }; static Protocol::ChangeNotificationPtr loadQSettingsItemNotification(QSettings *settings); static Protocol::ChangeNotificationPtr loadQSettingsCollectionNotification(QSettings *settings); // More modern mechanisms static Protocol::ChangeNotificationPtr loadItemNotification(QDataStream &stream, quint64 version); static Protocol::ChangeNotificationPtr loadCollectionNotification(QDataStream &stream, quint64 version); static Protocol::ChangeNotificationPtr loadTagNotification(QDataStream &stream, quint64 version); static Protocol::ChangeNotificationPtr loadRelationNotification(QDataStream &stream, quint64 version); static Protocol::ItemChangeNotification::Operation mapItemOperation(LegacyOp op); static Protocol::CollectionChangeNotification::Operation mapCollectionOperation(LegacyOp op); static Protocol::TagChangeNotification::Operation mapTagOperation(LegacyOp op); static Protocol::RelationChangeNotification::Operation mapRelationOperation(LegacyOp op); static QSet extractRelations(QSet &flags); }; class AKONADI_TESTS_EXPORT ChangeRecorderJournalWriter { public: static void saveTo(const QQueue &changes, QIODevice *device); private: static ChangeRecorderJournalReader::LegacyType mapToLegacyType(Protocol::Command::Type type); static void saveItemNotification(QDataStream &stream, const Protocol::ItemChangeNotification &ntf); static void saveCollectionNotification(QDataStream &stream, const Protocol::CollectionChangeNotification &ntf); static void saveTagNotification(QDataStream &stream, const Protocol::TagChangeNotification &ntf); static void saveRelationNotification(QDataStream &stream, const Protocol::RelationChangeNotification &ntf); static QSet encodeRelations(const QSet &relations); }; } // namespace #endif diff --git a/src/core/collection.h b/src/core/collection.h index a6b92452f..c5dc2305b 100644 --- a/src/core/collection.h +++ b/src/core/collection.h @@ -1,609 +1,609 @@ /* 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. */ #ifndef AKONADI_COLLECTION_H #define AKONADI_COLLECTION_H #include "akonadicore_export.h" #include "attribute.h" #include #include #include class QUrl; namespace Akonadi { class CachePolicy; class CollectionPrivate; class CollectionStatistics; /** * @short Represents a collection of PIM items. * * This class represents a collection of PIM items, such as a folder on a mail- or * groupware-server. * * Collections are hierarchical, i.e., they may have a parent collection. * * @code * * using namespace Akonadi; * * // fetching all collections recursive, starting at the root collection * CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); * connect( job, SIGNAL(result(KJob*)), SLOT(fetchFinished(KJob*)) ); * * ... * * MyClass::fetchFinished( KJob *job ) * { * if ( job->error() ) { * qDebug() << "Error occurred"; * return; * } * * CollectionFetchJob *fetchJob = qobject_cast( job ); * * const Collection::List collections = fetchJob->collections(); * foreach ( const Collection &collection, collections ) { * qDebug() << "Name:" << collection.name(); * } * } * * @endcode * * @author Volker Krause */ class AKONADICORE_EXPORT Collection { public: /** * Describes the unique id type. */ typedef qint64 Id; /** * Describes a list of collections. */ typedef QVector List; /** * Describes rights of a collection. */ enum Right { ReadOnly = 0x0, ///< Can only read items or subcollection of this collection CanChangeItem = 0x1, ///< Can change items in this collection CanCreateItem = 0x2, ///< Can create new items in this collection CanDeleteItem = 0x4, ///< Can delete items in this collection CanChangeCollection = 0x8, ///< Can change this collection CanCreateCollection = 0x10, ///< Can create new subcollections in this collection CanDeleteCollection = 0x20, ///< Can delete this collection CanLinkItem = 0x40, ///< Can create links to existing items in this virtual collection @since 4.4 CanUnlinkItem = 0x80, ///< Can remove links to items in this virtual collection @since 4.4 AllRights = (CanChangeItem | CanCreateItem | CanDeleteItem | CanChangeCollection | CanCreateCollection | CanDeleteCollection) ///< Has all rights on this storage collection }; Q_DECLARE_FLAGS(Rights, Right) /** * Creates an invalid collection. */ Collection(); /** * Create a new collection. * * @param id The unique identifier of the collection. */ explicit Collection(Id id); /** * Destroys the collection. */ ~Collection(); /** * Creates a collection from an @p other collection. */ Collection(const Collection &other); /** * Creates a collection from the given @p url. */ static Collection fromUrl(const QUrl &url); /** * Sets the unique @p identifier of the collection. */ void setId(Id identifier); /** * Returns the unique identifier of the collection. */ Q_REQUIRED_RESULT Id id() const; /** * Sets the remote @p id of the collection. */ void setRemoteId(const QString &id); /** * Returns the remote id of the collection. */ Q_REQUIRED_RESULT QString remoteId() const; /** * Sets the remote @p revision of the collection. * @param revision the collections's remote revision * The remote revision can be used by resources to store some * revision information of the backend to detect changes there. * * @note This method is supposed to be used by resources only. * @since 4.5 */ void setRemoteRevision(const QString &revision); /** * Returns the remote revision of the collection. * * @note This method is supposed to be used by resources only. * @since 4.5 */ Q_REQUIRED_RESULT QString remoteRevision() const; /** * Returns whether the collection is valid. */ Q_REQUIRED_RESULT bool isValid() const; /** * Returns whether this collections's id equals the * id of the @p other collection. */ Q_REQUIRED_RESULT bool operator==(const Collection &other) const; /** * Returns whether the collection's id does not equal the id * of the @p other collection. */ Q_REQUIRED_RESULT bool operator!=(const Collection &other) const; /** * Assigns the @p other to this collection and returns a reference to this * collection. * @param other the collection to assign */ Collection &operator=(const Collection &other); /** * @internal For use with containers only. * * @since 4.8 */ Q_REQUIRED_RESULT bool operator<(const Collection &other) const; /** * Returns the parent collection of this object. - * @note This will of course only return a useful value if it was explictely retrieved + * @note This will of course only return a useful value if it was explicitely retrieved * from the Akonadi server. * @since 4.4 */ Q_REQUIRED_RESULT Collection parentCollection() const; /** * Returns a reference to the parent collection of this object. - * @note This will of course only return a useful value if it was explictely retrieved + * @note This will of course only return a useful value if it was explicitely retrieved * from the Akonadi server. * @since 4.4 */ Q_REQUIRED_RESULT Collection &parentCollection(); /** * Set the parent collection of this object. * @note Calling this method has no immediate effect for the object itself, * such as being moved to another collection. * It is mainly relevant to provide a context for RID-based operations * inside resources. * @param parent The parent collection. * @since 4.4 */ void setParentCollection(const Collection &parent); /** * Adds an attribute to the collection. * * If an attribute of the same type name already exists, it is deleted and * replaced with the new one. * * @param attribute The new attribute. * * @note The collection takes the ownership of the attribute. */ void addAttribute(Attribute *attribute); /** * Removes and deletes the attribute of the given type @p name. */ void removeAttribute(const QByteArray &name); /** * Returns @c true if the collection has an attribute of the given type @p name, * false otherwise. */ bool hasAttribute(const QByteArray &name) const; /** * Returns a list of all attributes of the collection. * * @warning Do not modify the attributes returned from this method, * the change will not be reflected when updating the Collection * through CollectionModifyJob. */ Q_REQUIRED_RESULT Attribute::List attributes() const; /** * Removes and deletes all attributes of the collection. */ void clearAttributes(); /** * Returns the attribute of the given type @p name if available, 0 otherwise. */ Attribute *attribute(const QByteArray &name); const Attribute *attribute(const QByteArray &name) const; /** * Describes the options that can be passed to access attributes. */ enum CreateOption { AddIfMissing, ///< Creates the attribute if it is missing DontCreate ///< Default value }; /** * Returns the attribute of the requested type. * If the collection has no attribute of that type yet, passing AddIfMissing * as an argument will create and add it to the entity * * @param option The create options. */ template inline T *attribute(CreateOption option = DontCreate); /** * Returns the attribute of the requested type or 0 if it is not available. */ template inline const T *attribute() const; /** * Removes and deletes the attribute of the requested type. */ template inline void removeAttribute(); /** * Returns whether the collection has an attribute of the requested type. */ template inline bool hasAttribute() const; /** * Returns the i18n'ed name of the collection. */ Q_REQUIRED_RESULT QString name() const; /** * Returns the display name (EntityDisplayAttribute::displayName()) if set, * and Collection::name() otherwise. For human-readable strings this is preferred * over Collection::name(). * * @since 4.11 */ Q_REQUIRED_RESULT QString displayName() const; /** * Sets the i18n'ed name of the collection. * * @param name The new collection name. */ void setName(const QString &name); /** * Returns the rights the user has on the collection. */ Q_REQUIRED_RESULT Rights rights() const; /** * Sets the @p rights the user has on the collection. */ void setRights(Rights rights); /** * Returns a list of possible content mimetypes, * e.g. message/rfc822, x-akonadi/collection for a mail folder that * supports sub-folders. */ Q_REQUIRED_RESULT QStringList contentMimeTypes() const; /** * Sets the list of possible content mime @p types. */ void setContentMimeTypes(const QStringList &types); /** * Returns the root collection. */ Q_REQUIRED_RESULT static Collection root(); /** * Returns the mimetype used for collections. */ Q_REQUIRED_RESULT static QString mimeType(); /** * Returns the mimetype used for virtual collections * * @since 4.11 */ Q_REQUIRED_RESULT static QString virtualMimeType(); /** * Returns the identifier of the resource owning the collection. */ Q_REQUIRED_RESULT QString resource() const; /** * Sets the @p identifier of the resource owning the collection. */ void setResource(const QString &identifier); /** * Returns the cache policy of the collection. */ Q_REQUIRED_RESULT CachePolicy cachePolicy() const; /** * Sets the cache @p policy of the collection. */ void setCachePolicy(const CachePolicy &policy); /** * Returns the collection statistics of the collection. */ Q_REQUIRED_RESULT CollectionStatistics statistics() const; /** * Sets the collection @p statistics for the collection. */ void setStatistics(const CollectionStatistics &statistics); /** * Describes the type of url which is returned in url(). * * @since 4.7 */ enum UrlType { UrlShort = 0, ///< A short url which contains the identifier only (equivalent to url()) UrlWithName = 1 ///< A url with identifier and name }; /** * Returns the url of the collection. * @param type the type of url * @since 4.7 */ Q_REQUIRED_RESULT QUrl url(UrlType type = UrlShort) const; /** * Returns whether the collection is virtual, for example a search collection. * * @since 4.6 */ Q_REQUIRED_RESULT bool isVirtual() const; /** * Sets whether the collection is virtual or not. * Virtual collections can't be converted to non-virtual and vice versa. * @param isVirtual virtual collection if @c true, otherwise a normal collection * @since 4.10 */ void setVirtual(bool isVirtual); /** * Sets the collection's enabled state. * * Use this mechanism to set if a collection should be available * to the user or not. * * This can be used in conjunction with the local list preference for finer grained control * to define if a collection should be included depending on the purpose. * * For example: A collection is by default enabled, meaning it is displayed to the user, synchronized by the resource, * and indexed by the indexer. A disabled collection on the other hand is not displayed, synchronized or indexed. * The local list preference allows to locally override that default value for each purpose individually. * * The enabled state can be synchronized by backends. * E.g. an imap resource may synchronize this with the subscription state. * * @since 4.14 * @see setLocalListPreference, setShouldList */ void setEnabled(bool enabled); /** * Returns the collection's enabled state. * @since 4.14 * @see localListPreference */ Q_REQUIRED_RESULT bool enabled() const; /** * Describes the list preference value * * @since 4.14 */ enum ListPreference { ListEnabled, ///< Enable collection for specified purpose ListDisabled, ///< Disable collection for specified purpose ListDefault ///< Fallback to enabled state }; /** * Describes the purpose of the listing * * @since 4.14 */ enum ListPurpose { ListSync, ///< Listing for synchronization ListDisplay, ///< Listing for display to the user ListIndex ///< Listing for indexing the content }; /** * Sets the local list preference for the specified purpose. * * The local list preference overrides the enabled state unless set to ListDefault. * In case of ListDefault the enabled state should be taken as fallback (shouldList() implements this logic). * * The default value is ListDefault. * * @since 4.14 * @see shouldList, setEnabled */ void setLocalListPreference(ListPurpose purpose, ListPreference preference); /** * Returns the local list preference for the specified purpose. * @since 4.14 * @see setLocalListPreference */ Q_REQUIRED_RESULT ListPreference localListPreference(ListPurpose purpose) const; /** * Returns whether the collection should be listed or not for the specified purpose * Takes enabled state and local preference into account. * * @since 4.14 * @see setLocalListPreference, setEnabled */ Q_REQUIRED_RESULT bool shouldList(ListPurpose purpose) const; /** * Sets whether the collection should be listed or not for the specified purpose. * Takes enabled state and local preference into account. * * Use this instead of sestEnabled and setLocalListPreference to automatically set * the right setting. * * @since 4.14 * @see setLocalListPreference, setEnabled */ void setShouldList(ListPurpose purpose, bool shouldList); /** * Set during sync to indicate that the provided parts are only default values; * @since 4.15 */ void setKeepLocalChanges(const QSet &parts); /** * Returns what parts are only default values. */ QSet keepLocalChanges() const; private: friend class CollectionCreateJob; friend class CollectionFetchJob; friend class CollectionModifyJob; friend class ProtocolHelper; void markAttributeModified(const QByteArray &type); //@cond PRIVATE QSharedDataPointer d_ptr; friend class CollectionPrivate; //@endcond }; AKONADICORE_EXPORT uint qHash(const Akonadi::Collection &collection); template inline T *Akonadi::Collection::attribute(Collection::CreateOption option) { const QByteArray type = T().type(); markAttributeModified(type); // do this first in case it detaches if (hasAttribute(type)) { if (T *attr = dynamic_cast(attribute(type))) { return attr; } qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } else if (option == AddIfMissing) { T *attr = new T(); addAttribute(attr); return attr; } return nullptr; } template inline const T *Akonadi::Collection::attribute() const { const QByteArray type = T().type(); if (hasAttribute(type)) { if (const T *attr = dynamic_cast(attribute(type))) { return attr; } qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } return nullptr; } template inline void Akonadi::Collection::removeAttribute() { removeAttribute(T().type()); } template inline bool Akonadi::Collection::hasAttribute() const { return hasAttribute(T().type()); } } // namespace Akonadi /** * Allows to output a collection for debugging purposes. */ AKONADICORE_EXPORT QDebug operator<<(QDebug d, const Akonadi::Collection &collection); Q_DECLARE_METATYPE(Akonadi::Collection) Q_DECLARE_METATYPE(Akonadi::Collection::List) Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Collection::Rights) Q_DECLARE_TYPEINFO(Akonadi::Collection, Q_MOVABLE_TYPE); #endif diff --git a/src/core/collectionsync.cpp b/src/core/collectionsync.cpp index 055f7d03c..65f75a4e2 100644 --- a/src/core/collectionsync.cpp +++ b/src/core/collectionsync.cpp @@ -1,866 +1,866 @@ /* Copyright (c) 2007, 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. */ #include "collectionsync_p.h" #include "collection.h" #include "akonadicore_debug.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "collectionfetchscope.h" #include "collectionmovejob.h" #include "cachepolicy.h" #include #include #include #include using namespace Akonadi; static const char CONTENTMIMETYPES[] = "CONTENTMIMETYPES"; static const char ROOTPARENTRID[] = "AKONADI_ROOT_COLLECTION"; class RemoteId { public: explicit RemoteId() { } explicit inline RemoteId(const QStringList &ridChain): ridChain(ridChain) { } explicit inline RemoteId(const QString &rid) { ridChain.append(rid); } inline bool isAbsolute() const { return ridChain.last() == QString::fromLatin1(ROOTPARENTRID); } inline bool isEmpty() const { return ridChain.isEmpty(); } inline bool operator==(const RemoteId &other) const { return ridChain == other.ridChain; } QStringList ridChain; static RemoteId rootRid; }; RemoteId RemoteId::rootRid = RemoteId(QStringList() << QString::fromLatin1(ROOTPARENTRID)); Q_DECLARE_METATYPE(RemoteId) uint qHash(const RemoteId &rid) { uint hash = 0; for (QStringList::ConstIterator iter = rid.ridChain.constBegin(), end = rid.ridChain.constEnd(); iter != end; ++iter) { hash += qHash(*iter); } return hash; } inline bool operator<(const RemoteId &r1, const RemoteId &r2) { if (r1.ridChain.length() == r2.ridChain.length()) { QStringList::ConstIterator it1 = r1.ridChain.constBegin(), end1 = r1.ridChain.constEnd(), it2 = r2.ridChain.constBegin(); while (it1 != end1) { if ((*it1) == (*it2)) { ++it1; ++it2; continue; } return (*it1) < (*it2); } } else { return r1.ridChain.length() < r2.ridChain.length(); } return false; } QDebug operator<<(QDebug s, const RemoteId &rid) { s.nospace() << "RemoteId(" << rid.ridChain << ")"; return s; } /** * @internal */ class CollectionSync::Private { public: Private(CollectionSync *parent) : q(parent) , pendingJobs(0) , progress(0) , currentTransaction(nullptr) , incremental(false) , streaming(false) , hierarchicalRIDs(false) , localListDone(false) , deliveryDone(false) , akonadiRootCollection(Collection::root()) , resultEmitted(false) { } ~Private() { } RemoteId remoteIdForCollection(const Collection &collection) const { if (collection == Collection::root()) { return RemoteId::rootRid; } if (!hierarchicalRIDs) { return RemoteId(collection.remoteId()); } RemoteId rid; Collection parent = collection; while (parent.isValid() || !parent.remoteId().isEmpty()) { QString prid = parent.remoteId(); if (prid.isEmpty() && parent.isValid()) { prid = uidRidMap.value(parent.id()); } if (prid.isEmpty()) { break; } rid.ridChain.append(prid); parent = parent.parentCollection(); if (parent == akonadiRootCollection) { rid.ridChain.append(QString::fromLatin1(ROOTPARENTRID)); break; } } return rid; } void addRemoteColection(const Collection &collection, bool removed = false) { QHash &map = (removed ? removedRemoteCollections : remoteCollections); const Collection parentCollection = collection.parentCollection(); if (parentCollection.remoteId() == akonadiRootCollection.remoteId() || parentCollection.id() == akonadiRootCollection.id()) { Collection c2(collection); c2.setParentCollection(akonadiRootCollection); map[RemoteId::rootRid].append(c2); } else { Q_ASSERT(!parentCollection.remoteId().isEmpty()); map[remoteIdForCollection(parentCollection)].append(collection); } } /* Compares collections by remoteId and falls back to name comparison in case * local collection does not have remoteId (which can happen in some cases) */ bool matchLocalAndRemoteCollection(const Collection &local, const Collection &remote) { if (!local.remoteId().isEmpty()) { return local.remoteId() == remote.remoteId(); } else { return local.name() == remote.name(); } } void localCollectionsReceived(const Akonadi::Collection::List &localCols) { for (const Akonadi::Collection &collection : localCols) { const RemoteId parentRid = remoteIdForCollection(collection.parentCollection()); localCollections[parentRid] += collection; } } void processCollections(const RemoteId &parentRid) { Collection::List remoteChildren = remoteCollections.value(parentRid); Collection::List removedChildren = removedRemoteCollections.value(parentRid); Collection::List localChildren = localCollections.value(parentRid); // Iterate over the list of local children of localParent Collection::List::Iterator localIter, localEnd, removedIter, removedEnd, remoteIter, remoteEnd; for (localIter = localChildren.begin(), localEnd = localChildren.end(); localIter != localEnd;) { const Collection localCollection = *localIter; bool matched = false; uidRidMap.insert(localIter->id(), localIter->remoteId()); // Try to map removed remote collections (from incremental sync) to local collections for (removedIter = removedChildren.begin(), removedEnd = removedChildren.end(); removedIter != removedEnd;) { Collection removedCollection = *removedIter; if (matchLocalAndRemoteCollection(localCollection, removedCollection)) { matched = true; if (!localCollection.remoteId().isEmpty()) { localCollectionsToRemove.append(localCollection); } // Remove the matched removed collection from the list so that // we don't have to iterate over it again next time. removedIter = removedChildren.erase(removedIter); removedEnd = removedChildren.end(); break; } else { // Keep looking ++removedIter; } } if (matched) { // Remove the matched local collection from the list, because we // have already put it into localCollectionsToRemove localIter = localChildren.erase(localIter); localEnd = localChildren.end(); continue; } // Try to find a matching collection in the list of remote children for (remoteIter = remoteChildren.begin(), remoteEnd = remoteChildren.end(); !matched && remoteIter != remoteEnd;) { Collection remoteCollection = *remoteIter; // Yay, we found a match! if (matchLocalAndRemoteCollection(localCollection, remoteCollection)) { matched = true; // "Virtual" flag cannot be updated: we need to recreate // the collection from scratch. if (localCollection.isVirtual() != remoteCollection.isVirtual()) { // Mark the local collection and all its children for deletion and re-creation QList> parents = {{localCollection,remoteCollection}}; while (!parents.empty()) { auto parent = parents.takeFirst(); qCDebug(AKONADICORE_LOG) << "Local collection " << parent.first.name() << " will be recreated"; localCollectionsToRemove.push_back(parent.first); remoteCollectionsToCreate.push_back(parent.second); for (auto it = localChildren.begin(), end = localChildren.end(); it != end;) { if (it->parentCollection() == parent.first) { Collection remoteParent; auto remoteIt = std::find_if(remoteChildren.begin(), remoteChildren.end(), std::bind(&CollectionSync::Private::matchLocalAndRemoteCollection, this, parent.first, std::placeholders::_1)); if (remoteIt != remoteChildren.end()) { remoteParent = *remoteIt; remoteEnd = remoteChildren.erase(remoteIt); } parents.push_back({*it, remoteParent}); it = localChildren.erase(it); localEnd = end = localChildren.end(); } else { ++it; } } } } else if (collectionNeedsUpdate(localCollection, remoteCollection)) { // We need to store both local and remote collections, so that // we can copy over attributes to be preserved remoteCollectionsToUpdate.append(qMakePair(localCollection, remoteCollection)); } else { // Collections are the same, no need to update anything } // Remove the matched remote collection from the list so that // in the end we are left with list of collections that don't // exist locally (i.e. new collections) remoteIter = remoteChildren.erase(remoteIter); remoteEnd = remoteChildren.end(); break; } else { // Keep looking ++remoteIter; } } if (matched) { // Remove the matched local collection from the list so that // in the end we are left with list of collections that don't // exist remotely (i.e. removed collections) localIter = localChildren.erase(localIter); localEnd = localChildren.end(); } else { ++localIter; } } if (!removedChildren.isEmpty()) { removedRemoteCollections[parentRid] = removedChildren; } else { removedRemoteCollections.remove(parentRid); } if (!remoteChildren.isEmpty()) { remoteCollections[parentRid] = remoteChildren; } else { remoteCollections.remove(parentRid); } if (!localChildren.isEmpty()) { localCollections[parentRid] = localChildren; } else { localCollections.remove(parentRid); } } void processLocalCollections(const RemoteId &parentRid, const Collection &parentCollection) { const Collection::List originalChildren = localCollections.value(parentRid); processCollections(parentRid); const Collection::List remoteChildren = remoteCollections.take(parentRid); const Collection::List localChildren = localCollections.take(parentRid); // At this point remoteChildren contains collections that don't exist locally yet if (!remoteChildren.isEmpty()) { for (Collection c : remoteChildren) { c.setParentCollection(parentCollection); remoteCollectionsToCreate.append(c); } } // At this point localChildren contains collections that don't exist remotely anymore if (!localChildren.isEmpty() && !incremental) { for (const auto &c : localChildren) { if (!c.remoteId().isEmpty()) { localCollectionsToRemove.push_back(c); } } } // Recurse into children for (const Collection &c : originalChildren) { processLocalCollections(remoteIdForCollection(c), c); } } void localCollectionFetchResult(KJob *job) { if (job->error()) { return; // handled by the base class } processLocalCollections(RemoteId::rootRid, akonadiRootCollection); localListDone = true; execute(); } bool ignoreAttributeChanges(const Akonadi::Collection &col, const QByteArray &attribute) const { return (keepLocalChanges.contains(attribute) || col.keepLocalChanges().contains(attribute)); } /** Checks if the given localCollection and remoteCollection are different */ bool collectionNeedsUpdate(const Collection &localCollection, const Collection &remoteCollection) const { if (!ignoreAttributeChanges(remoteCollection, CONTENTMIMETYPES)) { if (localCollection.contentMimeTypes().size() != remoteCollection.contentMimeTypes().size()) { return true; } else { for (int i = 0, total = remoteCollection.contentMimeTypes().size(); i < total; ++i) { const QString &m = remoteCollection.contentMimeTypes().at(i); if (!localCollection.contentMimeTypes().contains(m)) { return true; } } } } if (localCollection.parentCollection().remoteId() != remoteCollection.parentCollection().remoteId()) { return true; } if (localCollection.name() != remoteCollection.name()) { return true; } if (localCollection.remoteId() != remoteCollection.remoteId()) { return true; } if (localCollection.remoteRevision() != remoteCollection.remoteRevision()) { return true; } if (!(localCollection.cachePolicy() == remoteCollection.cachePolicy())) { return true; } if (localCollection.enabled() != remoteCollection.enabled()) { return true; } // CollectionModifyJob adds the remote attributes to the local collection const Akonadi::Attribute::List lstAttr = remoteCollection.attributes(); for (const Attribute *attr : lstAttr) { const Attribute *localAttr = localCollection.attribute(attr->type()); if (localAttr && ignoreAttributeChanges(remoteCollection, attr->type())) { continue; } // The attribute must both exist and have equal contents if (!localAttr || localAttr->serialized() != attr->serialized()) { return true; } } return false; } void createLocalCollections() { if (remoteCollectionsToCreate.isEmpty()) { updateLocalCollections(); return; } Collection::List::Iterator iter, end; for (iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end;) { const Collection col = *iter; const Collection parentCollection = col.parentCollection(); // The parent already exists locally if (parentCollection == akonadiRootCollection || parentCollection.id() > 0) { ++pendingJobs; CollectionCreateJob *create = new CollectionCreateJob(col, currentTransaction); connect(create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*))); // Commit transaction after every 100 collections are created, - // otherwise it overlads database journal and things get veeery slow + // otherwise it overloads database journal and things get veeery slow if (pendingJobs % 100 == 0) { currentTransaction->commit(); createTransaction(); } iter = remoteCollectionsToCreate.erase(iter); end = remoteCollectionsToCreate.end(); } else { // Skip the collection, we'll try again once we create all the other // collection we already have a parent for ++iter; } } } void createLocalCollectionResult(KJob *job) { --pendingJobs; if (job->error()) { return; // handled by the base class } q->setProcessedAmount(KJob::Bytes, ++progress); const Collection newLocal = static_cast(job)->collection(); uidRidMap.insert(newLocal.id(), newLocal.remoteId()); const RemoteId newLocalRID = remoteIdForCollection(newLocal); // See if there are any pending collections that this collection is parent of and // update them if so Collection::List::Iterator iter, end; for (iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end; ++iter) { const Collection parentCollection = iter->parentCollection(); if (parentCollection != akonadiRootCollection && parentCollection.id() <= 0) { const RemoteId remoteRID = remoteIdForCollection(*iter); if (remoteRID.isAbsolute()) { if (newLocalRID == remoteIdForCollection(*iter)) { iter->setParentCollection(newLocal); } } else if (!hierarchicalRIDs) { if (remoteRID.ridChain.startsWith(parentCollection.remoteId())) { iter->setParentCollection(newLocal); } } } } // Enqueue all pending remote collections that are children of the just-created // collection Collection::List collectionsToCreate = remoteCollections.take(newLocalRID); if (collectionsToCreate.isEmpty() && !hierarchicalRIDs) { collectionsToCreate = remoteCollections.take(RemoteId(newLocal.remoteId())); } for (Collection col : qAsConst(collectionsToCreate)) { col.setParentCollection(newLocal); remoteCollectionsToCreate.append(col); } // If there are still any collections to create left, try if we just created // a parent for any of them if (!remoteCollectionsToCreate.isEmpty()) { createLocalCollections(); } else if (pendingJobs == 0) { Q_ASSERT(remoteCollectionsToCreate.isEmpty()); if (!remoteCollections.isEmpty()) { currentTransaction->rollback(); q->setError(Unknown); q->setErrorText(i18n("Found unresolved orphan collections")); qCWarning(AKONADICORE_LOG) << "found unresolved orphan collection"; emitResult(); return; } currentTransaction->commit(); createTransaction(); // Otherwise move to next task: updating existing collections updateLocalCollections(); } /* * else if (!remoteCollections.isEmpty()) { currentTransaction->rollback(); q->setError(Unknown); q->setErrorText(i18n("Incomplete collection tree")); emitResult(); return; } */ } /** Performs a local update for the given node pair. */ void updateLocalCollections() { if (remoteCollectionsToUpdate.isEmpty()) { deleteLocalCollections(); return; } typedef QPair CollectionPair; for (const CollectionPair &pair : qAsConst(remoteCollectionsToUpdate)) { const Collection local = pair.first; const Collection remote = pair.second; Collection upd(remote); Q_ASSERT(!upd.remoteId().isEmpty()); Q_ASSERT(currentTransaction); upd.setId(local.id()); if (ignoreAttributeChanges(remote, CONTENTMIMETYPES)) { upd.setContentMimeTypes(local.contentMimeTypes()); } Q_FOREACH (Attribute *remoteAttr, upd.attributes()) { if (ignoreAttributeChanges(remote, remoteAttr->type()) && local.hasAttribute(remoteAttr->type())) { //We don't want to overwrite the attribute changes with the defaults provided by the resource. const Attribute *localAttr = local.attribute(remoteAttr->type()); upd.removeAttribute(localAttr->type()); upd.addAttribute(localAttr->clone()); } } // ### HACK to work around the implicit move attempts of CollectionModifyJob // which we do explicitly below Collection c(upd); c.setParentCollection(local.parentCollection()); ++pendingJobs; CollectionModifyJob *mod = new CollectionModifyJob(c, currentTransaction); connect(mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*))); // detecting moves is only possible with global RIDs if (!hierarchicalRIDs) { if (remote.parentCollection().isValid() && remote.parentCollection().id() != local.parentCollection().id()) { ++pendingJobs; CollectionMoveJob *move = new CollectionMoveJob(upd, remote.parentCollection(), currentTransaction); connect(move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*))); } } } } void updateLocalCollectionResult(KJob *job) { --pendingJobs; if (job->error()) { return; // handled by the base class } if (qobject_cast(job)) { q->setProcessedAmount(KJob::Bytes, ++progress); } // All updates are done, time to move on to next task: deletion if (pendingJobs == 0) { currentTransaction->commit(); createTransaction(); deleteLocalCollections(); } } void deleteLocalCollections() { if (localCollectionsToRemove.isEmpty()) { done(); return; } for (const Collection &col : qAsConst(localCollectionsToRemove)) { Q_ASSERT(!col.remoteId().isEmpty()); // empty RID -> stuff we haven't even written to the remote side yet ++pendingJobs; Q_ASSERT(currentTransaction); CollectionDeleteJob *job = new CollectionDeleteJob(col, currentTransaction); connect(job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*))); // It can happen that the groupware servers report us deleted collections // twice, in this case this collection delete job will fail on the second try. // To avoid a rollback of the complete transaction we gracefully allow the job // to fail :) currentTransaction->setIgnoreJobFailure(job); } } void deleteLocalCollectionsResult(KJob *) { --pendingJobs; q->setProcessedAmount(KJob::Bytes, ++progress); if (pendingJobs == 0) { currentTransaction->commit(); currentTransaction = nullptr; done(); } } void done() { if (currentTransaction) { //This can trigger a direct call of transactionSequenceResult currentTransaction->commit(); currentTransaction = nullptr; } if (!remoteCollections.isEmpty()) { q->setError(Unknown); q->setErrorText(i18n("Found unresolved orphan collections")); } emitResult(); } void emitResult() { //Prevent double result emission Q_ASSERT(!resultEmitted); if (!resultEmitted) { if (q->hasSubjobs()) { // If there are subjobs, pick one, wait for it to finish, then // try again. This way we make sure we don't emit result() signal // while there is still a Transaction job running KJob *subjob = q->subjobs().at(0); connect(subjob, &KJob::result, q, [this](KJob *) { emitResult(); }, Qt::QueuedConnection); } else { resultEmitted = true; q->emitResult(); } } } void createTransaction() { currentTransaction = new TransactionSequence(q); currentTransaction->setAutomaticCommittingEnabled(false); q->connect(currentTransaction, SIGNAL(finished(KJob*)), q, SLOT(transactionSequenceResult(KJob*))); } /** After the transaction has finished report we're done as well. */ void transactionSequenceResult(KJob *job) { if (job->error()) { return; // handled by the base class } // If this was the last transaction, then finish, otherwise there's // a new transaction in the queue already if (job == currentTransaction) { currentTransaction = nullptr; } } /** Process what's currently available. */ void execute() { qCDebug(AKONADICORE_LOG) << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone; if (!localListDone && !deliveryDone) { return; } if (!localListDone && deliveryDone) { Job *parent = (currentTransaction ? static_cast(currentTransaction) : static_cast(q)); CollectionFetchJob *job = new CollectionFetchJob(akonadiRootCollection, CollectionFetchJob::Recursive, parent); job->fetchScope().setResource(resourceId); job->fetchScope().setListFilter(CollectionFetchScope::NoFilter); job->fetchScope().setAncestorRetrieval(CollectionFetchScope::All); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(localCollectionsReceived(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(localCollectionFetchResult(KJob*))); return; } // If a transaction is not started yet, it means we just finished local listing if (!currentTransaction) { // There's nothing to do after local listing -> we are done! if (remoteCollectionsToCreate.isEmpty() && remoteCollectionsToUpdate.isEmpty() && localCollectionsToRemove.isEmpty()) { qCDebug(AKONADICORE_LOG) << "Nothing to do"; emitResult(); return; } // Ok, there's some work to do, so create a transaction we can use createTransaction(); } createLocalCollections(); } CollectionSync *q; QString resourceId; int pendingJobs; int progress; TransactionSequence *currentTransaction; bool incremental; bool streaming; bool hierarchicalRIDs; bool localListDone; bool deliveryDone; // List of parts where local changes should not be overwritten QSet keepLocalChanges; QHash removedRemoteCollections; QHash remoteCollections; QHash localCollections; Collection::List localCollectionsToRemove; Collection::List remoteCollectionsToCreate; QList > remoteCollectionsToUpdate; QHash uidRidMap; // HACK: To workaround Collection copy constructor being very expensive, we // store the Collection::root() collection in a variable here for faster // access Collection akonadiRootCollection; bool resultEmitted; }; CollectionSync::CollectionSync(const QString &resourceId, QObject *parent) : Job(parent) , d(new Private(this)) { d->resourceId = resourceId; setTotalAmount(KJob::Bytes, 0); } CollectionSync::~CollectionSync() { delete d; } void CollectionSync::setRemoteCollections(const Collection::List &remoteCollections) { setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + remoteCollections.count()); for (const Collection &c : remoteCollections) { d->addRemoteColection(c); } if (!d->streaming) { d->deliveryDone = true; } d->execute(); } void CollectionSync::setRemoteCollections(const Collection::List &changedCollections, const Collection::List &removedCollections) { setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + changedCollections.count()); d->incremental = true; for (const Collection &c : changedCollections) { d->addRemoteColection(c); } for (const Collection &c : removedCollections) { d->addRemoteColection(c, true); } if (!d->streaming) { d->deliveryDone = true; } d->execute(); } void CollectionSync::doStart() { } void CollectionSync::setStreamingEnabled(bool streaming) { d->streaming = streaming; } void CollectionSync::retrievalDone() { d->deliveryDone = true; d->execute(); } void CollectionSync::setHierarchicalRemoteIds(bool hierarchical) { d->hierarchicalRIDs = hierarchical; } void CollectionSync::rollback() { if (d->currentTransaction) { d->currentTransaction->rollback(); } } void CollectionSync::setKeepLocalChanges(const QSet &parts) { d->keepLocalChanges = parts; } #include "moc_collectionsync_p.cpp" diff --git a/src/core/commandbuffer_p.h b/src/core/commandbuffer_p.h index c774d6238..412d2c262 100644 --- a/src/core/commandbuffer_p.h +++ b/src/core/commandbuffer_p.h @@ -1,146 +1,146 @@ /* 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. */ -#ifndef AKONADICORE_COMMANDBUFFER_H_ -#define AKONADICORE_COMMANDBUFFER_H_ +#ifndef AKONADICORE_COMMANDBUFFER_P_H_ +#define AKONADICORE_COMMANDBUFFER_P_H_ class QObject; #include #include #include #include "akonadicore_debug.h" namespace Akonadi { class CommandBufferLocker; class CommandBufferNotifyBlocker; class CommandBuffer { friend class CommandBufferLocker; friend class CommandBufferNotifyBlocker; public: struct Command { qint64 tag; Protocol::CommandPtr command; }; CommandBuffer(QObject *parent, const char *notifySlot) : mParent(parent) , mNotifySlot(notifySlot) { } void enqueue(qint64 tag, const Protocol::CommandPtr &command) { mCommands.enqueue({ tag, command }); if (mNotify) { const bool ok = QMetaObject::invokeMethod(mParent, mNotifySlot.constData(), Qt::QueuedConnection); Q_ASSERT(ok); Q_UNUSED(ok); } } inline Command dequeue() { return mCommands.dequeue(); } inline bool isEmpty() const { return mCommands.isEmpty(); } inline int size() const { return mCommands.size(); } private: QObject *mParent = nullptr; QByteArray mNotifySlot; QQueue mCommands; QMutex mLock; bool mNotify = true; }; class CommandBufferLocker { public: explicit CommandBufferLocker(CommandBuffer *buffer) : mBuffer(buffer) { relock(); } ~CommandBufferLocker() { unlock(); } inline void unlock() { if (mLocked) { mBuffer->mLock.unlock(); mLocked = false; } } inline void relock() { if (!mLocked) { mBuffer->mLock.lock(); mLocked = true; } } private: CommandBuffer *mBuffer = nullptr; bool mLocked = false; }; class CommandBufferNotifyBlocker { public: explicit CommandBufferNotifyBlocker(CommandBuffer *buffer) : mBuffer(buffer) { mBuffer->mNotify = false; } ~CommandBufferNotifyBlocker() { unblock(); } void unblock() { mBuffer->mNotify = true; } private: CommandBuffer *mBuffer; }; } // namespace #endif diff --git a/src/core/connection_p.h b/src/core/connection_p.h index f52cbd898..76d590cce 100644 --- a/src/core/connection_p.h +++ b/src/core/connection_p.h @@ -1,95 +1,95 @@ /* * Copyright 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. */ -#ifndef CONNECTIONTHREAD_P_H -#define CONNECTIONTHREAD_P_H +#ifndef CONNECTION_P_H +#define CONNECTION_P_H #include #include #include #include "private/protocol_p.h" #include "akonadicore_export.h" class QFile; namespace Akonadi { class SessionThread; class SessionPrivate; class CommandBuffer; class AKONADICORE_EXPORT Connection : public QObject { Q_OBJECT public: enum ConnectionType { CommandConnection, NotificationConnection }; Q_ENUM(ConnectionType) explicit Connection(ConnectionType connType, const QByteArray &sessionId, CommandBuffer *commandBuffer, QObject *parent = nullptr); ~Connection(); void setSession(SessionPrivate *session); QLocalSocket *socket() const; Q_INVOKABLE void reconnect(); void forceReconnect(); void closeConnection(); void sendCommand(qint64 tag, const Protocol::CommandPtr &command); void handleIncomingData(); Q_SIGNALS: void connected(); void reconnected(); void commandReceived(qint64 tag, const Akonadi::Protocol::CommandPtr &command); void socketDisconnected(); void socketError(const QString &message); private Q_SLOTS: void doReconnect(); void doForceReconnect(); void doCloseConnection(); void doSendCommand(qint64 tag, const Akonadi::Protocol::CommandPtr &command); private: QString defaultAddressForTypeAndMethod(ConnectionType type, const QString &method); bool handleCommand(qint64 tag, const Protocol::CommandPtr &cmd); ConnectionType mConnectionType; QScopedPointer mSocket; QFile *mLogFile = nullptr; QByteArray mSessionId; CommandBuffer *mCommandBuffer; friend class Akonadi::SessionThread; }; } -#endif // CONNECTIONTHREAD_P_H +#endif // CONNECTION_P_H diff --git a/src/core/entitycache_p.h b/src/core/entitycache_p.h index b9399434b..e2fd33c4a 100644 --- a/src/core/entitycache_p.h +++ b/src/core/entitycache_p.h @@ -1,544 +1,543 @@ /* 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_ENTITYCACHE_P_H #define AKONADI_ENTITYCACHE_P_H #include "item.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "collection.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "tag.h" #include "tagfetchjob.h" #include "tagfetchscope.h" #include "session.h" #include "akonaditests_export.h" #include #include #include #include -#include class KJob; Q_DECLARE_METATYPE(QList) namespace Akonadi { /** @internal QObject part of EntityCache. */ class AKONADI_TESTS_EXPORT EntityCacheBase : public QObject { Q_OBJECT public: explicit EntityCacheBase(Session *session, QObject *parent = nullptr); void setSession(Session *session); protected: Session *session = nullptr; Q_SIGNALS: void dataAvailable(); private Q_SLOTS: virtual void processResult(KJob *job) = 0; }; template struct EntityCacheNode { EntityCacheNode() : pending(false) , invalid(false) { } EntityCacheNode(typename T::Id id) : entity(T(id)) , pending(true) , invalid(false) { } T entity; bool pending; bool invalid; }; /** * @internal * A in-memory FIFO cache for a small amount of Item or Collection objects. */ template class EntityCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityCache(int maxCapacity, Session *session = nullptr, QObject *parent = nullptr) : EntityCacheBase(session, parent) , mCapacity(maxCapacity) { } ~EntityCache() override { qDeleteAll(mCache); } /** Object is available in the cache and can be retrieved. */ bool isCached(typename T::Id id) const { EntityCacheNode *node = cacheNodeForId(id); return node && !node->pending; } /** Object has been requested but is not yet loaded into the cache or is already available. */ bool isRequested(typename T::Id id) const { return cacheNodeForId(id); } /** Returns the cached object if available, an empty instance otherwise. */ virtual T retrieve(typename T::Id id) const { EntityCacheNode *node = cacheNodeForId(id); if (node && !node->pending && !node->invalid) { return node->entity; } return T(); } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate(typename T::Id id) { EntityCacheNode *node = cacheNodeForId(id); if (node) { node->invalid = true; } } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update(typename T::Id id, const FetchScope &scope) { EntityCacheNode *node = cacheNodeForId(id); if (node) { mCache.removeAll(node); if (node->pending) { request(id, scope); } delete node; } } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ virtual bool ensureCached(typename T::Id id, const FetchScope &scope) { EntityCacheNode *node = cacheNodeForId(id); if (!node) { request(id, scope); return false; } return !node->pending; } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ virtual void request(typename T::Id id, const FetchScope &scope) { Q_ASSERT(!isRequested(id)); shrinkCache(); EntityCacheNode *node = new EntityCacheNode(id); FetchJob *job = createFetchJob(id, scope); job->setProperty("EntityCacheNode", QVariant::fromValue(id)); connect(job, SIGNAL(result(KJob *)), SLOT(processResult(KJob *))); mCache.enqueue(node); } private: EntityCacheNode *cacheNodeForId(typename T::Id id) const { for (typename QQueue *>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it) { if ((*it)->entity.id() == id) { return *it; } } return nullptr; } void processResult(KJob *job) override { if (job->error()) { //This can happen if we have stale notifications for items that have already been removed } typename T::Id id = job->property("EntityCacheNode").template value(); EntityCacheNode *node = cacheNodeForId(id); if (!node) { return; // got replaced in the meantime } node->pending = false; extractResult(node, job); // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if (node->entity.id() != id) { // TODO: Recursion guard? If this is called with non-existing ids, the if will never be true! node->entity.setId(id); node->invalid = true; } Q_EMIT dataAvailable(); } void extractResult(EntityCacheNode *node, KJob *job) const; inline FetchJob *createFetchJob(typename T::Id id, const FetchScope &scope) { FetchJob *fetch = new FetchJob(T(id), session); fetch->setFetchScope(scope); return fetch; } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while (mCache.size() >= mCapacity && !mCache.first()->pending) { delete mCache.dequeue(); } } private: QQueue *> mCache; int mCapacity; }; template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { CollectionFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->collections().isEmpty()) { node->entity = Collection(); } else { node->entity = fetch->collections().at(0); } } template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->items().isEmpty()) { node->entity = Item(); } else { node->entity = fetch->items().at(0); } } template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { TagFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->tags().isEmpty()) { node->entity = Tag(); } else { node->entity = fetch->tags().at(0); } } template<> inline CollectionFetchJob *EntityCache::createFetchJob(Collection::Id id, const CollectionFetchScope &scope) { CollectionFetchJob *fetch = new CollectionFetchJob(Collection(id), CollectionFetchJob::Base, session); fetch->setFetchScope(scope); return fetch; } typedef EntityCache CollectionCache; typedef EntityCache ItemCache; typedef EntityCache TagCache; template struct EntityListCacheNode { EntityListCacheNode() : pending(false) , invalid(false) { } EntityListCacheNode(typename T::Id id) : entity(id) , pending(true) , invalid(false) { } T entity; bool pending; bool invalid; }; template class EntityListCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityListCache(int maxCapacity, Session *session = nullptr, QObject *parent = nullptr) : EntityCacheBase(session, parent) , mCapacity(maxCapacity) { } ~EntityListCache() override { qDeleteAll(mCache); } /** Returns the cached object if available, an empty instance otherwise. */ typename T::List retrieve(const QList &ids) const { typename T::List list; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node || node->pending || node->invalid) { return typename T::List(); } list << node->entity; } return list; } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ bool ensureCached(const QList &ids, const FetchScope &scope) { QList toRequest; bool result = true; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node) { toRequest << id; continue; } if (node->pending) { result = false; } } if (!toRequest.isEmpty()) { request(toRequest, scope, ids); return false; } return result; } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate(const QList &ids) { for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (node) { node->invalid = true; } } } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update(const QList &ids, const FetchScope &scope) { QList toRequest; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (node) { mCache.remove(id); if (node->pending) { toRequest << id; } delete node; } } if (!toRequest.isEmpty()) { request(toRequest, scope); } } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ void request(const QList &ids, const FetchScope &scope, const QList &preserveIds = QList()) { Q_ASSERT(isNotRequested(ids)); shrinkCache(preserveIds); for (typename T::Id id : ids) { EntityListCacheNode *node = new EntityListCacheNode(id); mCache.insert(id, node); } FetchJob *job = createFetchJob(ids, scope); job->setProperty("EntityListCacheIds", QVariant::fromValue>(ids)); connect(job, SIGNAL(result(KJob *)), SLOT(processResult(KJob *))); } bool isNotRequested(const QList &ids) const { for (typename T::Id id : ids) { if (mCache.contains(id)) { return false; } } return true; } /** Object is available in the cache and can be retrieved. */ bool isCached(const QList &ids) const { for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node || node->pending) { return false; } } return true; } private: /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache(const QList &preserveIds) { typename QHash *>::Iterator iter = mCache.begin(); while (iter != mCache.end() && mCache.size() >= mCapacity) { if (iter.value()->pending || preserveIds.contains(iter.key())) { ++iter; continue; } delete iter.value(); iter = mCache.erase(iter); } } inline FetchJob *createFetchJob(const QList &ids, const FetchScope &scope) { FetchJob *job = new FetchJob(ids, session); job->setFetchScope(scope); return job; } void processResult(KJob *job) override { if (job->error()) { qWarning() << job->errorString(); } const QList ids = job->property("EntityListCacheIds").value>(); typename T::List entities; extractResults(job, entities); for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node) { continue; // got replaced in the meantime } node->pending = false; T result; typename T::List::Iterator iter = entities.begin(); for (; iter != entities.end(); ++iter) { if ((*iter).id() == id) { result = *iter; entities.erase(iter); break; } } // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if (!result.isValid()) { node->entity = T(id); node->invalid = true; } else { node->entity = result; } } Q_EMIT dataAvailable(); } void extractResults(KJob *job, typename T::List &entities) const; private: QHash *> mCache; int mCapacity; }; template<> inline void EntityListCache::extractResults(KJob *job, Collection::List &collections) const { CollectionFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); collections = fetch->collections(); } template<> inline void EntityListCache::extractResults(KJob *job, Item::List &items) const { ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); items = fetch->items(); } template<> inline void EntityListCache::extractResults(KJob *job, Tag::List &tags) const { TagFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); tags = fetch->tags(); } template<> inline CollectionFetchJob *EntityListCache::createFetchJob(const QList &ids, const CollectionFetchScope &scope) { CollectionFetchJob *fetch = new CollectionFetchJob(ids, CollectionFetchJob::Base, session); fetch->setFetchScope(scope); return fetch; } typedef EntityListCache CollectionListCache; typedef EntityListCache ItemListCache; typedef EntityListCache TagListCache; } #endif diff --git a/src/core/exceptionbase.h b/src/core/exceptionbase.h index 769dfea90..b876ddab0 100644 --- a/src/core/exceptionbase.h +++ b/src/core/exceptionbase.h @@ -1,109 +1,109 @@ /* 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_EXCEPTION_H -#define AKONADI_EXCEPTION_H +#ifndef AKONADI_EXCEPTIONBASE_H +#define AKONADI_EXCEPTIONBASE_H #include "akonadicore_export.h" #include #include class QString; namespace Akonadi { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4275) // we are exporting a subclass of an unexported class, MSVC complains #endif /** Base class for exceptions used by the Akonadi library. */ class AKONADICORE_EXPORT Exception : public std::exception //krazy:exclude=dpointer { public: /** Creates a new exception with the error message @p what. */ explicit Exception(const char *what) throw(); /** Creates a new exception with the error message @p what. */ explicit Exception(const QByteArray &what) throw(); /** Creates a new exception with the error message @p what. */ explicit Exception(const QString &what) throw(); /** Copy constructor. */ Exception(const Exception &other) throw(); /** Destructor. */ ~Exception() throw() override; /** Returns the error message associated with this exception. */ const char *what() const throw() override; /** Returns the type of this exception. */ virtual QByteArray type() const throw(); // ### Akonadi 2: return const char * private: class Private; Private *d; }; #ifdef _MSC_VER #pragma warning(pop) #endif #define AKONADI_EXCEPTION_MAKE_TRIVIAL_INSTANCE( classname ) \ class AKONADICORE_EXPORT classname : public Akonadi::Exception \ { \ public: \ classname ( const char *what ) throw() : Akonadi::Exception( what ) \ { \ } \ classname ( const QByteArray &what ) throw() : Akonadi::Exception( what ) \ { \ } \ classname ( const QString &what ) throw() : Akonadi::Exception( what ) \ { \ } \ ~classname() throw(); \ QByteArray type() const throw() override; \ } AKONADI_EXCEPTION_MAKE_TRIVIAL_INSTANCE(PayloadException); #undef AKONADI_EXCEPTION_MAKE_TRIVIAL_INSTANCE } #endif diff --git a/src/core/item.h b/src/core/item.h index d347e3ad2..ab97eaad9 100644 --- a/src/core/item.h +++ b/src/core/item.h @@ -1,1089 +1,1089 @@ /* Copyright (c) 2006 Volker Krause 2007 Till Adam 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_ITEM_H #define AKONADI_ITEM_H #include "akonadicore_export.h" #include "attribute.h" #include "exceptionbase.h" #include "tag.h" #include "collection.h" #include "relation.h" #include "itempayloadinternals_p.h" #include "job.h" #include #include #include #include #include #include class QUrl; template class QVector; namespace Akonadi { class ItemPrivate; /** * @short Represents a PIM item stored in Akonadi storage. * * A PIM item consists of one or more parts, allowing a fine-grained access on its * content where needed (eg. mail envelope, mail body and attachments). * * There is also a namespace (prefix) for special parts which are local to Akonadi. * These parts, prefixed by "akonadi-", will never be fetched in the resource. * They are useful for local extensions like agents which might want to add meta data * to items in order to handle them but the meta data should not be stored back to the * resource. * * This class is implicitly shared. * *

Payload

* * This class contains, beside some type-agnostic information (flags, revision), * zero or more payload objects representing its actual data. Which objects these actually * are depends on the mimetype of the item and the corresponding serializer plugin(s). * * Technically the only restriction on payload objects is that they have to be copyable. * For safety reasons, pointer payloads are forbidden as well though, as the * ownership would not be clear. In this case, usage of a shared pointer is * recommended (such as boost::shared_ptr, QSharedPointer or std::shared_ptr). * * Using a shared pointer is also required in case the payload is a polymorphic * type. For supported shared pointer types implicit casting is provided when possible. * * When using a value-based class as payload, it is recommended to use one that does * support implicit sharing as setting and retrieving a payload as well as copying * an Akonadi::Item object imply copying of the payload object. * * Since KDE 4.6, Item supports multiple payload types per mime type, * and will automatically convert between them using the serialiser * plugins (which is slow). It also supports mixing shared pointer * types, e.g. inserting a boost::shared_ptr and extracting a * QSharedPointer. Since the two shared pointer types cannot * share ownership of the same object, the payload class @c T needs to * provide a @c clone() method with the usual signature, ie. * * @code * virtual T * T::clone() const * @endcode * * If the class that does not have a @c clone() method, asking for an * incompatible shared pointer will throw a PayloadException. * * Since using different shared pointer types and different payload * types for the same mimetype incurs slow conversions (between * payload types) and cloning (between shared pointer types), as well * as manifold memory usage (results of conversions are cached inside * the Item, and only destroyed when a new payload is set by the user * of the class), you want to restrict yourself to just one type and * one shared pointer type. This mechanism was mainly introduced for * backwards compatibility (e.g., putting in a * boost::shared_ptr and extracting a * QSharedPointer), so it is not optimized for * performance. * * The availability of a payload of a specific type can be checked using hasPayload(), * payloads can be retrieved by using payload() and set by using setPayload(). Refer * to the documentation of those methods for more details. * * @author Volker Krause , Till Adam , Marc Mutz */ class AKONADICORE_EXPORT Item { public: /** * Describes the unique id type. */ typedef qint64 Id; /** * Describes a list of items. */ typedef QVector List; /** * Describes a flag name. */ typedef QByteArray Flag; /** * Describes a set of flag names. */ typedef QSet Flags; /** * Describes the part name that is used to fetch the * full payload of an item. */ static const char FullPayload[]; /** * Creates a new item. */ Item(); /** * Creates a new item with the given unique @p id. */ explicit Item(Id id); /** * Creates a new item with the given mime type. * * @param mimeType The mime type of the item. */ explicit Item(const QString &mimeType); /** * Creates a new item from an @p other item. */ Item(const Item &other); /** * Destroys the item. */ ~Item(); /** * Creates an item from the given @p url. */ static Item fromUrl(const QUrl &url); /** * Sets the unique @p identifier of the item. */ void setId(Id identifier); /** * Returns the unique identifier of the item. */ Id id() const; /** * Sets the remote @p id of the item. */ void setRemoteId(const QString &id); /** * Returns the remote id of the item. */ QString remoteId() const; /** * Sets the remote @p revision of the item. * @param revision the item's remote revision * The remote revision can be used by resources to store some * revision information of the backend to detect changes there. * * @note This method is supposed to be used by resources only. * @since 4.5 */ void setRemoteRevision(const QString &revision); /** * Returns the remote revision of the item. * * @note This method is supposed to be used by resources only. * @since 4.5 */ QString remoteRevision() const; /** * Returns whether the item is valid. */ bool isValid() const; /** * Returns whether this item's id equals the id of the @p other item. */ bool operator==(const Item &other) const; /** * Returns whether the item's id does not equal the id of the @p other item. */ bool operator!=(const Item &other) const; /** * Assigns the @p other to this item and returns a reference to this item. * @param other the item to assign */ Item &operator=(const Item &other); /** * @internal For use with containers only. * * @since 4.8 */ bool operator<(const Item &other) const; /** * Returns the parent collection of this object. - * @note This will of course only return a useful value if it was explictely retrieved + * @note This will of course only return a useful value if it was explicitely retrieved * from the Akonadi server. * @since 4.4 */ Collection parentCollection() const; /** * Returns a reference to the parent collection of this object. - * @note This will of course only return a useful value if it was explictely retrieved + * @note This will of course only return a useful value if it was explicitely retrieved * from the Akonadi server. * @since 4.4 */ Collection &parentCollection(); /** * Set the parent collection of this object. * @note Calling this method has no immediate effect for the object itself, * such as being moved to another collection. * It is mainly relevant to provide a context for RID-based operations * inside resources. * @param parent The parent collection. * @since 4.4 */ void setParentCollection(const Collection &parent); /** * Adds an attribute to the item. * * If an attribute of the same type name already exists, it is deleted and * replaced with the new one. * * @param attribute The new attribute. * * @note The collection takes the ownership of the attribute. */ void addAttribute(Attribute *attribute); /** * Removes and deletes the attribute of the given type @p name. */ void removeAttribute(const QByteArray &name); /** * Returns @c true if the item has an attribute of the given type @p name, * false otherwise. */ bool hasAttribute(const QByteArray &name) const; /** * Returns a list of all attributes of the item. * * @warning Do not modify the attributes returned from this method, * the change will not be reflected when updating the Item through * ItemModifyJob. */ Attribute::List attributes() const; /** * Removes and deletes all attributes of the item. */ void clearAttributes(); /** * Returns the attribute of the given type @p name if available, 0 otherwise. */ Attribute *attribute(const QByteArray &name); const Attribute *attribute(const QByteArray &name) const; /** * Describes the options that can be passed to access attributes. */ enum CreateOption { AddIfMissing, ///< Creates the attribute if it is missing DontCreate ///< Do not create the attribute if it is missing (default) }; /** * Returns the attribute of the requested type. * If the item has no attribute of that type yet, a new one * is created and added to the entity. * * @param option The create options. */ template inline T *attribute(CreateOption option = DontCreate); /** * Returns the attribute of the requested type or 0 if it is not available. */ template inline const T *attribute() const; /** * Removes and deletes the attribute of the requested type. */ template inline void removeAttribute(); /** * Returns whether the item has an attribute of the requested type. */ template inline bool hasAttribute() const; /** * Returns all flags of this item. */ Flags flags() const; /** * Returns the timestamp of the last modification of this item. * @since 4.2 */ QDateTime modificationTime() const; /** * Sets the timestamp of the last modification of this item. * @param datetime the modification time to set * @note Do not modify this value from within an application, * it is updated automatically by the revision checking functions. * @since 4.2 */ void setModificationTime(const QDateTime &datetime); /** * Returns whether the flag with the given @p name is * set in the item. */ bool hasFlag(const QByteArray &name) const; /** * Sets the flag with the given @p name in the item. */ void setFlag(const QByteArray &name); /** * Removes the flag with the given @p name from the item. */ void clearFlag(const QByteArray &name); /** * Overwrites all flags of the item by the given @p flags. */ void setFlags(const Flags &flags); /** * Removes all flags from the item. */ void clearFlags(); void setTags(const Tag::List &list); void setTag(const Tag &tag); Tag::List tags() const; bool hasTag(const Tag &tag) const; void clearTag(const Tag &tag); void clearTags(); /** * Returns all relations of this item. * @since 4.15 * @see RelationCreateJob, RelationDeleteJob to modify relations */ Relation::List relations() const; /** * Sets the payload based on the canonical representation normally * used for data of this mime type. * * @param data The encoded data. * @see fullPayloadData */ void setPayloadFromData(const QByteArray &data); /** * Returns the full payload in its canonical representation, e.g. the * binary or textual format usually used for data with this mime type. * This is useful when communicating with non-Akonadi application by * e.g. drag&drop, copy&paste or stored files. */ QByteArray payloadData() const; /** * Returns the list of loaded payload parts. This is not necessarily * identical to all parts in the cache or to all available parts on the backend. */ QSet loadedPayloadParts() const; /** * Marks that the payload shall be cleared from the cache when this * item is passed to an ItemModifyJob the next time. * This will trigger a refetch of the payload from the backend when the * item is accessed afterwards. Only resources should have a need for * this functionality. * * @since 4.5 */ void clearPayload(); /** * Sets the @p revision number of the item. * @param revision the revision number to set * @note Do not modify this value from within an application, * it is updated automatically by the revision checking functions. */ void setRevision(int revision); /** * Returns the revision number of the item. */ int revision() const; /** * Returns the unique identifier of the collection this item is stored in. There is only * a single such collection, although the item can be linked into arbitrary many * virtual collections. * Calling this method makes sense only after running an ItemFetchJob on the item. * @returns the collection ID if it is known, -1 otherwise. * @since 4.3 */ Collection::Id storageCollectionId() const; /** * Set the size of the item in bytes. * @param size the size of the item in bytes * @since 4.2 */ void setSize(qint64 size); /** * Returns the size of the items in bytes. * * @since 4.2 */ qint64 size() const; /** * Sets the mime type of the item to @p mimeType. */ void setMimeType(const QString &mimeType); /** * Returns the mime type of the item. */ QString mimeType() const; /** * Sets the @p gid of the entity. * * @since 4.12 */ void setGid(const QString &gid); /** * Returns the gid of the entity. * * @since 4.12 */ QString gid() const; /** * Sets the virtual @p collections that this item is linked into. * * @note Note that changing this value makes no effect on what collections * this item is linked to. To link or unlink an item to/from a virtual * collection, use LinkJob and UnlinkJob. * * @since 4.14 */ void setVirtualReferences(const Collection::List &collections); /** * Lists virtual collections that this item is linked to. * * @note This value is populated only when this item was retrieved by * ItemFetchJob with fetchVirtualReferences set to true in ItemFetchScope, * otherwise this list is always empty. * * @since 4.14 */ Collection::List virtualReferences() const; /** * Returns a list of metatype-ids, describing the different * variants of payload that are currently contained in this item. * * The result is always sorted (increasing ids). */ QVector availablePayloadMetaTypeIds() const; /** * Sets a path to a file with full payload. * * This method can only be used by Resources and should not be used by Akonadi * clients. Clients should use setPayload() instead. * * Akonadi will not duplicate content of the file in its database but will * instead directly refer to this file. This means that the file must be * persistent (don't use this method with a temporary files), and the Akonadi * resource that owns the storage is responsible for updating the file path * if the file is changed, moved or removed. * * The payload can still be accessed via payload() methods. * * @see setPayload(), setPayloadFromData() * @since 5.6 */ void setPayloadPath(const QString &filePath); /** * Returns path to the payload file set by setPayloadPath() * * If payload was set via setPayload() or setPayloadFromData() then this * method will return a null string. */ QString payloadPath() const; /** * Sets the payload object of this PIM item. * * @param p The payload object. Must be copyable and must not be a pointer, * will cause a compilation failure otherwise. Using a type that can be copied * fast (such as implicitly shared classes) is recommended. * If the payload type is polymorphic and you intend to set and retrieve payload * objects with mismatching but castable types, make sure to use a supported * shared pointer implementation (currently boost::shared_ptr, QSharedPointer * and std::shared_ptr and make sure there is a specialization of * Akonadi::super_trait for your class. */ template void setPayload(const T &p); //@cond PRIVATE template void setPayload(T *p); // We know that auto_ptr is deprecated, but we still want to handle the case // without compilers yelling at us all the time just because item.h gets included // virtually everywhere #if __cplusplus < 201703L #ifdef __GNUC__ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #endif template void setPayload(std::auto_ptr p); #ifdef __GNUC__ #ifdef __clang__ #pragma clang diagnostic pop #else #pragma GCC diagnostic pop #endif #endif #endif template void setPayload(std::unique_ptr p); //@endcond /** * Returns the payload object of this PIM item. This method will only succeed if either * you requested the exact same payload type that was put in or the payload uses a * supported shared pointer type (currently boost::shared_ptr, QSharedPointer and * std::shared_ptr), and is castable to the requested type. For this to work there needs * to be a specialization of Akonadi::super_trait of the used classes. * * If a mismatching or non-castable payload type is requested, an Akonadi::PayloadException * is thrown. Therefore it is generally recommended to guard calls to payload() with a * corresponding hasPayload() call. * * Trying to retrieve a pointer type will fail to compile. */ template T payload() const; /** * Returns whether the item has a payload object. */ bool hasPayload() const; /** * Returns whether the item has a payload of type @c T. * This method will only return @c true if either you requested the exact same payload type * that was put in or the payload uses a supported shared pointer type (currently boost::shared_ptr, * QSharedPointer and std::shared_ptr), and is castable to the requested type. For this to work there needs * to be a specialization of Akonadi::super_trait of the used classes. * * Trying to retrieve a pointer type will fail to compile. */ template bool hasPayload() const; /** * Describes the type of url which is returned in url(). */ enum UrlType { UrlShort = 0, ///< A short url which contains the identifier only (default) UrlWithMimeType = 1 ///< A url with identifier and mimetype }; /** * Returns the url of the item. */ QUrl url(UrlType type = UrlShort) const; /** * Returns the parts available for this item. * * The returned set refers to parts available on the akonadi server or remotely, * but does not include the loadedPayloadParts() of this item. * * @since 4.4 */ QSet availablePayloadParts() const; /** * Returns the parts available for this item in the cache. The list might be a subset * of the actual parts in cache, as it contains only the requested parts. See @see ItemFetchJob and * @see ItemFetchScope * * The returned set refers to parts available on the akonadi server. * * @since 4.11 */ QSet cachedPayloadParts() const; /** * Applies the parts of Item @p other to this item. * Any parts or attributes available in other, will be applied to this item, * and the payload parts of other will be inserted into this item, overwriting * any existing parts with the same part name. * * If there is an ItemSerialzerPluginV2 for the type, the merge method in that plugin is * used to perform the merge. If only an ItemSerialzerPlugin class is found, or the merge * method of the -V2 plugin is not implemented, the merge is performed with multiple deserializations * of the payload. * @param other the item to get values from * @since 4.4 */ void apply(const Item &other); /** * Registers \a T as a legacy type for mime type \a mimeType. * * This is required information for Item to return the correct * type from payload() when clients have not been recompiled to * use the new code. * @param mimeType the mimeType to register * @since 4.6 */ template static void addToLegacyMapping(const QString &mimeType); void setCachedPayloadParts(const QSet &cachedParts); private: //@cond PRIVATE friend class ItemCreateJob; friend class ItemCreateJobPrivate; friend class ItemModifyJob; friend class ItemModifyJobPrivate; friend class ItemSync; friend class ProtocolHelper; Internal::PayloadBase *payloadBase() const; void setPayloadBase(Internal::PayloadBase *p); Internal::PayloadBase *payloadBaseV2(int sharedPointerId, int metaTypeId) const; //std::auto_ptr takePayloadBase( int sharedPointerId, int metaTypeId ); void setPayloadBaseV2(int sharedPointerId, int metaTypeId, std::unique_ptr &p); void addPayloadBaseVariant(int sharedPointerId, int metaTypeId, std::unique_ptr &p) const; static void addToLegacyMappingImpl(const QString &mimeType, int sharedPointerId, int metaTypeId, std::unique_ptr &p); /** * Try to ensure that we have a variant of the payload for metatype id @a mtid. * @return @c true if a type exists or could be created through conversion, @c false otherwise. */ bool ensureMetaTypeId(int mtid) const; template typename std::enable_if::isPolymorphic, void>::type setPayloadImpl(const T &p, const int * /*disambiguate*/ = nullptr); template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, void >::type setPayloadImpl(const T &p); template typename std::enable_if::isPolymorphic, T>::type payloadImpl(const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, T >::type payloadImpl() const; template typename std::enable_if::isPolymorphic, bool>::type hasPayloadImpl(const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, bool >::type hasPayloadImpl() const; template typename std::enable_if::value, bool>::type tryToClone(T *ret, const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if < !Internal::is_shared_pointer::value, bool >::type tryToClone(T *ret) const; template typename std::enable_if < !std::is_same::value, bool >::type tryToCloneImpl(T *ret, const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if::value, bool>::type tryToCloneImpl(T *ret) const; /** * Set the collection ID to where the item is stored in. Should be set only by the ItemFetchJob. * @param collectionId the unique identifier of the collection where this item is stored in. * @since 4.3 */ void setStorageCollectionId(Collection::Id collectionId); #if 0 /** * Helper function for non-template throwing of PayloadException. */ QString payloadExceptionText(int spid, int mtid) const; /** * Non-template throwing of PayloadException. * Needs to be inline, otherwise catch (Akonadi::PayloadException) * won't work (only catch (Akonadi::Exception)) */ inline void throwPayloadException(int spid, int mtid) const { throw PayloadException(payloadExceptionText(spid, mtid)); } #else void throwPayloadException(int spid, int mtid) const; #endif QSharedDataPointer d_ptr; friend class ItemPrivate; //@endcond }; AKONADICORE_EXPORT uint qHash(const Akonadi::Item &item); template inline T *Item::attribute(Item::CreateOption option) { const QByteArray type = T().type(); if (hasAttribute(type)) { if (T *attr = dynamic_cast(attribute(type))) { return attr; } qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } else if (option == AddIfMissing) { T *attr = new T(); addAttribute(attr); return attr; } return nullptr; } template inline const T *Item::attribute() const { const QByteArray type = T().type(); if (hasAttribute(type)) { if (const T *attr = dynamic_cast(attribute(type))) { return attr; } qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } return nullptr; } template inline void Item::removeAttribute() { removeAttribute(T().type()); } template inline bool Item::hasAttribute() const { return hasAttribute(T().type()); } template T Item::payload() const { static_assert(!std::is_pointer::value, "Payload must not be a pointer"); if (!hasPayload()) { throwPayloadException(-1, -1); } return payloadImpl(); } template typename std::enable_if::isPolymorphic, T>::type Item::payloadImpl(const int *) const { typedef Internal::PayloadTrait PayloadType; static_assert(PayloadType::isPolymorphic, "Non-polymorphic payload type in polymorphic implementation is not allowed"); typedef typename Internal::get_hierarchy_root::type Root_T; typedef Internal::PayloadTrait RootType; static_assert(!RootType::isPolymorphic, "Root type of payload type must not be polymorphic"); // prevent endless recursion return PayloadType::castFrom(payloadImpl()); } template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, T >::type Item::payloadImpl() const { typedef Internal::PayloadTrait PayloadType; static_assert(!PayloadType::isPolymorphic, "Polymorphic payload type in non-polymorphic implementation is not allowed"); const int metaTypeId = PayloadType::elementMetaTypeId(); // make sure that we have a payload format represented by 'metaTypeId': if (!ensureMetaTypeId(metaTypeId)) { throwPayloadException(PayloadType::sharedPointerId, metaTypeId); } // Check whether we have the exact payload // (metatype id and shared pointer type match) if (const Internal::Payload *const p = Internal::payload_cast(payloadBaseV2(PayloadType::sharedPointerId, metaTypeId))) { return p->payload; } T ret; if (!tryToClone(&ret)) { throwPayloadException(PayloadType::sharedPointerId, metaTypeId); } return ret; } template typename std::enable_if < !std::is_same::value, bool >::type Item::tryToCloneImpl(T *ret, const int *) const { typedef Internal::PayloadTrait PayloadType; typedef Internal::PayloadTrait NewPayloadType; const int metaTypeId = PayloadType::elementMetaTypeId(); Internal::PayloadBase *payloadBase = payloadBaseV2(NewPayloadType::sharedPointerId, metaTypeId); if (const Internal::Payload *const p = Internal::payload_cast(payloadBase)) { // If found, attempt to make a clone (required the payload to provide virtual T * T::clone() const) const T nt = PayloadType::clone(p->payload); if (!PayloadType::isNull(nt)) { // if clone succeeded, add the clone to the Item: std::unique_ptr npb(new Internal::Payload(nt)); addPayloadBaseVariant(PayloadType::sharedPointerId, metaTypeId, npb); // and return it if (ret) { *ret = nt; } return true; } } return tryToCloneImpl::next_shared_ptr>(ret); } template typename std::enable_if::value, bool>::type Item::tryToCloneImpl(T *) const { return false; } template typename std::enable_if::value, bool>::type Item::tryToClone(T *ret, const int *) const { typedef Internal::PayloadTrait PayloadType; static_assert(!PayloadType::isPolymorphic, "Polymorphic payload type in non-polymorphic implementation is not allowed"); return tryToCloneImpl::next_shared_ptr>(ret); } template typename std::enable_if < !Internal::is_shared_pointer::value, bool >::type Item::tryToClone(T *) const { typedef Internal::PayloadTrait PayloadType; static_assert(!PayloadType::isPolymorphic, "Polymorphic payload type in non-polymorphic implementation is not allowed"); return false; } template bool Item::hasPayload() const { static_assert(!std::is_pointer::value, "Payload type cannot be a pointer"); return hasPayload() && hasPayloadImpl(); } template typename std::enable_if::isPolymorphic, bool>::type Item::hasPayloadImpl(const int *) const { typedef Internal::PayloadTrait PayloadType; static_assert(PayloadType::isPolymorphic, "Non-polymorphic payload type in polymorphic implementation is no allowed"); typedef typename Internal::get_hierarchy_root::type Root_T; typedef Internal::PayloadTrait RootType; static_assert(!RootType::isPolymorphic, "Root type of payload type must not be polymorphic"); // prevent endless recursion try { return hasPayloadImpl() && PayloadType::canCastFrom(payload()); } catch (const Akonadi::PayloadException &e) { qDebug() << e.what(); Q_UNUSED(e) return false; } } template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, bool >::type Item::hasPayloadImpl() const { typedef Internal::PayloadTrait PayloadType; static_assert(!PayloadType::isPolymorphic, "Polymorphic payload type in non-polymorphic implementation is not allowed"); const int metaTypeId = PayloadType::elementMetaTypeId(); // make sure that we have a payload format represented by 'metaTypeId': if (!ensureMetaTypeId(metaTypeId)) { return false; } // Check whether we have the exact payload // (metatype id and shared pointer type match) if (const Internal::Payload *const p = Internal::payload_cast(payloadBaseV2(PayloadType::sharedPointerId, metaTypeId))) { return true; } return tryToClone(nullptr); } template void Item::setPayload(const T &p) { static_assert(!std::is_pointer::value, "Payload type must not be a pointer"); setPayloadImpl(p); } template typename std::enable_if::isPolymorphic>::type Item::setPayloadImpl(const T &p, const int *) { typedef Internal::PayloadTrait PayloadType; static_assert(PayloadType::isPolymorphic, "Non-polymorphic payload type in polymorphic implementation is not allowed"); typedef typename Internal::get_hierarchy_root::type Root_T; typedef Internal::PayloadTrait RootType; static_assert(!RootType::isPolymorphic, "Root type of payload type must not be polymorphic"); // prevent endless recursion setPayloadImpl(p); } template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic >::type Item::setPayloadImpl(const T &p) { typedef Internal::PayloadTrait PayloadType; std::unique_ptr pb(new Internal::Payload(p)); setPayloadBaseV2(PayloadType::sharedPointerId, PayloadType::elementMetaTypeId(), pb); } template void Item::setPayload(T *p) { p->You_MUST_NOT_use_a_pointer_as_payload; } #if __cplusplus < 201703L #ifdef __GNUC__ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #endif template void Item::setPayload(std::auto_ptr p) { p.Nice_try_but_a_std_auto_ptr_is_not_allowed_as_payload_either; } #ifdef __GNUC__ #ifdef __clang__ #pragma clang diagnostic pop #else #pragma GCC diagnostic pop #endif #endif #endif template void Item::setPayload(std::unique_ptr p) { p.Nope_even_std_unique_ptr_is_not_allowed; } template void Item::addToLegacyMapping(const QString &mimeType) { typedef Internal::PayloadTrait PayloadType; static_assert(!PayloadType::isPolymorphic, "Payload type must not be polymorphic"); std::unique_ptr p(new Internal::Payload); addToLegacyMappingImpl(mimeType, PayloadType::sharedPointerId, PayloadType::elementMetaTypeId(), p); } } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Item) Q_DECLARE_METATYPE(Akonadi::Item::List) #endif diff --git a/src/core/jobs/searchresultjob_p.h b/src/core/jobs/searchresultjob_p.h index 62d84a027..f6d7e250c 100644 --- a/src/core/jobs/searchresultjob_p.h +++ b/src/core/jobs/searchresultjob_p.h @@ -1,56 +1,56 @@ /* 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 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_SEARCHRESULTJOB_H -#define AKONADI_SEARCHRESULTJOB_H +#ifndef AKONADI_SEARCHRESULTJOB_P_H +#define AKONADI_SEARCHRESULTJOB_P_H #include "akonadicore_export.h" #include "job.h" namespace Akonadi { class SearchResultJobPrivate; class ImapSet; class Collection; class AKONADICORE_EXPORT SearchResultJob : public Akonadi::Job { Q_OBJECT public: explicit SearchResultJob(const QByteArray &searchId, const Collection &collection, QObject *parent = nullptr); ~SearchResultJob() override; void setSearchId(const QByteArray &searchId); Q_REQUIRED_RESULT QByteArray searchId() const; void setResult(const ImapSet &set); void setResult(const QVector &remoteIds); void setResult(const QVector &ids); protected: void doStart() override; bool doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) override; private: Q_DECLARE_PRIVATE(SearchResultJob) }; } #endif // AKONADI_SEARCHRESULTJOB_H diff --git a/src/core/models/collectionmodel_p.h b/src/core/models/collectionmodel_p.h index 9c4033c4b..a699f6c8e 100644 --- a/src/core/models/collectionmodel_p.h +++ b/src/core/models/collectionmodel_p.h @@ -1,113 +1,113 @@ /* 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. */ #ifndef AKONADI_COLLECTIONMODEL_P_H #define AKONADI_COLLECTIONMODEL_P_H #include "collection.h" #include #include #include #include #include class KJob; namespace Akonadi { class CollectionModel; class CollectionStatistics; class Monitor; class Session; /** * @internal */ class CollectionModelPrivate { public: Q_DECLARE_PUBLIC(CollectionModel) explicit CollectionModelPrivate(CollectionModel *parent) : q_ptr(parent) , headerContent(i18nc("@title:column, name of a thing", "Name")) { } virtual ~CollectionModelPrivate() { } CollectionModel *q_ptr; QHash collections; QHash > childCollections; QHash m_newCollections; QHash< Collection::Id, QVector > m_newChildCollections; Monitor *monitor = nullptr; Session *session = nullptr; QStringList mimeTypes; bool fetchStatistics = false; bool unsubscribed = false; QString headerContent; void init(); void startFirstListJob(); void collectionRemoved(const Akonadi::Collection &collection); void collectionChanged(const Akonadi::Collection &collection); void updateDone(KJob *job); void collectionStatisticsChanged(Collection::Id, const Akonadi::CollectionStatistics &statistics); void listDone(KJob *job); void editDone(KJob *job); void dropResult(KJob *job); void collectionsChanged(const Akonadi::Collection::List &cols); QIcon iconForCollection(const Collection &collection) const; QModelIndex indexForId(Collection::Id id, int column = 0) const; bool removeRowFromModel(int row, const QModelIndex &parent = QModelIndex()); bool supportsContentType(const QModelIndex &index, const QStringList &contentTypes); private: - // FIXME: This cache is a workaround for extremly slow QIcon::fromTheme() + // FIXME: This cache is a workaround for extremely slow QIcon::fromTheme() // caused by bottleneck in FrameworkIntegration. See bug #346644 for details. mutable QHash mIconCache; mutable QString mIconThemeName; void updateSupportedMimeTypes(const Collection &col) { const QStringList l = col.contentMimeTypes(); QStringList::ConstIterator constEnd(l.constEnd()); for (QStringList::ConstIterator it = l.constBegin(); it != constEnd; ++it) { if ((*it) == Collection::mimeType()) { continue; } if (!mimeTypes.contains(*it)) { mimeTypes << *it; } } } }; } #endif diff --git a/src/core/models/entitytreemodel_p.cpp b/src/core/models/entitytreemodel_p.cpp index 0410e4ede..ccde09089 100644 --- a/src/core/models/entitytreemodel_p.cpp +++ b/src/core/models/entitytreemodel_p.cpp @@ -1,1979 +1,1979 @@ /* 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. */ #include "entitytreemodel_p.h" #include "entitytreemodel.h" #include "agentmanagerinterface.h" #include "monitor_p.h" // For friend ref/deref #include "servermanager.h" #include "akranges.h" #include #include "agentmanager.h" #include "agenttype.h" #include "monitor.h" #include "changerecorder.h" #include "collectioncopyjob.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmovejob.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "entityhiddenattribute.h" #include "itemcopyjob.h" #include "itemfetchjob.h" #include "itemmodifyjob.h" #include "itemmovejob.h" #include "linkjob.h" #include "session.h" #include "private/protocol_p.h" #include "akonadicore_debug.h" #include #include #include QHash jobTimeTracker; Q_LOGGING_CATEGORY(DebugETM, "org.kde.pim.akonadi.ETM", QtInfoMsg) using namespace Akonadi; static CollectionFetchJob::Type getFetchType(EntityTreeModel::CollectionFetchStrategy strategy) { switch (strategy) { case EntityTreeModel::FetchFirstLevelChildCollections: return CollectionFetchJob::FirstLevel; case EntityTreeModel::InvisibleCollectionFetch: case EntityTreeModel::FetchCollectionsRecursive: default: break; } return CollectionFetchJob::Recursive; } EntityTreeModelPrivate::EntityTreeModelPrivate(EntityTreeModel *parent) : q_ptr(parent) , m_monitor(nullptr) , m_rootNode(nullptr) , m_collectionFetchStrategy(EntityTreeModel::FetchCollectionsRecursive) , m_itemPopulation(EntityTreeModel::ImmediatePopulation) , m_listFilter(CollectionFetchScope::NoFilter) , m_includeStatistics(false) , m_showRootCollection(false) , m_collectionTreeFetched(false) , m_session(nullptr) , m_showSystemEntities(false) { // using collection as a parameter of a queued call in runItemFetchJob() qRegisterMetaType(); Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self(); QObject::connect(agentManager, SIGNAL(instanceRemoved(Akonadi::AgentInstance)), q_ptr, SLOT(agentInstanceRemoved(Akonadi::AgentInstance))); } EntityTreeModelPrivate::~EntityTreeModelPrivate() { if (m_needDeleteRootNode) { delete m_rootNode; } m_rootNode = nullptr; } void EntityTreeModelPrivate::init(Monitor *monitor) { Q_Q(EntityTreeModel); Q_ASSERT(!m_monitor); m_monitor = monitor; // The default is to FetchCollectionsRecursive, so we tell the monitor to fetch collections // That way update signals from the monitor will contain the full collection. // This may be updated if the CollectionFetchStrategy is changed. m_monitor->fetchCollection(true); m_session = m_monitor->session(); m_rootCollectionDisplayName = QStringLiteral("[*]"); if (Akonadi::ChangeRecorder *cr = qobject_cast(m_monitor)) { cr->setChangeRecordingEnabled(false); } m_includeStatistics = true; m_monitor->fetchCollectionStatistics(true); m_monitor->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); q->connect(monitor, SIGNAL(mimeTypeMonitored(QString,bool)), SLOT(monitoredMimeTypeChanged(QString,bool))); q->connect(monitor, SIGNAL(collectionMonitored(Akonadi::Collection,bool)), SLOT(monitoredCollectionsChanged(Akonadi::Collection,bool))); q->connect(monitor, SIGNAL(itemMonitored(Akonadi::Item,bool)), SLOT(monitoredItemsChanged(Akonadi::Item,bool))); q->connect(monitor, SIGNAL(resourceMonitored(QByteArray,bool)), SLOT(monitoredResourcesChanged(QByteArray,bool))); // monitor collection changes q->connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection)), SLOT(monitoredCollectionChanged(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), SLOT(monitoredCollectionAdded(Akonadi::Collection,Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), SLOT(monitoredCollectionRemoved(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)), SLOT(monitoredCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); // Monitor item changes. q->connect(monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), SLOT(monitoredItemAdded(Akonadi::Item,Akonadi::Collection))); q->connect(monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), SLOT(monitoredItemChanged(Akonadi::Item,QSet))); q->connect(monitor, SIGNAL(itemRemoved(Akonadi::Item)), SLOT(monitoredItemRemoved(Akonadi::Item))); q->connect(monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), SLOT(monitoredItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); q->connect(monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)), SLOT(monitoredItemLinked(Akonadi::Item,Akonadi::Collection))); q->connect(monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)), SLOT(monitoredItemUnlinked(Akonadi::Item,Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), SLOT(monitoredCollectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics))); Akonadi::ServerManager *serverManager = Akonadi::ServerManager::self(); q->connect(serverManager, SIGNAL(started()), SLOT(serverStarted())); fillModel(); } void EntityTreeModelPrivate::serverStarted() { // Don't emit about to be reset. Too late for that endResetModel(); } void EntityTreeModelPrivate::changeFetchState(const Collection &parent) { Q_Q(EntityTreeModel); const QModelIndex collectionIndex = indexForCollection(parent); if (!collectionIndex.isValid()) { // Because we are called delayed, it is possible that @p parent has been deleted. return; } Q_EMIT q->dataChanged(collectionIndex, collectionIndex); } void EntityTreeModelPrivate::agentInstanceRemoved(const Akonadi::AgentInstance &instance) { Q_Q(EntityTreeModel); if (!instance.type().capabilities().contains(QLatin1String("Resource"))) { return; } if (m_rootCollection.isValid()) { if (m_rootCollection != Collection::root()) { if (m_rootCollection.resource() == instance.identifier()) { q->clearAndReset(); } return; } foreach (Node *node, m_childEntities[Collection::root().id()]) { Q_ASSERT(node->type == Node::Collection); const Collection collection = m_collections[node->id]; if (collection.resource() == instance.identifier()) { monitoredCollectionRemoved(collection); } } } } void EntityTreeModelPrivate::fetchItems(const Collection &parent) { Q_Q(const EntityTreeModel); Q_ASSERT(parent.isValid()); Q_ASSERT(m_collections.contains(parent.id())); // TODO: Use a more specific fetch scope to get only the envelope for mails etc. ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(parent, m_session); itemFetchJob->setFetchScope(m_monitor->itemFetchScope()); itemFetchJob->fetchScope().setAncestorRetrieval(ItemFetchScope::All); itemFetchJob->fetchScope().setIgnoreRetrievalErrors(true); itemFetchJob->setDeliveryOption(ItemFetchJob::EmitItemsInBatches); itemFetchJob->setProperty(FetchCollectionId().constData(), QVariant(parent.id())); if (m_showRootCollection || parent != m_rootCollection) { m_pendingCollectionRetrieveJobs.insert(parent.id()); // If collections are not in the model, there will be no valid index for them. if ((m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) && (m_collectionFetchStrategy != EntityTreeModel::FetchNoCollections)) { // We need to invoke this delayed because we would otherwise be emitting a sequence like // - beginInsertRows // - dataChanged // - endInsertRows // which would confuse proxies. QMetaObject::invokeMethod(const_cast(q), "changeFetchState", Qt::QueuedConnection, Q_ARG(Akonadi::Collection, parent)); } } q->connect(itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)), q, SLOT(itemsFetched(Akonadi::Item::List))); q->connect(itemFetchJob, SIGNAL(result(KJob*)), q, SLOT(itemFetchJobDone(KJob*))); qCDebug(DebugETM) << "collection:" << parent.name(); jobTimeTracker[itemFetchJob].start(); } void EntityTreeModelPrivate::fetchCollections(Akonadi::CollectionFetchJob *job) { Q_Q(EntityTreeModel); job->fetchScope().setListFilter(m_listFilter); job->fetchScope().setContentMimeTypes(m_monitor->mimeTypesMonitored()); m_pendingCollectionFetchJobs.insert(static_cast(job)); if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) { q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionListFetched(Akonadi::Collection::List))); } else { job->fetchScope().setIncludeStatistics(m_includeStatistics); job->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsFetched(Akonadi::Collection::List))); } q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); qCDebug(DebugETM) << "collection:" << job->collections(); jobTimeTracker[job].start(); } void EntityTreeModelPrivate::fetchCollections(const Collection::List &collections, CollectionFetchJob::Type type) { fetchCollections(new CollectionFetchJob(collections, type, m_session)); } void EntityTreeModelPrivate::fetchCollections(const Collection &collection, CollectionFetchJob::Type type) { Q_ASSERT(collection.isValid()); CollectionFetchJob *job = new CollectionFetchJob(collection, type, m_session); fetchCollections(job); } namespace Akonadi { template inline bool EntityTreeModelPrivate::isHiddenImpl(const T &entity, Node::Type type) const { if (m_showSystemEntities) { return false; } if (type == Node::Collection && entity.id() == m_rootCollection.id()) { return false; } // entity.hasAttribute() does not compile w/ GCC for // some reason if (entity.hasAttribute(EntityHiddenAttribute().type())) { return true; } const Collection parent = entity.parentCollection(); if (parent.isValid()) { return isHiddenImpl(parent, Node::Collection); } return false; } } bool EntityTreeModelPrivate::isHidden(const Akonadi::Collection &collection) const { return isHiddenImpl(collection, Node::Collection); } bool EntityTreeModelPrivate::isHidden(const Akonadi::Item &item) const { return isHiddenImpl(item, Node::Item); } void EntityTreeModelPrivate::collectionListFetched(const Akonadi::Collection::List &collections) { QVectorIterator it(collections); while (it.hasNext()) { const Collection collection = it.next(); if (isHidden(collection)) { continue; } m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); node->parent = -1; node->type = Node::Collection; m_childEntities[-1].prepend(node); fetchItems(collection); } } static QSet getChildren(Collection::Id parent, const QHash &childParentMap) { QSet children; for (auto it = childParentMap.cbegin(), e = childParentMap.cend(); it != e; ++it) { if (it.value() == parent) { children << it.key(); children += getChildren(it.key(), childParentMap); } } return children; } void EntityTreeModelPrivate::collectionsFetched(const Akonadi::Collection::List &collections) { Q_Q(EntityTreeModel); QTime t; t.start(); QVectorIterator it(collections); QHash collectionsToInsert; while (it.hasNext()) { const Collection collection = it.next(); const Collection::Id collectionId = collection.id(); if (isHidden(collection)) { continue; } auto collectionIt = m_collections.find(collectionId); if (collectionIt != m_collections.end()) { // This is probably the result of a parent of a previous collection already being in the model. // Replace the dummy collection with the real one and move on. // This could also be the result of a monitor signal having already inserted the collection // into this model. There's no way to tell, so we just emit dataChanged. *collectionIt = collection; const QModelIndex collectionIndex = indexForCollection(collection); dataChanged(collectionIndex, collectionIndex); Q_EMIT q->collectionFetched(collectionId); continue; } //If we're monitoring collections somewhere in the tree we need to retrieve their ancestors now if (collection.parentCollection() != m_rootCollection && m_monitor->collectionsMonitored().contains(collection)) { retrieveAncestors(collection, false); } collectionsToInsert.insert(collectionId, collection); } //Build a list of subtrees to insert, with the root of the subtree on the left, and the complete subtree including root on the right QHash > subTreesToInsert; { //Build a child-parent map that allows us to build the subtrees afterwards QHash childParentMap; Q_FOREACH (const Collection &col, collectionsToInsert) { childParentMap.insert(col.id(), col.parentCollection().id()); //Complete the subtree up to the last known parent Collection parent = col.parentCollection(); while (parent.isValid() && parent != m_rootCollection && !m_collections.contains(parent.id())) { childParentMap.insert(parent.id(), parent.parentCollection().id()); if (!collectionsToInsert.contains(parent.id())) { collectionsToInsert.insert(parent.id(), parent); } parent = parent.parentCollection(); } } QSet parents; //Find toplevel parents of the subtrees for (auto it = childParentMap.cbegin(), e = childParentMap.cend(); it != e; ++it) { //The child has a parent without parent (it's a toplevel node that is not yet in m_collections) if (!childParentMap.contains(it.value())) { Q_ASSERT(!m_collections.contains(it.key())); parents << it.key(); } } //Find children of each subtree Q_FOREACH (Collection::Id p, parents) { QSet children; //We add the parent itself as well so it can be inserted below as part of the same loop children << p; children += getChildren(p, childParentMap); subTreesToInsert[p] = children; } } const int row = 0; QHashIterator > collectionIt(subTreesToInsert); while (collectionIt.hasNext()) { collectionIt.next(); const Collection::Id topCollectionId = collectionIt.key(); qCDebug(DebugETM) << "Subtree: " << topCollectionId << collectionIt.value(); Q_ASSERT(!m_collections.contains(topCollectionId)); Collection topCollection = collectionsToInsert.value(topCollectionId); Q_ASSERT(topCollection.isValid()); //The toplevels parent must already be part of the model Q_ASSERT(m_collections.contains(topCollection.parentCollection().id())); const QModelIndex parentIndex = indexForCollection(topCollection.parentCollection()); q->beginInsertRows(parentIndex, row, row); Q_ASSERT(!collectionIt.value().isEmpty()); foreach (Collection::Id collectionId, collectionIt.value()) { const Collection collection = collectionsToInsert.take(collectionId); Q_ASSERT(collection.isValid()); m_collections.insert(collectionId, collection); Node *node = new Node; node->id = collectionId; Q_ASSERT(collection.parentCollection().isValid()); node->parent = collection.parentCollection().id(); node->type = Node::Collection; m_childEntities[node->parent].prepend(node); } q->endInsertRows(); if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { foreach (const Collection::Id &collectionId, collectionIt.value()) { const auto col = m_collections.value(collectionId); if (!m_mimeChecker.hasWantedMimeTypes() || m_mimeChecker.isWantedCollection(col)) { fetchItems(col); } else { // Consider collections that don't contain relevant mimetypes to be populated m_populatedCols.insert(collectionId); Q_EMIT q_ptr->collectionPopulated(collectionId); const auto idx = indexForCollection(Collection(collectionId)); Q_ASSERT(idx.isValid()); dataChanged(idx, idx); } } } } } void EntityTreeModelPrivate::itemsFetched(const Akonadi::Item::List &items) { Q_Q(EntityTreeModel); const Collection::Id collectionId = q->sender()->property(FetchCollectionId().constData()).value(); itemsFetched(collectionId, items); } void EntityTreeModelPrivate::itemsFetched(const Collection::Id collectionId, const Akonadi::Item::List &items) { Q_Q(EntityTreeModel); if (!m_collections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "Collection has been removed while fetching items"; return; } Item::List itemsToInsert; const Collection collection = m_collections.value(collectionId); Q_ASSERT(collection.isValid()); // if there are any items at all, remove from set of collections known to be empty if (!items.isEmpty()) { m_collectionsWithoutItems.remove(collectionId); } foreach (const Item &item, items) { if (isHidden(item)) { continue; } if ((!m_mimeChecker.hasWantedMimeTypes() || m_mimeChecker.isWantedItem(item))) { // When listing virtual collections we might get results for items which are already in // the model if their concrete collection has already been listed. // In that case the collectionId should be different though. // As an additional complication, new items might be both part of fetch job results and // part of monitor notifications. We only insert items which are not already in the model // considering their (possibly virtual) parent. bool isNewItem = true; auto itemIt = m_items.find(item.id()); if (itemIt != m_items.end()) { const Akonadi::Collection::List parents = getParentCollections(item); for (const Akonadi::Collection &parent : parents) { if (parent.id() == collectionId) { qCWarning(AKONADICORE_LOG) << "Fetched an item which is already in the model"; // Update it in case the revision changed; itemIt->value.apply(item); isNewItem = false; break; } } } if (isNewItem) { itemsToInsert << item; } } } if (!itemsToInsert.isEmpty()) { const Collection::Id colId = m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch ? m_rootCollection.id() : m_collectionFetchStrategy == EntityTreeModel::FetchNoCollections ? m_rootCollection.id() : collectionId; const int startRow = m_childEntities.value(colId).size(); Q_ASSERT(m_collections.contains(colId)); const QModelIndex parentIndex = indexForCollection(m_collections.value(colId)); q->beginInsertRows(parentIndex, startRow, startRow + itemsToInsert.size() - 1); foreach (const Item &item, itemsToInsert) { const Item::Id itemId = item.id(); m_items.ref(itemId, item); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; m_childEntities[colId].append(node); } q->endInsertRows(); } } void EntityTreeModelPrivate::monitoredMimeTypeChanged(const QString &mimeType, bool monitored) { beginResetModel(); if (monitored) { m_mimeChecker.addWantedMimeType(mimeType); } else { m_mimeChecker.removeWantedMimeType(mimeType); } endResetModel(); } void EntityTreeModelPrivate::monitoredCollectionsChanged(const Akonadi::Collection &collection, bool monitored) { if (monitored) { const CollectionFetchJob::Type fetchType = getFetchType(m_collectionFetchStrategy); fetchCollections(collection, CollectionFetchJob::Base); fetchCollections(collection, fetchType); } else { //If a collection is dereferenced and no longer explicitly monitored it might still match other filters if (!shouldBePartOfModel(collection)) { monitoredCollectionRemoved(collection); } } } void EntityTreeModelPrivate::monitoredItemsChanged(const Akonadi::Item &item, bool monitored) { Q_UNUSED(item) Q_UNUSED(monitored) beginResetModel(); endResetModel(); } void EntityTreeModelPrivate::monitoredResourcesChanged(const QByteArray &resource, bool monitored) { Q_UNUSED(resource) Q_UNUSED(monitored) beginResetModel(); endResetModel(); } void EntityTreeModelPrivate::retrieveAncestors(const Akonadi::Collection &collection, bool insertBaseCollection) { Q_Q(EntityTreeModel); Collection parentCollection = collection.parentCollection(); Q_ASSERT(parentCollection.isValid()); Q_ASSERT(parentCollection != Collection::root()); Collection::List ancestors; while (parentCollection != Collection::root() && !m_collections.contains(parentCollection.id())) { // Put a temporary node in the tree later. ancestors.prepend(parentCollection); parentCollection = parentCollection.parentCollection(); } Q_ASSERT(parentCollection.isValid()); - // if m_rootCollection is Collection::root(), we always have common ancestor and do the retrival + // if m_rootCollection is Collection::root(), we always have common ancestor and do the retrieval // if we traversed up to Collection::root() but are looking at a subtree only (m_rootCollection != Collection::root()) // we have no common ancestor, and we don't have to retrieve anything if (parentCollection == Collection::root() && m_rootCollection != Collection::root()) { return; } if (ancestors.isEmpty() && !insertBaseCollection) { //Nothing to do, avoid emitting insert signals return; } if (!ancestors.isEmpty()) { // Fetch the real ancestors CollectionFetchJob *job = new CollectionFetchJob(ancestors, CollectionFetchJob::Base, m_session); job->fetchScope().setListFilter(m_listFilter); job->fetchScope().setIncludeStatistics(m_includeStatistics); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(ancestorsFetched(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); } // Q_ASSERT( parentCollection != m_rootCollection ); const QModelIndex parent = indexForCollection(parentCollection); // Still prepending all collections for now. int row = 0; // Although we insert several Collections here, we only need to notify though the model // about the top-level one. The rest will be found automatically by the view. q->beginInsertRows(parent, row, row); Collection::List::const_iterator it = ancestors.constBegin(); const Collection::List::const_iterator end = ancestors.constEnd(); for (; it != end; ++it) { const Collection ancestor = *it; Q_ASSERT(ancestor.parentCollection().isValid()); m_collections.insert(ancestor.id(), ancestor); Node *node = new Node; node->id = ancestor.id(); node->parent = ancestor.parentCollection().id(); node->type = Node::Collection; m_childEntities[node->parent].prepend(node); } if (insertBaseCollection) { m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); // Can't just use parentCollection because that doesn't necessarily refer to collection. node->parent = collection.parentCollection().id(); node->type = Node::Collection; m_childEntities[node->parent].prepend(node); } q->endInsertRows(); } void EntityTreeModelPrivate::ancestorsFetched(const Akonadi::Collection::List &collectionList) { for (const Collection &collection : collectionList) { m_collections[collection.id()] = collection; const QModelIndex index = indexForCollection(collection); Q_ASSERT(index.isValid()); dataChanged(index, index); } } void EntityTreeModelPrivate::insertCollection(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Q_ASSERT(collection.isValid()); Q_ASSERT(parent.isValid()); Q_Q(EntityTreeModel); const int row = 0; const QModelIndex parentIndex = indexForCollection(parent); q->beginInsertRows(parentIndex, row, row); m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); node->parent = parent.id(); node->type = Node::Collection; m_childEntities[parent.id()].prepend(node); q->endInsertRows(); } bool EntityTreeModelPrivate::hasChildCollection(const Collection &collection) const { foreach (Node *node, m_childEntities[collection.id()]) { if (node->type == Node::Collection) { const Collection subcol = m_collections[node->id]; if (shouldBePartOfModel(subcol)) { return true; } } } return false; } bool EntityTreeModelPrivate::isAncestorMonitored(const Collection &collection) const { Akonadi::Collection parent = collection.parentCollection(); while (parent.isValid()) { if (m_monitor->collectionsMonitored().contains(parent)) { return true; } parent = parent.parentCollection(); } return false; } bool EntityTreeModelPrivate::shouldBePartOfModel(const Collection &collection) const { if (isHidden(collection)) { return false; } // We want a parent collection if it has at least one child that matches the // wanted mimetype if (hasChildCollection(collection)) { return true; } //Explicitly monitored collection if (m_monitor->collectionsMonitored().contains(collection)) { return true; } //We're explicitly monitoring collections, but didn't match the filter if (!m_mimeChecker.hasWantedMimeTypes() && !m_monitor->collectionsMonitored().isEmpty()) { //The collection should be included if one of the parents is monitored if (isAncestorMonitored(collection)) { return true; } return false; } // Some collection trees contain multiple mimetypes. Even though server side filtering ensures we // only get the ones we're interested in from the job, we have to filter on collections received through signals too. if (m_mimeChecker.hasWantedMimeTypes() && !m_mimeChecker.isWantedCollection(collection)) { return false; } if (m_listFilter == CollectionFetchScope::Enabled) { if (!collection.enabled()) { return false; } } else if (m_listFilter == CollectionFetchScope::Display) { if (!collection.shouldList(Collection::ListDisplay)) { return false; } } else if (m_listFilter == CollectionFetchScope::Sync) { if (!collection.shouldList(Collection::ListSync)) { return false; } } else if (m_listFilter == CollectionFetchScope::Index) { if (!collection.shouldList(Collection::ListIndex)) { return false; } } return true; } void EntityTreeModelPrivate::monitoredCollectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { // If the resource is removed while populating the model with it, we might still // get some monitor signals. These stale/out-of-order signals can't be completely eliminated // in the akonadi server due to implementation details, so we also handle such signals in the model silently // in all the monitored slots. // Stephen Kelly, 28, July 2009 // If a fetch job is started and a collection is added to akonadi after the fetch job is started, the // new collection will be added to the fetch job results. It will also be notified through the monitor. // We return early here in that case. if (m_collections.contains(collection.id())) { return; } //If the resource is explicitly monitored all other checks are skipped. topLevelCollectionsFetched still checks the hidden attribute. if (m_monitor->resourcesMonitored().contains(collection.resource().toUtf8()) && collection.parentCollection() == Collection::root()) { return topLevelCollectionsFetched(Collection::List() << collection); } if (!shouldBePartOfModel(collection)) { return; } if (!m_collections.contains(parent.id())) { // The collection we're interested in is contained in a collection we're not interested in. // We download the ancestors of the collection we're interested in to complete the tree. if (collection != Collection::root()) { retrieveAncestors(collection); } if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { fetchItems(collection); } return; } insertCollection(collection, parent); if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { fetchItems(collection); } } void EntityTreeModelPrivate::monitoredCollectionRemoved(const Akonadi::Collection &collection) { //if an explicitly monitored collection is removed, we would also have to remove collections which were included to show it (as in the move case) if ((collection == m_rootCollection) || m_monitor->collectionsMonitored().contains(collection)) { beginResetModel(); endResetModel(); return; } Collection::Id parentId = collection.parentCollection().id(); if (parentId < 0) { parentId = -1; } if (!m_collections.contains(parentId)) { return; } // This may be a signal for a collection we've already removed by removing its ancestor. // Or the collection may have been hidden. if (!m_collections.contains(collection.id())) { return; } Q_Q(EntityTreeModel); Q_ASSERT(m_childEntities.contains(parentId)); const int row = indexOf(m_childEntities.value(parentId), collection.id()); Q_ASSERT(row >= 0); Q_ASSERT(m_collections.contains(parentId)); const Collection parentCollection = m_collections.value(parentId); m_populatedCols.remove(collection.id()); const QModelIndex parentIndex = indexForCollection(parentCollection); q->beginRemoveRows(parentIndex, row, row); // Delete all descendant collections and items. removeChildEntities(collection.id()); // Remove deleted collection from its parent. delete m_childEntities[parentId].takeAt(row); // Remove deleted collection itself. m_collections.remove(collection.id()); q->endRemoveRows(); // After removing a collection, check whether it's parent should be removed too if (!shouldBePartOfModel(parentCollection)) { monitoredCollectionRemoved(parentCollection); } } void EntityTreeModelPrivate::removeChildEntities(Collection::Id collectionId) { QList childList = m_childEntities.value(collectionId); QList::const_iterator it = childList.constBegin(); const QList::const_iterator end = childList.constEnd(); for (; it != end; ++it) { if (Node::Item == (*it)->type) { m_items.unref((*it)->id); } else { removeChildEntities((*it)->id); m_collections.remove((*it)->id); m_populatedCols.remove((*it)->id); } } qDeleteAll(m_childEntities.take(collectionId)); } QStringList EntityTreeModelPrivate::childCollectionNames(const Collection &collection) const { QStringList names; foreach (Node *node, m_childEntities[collection.id()]) { if (node->type == Node::Collection) { names << m_collections.value(node->id).name(); } } return names; } void EntityTreeModelPrivate::monitoredCollectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destCollection) { if (isHidden(collection)) { return; } if (isHidden(sourceCollection)) { if (isHidden(destCollection)) { return; } monitoredCollectionAdded(collection, destCollection); return; } else if (isHidden(destCollection)) { monitoredCollectionRemoved(collection); return; } if (!m_collections.contains(collection.id())) { return; } if (m_monitor->collectionsMonitored().contains(collection)) { //if we don't reset here, we would have to make sure that destination collection is actually available, //and remove the sources parents if they were only included as parents of the moved collection beginResetModel(); endResetModel(); return; } Q_Q(EntityTreeModel); const QModelIndex srcParentIndex = indexForCollection(sourceCollection); const QModelIndex destParentIndex = indexForCollection(destCollection); Q_ASSERT(collection.parentCollection().isValid()); Q_ASSERT(destCollection.isValid()); Q_ASSERT(collection.parentCollection() == destCollection); const int srcRow = indexOf(m_childEntities.value(sourceCollection.id()), collection.id()); const int destRow = 0; // Prepend collections if (!q->beginMoveRows(srcParentIndex, srcRow, srcRow, destParentIndex, destRow)) { qCWarning(AKONADICORE_LOG) << "Invalid move"; return; } Node *node = m_childEntities[sourceCollection.id()].takeAt(srcRow); // collection has the correct parentCollection etc. We need to set it on the // internal data structure to not corrupt things. m_collections.insert(collection.id(), collection); node->parent = destCollection.id(); m_childEntities[destCollection.id()].prepend(node); q->endMoveRows(); } void EntityTreeModelPrivate::monitoredCollectionChanged(const Akonadi::Collection &collection) { if (!m_collections.contains(collection.id())) { // This can happen if // * we get a change notification after removing the collection. // * a collection of a non-monitored mimetype is changed elsewhere. Monitor does not // filter by content mimetype of Collections so we get notifications for all of them. //We might match the filter now, retry adding the collection monitoredCollectionAdded(collection, collection.parentCollection()); return; } if (!shouldBePartOfModel(collection)) { monitoredCollectionRemoved(collection); return; } m_collections[collection.id()] = collection; if (!m_showRootCollection && collection == m_rootCollection) { // If the root of the model is not Collection::root it might be modified. // But it doesn't exist in the accessible model structure, so we need to early return return; } const QModelIndex index = indexForCollection(collection); Q_ASSERT(index.isValid()); dataChanged(index, index); } void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics) { if (!m_collections.contains(id)) { return; } m_collections[id].setStatistics(statistics); // if the item count becomes 0, add to set of collections we know to be empty // otherwise remove if in there if (statistics.count() == 0) { m_collectionsWithoutItems.insert(id); } else { m_collectionsWithoutItems.remove(id); } if (!m_showRootCollection && id == m_rootCollection.id()) { // If the root of the model is not Collection::root it might be modified. // But it doesn't exist in the accessible model structure, so we need to early return return; } const QModelIndex index = indexForCollection(m_collections[id]); dataChanged(index, index); } void EntityTreeModelPrivate::monitoredItemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch && !m_collections.contains(collection.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'added' notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } if (m_items.contains(item.id())) { return; } Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collection.id()) : true); if (m_mimeChecker.hasWantedMimeTypes() && !m_mimeChecker.isWantedItem(item)) { return; } //Adding items to not yet populated collections would block fetchMore, resulting in only new items showing up in the collection //This is only a problem with lazy population, otherwise fetchMore is not used at all if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(collection.id())) { return; } int row; QModelIndex parentIndex; if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) { row = m_childEntities.value(collection.id()).size(); parentIndex = indexForCollection(m_collections.value(collection.id())); } else { row = q->rowCount(); } q->beginInsertRows(parentIndex, row, row); m_items.ref(item.id(), item); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) { m_childEntities[collection.id()].append(node); } else { m_childEntities[m_rootCollection.id()].append(node); } q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemRemoved(const Akonadi::Item &item, const Akonadi::Collection &parentCollection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(parentCollection.isValid() ? parentCollection.id() : item.parentCollection().id())) { return; } const Collection::List parents = getParentCollections(item); if (parents.isEmpty()) { return; } if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'removed' notification for an item which was already removed." << item.id() << item.remoteId(); return; } for (const auto &collection : parents) { Q_ASSERT(m_collections.contains(collection.id())); Q_ASSERT(m_childEntities.contains(collection.id())); const int row = indexOf(m_childEntities.value(collection.id()), item.id()); Q_ASSERT(row >= 0); const QModelIndex parentIndex = indexForCollection(m_collections.value(collection.id())); q->beginRemoveRows(parentIndex, row, row); m_items.unref(item.id()); delete m_childEntities[collection.id()].takeAt(row); q->endRemoveRows(); } } void EntityTreeModelPrivate::monitoredItemChanged(const Akonadi::Item &item, const QSet &) { if (isHidden(item)) { return; } if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(item.parentCollection().id())) { return; } auto itemIt = m_items.find(item.id()); if (itemIt == m_items.end()) { qCWarning(AKONADICORE_LOG) << "Got a stale 'changed' notification for an item which was already removed." << item.id() << item.remoteId(); return; } itemIt->value.apply(item); // Notifications about itemChange are always dispatched for real collection // and also all virtual collections the item belongs to. In order to preserve // the original storage collection when we need to have special handling for // notifications for virtual collections if (item.parentCollection().isVirtual()) { const Collection originalParent = itemIt->value.parentCollection(); itemIt->value.setParentCollection(originalParent); } const QModelIndexList indexes = indexesForItem(item); for (const QModelIndex &index : indexes) { if (index.isValid()) { dataChanged(index, index); } else { qCWarning(AKONADICORE_LOG) << "item has invalid index:" << item.id() << item.remoteId(); } } } void EntityTreeModelPrivate::monitoredItemMoved(const Akonadi::Item &item, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destCollection) { if (isHidden(item)) { return; } if (isHidden(sourceCollection)) { if (isHidden(destCollection)) { return; } monitoredItemAdded(item, destCollection); return; } else if (isHidden(destCollection)) { monitoredItemRemoved(item, sourceCollection); return; } else { monitoredItemRemoved(item, sourceCollection); monitoredItemAdded(item, destCollection); return; } // "Temporarily" commented out as it's likely the best course to // avoid the dreaded "reset storm" (or layoutChanged storm). The // whole itemMoved idea is great but not practical until all the // other proxy models play nicely with it, right now they just // transform moved signals in layout changed, which explodes into // a reset of the source model inside of the message list (ouch!) #if 0 if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'moved' notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collections.contains(sourceCollection.id())); Q_ASSERT(m_collections.contains(destCollection.id())); const QModelIndex srcIndex = indexForCollection(sourceCollection); const QModelIndex destIndex = indexForCollection(destCollection); // Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes? const Item::Id itemId = item.id(); const int srcRow = indexOf(m_childEntities.value(sourceCollection.id()), itemId); const int destRow = q->rowCount(destIndex); Q_ASSERT(srcRow >= 0); Q_ASSERT(destRow >= 0); if (!q->beginMoveRows(srcIndex, srcRow, srcRow, destIndex, destRow)) { qCWarning(AKONADICORE_LOG) << "Invalid move"; return; } Q_ASSERT(m_childEntities.contains(sourceCollection.id())); Q_ASSERT(m_childEntities[sourceCollection.id()].size() > srcRow); Node *node = m_childEntities[sourceCollection.id()].takeAt(srcRow); m_items.insert(item.id(), item); node->parent = destCollection.id(); m_childEntities[destCollection.id()].append(node); q->endMoveRows(); #endif } void EntityTreeModelPrivate::monitoredItemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } const Collection::Id collectionId = collection.id(); const Item::Id itemId = item.id(); if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch && !m_collections.contains(collection.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'linked' notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collectionId) : true); if (m_mimeChecker.hasWantedMimeTypes() && !m_mimeChecker.isWantedItem(item)) { return; } //Adding items to not yet populated collections would block fetchMore, resullting in only new items showing up in the collection //This is only a problem with lazy population, otherwise fetchMore is not used at all if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(collectionId)) { return; } QList &collectionEntities = m_childEntities[collectionId]; const int existingPosition = indexOf(collectionEntities, itemId); if (existingPosition > 0) { qCWarning(AKONADICORE_LOG) << "Item with id " << itemId << " already in virtual collection with id " << collectionId; return; } const int row = collectionEntities.size(); const QModelIndex parentIndex = indexForCollection(m_collections.value(collectionId)); q->beginInsertRows(parentIndex, row, row); m_items.ref(itemId, item); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; collectionEntities.append(node); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(item.parentCollection().id())) { return; } if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'unlinked' notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collection.id()) : true); const int row = indexOf(m_childEntities.value(collection.id()), item.id()); if (row < 0 || row >= m_childEntities[ collection.id() ].size()) { qCWarning(AKONADICORE_LOG) << "couldn't find index of unlinked item " << item.id() << collection.id() << row; Q_ASSERT(false); return; } const QModelIndex parentIndex = indexForCollection(m_collections.value(collection.id())); q->beginRemoveRows(parentIndex, row, row); delete m_childEntities[collection.id()].takeAt(row); m_items.unref(item.id()); q->endRemoveRows(); } void EntityTreeModelPrivate::collectionFetchJobDone(KJob *job) { m_pendingCollectionFetchJobs.remove(job); CollectionFetchJob *cJob = static_cast(job); if (job->error()) { qCWarning(AKONADICORE_LOG) << "Job error: " << job->errorString() << "for collection:" << cJob->collections() << endl; return; } if (!m_collectionTreeFetched && m_pendingCollectionFetchJobs.isEmpty()) { m_collectionTreeFetched = true; Q_EMIT q_ptr->collectionTreeFetched(m_collections | values | toQVector); } qCDebug(DebugETM) << "Fetch job took " << jobTimeTracker.take(job).elapsed() << "msec"; qCDebug(DebugETM) << "was collection fetch job: collections:" << cJob->collections().size(); if (!cJob->collections().isEmpty()) { qCDebug(DebugETM) << "first fetched collection:" << cJob->collections().at(0).name(); } } void EntityTreeModelPrivate::itemFetchJobDone(KJob *job) { const Collection::Id collectionId = job->property(FetchCollectionId().constData()).value(); m_pendingCollectionRetrieveJobs.remove(collectionId); if (job->error()) { qCWarning(AKONADICORE_LOG) << "Job error: " << job->errorString() << "for collection:" << collectionId << endl; return; } if (!m_collections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "Collection has been removed while fetching items"; return; } ItemFetchJob *iJob = static_cast(job); qCDebug(DebugETM) << "Fetch job took " << jobTimeTracker.take(job).elapsed() << "msec"; qCDebug(DebugETM) << "was item fetch job: items:" << iJob->count(); if (!iJob->count()) { m_collectionsWithoutItems.insert(collectionId); } else { m_collectionsWithoutItems.remove(collectionId); } m_populatedCols.insert(collectionId); Q_EMIT q_ptr->collectionPopulated(collectionId); // If collections are not in the model, there will be no valid index for them. if ((m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) && (m_collectionFetchStrategy != EntityTreeModel::FetchNoCollections) && !(!m_showRootCollection && collectionId == m_rootCollection.id())) { const QModelIndex index = indexForCollection(Collection(collectionId)); Q_ASSERT(index.isValid()); //To notify about the changed fetch and population state dataChanged(index, index); } } void EntityTreeModelPrivate::pasteJobDone(KJob *job) { if (job->error()) { QString errorMsg; if (qobject_cast(job)) { errorMsg = i18n("Could not copy item:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not copy collection:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not move item:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not move collection:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not link entity:"); } errorMsg += QLatin1Char(' ') + job->errorString(); QMessageBox::critical(nullptr, i18n("Error"), errorMsg); } } void EntityTreeModelPrivate::updateJobDone(KJob *job) { if (job->error()) { // TODO: handle job errors qCWarning(AKONADICORE_LOG) << "Job error:" << job->errorString(); } } void EntityTreeModelPrivate::rootFetchJobDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); return; } CollectionFetchJob *collectionJob = qobject_cast(job); const Collection::List list = collectionJob->collections(); Q_ASSERT(list.size() == 1); m_rootCollection = list.first(); startFirstListJob(); } void EntityTreeModelPrivate::startFirstListJob() { Q_Q(EntityTreeModel); if (!m_collections.isEmpty()) { return; } // Even if the root collection is the invalid collection, we still need to start // the first list job with Collection::root. if (m_showRootCollection) { // Notify the outside that we're putting collection::root into the model. q->beginInsertRows(QModelIndex(), 0, 0); m_collections.insert(m_rootCollection.id(), m_rootCollection); delete m_rootNode; m_rootNode = new Node; m_rootNode->id = m_rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_childEntities[-1].append(m_rootNode); q->endInsertRows(); } else { // Otherwise store it silently because it's not part of the usable model. delete m_rootNode; m_rootNode = new Node; m_needDeleteRootNode = true; m_rootNode->id = m_rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_collections.insert(m_rootCollection.id(), m_rootCollection); } const bool noMimetypes = !m_mimeChecker.hasWantedMimeTypes(); const bool noResources = m_monitor->resourcesMonitored().isEmpty(); const bool multipleCollections = m_monitor->collectionsMonitored().size() > 1; const bool generalPopulation = !noMimetypes || noResources; const CollectionFetchJob::Type fetchType = getFetchType(m_collectionFetchStrategy); //Collections can only be monitored if no resources and no mimetypes are monitored if (multipleCollections && noMimetypes && noResources) { fetchCollections(m_monitor->collectionsMonitored(), CollectionFetchJob::Base); fetchCollections(m_monitor->collectionsMonitored(), fetchType); return; } qCDebug(DebugETM) << "GEN" << generalPopulation << noMimetypes << noResources; if (generalPopulation) { fetchCollections(m_rootCollection, fetchType); } // If the root collection is not collection::root, then it could have items, and they will need to be // retrieved now. // Only fetch items NOT if there is NoItemPopulation, or if there is Lazypopulation and the root is visible // (if the root is not visible the lazy population can not be triggered) if ((m_itemPopulation != EntityTreeModel::NoItemPopulation) && !((m_itemPopulation == EntityTreeModel::LazyPopulation) && m_showRootCollection)) { if (m_rootCollection != Collection::root()) { fetchItems(m_rootCollection); } } // Resources which are explicitly monitored won't have appeared yet if their mimetype didn't match. // We fetch the top level collections and examine them for whether to add them. // This fetches virtual collections into the tree. if (!m_monitor->resourcesMonitored().isEmpty()) { fetchTopLevelCollections(); } } void EntityTreeModelPrivate::fetchTopLevelCollections() const { Q_Q(const EntityTreeModel); CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel, m_session); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(topLevelCollectionsFetched(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); qCDebug(DebugETM) << "EntityTreeModelPrivate::fetchTopLevelCollections"; jobTimeTracker[job].start(); } void EntityTreeModelPrivate::topLevelCollectionsFetched(const Akonadi::Collection::List &list) { Q_Q(EntityTreeModel); for (const Collection &collection : list) { // These collections have been explicitly shown in the Monitor, // but hidden trumps that for now. This may change in the future if we figure out a use for it. if (isHidden(collection)) { continue; } if (m_monitor->resourcesMonitored().contains(collection.resource().toUtf8()) && !m_collections.contains(collection.id())) { const QModelIndex parentIndex = indexForCollection(collection.parentCollection()); // Prepending new collections. const int row = 0; q->beginInsertRows(parentIndex, row, row); m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); Q_ASSERT(collection.parentCollection() == Collection::root()); node->parent = collection.parentCollection().id(); node->type = Node::Collection; m_childEntities[collection.parentCollection().id()].prepend(node); q->endInsertRows(); if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { fetchItems(collection); } Q_ASSERT(collection.isValid()); fetchCollections(collection, CollectionFetchJob::Recursive); } } } Akonadi::Collection::List EntityTreeModelPrivate::getParentCollections(const Item &item) const { Collection::List list; QHashIterator > iter(m_childEntities); while (iter.hasNext()) { iter.next(); int nodeIndex = indexOf(iter.value(), item.id()); if (nodeIndex != -1 && iter.value().at(nodeIndex)->type == Node::Item) { list << m_collections.value(iter.key()); } } return list; } void EntityTreeModelPrivate::ref(Collection::Id id) { m_monitor->d_ptr->ref(id); } bool EntityTreeModelPrivate::shouldPurge(Collection::Id id) { // reference counted collections should never be purged // they first have to be deref'ed until they reach 0. // if the collection is buffered, keep it. if (m_monitor->d_ptr->isMonitored(id)) { return false; } // otherwise we can safely purge this item return true; } bool EntityTreeModelPrivate::isMonitored(Collection::Id id) { return m_monitor->d_ptr->isMonitored(id); } bool EntityTreeModelPrivate::isBuffered(Collection::Id id) { return m_monitor->d_ptr->m_buffer.isBuffered(id); } void EntityTreeModelPrivate::deref(Collection::Id id) { const Collection::Id bumpedId = m_monitor->d_ptr->deref(id); if (bumpedId < 0) { return; } //The collection has already been removed, don't purge if (!m_collections.contains(bumpedId)) { return; } if (shouldPurge(bumpedId)) { purgeItems(bumpedId); } } QList::iterator EntityTreeModelPrivate::skipCollections(QList::iterator it, QList::iterator end, int *pos) { for (; it != end; ++it) { if ((*it)->type == Node::Item) { break; } ++(*pos); } return it; } QList::iterator EntityTreeModelPrivate::removeItems(QList::iterator it, QList::iterator end, int *pos, const Collection &collection) { Q_Q(EntityTreeModel); QList::iterator startIt = it; // figure out how many items we will delete int start = *pos; for (; it != end; ++it) { if ((*it)->type != Node::Item) { break; } ++(*pos); } it = startIt; const QModelIndex parentIndex = indexForCollection(collection); q->beginRemoveRows(parentIndex, start, (*pos) - 1); const int toDelete = (*pos) - start; Q_ASSERT(toDelete > 0); QList &es = m_childEntities[collection.id()]; //NOTE: .erase will invalidate all iterators besides "it"! for (int i = 0; i < toDelete; ++i) { Q_ASSERT(es.count(*it) == 1); // don't keep implicitly shared data alive Q_ASSERT(m_items.contains((*it)->id)); m_items.unref((*it)->id); // delete actual node delete *it; it = es.erase(it); } q->endRemoveRows(); return it; } void EntityTreeModelPrivate::purgeItems(Collection::Id id) { QList &childEntities = m_childEntities[id]; const Collection collection = m_collections.value(id); Q_ASSERT(collection.isValid()); QList::iterator begin = childEntities.begin(); QList::iterator end = childEntities.end(); int pos = 0; while ((begin = skipCollections(begin, end, &pos)) != end) { begin = removeItems(begin, end, &pos, collection); end = childEntities.end(); } m_populatedCols.remove(id); //if an empty collection is purged and we leave it in here, itemAdded will be ignored for the collection //and the collection is never populated by fetchMore (but maybe by statistics changed?) m_collectionsWithoutItems.remove(id); } void EntityTreeModelPrivate::dataChanged(const QModelIndex &top, const QModelIndex &bottom) { Q_Q(EntityTreeModel); QModelIndex rightIndex; Node *node = static_cast(bottom.internalPointer()); if (!node) { return; } if (node->type == Node::Collection) { rightIndex = bottom.sibling(bottom.row(), q->entityColumnCount(EntityTreeModel::CollectionTreeHeaders) - 1); } if (node->type == Node::Item) { rightIndex = bottom.sibling(bottom.row(), q->entityColumnCount(EntityTreeModel::ItemListHeaders) - 1); } Q_EMIT q->dataChanged(top, rightIndex); } QModelIndex EntityTreeModelPrivate::indexForCollection(const Collection &collection) const { Q_Q(const EntityTreeModel); if (!collection.isValid()) { return QModelIndex(); } if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) { return QModelIndex(); } // The id of the parent of Collection::root is not guaranteed to be -1 as assumed by startFirstListJob, // we ensure that we use -1 for the invalid Collection. Collection::Id parentId = -1; if ((collection == m_rootCollection)) { if (m_showRootCollection) { return q->createIndex(0, 0, static_cast(m_rootNode)); } return QModelIndex(); } if (collection == Collection::root()) { parentId = -1; } else if (collection.parentCollection().isValid()) { parentId = collection.parentCollection().id(); } else { QHash >::const_iterator it = m_childEntities.constBegin(); const QHash >::const_iterator end = m_childEntities.constEnd(); for (; it != end; ++it) { const int row = indexOf(it.value(), collection.id()); if (row < 0) { continue; } Node *node = it.value().at(row); return q->createIndex(row, 0, static_cast(node)); } return QModelIndex(); } const int row = indexOf(m_childEntities.value(parentId), collection.id()); if (row < 0) { return QModelIndex(); } Node *node = m_childEntities.value(parentId).at(row); return q->createIndex(row, 0, static_cast(node)); } QModelIndexList EntityTreeModelPrivate::indexesForItem(const Item &item) const { Q_Q(const EntityTreeModel); QModelIndexList indexes; if (m_collectionFetchStrategy == EntityTreeModel::FetchNoCollections) { Q_ASSERT(m_childEntities.contains(m_rootCollection.id())); QList nodeList = m_childEntities.value(m_rootCollection.id()); const int row = indexOf(nodeList, item.id()); Q_ASSERT(row >= 0); Q_ASSERT(row < nodeList.size()); Node *node = nodeList.at(row); indexes << q->createIndex(row, 0, static_cast(node)); return indexes; } const Collection::List collections = getParentCollections(item); indexes.reserve(collections.size()); for (const Collection &collection : collections) { const int row = indexOf(m_childEntities.value(collection.id()), item.id()); Q_ASSERT(row >= 0); Q_ASSERT(m_childEntities.contains(collection.id())); QList nodeList = m_childEntities.value(collection.id()); Q_ASSERT(row < nodeList.size()); Node *node = nodeList.at(row); indexes << q->createIndex(row, 0, static_cast(node)); } return indexes; } void EntityTreeModelPrivate::beginResetModel() { Q_Q(EntityTreeModel); q->beginResetModel(); } void EntityTreeModelPrivate::endResetModel() { Q_Q(EntityTreeModel); foreach (Akonadi::Job *job, m_session->findChildren()) { job->disconnect(q); } m_collections.clear(); m_collectionsWithoutItems.clear(); m_populatedCols.clear(); m_items.clear(); m_pendingCollectionFetchJobs.clear(); m_pendingCollectionRetrieveJobs.clear(); m_collectionTreeFetched = false; foreach (const QList &list, m_childEntities) { qDeleteAll(list); } m_childEntities.clear(); if (m_needDeleteRootNode) { m_needDeleteRootNode = false; delete m_rootNode; } m_rootNode = nullptr; q->endResetModel(); fillModel(); } void EntityTreeModelPrivate::monitoredItemsRetrieved(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); return; } Q_Q(EntityTreeModel); ItemFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob); Item::List list = fetchJob->items(); q->beginResetModel(); foreach (const Item &item, list) { Node *node = new Node; node->id = item.id(); node->parent = m_rootCollection.id(); node->type = Node::Item; m_childEntities[-1].append(node); m_items.ref(item.id(), item); } q->endResetModel(); } void EntityTreeModelPrivate::fillModel() { Q_Q(EntityTreeModel); m_mimeChecker.setWantedMimeTypes(m_monitor->mimeTypesMonitored()); const Collection::List collections = m_monitor->collectionsMonitored(); if (collections.isEmpty() && m_monitor->numMimeTypesMonitored() == 0 && m_monitor->numResourcesMonitored() == 0 && m_monitor->numItemsMonitored() != 0) { m_rootCollection = Collection(-1); m_collectionTreeFetched = true; Q_EMIT q_ptr->collectionTreeFetched(collections); // there are no collections to fetch Item::List items; items.reserve(m_monitor->itemsMonitoredEx().size()); Q_FOREACH (Item::Id id, m_monitor->itemsMonitoredEx()) { items.append(Item(id)); } ItemFetchJob *itemFetch = new ItemFetchJob(items, m_session); itemFetch->setFetchScope(m_monitor->itemFetchScope()); itemFetch->fetchScope().setIgnoreRetrievalErrors(true); q->connect(itemFetch, SIGNAL(finished(KJob*)), q, SLOT(monitoredItemsRetrieved(KJob*))); return; } // In case there is only a single collection monitored, we can use this // collection as root of the node tree, in all other cases // Collection::root() is used if (collections.size() == 1) { m_rootCollection = collections.first(); } else { m_rootCollection = Collection::root(); } if (m_rootCollection == Collection::root()) { QTimer::singleShot(0, q, SLOT(startFirstListJob())); } else { Q_ASSERT(m_rootCollection.isValid()); CollectionFetchJob *rootFetchJob = new CollectionFetchJob(m_rootCollection, CollectionFetchJob::Base, m_session); q->connect(rootFetchJob, SIGNAL(result(KJob*)), SLOT(rootFetchJobDone(KJob*))); qCDebug(DebugETM) << ""; jobTimeTracker[rootFetchJob].start(); } } bool EntityTreeModelPrivate::canFetchMore(const QModelIndex &parent) const { const Item item = parent.data(EntityTreeModel::ItemRole).value(); if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) { return false; } if (item.isValid()) { // items can't have more rows. // TODO: Should I use this for fetching more of an item, ie more payload parts? return false; } else { // but collections can... const Collection::Id colId = parent.data(EntityTreeModel::CollectionIdRole).toULongLong(); // But the root collection can't... if (Collection::root().id() == colId) { return false; } // Collections which contain no items at all can't contain more if (m_collectionsWithoutItems.contains(colId)) { return false; } // Don't start the same job multiple times. if (m_pendingCollectionRetrieveJobs.contains(colId)) { return false; } // Can't fetch more if the collection's items have already been fetched if (m_populatedCols.contains(colId)) { return false; } foreach (Node *node, m_childEntities.value(colId)) { if (Node::Item == node->type) { // Only try to fetch more from a collection if we don't already have items in it. // Otherwise we'd spend all the time listing items in collections. return false; } } return true; } } QIcon EntityTreeModelPrivate::iconForName(const QString &name) const { if (m_iconThemeName != QIcon::themeName()) { m_iconThemeName = QIcon::themeName(); m_iconCache.clear(); } QIcon &icon = m_iconCache[name]; if (icon.isNull()) { icon = QIcon::fromTheme(name); } return icon; } diff --git a/src/core/models/entitytreemodel_p.h b/src/core/models/entitytreemodel_p.h index 23a85a82e..7f971f650 100644 --- a/src/core/models/entitytreemodel_p.h +++ b/src/core/models/entitytreemodel_p.h @@ -1,377 +1,377 @@ /* 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 ENTITYTREEMODELPRIVATE_H #define ENTITYTREEMODELPRIVATE_H #include #include "item.h" #include "collectionfetchjob.h" #include "itemfetchscope.h" #include "mimetypechecker.h" #include "entitytreemodel.h" #include "akonaditests_export.h" #include Q_DECLARE_LOGGING_CATEGORY(DebugETM) namespace Akonadi { class Monitor; class AgentInstance; } struct Node { typedef qint64 Id; Id id; Akonadi::Collection::Id parent; enum Type { Item, Collection }; int type; }; template class RefCountedHash { mutable Value *defaultValue = nullptr; public: inline ~RefCountedHash() { delete defaultValue; } inline auto begin() { return mHash.begin(); } inline auto end() { return mHash.end(); } inline auto begin() const { return mHash.begin(); } inline auto end() const { return mHash.end(); } inline auto find(const Key &key) const { return mHash.find(key); } inline auto find(const Key &key) { return mHash.find(key); } inline bool size() const { return mHash.size(); } inline bool isEmpty() const { return mHash.isEmpty(); } inline void clear() { mHash.clear(); } inline bool contains(const Key &key) const { return mHash.contains(key); } inline const Value &value(const Key &key) const { auto it = mHash.find(key); if (it == mHash.end()) { return defaultValue ? *defaultValue : *(defaultValue = new Value()); } return it->value; } inline const Value &operator[](const Key &key) const { return value(key); } inline auto ref(const Key &key, const Value &value) { auto it = mHash.find(key); if (it != mHash.end()) { ++(it->refCnt); return it; } else { return mHash.insert(key, {1, std::move(value)}); } } inline void unref(const Key &key) { auto it = mHash.find(key); if (it == mHash.end()) { return; } --(it->refCnt); if (it->refCnt == 0) { mHash.erase(it); } } private: template struct RefCountedValue { uint8_t refCnt = 0; V value; }; QHash> mHash; }; namespace Akonadi { /** * @internal */ class AKONADI_TESTS_EXPORT EntityTreeModelPrivate { public: explicit EntityTreeModelPrivate(EntityTreeModel *parent); ~EntityTreeModelPrivate(); EntityTreeModel *const q_ptr; enum RetrieveDepth { Base, Recursive }; void init(Monitor *monitor); void fetchCollections(const Collection &collection, CollectionFetchJob::Type type = CollectionFetchJob::FirstLevel); void fetchCollections(const Collection::List &collections, CollectionFetchJob::Type type = CollectionFetchJob::FirstLevel); void fetchCollections(Akonadi::CollectionFetchJob *job); void fetchItems(const Collection &collection); void collectionsFetched(const Akonadi::Collection::List &collections); void collectionListFetched(const Akonadi::Collection::List &collections); void itemsFetched(const Akonadi::Item::List &items); void itemsFetched(const Collection::Id collectionId, const Akonadi::Item::List &items); void monitoredCollectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent); void monitoredCollectionRemoved(const Akonadi::Collection &collection); void monitoredCollectionChanged(const Akonadi::Collection &collection); void monitoredCollectionStatisticsChanged(Akonadi::Collection::Id, const Akonadi::CollectionStatistics &statistics); void monitoredCollectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destCollection); void monitoredItemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection); void monitoredItemRemoved(const Akonadi::Item &item, const Akonadi::Collection &collection = Akonadi::Collection()); void monitoredItemChanged(const Akonadi::Item &item, const QSet &); void monitoredItemMoved(const Akonadi::Item &item, const Akonadi::Collection &, const Akonadi::Collection &); void monitoredItemLinked(const Akonadi::Item &item, const Akonadi::Collection &); void monitoredItemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &); void monitoredMimeTypeChanged(const QString &mimeType, bool monitored); void monitoredCollectionsChanged(const Akonadi::Collection &collection, bool monitored); void monitoredItemsChanged(const Akonadi::Item &item, bool monitored); void monitoredResourcesChanged(const QByteArray &resource, bool monitored); Collection::List getParentCollections(const Item &item) const; void removeChildEntities(Collection::Id collectionId); /** * Returns the list of names of the child collections of @p collection. */ QStringList childCollectionNames(const Collection &collection) const; /** * Fetch parent collections and insert this @p collection and its parents into the node tree */ void retrieveAncestors(const Akonadi::Collection &collection, bool insertBaseCollection = true); void ancestorsFetched(const Akonadi::Collection::List &collectionList); void insertCollection(const Akonadi::Collection &collection, const Akonadi::Collection &parent); void beginResetModel(); void endResetModel(); /** * Start function for filling the Model, finds and fetches the root of the node tree * Next relevant function for filling the model is startFirstListJob() */ void fillModel(); void changeFetchState(const Collection &parent); - void agentInstanceRemoved(const Akonadi::AgentInstance &instace); + void agentInstanceRemoved(const Akonadi::AgentInstance &instance); QIcon iconForName(const QString &name) const; QHash m_collections; RefCountedHash m_items; QHash > m_childEntities; QSet m_populatedCols; QSet m_collectionsWithoutItems; QVector m_pendingCutItems; QVector m_pendingCutCollections; mutable QSet m_pendingCollectionRetrieveJobs; mutable QSet m_pendingCollectionFetchJobs; // Icon cache to workaround QIcon::fromTheme being very slow (bug #346644) mutable QHash m_iconCache; mutable QString m_iconThemeName; Monitor *m_monitor = nullptr; Collection m_rootCollection; Node *m_rootNode = nullptr; bool m_needDeleteRootNode = false; QString m_rootCollectionDisplayName; QStringList m_mimeTypeFilter; MimeTypeChecker m_mimeChecker; EntityTreeModel::CollectionFetchStrategy m_collectionFetchStrategy; EntityTreeModel::ItemPopulationStrategy m_itemPopulation; CollectionFetchScope::ListFilter m_listFilter; bool m_includeStatistics; bool m_showRootCollection; bool m_collectionTreeFetched; /** * Called after the root collection was fetched by fillModel * * Initiates further fetching of collections depending on the monitored collections * (in the monitor) and the m_collectionFetchStrategy. * * Further collections are either fetched directly with fetchCollections and * fetchItems or, in case that collections or resources are monitored explicitly * via fetchTopLevelCollections */ void startFirstListJob(); void serverStarted(); void monitoredItemsRetrieved(KJob *job); void rootFetchJobDone(KJob *job); void collectionFetchJobDone(KJob *job); void itemFetchJobDone(KJob *job); void updateJobDone(KJob *job); void pasteJobDone(KJob *job); /** * Returns the index of the node in @p list with the id @p id. Returns -1 if not found. */ template int indexOf(const QList &nodes, Node::Id id) const { int i = 0; for (const Node *node : nodes) { if (node->id == id && node->type == Type) { return i; } i++; } return -1; } /** * The id of the collection which starts an item fetch job. This is part of a hack with QObject::sender * in itemsReceivedFromJob to correctly insert items into the model. */ static QByteArray FetchCollectionId() { return "FetchCollectionId"; } Session *m_session = nullptr; Q_DECLARE_PUBLIC(EntityTreeModel) void fetchTopLevelCollections() const; void topLevelCollectionsFetched(const Akonadi::Collection::List &collectionList); /** @returns True if @p item or one of its descendants is hidden. */ bool isHidden(const Item &item) const; bool isHidden(const Collection &collection) const; template bool isHiddenImpl(const T &entity, Node::Type type) const; bool m_showSystemEntities; void ref(Collection::Id id); void deref(Collection::Id id); /** * @returns true if the collection is actively monitored (referenced or buffered with refcounting enabled) * * purely for testing */ bool isMonitored(Collection::Id id); /** * @returns true if the collection is buffered * * purely for testing */ bool isBuffered(Collection::Id id); /** @returns true if the Collection with the id of @p id should be purged. */ bool shouldPurge(Collection::Id id); /** Purges the items in the Collection @p id */ void purgeItems(Collection::Id id); /** Removes the items starting from @p it and up to a maximum of @p end in Collection @p col. @p pos should be the index of @p it in the m_childEntities before calling, and is updated to the position of the next Collection in m_childEntities afterward. This is required to emit model remove signals properly. @returns an iterator pointing to the next Collection after @p it, or at @p end */ QList::iterator removeItems(QList::iterator it, QList::iterator end, int *pos, const Collection &col); /** Skips over Collections in m_childEntities up to a maximum of @p end. @p it is an iterator pointing to the first Collection in a block of Collections, and @p pos initially describes the index of @p it in m_childEntities and is updated to point to the index of the next Item in the list. @returns an iterator pointing to the next Item after @p it, or at @p end */ QList::iterator skipCollections(QList::iterator it, QList::iterator end, int *pos); /** Emits the data changed signal for the entire row as in the subclass, instead of just for the first column. */ void dataChanged(const QModelIndex &top, const QModelIndex &bottom); /** * Returns the model index for the given @p collection. */ QModelIndex indexForCollection(const Collection &collection) const; /** * Returns the model indexes for the given @p item. */ QModelIndexList indexesForItem(const Item &item) const; bool canFetchMore(const QModelIndex &parent) const; /** * Returns true if the collection matches all filters and should be part of the model. * This method checks all properties that could change by modifying the collection. * Currently that includes: * * hidden attribute * * content mime types */ bool shouldBePartOfModel(const Collection &collection) const; bool hasChildCollection(const Collection &collection) const; bool isAncestorMonitored(const Collection &collection) const; }; } #endif diff --git a/src/core/monitor.h b/src/core/monitor.h index ffe796e87..63276beeb 100644 --- a/src/core/monitor.h +++ b/src/core/monitor.h @@ -1,840 +1,840 @@ /* 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. */ #ifndef AKONADI_MONITOR_H #define AKONADI_MONITOR_H #include "akonadicore_export.h" #include "tag.h" #include "collection.h" #include "item.h" #include "relation.h" #include namespace Akonadi { class CollectionFetchScope; class CollectionStatistics; class Item; class ItemFetchScope; class MonitorPrivate; class Session; class TagFetchScope; class NotificationSubscriber; class ChangeNotification; namespace Protocol { class Command; } /** * @short Monitors an item or collection for changes. * * The Monitor emits signals if some of these objects are changed or * removed or new ones are added to the Akonadi storage. * * There are various ways to filter these notifications. There are three types of filter * evaluation: * - (-) removal-only filter, ie. if the filter matches the notification is dropped, * if not filter evaluation continues with the next one * - (+) pass-exit filter, ie. if the filter matches the notification is delivered, * if not evaluation is continued * - (f) final filter, ie. evaluation ends here if the corresponding filter criteria is set, * the notification is delivered depending on the result, evaluation is only continued * if no filter criteria is defined * * The following filter are available, listed in evaluation order: * (1) ignored sessions (-) * (2) monitor everything (+) * (3a) resource and mimetype filters (f) (items only) * (3b) resource filters (f) (collections only) * (4) item is monitored (+) * (5) collection is monitored (+) * * Optionally, the changed objects can be fetched automatically from the server. * To enable this, see itemFetchScope() and collectionFetchScope(). * * Note that as a consequence of rule 3a, it is not possible to monitor (more than zero resources * OR more than zero mimetypes) AND more than zero collections. * * @todo Distinguish between monitoring collection properties and collection content. * @todo Special case for collection content counts changed * * @author Volker Krause */ class AKONADICORE_EXPORT Monitor : public QObject { Q_OBJECT public: enum Type { /** * @internal This must be kept in sync with Akonadi::NotificationMessageV2::Type */ Collections = 1, Items, Tags, Relations, /** * Listen to subscription changes of other Monitors connected to Akonadi. * This is only for debugging purposes and should not be used in real * applications. * @since 5.4 */ Subscribers, /** * Listens to all notifications being emitted by the server and provides * additional information about them. This is only for debugging purposes * and should not be used in real applications. * * @note Enabling monitoring this type has performance impact on the * Akonadi Server. * * @since 5.4 */ Notifications }; /** * Creates a new monitor. * * @param parent The parent object. */ explicit Monitor(QObject *parent = nullptr); /** * Destroys the monitor. */ ~Monitor() override; /** * Sets whether the specified collection shall be monitored for changes. If * monitoring is turned on for the collection, all notifications for items * in that collection will be emitted, and its child collections will also * be monitored. Note that move notifications will be emitted if either one * of the collections involved is being monitored. * * Note that if a session is being ignored, this takes precedence over * setCollectionMonitored() on that session. * * @param collection The collection to monitor. * If this collection is Collection::root(), all collections * in the Akonadi storage will be monitored. * @param monitored Whether to monitor the collection. */ void setCollectionMonitored(const Collection &collection, bool monitored = true); /** * Sets whether the specified item shall be monitored for changes. * * Note that if a session is being ignored, this takes precedence over * setItemMonitored() on that session. * * @param item The item to monitor. * @param monitored Whether to monitor the item. */ void setItemMonitored(const Item &item, bool monitored = true); /** * Sets whether the specified resource shall be monitored for changes. If * monitoring is turned on for the resource, all notifications for * collections and items in that resource will be emitted. * * Note that if a session is being ignored, this takes precedence over * setResourceMonitored() on that session. * * @param resource The resource identifier. * @param monitored Whether to monitor the resource. */ void setResourceMonitored(const QByteArray &resource, bool monitored = true); /** * Sets whether items of the specified mime type shall be monitored for changes. * If monitoring is turned on for the mime type, all notifications for items * matching that mime type will be emitted, but notifications for collections * matching that mime type will only be emitted if this is otherwise specified, * for example by setCollectionMonitored(). * * Note that if a session is being ignored, this takes precedence over * setMimeTypeMonitored() on that session. * * @param mimetype The mime type to monitor. * @param monitored Whether to monitor the mime type. */ void setMimeTypeMonitored(const QString &mimetype, bool monitored = true); /** * Sets whether the specified tag shall be monitored for changes. * * Same rules as for item monitoring apply. * * @param tag Tag to monitor. * @param monitored Whether to monitor the tag. * @since 4.13 */ void setTagMonitored(const Tag &tag, bool monitored = true); /** * Sets whether given type (Collection, Item, Tag should be monitored). * * By default all types are monitored, but once you change one, you have * to explicitly enable all other types you want to monitor. * * @param type Type to monitor. * @param monitored Whether to monitor the type * @since 4.13 */ void setTypeMonitored(Type type, bool monitored = true); /** * Sets whether all items shall be monitored. * @param monitored sets all items as monitored if set as @c true * Note that if a session is being ignored, this takes precedence over * setAllMonitored() on that session. */ void setAllMonitored(bool monitored = true); void setExclusive(bool exclusive = true); Q_REQUIRED_RESULT bool exclusive() const; /** * Ignores all change notifications caused by the given session. This * overrides all other settings on this session. * * @param session The session you want to ignore. */ void ignoreSession(Session *session); /** * Enables automatic fetching of changed collections from the Akonadi storage. * * @param enable @c true enables automatic fetching, @c false disables automatic fetching. */ void fetchCollection(bool enable); /** * Enables automatic fetching of changed collection statistics information from * the Akonadi storage. * * @param enable @c true to enables automatic fetching, @c false disables automatic fetching. */ void fetchCollectionStatistics(bool enable); /** * Sets the item fetch scope. * * Controls how much of an item's data is fetched from the server, e.g. * whether to fetch the full item payload or only meta data. * * @param fetchScope The new scope for item fetch operations. * * @see itemFetchScope() */ void setItemFetchScope(const ItemFetchScope &fetchScope); /** * Instructs the monitor to fetch only those parts that were changed and * were requested in the fetch scope. * * This is taken in account only for item modifications. * Example usage: * @code * monitor->itemFetchScope().fetchFullPayload( true ); * monitor->fetchChangedOnly(true); * @endcode * * In the example if an item was changed, but its payload was not, the full * payload will not be retrieved. * If the item's payload was changed, the monitor retrieves the changed * payload as well. * * The default is to fetch everything requested. * * @since 4.8 * * @param enable @c true to enable the feature, @c false means everything * that was requested will be fetched. */ void fetchChangedOnly(bool enable); /** * Returns the item fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the ItemFetchScope documentation * for an example. * * @return a reference to the current item fetch scope * * @see setItemFetchScope() for replacing the current item fetch scope */ ItemFetchScope &itemFetchScope(); /** * Sets the collection fetch scope. * * Controls which collections are monitored and how much of a collection's data * is fetched from the server. * * @param fetchScope The new scope for collection fetch operations. * * @see collectionFetchScope() * @since 4.4 */ void setCollectionFetchScope(const CollectionFetchScope &fetchScope); /** * Returns the collection fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the CollectionFetchScope documentation * for an example. * * @return a reference to the current collection fetch scope * * @see setCollectionFetchScope() for replacing the current collection fetch scope * @since 4.4 */ CollectionFetchScope &collectionFetchScope(); /** * Sets the tag fetch scope. * * Controls how much of an tag's data is fetched from the server. * * @param fetchScope The new scope for tag fetch operations. * * @see tagFetchScope() */ void setTagFetchScope(const TagFetchScope &fetchScope); /** * Returns the tag fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. * * @return a reference to the current tag fetch scope * * @see setTagFetchScope() for replacing the current tag fetch scope */ TagFetchScope &tagFetchScope(); /** * Returns the list of collections being monitored. * * @since 4.3 */ Q_REQUIRED_RESULT Collection::List collectionsMonitored() const; /** * Returns the set of items being monitored. * * Faster version (at least on 32-bit systems) of itemsMonitored(). * * @since 4.6 */ Q_REQUIRED_RESULT QVector itemsMonitoredEx() const; /** * Returns the number of items being monitored. * Optimization. * @since 4.14.3 */ Q_REQUIRED_RESULT int numItemsMonitored() const; /** * Returns the set of mimetypes being monitored. * * @since 4.3 */ Q_REQUIRED_RESULT QStringList mimeTypesMonitored() const; /** * Returns the number of mimetypes being monitored. * Optimization. * @since 4.14.3 */ Q_REQUIRED_RESULT int numMimeTypesMonitored() const; /** * Returns the set of tags being monitored. * * @since 4.13 */ Q_REQUIRED_RESULT QVector tagsMonitored() const; /** * Returns the set of types being monitored. * * @since 4.13 */ Q_REQUIRED_RESULT QVector typesMonitored() const; /** * Returns the set of identifiers for resources being monitored. * * @since 4.3 */ Q_REQUIRED_RESULT QList resourcesMonitored() const; /** * Returns the number of resources being monitored. * Optimization. * @since 4.14.3 */ Q_REQUIRED_RESULT int numResourcesMonitored() const; /** * Returns true if everything is being monitored. * * @since 4.3 */ Q_REQUIRED_RESULT bool isAllMonitored() const; /** * Sets the session used by the Monitor to communicate with the %Akonadi server. * If not set, the Akonadi::Session::defaultSession is used. * @param session the session to be set * @since 4.4 */ void setSession(Akonadi::Session *session); /** * Returns the Session used by the monitor to communicate with Akonadi. * * @since 4.4 */ Q_REQUIRED_RESULT Session *session() const; /** * Allows to enable/disable collection move translation. If enabled (the default), move * notifications are automatically translated into add/remove notifications if the source/destination * is outside of the monitored collection hierarchy. * @param enabled enables collection move translation if set as @c true * @since 4.9 */ void setCollectionMoveTranslationEnabled(bool enabled); Q_SIGNALS: /** * This signal is emitted if a monitored item has changed, e.g. item parts have been modified. * * @param item The changed item. * @param partIdentifiers The identifiers of the item parts that has been changed. */ void itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers); /** * This signal is emitted if flags of monitored items have changed. * * @param items Items that were changed * @param addedFlags Flags that have been added to each item in @p items * @param removedFlags Flags that have been removed from each item in @p items * @since 4.11 */ void itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags); /** * This signal is emitted if tags of monitored items have changed. * * @param items Items that were changed * @param addedTags Tags that have been added to each item in @p items. * @param removedTags Tags that have been removed from each item in @p items * @since 4.13 */ void itemsTagsChanged(const Akonadi::Item::List &items, const QSet &addedTags, const QSet &removedTags); /** * This signal is emitted if relations of monitored items have changed. * * @param items Items that were changed * @param addedRelations Relations that have been added to each item in @p items. * @param removedRelations Relations that have been removed from each item in @p items * @since 4.15 */ void itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations, const Akonadi::Relation::List &removedRelations); /** * This signal is emitted if a monitored item has been moved between two collections * * @param item The moved item. * @param collectionSource The collection the item has been moved from. * @param collectionDestination The collection the item has been moved to. */ void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination); /** * This is signal is emitted when multiple monitored items have been moved between two collections * * @param items Moved items * @param collectionSource The collection the items have been moved from. * @param collectionDestination The collection the items have been moved to. * * @since 4.11 */ void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination); /** * This signal is emitted if an item has been added to a monitored collection in the Akonadi storage. * * @param item The new item. * @param collection The collection the item has been added to. */ void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection); /** * This signal is emitted if * - a monitored item has been removed from the Akonadi storage * or * - a item has been removed from a monitored collection. * * @param item The removed item. */ void itemRemoved(const Akonadi::Item &item); /** * This signal is emitted if monitored items have been removed from Akonadi * storage of items have been removed from a monitored collection. * * @param items Removed items * * @since 4.11 */ void itemsRemoved(const Akonadi::Item::List &items); /** * This signal is emitted if a reference to an item is added to a virtual collection. * @param item The linked item. * @param collection The collection the item is linked to. * * @since 4.2 */ void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection); /** * This signal is emitted if a reference to multiple items is added to a virtual collection * * @param items The linked items * @param collection The collections the items are linked to * * @since 4.11 */ void itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); /** * This signal is emitted if a reference to an item is removed from a virtual collection. * @param item The unlinked item. * @param collection The collection the item is unlinked from. * * @since 4.2 */ void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection); /** * This signal is emitted if a reference to items is removed from a virtual collection * * @param items The unlinked items * @param collection The collections the items are unlinked from * * @since 4.11 */ void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); /** * This signal is emitted if a new collection has been added to a monitored collection in the Akonadi storage. * * @param collection The new collection. * @param parent The parent collection. */ void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent); /** * This signal is emitted if a monitored collection has been changed (properties or content). * * @param collection The changed collection. */ void collectionChanged(const Akonadi::Collection &collection); /** * This signal is emitted if a monitored collection has been changed (properties or attributes). * * @param collection The changed collection. * @param attributeNames The names of the collection attributes that have been changed. * * @since 4.4 */ void collectionChanged(const Akonadi::Collection &collection, const QSet &attributeNames); /** * This signals is emitted if a monitored collection has been moved. * * @param collection The moved collection. * @param source The previous parent collection. * @param destination The new parent collection. * * @since 4.4 */ void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination); /** * This signal is emitted if a monitored collection has been removed from the Akonadi storage. * * @param collection The removed collection. */ void collectionRemoved(const Akonadi::Collection &collection); /** * This signal is emitted if a collection has been subscribed to by the user. * It will be emitted even for unmonitored collections as the check for whether to * monitor it has not been applied yet. * * @param collection The subscribed collection * @param parent The parent collection of the subscribed collection. * * @since 4.6 */ void collectionSubscribed(const Akonadi::Collection &collection, const Akonadi::Collection &parent); /** * This signal is emitted if a user unsubscribes from a collection. * * @param collection The unsubscribed collection * * @since 4.6 */ void collectionUnsubscribed(const Akonadi::Collection &collection); /** * This signal is emitted if the statistics information of a monitored collection * has changed. * * @param id The collection identifier of the changed collection. * @param statistics The updated collection statistics, invalid if automatic * fetching of statistics changes is disabled. */ void collectionStatisticsChanged(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics); /** * This signal is emitted if a tag has been added to Akonadi storage. * * @param tag The added tag * @since 4.13 */ void tagAdded(const Akonadi::Tag &tag); /** * This signal is emitted if a monitored tag is changed on the server. * * @param tag The changed tag. * @since 4.13 */ void tagChanged(const Akonadi::Tag &tag); /** * This signal is emitted if a monitored tag is removed from the server storage. * * The monitor will also emit itemTagsChanged() signal for all monitored items * (if any) that were tagged by @p tag. * * @param tag The removed tag. * @since 4.13 */ void tagRemoved(const Akonadi::Tag &tag); /** * This signal is emitted if a relation has been added to Akonadi storage. * * The monitor will also emit itemRelationsChanged() signal for all monitored items * hat are affected by @p relation. * * @param relation The added relation * @since 4.13 */ void relationAdded(const Akonadi::Relation &relation); /** * This signal is emitted if a monitored relation is removed from the server storage. * * The monitor will also emit itemRelationsChanged() signal for all monitored items * that were affected by @p relation. * * @param relation The removed relation. * @since 4.13 */ void relationRemoved(const Akonadi::Relation &relation); /** * This signal is emitted when Subscribers are monitored and a new subscriber * subscribers to the server. * * @param subscriber The new subscriber * @since 5.4 * * @note Monitoring for subscribers and listening to this signal only makes * sense if you want to globally debug Monitors. There is no reason to use * this in regular applications. */ void notificationSubscriberAdded(const Akonadi::NotificationSubscriber &subscriber); /** * This signal is emitted when Subscribers are monitored and an existing * subscriber changes its subscription. * * @param subscriber The changed subscriber * @since 5.4 * * @note Monitoring for subscribers and listening to this signal only makes * sense if you want to globally debug Monitors. There is no reason to use * this in regular applications. */ void notificationSubscriberChanged(const Akonadi::NotificationSubscriber &subscriber); /** * This signal is emitted when Subscribers are monitored and an existing * subscriber unsubscribes from the server. * * @param subscriber The removed subscriber * @since 5.4 * * @note Monitoring for subscribers and listening to this signal only makes * sense if you want to globally debug Monitors. There is no reason to use * this in regular applications. */ void notificationSubscriberRemoved(const Akonadi::NotificationSubscriber &subscriber); /** * This signal is emitted when Notifications are monitored and the server emits - * anny change notification. + * any change notification. * * @since 5.4 * * @note Getting introspection into all change notifications only makes sense * if you want to globally debug Notifications. There is no reason to use * this in regular applications. */ void debugNotification(const Akonadi::ChangeNotification ¬ification); /** * This signal is emitted if the Monitor starts or stops monitoring @p collection explicitly. * @param collection The collection * @param monitored Whether the collection is now being monitored or not. * * @since 4.3 */ void collectionMonitored(const Akonadi::Collection &collection, bool monitored); /** * This signal is emitted if the Monitor starts or stops monitoring @p item explicitly. * @param item The item * @param monitored Whether the item is now being monitored or not. * * @since 4.3 */ void itemMonitored(const Akonadi::Item &item, bool monitored); /** * This signal is emitted if the Monitor starts or stops monitoring the resource with the identifier @p identifier explicitly. * @param identifier The identifier of the resource. * @param monitored Whether the resource is now being monitored or not. * * @since 4.3 */ void resourceMonitored(const QByteArray &identifier, bool monitored); /** * This signal is emitted if the Monitor starts or stops monitoring @p mimeType explicitly. * @param mimeType The mimeType. * @param monitored Whether the mimeType is now being monitored or not. * * @since 4.3 */ void mimeTypeMonitored(const QString &mimeType, bool monitored); /** * This signal is emitted if the Monitor starts or stops monitoring everything. * @param monitored Whether everything is now being monitored or not. * * @since 4.3 */ void allMonitored(bool monitored); /** * This signal is emitted if the Monitor starts or stops monitoring @p tag explicitly. * @param tag The tag. * @param monitored Whether the tag is now being monitored or not. * @since 4.13 */ void tagMonitored(const Akonadi::Tag &tag, bool monitored); /** * This signal is emitted if the Monitor starts or stops monitoring @p type explicitly * @param type The type. * @param monitored Whether the type is now being monitored or not. * @since 4.13 */ void typeMonitored(const Akonadi::Monitor::Type type, bool monitored); void monitorReady(); protected: //@cond PRIVATE void connectNotify(const QMetaMethod &signal) override; void disconnectNotify(const QMetaMethod &signal) override; friend class EntityTreeModel; friend class EntityTreeModelPrivate; MonitorPrivate *d_ptr; explicit Monitor(MonitorPrivate *d, QObject *parent = nullptr); //@endcond private: Q_DECLARE_PRIVATE(Monitor) //@cond PRIVATE Q_PRIVATE_SLOT(d_ptr, void slotSessionDestroyed(QObject *)) Q_PRIVATE_SLOT(d_ptr, void slotStatisticsChangedFinished(KJob *)) Q_PRIVATE_SLOT(d_ptr, void slotFlushRecentlyChangedCollections()) Q_PRIVATE_SLOT(d_ptr, void slotUpdateSubscription()) Q_PRIVATE_SLOT(d_ptr, void handleCommands()) Q_PRIVATE_SLOT(d_ptr, void dataAvailable()) Q_PRIVATE_SLOT(d_ptr, void serverStateChanged(Akonadi::ServerManager::State)) Q_PRIVATE_SLOT(d_ptr, void invalidateCollectionCache(qint64)) Q_PRIVATE_SLOT(d_ptr, void invalidateItemCache(qint64)) Q_PRIVATE_SLOT(d_ptr, void invalidateTagCache(qint64)) friend class ResourceBasePrivate; //@endcond }; } #endif diff --git a/src/core/pastehelper.cpp b/src/core/pastehelper.cpp index 267a351ee..9a00174d6 100644 --- a/src/core/pastehelper.cpp +++ b/src/core/pastehelper.cpp @@ -1,342 +1,342 @@ /* 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. */ #include "pastehelper_p.h" #include "collectioncopyjob.h" #include "collectionmovejob.h" #include "collectionfetchjob.h" #include "item.h" #include "itemcreatejob.h" #include "itemcopyjob.h" #include "itemmodifyjob.h" #include "itemmovejob.h" #include "linkjob.h" #include "transactionsequence.h" #include "session.h" #include "unlinkjob.h" #include "akonadicore_debug.h" #include #include #include #include #include using namespace Akonadi; class PasteHelperJob : public Akonadi::TransactionSequence { Q_OBJECT public: explicit PasteHelperJob(Qt::DropAction action, const Akonadi::Item::List &items, const Akonadi::Collection::List &collections, const Akonadi::Collection &destination, QObject *parent = nullptr); virtual ~PasteHelperJob(); private Q_SLOTS: void onDragSourceCollectionFetched(KJob *job); private: void runActions(); void runItemsActions(); void runCollectionsActions(); private: Akonadi::Item::List mItems; Akonadi::Collection::List mCollections; Akonadi::Collection mDestCollection; Qt::DropAction mAction; }; PasteHelperJob::PasteHelperJob(Qt::DropAction action, const Item::List &items, const Collection::List &collections, const Collection &destination, QObject *parent) : TransactionSequence(parent) , mItems(items) , mCollections(collections) , mDestCollection(destination) , mAction(action) { - //FIXME: The below code disables transactions in otder to avoid data loss due to nested + //FIXME: The below code disables transactions in order to avoid data loss due to nested //transactions (copy and colcopy in the server doesn't see the items retrieved into the cache and copies empty payloads). //Remove once this is fixed properly, see the other FIXME comments. setProperty("transactionsDisabled", true); Collection dragSourceCollection; if (!items.isEmpty() && items.first().parentCollection().isValid()) { // Check if all items have the same parent collection ID const Collection parent = items.first().parentCollection(); if (!std::any_of(items.cbegin(), items.cend(), [parent](const Item &item) { return item.parentCollection() != parent; })) { dragSourceCollection = parent; } } if (dragSourceCollection.isValid()) { // Disable autocommitting, because starting a Link/Unlink/Copy/Move job // after the transaction has ended leaves the job hanging setAutomaticCommittingEnabled(false); CollectionFetchJob *fetch = new CollectionFetchJob(dragSourceCollection, CollectionFetchJob::Base, this); QObject::connect(fetch, &KJob::finished, this, &PasteHelperJob::onDragSourceCollectionFetched); } else { runActions(); } } PasteHelperJob::~PasteHelperJob() { } void PasteHelperJob::onDragSourceCollectionFetched(KJob *job) { CollectionFetchJob *fetch = qobject_cast(job); qCDebug(AKONADICORE_LOG) << fetch->error() << fetch->collections().count(); if (fetch->error() || fetch->collections().count() != 1) { runActions(); commit(); return; } // If the source collection is virtual, treat copy and move actions differently const Collection sourceCollection = fetch->collections().at(0); qCDebug(AKONADICORE_LOG) << "FROM: " << sourceCollection.id() << sourceCollection.name() << sourceCollection.isVirtual(); qCDebug(AKONADICORE_LOG) << "DEST: " << mDestCollection.id() << mDestCollection.name() << mDestCollection.isVirtual(); qCDebug(AKONADICORE_LOG) << "ACTN:" << mAction; if (sourceCollection.isVirtual()) { switch (mAction) { case Qt::CopyAction: if (mDestCollection.isVirtual()) { new LinkJob(mDestCollection, mItems, this); } else { new ItemCopyJob(mItems, mDestCollection, this); } break; case Qt::MoveAction: new UnlinkJob(sourceCollection, mItems, this); if (mDestCollection.isVirtual()) { new LinkJob(mDestCollection, mItems, this); } else { new ItemCopyJob(mItems, mDestCollection, this); } break; case Qt::LinkAction: new LinkJob(mDestCollection, mItems, this); break; default: Q_ASSERT(false); } runCollectionsActions(); commit(); } else { runActions(); } commit(); } void PasteHelperJob::runActions() { runItemsActions(); runCollectionsActions(); } void PasteHelperJob::runItemsActions() { if (mItems.isEmpty()) { return; } switch (mAction) { case Qt::CopyAction: new ItemCopyJob(mItems, mDestCollection, this); break; case Qt::MoveAction: new ItemMoveJob(mItems, mDestCollection, this); break; case Qt::LinkAction: new LinkJob(mDestCollection, mItems, this); break; default: Q_ASSERT(false); // WTF?! } } void PasteHelperJob::runCollectionsActions() { if (mCollections.isEmpty()) { return; } switch (mAction) { case Qt::CopyAction: for (const Collection &col : qAsConst(mCollections)) { // FIXME: remove once we have a batch job for collections as well new CollectionCopyJob(col, mDestCollection, this); } break; case Qt::MoveAction: for (const Collection &col : qAsConst(mCollections)) { // FIXME: remove once we have a batch job for collections as well new CollectionMoveJob(col, mDestCollection, this); } break; case Qt::LinkAction: // Not supported for collections break; default: Q_ASSERT(false); // WTF?! } } bool PasteHelper::canPaste(const QMimeData *mimeData, const Collection &collection) { if (!mimeData || !collection.isValid()) { return false; } // check that the target collection has the rights to // create the pasted items resp. collections Collection::Rights neededRights = Collection::ReadOnly; if (mimeData->hasUrls()) { const QList urls = mimeData->urls(); for (const QUrl &url : urls) { const QUrlQuery query(url); if (query.hasQueryItem(QStringLiteral("item"))) { neededRights |= Collection::CanCreateItem; } else if (query.hasQueryItem(QStringLiteral("collection"))) { neededRights |= Collection::CanCreateCollection; } } if ((collection.rights() & neededRights) == 0) { return false; } // check that the target collection supports the mime types of the // items/collections that shall be pasted bool supportsMimeTypes = true; for (const QUrl &url : qAsConst(urls)) { const QUrlQuery query(url); // collections do not provide mimetype information, so ignore this check if (query.hasQueryItem(QStringLiteral("collection"))) { continue; } const QString mimeType = query.queryItemValue(QStringLiteral("type")); if (!collection.contentMimeTypes().contains(mimeType)) { supportsMimeTypes = false; break; } } if (!supportsMimeTypes) { return false; } return true; } return false; } KJob *PasteHelper::paste(const QMimeData *mimeData, const Collection &collection, bool copy, Session *session) { if (!canPaste(mimeData, collection)) { return nullptr; } // we try to drop data not coming with the akonadi:// url // find a type the target collection supports const QStringList lstFormats = mimeData->formats(); for (const QString &type : lstFormats) { if (!collection.contentMimeTypes().contains(type)) { continue; } QByteArray item = mimeData->data(type); // HACK for some unknown reason the data is sometimes 0-terminated... if (!item.isEmpty() && item.at(item.size() - 1) == 0) { item.resize(item.size() - 1); } Item it; it.setMimeType(type); it.setPayloadFromData(item); ItemCreateJob *job = new ItemCreateJob(it, collection); return job; } if (!mimeData->hasUrls()) { return nullptr; } // data contains an url list return pasteUriList(mimeData, collection, copy ? Qt::CopyAction : Qt::MoveAction, session); } KJob *PasteHelper::pasteUriList(const QMimeData *mimeData, const Collection &destination, Qt::DropAction action, Session *session) { if (!mimeData->hasUrls()) { return nullptr; } if (!canPaste(mimeData, destination)) { return nullptr; } const QList urls = mimeData->urls(); Collection::List collections; Item::List items; for (const QUrl &url : urls) { const QUrlQuery query(url); const Collection collection = Collection::fromUrl(url); if (collection.isValid()) { collections.append(collection); } Item item = Item::fromUrl(url); if (query.hasQueryItem(QStringLiteral("parent"))) { item.setParentCollection(Collection(query.queryItemValue(QStringLiteral("parent")).toLongLong())); } if (item.isValid()) { items.append(item); } // TODO: handle non Akonadi URLs? } PasteHelperJob *job = new PasteHelperJob(action, items, collections, destination, session); return job; } #include "pastehelper.moc" diff --git a/src/private/datastream_p_p.h b/src/private/datastream_p_p.h index 3e45362fd..db5e52d4b 100644 --- a/src/private/datastream_p_p.h +++ b/src/private/datastream_p_p.h @@ -1,365 +1,365 @@ /* * 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 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_PROTOCOL_DATASTREAM_H -#define AKONADI_PROTOCOL_DATASTREAM_H +#ifndef AKONADI_PROTOCOL_DATASTREAM_P_P_H +#define AKONADI_PROTOCOL_DATASTREAM_P_P_H #include #include "akonadiprivate_export.h" #include "protocol_exception_p.h" #include #include #include namespace Akonadi { namespace Protocol { class AKONADIPRIVATE_EXPORT DataStream { public: explicit DataStream(); explicit DataStream(QIODevice *device); ~DataStream(); static void waitForData(QIODevice *device, int timeoutMs); QIODevice *device() const; void setDevice(QIODevice *device); int waitTimeout() const; void setWaitTimeout(int timeout); template inline typename std::enable_if::value, DataStream>::type &operator<<(T val); template inline typename std::enable_if::value, DataStream>::type &operator<<(T val); inline DataStream &operator<<(const QString &str); inline DataStream &operator<<(const QByteArray &data); inline DataStream &operator<<(const QDateTime &dt); template inline typename std::enable_if::value, DataStream>::type &operator>>(T &val); template inline typename std::enable_if::value, DataStream>::type &operator>>(T &val); inline DataStream &operator>>(QString &str); inline DataStream &operator>>(QByteArray &data); inline DataStream &operator>>(QDateTime &dt); void writeRawData(const char *data, int len); void writeBytes(const char *bytes, int len); int readRawData(char *buffer, int len); void waitForData(quint32 size); private: Q_DISABLE_COPY(DataStream) inline void checkDevice() const { if (Q_UNLIKELY(!mDev)) { throw ProtocolException("Device does not exist"); } } QIODevice *mDev; int mWaitTimeout; }; template inline typename std::enable_if::value, DataStream>::type &DataStream::operator<<(T val) { checkDevice(); if (mDev->write((char *)&val, sizeof(T)) != sizeof(T)) { throw Akonadi::ProtocolException("Failed to write data to stream"); } return *this; } template inline typename std::enable_if::value, DataStream>::type &DataStream::operator<<(T val) { return *this << (typename std::underlying_type::type) val; } inline DataStream &DataStream::operator<<(const QString &str) { if (str.isNull()) { *this << (quint32) 0xffffffff; } else { writeBytes(reinterpret_cast(str.unicode()), sizeof(QChar) * str.length()); } return *this; } inline DataStream &DataStream::operator<<(const QByteArray &data) { if (data.isNull()) { *this << (quint32) 0xffffffff; } else { writeBytes(data.constData(), data.size()); } return *this; } inline DataStream &DataStream::operator<<(const QDateTime &dt) { *this << dt.date().toJulianDay() << dt.time().msecsSinceStartOfDay() << dt.timeSpec(); if (dt.timeSpec() == Qt::OffsetFromUTC) { *this << dt.offsetFromUtc(); } else if (dt.timeSpec() == Qt::TimeZone) { *this << dt.timeZone().id(); } return *this; } template inline typename std::enable_if::value, DataStream>::type &DataStream::operator>>(T &val) { checkDevice(); waitForData(sizeof(T)); if (mDev->read((char *)&val, sizeof(T)) != sizeof(T)) { throw Akonadi::ProtocolException("Failed to read enough data from stream"); } return *this; } template inline typename std::enable_if::value, DataStream>::type &DataStream::operator>>(T &val) { return *this >> reinterpret_cast::type &>(val); } inline DataStream &DataStream::operator>>(QString &str) { str.clear(); quint32 bytes = 0; *this >> bytes; if (bytes == 0xffffffff) { return *this; } else if (bytes == 0) { str = QString(QLatin1String("")); return *this; } if (bytes & 0x1) { str.clear(); throw Akonadi::ProtocolException("Read corrupt data"); } const quint32 step = 1024 * 1024; const quint32 len = bytes / 2; quint32 allocated = 0; while (allocated < len) { const int blockSize = qMin(step, len - allocated); waitForData(blockSize * sizeof(QChar)); str.resize(allocated + blockSize); if (readRawData(reinterpret_cast(str.data()) + allocated * sizeof(QChar), blockSize * sizeof(QChar)) != int(blockSize * sizeof(QChar))) { throw Akonadi::ProtocolException("Failed to read enough data from stream"); } allocated += blockSize; } return *this; } inline DataStream &DataStream::operator>>(QByteArray &data) { data.clear(); quint32 len = 0; *this >> len; if (len == 0xffffffff) { return *this; } const quint32 step = 1024 * 1024; quint32 allocated = 0; while (allocated < len) { const int blockSize = qMin(step, len - allocated); waitForData(blockSize); data.resize(allocated + blockSize); if (readRawData(data.data() + allocated, blockSize) != blockSize) { throw Akonadi::ProtocolException("Failed to read enough data from stream"); } allocated += blockSize; } return *this; } inline DataStream &DataStream::operator>>(QDateTime &dt) { QDate date; QTime time; qint64 jd; int mds; Qt::TimeSpec spec; *this >> jd >> mds >> spec; date = QDate::fromJulianDay(jd); time = QTime::fromMSecsSinceStartOfDay(mds); if (spec == Qt::OffsetFromUTC) { int offset = 0; *this >> offset; dt = QDateTime(date, time, spec, offset); } else if (spec == Qt::TimeZone) { QByteArray id; *this >> id; dt = QDateTime(date, time, QTimeZone(id)); } else { dt = QDateTime(date, time, spec); } return *this; } } // namespace Protocol } // namespace Akonadi // Inline functions template inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, QFlags flags) { return stream << static_cast::Int>(flags); } template inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, QFlags &flags) { stream >> reinterpret_cast::Int&>(flags); return stream; } // Generic streaming for all Qt value-based containers (as well as std containers that // implement operator<< for appending) template class Container> //typename std::enable_if::value, Akonadi::Protocol::DataStream>::type inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Container &list) { stream << (quint32) list.size(); for (auto iter = list.cbegin(), end = list.cend(); iter != end; ++iter) { stream << *iter; } return stream; } template class Container> // //typename std::enable_if::value, Akonadi::Protocol::DataStream>::type inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Container &list) { list.clear(); quint32 size = 0; stream >> size; list.reserve(size); for (quint32 i = 0; i < size; ++i) { T t; stream >> t; list << t; } return stream; } inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const QStringList &list) { return stream << static_cast>(list); } inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, QStringList &list) { return stream >> static_cast&>(list); } namespace Akonadi { namespace Protocol { namespace Private { template class Container> inline void container_reserve(Container &container, int size) { container.reserve(size); } template inline void container_reserve(QMap &, int) { // noop } } // namespace Private } // namespace Protocol } // namespace Akonadi // Generic streaming for all Qt dictionary-based containers template class Container> // typename std::enable_if::value, Akonadi::Protocol::DataStream>::type inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Container &map) { stream << (quint32) map.size(); auto iter = map.cend(), begin = map.cbegin(); while (iter != begin) { --iter; stream << iter.key() << iter.value(); } return stream; } template class Container> // typename std::enable_if::value, Akonadi::Protocol::DataStream>::type inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Container &map) { map.clear(); quint32 size = 0; stream >> size; Akonadi::Protocol::Private::container_reserve(map, size); for (quint32 i = 0; i < size; ++i) { Key key; Value value; stream >> key >> value; map.insertMulti(key, value); } return stream; } -#endif // AKONADI_PROTOCOL_DATASTREAM_H +#endif // AKONADI_PROTOCOL_DATASTREAM_P_P_H diff --git a/src/private/dbus_p.h b/src/private/dbus_p.h index 851ecb0c3..985fa34d2 100644 --- a/src/private/dbus_p.h +++ b/src/private/dbus_p.h @@ -1,89 +1,89 @@ /* Copyright (c) 2011 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_DBUS_H -#define AKONADI_DBUS_H +#ifndef AKONADI_DBUS_P_H +#define AKONADI_DBUS_P_H #include "akonadiprivate_export.h" #include #include "akoptional.h" /** * Helper methods for obtaining D-Bus identifiers. * This should be used instead of hardcoded identifiers or constants to support multi-instance namespacing * @since 1.7 */ #define AKONADI_DBUS_AGENTMANAGER_PATH "/AgentManager" #define AKONADI_DBUS_AGENTSERVER_PATH "/AgentServer" #define AKONADI_DBUS_STORAGEJANITOR_PATH "/Janitor" namespace Akonadi { namespace DBus { /** D-Bus service types used by the Akonadi server processes. */ enum ServiceType { Server, Control, ControlLock, AgentServer, StorageJanitor, UpgradeIndicator }; /** * Returns the service name for the given @p serviceType. */ AKONADIPRIVATE_EXPORT QString serviceName(ServiceType serviceType); /** Known D-Bus service name types for agents. */ enum AgentType { Unknown, Agent, Resource, Preprocessor }; struct AgentService { QString serviceName{}; DBus::AgentType agentType{DBus::Unknown}; }; /** * Parses a D-Bus service name and checks if it belongs to an agent of this instance. * @param serviceName The service name to parse. * @return The identifier of the agent, empty string if that's not an agent (or an agent of a different Akonadi instance) */ AKONADIPRIVATE_EXPORT akOptional parseAgentServiceName(const QString &serviceName); /** * Returns the D-Bus service name of the agent @p agentIdentifier for type @p agentType. */ AKONADIPRIVATE_EXPORT QString agentServiceName(const QString &agentIdentifier, DBus::AgentType agentType); } } #endif diff --git a/src/private/instance_p.h b/src/private/instance_p.h index 127d9c652..b446fe76c 100644 --- a/src/private/instance_p.h +++ b/src/private/instance_p.h @@ -1,38 +1,38 @@ /* * 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 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_INSTANCE_H -#define AKONADI_INSTANCE_H +#ifndef AKONADI_INSTANCE_P_H +#define AKONADI_INSTANCE_P_H #include "akonadiprivate_export.h" class QString; namespace Akonadi { namespace Instance { AKONADIPRIVATE_EXPORT bool hasIdentifier(); AKONADIPRIVATE_EXPORT void setIdentifier(const QString &identifier); AKONADIPRIVATE_EXPORT QString identifier(); } } -#endif // AKONADI_INSTANCE_H +#endif // AKONADI_INSTANCE_P_H diff --git a/src/private/protocol.cpp b/src/private/protocol.cpp index 6e8ba5601..4e4141eb8 100644 --- a/src/private/protocol.cpp +++ b/src/private/protocol.cpp @@ -1,919 +1,919 @@ /* * Copyright (c) 2015 Daniel Vrátil * Copyright (c) 2016 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 "protocol_p.h" #include "scope_p.h" #include "imapset_p.h" #include "datastream_p_p.h" #include #include #include #include #include #include #undef AKONADI_DECLARE_PRIVATE #define AKONADI_DECLARE_PRIVATE(Class) \ inline Class##Private* Class::d_func() {\ return reinterpret_cast(d_ptr.data()); \ } \ inline const Class##Private* Class::d_func() const {\ return reinterpret_cast(d_ptr.constData()); \ } #define COMPARE(prop) \ (prop == ((decltype(this)) other)->prop) namespace Akonadi { namespace Protocol { QDebug operator<<(QDebug _dbg, Command::Type type) { QDebug dbg(_dbg.noquote()); switch (type) { case Command::Invalid: return dbg << "Invalid"; case Command::Hello: return dbg << "Hello"; case Command::Login: return dbg << "Login"; case Command::Logout: return dbg << "Logout"; case Command::Transaction: return dbg << "Transaction"; case Command::CreateItem: return dbg << "CreateItem"; case Command::CopyItems: return dbg << "CopyItems"; case Command::DeleteItems: return dbg << "DeleteItems"; case Command::FetchItems: return dbg << "FetchItems"; case Command::LinkItems: return dbg << "LinkItems"; case Command::ModifyItems: return dbg << "ModifyItems"; case Command::MoveItems: return dbg << "MoveItems"; case Command::CreateCollection: return dbg << "CreateCollection"; case Command::CopyCollection: return dbg << "CopyCollection"; case Command::DeleteCollection: return dbg << "DeleteCollection"; case Command::FetchCollections: return dbg << "FetchCollections"; case Command::FetchCollectionStats: return dbg << "FetchCollectionStats"; case Command::ModifyCollection: return dbg << "ModifyCollection"; case Command::MoveCollection: return dbg << "MoveCollection"; case Command::Search: return dbg << "Search"; case Command::SearchResult: return dbg << "SearchResult"; case Command::StoreSearch: return dbg << "StoreSearch"; case Command::CreateTag: return dbg << "CreateTag"; case Command::DeleteTag: return dbg << "DeleteTag"; case Command::FetchTags: return dbg << "FetchTags"; case Command::ModifyTag: return dbg << "ModifyTag"; case Command::FetchRelations: return dbg << "FetchRelations"; case Command::ModifyRelation: return dbg << "ModifyRelation"; case Command::RemoveRelations: return dbg << "RemoveRelations"; case Command::SelectResource: return dbg << "SelectResource"; case Command::StreamPayload: return dbg << "StreamPayload"; case Command::ItemChangeNotification: return dbg << "ItemChangeNotification"; case Command::CollectionChangeNotification: return dbg << "CollectionChangeNotification"; case Command::TagChangeNotification: return dbg << "TagChangeNotification"; case Command::RelationChangeNotification: return dbg << "RelationChangeNotification"; case Command::SubscriptionChangeNotification: return dbg << "SubscriptionChangeNotification"; case Command::DebugChangeNotification: return dbg << "DebugChangeNotification"; case Command::CreateSubscription: return dbg << "CreateSubscription"; case Command::ModifySubscription: return dbg << "ModifySubscription"; case Command::_ResponseBit: Q_ASSERT(false); return dbg << static_cast(type); } Q_ASSERT(false); return dbg << static_cast(type); } template DataStream &operator<<(DataStream &stream, const QSharedPointer &ptr) { Protocol::serialize(stream.device(), ptr); return stream; } template DataStream &operator>>(DataStream &stream, QSharedPointer &ptr) { ptr = Protocol::deserialize(stream.device()).staticCast(); return stream; } /******************************************************************************/ Command::Command(quint8 type) : mType(type) { } bool Command::operator==(const Command &other) const { return mType == other.mType; } void Command::toJson(QJsonObject &json) const { json[QStringLiteral("response")] = static_cast(mType & Command::_ResponseBit); #define case_label(x) case Command::x: { \ json[QStringLiteral("type")] = QStringLiteral(#x); \ } break; switch (mType & ~Command::_ResponseBit) { case_label(Invalid) case_label(Hello) case_label(Login) case_label(Logout) case_label(Transaction) case_label(CreateItem) case_label(CopyItems) case_label(DeleteItems) case_label(FetchItems) case_label(LinkItems) case_label(ModifyItems) case_label(MoveItems) case_label(CreateCollection) case_label(CopyCollection) case_label(DeleteCollection) case_label(FetchCollections) case_label(FetchCollectionStats) case_label(ModifyCollection) case_label(MoveCollection) case_label(Search) case_label(SearchResult) case_label(StoreSearch) case_label(CreateTag) case_label(DeleteTag) case_label(FetchTags) case_label(ModifyTag) case_label(FetchRelations) case_label(ModifyRelation) case_label(RemoveRelations) case_label(SelectResource) case_label(StreamPayload) case_label(CreateSubscription) case_label(ModifySubscription) case_label(DebugChangeNotification) case_label(ItemChangeNotification) case_label(CollectionChangeNotification) case_label(TagChangeNotification) case_label(RelationChangeNotification) case_label(SubscriptionChangeNotification) } #undef case_label } DataStream &operator<<(DataStream &stream, const Command &cmd) { return stream << cmd.mType; } DataStream &operator>>(DataStream &stream, Command &cmd) { return stream >> cmd.mType; } QDebug operator<<(QDebug dbg, const Command &cmd) { return dbg.noquote() << ((cmd.mType & Command::_ResponseBit) ? "Response:" : "Command:") << static_cast(cmd.mType & ~Command::_ResponseBit) << "\n"; } void toJson(const Akonadi::Protocol::Command *command, QJsonObject &json) { #define case_notificationlabel(x, class) case Command::x: { \ static_cast(command)->toJson(json); \ } break; #define case_commandlabel(x, cmd, resp) \ case Command::x: { \ static_cast(command)->toJson(json); \ } break; \ case Command::x | Command::_ResponseBit: { \ static_cast(command)->toJson(json); \ } break; switch (command->mType) { case Command::Invalid: break; case Command::Hello | Command::_ResponseBit: { static_cast(command)->toJson(json); } break; case_commandlabel(Login, LoginCommand, LoginResponse) case_commandlabel(Logout, LogoutCommand, LogoutResponse) case_commandlabel(Transaction, TransactionCommand, TransactionResponse) case_commandlabel(CreateItem, CreateItemCommand, CreateItemResponse) case_commandlabel(CopyItems, CopyItemsCommand, CopyItemsResponse) case_commandlabel(DeleteItems, DeleteItemsCommand, DeleteItemsResponse) case_commandlabel(FetchItems, FetchItemsCommand, FetchItemsResponse) case_commandlabel(LinkItems, LinkItemsCommand, LinkItemsResponse) case_commandlabel(ModifyItems, ModifyItemsCommand, ModifyItemsResponse) case_commandlabel(MoveItems, MoveItemsCommand, MoveItemsResponse) case_commandlabel(CreateCollection, CreateCollectionCommand, CreateCollectionResponse) case_commandlabel(CopyCollection, CopyCollectionCommand, CopyCollectionResponse) case_commandlabel(DeleteCollection, DeleteCollectionCommand, DeleteCollectionResponse) case_commandlabel(FetchCollections, FetchCollectionsCommand, FetchCollectionsResponse) case_commandlabel(FetchCollectionStats, FetchCollectionStatsCommand, FetchCollectionStatsResponse) case_commandlabel(ModifyCollection, ModifyCollectionCommand, ModifyCollectionResponse) case_commandlabel(MoveCollection, MoveCollectionCommand, MoveCollectionResponse) case_commandlabel(Search, SearchCommand, SearchResponse) case_commandlabel(SearchResult, SearchResultCommand, SearchResultResponse) case_commandlabel(StoreSearch, StoreSearchCommand, StoreSearchResponse) case_commandlabel(CreateTag, CreateTagCommand, CreateTagResponse) case_commandlabel(DeleteTag, DeleteTagCommand, DeleteTagResponse) case_commandlabel(FetchTags, FetchTagsCommand, FetchTagsResponse) case_commandlabel(ModifyTag, ModifyTagCommand, ModifyTagResponse) case_commandlabel(FetchRelations, FetchRelationsCommand, FetchRelationsResponse) case_commandlabel(ModifyRelation, ModifyRelationCommand, ModifyRelationResponse) case_commandlabel(RemoveRelations, RemoveRelationsCommand, RemoveRelationsResponse) case_commandlabel(SelectResource, SelectResourceCommand, SelectResourceResponse) case_commandlabel(StreamPayload, StreamPayloadCommand, StreamPayloadResponse) case_commandlabel(CreateSubscription, CreateSubscriptionCommand, CreateSubscriptionResponse) case_commandlabel(ModifySubscription, ModifySubscriptionCommand, ModifySubscriptionResponse) case_notificationlabel(DebugChangeNotification, DebugChangeNotification) case_notificationlabel(ItemChangeNotification, ItemChangeNotification) case_notificationlabel(CollectionChangeNotification, CollectionChangeNotification) case_notificationlabel(TagChangeNotification, TagChangeNotification) case_notificationlabel(RelationChangeNotification, RelationChangeNotification) case_notificationlabel(SubscriptionChangeNotification, SubscriptionChangeNotification) } #undef case_notificationlabel #undef case_commandlabel } /******************************************************************************/ Response::Response() : Command(Command::Invalid | Command::_ResponseBit) , mErrorCode(0) { } Response::Response(Command::Type type) : Command(type | Command::_ResponseBit) , mErrorCode(0) { } bool Response::operator==(const Response &other) const { return *static_cast(this) == static_cast(other) && mErrorCode == other.mErrorCode && mErrorMsg == other.mErrorMsg; } void Response::toJson(QJsonObject &json) const { static_cast(this)->toJson(json); if (isError()) { QJsonObject error; error[QStringLiteral("code")] = errorCode(); error[QStringLiteral("message")] = errorMessage(); json[QStringLiteral("error")] = error; } else { json[QStringLiteral("error")] = false; } } DataStream &operator<<(DataStream &stream, const Response &cmd) { return stream << static_cast(cmd) << cmd.mErrorCode << cmd.mErrorMsg; } DataStream &operator>>(DataStream &stream, Response &cmd) { return stream >> static_cast(cmd) >> cmd.mErrorCode >> cmd.mErrorMsg; } QDebug operator<<(QDebug dbg, const Response &resp) { return dbg.noquote() << static_cast(resp) << "Error code:" << resp.mErrorCode << "\n" << "Error msg:" << resp.mErrorMsg << "\n"; } /******************************************************************************/ class FactoryPrivate { public: typedef CommandPtr (*CommandFactoryFunc)(); typedef ResponsePtr (*ResponseFactoryFunc)(); FactoryPrivate() { // Session management registerType(); registerType(); registerType(); // Transactions registerType(); // Items registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Collections registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Search registerType(); registerType(); registerType(); // Tag registerType(); registerType(); registerType(); registerType(); // Relation registerType(); registerType(); registerType(); // Resources registerType(); // Other...? registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); } // clang has problem resolving the right qHash() overload for Command::Type, // so use its underlying integer type instead QHash::type, QPair> registrar; private: template static CommandPtr commandFactoryFunc() { return QSharedPointer::create(); } template static ResponsePtr responseFactoryFunc() { return QSharedPointer::create(); } template void registerType() { CommandFactoryFunc cmdFunc = &commandFactoryFunc; ResponseFactoryFunc respFunc = &responseFactoryFunc; registrar.insert(T, qMakePair(cmdFunc, respFunc)); } }; Q_GLOBAL_STATIC(FactoryPrivate, sFactoryPrivate) CommandPtr Factory::command(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->first(); } ResponsePtr Factory::response(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->second(); } /******************************************************************************/ /******************************************************************************/ bool ItemFetchScope::operator==(const ItemFetchScope &other) const { return mRequestedParts == other.mRequestedParts && mChangedSince == other.mChangedSince && mAncestorDepth == other.mAncestorDepth && mFlags == other.mFlags; } QVector ItemFetchScope::requestedPayloads() const { QVector rv; std::copy_if(mRequestedParts.begin(), mRequestedParts.end(), std::back_inserter(rv), [](const QByteArray &ba) { return ba.startsWith("PLD:"); }); return rv; } void ItemFetchScope::setFetch(FetchFlags attributes, bool fetch) { if (fetch) { mFlags |= attributes; if (attributes & FullPayload) { if (!mRequestedParts.contains(AKONADI_PARAM_PLD_RFC822)) { mRequestedParts << AKONADI_PARAM_PLD_RFC822; } } } else { mFlags &= ~attributes; } } bool ItemFetchScope::fetch(FetchFlags flags) const { if (flags == None) { return mFlags == None; } else { return mFlags & flags; } } void ItemFetchScope::toJson(QJsonObject &json) const { json[QStringLiteral("flags")] = static_cast(mFlags); json[QStringLiteral("ChangedSince")] = mChangedSince.toString(); json[QStringLiteral("AncestorDepth")] = static_cast::type>(mAncestorDepth); QJsonArray requestedPartsArray; for (const auto &part : qAsConst(mRequestedParts)) { requestedPartsArray.append(QString::fromUtf8(part)); } json[QStringLiteral("RequestedParts")] = requestedPartsArray; } QDebug operator<<(QDebug dbg, ItemFetchScope::AncestorDepth depth) { switch (depth) { case ItemFetchScope::NoAncestor: return dbg << "No ancestor"; case ItemFetchScope::ParentAncestor: return dbg << "Parent ancestor"; case ItemFetchScope::AllAncestors: return dbg << "All ancestors"; } Q_UNREACHABLE(); } DataStream &operator<<(DataStream &stream, const ItemFetchScope &scope) { return stream << scope.mRequestedParts << scope.mChangedSince << scope.mAncestorDepth << scope.mFlags; } DataStream &operator>>(DataStream &stream, ItemFetchScope &scope) { return stream >> scope.mRequestedParts >> scope.mChangedSince >> scope.mAncestorDepth >> scope.mFlags; } QDebug operator<<(QDebug dbg, const ItemFetchScope &scope) { return dbg.noquote() << "FetchScope(\n" << "Fetch Flags:" << scope.mFlags << "\n" << "Changed Since:" << scope.mChangedSince << "\n" << "Ancestor Depth:" << scope.mAncestorDepth << "\n" << "Requested Parts:" << scope.mRequestedParts << ")\n"; } /******************************************************************************/ ScopeContext::ScopeContext(Type type, qint64 id) { if (type == ScopeContext::Tag) { mTagCtx = id; } else if (type == ScopeContext::Collection) { mColCtx = id; } } ScopeContext::ScopeContext(Type type, const QString &ctx) { if (type == ScopeContext::Tag) { mTagCtx = ctx; } else if (type == ScopeContext::Collection) { mColCtx = ctx; } } bool ScopeContext::operator==(const ScopeContext &other) const { return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx; } void ScopeContext::toJson(QJsonObject &json) const { if (isEmpty()) { json[QStringLiteral("scopeContext")] = false; } else if (hasContextId(ScopeContext::Tag)) { json[QStringLiteral("scopeContext")] = QStringLiteral("tag"); json[QStringLiteral("TagID")] = contextId(ScopeContext::Tag); } else if (hasContextId(ScopeContext::Collection)) { json[QStringLiteral("scopeContext")] = QStringLiteral("collection"); json[QStringLiteral("ColID")] = contextId(ScopeContext::Collection); } else if (hasContextRID(ScopeContext::Tag)) { json[QStringLiteral("scopeContext")] = QStringLiteral("tagrid"); json[QStringLiteral("TagRID")] = contextRID(ScopeContext::Tag); } else if (hasContextRID(ScopeContext::Collection)) { json[QStringLiteral("scopeContext")] = QStringLiteral("colrid"); json[QStringLiteral("ColRID")] = contextRID(ScopeContext::Collection); } } DataStream &operator<<(DataStream &stream, const ScopeContext &context) { // We don't have a custom generic DataStream streaming operator for QVariant // because it's very hard, esp. without access to QVariant private - // stuff, so we have have to decompose it manually here. + // stuff, so we have to decompose it manually here. QVariant::Type vType = context.mColCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mColCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mColCtx.toString(); } vType = context.mTagCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mTagCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mTagCtx.toString(); } return stream; } DataStream &operator>>(DataStream &stream, ScopeContext &context) { QVariant::Type vType; qint64 id; QString rid; for (ScopeContext::Type type : { ScopeContext::Collection, ScopeContext::Tag }) { stream >> vType; if (vType == QVariant::LongLong) { stream >> id; context.setContext(type, id); } else if (vType == QVariant::String) { stream >> rid; context.setContext(type, rid); } } return stream; } QDebug operator<<(QDebug _dbg, const ScopeContext &ctx) { QDebug dbg(_dbg.noquote()); dbg << "ScopeContext("; if (ctx.isEmpty()) { dbg << "empty"; } else if (ctx.hasContextId(ScopeContext::Tag)) { dbg << "Tag ID:" << ctx.contextId(ScopeContext::Tag); } else if (ctx.hasContextId(ScopeContext::Collection)) { dbg << "Col ID:" << ctx.contextId(ScopeContext::Collection); } else if (ctx.hasContextRID(ScopeContext::Tag)) { dbg << "Tag RID:" << ctx.contextRID(ScopeContext::Tag); } else if (ctx.hasContextRID(ScopeContext::Collection)) { dbg << "Col RID:" << ctx.contextRID(ScopeContext::Collection); } return dbg << ")\n"; } /******************************************************************************/ ChangeNotification::ChangeNotification(Command::Type type) : Command(type) { } bool ChangeNotification::operator==(const ChangeNotification &other) const { return static_cast(*this) == other && mSessionId == other.mSessionId; // metadata are not compared } QList ChangeNotification::itemsToUids(const QVector &items) { QList rv; rv.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(rv), [](const FetchItemsResponse &item) { return item.id(); }); return rv; } bool ChangeNotification::isRemove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Remove; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Remove; case Command::TagChangeNotification: return static_cast(this)->operation() == TagChangeNotification::Remove; case Command::RelationChangeNotification: return static_cast(this)->operation() == RelationChangeNotification::Remove; case Command::SubscriptionChangeNotification: return static_cast(this)->operation() == SubscriptionChangeNotification::Remove; case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::isMove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Move; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Move; case Command::TagChangeNotification: case Command::RelationChangeNotification: case Command::SubscriptionChangeNotification: case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg) { //It is likely that compressable notifications are within the last few notifications, so avoid searching a list that is potentially huge static const int maxCompressionSearchLength = 10; int searchCounter = 0; // There are often multiple Collection Modify notifications in the queue, // so we optimize for this case. if (msg->type() == Command::CollectionChangeNotification) { const auto &cmsg = Protocol::cmdCast(msg); if (cmsg.operation() == CollectionChangeNotification::Modify) { // We are iterating from end, since there's higher probability of finding // matching notification for (auto iter = list.end(), begin = list.begin(); iter != begin;) { --iter; if ((*iter)->type() == Protocol::Command::CollectionChangeNotification) { auto &it = Protocol::cmdCast(*iter); const auto &msgCol = cmsg.collection(); const auto &itCol = it.collection(); if (msgCol.id() == itCol.id() && msgCol.remoteId() == itCol.remoteId() && msgCol.remoteRevision() == itCol.remoteRevision() && msgCol.resource() == itCol.resource() && cmsg.destinationResource() == it.destinationResource() && cmsg.parentCollection() == it.parentCollection() && cmsg.parentDestCollection() == it.parentDestCollection()) { // both are modifications, merge them together and drop the new one if (cmsg.operation() == CollectionChangeNotification::Modify && it.operation() == CollectionChangeNotification::Modify) { const auto parts = it.changedParts(); it.setChangedParts(parts + cmsg.changedParts()); return false; } // we found Add notification, which means we can drop this modification if (it.operation() == CollectionChangeNotification::Add) { return false; } } } searchCounter++; if (searchCounter >= maxCompressionSearchLength) { break; } } } } // All other cases are just append, as the compression becomes too expensive in large batches list.append(msg); return true; } void ChangeNotification::toJson(QJsonObject &json) const { static_cast(this)->toJson(json); json[QStringLiteral("session")] = QString::fromUtf8(mSessionId); QJsonArray metadata; for (const auto &m : qAsConst(mMetaData)) { metadata.append(QString::fromUtf8(m)); } json[QStringLiteral("metadata")] = metadata; } DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf) { return stream << static_cast(ntf) << ntf.mSessionId; } DataStream &operator>>(DataStream &stream, ChangeNotification &ntf) { return stream >> static_cast(ntf) >> ntf.mSessionId; } QDebug operator<<(QDebug dbg, const ChangeNotification &ntf) { return dbg.noquote() << static_cast(ntf) << "Session:" << ntf.mSessionId << "\n" << "MetaData:" << ntf.mMetaData << "\n"; } DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation) { return stream >> relation.type >> relation.leftId >> relation.rightId; } DataStream &operator<<(DataStream &stream, const ChangeNotification::Relation &relation) { return stream << relation.type << relation.leftId << relation.rightId; } QDebug operator<<(QDebug _dbg, const ChangeNotification::Relation &rel) { QDebug dbg(_dbg.noquote()); return dbg << "Left: " << rel.leftId << ", Right:" << rel.rightId << ", Type: " << rel.type; } } // namespace Protocol } // namespace Akonadi // Helpers for the generated code namespace Akonadi { namespace Protocol { template class Container> inline bool containerComparator(const Container &c1, const Container &c2) { return c1 == c2; } template class Container> inline bool containerComparator(const Container> &c1, const Container> &c2) { if (c1.size() != c2.size()) { return false; } for (auto it1 = c1.cbegin(), it2 = c2.cbegin(), end1 = c1.cend(); it1 != end1; ++it1, ++it2) { if (**it1 != **it2) { return false; } } return true; } } } /******************************************************************************/ // Here comes the generated protocol implementation #include "protocol_gen.cpp" /******************************************************************************/ diff --git a/src/private/protocol_p.h b/src/private/protocol_p.h index 581ea5d2b..eb233206a 100644 --- a/src/private/protocol_p.h +++ b/src/private/protocol_p.h @@ -1,654 +1,654 @@ /* Copyright (c) 2007 Volker Krause Copyright (c) 2015 Daniel Vrátil Copyright (c) 2016 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_PROTOCOL_COMMON_P_H -#define AKONADI_PROTOCOL_COMMON_P_H +#ifndef AKONADI_PROTOCOL_P_H +#define AKONADI_PROTOCOL_P_H #include "akonadiprivate_export.h" #include #include #include #include #include #include #include "tristate_p.h" #include "scope_p.h" /** @file protocol_p.h Shared constants used in the communication protocol between the Akonadi server and its clients. */ namespace Akonadi { namespace Protocol { class Factory; class DataStream; class Command; class Response; class ItemFetchScope; class ScopeContext; class ChangeNotification; using Attributes = QMap; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT void toJson(const Command *cmd, QJsonObject &json); using CommandPtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Command { public: enum Type : quint8 { Invalid = 0, // Session management Hello = 1, Login, Logout, // Transactions Transaction = 10, // Items CreateItem = 20, CopyItems, DeleteItems, FetchItems, LinkItems, ModifyItems, MoveItems, // Collections CreateCollection = 40, CopyCollection, DeleteCollection, FetchCollections, FetchCollectionStats, ModifyCollection, MoveCollection, // Search Search = 60, SearchResult, StoreSearch, // Tag CreateTag = 70, DeleteTag, FetchTags, ModifyTag, // Relation FetchRelations = 80, ModifyRelation, RemoveRelations, // Resources SelectResource = 90, // Other StreamPayload = 100, // Notifications ItemChangeNotification = 110, CollectionChangeNotification, TagChangeNotification, RelationChangeNotification, SubscriptionChangeNotification, DebugChangeNotification, CreateSubscription, ModifySubscription, // _MaxValue = 127 _ResponseBit = 0x80 // reserved }; explicit Command() = default; explicit Command(const Command &) = default; Command(Command &&) = default; ~Command() = default; Command &operator=(const Command &) = default; Command &operator=(Command &&) = default; bool operator==(const Command &other) const; inline bool operator!=(const Command &other) const { return !operator==(other); } inline Type type() const { return static_cast(mType & ~_ResponseBit); } inline bool isValid() const { return type() != Invalid; } inline bool isResponse() const { return mType & _ResponseBit; } void toJson(QJsonObject &stream) const; protected: explicit Command(quint8 type); quint8 mType = Invalid; // unused 7 bytes private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(::QDebug dbg, const Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT void toJson(const Akonadi::Protocol::Command *cmd, QJsonObject &json); }; AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, Command::Type type); } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::Command::Type) Q_DECLARE_METATYPE(Akonadi::Protocol::CommandPtr) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &response); using ResponsePtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Response : public Command { public: explicit Response(); explicit Response(const Response &) = default; Response(Response &&) = default; Response &operator=(const Response &) = default; Response &operator=(Response &&) = default; inline void setError(int code, const QString &message) { mErrorCode = code; mErrorMsg = message; } bool operator==(const Response &other) const; inline bool operator!=(const Response &other) const { return !operator==(other); } inline bool isError() const { return mErrorCode > 0; } inline int errorCode() const { return mErrorCode; } inline QString errorMessage() const { return mErrorMsg; } void toJson(QJsonObject &json) const; protected: explicit Response(Command::Type type); int mErrorCode; QString mErrorMsg; private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &cmd); }; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { template inline const X &cmdCast(const QSharedPointer &p) { return static_cast(*p); } template inline X &cmdCast(QSharedPointer &p) { return static_cast(*p); } class AKONADIPRIVATE_EXPORT Factory { public: static CommandPtr command(Command::Type type); static ResponsePtr response(Command::Type type); private: template friend AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); }; AKONADIPRIVATE_EXPORT void serialize(QIODevice *device, const CommandPtr &command); AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); AKONADIPRIVATE_EXPORT QString debugString(const Command &command); AKONADIPRIVATE_EXPORT inline QString debugString(const CommandPtr &command) { return debugString(*command); } } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); class AKONADIPRIVATE_EXPORT ItemFetchScope { public: enum FetchFlag : int { None = 0, CacheOnly = 1 << 0, CheckCachedPayloadPartsOnly = 1 << 1, FullPayload = 1 << 2, AllAttributes = 1 << 3, Size = 1 << 4, MTime = 1 << 5, RemoteRevision = 1 << 6, IgnoreErrors = 1 << 7, Flags = 1 << 8, RemoteID = 1 << 9, GID = 1 << 10, Tags = 1 << 11, Relations = 1 << 12, VirtReferences = 1 << 13 }; Q_DECLARE_FLAGS(FetchFlags, FetchFlag) enum AncestorDepth : ushort { NoAncestor, ParentAncestor, AllAncestors }; explicit ItemFetchScope() = default; ItemFetchScope(const ItemFetchScope &) = default; ItemFetchScope(ItemFetchScope &&other) = default; ~ItemFetchScope() = default; ItemFetchScope &operator=(const ItemFetchScope &) = default; ItemFetchScope &operator=(ItemFetchScope &&) = default; bool operator==(const ItemFetchScope &other) const; inline bool operator!=(const ItemFetchScope &other) const { return !operator==(other); } inline void setRequestedParts(const QVector &requestedParts) { mRequestedParts = requestedParts; } inline QVector requestedParts() const { return mRequestedParts; } QVector requestedPayloads() const; inline void setChangedSince(const QDateTime &changedSince) { mChangedSince = changedSince; } inline QDateTime changedSince() const { return mChangedSince; } inline void setAncestorDepth(AncestorDepth depth) { mAncestorDepth = depth; } inline AncestorDepth ancestorDepth() const { return mAncestorDepth; } inline bool cacheOnly() const { return mFlags & CacheOnly; } inline bool checkCachedPayloadPartsOnly() const { return mFlags & CheckCachedPayloadPartsOnly; } inline bool fullPayload() const { return mFlags & FullPayload; } inline bool allAttributes() const { return mFlags & AllAttributes; } inline bool fetchSize() const { return mFlags & Size; } inline bool fetchMTime() const { return mFlags & MTime; } inline bool fetchRemoteRevision() const { return mFlags & RemoteRevision; } inline bool ignoreErrors() const { return mFlags & IgnoreErrors; } inline bool fetchFlags() const { return mFlags & Flags; } inline bool fetchRemoteId() const { return mFlags & RemoteID; } inline bool fetchGID() const { return mFlags & GID; } inline bool fetchTags() const { return mFlags & Tags; } inline bool fetchRelations() const { return mFlags & Relations; } inline bool fetchVirtualReferences() const { return mFlags & VirtReferences; } void setFetch(FetchFlags attributes, bool fetch = true); bool fetch(FetchFlags flags) const; void toJson(QJsonObject &json) const; private: AncestorDepth mAncestorDepth = NoAncestor; // 2 bytes free FetchFlags mFlags = None; QVector mRequestedParts; QDateTime mChangedSince; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); }; } // namespace Protocol } // namespace Akonadi Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Protocol::ItemFetchScope::FetchFlags) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); class AKONADIPRIVATE_EXPORT ScopeContext { public: enum Type : uchar { Any = 0, Collection, Tag }; explicit ScopeContext() = default; ScopeContext(Type type, qint64 id); ScopeContext(Type type, const QString &id); ScopeContext(const ScopeContext &) = default; ScopeContext(ScopeContext &&) = default; ~ScopeContext() = default; ScopeContext &operator=(const ScopeContext &) = default; ScopeContext &operator=(ScopeContext &&) = default; bool operator==(const ScopeContext &other) const; inline bool operator!=(const ScopeContext &other) const { return !operator==(other); } inline bool isEmpty() const { return mColCtx.isNull() && mTagCtx.isNull(); } inline void setContext(Type type, qint64 id) { setCtx(type, id); } inline void setContext(Type type, const QString &id) { setCtx(type, id); } inline void clearContext(Type type) { setCtx(type, QVariant()); } inline bool hasContextId(Type type) const { return ctx(type).type() == QVariant::LongLong; } inline qint64 contextId(Type type) const { return hasContextId(type) ? ctx(type).toLongLong() : 0; } inline bool hasContextRID(Type type) const { return ctx(type).type() == QVariant::String; } inline QString contextRID(Type type) const { return hasContextRID(type) ? ctx(type).toString() : QString(); } void toJson(QJsonObject &json) const; private: QVariant mColCtx; QVariant mTagCtx; inline QVariant ctx(Type type) const { return type == Collection ? mColCtx : type == Tag ? mTagCtx : QVariant(); } inline void setCtx(Type type, const QVariant &v) { if (type == Collection) { mColCtx = v; } else if (type == Tag) { mTagCtx = v; } } friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); }; } // namespace Protocol } // namespace akonadi namespace Akonadi { namespace Protocol { class FetchItemsResponse; typedef QSharedPointer FetchItemsResponsePtr; AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); using ChangeNotificationPtr = QSharedPointer; using ChangeNotificationList = QVector; class AKONADIPRIVATE_EXPORT ChangeNotification : public Command { public: static QList itemsToUids(const QVector &items); class Relation { public: Relation() = default; Relation(const Relation &) = default; Relation(Relation &&) = default; inline Relation(qint64 leftId, qint64 rightId, const QString &type) : leftId(leftId) , rightId(rightId) , type(type) { } Relation &operator=(const Relation &) = default; Relation &operator=(Relation &&) = default; inline bool operator==(const Relation &other) const { return leftId == other.leftId && rightId == other.rightId && type == other.type; } void toJson(QJsonObject &json) const { json[QStringLiteral("leftId")] = leftId; json[QStringLiteral("rightId")] = rightId; json[QStringLiteral("type")] = type; } qint64 leftId = -1; qint64 rightId = -1; QString type; }; ChangeNotification &operator=(const ChangeNotification &) = default; ChangeNotification &operator=(ChangeNotification &&) = default; bool operator==(const ChangeNotification &other) const; inline bool operator!=(const ChangeNotification &other) const { return !operator==(other); } bool isRemove() const; bool isMove() const; inline QByteArray sessionId() const { return mSessionId; } inline void setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } inline void addMetadata(const QByteArray &metadata) { mMetaData << metadata; } inline void removeMetadata(const QByteArray &metadata) { mMetaData.removeAll(metadata); } QVector metadata() const { return mMetaData; } static bool appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg); void toJson(QJsonObject &json) const; protected: explicit ChangeNotification() = default; explicit ChangeNotification(Command::Type type); ChangeNotification(const ChangeNotification &) = default; ChangeNotification(ChangeNotification &&) = default; QByteArray mSessionId; // For internal use only: Akonadi server can add some additional information // that might be useful when evaluating the notification for example, but // it is never transferred to clients QVector mMetaData; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); }; inline uint qHash(const ChangeNotification::Relation &rel) { return ::qHash(rel.leftId + rel.rightId); } // TODO: Internalize? AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification::Relation &relation); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification::Relation &relation); } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationPtr) Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationList) /******************************************************************************/ // Here comes the actual generated Protocol. See protocol.xml for definitions, // and genprotocol folder for the generator. #include "protocol_gen.h" /******************************************************************************/ // Command parameters #define AKONADI_PARAM_ATR "ATR:" #define AKONADI_PARAM_CACHEPOLICY "CACHEPOLICY" #define AKONADI_PARAM_DISPLAY "DISPLAY" #define AKONADI_PARAM_ENABLED "ENABLED" #define AKONADI_PARAM_FLAGS "FLAGS" #define AKONADI_PARAM_TAGS "TAGS" #define AKONADI_PARAM_GID "GID" #define AKONADI_PARAM_INDEX "INDEX" #define AKONADI_PARAM_MIMETYPE "MIMETYPE" #define AKONADI_PARAM_NAME "NAME" #define AKONADI_PARAM_PARENT "PARENT" #define AKONADI_PARAM_PERSISTENTSEARCH "PERSISTENTSEARCH" #define AKONADI_PARAM_PLD "PLD:" #define AKONADI_PARAM_PLD_RFC822 "PLD:RFC822" #define AKONADI_PARAM_RECURSIVE "RECURSIVE" #define AKONADI_PARAM_REMOTE "REMOTE" #define AKONADI_PARAM_REMOTEID "REMOTEID" #define AKONADI_PARAM_REMOTEREVISION "REMOTEREVISION" #define AKONADI_PARAM_REVISION "REV" #define AKONADI_PARAM_SIZE "SIZE" #define AKONADI_PARAM_SYNC "SYNC" #define AKONADI_PARAM_TAG "TAG" #define AKONADI_PARAM_TYPE "TYPE" #define AKONADI_PARAM_VIRTUAL "VIRTUAL" // Flags #define AKONADI_FLAG_GID "\\Gid" #define AKONADI_FLAG_IGNORED "$IGNORED" #define AKONADI_FLAG_MIMETYPE "\\MimeType" #define AKONADI_FLAG_REMOTEID "\\RemoteId" #define AKONADI_FLAG_REMOTEREVISION "\\RemoteRevision" #define AKONADI_FLAG_TAG "\\Tag" #define AKONADI_FLAG_RTAG "\\RTag" #define AKONADI_FLAG_SEEN "\\SEEN" // Attributes #define AKONADI_ATTRIBUTE_HIDDEN "ATR:HIDDEN" #define AKONADI_ATTRIBUTE_MESSAGES "MESSAGES" #define AKONADI_ATTRIBUTE_UNSEEN "UNSEEN" // special resource names #define AKONADI_SEARCH_RESOURCE "akonadi_search_resource" namespace Akonadi { static const QString CollectionMimeType = QStringLiteral("inode/directory"); static const QString VirtualCollectionMimeType = QStringLiteral("application/x-vnd.akonadi.collection.virtual"); } #endif diff --git a/src/private/standarddirs_p.h b/src/private/standarddirs_p.h index 658679bab..e903aaffb 100644 --- a/src/private/standarddirs_p.h +++ b/src/private/standarddirs_p.h @@ -1,117 +1,117 @@ /* Copyright (c) 2011 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 AKSTANDARDDIRS_H -#define AKSTANDARDDIRS_H +#ifndef AKSTANDARDDIRS__P_H +#define AKSTANDARDDIRS_P_H #include "akonadiprivate_export.h" #include namespace Akonadi { /** * Convenience wrappers on top of QStandardPaths that are instance namespace aware. * @since 1.7 */ namespace StandardDirs { /** * @brief Open mode flags for resource files * * FileAccessMode is a typedef for QFlags. It stores * a OR combination of FileAccessFlag values */ enum FileAccessMode { ReadOnly = 0x1, WriteOnly = 0x2, ReadWrite = ReadOnly | WriteOnly }; /** * Returns path to the config file @p configFile. */ AKONADIPRIVATE_EXPORT QString configFile(const QString &configFile, FileAccessMode openMode = ReadOnly); /** * Returns the full path to the server config file (akonadiserverrc). */ AKONADIPRIVATE_EXPORT QString serverConfigFile(FileAccessMode openMode = ReadOnly); /** * Returns the full path to the connection config file (akonadiconnectionrc). */ AKONADIPRIVATE_EXPORT QString connectionConfigFile(FileAccessMode openMode = ReadOnly); /** * Returns the full path to the agentsrc config file */ AKONADIPRIVATE_EXPORT QString agentsConfigFile(FileAccessMode openMode = ReadOnly); /** * Returns the full path to config file of agent @p identifier. * * Never returns empty string. * * @param identifier identifier of the agent (akonadi_foo_resource_0) */ AKONADIPRIVATE_EXPORT QString agentConfigFile(const QString &identifier, FileAccessMode openMode = ReadOnly); /** * Instance-aware wrapper for QStandardPaths * @note @p relPath does not need to include the "akonadi/" folder. */ AKONADIPRIVATE_EXPORT QString saveDir(const char *resource, const QString &relPath = QString()); /** * @brief Searches the resource specific directories for a given file * * Convenience method for finding a given file (with optional relative path) * in any of the configured base directories for a given resource type. * * Will check the user local directory first and then process the system * wide path list according to the inherent priority. * * @param resource a named resource type, e.g. "config" * @param relPath relative path of a file to look for, e.g."akonadi/akonadiserverrc" * * @returns the file path of the first match, or @c QString() if no such relative path * exists in any of the base directories or if a match is not a file */ AKONADIPRIVATE_EXPORT QString locateResourceFile(const char *resource, const QString &relPath); /** * Equivalent to QStandardPaths::locateAll() but always includes at least the * default Akonadi compile prefix. */ AKONADIPRIVATE_EXPORT QStringList locateAllResourceDirs(const QString &relPath); /** * Equivalent to QStandardPaths::findExecutable() but it looks in * qApp->applicationDirPath() first. */ AKONADIPRIVATE_EXPORT QString findExecutable(const QString &relPath); } } #endif diff --git a/src/server/handler/relationfetchhandler.h b/src/server/handler/relationfetchhandler.h index e2cb5e7a8..5967a3d74 100644 --- a/src/server/handler/relationfetchhandler.h +++ b/src/server/handler/relationfetchhandler.h @@ -1,46 +1,46 @@ /*************************************************************************** * 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 AKONADI_FETCHRELATIONHANDLER_H_ -#define AKONADI_FETCHRELATIONHANDLER_H_ +#ifndef AKONADI_RELATIONFETCHHANDLER_H_ +#define AKONADI_RELATIONFETCHHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the RELATIONFETCH command. */ class RelationFetchHandler: public Handler { public: ~RelationFetchHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/dbconfigpostgresql.cpp b/src/server/storage/dbconfigpostgresql.cpp index 815adb2ba..d90ec34bf 100644 --- a/src/server/storage/dbconfigpostgresql.cpp +++ b/src/server/storage/dbconfigpostgresql.cpp @@ -1,636 +1,636 @@ /* Copyright (c) 2010 Tobias Koenig 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 "dbconfigpostgresql.h" #include "utils.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include using namespace std::chrono_literals; using namespace Akonadi; using namespace Akonadi::Server; DbConfigPostgresql::DbConfigPostgresql() : mHostPort(0) , mInternalServer(true) { } QString DbConfigPostgresql::driverName() const { return QStringLiteral("QPSQL"); } QString DbConfigPostgresql::databaseName() const { return mDatabaseName; } namespace { struct VersionCompare { bool operator()(const QFileInfo &lhsFi, const QFileInfo &rhsFi) const { const auto lhs = parseVersion(lhsFi.fileName()); if (!lhs.has_value()) { return false; } const auto rhs = parseVersion(rhsFi.fileName()); if (!rhs.has_value()) { return true; } return std::tie(lhs->major, lhs->minor) < std::tie(rhs->major, rhs->minor); } private: struct Version { int major; int minor; }; akOptional parseVersion(const QString &name) const { const auto dotIdx = name.indexOf(QLatin1Char('.')); if (dotIdx == -1) { return {}; } bool ok = false; const auto major = name.leftRef(dotIdx).toInt(&ok); if (!ok) { return {}; } const auto minor = name.midRef(dotIdx + 1).toInt(&ok); if (!ok) { return {}; } return Version{major, minor}; } }; } QStringList DbConfigPostgresql::postgresSearchPaths(const QString &versionedPath) const { QStringList paths; #ifdef POSTGRES_PATH const QString dir(QStringLiteral(POSTGRES_PATH)); if (QDir(dir).exists()) { paths.push_back(QStringLiteral(POSTGRES_PATH)); } #endif paths << QStringLiteral("/usr/bin") << QStringLiteral("/usr/sbin") << QStringLiteral("/usr/local/sbin"); // Locate all versions in /usr/lib/postgresql (i.e. /usr/lib/postgresql/X.Y) in reversed // sorted order, so we search from the newest one to the oldest. QDir versionedDir(versionedPath); if (versionedDir.exists()) { auto versionedDirs = versionedDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort); qDebug() << versionedDirs; std::sort(versionedDirs.begin(), versionedDirs.end(), VersionCompare()); std::reverse(versionedDirs.begin(), versionedDirs.end()); paths += versionedDirs | transform([](const auto &dir) -> QString { return dir.absoluteFilePath() + QStringLiteral("/bin"); }) | toQList; } return paths; } bool DbConfigPostgresql::init(QSettings &settings) { // determine default settings depending on the driver QString defaultHostName; QString defaultOptions; QString defaultServerPath; QString defaultInitDbPath; QString defaultPgUpgradePath; QString defaultPgData; #ifndef Q_WS_WIN // We assume that PostgreSQL is running as service on Windows const bool defaultInternalServer = true; #else const bool defaultInternalServer = false; #endif mInternalServer = settings.value(QStringLiteral("QPSQL/StartServer"), defaultInternalServer).toBool(); if (mInternalServer) { const auto paths = postgresSearchPaths(QStringLiteral("/usr/lib/postgresql")); defaultServerPath = QStandardPaths::findExecutable(QStringLiteral("pg_ctl"), paths); defaultInitDbPath = QStandardPaths::findExecutable(QStringLiteral("initdb"), paths); defaultHostName = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc"))); defaultPgUpgradePath = QStandardPaths::findExecutable(QStringLiteral("pg_upgrade"), paths); defaultPgData = StandardDirs::saveDir("data", QStringLiteral("db_data")); } // read settings for current driver settings.beginGroup(driverName()); mDatabaseName = settings.value(QStringLiteral("Name"), defaultDatabaseName()).toString(); if (mDatabaseName.isEmpty()) { mDatabaseName = defaultDatabaseName(); } mHostName = settings.value(QStringLiteral("Host"), defaultHostName).toString(); if (mHostName.isEmpty()) { mHostName = defaultHostName; } mHostPort = settings.value(QStringLiteral("Port")).toInt(); // User, password and Options can be empty and still valid, so don't override them mUserName = settings.value(QStringLiteral("User")).toString(); mPassword = settings.value(QStringLiteral("Password")).toString(); mConnectionOptions = settings.value(QStringLiteral("Options"), defaultOptions).toString(); mServerPath = settings.value(QStringLiteral("ServerPath"), defaultServerPath).toString(); if (mInternalServer && mServerPath.isEmpty()) { mServerPath = defaultServerPath; } qCDebug(AKONADISERVER_LOG) << "Found pg_ctl:" << mServerPath; mInitDbPath = settings.value(QStringLiteral("InitDbPath"), defaultInitDbPath).toString(); if (mInternalServer && mInitDbPath.isEmpty()) { mInitDbPath = defaultInitDbPath; } qCDebug(AKONADISERVER_LOG) << "Found initdb:" << mServerPath; mPgUpgradePath = settings.value(QStringLiteral("UpgradePath"), defaultPgUpgradePath).toString(); if (mInternalServer && mPgUpgradePath.isEmpty()) { mPgUpgradePath = defaultPgUpgradePath; } qCDebug(AKONADISERVER_LOG) << "Found pg_upgrade:" << mPgUpgradePath; mPgData = settings.value(QStringLiteral("PgData"), defaultPgData).toString(); if (mPgData.isEmpty()) { mPgData = defaultPgData; } settings.endGroup(); // store back the default values settings.beginGroup(driverName()); settings.setValue(QStringLiteral("Name"), mDatabaseName); settings.setValue(QStringLiteral("Host"), mHostName); if (mHostPort) { settings.setValue(QStringLiteral("Port"), mHostPort); } settings.setValue(QStringLiteral("Options"), mConnectionOptions); settings.setValue(QStringLiteral("ServerPath"), mServerPath); settings.setValue(QStringLiteral("InitDbPath"), mInitDbPath); settings.setValue(QStringLiteral("StartServer"), mInternalServer); settings.endGroup(); settings.sync(); return true; } void DbConfigPostgresql::apply(QSqlDatabase &database) { if (!mDatabaseName.isEmpty()) { database.setDatabaseName(mDatabaseName); } if (!mHostName.isEmpty()) { database.setHostName(mHostName); } if (mHostPort > 0 && mHostPort < 65535) { database.setPort(mHostPort); } if (!mUserName.isEmpty()) { database.setUserName(mUserName); } if (!mPassword.isEmpty()) { database.setPassword(mPassword); } database.setConnectOptions(mConnectionOptions); // can we check that during init() already? Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId)); } bool DbConfigPostgresql::useInternalServer() const { return mInternalServer; } akOptional DbConfigPostgresql::checkPgVersion() const { // Contains major version of Postgres that creted the cluster QFile pgVersionFile(QStringLiteral("%1/PG_VERSION").arg(mPgData)); if (!pgVersionFile.open(QIODevice::ReadOnly)) { return nullopt; } const auto clusterVersion = pgVersionFile.readAll().toInt(); QProcess pgctl; pgctl.start(mServerPath, { QStringLiteral("--version") }, QIODevice::ReadOnly); if (!pgctl.waitForFinished()) { return nullopt; } // Looks like "pg_ctl (PostgreSQL) 11.2" const auto output = QString::fromUtf8(pgctl.readAll()); // Get the major version from major.minor QRegularExpression re(QStringLiteral("\\(PostgreSQL\\) ([0-9]+).[0-9]+")); const auto match = re.match(output); if (!match.hasMatch()) { return nullopt; } const auto serverVersion = match.captured(1).toInt(); qDebug(AKONADISERVER_LOG) << "Detected psql versions - cluster:" << clusterVersion << ", server:" << serverVersion; return {{ clusterVersion, serverVersion }}; } bool DbConfigPostgresql::runInitDb(const QString &newDbPath) { // Make sure the cluster directory exists if (!QDir(newDbPath).exists()) { if (!QDir().mkpath(newDbPath)) { return false; } } #ifdef Q_OS_LINUX // It is recommended to disable CoW feature when running on Btrfs to improve // database performance. This only has effect when done on empty directory, // so we only call this before calling initdb if (Utils::getDirectoryFileSystem(newDbPath) == QLatin1String("btrfs")) { Utils::disableCoW(newDbPath); } #endif // call 'initdb --pgdata=/home/user/.local/share/akonadi/data_db' return execute(mInitDbPath, { QStringLiteral("--pgdata=%1").arg(newDbPath), QStringLiteral("--locale=en_US.UTF-8") /* TODO: check locale */ }) == 0; } namespace { akOptional findBinPathForVersion(int version) { // First we need to find where the previous PostgreSQL version binaries are available const auto oldBinSearchPaths = { QStringLiteral("/usr/lib64/pgsql/postgresql-%1/bin").arg(version), // Fedora & friends QStringLiteral("/usr/lib/pgsql/postgresql-%1/bin").arg(version), QStringLiteral("/usr/lib/postgresql/%1/bin").arg(version), // Debian-based QStringLiteral("/opt/pgsql-%1/bin").arg(version), // Arch Linux // TODO: Check other distros as well, they might do things differently. }; for (const auto &path : oldBinSearchPaths) { if (QDir(path).exists()) { return path; } } return nullopt; } bool checkAndRemoveTmpCluster(const QDir &baseDir, const QString &clusterName) { if (baseDir.exists(clusterName)) { qCInfo(AKONADISERVER_LOG) << "Postgres cluster update:" << clusterName << "cluster already exists, trying to remove it first"; if (!QDir(baseDir.path() + QDir::separator() + clusterName).removeRecursively()) { qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to remove" << clusterName << "cluster from some previous run, not performing auto-upgrade"; return false; } } return true; } bool runPgUpgrade(const QString &pgUpgrade, const QDir &baseDir, const QString &oldBinPath, const QString &newBinPath, const QString &oldDbData, const QString &newDbData) { QProcess process; const QStringList args = { QString(QStringLiteral("--old-bindir=%1").arg(oldBinPath)), QString(QStringLiteral("--new-bindir=%1").arg(newBinPath)), QString(QStringLiteral("--old-datadir=%1").arg(oldDbData)), QString(QStringLiteral("--new-datadir=%1").arg(newDbData)) }; qCInfo(AKONADISERVER_LOG) << "Postgres cluster update: starting pg_upgrade to upgrade your Akonadi DB cluster"; qCDebug(AKONADISERVER_LOG) << "Executing pg_upgrade" << QStringList(args); process.setWorkingDirectory(baseDir.path()); process.start(pgUpgrade, args); process.waitForFinished(std::chrono::milliseconds(1h).count()); if (process.exitCode() != 0) { qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: pg_upgrade finished with exit code" << process.exitCode() << ", please run migration manually."; return false; } qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: pg_upgrade finished successfully."; return true; } bool swapClusters(QDir &baseDir, const QString &oldDbDataCluster, const QString &newDbDataCluster) { // If everything went fine, swap the old and new clusters if (!baseDir.rename(QStringLiteral("db_data"), oldDbDataCluster)) { qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to rename old db_data to" << oldDbDataCluster; return false; } if (!baseDir.rename(newDbDataCluster, QStringLiteral("db_data"))) { qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to rename" << newDbDataCluster << "to db_data, rolling back"; if (!baseDir.rename(oldDbDataCluster, QStringLiteral("db_data"))) { qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to roll back from" << oldDbDataCluster << "to db_data."; return false; } qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: rollback successful."; return false; } return true; } } bool DbConfigPostgresql::upgradeCluster(int clusterVersion) { const auto oldDbDataCluster = QStringLiteral("old_db_data"); const auto newDbDataCluster = QStringLiteral("new_db_data"); QDir baseDir(mPgData); // db_data baseDir.cdUp(); // move to its parent folder const auto oldBinPath = findBinPathForVersion(clusterVersion); if (!oldBinPath.has_value()) { qCDebug(AKONADISERVER_LOG) << "Postgres cluster update: failed to find Postgres server for version" << clusterVersion; return false; } const auto newBinPath = QFileInfo(mServerPath).path(); if (!checkAndRemoveTmpCluster(baseDir, oldDbDataCluster)) { return false; } if (!checkAndRemoveTmpCluster(baseDir, newDbDataCluster)) { return false; } // Next, initialize a new cluster const QString newDbData = baseDir.path() + QDir::separator() + newDbDataCluster; qCInfo(AKONADISERVER_LOG) << "Postgres cluster upgrade: creating a new cluster for current Postgres server"; if (!runInitDb(newDbData)) { qCWarning(AKONADISERVER_LOG) << "Postgres cluster update: failed to initialize new db cluster"; return false; } // Now migrate the old cluster from the old version into the new cluster if (!runPgUpgrade(mPgUpgradePath, baseDir, *oldBinPath, newBinPath, mPgData, newDbData)) { return false; } if (!swapClusters(baseDir, oldDbDataCluster, newDbDataCluster)) { return false; } // Drop the old cluster if (!QDir(baseDir.path() + QDir::separator() + oldDbDataCluster).removeRecursively()) { qCInfo(AKONADISERVER_LOG) << "Postgres cluster update: failed to remove" << oldDbDataCluster << "cluster (not an issue, continuing)"; } return true; } bool DbConfigPostgresql::startInternalServer() { // We defined the mHostName to the socket directory, during init const QString socketDir = mHostName; bool success = true; // Make sure the path exists, otherwise pg_ctl fails if (!QFile::exists(socketDir)) { QDir().mkpath(socketDir); } // TODO Windows support #ifndef Q_WS_WIN // If postmaster.pid exists, check whether the postgres process still exists too, // because normally we shouldn't be able to get this far if Akonadi is already // running. If postgres is not running, then the pidfile was left after a system // crash or something similar and we can remove it (otherwise pg_ctl won't start) QFile postmaster(QStringLiteral("%1/postmaster.pid").arg(mPgData)); if (postmaster.exists() && postmaster.open(QIODevice::ReadOnly)) { qCDebug(AKONADISERVER_LOG) << "Found a postmaster.pid pidfile, checking whether the server is still running..."; QByteArray pid = postmaster.readLine(); // Remove newline character pid.chop(1); QFile proc(QString::fromLatin1("/proc/" + pid + "/stat")); // Check whether the process with the PID from pidfile still exists and whether // it's actually still postgres or, whether the PID has been recycled in the // meanwhile. if (proc.open(QIODevice::ReadOnly)) { const QByteArray stat = proc.readAll(); const QList stats = stat.split(' '); if (stats.count() > 1) { // Make sure the PID actually belongs to postgres process if (stats[1] == "(postgres)") { // Yup, our PostgreSQL is actually running, so pretend we started the server // and try to connect to it qCWarning(AKONADISERVER_LOG) << "PostgreSQL for Akonadi is already running, trying to connect to it."; return true; } } proc.close(); } qCDebug(AKONADISERVER_LOG) << "No postgres process with specified PID is running. Removing the pidfile and starting a new Postgres instance..."; postmaster.close(); postmaster.remove(); } #endif // postgres data directory not initialized yet, so call initdb on it if (!QFile::exists(QStringLiteral("%1/PG_VERSION").arg(mPgData))) { #ifdef Q_OS_LINUX // It is recommended to disable CoW feature when running on Btrfs to improve // database performance. This only has effect when done on an empty directory, // so we call this before calling initdb. if (Utils::getDirectoryFileSystem(mPgData) == QLatin1String("btrfs")) { Utils::disableCoW(mPgData); } #endif // call 'initdb --pgdata=/home/user/.local/share/akonadi/db_data' execute(mInitDbPath, { QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--locale=en_US.UTF-8") // TODO: check locale }); } else { const auto versions = checkPgVersion(); if (versions.has_value() && (versions->clusterVersion < versions->pgServerVersion)) { qCInfo(AKONADISERVER_LOG) << "Cluster PG_VERSION is" << versions->clusterVersion << ", PostgreSQL server is version " << versions->pgServerVersion << ", will attempt to upgrade the cluster"; if (upgradeCluster(versions->clusterVersion)) { - qCInfo(AKONADISERVER_LOG) << "Succesfully upgraded db cluster from Postgres" << versions->clusterVersion << "to" << versions->pgServerVersion; + qCInfo(AKONADISERVER_LOG) << "Successfully upgraded db cluster from Postgres" << versions->clusterVersion << "to" << versions->pgServerVersion; } else { qCWarning(AKONADISERVER_LOG) << "Postgres db cluster upgrade failed, Akonadi will fail to start. Sorry."; } } } // synthesize the postgres command QStringList arguments; arguments << QStringLiteral("start") << QStringLiteral("-w") << QStringLiteral("--timeout=10") // default is 60 seconds. << QStringLiteral("--pgdata=%1").arg(mPgData) // These options are passed to postgres // -k - directory for unix domain socket communication // -h - disable listening for TCP/IP << QStringLiteral("-o \"-k%1\" -h ''").arg(socketDir); qCDebug(AKONADISERVER_LOG) << "Executing:" << mServerPath << arguments.join(QLatin1Char(' ')); QProcess pgCtl; pgCtl.start(mServerPath, arguments); if (!pgCtl.waitForStarted()) { qCCritical(AKONADISERVER_LOG) << "Could not start database server!"; qCCritical(AKONADISERVER_LOG) << "executable:" << mServerPath; qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments; qCCritical(AKONADISERVER_LOG) << "process error:" << pgCtl.errorString(); return false; } const QLatin1String initCon("initConnection"); { QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QPSQL"), initCon); apply(db); // use the default database that is always available db.setDatabaseName(QStringLiteral("postgres")); if (!db.isValid()) { qCCritical(AKONADISERVER_LOG) << "Invalid database object during database server startup"; return false; } bool opened = false; for (int i = 0; i < 120; ++i) { opened = db.open(); if (opened) { break; } if (pgCtl.waitForFinished(500) && pgCtl.exitCode()) { qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!"; qCCritical(AKONADISERVER_LOG) << "executable:" << mServerPath; qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments; qCCritical(AKONADISERVER_LOG) << "stdout:" << pgCtl.readAllStandardOutput(); qCCritical(AKONADISERVER_LOG) << "stderr:" << pgCtl.readAllStandardError(); qCCritical(AKONADISERVER_LOG) << "exit code:" << pgCtl.exitCode(); qCCritical(AKONADISERVER_LOG) << "process error:" << pgCtl.errorString(); return false; } } if (opened) { { QSqlQuery query(db); // check if the 'akonadi' database already exists query.exec(QStringLiteral("SELECT 1 FROM pg_catalog.pg_database WHERE datname = '%1'").arg(mDatabaseName)); // if not, create it if (!query.first()) { if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(mDatabaseName))) { qCCritical(AKONADISERVER_LOG) << "Failed to create database"; qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } } // make sure query is destroyed before we close the db db.close(); } } // Make sure pg_ctl has returned pgCtl.waitForFinished(); QSqlDatabase::removeDatabase(initCon); return success; } void DbConfigPostgresql::stopInternalServer() { if (!checkServerIsRunning()) { qCDebug(AKONADISERVER_LOG) << "Database is no longer running"; return; } // first, try a FAST shutdown execute(mServerPath, { QStringLiteral("stop"), QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--mode=fast") }); if (!checkServerIsRunning()) { return; } // second, try an IMMEDIATE shutdown execute(mServerPath, { QStringLiteral("stop"), QStringLiteral("--pgdata=%1").arg(mPgData), QStringLiteral("--mode=immediate") }); if (!checkServerIsRunning()) { return; } // third, pg_ctl couldn't terminate all the postgres processes, we have to // kill the master one. We don't want to do that, but we've passed the last // call. pg_ctl is used to send the kill signal (safe when kill is not // supported by OS) const QString pidFileName = QStringLiteral("%1/postmaster.pid").arg(mPgData); QFile pidFile(pidFileName); if (pidFile.open(QIODevice::ReadOnly)) { QString postmasterPid = QString::fromUtf8(pidFile.readLine(0).trimmed()); qCCritical(AKONADISERVER_LOG) << "The postmaster is still running. Killing it."; execute(mServerPath, { QStringLiteral("kill"), QStringLiteral("ABRT"), postmasterPid }); } } bool DbConfigPostgresql::checkServerIsRunning() { const QString command = mServerPath; QStringList arguments; arguments << QStringLiteral("status") << QStringLiteral("--pgdata=%1").arg(mPgData); QProcess pgCtl; pgCtl.start(command, arguments, QIODevice::ReadOnly); if (!pgCtl.waitForFinished(3000)) { // Error? return false; } // "pg_ctl status" exits with 0 when server is running and a non-zero code when not. return pgCtl.exitCode() == 0; } diff --git a/src/server/storage/dbupdater.cpp b/src/server/storage/dbupdater.cpp index a3e64b576..02af0989d 100644 --- a/src/server/storage/dbupdater.cpp +++ b/src/server/storage/dbupdater.cpp @@ -1,578 +1,578 @@ /* Copyright (c) 2007 - 2012 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 "dbupdater.h" #include "dbtype.h" #include "entities.h" #include "akonadischema.h" #include "querybuilder.h" #include "selectquerybuilder.h" #include "datastore.h" #include "dbconfig.h" #include "dbintrospector.h" #include "dbinitializer_p.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; DbUpdater::DbUpdater(const QSqlDatabase &database, const QString &filename) : m_database(database) , m_filename(filename) { } bool DbUpdater::run() { Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); // TODO error handling SchemaVersion currentVersion = SchemaVersion::retrieveAll().first(); UpdateSet::Map updates; if (!parseUpdateSets(currentVersion.version(), updates)) { return false; } if (updates.isEmpty()) { return true; } // indicate clients this might take a while // we can ignore unregistration in error cases, that'll kill the server anyway if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::UpgradeIndicator))) { qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message(); } // QMap is sorted, so we should be replaying the changes in correct order for (QMap::ConstIterator it = updates.constBegin(); it != updates.constEnd(); ++it) { Q_ASSERT(it.key() > currentVersion.version()); qCDebug(AKONADISERVER_LOG) << "DbUpdater: update to version:" << it.key() << " mandatory:" << it.value().abortOnFailure; bool success = false; bool hasTransaction = false; if (it.value().complex) { // complex update const QString methodName = QStringLiteral("complexUpdate_%1()").arg(it.value().version); const int index = metaObject()->indexOfMethod(methodName.toLatin1().constData()); if (index == -1) { success = false; qCCritical(AKONADISERVER_LOG) << "Update to version" << it.value().version << "marked as complex, but no implementation is available"; } else { const QMetaMethod method = metaObject()->method(index); method.invoke(this, Q_RETURN_ARG(bool, success)); if (!success) { qCCritical(AKONADISERVER_LOG) << "Update failed"; } } } else { // regular update success = m_database.transaction(); if (success) { hasTransaction = true; const QStringList statements = it.value().statements; for (const QString &statement : statements) { QSqlQuery query(m_database); success = query.exec(statement); if (!success) { qCCritical(AKONADISERVER_LOG) << "DBUpdater: query error:" << query.lastError().text() << m_database.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Query was: " << statement; qCCritical(AKONADISERVER_LOG) << "Target version was: " << it.key(); qCCritical(AKONADISERVER_LOG) << "Mandatory: " << it.value().abortOnFailure; } } } } if (success) { currentVersion.setVersion(it.key()); success = currentVersion.update(); } if (!success || (hasTransaction && !m_database.commit())) { qCCritical(AKONADISERVER_LOG) << "Failed to commit transaction for database update"; if (hasTransaction) { m_database.rollback(); } if (it.value().abortOnFailure) { return false; } } } QDBusConnection::sessionBus().unregisterService(DBus::serviceName(DBus::UpgradeIndicator)); return true; } bool DbUpdater::parseUpdateSets(int currentVersion, UpdateSet::Map &updates) const { QFile file(m_filename); if (!file.open(QIODevice::ReadOnly)) { qCCritical(AKONADISERVER_LOG) << "Unable to open update description file" << m_filename; return false; } QDomDocument document; QString errorMsg; int line, column; if (!document.setContent(&file, &errorMsg, &line, &column)) { qCCritical(AKONADISERVER_LOG) << "Unable to parse update description file" << m_filename << ":" << errorMsg << "at line" << line << "column" << column; return false; } const QDomElement documentElement = document.documentElement(); if (documentElement.tagName() != QLatin1String("updates")) { - qCCritical(AKONADISERVER_LOG) << "Invalid update description file formant"; + qCCritical(AKONADISERVER_LOG) << "Invalid update description file format"; return false; } // iterate over the xml document and extract update information into an UpdateSet QDomElement updateElement = documentElement.firstChildElement(); while (!updateElement.isNull()) { if (updateElement.tagName() == QLatin1String("update")) { const int version = updateElement.attribute(QStringLiteral("version"), QStringLiteral("-1")).toInt(); if (version <= 0) { qCCritical(AKONADISERVER_LOG) << "Invalid version attribute in database update description"; return false; } if (updates.contains(version)) { qCCritical(AKONADISERVER_LOG) << "Duplicate version attribute in database update description"; return false; } if (version <= currentVersion) { qCDebug(AKONADISERVER_LOG) << "skipping update" << version; } else { UpdateSet updateSet; updateSet.version = version; updateSet.abortOnFailure = (updateElement.attribute(QStringLiteral("abortOnFailure")) == QLatin1String("true")); QDomElement childElement = updateElement.firstChildElement(); while (!childElement.isNull()) { if (childElement.tagName() == QLatin1String("raw-sql")) { if (updateApplicable(childElement.attribute(QStringLiteral("backends")))) { updateSet.statements << buildRawSqlStatement(childElement); } } else if (childElement.tagName() == QLatin1String("complex-update")) { if (updateApplicable(childElement.attribute(QStringLiteral("backends")))) { updateSet.complex = true; } } //TODO: check for generic tags here in the future childElement = childElement.nextSiblingElement(); } if (!updateSet.statements.isEmpty() || updateSet.complex) { updates.insert(version, updateSet); } } } updateElement = updateElement.nextSiblingElement(); } return true; } bool DbUpdater::updateApplicable(const QString &backends) const { const QStringList matchingBackends = backends.split(QLatin1Char(',')); QString currentBackend; switch (DbType::type(m_database)) { case DbType::MySQL: currentBackend = QStringLiteral("mysql"); break; case DbType::PostgreSQL: currentBackend = QStringLiteral("psql"); break; case DbType::Sqlite: currentBackend = QStringLiteral("sqlite"); break; case DbType::Unknown: return false; } return matchingBackends.contains(currentBackend); } QString DbUpdater::buildRawSqlStatement(const QDomElement &element) const { return element.text().trimmed(); } bool DbUpdater::complexUpdate_25() { qCDebug(AKONADISERVER_LOG) << "Starting database update to version 25"; DbType::Type dbType = DbType::type(DataStore::self()->database()); QElapsedTimer ttotal; ttotal.start(); // Recover from possibly failed or interrupted update { // We don't care if this fails, it just means that there was no failed update QSqlQuery query(DataStore::self()->database()); query.exec(QStringLiteral("ALTER TABLE PartTable_old RENAME TO PartTable")); } { QSqlQuery query(DataStore::self()->database()); query.exec(QStringLiteral("DROP TABLE IF EXISTS PartTable_new")); } { // Make sure the table is empty, otherwise we get duplicate key error QSqlQuery query(DataStore::self()->database()); if (dbType == DbType::Sqlite) { query.exec(QStringLiteral("DELETE FROM PartTypeTable")); } else { // MySQL, PostgreSQL query.exec(QStringLiteral("TRUNCATE TABLE PartTypeTable")); } } { // It appears that more users than expected have the invalid "GID" part in their // PartTable, which breaks the migration below (see BKO#331867), so we apply this // wanna-be fix to remove the invalid part before we start the actual migration. QueryBuilder qb(QStringLiteral("PartTable"), QueryBuilder::Delete); qb.addValueCondition(QStringLiteral("PartTable.name"), Query::Equals, QLatin1String("GID")); qb.exec(); } qCDebug(AKONADISERVER_LOG) << "Creating a PartTable_new"; { TableDescription description; description.name = QStringLiteral("PartTable_new"); ColumnDescription idColumn; idColumn.name = QStringLiteral("id"); idColumn.type = QStringLiteral("qint64"); idColumn.isAutoIncrement = true; idColumn.isPrimaryKey = true; description.columns << idColumn; ColumnDescription pimItemIdColumn; pimItemIdColumn.name = QStringLiteral("pimItemId"); pimItemIdColumn.type = QStringLiteral("qint64"); pimItemIdColumn.allowNull = false; description.columns << pimItemIdColumn; ColumnDescription partTypeIdColumn; partTypeIdColumn.name = QStringLiteral("partTypeId"); partTypeIdColumn.type = QStringLiteral("qint64"); partTypeIdColumn.allowNull = false; description.columns << partTypeIdColumn; ColumnDescription dataColumn; dataColumn.name = QStringLiteral("data"); dataColumn.type = QStringLiteral("QByteArray"); description.columns << dataColumn; ColumnDescription dataSizeColumn; dataSizeColumn.name = QStringLiteral("datasize"); dataSizeColumn.type = QStringLiteral("qint64"); dataSizeColumn.allowNull = false; description.columns << dataSizeColumn; ColumnDescription versionColumn; versionColumn.name = QStringLiteral("version"); versionColumn.type = QStringLiteral("int"); versionColumn.defaultValue = QStringLiteral("0"); description.columns << versionColumn; ColumnDescription externalColumn; externalColumn.name = QStringLiteral("external"); externalColumn.type = QStringLiteral("bool"); externalColumn.defaultValue = QStringLiteral("false"); description.columns << externalColumn; DbInitializer::Ptr initializer = DbInitializer::createInstance(DataStore::self()->database()); const QString queryString = initializer->buildCreateTableStatement(description); QSqlQuery query(DataStore::self()->database()); if (!query.exec(queryString)) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); return false; } } qCDebug(AKONADISERVER_LOG) << "Migrating part types"; { // Get list of all part names QueryBuilder qb(QStringLiteral("PartTable"), QueryBuilder::Select); qb.setDistinct(true); qb.addColumn(QStringLiteral("PartTable.name")); if (!qb.exec()) { qCCritical(AKONADISERVER_LOG) << qb.query().lastError().text(); return false; } // Process them one by one QSqlQuery query = qb.query(); while (query.next()) { // Split the part name to namespace and name and insert it to PartTypeTable const QString partName = query.value(0).toString(); const QString ns = partName.left(3); const QString name = partName.mid(4); { QueryBuilder qb(QStringLiteral("PartTypeTable"), QueryBuilder::Insert); qb.setColumnValue(QStringLiteral("ns"), ns); qb.setColumnValue(QStringLiteral("name"), name); if (!qb.exec()) { qCCritical(AKONADISERVER_LOG) << qb.query().lastError().text(); return false; } } qCDebug(AKONADISERVER_LOG) << "\t Moved part type" << partName << "to PartTypeTable"; } query.finish(); } qCDebug(AKONADISERVER_LOG) << "Migrating data from PartTable to PartTable_new"; { QSqlQuery query(DataStore::self()->database()); QString queryString; if (dbType == DbType::PostgreSQL) { queryString = QStringLiteral("INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) " "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, " " PartTable.datasize, PartTable.version, PartTable.external " "FROM PartTable " "LEFT JOIN PartTypeTable ON " " PartTable.name = CONCAT(PartTypeTable.ns, ':', PartTypeTable.name)"); } else if (dbType == DbType::MySQL) { queryString = QStringLiteral("INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) " "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, " "PartTable.datasize, PartTable.version, PartTable.external " "FROM PartTable " "LEFT JOIN PartTypeTable ON PartTable.name = CONCAT(PartTypeTable.ns, ':', PartTypeTable.name)"); } else if (dbType == DbType::Sqlite) { queryString = QStringLiteral("INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) " "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, " "PartTable.datasize, PartTable.version, PartTable.external " "FROM PartTable " "LEFT JOIN PartTypeTable ON PartTable.name = PartTypeTable.ns || ':' || PartTypeTable.name"); } if (!query.exec(queryString)) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); return false; } } qCDebug(AKONADISERVER_LOG) << "Swapping PartTable_new for PartTable"; { // Does an atomic swap QSqlQuery query(DataStore::self()->database()); if (dbType == DbType::PostgreSQL || dbType == DbType::Sqlite) { if (dbType == DbType::PostgreSQL) { DataStore::self()->beginTransaction(QStringLiteral("DBUPDATER (r25)")); } if (!query.exec(QStringLiteral("ALTER TABLE PartTable RENAME TO PartTable_old"))) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); DataStore::self()->rollbackTransaction(); return false; } // If this fails in SQLite (i.e. without transaction), we can still recover on next start) if (!query.exec(QStringLiteral("ALTER TABLE PartTable_new RENAME TO PartTable"))) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); if (DataStore::self()->inTransaction()) { DataStore::self()->rollbackTransaction(); } return false; } if (dbType == DbType::PostgreSQL) { DataStore::self()->commitTransaction(); } } else { // MySQL cannot do rename in transaction, but supports atomic renames if (!query.exec(QStringLiteral("RENAME TABLE PartTable TO PartTable_old," " PartTable_new TO PartTable"))) { qCCritical(AKONADISERVER_LOG) << query.lastError().text(); return false; } } } qCDebug(AKONADISERVER_LOG) << "Removing PartTable_old"; { QSqlQuery query(DataStore::self()->database()); if (!query.exec(QStringLiteral("DROP TABLE PartTable_old;"))) { // It does not matter when this fails, we are successfully migrated qCDebug(AKONADISERVER_LOG) << query.lastError().text(); qCDebug(AKONADISERVER_LOG) << "Not a fatal problem, continuing..."; } } // Fine tuning for PostgreSQL qCDebug(AKONADISERVER_LOG) << "Final tuning of new PartTable"; { QSqlQuery query(DataStore::self()->database()); if (dbType == DbType::PostgreSQL) { query.exec(QStringLiteral("ALTER TABLE PartTable RENAME CONSTRAINT parttable_new_pkey TO parttable_pkey")); query.exec(QStringLiteral("ALTER SEQUENCE parttable_new_id_seq RENAME TO parttable_id_seq")); query.exec(QStringLiteral("SELECT setval('parttable_id_seq', MAX(id) + 1) FROM PartTable")); } else if (dbType == DbType::MySQL) { // 0 will automatically reset AUTO_INCREMENT to SELECT MAX(id) + 1 FROM PartTable query.exec(QStringLiteral("ALTER TABLE PartTable AUTO_INCREMENT = 0")); } } qCDebug(AKONADISERVER_LOG) << "Update done in" << ttotal.elapsed() << "ms"; // Foreign keys and constraints will be reconstructed automatically once // all updates are done return true; } bool DbUpdater::complexUpdate_36() { qCDebug(AKONADISERVER_LOG, "Starting database update to version 36"); Q_ASSERT(DbType::type(DataStore::self()->database()) == DbType::Sqlite); QSqlQuery query(DataStore::self()->database()); if (!query.exec(QStringLiteral("PRAGMA foreign_key_checks=OFF"))) { qCCritical(AKONADISERVER_LOG, "Failed to disable foreign key checks!"); return false; } const auto hasForeignKeys = [](const TableDescription &desc) { return std::any_of(desc.columns.cbegin(), desc.columns.cend(), [](const ColumnDescription &col) { return !col.refTable.isEmpty() && !col.refColumn.isEmpty(); }); }; const auto recreateTableWithForeignKeys = [](const TableDescription &table) -> QPair { qCDebug(AKONADISERVER_LOG) << "Updating foreign keys in table" << table.name; QSqlQuery query(DataStore::self()->database()); // Recover from possibly failed or interrupted update // We don't care if this fails, it just means that there was no failed update query.exec(QStringLiteral("ALTER TABLE %1_old RENAME TO %1").arg(table.name)); query.exec(QStringLiteral("DROP TABLE %1_new").arg(table.name)); qCDebug(AKONADISERVER_LOG, "\tCreating table %s_new with foreign keys", qUtf8Printable(table.name)); { const auto initializer = DbInitializer::createInstance(DataStore::self()->database()); TableDescription copy = table; copy.name += QStringLiteral("_new"); if (!query.exec(initializer->buildCreateTableStatement(copy))) { // If this fails we will recover on next start return {false, query}; } } qCDebug(AKONADISERVER_LOG, "\tCopying values from %s to %s_new (this may take a very long of time...)", qUtf8Printable(table.name), qUtf8Printable(table.name)); if (!query.exec(QStringLiteral("INSERT INTO %1_new SELECT * FROM %1").arg(table.name))) { // If this fails, we will recover on next start return {false, query}; } qCDebug(AKONADISERVER_LOG, "\tSwapping %s_new for %s", qUtf8Printable(table.name), qUtf8Printable(table.name)); if (!query.exec(QStringLiteral("ALTER TABLE %1 RENAME TO %1_old").arg(table.name))) { // If this fails we will recover on next start return {false, query}; } if (!query.exec(QStringLiteral("ALTER TABLE %1_new RENAME TO %1").arg(table.name))) { // If this fails we will recover on next start return {false, query}; } qCDebug(AKONADISERVER_LOG, "\tRemoving table %s_old", qUtf8Printable(table.name)); if (!query.exec(QStringLiteral("DROP TABLE %1_old").arg(table.name))) { // We don't care if this fails qCWarning(AKONADISERVER_LOG, "Failed to DROP TABLE %s (not fatal, update will continue)", qUtf8Printable(table.name)); qCWarning(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text())); } qCDebug(AKONADISERVER_LOG) << "\tOptimizing table %s", qUtf8Printable(table.name); if (!query.exec(QStringLiteral("ANALYZE %1").arg(table.name))) { // We don't care if this fails qCWarning(AKONADISERVER_LOG, "Failed to ANALYZE %s (not fatal, update will continue)", qUtf8Printable(table.name)); qCWarning(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text())); } qCDebug(AKONADISERVER_LOG) << "\tDone"; return {true, QSqlQuery()}; }; AkonadiSchema schema; const auto tables = schema.tables(); for (const auto &table : tables) { if (!hasForeignKeys(table)) { continue; } const auto result = recreateTableWithForeignKeys(table); if (!result.first) { qCCritical(AKONADISERVER_LOG, "SQL error when updating table %s", qUtf8Printable(table.name)); qCCritical(AKONADISERVER_LOG, "Query: %s", qUtf8Printable(result.second.executedQuery())); qCCritical(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(result.second.lastError().text())); return false; } } const auto relations = schema.relations(); for (const auto &relation : relations) { const RelationTableDescription table(relation); const auto result = recreateTableWithForeignKeys(table); if (!result.first) { qCCritical(AKONADISERVER_LOG, "SQL error when updating relation table %s", qUtf8Printable(table.name)); qCCritical(AKONADISERVER_LOG, "Query: %s", qUtf8Printable(result.second.executedQuery())); qCCritical(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(result.second.lastError().text())); return false; } } qCDebug(AKONADISERVER_LOG) << "Running VACUUM to reduce DB size"; if (!query.exec(QStringLiteral("VACUUM"))) { qCWarning(AKONADISERVER_LOG) << "Vacuum failed (not fatal, update will continue)"; qCWarning(AKONADISERVER_LOG) << "Error:" << query.lastError().text(); } return true; } diff --git a/src/server/storage/notificationcollector.cpp b/src/server/storage/notificationcollector.cpp index 44413e6b5..d9073e915 100644 --- a/src/server/storage/notificationcollector.cpp +++ b/src/server/storage/notificationcollector.cpp @@ -1,615 +1,615 @@ /* 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 "notificationcollector.h" #include "storage/datastore.h" #include "storage/entity.h" #include "storage/collectionstatistics.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "search/searchmanager.h" #include "akonadi.h" #include "notificationmanager.h" #include "aggregatedfetchscope.h" #include "selectquerybuilder.h" #include "handler/itemfetchhelper.h" #include "connection.h" #include "shared/akranges.h" #include "akonadiserver_debug.h" #include using namespace Akonadi; using namespace Akonadi::Server; NotificationCollector::NotificationCollector(DataStore *db) : mDb(db) { QObject::connect(db, &DataStore::transactionCommitted, [this]() { if (!mIgnoreTransactions) { dispatchNotifications(); } }); QObject::connect(db, &DataStore::transactionRolledBack, [this]() { if (!mIgnoreTransactions) { clear(); } }); } void NotificationCollector::itemAdded(const PimItem &item, bool seen, const Collection &collection, const QByteArray &resource) { SearchManager::instance()->scheduleSearchUpdate(); CollectionStatistics::self()->itemAdded(collection, item.size(), seen); itemNotification(Protocol::ItemChangeNotification::Add, item, collection, Collection(), resource); } void NotificationCollector::itemChanged(const PimItem &item, const QSet &changedParts, const Collection &collection, const QByteArray &resource) { SearchManager::instance()->scheduleSearchUpdate(); itemNotification(Protocol::ItemChangeNotification::Modify, item, collection, Collection(), resource, changedParts); } void NotificationCollector::itemsFlagsChanged(const PimItem::List &items, const QSet &addedFlags, const QSet &removedFlags, const Collection &collection, const QByteArray &resource) { int seenCount = (addedFlags.contains(AKONADI_FLAG_SEEN) || addedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0); seenCount -= (removedFlags.contains(AKONADI_FLAG_SEEN) || removedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0); CollectionStatistics::self()->itemsSeenChanged(collection, seenCount); itemNotification(Protocol::ItemChangeNotification::ModifyFlags, items, collection, Collection(), resource, QSet(), addedFlags, removedFlags); } void NotificationCollector::itemsTagsChanged(const PimItem::List &items, const QSet &addedTags, const QSet &removedTags, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::ModifyTags, items, collection, Collection(), resource, QSet(), QSet(), QSet(), addedTags, removedTags); } void NotificationCollector::itemsRelationsChanged(const PimItem::List &items, const Relation::List &addedRelations, const Relation::List &removedRelations, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::ModifyRelations, items, collection, Collection(), resource, QSet(), QSet(), QSet(), QSet(), QSet(), addedRelations, removedRelations); } void NotificationCollector::itemsMoved(const PimItem::List &items, const Collection &collectionSrc, const Collection &collectionDest, const QByteArray &sourceResource) { SearchManager::instance()->scheduleSearchUpdate(); itemNotification(Protocol::ItemChangeNotification::Move, items, collectionSrc, collectionDest, sourceResource); } void NotificationCollector::itemsRemoved(const PimItem::List &items, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::Remove, items, collection, Collection(), resource); } void NotificationCollector::itemsLinked(const PimItem::List &items, const Collection &collection) { itemNotification(Protocol::ItemChangeNotification::Link, items, collection, Collection(), QByteArray()); } void NotificationCollector::itemsUnlinked(const PimItem::List &items, const Collection &collection) { itemNotification(Protocol::ItemChangeNotification::Unlink, items, collection, Collection(), QByteArray()); } void NotificationCollector::collectionAdded(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionAdded(collection.id()); } AkonadiServer::instance()->intervalChecker()->collectionAdded(collection.id()); collectionNotification(Protocol::CollectionChangeNotification::Add, collection, collection.parentId(), -1, resource); } void NotificationCollector::collectionChanged(const Collection &collection, const QList &changes, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionChanged(collection.id()); } AkonadiServer::instance()->intervalChecker()->collectionChanged(collection.id()); if (changes.contains(AKONADI_PARAM_ENABLED)) { CollectionStatistics::self()->invalidateCollection(collection); } collectionNotification(Protocol::CollectionChangeNotification::Modify, collection, collection.parentId(), -1, resource, changes | toQSet); } void NotificationCollector::collectionMoved(const Collection &collection, const Collection &source, const QByteArray &resource, const QByteArray &destResource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionChanged(collection.id()); } AkonadiServer::instance()->intervalChecker()->collectionChanged(collection.id()); collectionNotification(Protocol::CollectionChangeNotification::Move, collection, source.id(), collection.parentId(), resource, QSet(), destResource); } void NotificationCollector::collectionRemoved(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionRemoved(collection.id()); } AkonadiServer::instance()->intervalChecker()->collectionRemoved(collection.id()); CollectionStatistics::self()->invalidateCollection(collection); collectionNotification(Protocol::CollectionChangeNotification::Remove, collection, collection.parentId(), -1, resource); } void NotificationCollector::collectionSubscribed(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionAdded(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionAdded(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Subscribe, collection, collection.parentId(), -1, resource, QSet()); } void NotificationCollector::collectionUnsubscribed(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionRemoved(collection.id()); } AkonadiServer::instance()->intervalChecker()->collectionRemoved(collection.id()); CollectionStatistics::self()->invalidateCollection(collection); collectionNotification(Protocol::CollectionChangeNotification::Unsubscribe, collection, collection.parentId(), -1, resource, QSet()); } void NotificationCollector::tagAdded(const Tag &tag) { tagNotification(Protocol::TagChangeNotification::Add, tag); } void NotificationCollector::tagChanged(const Tag &tag) { tagNotification(Protocol::TagChangeNotification::Modify, tag); } void NotificationCollector::tagRemoved(const Tag &tag, const QByteArray &resource, const QString &remoteId) { tagNotification(Protocol::TagChangeNotification::Remove, tag, resource, remoteId); } void NotificationCollector::relationAdded(const Relation &relation) { relationNotification(Protocol::RelationChangeNotification::Add, relation); } void NotificationCollector::relationRemoved(const Relation &relation) { relationNotification(Protocol::RelationChangeNotification::Remove, relation); } void NotificationCollector::clear() { mNotifications.clear(); } void NotificationCollector::setConnection(Connection *connection) { mConnection = connection; } void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem &item, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts) { PimItem::List items; items << item; itemNotification(op, items, collection, collectionDest, resource, parts); } void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem::List &items, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts, const QSet &addedFlags, const QSet &removedFlags, const QSet &addedTags, const QSet &removedTags, const Relation::List &addedRelations, const Relation::List &removedRelations) { QMap > vCollections; if ((op == Protocol::ItemChangeNotification::Modify) || (op == Protocol::ItemChangeNotification::ModifyFlags) || (op == Protocol::ItemChangeNotification::ModifyTags) || (op == Protocol::ItemChangeNotification::ModifyRelations)) { vCollections = DataStore::self()->virtualCollections(items); } auto msg = Protocol::ItemChangeNotificationPtr::create(); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setOperation(op); msg->setItemParts(parts); msg->setAddedFlags(addedFlags); msg->setRemovedFlags(removedFlags); msg->setAddedTags(addedTags); msg->setRemovedTags(removedTags); if (!addedRelations.isEmpty()) { QSet rels; Q_FOREACH (const Relation &rel, addedRelations) { rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name())); } msg->setAddedRelations(rels); } if (!removedRelations.isEmpty()) { QSet rels; Q_FOREACH (const Relation &rel, removedRelations) { rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name())); } msg->setRemovedRelations(rels); } if (collectionDest.isValid()) { QByteArray destResourceName; destResourceName = collectionDest.resource().name().toLatin1(); msg->setDestinationResource(destResourceName); } msg->setParentDestCollection(collectionDest.id()); QVector ntfItems; Q_FOREACH (const PimItem &item, items) { Protocol::FetchItemsResponse i; i.setId(item.id()); i.setRemoteId(item.remoteId()); i.setRemoteRevision(item.remoteRevision()); i.setMimeType(item.mimeType().name()); ntfItems.push_back(std::move(i)); } /* Notify all virtual collections the items are linked to. */ QHash virtItems; for (const auto &ntfItem : ntfItems) { virtItems.insert(ntfItem.id(), std::move(ntfItem)); } auto iter = vCollections.constBegin(), endIter = vCollections.constEnd(); for (; iter != endIter; ++iter) { auto copy = Protocol::ItemChangeNotificationPtr::create(*msg); QVector items; items.reserve(iter->size()); for (const auto &item : qAsConst(*iter)) { items.append(virtItems.value(item.id())); } copy->setItems(items); copy->setParentCollection(iter.key()); copy->setResource(resource); CollectionStatistics::self()->invalidateCollection(Collection::retrieveById(iter.key())); dispatchNotification(copy); } msg->setItems(ntfItems); Collection col; if (!collection.isValid()) { msg->setParentCollection(items.first().collection().id()); col = items.first().collection(); } else { msg->setParentCollection(collection.id()); col = collection; } QByteArray res = resource; if (res.isEmpty()) { if (col.resourceId() <= 0) { col = Collection::retrieveById(col.id()); } res = col.resource().name().toLatin1(); } msg->setResource(res); // Add and ModifyFlags are handled incrementally // (see itemAdded() and itemsFlagsChanged()) if (msg->operation() != Protocol::ItemChangeNotification::Add && msg->operation() != Protocol::ItemChangeNotification::ModifyFlags) { CollectionStatistics::self()->invalidateCollection(col); } dispatchNotification(msg); } void NotificationCollector::collectionNotification(Protocol::CollectionChangeNotification::Operation op, const Collection &collection, Collection::Id source, Collection::Id destination, const QByteArray &resource, const QSet &changes, const QByteArray &destResource) { auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setOperation(op); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setParentCollection(source); msg->setParentDestCollection(destination); msg->setDestinationResource(destResource); msg->setChangedParts(changes); auto msgCollection = HandlerHelper::fetchCollectionsResponse(collection); if (auto mgr = AkonadiServer::instance()->notificationManager()) { auto fetchScope = mgr->collectionFetchScope(); // Make sure we have all the data if (!fetchScope->fetchIdOnly() && msgCollection.name().isEmpty()) { const auto col = Collection::retrieveById(msgCollection.id()); const auto mts = col.mimeTypes(); QStringList mimeTypes; mimeTypes.reserve(mts.size()); for (const auto &mt : mts) { mimeTypes.push_back(mt.name()); } msgCollection = HandlerHelper::fetchCollectionsResponse(col, {}, false, 0, {}, {}, mimeTypes); } // Get up-to-date statistics if (fetchScope->fetchStatistics()) { Collection col; col.setId(msgCollection.id()); const auto stats = CollectionStatistics::self()->statistics(col); msgCollection.setStatistics(Protocol::FetchCollectionStatsResponse(stats.count, stats.count - stats.read, stats.size)); } // Get attributes const auto requestedAttrs = fetchScope->attributes(); auto msgColAttrs = msgCollection.attributes(); // TODO: This assumes that we have either none or all attributes in msgCollection if (msgColAttrs.isEmpty() && !requestedAttrs.isEmpty()) { SelectQueryBuilder qb; qb.addColumn(CollectionAttribute::typeFullColumnName()); qb.addColumn(CollectionAttribute::valueFullColumnName()); qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(), Query::Equals, msgCollection.id()); Query::Condition cond(Query::Or); for (const auto &attr : requestedAttrs) { cond.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::Equals, attr); } qb.addCondition(cond); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "NotificationCollector failed to query attributes for Collection" << collection.name() << "(ID" << collection.id() << ")"; } const auto attrs = qb.result(); for (const auto &attr : attrs) { msgColAttrs.insert(attr.type(), attr.value()); } msgCollection.setAttributes(msgColAttrs); } } msg->setCollection(std::move(msgCollection)); if (!collection.enabled()) { msg->addMetadata("DISABLED"); } QByteArray res = resource; if (res.isEmpty()) { res = collection.resource().name().toLatin1(); } msg->setResource(res); dispatchNotification(msg); } void NotificationCollector::tagNotification(Protocol::TagChangeNotification::Operation op, const Tag &tag, const QByteArray &resource, const QString &remoteId) { auto msg = Protocol::TagChangeNotificationPtr::create(); msg->setOperation(op); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setResource(resource); Protocol::FetchTagsResponse msgTag; msgTag.setId(tag.id()); msgTag.setRemoteId(remoteId.toUtf8()); if (auto mgr = AkonadiServer::instance()->notificationManager()) { auto fetchScope = mgr->tagFetchScope(); if (!fetchScope->fetchIdOnly() && msgTag.gid().isEmpty()) { msgTag = HandlerHelper::fetchTagsResponse(Tag::retrieveById(msgTag.id()), fetchScope->toFetchScope(), mConnection); } const auto requestedAttrs = fetchScope->attributes(); auto msgTagAttrs = msgTag.attributes(); if (msgTagAttrs.isEmpty() && !requestedAttrs.isEmpty()) { SelectQueryBuilder qb; qb.addColumn(TagAttribute::typeFullColumnName()); qb.addColumn(TagAttribute::valueFullColumnName()); qb.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, msgTag.id()); Query::Condition cond(Query::Or); for (const auto &attr : requestedAttrs) { cond.addValueCondition(TagAttribute::typeFullColumnName(), Query::Equals, attr); } qb.addCondition(cond); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "NotificationCollection failed to query attributes for Tag" << tag.id(); } const auto attrs = qb.result(); for (const auto &attr : attrs) { msgTagAttrs.insert(attr.type(), attr.value()); } msgTag.setAttributes(msgTagAttrs); } } msg->setTag(std::move(msgTag)); dispatchNotification(msg); } void NotificationCollector::relationNotification(Protocol::RelationChangeNotification::Operation op, const Relation &relation) { auto msg = Protocol::RelationChangeNotificationPtr::create(); msg->setOperation(op); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setRelation(HandlerHelper::fetchRelationsResponse(relation)); dispatchNotification(msg); } void NotificationCollector::completeNotification(const Protocol::ChangeNotificationPtr &changeMsg) { if (changeMsg->type() == Protocol::Command::ItemChangeNotification) { const auto msg = changeMsg.staticCast(); const auto mgr = AkonadiServer::instance()->notificationManager(); if (mgr && msg->operation() != Protocol::ItemChangeNotification::Remove) { if (mDb->inTransaction()) { qCWarning(AKONADISERVER_LOG) << "NotificationCollector requested FetchHelper from within a transaction." << "Aborting since this would deadlock!"; return; } auto fetchScope = mgr->itemFetchScope(); // NOTE: Checking and retrieving missing elements for each Item manually // here would require a complex code (and I'm too lazy), so instead we simply // feed the Items to FetchHelper and retrieve them all with the setup from // the aggregated fetch scope. The worst case is that we re-fetch everything // we already have, but that's stil better than the pre-ntf-payload situation QVector ids; const auto items = msg->items(); ids.reserve(items.size()); bool allHaveRID = true; for (const auto &item : items) { ids.push_back(item.id()); allHaveRID &= !item.remoteId().isEmpty(); } // FetchHelper may trigger ItemRetriever, which needs RemoteID. If we - // dont have one (maybe because the Resource has not stored it yet, + // don't have one (maybe because the Resource has not stored it yet, // we emit a notification without it and leave it up to the Monitor // to retrieve the Item on demand - we should have a RID stored in // Akonadi by then. if (mConnection && (allHaveRID || msg->operation() != Protocol::ItemChangeNotification::Add)) { // Prevent transactions inside FetchHelper to recursively call our slot QScopedValueRollback ignoreTransactions(mIgnoreTransactions); mIgnoreTransactions = true; CommandContext context; auto itemFetchScope = fetchScope->toFetchScope(); auto tagFetchScope = mgr->tagFetchScope()->toFetchScope(); itemFetchScope.setFetch(Protocol::ItemFetchScope::CacheOnly); ItemFetchHelper helper(mConnection, &context, Scope(ids), itemFetchScope, tagFetchScope); // The Item was just changed, which means the atime was // updated, no need to do it again a couple milliseconds later. helper.disableATimeUpdates(); QVector fetchedItems; auto callback = [&fetchedItems](Protocol::FetchItemsResponse &&cmd) { fetchedItems.push_back(std::move(cmd)); }; if (helper.fetchItems(std::move(callback))) { msg->setItems(fetchedItems); } else { qCWarning(AKONADISERVER_LOG) << "NotificationCollector railed to retrieve Items for notification!"; } } else { QVector fetchedItems; for (const auto &item : items) { Protocol::FetchItemsResponse resp; resp.setId(item.id()); resp.setRevision(item.revision()); resp.setMimeType(item.mimeType()); resp.setParentId(item.parentId()); resp.setGid(item.gid()); resp.setSize(item.size()); resp.setMTime(item.mTime()); resp.setFlags(item.flags()); fetchedItems.push_back(std::move(resp)); } msg->setItems(fetchedItems); msg->setMustRetrieve(true); } } } } void NotificationCollector::dispatchNotification(const Protocol::ChangeNotificationPtr &msg) { if (!mDb || mDb->inTransaction()) { if (msg->type() == Protocol::Command::CollectionChangeNotification) { Protocol::CollectionChangeNotification::appendAndCompress(mNotifications, msg); } else { mNotifications.append(msg); } } else { completeNotification(msg); notify({msg}); } } void NotificationCollector::dispatchNotifications() { if (!mNotifications.isEmpty()) { for (auto &ntf : mNotifications) { completeNotification(ntf); } notify(std::move(mNotifications)); clear(); } } void NotificationCollector::notify(Protocol::ChangeNotificationList msgs) { if (auto mgr = AkonadiServer::instance()->notificationManager()) { QMetaObject::invokeMethod(mgr, "slotNotify", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationList, msgs)); } } diff --git a/src/widgets/agentconfigurationwidget.h b/src/widgets/agentconfigurationwidget.h index 1b00bd3fe..e9f229b3e 100644 --- a/src/widgets/agentconfigurationwidget.h +++ b/src/widgets/agentconfigurationwidget.h @@ -1,65 +1,65 @@ /* 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. */ -#ifndef AKONADI_AGENTCONFIGURATIOWIDGET_H -#define AKONADI_AGENTCONFIGURATIOWIDGET_H +#ifndef AKONADI_AGENTCONFIGURATIONWIDGET_H +#define AKONADI_AGENTCONFIGURATIONWIDGET_H #include #include #include "akonadiwidgets_export.h" namespace Akonadi { class AgentInstance; class AgentConfigurationDialog; /** * @brief A widget for displaying agent configuration in applications. * * To implement an agent configuration widget, see AgentConfigurationBase. */ class AKONADIWIDGETS_EXPORT AgentConfigurationWidget : public QWidget { Q_OBJECT public: explicit AgentConfigurationWidget(const Akonadi::AgentInstance &instance, QWidget *parent = nullptr); ~AgentConfigurationWidget() override; void load(); void save(); QSize restoreDialogSize() const; void saveDialogSize(const QSize &size); QDialogButtonBox::StandardButtons standardButtons() const; Q_SIGNALS: void enableOkButton(bool enabled); protected: void childEvent(QChildEvent *event) override; private: class Private; friend class Private; friend class AgentConfigurationDialog; QScopedPointer d; }; } #endif