1 /*
2     SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #pragma once
8 
9 class KJob;
10 
11 #include "collectionfetchjob.h"
12 #include "item.h"
13 #include "itemfetchscope.h"
14 #include "mimetypechecker.h"
15 
16 #include "entitytreemodel.h"
17 
18 #include "akonaditests_export.h"
19 
20 #include <QLoggingCategory>
21 
Q_DECLARE_LOGGING_CATEGORY(DebugETM)22 Q_DECLARE_LOGGING_CATEGORY(DebugETM)
23 
24 namespace Akonadi
25 {
26 class Monitor;
27 class AgentInstance;
28 }
29 
30 struct Node {
31     using Id = qint64;
32     enum Type : char {
33         Item,
34         Collection,
35     };
36 
NodeNode37     explicit Node(Type type, Id id, Akonadi::Collection::Id parentId)
38         : id(id)
39         , parent(parentId)
40         , type(type)
41     {
42     }
43 
isItemNode44     static bool isItem(Node *node)
45     {
46         return node->type == Node::Item;
47     }
48 
isCollectionNode49     static bool isCollection(Node *node)
50     {
51         return node->type == Node::Collection;
52     }
53 
54     Id id;
55     Akonadi::Collection::Id parent;
56     Type type;
57 };
58 
59 template<typename Key, typename Value> class RefCountedHash
60 {
61     mutable Value *defaultValue = nullptr;
62 
63 public:
64     explicit RefCountedHash() = default;
65     Q_DISABLE_COPY_MOVE(RefCountedHash)
66 
~RefCountedHash()67     ~RefCountedHash()
68     {
69         delete defaultValue;
70     }
71 
begin()72     inline auto begin()
73     {
74         return mHash.begin();
75     }
end()76     inline auto end()
77     {
78         return mHash.end();
79     }
begin()80     inline auto begin() const
81     {
82         return mHash.begin();
83     }
end()84     inline auto end() const
85     {
86         return mHash.end();
87     }
find(const Key & key)88     inline auto find(const Key &key) const
89     {
90         return mHash.find(key);
91     }
find(const Key & key)92     inline auto find(const Key &key)
93     {
94         return mHash.find(key);
95     }
96 
size()97     inline bool size() const
98     {
99         return mHash.size();
100     }
isEmpty()101     inline bool isEmpty() const
102     {
103         return mHash.isEmpty();
104     }
105 
clear()106     inline void clear()
107     {
108         mHash.clear();
109     }
contains(const Key & key)110     inline bool contains(const Key &key) const
111     {
112         return mHash.contains(key);
113     }
114 
value(const Key & key)115     inline const Value &value(const Key &key) const
116     {
117         auto it = mHash.find(key);
118         if (it == mHash.end()) {
119             return defaultValue ? *defaultValue : *(defaultValue = new Value());
120         }
121         return it->value;
122     }
123 
124     inline const Value &operator[](const Key &key) const
125     {
126         return value(key);
127     }
128 
ref(const Key & key,const Value & value)129     inline auto ref(const Key &key, const Value &value)
130     {
131         auto it = mHash.find(key);
132         if (it != mHash.end()) {
133             ++(it->refCnt);
134             return it;
135         } else {
136             return mHash.insert(key, {1, std::move(value)});
137         }
138     }
139 
unref(const Key & key)140     inline void unref(const Key &key)
141     {
142         auto it = mHash.find(key);
143         if (it == mHash.end()) {
144             return;
145         }
146         --(it->refCnt);
147         if (it->refCnt == 0) {
148             mHash.erase(it);
149         }
150     }
151 
152 private:
153     template<typename V> struct RefCountedValue {
154         uint8_t refCnt = 0;
155         V value;
156     };
157     QHash<Key, RefCountedValue<Value>> mHash;
158 };
159 
160 namespace Akonadi
161 {
162 /**
163  * @internal
164  */
165 class AKONADI_TESTS_EXPORT EntityTreeModelPrivate
166 {
167 public:
168     explicit EntityTreeModelPrivate(EntityTreeModel *parent);
169     ~EntityTreeModelPrivate();
170     EntityTreeModel *const q_ptr;
171 
172     enum RetrieveDepth {
173         Base,
174         Recursive,
175     };
176 
177     void init(Monitor *monitor);
178 
179     void prependNode(Node *node);
180     void appendNode(Node *node);
181 
182     void fetchCollections(const Collection &collection, CollectionFetchJob::Type type = CollectionFetchJob::FirstLevel);
183     void fetchCollections(const Collection::List &collections, CollectionFetchJob::Type type = CollectionFetchJob::FirstLevel);
184     void fetchCollections(Akonadi::CollectionFetchJob *job);
185     void fetchItems(const Collection &collection);
186     void collectionsFetched(const Akonadi::Collection::List &collections);
187     void itemsFetched(const Akonadi::Item::List &items);
188     void itemsFetched(const Collection::Id collectionId, const Akonadi::Item::List &items);
189 
190     void monitoredCollectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent);
191     void monitoredCollectionRemoved(const Akonadi::Collection &collection);
192     void monitoredCollectionChanged(const Akonadi::Collection &collection);
193     void monitoredCollectionStatisticsChanged(Akonadi::Collection::Id, const Akonadi::CollectionStatistics &statistics);
194     void
195     monitoredCollectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destCollection);
196 
197     void monitoredItemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection);
198     void monitoredItemRemoved(const Akonadi::Item &item, const Akonadi::Collection &collection = Akonadi::Collection());
199     void monitoredItemChanged(const Akonadi::Item &item, const QSet<QByteArray> &);
200     void monitoredItemMoved(const Akonadi::Item &item, const Akonadi::Collection &, const Akonadi::Collection &);
201 
202     void monitoredItemLinked(const Akonadi::Item &item, const Akonadi::Collection &);
203     void monitoredItemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &);
204 
205     void monitoredMimeTypeChanged(const QString &mimeType, bool monitored);
206     void monitoredCollectionsChanged(const Akonadi::Collection &collection, bool monitored);
207     void monitoredItemsChanged(const Akonadi::Item &item, bool monitored);
208     void monitoredResourcesChanged(const QByteArray &resource, bool monitored);
209 
210     Collection::List getParentCollections(const Item &item) const;
211     void removeChildEntities(Collection::Id collectionId);
212 
213     /**
214      * Returns the list of names of the child collections of @p collection.
215      */
216     QStringList childCollectionNames(const Collection &collection) const;
217 
218     /**
219      * Fetch parent collections and insert this @p collection and its parents into the node tree
220      *
221      * Returns whether the ancestor chain was complete and the parent collections were inserted into
222      * the tree.
223      */
224     bool retrieveAncestors(const Akonadi::Collection &collection, bool insertBaseCollection = true);
225     void ancestorsFetched(const Akonadi::Collection::List &collectionList);
226     void insertCollection(const Akonadi::Collection &collection, const Akonadi::Collection &parent);
227 
228     void beginResetModel();
229     void endResetModel();
230     /**
231      * Start function for filling the Model, finds and fetches the root of the node tree
232      * Next relevant function for filling the model is startFirstListJob()
233      */
234     void fillModel();
235 
236     void changeFetchState(const Collection &parent);
237     void agentInstanceRemoved(const Akonadi::AgentInstance &instance);
238 
239     QIcon iconForName(const QString &name) const;
240 
241     QHash<Collection::Id, Collection> m_collections;
242     RefCountedHash<Item::Id, Item> m_items;
243     QHash<Collection::Id, QList<Node *>> m_childEntities;
244     QSet<Collection::Id> m_populatedCols;
245     QSet<Collection::Id> m_collectionsWithoutItems;
246 
247     QVector<Item::Id> m_pendingCutItems;
248     QVector<Item::Id> m_pendingCutCollections;
249     mutable QSet<Collection::Id> m_pendingCollectionRetrieveJobs;
250     mutable QSet<KJob *> m_pendingCollectionFetchJobs;
251 
252     // Icon cache to workaround QIcon::fromTheme being very slow (bug #346644)
253     mutable QHash<QString, QIcon> m_iconCache;
254     mutable QString m_iconThemeName;
255 
256     Monitor *m_monitor = nullptr;
257     Collection m_rootCollection;
258     Node *m_rootNode = nullptr;
259     bool m_needDeleteRootNode = false;
260     QString m_rootCollectionDisplayName;
261     QStringList m_mimeTypeFilter;
262     MimeTypeChecker m_mimeChecker;
263     EntityTreeModel::CollectionFetchStrategy m_collectionFetchStrategy = EntityTreeModel::FetchCollectionsRecursive;
264     EntityTreeModel::ItemPopulationStrategy m_itemPopulation = EntityTreeModel::ImmediatePopulation;
265     CollectionFetchScope::ListFilter m_listFilter = CollectionFetchScope::NoFilter;
266     bool m_includeStatistics = false;
267     bool m_showRootCollection = false;
268     bool m_collectionTreeFetched = false;
269     bool m_showSystemEntities = false;
270     Session *m_session = nullptr;
271     /**
272      * Called after the root collection was fetched by fillModel
273      *
274      * Initiates further fetching of collections depending on the monitored collections
275      * (in the monitor) and the m_collectionFetchStrategy.
276      *
277      * Further collections are either fetched directly with fetchCollections and
278      * fetchItems or, in case that collections or resources are monitored explicitly
279      * via fetchTopLevelCollections
280      */
281     void startFirstListJob();
282 
283     void serverStarted();
284 
285     void monitoredItemsRetrieved(KJob *job);
286     void rootFetchJobDone(KJob *job);
287     void collectionFetchJobDone(KJob *job);
288     void itemFetchJobDone(Collection::Id collectionId, KJob *job);
289     void updateJobDone(KJob *job);
290     void pasteJobDone(KJob *job);
291 
292     /**
293      * Returns the index of the node in @p list with the id @p id. Returns -1 if not found.
294      */
indexOf(const QList<Node * > & nodes,Node::Id id)295     template<Node::Type Type> int indexOf(const QList<Node *> &nodes, Node::Id id) const
296     {
297         int i = 0;
298         for (const Node *node : nodes) {
299             if (node->id == id && node->type == Type) {
300                 return i;
301             }
302             i++;
303         }
304 
305         return -1;
306     }
307 
308     Q_DECLARE_PUBLIC(EntityTreeModel)
309 
310     void fetchTopLevelCollections() const;
311     void topLevelCollectionsFetched(const Akonadi::Collection::List &collectionList);
312 
313     /**
314       @returns True if @p item or one of its descendants is hidden.
315     */
316     bool isHidden(const Item &item) const;
317     bool isHidden(const Collection &collection) const;
318 
319     template<typename T> bool isHiddenImpl(const T &entity, Node::Type type) const;
320 
321     void ref(Collection::Id id);
322     void deref(Collection::Id id);
323 
324     /**
325      * @returns true if the collection is actively monitored (referenced or buffered with refcounting enabled)
326      *
327      * purely for testing
328      */
329     bool isMonitored(Collection::Id id) const;
330 
331     /**
332      * @returns true if the collection is buffered
333      *
334      * purely for testing
335      */
336     bool isBuffered(Collection::Id id) const;
337 
338     /**
339       @returns true if the Collection with the id of @p id should be purged.
340     */
341     bool shouldPurge(Collection::Id id) const;
342 
343     /**
344       Purges the items in the Collection @p id
345     */
346     void purgeItems(Collection::Id id);
347 
348     /**
349       Removes the items starting from @p it and up to a maximum of @p end in Collection @p col. @p pos should be the index of @p it
350       in the m_childEntities before calling, and is updated to the position of the next Collection in m_childEntities afterward.
351       This is required to emit model remove signals properly.
352 
353       @returns an iterator pointing to the next Collection after @p it, or at @p end
354     */
355     QList<Node *>::iterator removeItems(QList<Node *>::iterator it, const QList<Node *>::iterator &end, int *pos, const Collection &col);
356 
357     /**
358       Skips over Collections in m_childEntities up to a maximum of @p end. @p it is an iterator pointing to the first Collection
359       in a block of Collections, and @p pos initially describes the index of @p it in m_childEntities and is updated to point to
360       the index of the next Item in the list.
361 
362       @returns an iterator pointing to the next Item after @p it, or at @p end
363     */
364     QList<Node *>::iterator skipCollections(QList<Node *>::iterator it, const QList<Node *>::iterator &end, int *pos);
365 
366     /**
367       Emits the data changed signal for the entire row as in the subclass, instead of just for the first column.
368     */
369     void dataChanged(const QModelIndex &top, const QModelIndex &bottom);
370 
371     /**
372      * Returns the model index for the given @p collection.
373      */
374     QModelIndex indexForCollection(const Collection &collection) const;
375 
376     /**
377      * Returns the model indexes for the given @p item.
378      */
379     QModelIndexList indexesForItem(const Item &item) const;
380 
381     bool canFetchMore(const QModelIndex &parent) const;
382 
383     /**
384      * Returns true if the collection matches all filters and should be part of the model.
385      * This method checks all properties that could change by modifying the collection.
386      * Currently that includes:
387      * * hidden attribute
388      * * content mime types
389      */
390     bool shouldBePartOfModel(const Collection &collection) const;
391     bool hasChildCollection(const Collection &collection) const;
392     bool isAncestorMonitored(const Collection &collection) const;
393 };
394 
395 }
396 
397