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