diff --git a/src/core/attributestorage.cpp b/src/core/attributestorage.cpp index 0d6793f9f..f6af86edd 100644 --- a/src/core/attributestorage.cpp +++ b/src/core/attributestorage.cpp @@ -1,137 +1,143 @@ /* Copyright (c) 2019 David Faure 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 "attributestorage_p.h" using namespace Akonadi; AttributeStorage::AttributeStorage() { } AttributeStorage::AttributeStorage(const AttributeStorage &other) : mModifiedAttributes(other.mModifiedAttributes), mDeletedAttributes(other.mDeletedAttributes) { for (Attribute *attr : qAsConst(other.mAttributes)) { mAttributes.insert(attr->type(), attr->clone()); } } AttributeStorage &AttributeStorage::operator=(const AttributeStorage &other) { AttributeStorage copy(other); swap(copy); return *this; } void AttributeStorage::swap(AttributeStorage &other) noexcept { using std::swap; swap(other.mAttributes, mAttributes); swap(other.mModifiedAttributes, mModifiedAttributes); swap(other.mDeletedAttributes, mDeletedAttributes); } AttributeStorage::~AttributeStorage() { qDeleteAll(mAttributes); } void AttributeStorage::addAttribute(Attribute *attr) { Q_ASSERT(attr); const QByteArray type = attr->type(); Attribute *existing = mAttributes.value(type); if (existing) { if (attr == existing) { return; } mAttributes.remove(type); delete existing; } mAttributes.insert(type, attr); markAttributeModified(type); } void AttributeStorage::removeAttribute(const QByteArray &type) { mModifiedAttributes.erase(type); mDeletedAttributes.insert(type); delete mAttributes.take(type); } bool AttributeStorage::hasAttribute(const QByteArray &type) const { return mAttributes.contains(type); } Attribute::List AttributeStorage::attributes() const { return mAttributes.values(); } void AttributeStorage::clearAttributes() { for (Attribute *attr : qAsConst(mAttributes)) { mDeletedAttributes.insert(attr->type()); delete attr; } mAttributes.clear(); mModifiedAttributes.clear(); } -Attribute *AttributeStorage::attribute(const QByteArray &type) const +const Attribute *AttributeStorage::attribute(const QByteArray &type) const { return mAttributes.value(type); } +Attribute *AttributeStorage::attribute(const QByteArray &type) +{ + markAttributeModified(type); + return mAttributes.value(type); +} + void AttributeStorage::markAttributeModified(const QByteArray &type) { mDeletedAttributes.remove(type); mModifiedAttributes.insert(type); } void AttributeStorage::resetChangeLog() { mModifiedAttributes.clear(); mDeletedAttributes.clear(); } QSet AttributeStorage::deletedAttributes() const { return mDeletedAttributes; } bool AttributeStorage::hasModifiedAttributes() const { return !mModifiedAttributes.empty(); } std::vector AttributeStorage::modifiedAttributes() const { std::vector ret; ret.reserve(mModifiedAttributes.size()); for (const auto &type : mModifiedAttributes) { Attribute *attr = mAttributes.value(type); Q_ASSERT(attr); ret.push_back(attr); } return ret; } diff --git a/src/core/attributestorage_p.h b/src/core/attributestorage_p.h index e38f255f7..47ac096cc 100644 --- a/src/core/attributestorage_p.h +++ b/src/core/attributestorage_p.h @@ -1,67 +1,68 @@ /* Copyright (c) 2019 David Faure 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 ATTRIBUTESTORAGE_P_H #define ATTRIBUTESTORAGE_P_H #include #include #include #include #include "attribute.h" namespace Akonadi { /** * The AttributeStorage class is used by Collection, Item, Tag... * to store a set of attributes, remembering modifications. * I.e. it knows which attributes have been added or removed * compared to the initial set (e.g. fetched from server). */ class AttributeStorage { public: AttributeStorage(); AttributeStorage(const AttributeStorage &other); AttributeStorage& operator=(const AttributeStorage &other); void swap(AttributeStorage &other) noexcept; ~AttributeStorage(); void addAttribute(Attribute *attr); void removeAttribute(const QByteArray &type); bool hasAttribute(const QByteArray &type) const; Attribute::List attributes() const; void clearAttributes(); - Attribute *attribute(const QByteArray &type) const; + const Attribute *attribute(const QByteArray &type) const; + Attribute *attribute(const QByteArray &type); void markAttributeModified(const QByteArray &type); void resetChangeLog(); QSet deletedAttributes() const; bool hasModifiedAttributes() const; std::vector modifiedAttributes() const; private: QHash mAttributes; std::set mModifiedAttributes; QSet mDeletedAttributes; }; } #endif // ATTRIBUTESTORAGE_P_H diff --git a/src/core/collection.cpp b/src/core/collection.cpp index 9fe833913..c94ea291a 100644 --- a/src/core/collection.cpp +++ b/src/core/collection.cpp @@ -1,452 +1,456 @@ /* 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 "collection.h" #include "collection_p.h" #include "attributefactory.h" #include "cachepolicy.h" #include "collectionrightsattribute_p.h" #include "collectionstatistics.h" #include "entitydisplayattribute.h" #include #include #include #include #include #include using namespace Akonadi; Q_GLOBAL_STATIC(Akonadi::Collection, s_defaultParentCollection) uint Akonadi::qHash(const Akonadi::Collection &collection) { return ::qHash(collection.id()); } /** * Helper method for assignment operator and copy constructor. */ static void assignCollectionPrivate(QSharedDataPointer &one, const QSharedDataPointer &other) { // We can't simply do one = other here, we have to use a temp. // Otherwise ProtocolHelperTest::testParentCollectionAfterCollectionParsing() // will break. // // The reason are assignments like // col = col.parentCollection() // // Here, parentCollection() actually returns a reference to a pointer owned // by col. So when col (or rather, it's private class) is deleted, the pointer // to the parent collection and therefore the reference becomes invalid. // // With a single-line assignment here, the parent collection would be deleted // before it is assigned, and therefore the resulting object would point to // uninitalized memory. QSharedDataPointer temp = other; one = temp; } class CollectionRoot : public Collection { public: CollectionRoot() : Collection(0) { setContentMimeTypes({ Collection::mimeType() }); // The root collection is read-only for the users setRights(Collection::ReadOnly); } }; Q_GLOBAL_STATIC(CollectionRoot, s_root) Collection::Collection() : d_ptr(new CollectionPrivate) { static int lastId = -1; d_ptr->mId = lastId--; } Collection::Collection(Id id) : d_ptr(new CollectionPrivate(id)) { } Collection::Collection(const Collection &other) { assignCollectionPrivate(d_ptr, other.d_ptr); } Collection::~Collection() { } void Collection::setId(Collection::Id identifier) { d_ptr->mId = identifier; } Collection::Id Collection::id() const { return d_ptr->mId; } void Collection::setRemoteId(const QString &id) { d_ptr->mRemoteId = id; } QString Collection::remoteId() const { return d_ptr->mRemoteId; } void Collection::setRemoteRevision(const QString &revision) { d_ptr->mRemoteRevision = revision; } QString Collection::remoteRevision() const { return d_ptr->mRemoteRevision; } bool Collection::isValid() const { return (d_ptr->mId >= 0); } bool Collection::operator==(const Collection &other) const { // Invalid collections are the same, no matter what their internal ID is return (!isValid() && !other.isValid()) || (d_ptr->mId == other.d_ptr->mId); } bool Akonadi::Collection::operator!=(const Collection &other) const { return (isValid() || other.isValid()) && (d_ptr->mId != other.d_ptr->mId); } Collection &Collection ::operator=(const Collection &other) { if (this != &other) { assignCollectionPrivate(d_ptr, other.d_ptr); } return *this; } bool Akonadi::Collection::operator<(const Collection &other) const { return d_ptr->mId < other.d_ptr->mId; } void Collection::addAttribute(Attribute *attr) { d_ptr->mAttributeStorage.addAttribute(attr); } void Collection::removeAttribute(const QByteArray &type) { d_ptr->mAttributeStorage.removeAttribute(type); } bool Collection::hasAttribute(const QByteArray &type) const { return d_ptr->mAttributeStorage.hasAttribute(type); } Attribute::List Collection::attributes() const { return d_ptr->mAttributeStorage.attributes(); } void Akonadi::Collection::clearAttributes() { return d_ptr->mAttributeStorage.clearAttributes(); } -Attribute *Collection::attribute(const QByteArray &type) const +Attribute *Collection::attribute(const QByteArray &type) +{ + markAttributeModified(type); + return d_ptr->mAttributeStorage.attribute(type); +} + +const Attribute *Collection::attribute(const QByteArray &type) const { return d_ptr->mAttributeStorage.attribute(type); } Collection &Collection::parentCollection() { if (!d_ptr->mParent) { d_ptr->mParent = new Collection(); } return *(d_ptr->mParent); } Collection Collection::parentCollection() const { if (!d_ptr->mParent) { return *(s_defaultParentCollection); } else { return *(d_ptr->mParent); } } void Collection::setParentCollection(const Collection &parent) { delete d_ptr->mParent; d_ptr->mParent = new Collection(parent); } QString Collection::name() const { return d_ptr->name; } QString Collection::displayName() const { const EntityDisplayAttribute *const attr = attribute(); const QString displayName = attr ? attr->displayName() : QString(); return !displayName.isEmpty() ? displayName : d_ptr->name; } void Collection::setName(const QString &name) { d_ptr->name = name; } Collection::Rights Collection::rights() const { - CollectionRightsAttribute *attr = attribute(); - if (attr) { + if (const auto attr = attribute()) { return attr->rights(); } else { return AllRights; } } void Collection::setRights(Rights rights) { - CollectionRightsAttribute *attr = attribute(AddIfMissing); - attr->setRights(rights); + attribute(AddIfMissing)->setRights(rights); } QStringList Collection::contentMimeTypes() const { return d_ptr->contentTypes; } void Collection::setContentMimeTypes(const QStringList &types) { if (d_ptr->contentTypes != types) { d_ptr->contentTypes = types; d_ptr->contentTypesChanged = true; } } QUrl Collection::url(UrlType type) const { QUrlQuery query; query.addQueryItem(QStringLiteral("collection"), QString::number(id())); if (type == UrlWithName) { query.addQueryItem(QStringLiteral("name"), name()); } QUrl url; url.setScheme(QStringLiteral("akonadi")); url.setQuery(query); return url; } Collection Collection::fromUrl(const QUrl &url) { if (url.scheme() != QLatin1String("akonadi")) { return Collection(); } const QString colStr = QUrlQuery(url).queryItemValue(QStringLiteral("collection")); bool ok = false; Collection::Id colId = colStr.toLongLong(&ok); if (!ok) { return Collection(); } if (colId == 0) { return Collection::root(); } return Collection(colId); } Collection Collection::root() { return *s_root; } QString Collection::mimeType() { return QStringLiteral("inode/directory"); } QString Akonadi::Collection::virtualMimeType() { return QStringLiteral("application/x-vnd.akonadi.collection.virtual"); } QString Collection::resource() const { return d_ptr->resource; } void Collection::setResource(const QString &resource) { d_ptr->resource = resource; } QDebug operator <<(QDebug d, const Akonadi::Collection &collection) { return d << "Collection ID:" << collection.id() << " remote ID:" << collection.remoteId() << endl << " name:" << collection.name() << endl << " url:" << collection.url() << endl << " parent:" << collection.parentCollection().id() << collection.parentCollection().remoteId() << endl << " resource:" << collection.resource() << endl << " rights:" << collection.rights() << endl << " contents mime type:" << collection.contentMimeTypes() << endl << " isVirtual:" << collection.isVirtual() << endl << " " << collection.cachePolicy() << endl << " " << collection.statistics(); } CollectionStatistics Collection::statistics() const { return d_ptr->statistics; } void Collection::setStatistics(const CollectionStatistics &statistics) { d_ptr->statistics = statistics; } CachePolicy Collection::cachePolicy() const { return d_ptr->cachePolicy; } void Collection::setCachePolicy(const CachePolicy &cachePolicy) { d_ptr->cachePolicy = cachePolicy; d_ptr->cachePolicyChanged = true; } bool Collection::isVirtual() const { return d_ptr->isVirtual; } void Akonadi::Collection::setVirtual(bool isVirtual) { d_ptr->isVirtual = isVirtual; } void Collection::setEnabled(bool enabled) { d_ptr->enabledChanged = true; d_ptr->enabled = enabled; } bool Collection::enabled() const { return d_ptr->enabled; } void Collection::setLocalListPreference(Collection::ListPurpose purpose, Collection::ListPreference preference) { switch (purpose) { case ListDisplay: d_ptr->displayPreference = preference; break; case ListSync: d_ptr->syncPreference = preference; break; case ListIndex: d_ptr->indexPreference = preference; break; } d_ptr->listPreferenceChanged = true; } Collection::ListPreference Collection::localListPreference(Collection::ListPurpose purpose) const { switch (purpose) { case ListDisplay: return d_ptr->displayPreference; case ListSync: return d_ptr->syncPreference; case ListIndex: return d_ptr->indexPreference; } return ListDefault; } bool Collection::shouldList(Collection::ListPurpose purpose) const { if (localListPreference(purpose) == ListDefault) { return enabled() || referenced(); } return (localListPreference(purpose) == ListEnabled); } void Collection::setShouldList(ListPurpose purpose, bool list) { if (localListPreference(purpose) == ListDefault) { setEnabled(list); } else { setLocalListPreference(purpose, list ? ListEnabled : ListDisabled); } } void Collection::setReferenced(bool referenced) { d_ptr->referencedChanged = true; d_ptr->referenced = referenced; } bool Collection::referenced() const { return d_ptr->referenced; } void Collection::setKeepLocalChanges(const QSet &parts) { d_ptr->keepLocalChanges = parts; } QSet Collection::keepLocalChanges() const { return d_ptr->keepLocalChanges; } void Collection::markAttributeModified(const QByteArray &type) { d_ptr->mAttributeStorage.markAttributeModified(type); } diff --git a/src/core/collection.h b/src/core/collection.h index 8a51eb0eb..f886ed083 100644 --- a/src/core/collection.h +++ b/src/core/collection.h @@ -1,632 +1,626 @@ /* 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 * 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 * 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 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 + 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, a new one - * is created and added to the entity. + * 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); + inline T *attribute(CreateOption option = DontCreate); /** * Returns the attribute of the requested type or 0 if it is not available. */ template - inline T *attribute() const; + 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); /** * Sets a collection to be referenced. * * A referenced collection is temporarily shown and synchronized even when disabled. * A reference is only valid for the duration of a session, and is automatically removed afterwards. * * Referenced collections are only visible if explicitly monitored in the ETM. * * @since 4.14 */ void setReferenced(bool referenced); /** * Returns the referenced state of the collection. * @since 4.14 */ Q_REQUIRED_RESULT bool referenced() const; /** * 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) { - Q_UNUSED(option); - const QByteArray type = T().type(); - markAttributeModified(type); // do this first in case it detaches if (hasAttribute(type)) { - T *attr = dynamic_cast(attribute(type)); - if (attr) { + if (T *attr = dynamic_cast(attribute(type))) { return attr; } - //Reuse 5250 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; } - T *attr = new T(); - addAttribute(attr); - return attr; + return nullptr; } template -inline T *Akonadi::Collection::attribute() const +inline const T *Akonadi::Collection::attribute() const { const QByteArray type = T().type(); if (hasAttribute(type)) { - T *attr = dynamic_cast(attribute(type)); - if (attr) { - // FIXME: This method returns a non-const pointer, so callers may still modify the - // attribute. Unfortunately, just making this function return a const pointer and - // creating a non-const overload does not work, as many users of this function abuse the - // non-const pointer and modify the attribute even on a const object. - const_cast(this)->markAttributeModified(type); + if (const T *attr = dynamic_cast(attribute(type))) { return attr; } - //reuse 5250 qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } return nullptr; } template inline void Akonadi::Collection::removeAttribute() { - const T dummy; - removeAttribute(dummy.type()); + removeAttribute(T().type()); } template inline bool Akonadi::Collection::hasAttribute() const { - const T dummy; - return hasAttribute(dummy.type()); + 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 ffdfbf706..055f7d03c 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 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. - Attribute *localAttr = local.attribute(remoteAttr->type()); + 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/item.cpp b/src/core/item.cpp index be6a3c32c..7f676da6e 100644 --- a/src/core/item.cpp +++ b/src/core/item.cpp @@ -1,745 +1,746 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "item.h" #include "item_p.h" #include "akonadicore_debug.h" #include "itemserializer_p.h" #include "private/protocol_p.h" #include #include #include #include #include #include #include using namespace Akonadi; Q_GLOBAL_STATIC(Akonadi::Collection, s_defaultParentCollection) uint Akonadi::qHash(const Akonadi::Item &item) { return ::qHash(item.id()); } namespace { struct ByTypeId { typedef bool result_type; bool operator()(const std::shared_ptr &lhs, const std::shared_ptr &rhs) const { return strcmp(lhs->typeName(), rhs->typeName()) < 0; } }; } // anon namespace typedef QHash, std::pair, ByTypeId>> LegacyMap; Q_GLOBAL_STATIC(LegacyMap, typeInfoToMetaTypeIdMap) Q_GLOBAL_STATIC_WITH_ARGS(QReadWriteLock, legacyMapLock, (QReadWriteLock::Recursive)) void Item::addToLegacyMappingImpl(const QString &mimeType, int spid, int mtid, std::unique_ptr &p) { if (!p.get()) { return; } const std::shared_ptr sp(p.release()); const QWriteLocker locker(legacyMapLock()); std::pair &item = (*typeInfoToMetaTypeIdMap())[mimeType][sp]; item.first = spid; item.second = mtid; } namespace { class MyReadLocker { public: explicit MyReadLocker(QReadWriteLock *rwl) : rwl(rwl) , locked(false) { if (rwl) { rwl->lockForRead(); } locked = true; } ~MyReadLocker() { if (rwl && locked) { rwl->unlock(); } } template std::shared_ptr makeUnlockingPointer(T *t) { if (t) { // the bind() doesn't throw, so if shared_ptr // construction line below, or anything else after it, // throws, we're unlocked. Mark us as such: locked = false; const std::shared_ptr result(t, [&](const void *) { rwl->unlock(); }); // from now on, the shared_ptr is responsible for unlocking return result; } else { return std::shared_ptr(); } } private: Q_DISABLE_COPY(MyReadLocker) QReadWriteLock *const rwl; bool locked; }; } static std::shared_ptr> lookupLegacyMapping(const QString &mimeType, Internal::PayloadBase *p) { MyReadLocker locker(legacyMapLock()); const LegacyMap::const_iterator hit = typeInfoToMetaTypeIdMap()->constFind(mimeType); if (hit == typeInfoToMetaTypeIdMap()->constEnd()) { return std::shared_ptr>(); } const std::shared_ptr sp(p, [ = ](Internal::PayloadBase *) { /*noop*/ }); const LegacyMap::mapped_type::const_iterator it = hit->find(sp); if (it == hit->end()) { return std::shared_ptr>(); } return locker.makeUnlockingPointer(&it->second); } // Change to something != RFC822 as soon as the server supports it const char Item::FullPayload[] = "RFC822"; Item::Item() : d_ptr(new ItemPrivate) { } Item::Item(Id id) : d_ptr(new ItemPrivate(id)) { } Item::Item(const QString &mimeType) : d_ptr(new ItemPrivate) { d_ptr->mMimeType = mimeType; } Item::Item(const Item &other) : d_ptr(other.d_ptr) { } Item::~Item() { } void Item::setId(Item::Id identifier) { d_ptr->mId = identifier; } Item::Id Item::id() const { return d_ptr->mId; } void Item::setRemoteId(const QString &id) { d_ptr->mRemoteId = id; } QString Item::remoteId() const { return d_ptr->mRemoteId; } void Item::setRemoteRevision(const QString &revision) { d_ptr->mRemoteRevision = revision; } QString Item::remoteRevision() const { return d_ptr->mRemoteRevision; } bool Item::isValid() const { return (d_ptr->mId >= 0); } bool Item::operator==(const Item &other) const { // Invalid collections are the same, no matter what their internal ID is return (!isValid() && !other.isValid()) || (d_ptr->mId == other.d_ptr->mId); } bool Akonadi::Item::operator!=(const Item &other) const { return (isValid() || other.isValid()) && (d_ptr->mId != other.d_ptr->mId); } Item &Item ::operator=(const Item &other) { if (this != &other) { d_ptr = other.d_ptr; } return *this; } bool Akonadi::Item::operator<(const Item &other) const { return d_ptr->mId < other.d_ptr->mId; } void Item::addAttribute(Attribute *attr) { ItemChangeLog::instance()->attributeStorage(d_ptr).addAttribute(attr); } void Item::removeAttribute(const QByteArray &type) { ItemChangeLog::instance()->attributeStorage(d_ptr).removeAttribute(type); } bool Item::hasAttribute(const QByteArray &type) const { return ItemChangeLog::instance()->attributeStorage(d_ptr).hasAttribute(type); } Attribute::List Item::attributes() const { return ItemChangeLog::instance()->attributeStorage(d_ptr).attributes(); } void Akonadi::Item::clearAttributes() { ItemChangeLog::instance()->attributeStorage(d_ptr).clearAttributes(); } -Attribute *Item::attribute(const QByteArray &type) const +Attribute *Item::attribute(const QByteArray &type) { - // FIXME: Createa a truly const and non-const overloads of this method - // so only non-const access marks the attribute as modified - auto &storage = ItemChangeLog::instance()->attributeStorage(d_ptr); - storage.markAttributeModified(type); - return storage.attribute(type); + return ItemChangeLog::instance()->attributeStorage(d_ptr).attribute(type); +} + +const Attribute *Item::attribute(const QByteArray &type) const +{ + return ItemChangeLog::instance()->attributeStorage(d_ptr).attribute(type); } Collection &Item::parentCollection() { if (!d_ptr->mParent) { d_ptr->mParent = new Collection(); } return *(d_ptr->mParent); } Collection Item::parentCollection() const { if (!d_ptr->mParent) { return *(s_defaultParentCollection); } else { return *(d_ptr->mParent); } } void Item::setParentCollection(const Collection &parent) { delete d_ptr->mParent; d_ptr->mParent = new Collection(parent); } Item::Flags Item::flags() const { return d_ptr->mFlags; } void Item::setFlag(const QByteArray &name) { d_ptr->mFlags.insert(name); if (!d_ptr->mFlagsOverwritten) { Item::Flags &deletedFlags = ItemChangeLog::instance()->deletedFlags(d_ptr); auto iter = deletedFlags.find(name); if (iter != deletedFlags.end()) { deletedFlags.erase(iter); } else { ItemChangeLog::instance()->addedFlags(d_ptr).insert(name); } } } void Item::clearFlag(const QByteArray &name) { d_ptr->mFlags.remove(name); if (!d_ptr->mFlagsOverwritten) { Item::Flags &addedFlags = ItemChangeLog::instance()->addedFlags(d_ptr); auto iter = addedFlags.find(name); if (iter != addedFlags.end()) { addedFlags.erase(iter); } else { ItemChangeLog::instance()->deletedFlags(d_ptr).insert(name); } } } void Item::setFlags(const Flags &flags) { d_ptr->mFlags = flags; d_ptr->mFlagsOverwritten = true; } void Item::clearFlags() { d_ptr->mFlags.clear(); d_ptr->mFlagsOverwritten = true; } QDateTime Item::modificationTime() const { return d_ptr->mModificationTime; } void Item::setModificationTime(const QDateTime &datetime) { d_ptr->mModificationTime = datetime; } bool Item::hasFlag(const QByteArray &name) const { return d_ptr->mFlags.contains(name); } void Item::setTags(const Tag::List &list) { d_ptr->mTags = list; d_ptr->mTagsOverwritten = true; } void Item::setTag(const Tag &tag) { d_ptr->mTags << tag; if (!d_ptr->mTagsOverwritten) { Tag::List &deletedTags = ItemChangeLog::instance()->deletedTags(d_ptr); if (deletedTags.contains(tag)) { deletedTags.removeOne(tag); } else { ItemChangeLog::instance()->addedTags(d_ptr).push_back(tag); } } } void Item::clearTags() { d_ptr->mTags.clear(); d_ptr->mTagsOverwritten = true; } void Item::clearTag(const Tag &tag) { d_ptr->mTags.removeOne(tag); if (!d_ptr->mTagsOverwritten) { Tag::List &addedTags = ItemChangeLog::instance()->addedTags(d_ptr); if (addedTags.contains(tag)) { addedTags.removeOne(tag); } else { ItemChangeLog::instance()->deletedTags(d_ptr).push_back(tag); } } } bool Item::hasTag(const Tag &tag) const { return d_ptr->mTags.contains(tag); } Tag::List Item::tags() const { return d_ptr->mTags; } Relation::List Item::relations() const { return d_ptr->mRelations; } QSet Item::loadedPayloadParts() const { return ItemSerializer::parts(*this); } QByteArray Item::payloadData() const { int version = 0; QByteArray data; ItemSerializer::serialize(*this, FullPayload, data, version); return data; } void Item::setPayloadFromData(const QByteArray &data) { ItemSerializer::deserialize(*this, FullPayload, data, 0, ItemSerializer::Internal); } void Item::clearPayload() { d_ptr->mClearPayload = true; } int Item::revision() const { return d_ptr->mRevision; } void Item::setRevision(int rev) { d_ptr->mRevision = rev; } Collection::Id Item::storageCollectionId() const { return d_ptr->mCollectionId; } void Item::setStorageCollectionId(Collection::Id collectionId) { d_ptr->mCollectionId = collectionId; } QString Item::mimeType() const { return d_ptr->mMimeType; } void Item::setSize(qint64 size) { d_ptr->mSize = size; d_ptr->mSizeChanged = true; } qint64 Item::size() const { return d_ptr->mSize; } void Item::setMimeType(const QString &mimeType) { d_ptr->mMimeType = mimeType; } void Item::setGid(const QString &id) { d_ptr->mGid = id; } QString Item::gid() const { return d_ptr->mGid; } void Item::setVirtualReferences(const Collection::List &collections) { d_ptr->mVirtualReferences = collections; } Collection::List Item::virtualReferences() const { return d_ptr->mVirtualReferences; } bool Item::hasPayload() const { return d_ptr->hasMetaTypeId(-1); } QUrl Item::url(UrlType type) const { QUrlQuery query; query.addQueryItem(QStringLiteral("item"), QString::number(id())); if (type == UrlWithMimeType) { query.addQueryItem(QStringLiteral("type"), mimeType()); } QUrl url; url.setScheme(QStringLiteral("akonadi")); url.setQuery(query); return url; } Item Item::fromUrl(const QUrl &url) { if (url.scheme() != QLatin1String("akonadi")) { return Item(); } const QString itemStr = QUrlQuery(url).queryItemValue(QStringLiteral("item")); bool ok = false; Item::Id itemId = itemStr.toLongLong(&ok); if (!ok) { return Item(); } return Item(itemId); } namespace { class Dummy { }; } Q_GLOBAL_STATIC(Internal::Payload, dummyPayload) Internal::PayloadBase *Item::payloadBase() const { d_ptr->tryEnsureLegacyPayload(); if (d_ptr->mLegacyPayload) { return d_ptr->mLegacyPayload.get(); } else { return dummyPayload(); } } void ItemPrivate::tryEnsureLegacyPayload() const { if (!mLegacyPayload) { for (PayloadContainer::const_iterator it = mPayloads.begin(), end = mPayloads.end(); it != end; ++it) { if (lookupLegacyMapping(mMimeType, it->payload.get())) { mLegacyPayload = it->payload; // clones } } } } Internal::PayloadBase *Item::payloadBaseV2(int spid, int mtid) const { return d_ptr->payloadBaseImpl(spid, mtid); } namespace { class ConversionGuard { const bool old; bool &b; public: explicit ConversionGuard(bool &b) : old(b) , b(b) { b = true; } ~ConversionGuard() { b = old; } private: Q_DISABLE_COPY(ConversionGuard) }; } bool Item::ensureMetaTypeId(int mtid) const { // 0. Nothing there - nothing to convert from, either if (d_ptr->mPayloads.empty()) { return false; } // 1. Look whether we already have one: if (d_ptr->hasMetaTypeId(mtid)) { return true; } // recursion detection (shouldn't trigger, but does if the // serialiser plugins are acting funky): if (d_ptr->mConversionInProgress) { return false; } // 2. Try to create one by conversion from a different representation: try { const ConversionGuard guard(d_ptr->mConversionInProgress); Item converted = ItemSerializer::convert(*this, mtid); return d_ptr->movePayloadFrom(converted.d_ptr, mtid); } catch (const std::exception &e) { qCDebug(AKONADICORE_LOG) << "conversion threw:" << e.what(); return false; } catch (...) { qCDebug(AKONADICORE_LOG) << "conversion threw something not derived from std::exception: fix the program!"; return false; } } static QString format_type(int spid, int mtid) { return QStringLiteral("sp(%1)<%2>") .arg(spid).arg(QLatin1String(QMetaType::typeName(mtid))); } static QString format_types(const PayloadContainer &c) { QStringList result; result.reserve(c.size()); for (PayloadContainer::const_iterator it = c.begin(), end = c.end(); it != end; ++it) { result.push_back(format_type(it->sharedPointerId, it->metaTypeId)); } return result.join(QStringLiteral(", ")); } #if 0 QString Item::payloadExceptionText(int spid, int mtid) const { if (d_ptr->mPayloads.empty()) { return QStringLiteral("No payload set"); } else { return QStringLiteral("Wrong payload type (requested: %1; present: %2") .arg(format_type(spid, mtid), format_types(d_ptr->mPayloads)); } } #else void Item::throwPayloadException(int spid, int mtid) const { if (d_ptr->mPayloads.empty()) { qCDebug(AKONADICORE_LOG) << "Throwing PayloadException: No payload set"; throw PayloadException("No payload set"); } else { qCDebug(AKONADICORE_LOG) << "Throwing PayloadException: Wrong payload type (requested:" << format_type(spid, mtid) << "; present: " << format_types(d_ptr->mPayloads) << "), item mime type is" << mimeType(); throw PayloadException(QStringLiteral("Wrong payload type (requested: %1; present: %2)") .arg(format_type(spid, mtid), format_types(d_ptr->mPayloads))); } } #endif void Item::setPayloadBase(Internal::PayloadBase *p) { d_ptr->setLegacyPayloadBaseImpl(std::unique_ptr(p)); } void ItemPrivate::setLegacyPayloadBaseImpl(std::unique_ptr p) { if (const std::shared_ptr> pair = lookupLegacyMapping(mMimeType, p.get())) { std::unique_ptr clone; if (p.get()) { clone.reset(p->clone()); } setPayloadBaseImpl(pair->first, pair->second, p, false); mLegacyPayload.reset(clone.release()); } else { mPayloads.clear(); mLegacyPayload.reset(p.release()); } } void Item::setPayloadBaseV2(int spid, int mtid, std::unique_ptr &p) { d_ptr->setPayloadBaseImpl(spid, mtid, p, false); } void Item::addPayloadBaseVariant(int spid, int mtid, std::unique_ptr &p) const { d_ptr->setPayloadBaseImpl(spid, mtid, p, true); } QSet Item::cachedPayloadParts() const { return d_ptr->mCachedPayloadParts; } void Item::setCachedPayloadParts(const QSet &cachedParts) { d_ptr->mCachedPayloadParts = cachedParts; } QSet Item::availablePayloadParts() const { return ItemSerializer::availableParts(*this); } QVector Item::availablePayloadMetaTypeIds() const { QVector result; result.reserve(d_ptr->mPayloads.size()); // Stable Insertion Sort - N is typically _very_ low (1 or 2). for (PayloadContainer::const_iterator it = d_ptr->mPayloads.begin(), end = d_ptr->mPayloads.end(); it != end; ++it) { result.insert(std::upper_bound(result.begin(), result.end(), it->metaTypeId), it->metaTypeId); } return result; } void Item::setPayloadPath(const QString &filePath) { // Load payload from the external file, so that it's accessible via // Item::payload(). It internally calls setPayload(), which will clear // mPayloadPath, so we call it afterwards ItemSerializer::deserialize(*this, "RFC822", filePath.toUtf8(), 0, ItemSerializer::Foreign); d_ptr->mPayloadPath = filePath; } QString Item::payloadPath() const { return d_ptr->mPayloadPath; } void Item::apply(const Item &other) { if (mimeType() != other.mimeType() || id() != other.id()) { qCDebug(AKONADICORE_LOG) << "mimeType() = " << mimeType() << "; other.mimeType() = " << other.mimeType(); qCDebug(AKONADICORE_LOG) << "id() = " << id() << "; other.id() = " << other.id(); Q_ASSERT_X(false, "Item::apply", "mimetype or id missmatch"); } setRemoteId(other.remoteId()); setRevision(other.revision()); setRemoteRevision(other.remoteRevision()); setFlags(other.flags()); setTags(other.tags()); setModificationTime(other.modificationTime()); setSize(other.size()); setParentCollection(other.parentCollection()); setStorageCollectionId(other.storageCollectionId()); ItemChangeLog *changelog = ItemChangeLog::instance(); changelog->attributeStorage(d_ptr) = changelog->attributeStorage(other.d_ptr); ItemSerializer::apply(*this, other); d_ptr->resetChangeLog(); // Must happen after payload update d_ptr->mPayloadPath = other.payloadPath(); } diff --git a/src/core/item.h b/src/core/item.h index 7999af3fe..d347e3ad2 100644 --- a/src/core/item.h +++ b/src/core/item.h @@ -1,1090 +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 * 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 * 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 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 + 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); + inline T *attribute(CreateOption option = DontCreate); /** * Returns the attribute of the requested type or 0 if it is not available. */ template - inline T *attribute() const; + 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) { - Q_UNUSED(option); - - const T dummy; - if (hasAttribute(dummy.type())) { - T *attr = dynamic_cast(attribute(dummy.type())); - if (attr) { + const QByteArray type = T().type(); + if (hasAttribute(type)) { + if (T *attr = dynamic_cast(attribute(type))) { return attr; } - //Reuse 5250 - qWarning() << "Found attribute of unknown type" << dummy.type() + 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; } - T *attr = new T(); - addAttribute(attr); - return attr; + return nullptr; } template -inline T *Item::attribute() const +inline const T *Item::attribute() const { - const T dummy; - if (hasAttribute(dummy.type())) { - T *attr = dynamic_cast(attribute(dummy.type())); - if (attr) { - + const QByteArray type = T().type(); + if (hasAttribute(type)) { + if (const T *attr = dynamic_cast(attribute(type))) { return attr; } - //reuse 5250 - qWarning() << "Found attribute of unknown type" << dummy.type() + qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } return nullptr; } template inline void Item::removeAttribute() { - const T dummy; - removeAttribute(dummy.type()); + removeAttribute(T().type()); } template inline bool Item::hasAttribute() const { - const T dummy; - return hasAttribute(dummy.type()); + 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/itemchangelog.cpp b/src/core/itemchangelog.cpp index a3e300287..5059b6aec 100644 --- a/src/core/itemchangelog.cpp +++ b/src/core/itemchangelog.cpp @@ -1,83 +1,88 @@ /* * Copyright 2015 Daniel Vrátil * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see . * */ #include "itemchangelog_p.h" using namespace Akonadi; ItemChangeLog *ItemChangeLog::sInstance = nullptr; ItemChangeLog *ItemChangeLog::instance() { if (!sInstance) { sInstance = new ItemChangeLog; } return sInstance; } ItemChangeLog::ItemChangeLog() { } Item::Flags &ItemChangeLog::addedFlags(const ItemPrivate *priv) { return m_addedFlags[const_cast(priv)]; } Item::Flags &ItemChangeLog::deletedFlags(const ItemPrivate *priv) { return m_deletedFlags[const_cast(priv)]; } Tag::List &ItemChangeLog::addedTags(const ItemPrivate *priv) { return m_addedTags[const_cast(priv)]; } Tag::List &ItemChangeLog::deletedTags(const ItemPrivate *priv) { return m_deletedTags[const_cast(priv)]; } -AttributeStorage &ItemChangeLog::attributeStorage(const ItemPrivate *priv) +AttributeStorage &ItemChangeLog::attributeStorage(ItemPrivate *priv) +{ + return m_attributeStorage[priv]; +} + +const AttributeStorage &ItemChangeLog::attributeStorage(const ItemPrivate *priv) { return m_attributeStorage[const_cast(priv)]; } void ItemChangeLog::removeItem(const ItemPrivate *priv) { ItemPrivate *p = const_cast(priv); m_addedFlags.remove(p); m_deletedFlags.remove(p); m_addedTags.remove(p); m_deletedTags.remove(p); m_attributeStorage.remove(p); } void ItemChangeLog::clearItemChangelog(const ItemPrivate *priv) { ItemPrivate *p = const_cast(priv); m_addedFlags.remove(p); m_deletedFlags.remove(p); m_addedTags.remove(p); m_deletedTags.remove(p); m_attributeStorage[p].resetChangeLog(); // keep the attributes } diff --git a/src/core/itemchangelog_p.h b/src/core/itemchangelog_p.h index 78c6c2a3a..34f201c0c 100644 --- a/src/core/itemchangelog_p.h +++ b/src/core/itemchangelog_p.h @@ -1,63 +1,64 @@ /* * Copyright 2015 Daniel Vrátil * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see . * */ #ifndef ITEMCHANGELOG_H #define ITEMCHANGELOG_H #include "item.h" #include "akonaditests_export.h" #include "attributestorage_p.h" namespace Akonadi { class AKONADI_TESTS_EXPORT ItemChangeLog { public: static ItemChangeLog *instance(); Item::Flags &addedFlags(const ItemPrivate *priv); Item::Flags &deletedFlags(const ItemPrivate *priv); Tag::List &addedTags(const ItemPrivate *priv); Tag::List &deletedTags(const ItemPrivate *priv); - AttributeStorage &attributeStorage(const ItemPrivate *priv); + const AttributeStorage &attributeStorage(const ItemPrivate *priv); + AttributeStorage &attributeStorage(ItemPrivate *priv); void removeItem(const ItemPrivate *priv); void clearItemChangelog(const ItemPrivate *priv); private: explicit ItemChangeLog(); static ItemChangeLog *sInstance; QHash m_addedFlags; QHash m_deletedFlags; QHash m_addedTags; QHash m_deletedTags; QHash m_attributeStorage; }; } // namespace Akonadi #endif // ITEMCHANGELOG_H diff --git a/src/core/models/entitytreemodel.cpp b/src/core/models/entitytreemodel.cpp index 606e2d330..da7f459e5 100644 --- a/src/core/models/entitytreemodel.cpp +++ b/src/core/models/entitytreemodel.cpp @@ -1,1231 +1,1231 @@ /* 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.h" #include "entitytreemodel_p.h" #include "akonadicore_debug.h" #include "monitor_p.h" #include #include #include #include #include #include #include #include "attributefactory.h" #include "monitor.h" #include "collectionmodifyjob.h" #include "entitydisplayattribute.h" #include "transactionsequence.h" #include "itemmodifyjob.h" #include "session.h" #include "collectionfetchscope.h" #include "collectionutils.h" #include "pastehelper_p.h" Q_DECLARE_METATYPE(QSet) using namespace Akonadi; EntityTreeModel::EntityTreeModel(Monitor *monitor, QObject *parent) : QAbstractItemModel(parent) , d_ptr(new EntityTreeModelPrivate(this)) { Q_D(EntityTreeModel); d->init(monitor); } EntityTreeModel::EntityTreeModel(Monitor *monitor, EntityTreeModelPrivate *d, QObject *parent) : QAbstractItemModel(parent) , d_ptr(d) { d->init(monitor); } EntityTreeModel::~EntityTreeModel() { Q_D(EntityTreeModel); for (const QList &list : qAsConst(d->m_childEntities)) { QList::const_iterator it = list.constBegin(); const QList::const_iterator end = list.constEnd(); for (; it != end; ++it) { delete *it; } } delete d_ptr; } CollectionFetchScope::ListFilter EntityTreeModel::listFilter() const { Q_D(const EntityTreeModel); return d->m_listFilter; } void EntityTreeModel::setListFilter(CollectionFetchScope::ListFilter filter) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_listFilter = filter; d->m_monitor->setAllMonitored(filter == CollectionFetchScope::NoFilter); d->endResetModel(); } void EntityTreeModel::setCollectionsMonitored(const Collection::List &collections) { Q_D(EntityTreeModel); d->beginResetModel(); const Akonadi::Collection::List lstCols = d->m_monitor->collectionsMonitored(); for (const Akonadi::Collection &col : lstCols) { d->m_monitor->setCollectionMonitored(col, false); } for (const Akonadi::Collection &col : collections) { d->m_monitor->setCollectionMonitored(col, true); } d->endResetModel(); } void EntityTreeModel::setCollectionMonitored(const Collection &col, bool monitored) { Q_D(EntityTreeModel); d->m_monitor->setCollectionMonitored(col, monitored); } void EntityTreeModel::setCollectionReferenced(const Akonadi::Collection &col, bool referenced) { Q_D(EntityTreeModel); Akonadi::Collection referencedCollection = col; referencedCollection.setReferenced(referenced); //We have to use the same session as the monitor, so the monitor can fetch the collection afterwards new Akonadi::CollectionModifyJob(referencedCollection, d->m_monitor->session()); } bool EntityTreeModel::systemEntitiesShown() const { Q_D(const EntityTreeModel); return d->m_showSystemEntities; } void EntityTreeModel::setShowSystemEntities(bool show) { Q_D(EntityTreeModel); d->m_showSystemEntities = show; } void EntityTreeModel::clearAndReset() { Q_D(EntityTreeModel); d->beginResetModel(); d->endResetModel(); } QHash EntityTreeModel::roleNames() const { QHash names = QAbstractItemModel::roleNames(); names.insert(EntityTreeModel::UnreadCountRole, "unreadCount"); names.insert(EntityTreeModel::FetchStateRole, "fetchState"); names.insert(EntityTreeModel::ItemIdRole, "itemId"); return names; } int EntityTreeModel::columnCount(const QModelIndex &parent) const { // TODO: Statistics? if (parent.isValid() && parent.column() != 0) { return 0; } return qMax(entityColumnCount(CollectionTreeHeaders), entityColumnCount(ItemListHeaders)); } QVariant EntityTreeModel::entityData(const Item &item, int column, int role) const { Q_D(const EntityTreeModel); if (column == 0) { switch (role) { case Qt::DisplayRole: case Qt::EditRole: if (item.hasAttribute() && !item.attribute()->displayName().isEmpty()) { return item.attribute()->displayName(); } else { if (!item.remoteId().isEmpty()) { return item.remoteId(); } return QString(QLatin1Char('<') + QString::number(item.id()) + QLatin1Char('>')); } break; case Qt::DecorationRole: if (item.hasAttribute() && !item.attribute()->iconName().isEmpty()) { return d->iconForName(item.attribute()->iconName()); } break; default: break; } } return QVariant(); } QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const { Q_D(const EntityTreeModel); if (column > 0) { return QString(); } if (collection == Collection::root()) { // Only display the root collection. It may not be edited. if (role == Qt::DisplayRole) { return d->m_rootCollectionDisplayName; } if (role == Qt::EditRole) { return QVariant(); } } switch (role) { case Qt::DisplayRole: case Qt::EditRole: if (column == 0) { const QString displayName = collection.displayName(); if (!displayName.isEmpty()) { return displayName; } else { return i18n("Loading..."); } } break; case Qt::DecorationRole: if (collection.hasAttribute() && !collection.attribute()->iconName().isEmpty()) { return d->iconForName(collection.attribute()->iconName()); } return d->iconForName(CollectionUtils::defaultIconName(collection)); default: break; } return QVariant(); } QVariant EntityTreeModel::data(const QModelIndex &index, int role) const { Q_D(const EntityTreeModel); if (role == SessionRole) { return QVariant::fromValue(qobject_cast(d->m_session)); } // Ugly, but at least the API is clean. const HeaderGroup headerGroup = static_cast((role / static_cast(TerminalUserRole))); role %= TerminalUserRole; if (!index.isValid()) { if (ColumnCountRole != role) { return QVariant(); } return entityColumnCount(headerGroup); } if (ColumnCountRole == role) { return entityColumnCount(headerGroup); } const Node *node = reinterpret_cast(index.internalPointer()); if (ParentCollectionRole == role && d->m_collectionFetchStrategy != FetchNoCollections) { const Collection parentCollection = d->m_collections.value(node->parent); Q_ASSERT(parentCollection.isValid()); return QVariant::fromValue(parentCollection); } if (Node::Collection == node->type) { const Collection collection = d->m_collections.value(node->id); if (!collection.isValid()) { return QVariant(); } switch (role) { case MimeTypeRole: return collection.mimeType(); break; case RemoteIdRole: return collection.remoteId(); break; case CollectionIdRole: return collection.id(); break; case ItemIdRole: // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole // and CollectionIdRole (below) specially return -1; break; case CollectionRole: return QVariant::fromValue(collection); break; case EntityUrlRole: return collection.url().url(); break; case UnreadCountRole: { CollectionStatistics statistics = collection.statistics(); return statistics.unreadCount(); } case FetchStateRole: { return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState; } case IsPopulatedRole: { return d->m_populatedCols.contains(collection.id()); } case OriginalCollectionNameRole: { return entityData(collection, index.column(), Qt::DisplayRole); } case PendingCutRole: return d->m_pendingCutCollections.contains(node->id); case Qt::BackgroundRole: { if (collection.hasAttribute()) { - EntityDisplayAttribute *eda = collection.attribute(); + const EntityDisplayAttribute *eda = collection.attribute(); QColor color = eda->backgroundColor(); if (color.isValid()) { return color; } } // fall through. Q_FALLTHROUGH(); } default: return entityData(collection, index.column(), role); break; } } else if (Node::Item == node->type) { const Item item = d->m_items.value(node->id); if (!item.isValid()) { return QVariant(); } switch (role) { case ParentCollectionRole: return QVariant::fromValue(item.parentCollection()); case MimeTypeRole: return item.mimeType(); break; case RemoteIdRole: return item.remoteId(); break; case ItemRole: return QVariant::fromValue(item); break; case ItemIdRole: return item.id(); break; case CollectionIdRole: return -1; break; case LoadedPartsRole: return QVariant::fromValue(item.loadedPayloadParts()); break; case AvailablePartsRole: return QVariant::fromValue(item.availablePayloadParts()); break; case EntityUrlRole: return item.url(Akonadi::Item::UrlWithMimeType).url(); break; case PendingCutRole: return d->m_pendingCutItems.contains(node->id); case Qt::BackgroundRole: { if (item.hasAttribute()) { - EntityDisplayAttribute *eda = item.attribute(); + const EntityDisplayAttribute *eda = item.attribute(); const QColor color = eda->backgroundColor(); if (color.isValid()) { return color; } } // fall through. Q_FALLTHROUGH(); } default: return entityData(item, index.column(), role); break; } } return QVariant(); } Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const { Q_D(const EntityTreeModel); // Pass modeltest. if (!index.isValid()) { return {}; } Qt::ItemFlags flags = QAbstractItemModel::flags(index); const Node *node = reinterpret_cast(index.internalPointer()); if (Node::Collection == node->type) { const Collection collection = d->m_collections.value(node->id); if (collection.isValid()) { if (collection == Collection::root()) { // Selectable and displayable only. return flags; } const int rights = collection.rights(); if (rights & Collection::CanChangeCollection) { if (index.column() == 0) { flags |= Qt::ItemIsEditable; } // Changing the collection includes changing the metadata (child entityordering). // Need to allow this by drag and drop. flags |= Qt::ItemIsDropEnabled; } if (rights & (Collection::CanCreateCollection | Collection::CanCreateItem | Collection::CanLinkItem)) { // Can we drop new collections and items into this collection? flags |= Qt::ItemIsDropEnabled; } // dragging is always possible, even for read-only objects, but they can only be copied, not moved. flags |= Qt::ItemIsDragEnabled; } } else if (Node::Item == node->type) { // cut out entities are shown as disabled // TODO: Not sure this is wanted, it prevents any interaction with them, better // solution would be to move this to the delegate, as was done for collections. if (d->m_pendingCutItems.contains(node->id)) { return Qt::ItemIsSelectable; } // Rights come from the parent collection. Collection parentCollection; if (!index.parent().isValid()) { parentCollection = d->m_rootCollection; } else { const Node *parentNode = reinterpret_cast(index.parent().internalPointer()); parentCollection = d->m_collections.value(parentNode->id); } if (parentCollection.isValid()) { const int rights = parentCollection.rights(); // Can't drop onto items. if (rights & Collection::CanChangeItem && index.column() == 0) { flags |= Qt::ItemIsEditable; } // dragging is always possible, even for read-only objects, but they can only be copied, not moved. flags |= Qt::ItemIsDragEnabled; } } return flags; } Qt::DropActions EntityTreeModel::supportedDropActions() const { return (Qt::CopyAction | Qt::MoveAction | Qt::LinkAction); } QStringList EntityTreeModel::mimeTypes() const { // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example. return {QStringLiteral("text/uri-list")}; } bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); Q_D(EntityTreeModel); // Can't drop onto Collection::root. if (!parent.isValid()) { return false; } // TODO Use action and collection rights and return false if necessary // if row and column are -1, then the drop was on parent directly. // data should then be appended on the end of the items of the collections as appropriate. // That will mean begin insert rows etc. // Otherwise it was a sibling of the row^th item of parent. // Needs to be handled when ordering is accounted for. // Handle dropping between items as well as on items. // if ( row != -1 && column != -1 ) // { // } if (action == Qt::IgnoreAction) { return true; } // Shouldn't do this. Need to be able to drop vcards for example. // if ( !data->hasFormat( "text/uri-list" ) ) // return false; Node *node = reinterpret_cast(parent.internalId()); Q_ASSERT(node); if (Node::Item == node->type) { if (!parent.parent().isValid()) { // The drop is somehow on an item with no parent (shouldn't happen) // The drop should be considered handled anyway. qCWarning(AKONADICORE_LOG) << "Dropped onto item with no parent collection"; return true; } // A drop onto an item should be considered as a drop onto its parent collection node = reinterpret_cast(parent.parent().internalId()); } if (Node::Collection == node->type) { const Collection destCollection = d->m_collections.value(node->id); // Applications can't create new collections in root. Only resources can. if (destCollection == Collection::root()) { // Accept the event so that it doesn't propagate. return true; } if (data->hasFormat(QStringLiteral("text/uri-list"))) { MimeTypeChecker mimeChecker; mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes()); const QList urls = data->urls(); for (const QUrl &url : urls) { const Collection collection = d->m_collections.value(Collection::fromUrl(url).id()); if (collection.isValid()) { if (collection.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same."; return false; } if (!mimeChecker.isWantedCollection(collection)) { qCDebug(AKONADICORE_LOG) << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes(); return false; } QUrlQuery query(url); if (query.hasQueryItem(QStringLiteral("name"))) { const QString collectionName = query.queryItemValue(QStringLiteral("name")); const QStringList collectionNames = d->childCollectionNames(destCollection); if (collectionNames.contains(collectionName)) { QMessageBox::critical(nullptr, i18n("Error"), i18n("The target collection '%1' contains already\na collection with name '%2'.", destCollection.name(), collection.name())); return false; } } } else { const Item item = d->m_items.value(Item::fromUrl(url).id()); if (item.isValid()) { if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same."; return false; } if (!mimeChecker.isWantedItem(item)) { qCDebug(AKONADICORE_LOG) << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType(); return false; } } } } KJob *job = PasteHelper::pasteUriList(data, destCollection, action, d->m_session); if (!job) { return false; } connect(job, SIGNAL(result(KJob*)), SLOT(pasteJobDone(KJob*))); // Accept the event so that it doesn't propagate. return true; } else { // not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do // fromMimeData for them. Hmm, put it in the same transaction with the above? // TODO: This should be handled first, not last. } } return false; } QModelIndex EntityTreeModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const EntityTreeModel); if (parent.column() > 0) { return QModelIndex(); } //TODO: don't use column count here? Use some d-> func. if (column >= columnCount() || column < 0) { return QModelIndex(); } QList childEntities; const Node *parentNode = reinterpret_cast(parent.internalPointer()); if (!parentNode || !parent.isValid()) { if (d->m_showRootCollection) { childEntities << d->m_childEntities.value(-1); } else { childEntities = d->m_childEntities.value(d->m_rootCollection.id()); } } else { if (parentNode->id >= 0) { childEntities = d->m_childEntities.value(parentNode->id); } } const int size = childEntities.size(); if (row < 0 || row >= size) { return QModelIndex(); } Node *node = childEntities.at(row); return createIndex(row, column, reinterpret_cast(node)); } QModelIndex EntityTreeModel::parent(const QModelIndex &index) const { Q_D(const EntityTreeModel); if (!index.isValid()) { return QModelIndex(); } if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) { return QModelIndex(); } const Node *node = reinterpret_cast(index.internalPointer()); if (!node) { return QModelIndex(); } const Collection collection = d->m_collections.value(node->parent); if (!collection.isValid()) { return QModelIndex(); } if (collection.id() == d->m_rootCollection.id()) { if (!d->m_showRootCollection) { return QModelIndex(); } else { return createIndex(0, 0, reinterpret_cast(d->m_rootNode)); } } Q_ASSERT(collection.parentCollection().isValid()); const int row = d->indexOf(d->m_childEntities.value(collection.parentCollection().id()), collection.id()); Q_ASSERT(row >= 0); Node *parentNode = d->m_childEntities.value(collection.parentCollection().id()).at(row); return createIndex(row, 0, reinterpret_cast(parentNode)); } int EntityTreeModel::rowCount(const QModelIndex &parent) const { Q_D(const EntityTreeModel); if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) { if (parent.isValid()) { return 0; } else { return d->m_items.size(); } } if (!parent.isValid()) { // If we're showing the root collection then it will be the only child of the root. if (d->m_showRootCollection) { return d->m_childEntities.value(-1).size(); } return d->m_childEntities.value(d->m_rootCollection.id()).size(); } if (parent.column() != 0) { return 0; } const Node *node = reinterpret_cast(parent.internalPointer()); if (!node) { return 0; } if (Node::Item == node->type) { return 0; } Q_ASSERT(parent.isValid()); return d->m_childEntities.value(node->id).size(); } int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const { // Not needed in this model. Q_UNUSED(headerGroup); return 1; } QVariant EntityTreeModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const { Q_D(const EntityTreeModel); // Not needed in this model. Q_UNUSED(headerGroup); if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (d->m_rootCollection == Collection::root()) { return i18nc("@title:column Name of a thing", "Name"); } return d->m_rootCollection.name(); } return QAbstractItemModel::headerData(section, orientation, role); } QVariant EntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { const HeaderGroup headerGroup = static_cast((role / static_cast(TerminalUserRole))); role %= TerminalUserRole; return entityHeaderData(section, orientation, role, headerGroup); } QMimeData *EntityTreeModel::mimeData(const QModelIndexList &indexes) const { Q_D(const EntityTreeModel); QMimeData *data = new QMimeData(); QList urls; for (const QModelIndex &index : indexes) { if (index.column() != 0) { continue; } if (!index.isValid()) { continue; } const Node *node = reinterpret_cast(index.internalPointer()); if (Node::Collection == node->type) { urls << d->m_collections.value(node->id).url(Collection::UrlWithName); } else if (Node::Item == node->type) { QUrl url = d->m_items.value(node->id).url(Item::Item::UrlWithMimeType); QUrlQuery query(url); query.addQueryItem(QStringLiteral("parent"), QString::number(node->parent)); url.setQuery(query); urls << url; } else { // if that happens something went horrible wrong Q_ASSERT(false); } } data->setUrls(urls); return data; } // Always return false for actions which take place asynchronously, eg via a Job. bool EntityTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_D(EntityTreeModel); const Node *node = reinterpret_cast(index.internalPointer()); if (role == PendingCutRole) { if (index.isValid() && value.toBool()) { if (Node::Collection == node->type) { d->m_pendingCutCollections.append(node->id); } if (Node::Item == node->type) { d->m_pendingCutItems.append(node->id); } } else { d->m_pendingCutCollections.clear(); d->m_pendingCutItems.clear(); } return true; } if (index.isValid() && node->type == Node::Collection && (role == CollectionRefRole || role == CollectionDerefRole)) { const Collection collection = index.data(CollectionRole).value(); Q_ASSERT(collection.isValid()); if (role == CollectionDerefRole) { d->deref(collection.id()); } else if (role == CollectionRefRole) { d->ref(collection.id()); } return true; } if (index.column() == 0 && (role & (Qt::EditRole | ItemRole | CollectionRole))) { if (Node::Collection == node->type) { Collection collection = d->m_collections.value(node->id); if (!collection.isValid() || !value.isValid()) { return false; } if (Qt::EditRole == role) { collection.setName(value.toString()); if (collection.hasAttribute()) { EntityDisplayAttribute *displayAttribute = collection.attribute(); displayAttribute->setDisplayName(value.toString()); } } if (Qt::BackgroundRole == role) { QColor color = value.value(); if (!color.isValid()) { return false; } EntityDisplayAttribute *eda = collection.attribute(Collection::AddIfMissing); eda->setBackgroundColor(color); } if (CollectionRole == role) { collection = value.value(); } CollectionModifyJob *job = new CollectionModifyJob(collection, d->m_session); connect(job, SIGNAL(result(KJob*)), SLOT(updateJobDone(KJob*))); return false; } else if (Node::Item == node->type) { Item item = d->m_items.value(node->id); if (!item.isValid() || !value.isValid()) { return false; } if (Qt::EditRole == role) { if (item.hasAttribute()) { EntityDisplayAttribute *displayAttribute = item.attribute(Item::AddIfMissing); displayAttribute->setDisplayName(value.toString()); } } if (Qt::BackgroundRole == role) { QColor color = value.value(); if (!color.isValid()) { return false; } EntityDisplayAttribute *eda = item.attribute(Item::AddIfMissing); eda->setBackgroundColor(color); } if (ItemRole == role) { item = value.value(); Q_ASSERT(item.id() == node->id); } ItemModifyJob *itemModifyJob = new ItemModifyJob(item, d->m_session); connect(itemModifyJob, SIGNAL(result(KJob*)), SLOT(updateJobDone(KJob*))); return false; } } return QAbstractItemModel::setData(index, value, role); } bool EntityTreeModel::canFetchMore(const QModelIndex &parent) const { Q_UNUSED(parent) return false; } void EntityTreeModel::fetchMore(const QModelIndex &parent) { Q_D(EntityTreeModel); if (!d->canFetchMore(parent)) { return; } if (d->m_collectionFetchStrategy == InvisibleCollectionFetch) { return; } if (d->m_itemPopulation == ImmediatePopulation) { // Nothing to do. The items are already in the model. return; } else if (d->m_itemPopulation == LazyPopulation) { const Collection collection = parent.data(CollectionRole).value(); if (!collection.isValid()) { return; } d->fetchItems(collection); } } bool EntityTreeModel::hasChildren(const QModelIndex &parent) const { Q_D(const EntityTreeModel); if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) { return parent.isValid() ? false : !d->m_items.isEmpty(); } // TODO: Empty collections right now will return true and get a little + to expand. // There is probably no way to tell if a collection // has child items in akonadi without first attempting an itemFetchJob... // Figure out a way to fix this. (Statistics) return ((rowCount(parent) > 0) || (canFetchMore(parent) && d->m_itemPopulation == LazyPopulation)); } bool EntityTreeModel::isCollectionTreeFetched() const { Q_D(const EntityTreeModel); return d->m_collectionTreeFetched; } bool EntityTreeModel::isCollectionPopulated(Collection::Id id) const { Q_D(const EntityTreeModel); return d->m_populatedCols.contains(id); } bool EntityTreeModel::isFullyPopulated() const { Q_D(const EntityTreeModel); return d->m_collectionTreeFetched && d->m_pendingCollectionRetrieveJobs.isEmpty(); } QModelIndexList EntityTreeModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { Q_D(const EntityTreeModel); if (role == CollectionIdRole || role == CollectionRole) { Collection::Id id; if (role == CollectionRole) { const Collection collection = value.value(); id = collection.id(); } else { id = value.toLongLong(); } QModelIndexList list; const Collection collection = d->m_collections.value(id); if (!collection.isValid()) { return list; } const QModelIndex collectionIndex = d->indexForCollection(collection); Q_ASSERT(collectionIndex.isValid()); list << collectionIndex; return list; } if (role == ItemIdRole || role == ItemRole) { Item::Id id; if (role == ItemRole) { const Item item = value.value(); id = item.id(); } else { id = value.toLongLong(); } QModelIndexList list; const Item item = d->m_items.value(id); if (!item.isValid()) { return list; } return d->indexesForItem(item); } if (role == EntityUrlRole) { const QUrl url(value.toString()); const Item item = Item::fromUrl(url); if (item.isValid()) { return d->indexesForItem(d->m_items.value(item.id())); } const Collection collection = Collection::fromUrl(url); QModelIndexList list; if (collection.isValid()) { list << d->indexForCollection(collection); } return list; } return QAbstractItemModel::match(start, role, value, hits, flags); } bool EntityTreeModel::insertRows(int, int, const QModelIndex &) { return false; } bool EntityTreeModel::insertColumns(int, int, const QModelIndex &) { return false; } bool EntityTreeModel::removeRows(int, int, const QModelIndex &) { return false; } bool EntityTreeModel::removeColumns(int, int, const QModelIndex &) { return false; } void EntityTreeModel::setItemPopulationStrategy(ItemPopulationStrategy strategy) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_itemPopulation = strategy; if (strategy == NoItemPopulation) { disconnect(d->m_monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), this, SLOT(monitoredItemAdded(Akonadi::Item,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), this, SLOT(monitoredItemChanged(Akonadi::Item,QSet))); disconnect(d->m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(monitoredItemRemoved(Akonadi::Item))); disconnect(d->m_monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), this, SLOT(monitoredItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)), this, SLOT(monitoredItemLinked(Akonadi::Item,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)), this, SLOT(monitoredItemUnlinked(Akonadi::Item,Akonadi::Collection))); } d->m_monitor->d_ptr->useRefCounting = (strategy == LazyPopulation); d->endResetModel(); } EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const { Q_D(const EntityTreeModel); return d->m_itemPopulation; } void EntityTreeModel::setIncludeRootCollection(bool include) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_showRootCollection = include; d->endResetModel(); } bool EntityTreeModel::includeRootCollection() const { Q_D(const EntityTreeModel); return d->m_showRootCollection; } void EntityTreeModel::setRootCollectionDisplayName(const QString &displayName) { Q_D(EntityTreeModel); d->m_rootCollectionDisplayName = displayName; // TODO: Emit datachanged if it is being shown. } QString EntityTreeModel::rootCollectionDisplayName() const { Q_D(const EntityTreeModel); return d->m_rootCollectionDisplayName; } void EntityTreeModel::setCollectionFetchStrategy(CollectionFetchStrategy strategy) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_collectionFetchStrategy = strategy; if (strategy == FetchNoCollections || strategy == InvisibleCollectionFetch) { disconnect(d->m_monitor, SIGNAL(collectionChanged(Akonadi::Collection)), this, SLOT(monitoredCollectionChanged(Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), this, SLOT(monitoredCollectionAdded(Akonadi::Collection,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), this, SLOT(monitoredCollectionRemoved(Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)), this, SLOT(monitoredCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); d->m_monitor->fetchCollection(false); } else { d->m_monitor->fetchCollection(true); } d->endResetModel(); } EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const { Q_D(const EntityTreeModel); return d->m_collectionFetchStrategy; } static QPair, const EntityTreeModel *> proxiesAndModel(const QAbstractItemModel *model) { QList proxyChain; const QAbstractProxyModel *proxy = qobject_cast(model); const QAbstractItemModel *_model = model; while (proxy) { proxyChain.prepend(proxy); _model = proxy->sourceModel(); proxy = qobject_cast(_model); } const EntityTreeModel *etm = qobject_cast(_model); return qMakePair(proxyChain, etm); } static QModelIndex proxiedIndex(const QModelIndex &idx, const QList &proxyChain) { QListIterator it(proxyChain); QModelIndex _idx = idx; while (it.hasNext()) { _idx = it.next()->mapFromSource(_idx); } return _idx; } QModelIndex EntityTreeModel::modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection) { QPair, const EntityTreeModel *> pair = proxiesAndModel(model); Q_ASSERT(pair.second); QModelIndex idx = pair.second->d_ptr->indexForCollection(collection); return proxiedIndex(idx, pair.first); } QModelIndexList EntityTreeModel::modelIndexesForItem(const QAbstractItemModel *model, const Item &item) { QPair, const EntityTreeModel *> pair = proxiesAndModel(model); if (!pair.second) { qCWarning(AKONADICORE_LOG) << "Couldn't find an EntityTreeModel"; return QModelIndexList(); } const QModelIndexList list = pair.second->d_ptr->indexesForItem(item); QModelIndexList proxyList; for (const QModelIndex &idx : list) { const QModelIndex pIdx = proxiedIndex(idx, pair.first); if (pIdx.isValid()) { proxyList << pIdx; } } return proxyList; } Collection EntityTreeModel::updatedCollection(const QAbstractItemModel *model, qint64 collectionId) { const QAbstractProxyModel *proxy = qobject_cast(model); const QAbstractItemModel *_model = model; while (proxy) { _model = proxy->sourceModel(); proxy = qobject_cast(_model); } auto etm = qobject_cast(_model); if (etm) { return etm->d_ptr->m_collections.value(collectionId); } else { return Collection{ collectionId }; } } Collection EntityTreeModel::updatedCollection(const QAbstractItemModel *model, const Collection &collection) { return updatedCollection(model, collection.id()); } #include "moc_entitytreemodel.cpp" diff --git a/src/core/models/statisticsproxymodel.cpp b/src/core/models/statisticsproxymodel.cpp index d5ff92891..8feda6bbb 100644 --- a/src/core/models/statisticsproxymodel.cpp +++ b/src/core/models/statisticsproxymodel.cpp @@ -1,343 +1,343 @@ /* Copyright (c) 2009 Kevin Ottens 2016 David Faure 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 "statisticsproxymodel.h" #include "akonadicore_debug.h" #include "kitemmodels_version.h" #include "entitytreemodel.h" #include "collectionutils.h" #include "collectionquotaattribute.h" #include "collectionstatistics.h" #include "entitydisplayattribute.h" #include #include #include #include #include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN StatisticsProxyModel::Private { public: Private(StatisticsProxyModel *parent) : q(parent), mToolTipEnabled(false), mExtraColumnsEnabled(false) { } void getCountRecursive(const QModelIndex &index, qint64 &totalSize) const { Collection collection = qvariant_cast(index.data(EntityTreeModel::CollectionRole)); // Do not assert on invalid collections, since a collection may be deleted // in the meantime and deleted collections are invalid. if (collection.isValid()) { CollectionStatistics statistics = collection.statistics(); totalSize += qMax(0LL, statistics.size()); if (index.model()->hasChildren(index)) { const int rowCount = index.model()->rowCount(index); for (int row = 0; row < rowCount; row++) { static const int column = 0; getCountRecursive(index.model()->index(row, column, index), totalSize); } } } } int sourceColumnCount() const { return q->sourceModel()->columnCount(); } QString toolTipForCollection(const QModelIndex &index, const Collection &collection) { const QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name(); const QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name(); QString tip = QStringLiteral( "\n" ); const QString textDirection = (QApplication::layoutDirection() == Qt::LeftToRight) ? QStringLiteral("left") : QStringLiteral("right"); tip += QStringLiteral( " \n" " \n" " \n" ).arg(txtColor, bckColor, index.data(Qt::DisplayRole).toString(), textDirection); tip += QStringLiteral( " \n" " \n" ).arg(iconPath).arg(icon_size_found); if (QApplication::layoutDirection() == Qt::LeftToRight) { tip += tipInfo + QStringLiteral("" \ "
\n" "
\n" " %3\n" "
\n" "
\n" ).arg(textDirection); QString tipInfo = QStringLiteral( " %1: %2
\n" " %3: %4

\n" ).arg(i18n("Total Messages")).arg(collection.statistics().count()) .arg(i18n("Unread Messages")).arg(collection.statistics().unreadCount()); if (collection.hasAttribute()) { - CollectionQuotaAttribute *quota = collection.attribute(); + const CollectionQuotaAttribute *quota = collection.attribute(); if (quota->currentValue() > -1 && quota->maximumValue() > 0) { qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue(); if (qAbs(percentage) >= 0.01) { QString percentStr = QString::number(percentage, 'f', 2); tipInfo += QStringLiteral( " %1: %2%
\n" ).arg(i18n("Quota"), percentStr); } } } KFormat formatter; qint64 currentFolderSize(collection.statistics().size()); tipInfo += QStringLiteral( " %1: %2
\n" ).arg(i18n("Storage Size"), formatter.formatByteSize(currentFolderSize)); qint64 totalSize = 0; getCountRecursive(index, totalSize); totalSize -= currentFolderSize; if (totalSize > 0) { tipInfo += QStringLiteral( "%1: %2
" ).arg(i18n("Subfolder Storage Size"), formatter.formatByteSize(totalSize)); } QString iconName = CollectionUtils::defaultIconName(collection); if (collection.hasAttribute() && !collection.attribute()->iconName().isEmpty()) { if (!collection.attribute()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) { iconName = collection.attribute()->activeIconName(); } else { iconName = collection.attribute()->iconName(); } } int iconSizes[] = { 32, 22 }; int icon_size_found = 32; QString iconPath; for (int i = 0; i < 2; ++i) { iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[ i ], true); if (!iconPath.isEmpty()) { icon_size_found = iconSizes[ i ]; break; } } if (iconPath.isEmpty()) { iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false); } QString tipIcon = QStringLiteral( "
\n" " \n" "
\n" "
").arg(textDirection) + tipIcon; } else { tip += tipIcon + QStringLiteral("").arg(textDirection) + tipInfo; } tip += QLatin1String( "
" ); return tip; } void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); StatisticsProxyModel *q = nullptr; bool mToolTipEnabled; bool mExtraColumnsEnabled; }; void StatisticsProxyModel::Private::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QModelIndex proxyTopLeft(q->mapFromSource(topLeft)); QModelIndex proxyBottomRight(q->mapFromSource(bottomRight)); // Emit data changed for the whole row (bug #222292) if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet const int lastColumn = q->columnCount() - 1; proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn); } Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles); } void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(_k_sourceDataChanged(QModelIndex,QModelIndex,QVector))); } KExtraColumnsProxyModel::setSourceModel(model); if (model) { // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex,QVector))); connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(_k_sourceDataChanged(QModelIndex,QModelIndex,QVector))); } } StatisticsProxyModel::StatisticsProxyModel(QObject *parent) : KExtraColumnsProxyModel(parent), d(new Private(this)) { setExtraColumnsEnabled(true); } StatisticsProxyModel::~StatisticsProxyModel() { delete d; } void StatisticsProxyModel::setToolTipEnabled(bool enable) { d->mToolTipEnabled = enable; } bool StatisticsProxyModel::isToolTipEnabled() const { return d->mToolTipEnabled; } void StatisticsProxyModel::setExtraColumnsEnabled(bool enable) { if (d->mExtraColumnsEnabled == enable) { return; } d->mExtraColumnsEnabled = enable; if (enable) { KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread")); KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total")); KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size")); } else { KExtraColumnsProxyModel::removeExtraColumn(2); KExtraColumnsProxyModel::removeExtraColumn(1); KExtraColumnsProxyModel::removeExtraColumn(0); } } bool StatisticsProxyModel::isExtraColumnsEnabled() const { return d->mExtraColumnsEnabled; } QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const { switch (role) { case Qt::DisplayRole: { const QModelIndex firstColumn = index(row, 0, parent); const Collection collection = data(firstColumn, EntityTreeModel::CollectionRole).value(); if (collection.isValid() && collection.statistics().count() >= 0) { const CollectionStatistics stats = collection.statistics(); if (extraColumn == 2) { KFormat formatter; return formatter.formatByteSize(stats.size()); } else if (extraColumn == 1) { return stats.count(); } else if (extraColumn == 0) { if (stats.unreadCount() > 0) { return stats.unreadCount(); } else { return QString(); } } else { qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size."; } } } break; case Qt::TextAlignmentRole: { return Qt::AlignRight; } default: break; } return QVariant(); } QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const { if (role == Qt::ToolTipRole && d->mToolTipEnabled) { const QModelIndex firstColumn = index.sibling(index.row(), 0); const Collection collection = data(firstColumn, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { return d->toolTipForCollection(firstColumn, collection); } } return KExtraColumnsProxyModel::data(index, role); } Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const { if (index_.column() >= d->sourceColumnCount()) { const QModelIndex firstColumn = index_.sibling(index_.row(), 0); return KExtraColumnsProxyModel::flags(firstColumn) & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); } return KExtraColumnsProxyModel::flags(index_); } // Not sure this is still necessary.... QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { if (role < Qt::UserRole) { return KExtraColumnsProxyModel::match(start, role, value, hits, flags); } QModelIndexList list; QModelIndex proxyIndex; foreach (const QModelIndex &idx, sourceModel()->match(mapToSource(start), role, value, hits, flags)) { proxyIndex = mapFromSource(idx); if (proxyIndex.isValid()) { list << proxyIndex; } } return list; } #include "moc_statisticsproxymodel.cpp" diff --git a/src/core/models/tagmodel.cpp b/src/core/models/tagmodel.cpp index 14028a1f6..96d529431 100644 --- a/src/core/models/tagmodel.cpp +++ b/src/core/models/tagmodel.cpp @@ -1,181 +1,180 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tagmodel.h" #include "tagmodel_p.h" #include "tagattribute.h" #include #include using namespace Akonadi; TagModel::TagModel(Monitor *recorder, QObject *parent) : QAbstractItemModel(parent) , d_ptr(new TagModelPrivate(this)) { Q_D(TagModel); d->init(recorder); } TagModel::TagModel(Monitor *recorder, TagModelPrivate *dd, QObject *parent) : QAbstractItemModel(parent) , d_ptr(dd) { Q_D(TagModel); d->init(recorder); } TagModel::~TagModel() { delete d_ptr; } int TagModel::columnCount(const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) { return 0; } return 1; } int TagModel::rowCount(const QModelIndex &parent) const { Q_D(const TagModel); Tag::Id parentTagId = -1; if (parent.isValid()) { parentTagId = d->mChildTags[parent.internalId()].at(parent.row()).id(); } return d->mChildTags[parentTagId].count(); } QVariant TagModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) { return QVariant(); } if (role == Qt::DisplayRole) { switch (section) { case 0: return i18n("Tag"); } } return QAbstractItemModel::headerData(section, orientation, role); } QVariant TagModel::data(const QModelIndex &index, int role) const { Q_D(const TagModel); if (!index.isValid()) { return QVariant(); } const Tag tag = d->tagForIndex(index); if (!tag.isValid()) { return QVariant(); } switch (role) { case Qt::DisplayRole: // fall-through case NameRole: return tag.name(); case IdRole: return tag.id(); case GIDRole: return tag.gid(); case ParentRole: return QVariant::fromValue(tag.parent()); case TagRole: return QVariant::fromValue(tag); case Qt::DecorationRole: { - TagAttribute *attr = tag.attribute(); - if (attr) { + if (const TagAttribute *attr = tag.attribute()) { return QIcon::fromTheme(attr->iconName()); } else { return QVariant(); } } } return QVariant(); } QModelIndex TagModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const TagModel); qint64 parentId = -1; if (parent.isValid()) { const Tag parentTag = d->tagForIndex(parent); parentId = parentTag.id(); } const Tag::List &children = d->mChildTags.value(parentId); if (row >= children.count()) { return QModelIndex(); } return createIndex(row, column, (int) parentId); } QModelIndex TagModel::parent(const QModelIndex &child) const { Q_D(const TagModel); if (!child.isValid()) { return QModelIndex(); } const qint64 parentId = child.internalId(); return d->indexForTag(parentId); } Qt::ItemFlags TagModel::flags(const QModelIndex &index) const { Q_UNUSED(index); return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; } bool TagModel::insertColumns(int, int, const QModelIndex &) { return false; } bool TagModel::insertRows(int, int, const QModelIndex &) { return false; } bool TagModel::removeColumns(int, int, const QModelIndex &) { return false; } bool TagModel::removeRows(int, int, const QModelIndex &) { return false; } #include "moc_tagmodel.cpp" diff --git a/src/core/tag.cpp b/src/core/tag.cpp index 131eadccf..563304c83 100644 --- a/src/core/tag.cpp +++ b/src/core/tag.cpp @@ -1,260 +1,266 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tag.h" #include "tag_p.h" #include "akonadicore_debug.h" #include "tagattribute.h" #include #include using namespace Akonadi; const char Akonadi::Tag::PLAIN[] = "PLAIN"; const char Akonadi::Tag::GENERIC[] = "GENERIC"; uint Akonadi::qHash(const Tag &tag) { return ::qHash(tag.id()); } Tag::Tag() : d_ptr(new TagPrivate) { } Tag::Tag(Tag::Id id) : d_ptr(new TagPrivate) { d_ptr->id = id; } Tag::Tag(const QString &name) : d_ptr(new TagPrivate) { d_ptr->gid = name.toUtf8(); d_ptr->type = PLAIN; } Tag::Tag(const Tag &other) : d_ptr(other.d_ptr) { } Tag::~Tag() { } Tag &Tag::operator=(const Tag &other) { if (this != &other) { d_ptr = other.d_ptr; } return *this; } bool Tag::operator==(const Tag &other) const { // Valid tags are equal if their IDs are equal if (isValid() && other.isValid()) { return d_ptr->id == other.d_ptr->id; } // Invalid tags are equal if their GIDs are non empty but equal if (!d_ptr->gid.isEmpty() || !other.d_ptr->gid.isEmpty()) { return d_ptr->gid == other.d_ptr->gid; } // Invalid tags are equal if both are invalid return !isValid() && !other.isValid(); } bool Tag::operator!=(const Tag &other) const { return !operator==(other); } Tag Tag::fromUrl(const QUrl &url) { if (url.scheme() != QLatin1String("akonadi")) { return Tag(); } const QString tagStr = QUrlQuery(url).queryItemValue(QStringLiteral("tag")); bool ok = false; Tag::Id itemId = tagStr.toLongLong(&ok); if (!ok) { return Tag(); } return Tag(itemId); } QUrl Tag::url() const { QUrlQuery query; query.addQueryItem(QStringLiteral("tag"), QString::number(id())); QUrl url; url.setScheme(QStringLiteral("akonadi")); url.setQuery(query); return url; } void Tag::addAttribute(Attribute *attr) { d_ptr->mAttributeStorage.addAttribute(attr); } void Tag::removeAttribute(const QByteArray &type) { d_ptr->mAttributeStorage.removeAttribute(type); } bool Tag::hasAttribute(const QByteArray &type) const { return d_ptr->mAttributeStorage.hasAttribute(type); } Attribute::List Tag::attributes() const { return d_ptr->mAttributeStorage.attributes(); } void Tag::clearAttributes() { d_ptr->mAttributeStorage.clearAttributes(); } -Attribute *Tag::attribute(const QByteArray &type) const +const Attribute *Tag::attribute(const QByteArray &type) const { return d_ptr->mAttributeStorage.attribute(type); } +Attribute *Tag::attribute(const QByteArray &type) +{ + markAttributeModified(type); + return d_ptr->mAttributeStorage.attribute(type); +} + void Tag::setId(Tag::Id identifier) { d_ptr->id = identifier; } Tag::Id Tag::id() const { return d_ptr->id; } void Tag::setGid(const QByteArray &gid) { d_ptr->gid = gid; } QByteArray Tag::gid() const { return d_ptr->gid; } void Tag::setRemoteId(const QByteArray &remoteId) { d_ptr->remoteId = remoteId; } QByteArray Tag::remoteId() const { return d_ptr->remoteId; } void Tag::setName(const QString &name) { if (!name.isEmpty()) { TagAttribute *const attr = attribute(Tag::AddIfMissing); attr->setDisplayName(name); } } QString Tag::name() const { const TagAttribute *const attr = attribute(); const QString displayName = attr ? attr->displayName() : QString(); return !displayName.isEmpty() ? displayName : QString::fromUtf8(d_ptr->gid); } void Tag::setParent(const Tag &parent) { d_ptr->parent.reset(new Tag(parent)); } Tag Tag::parent() const { if (!d_ptr->parent) { return Tag(); } return *d_ptr->parent; } void Tag::setType(const QByteArray &type) { d_ptr->type = type; } QByteArray Tag::type() const { return d_ptr->type; } bool Tag::isValid() const { return d_ptr->id >= 0; } bool Tag::isImmutable() const { return (d_ptr->type.isEmpty() || d_ptr->type == PLAIN); } QDebug &operator<<(QDebug &debug, const Tag &tag) { QDebugStateSaver saver(debug); debug.nospace() << "Akonadi::Tag(ID " << tag.id() << ", GID " << tag.gid() << ", parent tag ID " << tag.parent().id() << ")"; return debug; } Tag Tag::genericTag(const QString &name) { Tag tag; tag.d_ptr->type = GENERIC; tag.d_ptr->gid = QUuid::createUuid().toByteArray().mid(1, 36); tag.setName(name); return tag; } -bool Tag::checkAttribute(Attribute *attr, const QByteArray &type) const +bool Tag::checkAttribute(const Attribute *attr, const QByteArray &type) const { if (attr) { return true; } qCWarning(AKONADICORE_LOG) << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; return false; } void Tag::markAttributeModified(const QByteArray &type) { d_ptr->mAttributeStorage.markAttributeModified(type); } diff --git a/src/core/tag.h b/src/core/tag.h index 20ca6c9ab..9f41c7301 100644 --- a/src/core/tag.h +++ b/src/core/tag.h @@ -1,273 +1,272 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TAG_H #define AKONADI_TAG_H #include "akonadicore_export.h" #include "attribute.h" #include #include #include #include namespace Akonadi { class TagModifyJob; class TagPrivate; /** * An Akonadi Tag. */ class AKONADICORE_EXPORT Tag { public: typedef QVector List; typedef qint64 Id; /** * The PLAIN type has the following properties: * * gid == displayName * * immutable * * no hierarchy (no parent) * * PLAIN tags are general purpose tags that are easy to map by backends. */ static const char PLAIN[]; /** * The GENERIC type has the following properties: * * mutable * * gid is RFC 4122 compatible * * no hierarchy (no parent) * * GENERIC tags are general purpose tags, that are used, if you can change tag name. */ static const char GENERIC[]; Tag(); explicit Tag(Id id); /** * Creates a PLAIN tag */ explicit Tag(const QString &name); Tag(const Tag &other); ~Tag(); Tag &operator=(const Tag &); //Avoid slicing bool operator==(const Tag &) const; bool operator!=(const Tag &) const; static Tag fromUrl(const QUrl &url); /** * Adds an attribute to the entity. * * 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 entity 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 entity 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 entity. */ Attribute::List attributes() const; /** * Removes and deletes all attributes of the entity. */ void clearAttributes(); /** * Returns the attribute of the given type @p name if available, 0 otherwise. */ - Attribute *attribute(const QByteArray &name) const; + const Attribute *attribute(const QByteArray &name) const; + Attribute *attribute(const QByteArray &name); /** * Describes the options that can be passed to access attributes. */ enum CreateOption { - AddIfMissing ///< Creates the attribute if it is missing + AddIfMissing, ///< Creates the attribute if it is missing + DontCreate ///< Does not create an attribute if it is missing (default) }; /** * Returns the attribute of the requested type. * If the entity 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); + inline T *attribute(CreateOption option = DontCreate); /** * Returns the attribute of the requested type or 0 if it is not available. */ template - inline T *attribute() const; + inline const T *attribute() const; /** * Removes and deletes the attribute of the requested type. */ template inline void removeAttribute(); /** * Returns whether the entity has an attribute of the requested type. */ template inline bool hasAttribute() const; /** * Returns the url of the tag. */ QUrl url() const; /** * Sets the unique @p identifier of the tag. */ void setId(Id identifier); /** * Returns the unique identifier of the tag. */ Id id() const; void setGid(const QByteArray &gid); QByteArray gid() const; void setRemoteId(const QByteArray &remoteId); QByteArray remoteId() const; void setType(const QByteArray &type); QByteArray type() const; void setName(const QString &name); QString name() const; void setParent(const Tag &parent); Tag parent() const; bool isValid() const; /** * Returns true if the tag is immutable (cannot be modified after creation). * Note that the immutability does not affect the attributes. */ bool isImmutable() const; /** * Returns a GENERIC tag with the given name and a valid gid */ static Tag genericTag(const QString &name); private: - bool checkAttribute(Attribute *attr, const QByteArray &type) const; + bool checkAttribute(const Attribute *attr, const QByteArray &type) const; void markAttributeModified(const QByteArray &type); //@cond PRIVATE friend class TagModifyJob; friend class TagFetchJob; friend class ProtocolHelper; QSharedDataPointer d_ptr; //@endcond }; AKONADICORE_EXPORT uint qHash(const Akonadi::Tag &); template inline T *Tag::attribute(CreateOption option) { - Q_UNUSED(option); - const QByteArray type = T().type(); markAttributeModified(type); if (hasAttribute(type)) { T *attr = dynamic_cast(attribute(type)); if (checkAttribute(attr, type)) { return attr; } + } else if (option == AddIfMissing) { + T *attr = new T(); + addAttribute(attr); + return attr; } - T *attr = new T(); - addAttribute(attr); - return attr; + return nullptr; } template -inline T *Tag::attribute() const +inline const T *Tag::attribute() const { const QByteArray type = T().type(); if (hasAttribute(type)) { - T *attr = dynamic_cast(attribute(type)); + const T *attr = dynamic_cast(attribute(type)); if (checkAttribute(attr, type)) { - // FIXME: Make this a truly const method so that callers may not modify - // the attribute returned from here. - const_cast(this)->markAttributeModified(type); return attr; } } return nullptr; } template inline void Tag::removeAttribute() { const T dummy; removeAttribute(dummy.type()); } template inline bool Tag::hasAttribute() const { const T dummy; return hasAttribute(dummy.type()); } } // namespace Akonadi AKONADICORE_EXPORT QDebug &operator<<(QDebug &debug, const Akonadi::Tag &tag); Q_DECLARE_METATYPE(Akonadi::Tag) Q_DECLARE_METATYPE(Akonadi::Tag::List) Q_DECLARE_METATYPE(QSet) Q_DECLARE_TYPEINFO(Akonadi::Tag, Q_MOVABLE_TYPE); #endif diff --git a/src/widgets/collectionmaintenancepage.cpp b/src/widgets/collectionmaintenancepage.cpp index 47a0a22f9..ef96c77f8 100644 --- a/src/widgets/collectionmaintenancepage.cpp +++ b/src/widgets/collectionmaintenancepage.cpp @@ -1,175 +1,175 @@ /* Copyright (C) 2009-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "collectionmaintenancepage.h" #include "ui_collectionmaintenancepage.h" #include "core/collectionstatistics.h" #include "monitor.h" #include "agentmanager.h" #include "akonadiwidgets_debug.h" #include "indexpolicyattribute.h" #include "cachepolicy.h" #include "servermanager.h" #include #include #include #include #include #include #include using namespace Akonadi; class CollectionMaintenancePage::Private { public: Private() {} void slotReindexCollection() { if (currentCollection.isValid()) { //Don't allow to reindex twice. ui.reindexButton->setEnabled(false); const auto service = ServerManager::agentServiceName(ServerManager::Agent, QStringLiteral("akonadi_indexing_agent")); QDBusInterface indexingAgentIface(service, QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.Indexer")); if (indexingAgentIface.isValid()) { indexingAgentIface.call(QStringLiteral("reindexCollection"), static_cast(currentCollection.id())); ui.indexedCountLbl->setText(i18n("Remember that indexing can take some minutes.")); } else { qCWarning(AKONADIWIDGETS_LOG) << "indexer interface not valid"; } } } void updateLabel(qint64 nbMail, qint64 nbUnreadMail, qint64 size) { ui.itemsCountLbl->setText(QString::number(qMax(0LL, nbMail))); ui.unreadItemsCountLbl->setText(QString::number(qMax(0LL, nbUnreadMail))); ui.folderSizeLbl->setText(KFormat().formatByteSize(qMax(0LL, size))); } Akonadi::Collection currentCollection; Akonadi::Monitor *monitor = nullptr; Ui::CollectionMaintenancePage ui; }; CollectionMaintenancePage::CollectionMaintenancePage(QWidget *parent) : CollectionPropertiesPage(parent) , d(new Private) { setObjectName(QStringLiteral("Akonadi::CollectionMaintenancePage")); setPageTitle(i18n("Maintenance")); } CollectionMaintenancePage::~CollectionMaintenancePage() { delete d; } void CollectionMaintenancePage::init(const Collection &col) { d->ui.setupUi(this); d->currentCollection = col; d->monitor = new Monitor(this); d->monitor->setObjectName(QStringLiteral("CollectionMaintenancePageMonitor")); d->monitor->setCollectionMonitored(col, true); d->monitor->fetchCollectionStatistics(true); connect(d->monitor, &Monitor::collectionStatisticsChanged, this, [this](Collection::Id, const CollectionStatistics &stats) { d->updateLabel(stats.count(), stats.unreadCount(), stats.size()); }); if (!col.isVirtual()) { const AgentInstance instance = Akonadi::AgentManager::self()->instance(col.resource()); d->ui.folderTypeLbl->setText(instance.type().name()); } else { d->ui.folderTypeLbl->hide(); d->ui.filesLayout->labelForField(d->ui.folderTypeLbl)->hide(); } connect(d->ui.reindexButton, &QPushButton::clicked, this, [this]() { d->slotReindexCollection(); }); // Check if the resource caches full payloads or at least has local storage // (so that the indexer can retrieve the payloads on demand) const auto resource = Akonadi::AgentManager::self()->instance(col.resource()).type(); if (!col.cachePolicy().localParts().contains(QLatin1String("RFC822")) && resource.customProperties().value(QStringLiteral("HasLocalStorage"), QString()) != QLatin1String("true")) { d->ui.indexingGroup->hide(); } } void CollectionMaintenancePage::load(const Collection &col) { init(col); if (col.isValid()) { d->updateLabel(col.statistics().count(), col.statistics().unreadCount(), col.statistics().size()); - Akonadi::IndexPolicyAttribute *attr = col.attribute(); + const Akonadi::IndexPolicyAttribute *attr = col.attribute(); const bool indexingWasEnabled(!attr || attr->indexingEnabled()); d->ui.enableIndexingChkBox->setChecked(indexingWasEnabled); if (indexingWasEnabled) { const auto service = ServerManager::agentServiceName(ServerManager::Agent, QStringLiteral("akonadi_indexing_agent")); QDBusInterface indexingAgentIface(service, QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.Indexer")); if (indexingAgentIface.isValid()) { auto reply = indexingAgentIface.asyncCall(QStringLiteral("indexedItems"), (qlonglong) col.id()); auto w = new QDBusPendingCallWatcher(reply, this); connect(w, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) { QDBusPendingReply reply = *w; if (reply.isError()) { d->ui.indexedCountLbl->setText(i18n("Error while retrieving indexed items count")); qCWarning(AKONADIWIDGETS_LOG) << "Failed to retrieve indexed items count:" << reply.error().message(); } else { d->ui.indexedCountLbl->setText(i18np("Indexed %1 item in this folder", "Indexed %1 items in this folder", reply.argumentAt<0>())); } w->deleteLater(); }); d->ui.indexedCountLbl->setText(i18n("Calculating indexed items...")); } else { qCDebug(AKONADIWIDGETS_LOG) << "Failed to obtain Indexer interface"; d->ui.indexedCountLbl->hide(); } } else { d->ui.indexedCountLbl->hide(); } } } void CollectionMaintenancePage::save(Collection &collection) { if (!collection.hasAttribute() && d->ui.enableIndexingChkBox->isChecked()) { return; } Akonadi::IndexPolicyAttribute *attr = collection.attribute(Akonadi::Collection::AddIfMissing); attr->setIndexingEnabled(d->ui.enableIndexingChkBox->isChecked()); } diff --git a/src/xml/autotests/collectiontest.cpp b/src/xml/autotests/collectiontest.cpp index 9f181710b..e7006c0da 100644 --- a/src/xml/autotests/collectiontest.cpp +++ b/src/xml/autotests/collectiontest.cpp @@ -1,107 +1,107 @@ /* Copyright (c) Igor Trindade Oliveira This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "collectiontest.h" #include #include #include "entitydisplayattribute.h" #include #include #include using namespace Akonadi; QTEST_MAIN(CollectionTest) // NOTE: XML element attributes are stored in QHash, which means that there are // always in random order when converting to string. This test has QT_HASH_SEED // always set to 1, but it appears that it has different effect on my computer // and on Jenkins. This order of attributes is the order that passes on Jenkis, // so if it fails for you locally because of different order of arguments, // please make sure that your fix won't break the test on Jenkins. QByteArray collection1( "\n" " \n" " (\"Posteingang\" \"mail-folder-inbox\" \"\" ())\n" " \n" "\n"); QByteArray collection2( " \ \ (\"Posteingang\" \"mail-folder-inbox\" false) \ \ \ \ wcW \ \ \ "); void CollectionTest::testBuildCollection() { QDomDocument mDocument; mDocument.setContent(collection1, true, nullptr); Collection::List colist = XmlReader::readCollections(mDocument.documentElement()); const QStringList mimeType {QStringLiteral("inode/directory"),QStringLiteral("message/rfc822")}; QCOMPARE(colist.size(), 1); verifyCollection(colist, 0, QStringLiteral("c11"), QStringLiteral("Inbox"), mimeType); mDocument.setContent(collection2, true, nullptr); colist = XmlReader::readCollections(mDocument.documentElement()); QCOMPARE(colist.size(), 3); verifyCollection(colist, 0, QStringLiteral("c11"), QStringLiteral("Inbox"), mimeType); verifyCollection(colist, 1, QStringLiteral("c111"), QStringLiteral("KDE PIM"), mimeType); verifyCollection(colist, 2, QStringLiteral("c112"), QStringLiteral("Akonadi"), mimeType); QVERIFY(colist.at(0).hasAttribute()); - EntityDisplayAttribute *attr = colist.at(0).attribute(); + const EntityDisplayAttribute *attr = colist.at(0).attribute(); QCOMPARE(attr->displayName(), QStringLiteral("Posteingang")); } void CollectionTest::serializeCollection() { Collection c; c.setRemoteId(QStringLiteral("c11")); c.setName(QStringLiteral("Inbox")); c.setContentMimeTypes(QStringList() << Collection::mimeType() << QStringLiteral("message/rfc822")); c.attribute(Collection::AddIfMissing)->setDisplayName(QStringLiteral("Posteingang")); c.attribute()->setIconName(QStringLiteral("mail-folder-inbox")); QDomDocument doc; QDomElement root = doc.createElement(QStringLiteral("test")); doc.appendChild(root); XmlWriter::writeCollection(c, root); QCOMPARE(doc.toString(), QString::fromUtf8(collection1)); } void CollectionTest::verifyCollection(const Collection::List &colist, int listPosition, const QString &remoteId, const QString &name, const QStringList &mimeType) { QVERIFY(colist.at(listPosition).name() == name); QVERIFY(colist.at(listPosition).remoteId() == remoteId); QVERIFY(colist.at(listPosition).contentMimeTypes() == mimeType); }