File indexing completed on 2024-06-16 04:50:16

0001 /*
0002     SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include "cachepolicy.h"
0010 #include "collection.h"
0011 #include "collectionfetchscope.h"
0012 #include "collectionutils.h"
0013 #include "item.h"
0014 #include "itemfetchscope.h"
0015 #include "sharedvaluepool_p.h"
0016 #include "tag.h"
0017 
0018 #include "private/imapparser_p.h"
0019 #include "private/protocol_p.h"
0020 #include "private/scope_p.h"
0021 #include "private/tristate_p.h"
0022 
0023 #include <QString>
0024 
0025 #include <algorithm>
0026 #include <cassert>
0027 #include <functional>
0028 #include <set>
0029 #include <type_traits>
0030 
0031 namespace Akonadi
0032 {
0033 struct ProtocolHelperValuePool {
0034     using FlagPool = Internal::SharedValuePool<QByteArray, QList>;
0035     using MimeTypePool = Internal::SharedValuePool<QString, QList>;
0036 
0037     FlagPool flagPool;
0038     MimeTypePool mimeTypePool;
0039     QHash<Collection::Id, Collection> ancestorCollections;
0040 };
0041 
0042 /**
0043   @internal
0044   Helper methods for converting between libakonadi objects and their protocol
0045   representation.
0046 
0047   @todo Add unit tests for this.
0048   @todo Use exceptions for a useful error handling
0049 */
0050 class ProtocolHelper
0051 {
0052 public:
0053     /** Part namespaces. */
0054     enum PartNamespace {
0055         PartGlobal,
0056         PartPayload,
0057         PartAttribute,
0058     };
0059 
0060     /**
0061       Parse a cache policy definition.
0062       @param policy The parsed cache policy.
0063       @returns Akonadi::CachePolicy
0064     */
0065     static CachePolicy parseCachePolicy(const Protocol::CachePolicy &policy);
0066 
0067     /**
0068       Convert a cache policy object into its protocol representation.
0069     */
0070     static Protocol::CachePolicy cachePolicyToProtocol(const CachePolicy &policy);
0071 
0072     /**
0073       Convert a ancestor chain from its protocol representation into an Item object.
0074     */
0075     static void parseAncestors(const QList<Protocol::Ancestor> &ancestors, Item *item);
0076 
0077     /**
0078       Convert a ancestor chain from its protocol representation into a Collection object.
0079     */
0080     static void parseAncestors(const QList<Protocol::Ancestor> &ancestors, Collection *collection);
0081 
0082     /**
0083       Convert a ancestor chain from its protocol representation into an Item object.
0084 
0085       This method allows to pass a @p valuePool which acts as cache, so ancestor paths for the
0086       same @p parentCollection don't have to be parsed twice.
0087     */
0088     static void
0089     parseAncestorsCached(const QList<Protocol::Ancestor> &ancestors, Item *item, Collection::Id parentCollection, ProtocolHelperValuePool *valuePool = nullptr);
0090 
0091     /**
0092       Convert a ancestor chain from its protocol representation into an Collection object.
0093 
0094       This method allows to pass a @p valuePool which acts as cache, so ancestor paths for the
0095       same @p parentCollection don't have to be parsed twice.
0096     */
0097     static void parseAncestorsCached(const QList<Protocol::Ancestor> &ancestors,
0098                                      Collection *collection,
0099                                      Collection::Id parentCollection,
0100                                      ProtocolHelperValuePool *valuePool = nullptr);
0101     /**
0102       Parse a collection description.
0103       @param data The input data.
0104       @param requireParent Whether or not we require a parent as part of the data.
0105       @returns The parsed collection
0106     */
0107     static Collection parseCollection(const Protocol::FetchCollectionsResponse &data, bool requireParent = true);
0108 
0109     static Tag parseTag(const Protocol::FetchTagsResponse &data);
0110 
0111     static void parseAttributes(const Protocol::Attributes &attributes, Item *item);
0112     static void parseAttributes(const Protocol::Attributes &attributes, Collection *collection);
0113     static void parseAttributes(const Protocol::Attributes &attributes, Tag *entity);
0114 
0115     static CollectionStatistics parseCollectionStatistics(const Protocol::FetchCollectionStatsResponse &stats);
0116 
0117     /**
0118       Convert attributes to their protocol representation.
0119     */
0120     static Protocol::Attributes attributesToProtocol(const Item &item, bool ns = false);
0121     static Protocol::Attributes attributesToProtocol(const Collection &collection, bool ns = false);
0122     static Protocol::Attributes attributesToProtocol(const Tag &entity, bool ns = false);
0123     static Protocol::Attributes attributesToProtocol(const std::vector<Attribute *> &modifiedAttributes, bool ns = false);
0124 
0125     /**
0126       Encodes part label and namespace.
0127     */
0128     static QByteArray encodePartIdentifier(PartNamespace ns, const QByteArray &label);
0129 
0130     /**
0131       Decode part label and namespace.
0132     */
0133     static QByteArray decodePartIdentifier(const QByteArray &data, PartNamespace &ns);
0134 
0135     /**
0136       Converts the given set of items into a protocol representation.
0137       @throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers.
0138     */
0139     template<typename T, template<typename> class Container>
0140     static Scope entitySetToScope(const Container<T> &_objects)
0141     {
0142         if (_objects.isEmpty()) {
0143             throw Exception("No objects specified");
0144         }
0145 
0146         Container<T> objects(_objects);
0147         using namespace std::placeholders;
0148         std::sort(objects.begin(), objects.end(), [](const T &a, const T &b) -> bool {
0149             return a.id() < b.id();
0150         });
0151         if (objects.at(0).isValid()) {
0152             QList<typename T::Id> uids;
0153             uids.reserve(objects.size());
0154             for (const T &object : objects) {
0155                 uids << object.id();
0156             }
0157             ImapSet set;
0158             set.add(uids);
0159             return Scope(set);
0160         }
0161 
0162         if (entitySetHasGID(_objects)) {
0163             return entitySetToGID(_objects);
0164         }
0165 
0166         if (!entitySetHasRemoteIdentifier(_objects, std::mem_fn(&T::remoteId))) {
0167             throw Exception("No remote identifier specified");
0168         }
0169 
0170         // check if we have RIDs or HRIDs
0171         if (entitySetHasHRID(_objects)) {
0172             return hierarchicalRidToScope(objects.first());
0173         }
0174 
0175         return entitySetToRemoteIdentifier(Scope::Rid, _objects, std::mem_fn(&T::remoteId));
0176     }
0177 
0178     static Protocol::ScopeContext commandContextToProtocol(const Akonadi::Collection &collection, const Akonadi::Tag &tag, const Item::List &requestedItems);
0179 
0180     /**
0181       Converts the given object identifier into a protocol representation.
0182       @throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers.
0183     */
0184     template<typename T>
0185     static Scope entityToScope(const T &object)
0186     {
0187         return entitySetToScope(QList<T>() << object);
0188     }
0189 
0190     /**
0191       Converts the given collection's hierarchical RID into a protocol representation.
0192       Assumes @p col has a valid hierarchical RID, so check that before!
0193     */
0194     static Scope hierarchicalRidToScope(const Collection &col);
0195 
0196     /**
0197       Converts the HRID of the given item into an ASAP protocol representation.
0198       Assumes @p item has a valid HRID.
0199     */
0200     static Scope hierarchicalRidToScope(const Item &item);
0201 
0202     static Scope hierarchicalRidToScope(const Tag & /*tag*/)
0203     {
0204         assert(false);
0205         return Scope();
0206     }
0207 
0208     /**
0209       Converts a given ItemFetchScope object into a protocol representation.
0210     */
0211     static Protocol::ItemFetchScope itemFetchScopeToProtocol(const ItemFetchScope &fetchScope);
0212     static ItemFetchScope parseItemFetchScope(const Protocol::ItemFetchScope &fetchScope);
0213 
0214     static Protocol::CollectionFetchScope collectionFetchScopeToProtocol(const CollectionFetchScope &fetchScope);
0215     static CollectionFetchScope parseCollectionFetchScope(const Protocol::CollectionFetchScope &fetchScope);
0216 
0217     static Protocol::TagFetchScope tagFetchScopeToProtocol(const TagFetchScope &fetchScope);
0218     static TagFetchScope parseTagFetchScope(const Protocol::TagFetchScope &fetchScope);
0219 
0220     /**
0221      * Parses a single line from an item fetch job result into an Item object.
0222      * FIXME: std::optional
0223      */
0224     static Item
0225     parseItemFetchResult(const Protocol::FetchItemsResponse &data, const ItemFetchScope *fetchScope = nullptr, ProtocolHelperValuePool *valuePool = nullptr);
0226     static Tag parseTagFetchResult(const Protocol::FetchTagsResponse &data);
0227     static Relation parseRelationFetchResult(const Protocol::FetchRelationsResponse &data);
0228 
0229     static bool streamPayloadToFile(const QString &file, const QByteArray &data, QByteArray &error);
0230 
0231     static Akonadi::Tristate listPreference(const Collection::ListPreference pref);
0232 
0233 private:
0234     template<typename T, template<typename> class Container>
0235     inline static bool entitySetHasGID(const Container<T> &objects)
0236     {
0237         if constexpr (std::is_same_v<T, Akonadi::Collection>) {
0238             Q_UNUSED(objects);
0239             return false;
0240         } else {
0241             return entitySetHasRemoteIdentifier(objects, std::mem_fn(&T::gid));
0242         }
0243     }
0244 
0245     template<typename T, template<typename> class Container>
0246     inline static Scope entitySetToGID(const Container<T> &objects)
0247     {
0248         if constexpr (std::is_same_v<T, Akonadi::Collection>) {
0249             Q_UNUSED(objects);
0250             return Scope();
0251         } else {
0252             return entitySetToRemoteIdentifier(Scope::Gid, objects, std::mem_fn(&T::gid));
0253         }
0254     }
0255 
0256     template<typename T, template<typename> class Container, typename RIDFunc>
0257     inline static bool entitySetHasRemoteIdentifier(const Container<T> &objects, const RIDFunc &ridFunc)
0258     {
0259         return std::find_if(objects.constBegin(),
0260                             objects.constEnd(),
0261                             [=](const T &obj) {
0262                                 return ridFunc(obj).isEmpty();
0263                             })
0264             == objects.constEnd();
0265     }
0266 
0267     template<typename T, template<typename> class Container, typename RIDFunc>
0268     inline static Scope entitySetToRemoteIdentifier(Scope::SelectionScope scope, const Container<T> &objects, RIDFunc &&ridFunc)
0269     {
0270         QStringList rids;
0271         rids.reserve(objects.size());
0272         std::transform(objects.cbegin(), objects.cend(), std::back_inserter(rids), [=](const T &obj) -> QString {
0273             if constexpr (std::is_same_v<QString, std::remove_cvref_t<std::invoke_result_t<RIDFunc, const T &>>>) {
0274                 return ridFunc(obj);
0275             } else {
0276                 return QString::fromLatin1(ridFunc(obj));
0277             }
0278         });
0279         return Scope(scope, rids);
0280     }
0281 
0282     template<typename T, template<typename> class Container>
0283     inline static bool entitySetHasHRID(const Container<T> &objects)
0284     {
0285         if constexpr (std::is_same_v<T, Tag>) {
0286             return false;
0287         } else {
0288             return objects.size() == 1
0289                 && std::find_if(objects.constBegin(),
0290                                 objects.constEnd(),
0291                                 [](const T &obj) -> bool {
0292                                     return !CollectionUtils::hasValidHierarchicalRID(obj);
0293                                 })
0294                 == objects.constEnd(); // ### HRID sets are not yet specified
0295         }
0296     }
0297 };
0298 
0299 }