1/*
2 Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "entitytreemodel_p.h"
21#include "entitytreemodel.h"
22
23#include "agentmanagerinterface.h"
24#include "dbusconnectionpool.h"
25#include "monitor_p.h" // For friend ref/deref
26#include "servermanager.h"
27
28#include <KDE/KLocalizedString>
29#include <KDE/KMessageBox>
30#include <KDE/KUrl>
31
32#include <akonadi/agentmanager.h>
33#include <akonadi/agenttype.h>
34#include <akonadi/changerecorder.h>
35#include <akonadi/collectioncopyjob.h>
36#include <akonadi/collectionfetchjob.h>
37#include <akonadi/collectionfetchscope.h>
38#include <akonadi/collectionmovejob.h>
39#include <akonadi/collectionstatistics.h>
40#include <akonadi/collectionstatisticsjob.h>
41#include <akonadi/entityhiddenattribute.h>
42#include <akonadi/itemcopyjob.h>
43#include <akonadi/itemfetchjob.h>
44#include <akonadi/itemmodifyjob.h>
45#include <akonadi/itemmovejob.h>
46#include <akonadi/linkjob.h>
47#include <akonadi/private/protocol_p.h>
48#include <akonadi/session.h>
49#include <akonadi/servermanager.h>
50
51#include <kdebug.h>
52
53/// comment this out to track time spent on jobs created by the ETM
54// #define DBG_TRACK_JOB_TIMES
55
56#ifdef DBG_TRACK_JOB_TIMES
57QMap<KJob *, QTime> jobTimeTracker;
58#define ifDebug(x) x
59#else
60#define ifDebug(x)
61#endif
62
63using namespace Akonadi;
64
65static CollectionFetchJob::Type getFetchType(EntityTreeModel::CollectionFetchStrategy strategy)
66{
67 switch(strategy) {
68 case EntityTreeModel::FetchFirstLevelChildCollections:
69 return CollectionFetchJob::FirstLevel;
70 case EntityTreeModel::InvisibleCollectionFetch:
71 case EntityTreeModel::FetchCollectionsRecursive:
72 default:
73 break;
74 }
75 return CollectionFetchJob::Recursive;
76}
77
78
79EntityTreeModelPrivate::EntityTreeModelPrivate(EntityTreeModel *parent)
80 : q_ptr(parent)
81 , m_rootNode(0)
82 , m_collectionFetchStrategy(EntityTreeModel::FetchCollectionsRecursive)
83 , m_itemPopulation(EntityTreeModel::ImmediatePopulation)
84 , m_listFilter(CollectionFetchScope::NoFilter)
85 , m_includeStatistics(false)
86 , m_showRootCollection(false)
87 , m_collectionTreeFetched(false)
88 , m_showSystemEntities(false)
89{
90 // using collection as a parameter of a queued call in runItemFetchJob()
91 qRegisterMetaType<Collection>();
92
93 org::freedesktop::Akonadi::AgentManager *manager =
94 new org::freedesktop::Akonadi::AgentManager(ServerManager::serviceName(Akonadi::ServerManager::Control),
95 QLatin1String("/AgentManager"),
96 DBusConnectionPool::threadConnection(), q_ptr);
97
98 QObject::connect(manager, SIGNAL(agentInstanceAdvancedStatusChanged(QString,QVariantMap)),
99 q_ptr, SLOT(agentInstanceAdvancedStatusChanged(QString,QVariantMap)));
100
101 Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self();
102 QObject::connect(agentManager, SIGNAL(instanceRemoved(Akonadi::AgentInstance)),
103 q_ptr, SLOT(agentInstanceRemoved(Akonadi::AgentInstance)));
104
105}
106
107EntityTreeModelPrivate::~EntityTreeModelPrivate()
108{
109}
110
111void EntityTreeModelPrivate::init(ChangeRecorder *monitor)
112{
113 Q_Q(EntityTreeModel);
114 m_monitor = monitor;
115 // The default is to FetchCollectionsRecursive, so we tell the monitor to fetch collections
116 // That way update signals from the monitor will contain the full collection.
117 // This may be updated if the CollectionFetchStrategy is changed.
118 m_monitor->fetchCollection(true);
119 m_session = m_monitor->session();
120
121 m_monitor->setChangeRecordingEnabled(false);
122
123 m_rootCollectionDisplayName = QLatin1String("[*]");
124
125 m_includeStatistics = true;
126 m_monitor->fetchCollectionStatistics(true);
127 m_monitor->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All);
128
129 q->connect(monitor, SIGNAL(mimeTypeMonitored(QString,bool)),
130 SLOT(monitoredMimeTypeChanged(QString,bool)));
131 q->connect(monitor, SIGNAL(collectionMonitored(Akonadi::Collection,bool)),
132 SLOT(monitoredCollectionsChanged(Akonadi::Collection,bool)));
133 q->connect(monitor, SIGNAL(itemMonitored(Akonadi::Item,bool)),
134 SLOT(monitoredItemsChanged(Akonadi::Item,bool)));
135 q->connect(monitor, SIGNAL(resourceMonitored(QByteArray,bool)),
136 SLOT(monitoredResourcesChanged(QByteArray,bool)));
137
138 // monitor collection changes
139 q->connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection)),
140 SLOT(monitoredCollectionChanged(Akonadi::Collection)));
141 q->connect(monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)),
142 SLOT(monitoredCollectionAdded(Akonadi::Collection,Akonadi::Collection)));
143 q->connect(monitor, SIGNAL(collectionRemoved(Akonadi::Collection)),
144 SLOT(monitoredCollectionRemoved(Akonadi::Collection)));
145 q->connect(monitor,
146 SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)),
147 SLOT(monitoredCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)));
148
149 // Monitor item changes.
150 q->connect(monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)),
151 SLOT(monitoredItemAdded(Akonadi::Item,Akonadi::Collection)));
152 q->connect(monitor, SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)),
153 SLOT(monitoredItemChanged(Akonadi::Item,QSet<QByteArray>)));
154 q->connect(monitor, SIGNAL(itemRemoved(Akonadi::Item)),
155 SLOT(monitoredItemRemoved(Akonadi::Item)));
156 q->connect(monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)),
157 SLOT(monitoredItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)));
158
159 q->connect(monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)),
160 SLOT(monitoredItemLinked(Akonadi::Item,Akonadi::Collection)));
161 q->connect(monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)),
162 SLOT(monitoredItemUnlinked(Akonadi::Item,Akonadi::Collection)));
163
164 q->connect(monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)),
165 SLOT(monitoredCollectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)));
166
167 Akonadi::ServerManager *serverManager = Akonadi::ServerManager::self();
168 q->connect(serverManager, SIGNAL(started()), SLOT(serverStarted()));
169
170 QHash<int, QByteArray> names = q->roleNames();
171
172 names.insert(EntityTreeModel::UnreadCountRole, "unreadCount");
173 names.insert(EntityTreeModel::FetchStateRole, "fetchState");
174 names.insert(EntityTreeModel::CollectionSyncProgressRole, "collectionSyncProgress");
175 names.insert(EntityTreeModel::ItemIdRole, "itemId");
176
177 q->setRoleNames(names);
178
179 fillModel();
180}
181
182void EntityTreeModelPrivate::serverStarted()
183{
184 // Don't emit about to be reset. Too late for that
185 endResetModel();
186}
187
188void EntityTreeModelPrivate::changeFetchState(const Collection &parent)
189{
190 Q_Q(EntityTreeModel);
191 const QModelIndex collectionIndex = indexForCollection(parent);
192 if (!collectionIndex.isValid()) {
193 // Because we are called delayed, it is possible that @p parent has been deleted.
194 return;
195 }
196 q->dataChanged(collectionIndex, collectionIndex);
197}
198
199void EntityTreeModelPrivate::agentInstanceRemoved(const Akonadi::AgentInstance &instance)
200{
201 Q_Q(EntityTreeModel);
202 if (!instance.type().capabilities().contains(QLatin1String("Resource"))) {
203 return;
204 }
205
206 if (m_rootCollection.isValid()) {
207 if (m_rootCollection != Collection::root()) {
208 if (m_rootCollection.resource() == instance.identifier()) {
209 q->clearAndReset();
210 }
211 return;
212 }
213 foreach (Node *node, m_childEntities[Collection::root().id()]) {
214 Q_ASSERT(node->type == Node::Collection);
215
216 const Collection collection = m_collections[node->id];
217 if (collection.resource() == instance.identifier()) {
218 monitoredCollectionRemoved(collection);
219 }
220 }
221 }
222}
223
224void EntityTreeModelPrivate::agentInstanceAdvancedStatusChanged(const QString &, const QVariantMap &status)
225{
226 const QString key = status.value(QLatin1String("key")).toString();
227 if (key != QLatin1String("collectionSyncProgress")) {
228 return;
229 }
230
231 const Collection::Id collectionId = status.value(QLatin1String("collectionId")).toLongLong();
232 const uint percent = status.value(QLatin1String("percent")).toUInt();
233 if (m_collectionSyncProgress.value(collectionId) == percent) {
234 return;
235 }
236 m_collectionSyncProgress.insert(collectionId, percent);
237
238 const QModelIndex collectionIndex = indexForCollection(Collection(collectionId));
239 if (!collectionIndex.isValid()) {
240 return;
241 }
242
243 Q_Q(EntityTreeModel);
244 // This is really slow (80 levels of method calls in proxy models...), and called
245 // very often during an imap sync...
246 q->dataChanged(collectionIndex, collectionIndex);
247}
248
249void EntityTreeModelPrivate::fetchItems(const Collection &parent)
250{
251 Q_Q(const EntityTreeModel);
252 Q_ASSERT(parent.isValid());
253 Q_ASSERT(m_collections.contains(parent.id()));
254 // TODO: Use a more specific fetch scope to get only the envelope for mails etc.
255 ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(parent, m_session);
256 itemFetchJob->setFetchScope(m_monitor->itemFetchScope());
257 itemFetchJob->fetchScope().setAncestorRetrieval(ItemFetchScope::All);
258 itemFetchJob->fetchScope().setIgnoreRetrievalErrors(true);
259 itemFetchJob->setDeliveryOption(ItemFetchJob::EmitItemsInBatches);
260
261 itemFetchJob->setProperty(FetchCollectionId(), QVariant(parent.id()));
262
263 if (m_showRootCollection || parent != m_rootCollection) {
264 m_pendingCollectionRetrieveJobs.insert(parent.id());
265
266 // If collections are not in the model, there will be no valid index for them.
267 if ((m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) &&
268 (m_collectionFetchStrategy != EntityTreeModel::FetchNoCollections)) {
269 // We need to invoke this delayed because we would otherwise be emitting a sequence like
270 // - beginInsertRows
271 // - dataChanged
272 // - endInsertRows
273 // which would confuse proxies.
274 QMetaObject::invokeMethod(const_cast<EntityTreeModel *>(q), "changeFetchState", Qt::QueuedConnection, Q_ARG(Akonadi::Collection, parent));
275 }
276 }
277
278 q->connect(itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)),
279 q, SLOT(itemsFetched(Akonadi::Item::List)));
280 q->connect(itemFetchJob, SIGNAL(result(KJob*)),
281 q, SLOT(itemFetchJobDone(KJob*)));
282 ifDebug(kDebug() << "collection:" << parent.name(); jobTimeTracker[itemFetchJob].start();)
283}
284
285void EntityTreeModelPrivate::fetchCollections(Akonadi::CollectionFetchJob *job)
286{
287 Q_Q(EntityTreeModel);
288
289 job->fetchScope().setListFilter(m_listFilter);
290 job->fetchScope().setContentMimeTypes(m_monitor->mimeTypesMonitored());
291
292 if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) {
293 q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
294 q, SLOT(collectionListFetched(Akonadi::Collection::List)));
295 } else {
296 job->fetchScope().setIncludeStatistics(m_includeStatistics);
297 job->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All);
298 q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
299 q, SLOT(collectionsFetched(Akonadi::Collection::List)));
300 q->connect(job, SIGNAL(result(KJob*)),
301 q, SLOT(finalCollectionFetchJobDone(KJob*)));
302 }
303 q->connect(job, SIGNAL(result(KJob*)),
304 q, SLOT(collectionFetchJobDone(KJob*)));
305 ifDebug(kDebug() << "collection:" << collection.name(); jobTimeTracker[job].start();)
306}
307
308void EntityTreeModelPrivate::fetchCollections(const Collection::List &collections, CollectionFetchJob::Type type)
309{
310 fetchCollections(new CollectionFetchJob(collections, type, m_session));
311}
312
313void EntityTreeModelPrivate::fetchCollections(const Collection &collection, CollectionFetchJob::Type type)
314{
315 Q_ASSERT(collection.isValid());
316 CollectionFetchJob *job = new CollectionFetchJob(collection, type, m_session);
317 fetchCollections(job);
318}
319
320// Specialization needs to be in the same namespace as the definition
321namespace Akonadi {
322
323template<>
324bool EntityTreeModelPrivate::isHidden<Akonadi::Collection>(const Akonadi::Collection &entity) const
325{
326 return isHidden(entity, Node::Collection);
327}
328
329template<>
330bool EntityTreeModelPrivate::isHidden<Akonadi::Item>(const Akonadi::Item &entity) const
331{
332 return isHidden(entity, Node::Item);
333}
334
335}
336
337bool EntityTreeModelPrivate::isHidden(const Entity &entity, Node::Type type) const
338{
339 if (m_showSystemEntities) {
340 return false;
341 }
342
343 if (type == Node::Collection &&
344 entity.id() == m_rootCollection.id()) {
345 return false;
346 }
347
348 if (entity.hasAttribute<EntityHiddenAttribute>()) {
349 return true;
350 }
351
352 const Collection parent = entity.parentCollection();
353 if (parent.isValid()) {
354 return isHidden(parent, Node::Collection);
355 }
356
357 return false;
358}
359
360void EntityTreeModelPrivate::collectionListFetched(const Akonadi::Collection::List &collections)
361{
362 QListIterator<Akonadi::Collection> it(collections);
363
364 while (it.hasNext()) {
365 const Collection collection = it.next();
366
367 if (isHidden(collection)) {
368 continue;
369 }
370
371 m_collections.insert(collection.id(), collection);
372
373 Node *node = new Node;
374 node->id = collection.id();
375 node->parent = -1;
376 node->type = Node::Collection;
377 m_childEntities[-1].prepend(node);
378
379 fetchItems(collection);
380 }
381}
382
383void EntityTreeModelPrivate::collectionsFetched(const Akonadi::Collection::List &collections)
384{
385 Q_Q(EntityTreeModel);
386 QTime t;
387 t.start();
388
389 QListIterator<Akonadi::Collection> it(collections);
390
391 QHash<Collection::Id, Collection> collectionsToInsert;
392 QHash<Collection::Id, QVector<Collection::Id> > subTreesToInsert;
393 QHash<Collection::Id, Collection> parents;
394
395 while (it.hasNext()) {
396 const Collection collection = it.next();
397
398 const Collection::Id collectionId = collection.id();
399
400 // If a collection is hidden, we still need to put it in the model if it has a
401 // non-hidden child. We rely on the fact that children will be returned
402 // first and will be in collectionsToInsert (if returned in this batch)
403 // or will already be in the model as a dummy node in m_collections
404 // if returned and processed in an earlier batch.
405 if (isHidden(collection) &&
406 !collectionsToInsert.contains(collectionId) &&
407 !m_collections.contains(collectionId)) {
408 continue;
409 }
410
411 if (m_collections.contains(collectionId)) {
412 // This is probably the result of a parent of a previous collection already being in the model.
413 // Replace the dummy collection with the real one and move on.
414
415 // This could also be the result of a monitor signal having already inserted the collection
416 // into this model. There's no way to tell, so we just emit dataChanged.
417
418 m_collections[collectionId] = collection;
419
420 const QModelIndex collectionIndex = indexForCollection(collection);
421 dataChanged(collectionIndex, collectionIndex);
422 emit q->collectionFetched(collectionId);
423 continue;
424 }
425
426 //If we're monitoring collections somewhere in the tree we need to retrieve their ancestors now
427 if (collection.parentCollection() != m_rootCollection && m_monitor->collectionsMonitored().contains(collection)) {
428 retrieveAncestors(collection, false);
429 }
430
431 Collection parent = collection;
432
433 while (!m_collections.contains(parent.parentCollection().id())) {
434 if (!subTreesToInsert[parent.parentCollection().id()].contains(parent.parentCollection().id())) {
435 subTreesToInsert[parent.parentCollection().id()].append(parent.parentCollection().id());
436 collectionsToInsert.insert(parent.parentCollection().id(), parent.parentCollection());
437 }
438
439 foreach (Collection::Id collectionId, subTreesToInsert.take(parent.id())) {
440 if (!subTreesToInsert[parent.parentCollection().id()].contains(collectionId)) {
441 subTreesToInsert[parent.parentCollection().id()].append(collectionId);
442 }
443 }
444
445 parent = parent.parentCollection();
446 }
447
448 if (!subTreesToInsert[parent.id()].contains(collectionId)) {
449 subTreesToInsert[parent.id()].append(collectionId);
450 }
451
452 collectionsToInsert.insert(collectionId, collection);
453 if (!parents.contains(parent.id())) {
454 parents.insert(parent.id(), parent.parentCollection());
455 }
456 }
457
458 const int row = 0;
459
460 QHashIterator<Collection::Id, QVector<Collection::Id> > collectionIt(subTreesToInsert);
461 while (collectionIt.hasNext()) {
462 collectionIt.next();
463
464 const Collection::Id topCollectionId = collectionIt.key();
465
466 Q_ASSERT(!m_collections.contains(topCollectionId));
467
468 Q_ASSERT(parents.contains(topCollectionId));
469 const QModelIndex parentIndex = indexForCollection(parents.value(topCollectionId));
470
471 q->beginInsertRows(parentIndex, row, row);
472 Q_ASSERT(!collectionIt.value().isEmpty());
473 Q_ASSERT(m_collections.contains(parents.value(topCollectionId).id()));
474
475 foreach (Collection::Id collectionId, collectionIt.value()) {
476 const Collection collection = collectionsToInsert.take(collectionId);
477 Q_ASSERT(collection.isValid());
478
479 m_collections.insert(collectionId, collection);
480
481 Node *node = new Node;
482 node->id = collectionId;
483 Q_ASSERT(collection.parentCollection().isValid());
484 node->parent = collection.parentCollection().id();
485 node->type = Node::Collection;
486 m_childEntities[node->parent].prepend(node);
487 }
488 q->endInsertRows();
489
490 if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) {
491 foreach (const Collection::Id &collectionId, collectionIt.value()) {
492 fetchItems(m_collections.value(collectionId));
493 }
494 }
495 }
496}
497
498void EntityTreeModelPrivate::itemsFetched(const Akonadi::Item::List &items)
499{
500 Q_Q(EntityTreeModel);
501
502 const Collection::Id collectionId = q->sender()->property(FetchCollectionId()).value<Collection::Id>();
503
504 itemsFetched(collectionId, items);
505}
506
507void EntityTreeModelPrivate::itemsFetched(const Collection::Id collectionId, const Akonadi::Item::List &items)
508{
509 Q_Q(EntityTreeModel);
510
511 if (!m_collections.contains(collectionId)) {
512 kWarning() << "Collection has been removed while fetching items";
513 return;
514 }
515
516 Item::List itemsToInsert;
517
518 const Collection collection = m_collections.value(collectionId);
519
520 Q_ASSERT(collection.isValid());
521
522 // if there are any items at all, remove from set of collections known to be empty
523 if (!items.isEmpty()) {
524 m_collectionsWithoutItems.remove(collectionId);
525 }
526
527 foreach (const Item &item, items) {
528
529 if (isHidden(item)) {
530 continue;
531 }
532
533 if ((m_mimeChecker.wantedMimeTypes().isEmpty() ||
534 m_mimeChecker.isWantedItem(item))) {
535 // When listing virtual collections we might get results for items which are already in
536 // the model if their concrete collection has already been listed.
537 // In that case the collectionId should be different though.
538
539 // As an additional complication, new items might be both part of fetch job results and
540 // part of monitor notifications. We only insert items which are not already in the model
541 // considering their (possibly virtual) parent.
542 bool isNewItem = true;
543 if (m_items.contains(item.id())) {
544 const Akonadi::Collection::List parents = getParentCollections(item);
545 foreach (const Akonadi::Collection &parent, parents) {
546 if (parent.id() == collectionId) {
547 kWarning() << "Fetched an item which is already in the model";
548 // Update it in case the revision changed;
549 m_items[item.id()].apply(item);
550 isNewItem = false;
551 break;
552 }
553 }
554 }
555
556 if (isNewItem) {
557 itemsToInsert << item;
558 }
559 }
560 }
561
562 if (itemsToInsert.size() > 0) {
563 const Collection::Id colId = m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch ? m_rootCollection.id()
564 : m_collectionFetchStrategy == EntityTreeModel::FetchNoCollections ? m_rootCollection.id()
565 : collectionId;
566 const int startRow = m_childEntities.value(colId).size();
567
568 Q_ASSERT(m_collections.contains(colId));
569
570 const QModelIndex parentIndex = indexForCollection(m_collections.value(colId));
571 q->beginInsertRows(parentIndex, startRow, startRow + itemsToInsert.size() - 1);
572
573 foreach (const Item &item, itemsToInsert) {
574 const Item::Id itemId = item.id();
575 // Don't reinsert when listing virtual collections.
576 if (!m_items.contains(item.id())) {
577 m_items.insert(itemId, item);
578 }
579
580 Node *node = new Node;
581 node->id = itemId;
582 node->parent = collectionId;
583 node->type = Node::Item;
584
585 m_childEntities[colId].append(node);
586 }
587 q->endInsertRows();
588 }
589}
590
591void EntityTreeModelPrivate::monitoredMimeTypeChanged(const QString &mimeType, bool monitored)
592{
593 beginResetModel();
594 if (monitored) {
595 m_mimeChecker.addWantedMimeType(mimeType);
596 } else {
597 m_mimeChecker.removeWantedMimeType(mimeType);
598 }
599 endResetModel();
600}
601
602void EntityTreeModelPrivate::monitoredCollectionsChanged(const Akonadi::Collection &collection, bool monitored)
603{
604 if (monitored) {
605 const CollectionFetchJob::Type fetchType = getFetchType(m_collectionFetchStrategy);
606 fetchCollections(collection, CollectionFetchJob::Base);
607 fetchCollections(collection, fetchType);
608 } else {
609 //If a collection is derefernced and no longer explicitly monitored it might still match other filters
610 if (!shouldBePartOfModel(collection)) {
611 monitoredCollectionRemoved(collection);
612 }
613 }
614}
615
616void EntityTreeModelPrivate::monitoredItemsChanged(const Akonadi::Item &item, bool monitored)
617{
618 Q_UNUSED(item)
619 Q_UNUSED(monitored)
620 beginResetModel();
621 endResetModel();
622}
623
624void EntityTreeModelPrivate::monitoredResourcesChanged(const QByteArray &resource, bool monitored)
625{
626 Q_UNUSED(resource)
627 Q_UNUSED(monitored)
628 beginResetModel();
629 endResetModel();
630}
631
632void EntityTreeModelPrivate::retrieveAncestors(const Akonadi::Collection &collection, bool insertBaseCollection)
633{
634 Q_Q(EntityTreeModel);
635
636 Collection parentCollection = collection.parentCollection();
637
638 Q_ASSERT(parentCollection.isValid());
639 Q_ASSERT(parentCollection != Collection::root());
640
641 Collection::List ancestors;
642
643 while (parentCollection != Collection::root() && !m_collections.contains(parentCollection.id())) {
644 // Put a temporary node in the tree later.
645 ancestors.prepend(parentCollection);
646
647 parentCollection = parentCollection.parentCollection();
648 }
649 Q_ASSERT(parentCollection.isValid());
650 // if m_rootCollection is Collection::root(), we always have common ancestor and do the retrival
651 // if we traversed up to Collection::root() but are looking at a subtree only (m_rootCollection != Collection::root())
652 // we have no common ancestor, and we don't have to retrieve anything
653 if (parentCollection == Collection::root() && m_rootCollection != Collection::root()) {
654 return;
655 }
656
657 if (!ancestors.isEmpty()) {
658 // Fetch the real ancestors
659 CollectionFetchJob *job = new CollectionFetchJob(ancestors, CollectionFetchJob::Base, m_session);
660 job->fetchScope().setListFilter(m_listFilter);
661 job->fetchScope().setIncludeStatistics(m_includeStatistics);
662 q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
663 q, SLOT(ancestorsFetched(Akonadi::Collection::List)));
664 q->connect(job, SIGNAL(result(KJob*)),
665 q, SLOT(collectionFetchJobDone(KJob*)));
666 }
667
668// Q_ASSERT( parentCollection != m_rootCollection );
669 const QModelIndex parent = indexForCollection(parentCollection);
670
671 // Still prepending all collections for now.
672 int row = 0;
673
674
675 if (insertBaseCollection) {
676 // Although we insert several Collections here, we only need to notify though the model
677 // about the top-level one. The rest will be found auotmatically by the view.
678 q->beginInsertRows(parent, row, row);
679 m_collections.insert(collection.id(), collection);
680 Node *node = new Node;
681 node->id = collection.id();
682 // Can't just use parentCollection because that doesn't necessarily refer to collection.
683 node->parent = collection.parentCollection().id();
684 node->type = Node::Collection;
685 m_childEntities[node->parent].prepend(node);
686 }
687
688 Collection::List::const_iterator it = ancestors.constBegin();
689 const Collection::List::const_iterator end = ancestors.constEnd();
690
691 for (; it != end; ++it) {
692 const Collection ancestor = *it;
693 Q_ASSERT(ancestor.parentCollection().isValid());
694 if (!insertBaseCollection) {
695 const QModelIndex ancestorParent = indexForCollection(ancestor.parentCollection());
696 q->beginInsertRows(ancestorParent, 0, 0);
697 }
698 m_collections.insert(ancestor.id(), ancestor);
699
700 Node *node = new Node;
701 node->id = ancestor.id();
702 node->parent = ancestor.parentCollection().id();
703 node->type = Node::Collection;
704 m_childEntities[node->parent].prepend(node);
705 if (!insertBaseCollection) {
706 q->endInsertRows();
707 }
708 }
709
710 if (insertBaseCollection) {
711 q->endInsertRows();
712 }
713}
714
715void EntityTreeModelPrivate::ancestorsFetched(const Akonadi::Collection::List &collectionList)
716{
717 foreach (const Collection &collection, collectionList) {
718 m_collections[collection.id()] = collection;
719
720 const QModelIndex index = indexForCollection(collection);
721 Q_ASSERT(index.isValid());
722 dataChanged(index, index);
723 }
724}
725
726void EntityTreeModelPrivate::insertCollection(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
727{
728 Q_ASSERT(collection.isValid());
729 Q_ASSERT(parent.isValid());
730
731 Q_Q(EntityTreeModel);
732
733 const int row = 0;
734 const QModelIndex parentIndex = indexForCollection(parent);
735 q->beginInsertRows(parentIndex, row, row);
736 m_collections.insert(collection.id(), collection);
737
738 Node *node = new Node;
739 node->id = collection.id();
740 node->parent = parent.id();
741 node->type = Node::Collection;
742 m_childEntities[parent.id()].prepend(node);
743 q->endInsertRows();
744}
745
746bool EntityTreeModelPrivate::hasChildCollection(const Collection &collection) const
747{
748 foreach (Node *node, m_childEntities[collection.id()]) {
749 if (node->type == Node::Collection) {
750 const Collection subcol = m_collections[node->id];
751 if (shouldBePartOfModel(subcol)) {
752 return true;
753 }
754 }
755 }
756 return false;
757}
758
759bool EntityTreeModelPrivate::isAncestorMonitored(const Collection &collection) const
760{
761 Akonadi::Collection parent = collection.parentCollection();
762 while (parent.isValid()) {
763 if (m_monitor->collectionsMonitored().contains(parent)) {
764 return true;
765 }
766 parent = parent.parentCollection();
767 }
768 return false;
769}
770
771bool EntityTreeModelPrivate::shouldBePartOfModel(const Collection &collection) const
772{
773 if (isHidden(collection)) {
774 return false;
775 }
776
777 // We want a parent collection if it has at least one child that matches the
778 // wanted mimetype
779 if (hasChildCollection(collection)) {
780 return true;
781 }
782
783 //Explicitly monitored collection
784 if (m_monitor->collectionsMonitored().contains(collection)) {
785 return true;
786 }
787
788 //We're explicitly monitoring collections, but didn't match the filter
789 if (m_mimeChecker.wantedMimeTypes().isEmpty() && !m_monitor->collectionsMonitored().isEmpty()) {
790 //The collection should be included if one of the parents is monitored
791 if (isAncestorMonitored(collection)) {
792 return true;
793 }
794 return false;
795 }
796
797 // Some collection trees contain multiple mimetypes. Even though server side filtering ensures we
798 // only get the ones we're interested in from the job, we have to filter on collections received through signals too.
799 if (!m_mimeChecker.wantedMimeTypes().isEmpty() &&
800 !m_mimeChecker.isWantedCollection(collection)) {
801 return false;
802 }
803
804 if (m_listFilter == CollectionFetchScope::Enabled) {
805 if (!collection.enabled()) {
806 return false;
807 }
808 } else if (m_listFilter == CollectionFetchScope::Display) {
809 if (!collection.shouldList(Collection::ListDisplay)) {
810 return false;
811 }
812 } else if (m_listFilter == CollectionFetchScope::Sync) {
813 if (!collection.shouldList(Collection::ListSync)) {
814 return false;
815 }
816 } else if (m_listFilter == CollectionFetchScope::Index) {
817 if (!collection.shouldList(Collection::ListIndex)) {
818 return false;
819 }
820 }
821
822 return true;
823}
824
825void EntityTreeModelPrivate::monitoredCollectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
826{
827 // If the resource is removed while populating the model with it, we might still
828 // get some monitor signals. These stale/out-of-order signals can't be completely eliminated
829 // in the akonadi server due to implementation details, so we also handle such signals in the model silently
830 // in all the monitored slots.
831 // Stephen Kelly, 28, July 2009
832
833 // If a fetch job is started and a collection is added to akonadi after the fetch job is started, the
834 // new collection will be added to the fetch job results. It will also be notified through the monitor.
835 // We return early here in that case.
836 if (m_collections.contains(collection.id())) {
837 return;
838 }
839
840 //If the resource is explicitly monitored all other checks are skipped. topLevelCollectionsFetched still checks the hidden attribute.
841 if (m_monitor->resourcesMonitored().contains(collection.resource().toUtf8()) &&
842 collection.parentCollection() == Collection::root()) {
843 return topLevelCollectionsFetched(Collection::List() << collection);
844 }
845
846 if (!shouldBePartOfModel(collection)) {
847 return;
848 }
849
850 if (!m_collections.contains(parent.id())) {
851 // The collection we're interested in is contained in a collection we're not interested in.
852 // We download the ancestors of the collection we're interested in to complete the tree.
853 if (collection != Collection::root()) {
854 retrieveAncestors(collection);
855 }
856 return;
857 }
858
859 insertCollection(collection, parent);
860}
861
862void EntityTreeModelPrivate::monitoredCollectionRemoved(const Akonadi::Collection &collection)
863{
864 //if an explictly monitored collection is removed, we would also have to remove collections which were included to show it (as in the move case)
865 if ((collection == m_rootCollection) ||
866 m_monitor->collectionsMonitored().contains(collection)) {
867 beginResetModel();
868 endResetModel();
869 return;
870 }
871
872 Collection::Id parentId = collection.parentCollection().id();
873
874 if (parentId < 0) {
875 parentId = -1;
876 }
877
878 if (!m_collections.contains(parentId)) {
879 return;
880 }
881
882 // This may be a signal for a collection we've already removed by removing its ancestor.
883 // Or the collection may have been hidden.
884 if (!m_collections.contains(collection.id())) {
885 return;
886 }
887
888 Q_Q(EntityTreeModel);
889
890 Q_ASSERT(m_childEntities.contains(parentId));
891
892 const int row = indexOf<Node::Collection>(m_childEntities.value(parentId), collection.id());
893
894 Q_ASSERT(row >= 0);
895
896 Q_ASSERT(m_collections.contains(parentId));
897
898 const Collection parentCollection = m_collections.value(parentId);
899
900 m_populatedCols.remove(collection.id());
901
902 const QModelIndex parentIndex = indexForCollection(parentCollection);
903
904 q->beginRemoveRows(parentIndex, row, row);
905
906 // Delete all descendant collections and items.
907 removeChildEntities(collection.id());
908
909 // Remove deleted collection from its parent.
910 delete m_childEntities[parentId].takeAt(row);
911
912 // Remove deleted collection itself.
913 m_collections.remove(collection.id());
914
915 q->endRemoveRows();
916
917 // After removing a collection, check whether it's parent should be removed too
918 if (!shouldBePartOfModel(parentCollection)) {
919 monitoredCollectionRemoved(parentCollection);
920 }
921}
922
923void EntityTreeModelPrivate::removeChildEntities(Collection::Id collectionId)
924{
925 QList<Node *> childList = m_childEntities.value(collectionId);
926 QList<Node *>::const_iterator it = childList.constBegin();
927 const QList<Node *>::const_iterator end = childList.constEnd();
928 for (; it != end; ++it) {
929 if (Node::Item == (*it)->type) {
930 m_items.remove((*it)->id);
931 } else {
932 removeChildEntities((*it)->id);
933 m_collections.remove((*it)->id);
934 m_populatedCols.remove((*it)->id);
935 }
936 }
937
938 qDeleteAll(m_childEntities.take(collectionId));
939}
940
941QStringList EntityTreeModelPrivate::childCollectionNames(const Collection &collection) const
942{
943 QStringList names;
944
945 foreach (Node *node, m_childEntities[collection.id()]) {
946 if (node->type == Node::Collection) {
947 names << m_collections.value(node->id).name();
948 }
949 }
950
951 return names;
952}
953
954void EntityTreeModelPrivate::monitoredCollectionMoved(const Akonadi::Collection &collection,
955 const Akonadi::Collection &sourceCollection,
956 const Akonadi::Collection &destCollection)
957{
958 if (isHidden(collection)) {
959 return;
960 }
961
962 if (isHidden(sourceCollection)) {
963 if (isHidden(destCollection)) {
964 return;
965 }
966
967 monitoredCollectionAdded(collection, destCollection);
968 return;
969 } else if (isHidden(destCollection)) {
970 monitoredCollectionRemoved(collection);
971 return;
972 }
973
974 if (!m_collections.contains(collection.id())) {
975 return;
976 }
977
978 if (m_monitor->collectionsMonitored().contains(collection)) {
979 //if we don't reset here, we would have to make sure that destination collection is actually available,
980 //and remove the sources parents if they were only included as parents of the moved collection
981 beginResetModel();
982 endResetModel();
983 return;
984 }
985 Q_Q(EntityTreeModel);
986
987 const QModelIndex srcParentIndex = indexForCollection(sourceCollection);
988 const QModelIndex destParentIndex = indexForCollection(destCollection);
989
990 Q_ASSERT(collection.parentCollection().isValid());
991 Q_ASSERT(destCollection.isValid());
992 Q_ASSERT(collection.parentCollection() == destCollection);
993
994 const int srcRow = indexOf<Node::Collection>(m_childEntities.value(sourceCollection.id()), collection.id());
995 const int destRow = 0; // Prepend collections
996
997 if (!q->beginMoveRows(srcParentIndex, srcRow, srcRow, destParentIndex, destRow)) {
998 kWarning() << "Invalid move";
999 return;
1000 }
1001
1002 Node *node = m_childEntities[sourceCollection.id()].takeAt(srcRow);
1003 // collection has the correct parentCollection etc. We need to set it on the
1004 // internal data structure to not corrupt things.
1005 m_collections.insert(collection.id(), collection);
1006 node->parent = destCollection.id();
1007 m_childEntities[destCollection.id()].prepend(node);
1008 q->endMoveRows();
1009}
1010
1011void EntityTreeModelPrivate::monitoredCollectionChanged(const Akonadi::Collection &collection)
1012{
1013 if (!m_collections.contains(collection.id())) {
1014 // This can happen if
1015 // * we get a change notification after removing the collection.
1016 // * a collection of a non-monitored mimetype is changed elsewhere. Monitor does not
1017 // filter by content mimetype of Collections so we get notifications for all of them.
1018
1019 //We might match the filter now, retry adding the collection
1020 monitoredCollectionAdded(collection, collection.parentCollection());
1021 return;
1022 }
1023
1024 if (!shouldBePartOfModel(collection)) {
1025 monitoredCollectionRemoved(collection);
1026 return;
1027 }
1028
1029 m_collections[collection.id()] = collection;
1030
1031 if (!m_showRootCollection &&
1032 collection == m_rootCollection) {
1033 // If the root of the model is not Collection::root it might be modified.
1034 // But it doesn't exist in the accessible model structure, so we need to early return
1035 return;
1036 }
1037
1038 const QModelIndex index = indexForCollection(collection);
1039 Q_ASSERT(index.isValid());
1040 dataChanged(index, index);
1041}
1042
1043void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged(Akonadi::Collection::Id id,
1044 const Akonadi::CollectionStatistics &statistics)
1045{
1046 if (!m_collections.contains(id)) {
1047 return;
1048 }
1049
1050 m_collections[id].setStatistics(statistics);
1051
1052 // if the item count becomes 0, add to set of collections we know to be empty
1053 // otherwise remove if in there
1054 if (statistics.count() == 0) {
1055 m_collectionsWithoutItems.insert(id);
1056 } else {
1057 m_collectionsWithoutItems.remove(id);
1058 }
1059
1060 if (!m_showRootCollection &&
1061 id == m_rootCollection.id()) {
1062 // If the root of the model is not Collection::root it might be modified.
1063 // But it doesn't exist in the accessible model structure, so we need to early return
1064 return;
1065 }
1066
1067 const QModelIndex index = indexForCollection(m_collections[id]);
1068 dataChanged(index, index);
1069}
1070
1071void EntityTreeModelPrivate::monitoredItemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection)
1072{
1073 Q_Q(EntityTreeModel);
1074
1075 if (isHidden(item)) {
1076 return;
1077 }
1078
1079 if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch &&
1080 !m_collections.contains(collection.id())) {
1081 kWarning() << "Got a stale notification for an item whose collection was already removed." << item.id() << item.remoteId();
1082 return;
1083 }
1084
1085 if (m_items.contains(item.id())) {
1086 return;
1087 }
1088
1089 Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collection.id()) : true);
1090
1091 if (!m_mimeChecker.wantedMimeTypes().isEmpty() &&
1092 !m_mimeChecker.isWantedItem(item)) {
1093 return;
1094 }
1095
1096 //Adding items to not yet populated collections would block fetchMore, resulting in only new items showing up in the collection
1097 //This is only a problem with lazy population, otherwise fetchMore is not used at all
1098 if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(collection.id())) {
1099 return;
1100 }
1101
1102 int row;
1103 QModelIndex parentIndex;
1104 if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) {
1105 row = m_childEntities.value(collection.id()).size();
1106 parentIndex = indexForCollection(m_collections.value(collection.id()));
1107 } else {
1108 row = q->rowCount();
1109 }
1110 q->beginInsertRows(parentIndex, row, row);
1111 m_items.insert(item.id(), item);
1112 Node *node = new Node;
1113 node->id = item.id();
1114 node->parent = collection.id();
1115 node->type = Node::Item;
1116 if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) {
1117 m_childEntities[collection.id()].append(node);
1118 } else {
1119 m_childEntities[m_rootCollection.id()].append(node);
1120 }
1121 q->endInsertRows();
1122}
1123
1124void EntityTreeModelPrivate::monitoredItemRemoved(const Akonadi::Item &item)
1125{
1126 Q_Q(EntityTreeModel);
1127
1128 if (isHidden(item)) {
1129 return;
1130 }
1131
1132 const Collection::List parents = getParentCollections(item);
1133 if (parents.isEmpty()) {
1134 return;
1135 }
1136
1137 if (!m_items.contains(item.id())) {
1138 kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId();
1139 return;
1140 }
1141
1142 // TODO: Iterate over all (virtual) collections.
1143 const Collection collection = parents.first();
1144
1145 Q_ASSERT(m_collections.contains(collection.id()));
1146 Q_ASSERT(m_childEntities.contains(collection.id()));
1147
1148 const int row = indexOf<Node::Item>(m_childEntities.value(collection.id()), item.id());
1149 Q_ASSERT(row >= 0);
1150
1151 const QModelIndex parentIndex = indexForCollection(m_collections.value(collection.id()));
1152
1153 q->beginRemoveRows(parentIndex, row, row);
1154 m_items.remove(item.id());
1155 delete m_childEntities[collection.id()].takeAt(row);
1156 q->endRemoveRows();
1157}
1158
1159void EntityTreeModelPrivate::monitoredItemChanged(const Akonadi::Item &item, const QSet<QByteArray> &)
1160{
1161 if (isHidden(item)) {
1162 return;
1163 }
1164
1165 if (!m_items.contains(item.id())) {
1166 kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId();
1167 return;
1168 }
1169
1170 // Notifications about itemChange are always dispatched for real collection
1171 // and also all virtual collections the item belongs to. In order to preserve
1172 // the original storage collection when we need to have special handling for
1173 // notifications for virtual collections
1174 if (item.parentCollection().isVirtual()) {
1175 const Collection originalParent = m_items[item.id()].parentCollection();
1176 m_items[item.id()].apply(item);
1177 m_items[item.id()].setParentCollection(originalParent);
1178 } else {
1179 m_items[item.id()].apply(item);
1180 }
1181
1182 const QModelIndexList indexes = indexesForItem(item);
1183 foreach (const QModelIndex &index, indexes) {
1184 if (!index.isValid()) {
1185 kWarning() << "item has invalid index:" << item.id() << item.remoteId();
1186 } else {
1187 dataChanged(index, index);
1188 }
1189 }
1190}
1191
1192void EntityTreeModelPrivate::monitoredItemMoved(const Akonadi::Item &item,
1193 const Akonadi::Collection &sourceCollection,
1194 const Akonadi::Collection &destCollection)
1195{
1196
1197 if (isHidden(item)) {
1198 return;
1199 }
1200
1201 if (isHidden(sourceCollection)) {
1202 if (isHidden(destCollection)) {
1203 return;
1204 }
1205
1206 monitoredItemAdded(item, destCollection);
1207 return;
1208 } else if (isHidden(destCollection)) {
1209 monitoredItemRemoved(item);
1210 return;
1211 } else {
1212 monitoredItemRemoved(item);
1213 monitoredItemAdded(item, destCollection);
1214 return;
1215 }
1216 // "Temporarily" commented out as it's likely the best course to
1217 // avoid the dreaded "reset storm" (or layoutChanged storm). The
1218 // whole itemMoved idea is great but not practical until all the
1219 // other proxy models play nicely with it, right now they just
1220 // transform moved signals in layout changed, which explodes into
1221 // a reset of the source model inside of the message list (ouch!)
1222#if 0
1223 if (!m_items.contains(item.id())) {
1224 kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId();
1225 return;
1226 }
1227
1228 Q_ASSERT(m_collections.contains(sourceCollection.id()));
1229 Q_ASSERT(m_collections.contains(destCollection.id()));
1230
1231 const QModelIndex srcIndex = indexForCollection(sourceCollection);
1232 const QModelIndex destIndex = indexForCollection(destCollection);
1233
1234 // Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes?
1235
1236 const Item::Id itemId = item.id();
1237
1238 const int srcRow = indexOf<Node::Item>(m_childEntities.value(sourceCollection.id()), itemId);
1239 const int destRow = q->rowCount(destIndex);
1240
1241 Q_ASSERT(srcRow >= 0);
1242 Q_ASSERT(destRow >= 0);
1243 if (!q->beginMoveRows(srcIndex, srcRow, srcRow, destIndex, destRow)) {
1244 kWarning() << "Invalid move";
1245 return;
1246 }
1247
1248 Q_ASSERT(m_childEntities.contains(sourceCollection.id()));
1249 Q_ASSERT(m_childEntities[sourceCollection.id()].size() > srcRow);
1250
1251 Node *node = m_childEntities[sourceCollection.id()].takeAt(srcRow);
1252 m_items.insert(item.id(), item);
1253 node->parent = destCollection.id();
1254 m_childEntities[destCollection.id()].append(node);
1255 q->endMoveRows();
1256#endif
1257}
1258
1259void EntityTreeModelPrivate::monitoredItemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection)
1260{
1261 Q_Q(EntityTreeModel);
1262
1263 if (isHidden(item)) {
1264 return;
1265 }
1266
1267 const Collection::Id collectionId = collection.id();
1268 const Item::Id itemId = item.id();
1269
1270 Q_ASSERT(m_collections.contains(collectionId));
1271
1272 if (!m_mimeChecker.wantedMimeTypes().isEmpty() &&
1273 !m_mimeChecker.isWantedItem(item)) {
1274 return;
1275 }
1276
1277 QList<Node *> &collectionEntities = m_childEntities[collectionId];
1278
1279 int existingPosition = indexOf<Node::Item>(collectionEntities, itemId);
1280
1281 if (existingPosition > 0) {
1282 qWarning() << "Item with id " << itemId << " already in virtual collection with id " << collectionId;
1283 return;
1284 }
1285
1286 const int row = collectionEntities.size();
1287
1288 const QModelIndex parentIndex = indexForCollection(m_collections.value(collectionId));
1289
1290 q->beginInsertRows(parentIndex, row, row);
1291 if (!m_items.contains(itemId)) {
1292 m_items.insert(itemId, item);
1293 }
1294 Node *node = new Node;
1295 node->id = itemId;
1296 node->parent = collectionId;
1297 node->type = Node::Item;
1298 collectionEntities.append(node);
1299 q->endInsertRows();
1300}
1301
1302void EntityTreeModelPrivate::monitoredItemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection)
1303{
1304 Q_Q(EntityTreeModel);
1305
1306 if (isHidden(item)) {
1307 return;
1308 }
1309
1310 if (!m_items.contains(item.id())) {
1311 kWarning() << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId();
1312 return;
1313 }
1314
1315 Q_ASSERT(m_collections.contains(collection.id()));
1316
1317 const int row = indexOf<Node::Item>( m_childEntities.value( collection.id() ), item.id() );
1318 if ( row < 0 || row >= m_childEntities[ collection.id() ].size() ) {
1319 kWarning() << "couldn't find index of unlinked item " << item.id() << collection.id() << row;
1320 Q_ASSERT(false);
1321 return;
1322 }
1323
1324 const QModelIndex parentIndex = indexForCollection(m_collections.value(collection.id()));
1325
1326 q->beginRemoveRows(parentIndex, row, row);
1327 delete m_childEntities[collection.id()].takeAt(row);
1328 q->endRemoveRows();
1329}
1330
1331void EntityTreeModelPrivate::collectionFetchJobDone(KJob *job)
1332{
1333 CollectionFetchJob *cJob = static_cast<CollectionFetchJob *>(job);
1334 if (job->error()) {
1335 kWarning() << "Job error: " << job->errorString() << "for collection:" << cJob->collections() << endl;
1336 return;
1337 }
1338
1339#ifdef DBG_TRACK_JOB_TIMES
1340 kDebug() << "Fetch job took " << jobTimeTracker.take(job).elapsed() << "msec";
1341 kDebug() << "was collection fetch job: collections:" << cJob->collections().size();
1342 if (!cJob->collections().isEmpty()) {
1343 kDebug() << "first fetched collection:" << cJob->collections().first().name();
1344 }
1345#endif
1346}
1347
1348void EntityTreeModelPrivate::itemFetchJobDone(KJob *job)
1349{
1350 const Collection::Id collectionId = job->property(FetchCollectionId()).value<Collection::Id>();
1351 m_pendingCollectionRetrieveJobs.remove(collectionId);
1352
1353 if (job->error()) {
1354 kWarning() << "Job error: " << job->errorString() << "for collection:" << collectionId << endl;
1355 return;
1356 }
1357 if (!m_collections.contains(collectionId)) {
1358 kWarning() << "Collection has been removed while fetching items";
1359 return;
1360 }
1361 ItemFetchJob *iJob = static_cast<ItemFetchJob *>(job);
1362
1363#ifdef DBG_TRACK_JOB_TIMES
1364 kDebug() << "Fetch job took " << jobTimeTracker.take(job).elapsed() << "msec";
1365 kDebug() << "was item fetch job: items:" << iJob->items().size();
1366 if (!iJob->items().isEmpty()) {
1367 kDebug() << "first item collection:" << iJob->items().first().parentCollection().name();
1368 }
1369#endif
1370
1371 if (!iJob->count()) {
1372 m_collectionsWithoutItems.insert(collectionId);
1373 } else {
1374 m_collectionsWithoutItems.remove(collectionId);
1375 }
1376
1377 m_populatedCols.insert(collectionId);
1378 emit q_ptr->collectionPopulated(collectionId);
1379
1380 // If collections are not in the model, there will be no valid index for them.
1381 if ((m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) &&
1382 (m_collectionFetchStrategy != EntityTreeModel::FetchNoCollections)) {
1383 const QModelIndex index = indexForCollection(Collection(collectionId));
1384 Q_ASSERT(index.isValid());
1385 //To notify about the changed fetch and population state
1386 emit dataChanged(index, index);
1387 }
1388}
1389
1390void EntityTreeModelPrivate::pasteJobDone(KJob *job)
1391{
1392 if (job->error()) {
1393 QString errorMsg;
1394 if (qobject_cast<ItemCopyJob *>(job)) {
1395 errorMsg = i18n("Could not copy item:");
1396 } else if (qobject_cast<CollectionCopyJob *>(job)) {
1397 errorMsg = i18n("Could not copy collection:");
1398 } else if (qobject_cast<ItemMoveJob *>(job)) {
1399 errorMsg = i18n("Could not move item:");
1400 } else if (qobject_cast<CollectionMoveJob *>(job)) {
1401 errorMsg = i18n("Could not move collection:");
1402 } else if (qobject_cast<LinkJob *>(job)) {
1403 errorMsg = i18n("Could not link entity:");
1404 }
1405
1406 errorMsg += QLatin1Char(' ') + job->errorString();
1407
1408 KMessageBox::error(0, errorMsg);
1409 }
1410}
1411
1412void EntityTreeModelPrivate::updateJobDone(KJob *job)
1413{
1414 if (job->error()) {
1415 // TODO: handle job errors
1416 kWarning() << "Job error:" << job->errorString();
1417 } else {
1418
1419 //FIXME: This seems pretty pointless since we'll get an update through the monitor anyways
1420 ItemModifyJob *modifyJob = qobject_cast<ItemModifyJob *>(job);
1421 if (!modifyJob) {
1422 return;
1423 }
1424
1425 const Item item = modifyJob->item();
1426
1427 Q_ASSERT(item.isValid());
1428
1429 m_items[item.id()].apply(item);
1430 const QModelIndexList list = indexesForItem(item);
1431
1432 foreach (const QModelIndex &index, list) {
1433 dataChanged(index, index);
1434 }
1435 }
1436}
1437
1438void EntityTreeModelPrivate::rootFetchJobDone(KJob *job)
1439{
1440 if (job->error()) {
1441 kWarning() << job->errorString();
1442 return;
1443 }
1444 CollectionFetchJob *collectionJob = qobject_cast<CollectionFetchJob *>(job);
1445 const Collection::List list = collectionJob->collections();
1446
1447 Q_ASSERT(list.size() == 1);
1448 m_rootCollection = list.first();
1449 startFirstListJob();
1450}
1451
1452void EntityTreeModelPrivate::startFirstListJob()
1453{
1454 Q_Q(EntityTreeModel);
1455
1456 if (m_collections.size() > 0) {
1457 return;
1458 }
1459
1460 // Even if the root collection is the invalid collection, we still need to start
1461 // the first list job with Collection::root.
1462 if (m_showRootCollection) {
1463 // Notify the outside that we're putting collection::root into the model.
1464 q->beginInsertRows(QModelIndex(), 0, 0);
1465 m_collections.insert(m_rootCollection.id(), m_rootCollection);
1466 delete m_rootNode;
1467 m_rootNode = new Node;
1468 m_rootNode->id = m_rootCollection.id();
1469 m_rootNode->parent = -1;
1470 m_rootNode->type = Node::Collection;
1471 m_childEntities[-1].append(m_rootNode);
1472 q->endInsertRows();
1473 } else {
1474 // Otherwise store it silently because it's not part of the usable model.
1475 delete m_rootNode;
1476 m_rootNode = new Node;
1477 m_rootNode->id = m_rootCollection.id();
1478 m_rootNode->parent = -1;
1479 m_rootNode->type = Node::Collection;
1480 m_collections.insert(m_rootCollection.id(), m_rootCollection);
1481 }
1482
1483 const bool noMimetypes = m_mimeChecker.wantedMimeTypes().isEmpty();
1484 const bool noResources = m_monitor->resourcesMonitored().isEmpty();
1485 const bool multipleCollections = m_monitor->collectionsMonitored().size() > 1;
1486 const bool generalPopulation = !noMimetypes || (noMimetypes && noResources);
1487
1488 const CollectionFetchJob::Type fetchType = getFetchType(m_collectionFetchStrategy);
1489
1490 //Collections can only be monitored if no resources and no mimetypes are monitored
1491 if (multipleCollections && noMimetypes && noResources) {
1492 fetchCollections(m_monitor->collectionsMonitored(), CollectionFetchJob::Base);
1493 fetchCollections(m_monitor->collectionsMonitored(), fetchType);
1494 return;
1495 }
1496
1497 kDebug() << "GEN" << generalPopulation << noMimetypes << noResources;
1498 if (generalPopulation) {
1499 fetchCollections(m_rootCollection, fetchType);
1500 }
1501
1502 // If the root collection is not collection::root, then it could have items, and they will need to be
1503 // retrieved now.
1504 // Only fetch items NOT if there is NoItemPopulation, or if there is Lazypopulation and the root is visible
1505 // (if the root is not visible the lazy population can not be triggered)
1506 if ((m_itemPopulation != EntityTreeModel::NoItemPopulation) &&
1507 !((m_itemPopulation == EntityTreeModel::LazyPopulation) &&
1508 m_showRootCollection)) {
1509 if (m_rootCollection != Collection::root()) {
1510 fetchItems(m_rootCollection);
1511 }
1512 }
1513
1514 // Resources which are explicitly monitored won't have appeared yet if their mimetype didn't match.
1515 // We fetch the top level collections and examine them for whether to add them.
1516 // This fetches virtual collections into the tree.
1517 if (!m_monitor->resourcesMonitored().isEmpty()) {
1518 fetchTopLevelCollections();
1519 }
1520}
1521
1522void EntityTreeModelPrivate::finalCollectionFetchJobDone(KJob *job)
1523{
1524 if (job->error()) {
1525 kWarning() << job->errorString();
1526 return;
1527 }
1528
1529 Akonadi::CollectionFetchJob *fetchJob = static_cast<Akonadi::CollectionFetchJob*>(job);
1530 //Can happen when monitoring resources
1531 if (!m_collectionTreeFetched) {
1532 m_collectionTreeFetched = true;
1533 emit q_ptr->collectionTreeFetched(fetchJob->collections());
1534 }
1535}
1536
1537void EntityTreeModelPrivate::fetchTopLevelCollections() const
1538{
1539 Q_Q(const EntityTreeModel);
1540 CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel, m_session);
1541 q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
1542 q, SLOT(topLevelCollectionsFetched(Akonadi::Collection::List)));
1543 q->connect(job, SIGNAL(result(KJob*)),
1544 q, SLOT(collectionFetchJobDone(KJob*)));
1545 ifDebug(kDebug() << ""; jobTimeTracker[job].start();)
1546}
1547
1548void EntityTreeModelPrivate::topLevelCollectionsFetched(const Akonadi::Collection::List &list)
1549{
1550 Q_Q(EntityTreeModel);
1551 foreach (const Collection &collection, list) {
1552 // These collections have been explicitly shown in the Monitor,
1553 // but hidden trumps that for now. This may change in the future if we figure out a use for it.
1554 if (isHidden(collection)) {
1555 continue;
1556 }
1557
1558 if (m_monitor->resourcesMonitored().contains(collection.resource().toUtf8()) &&
1559 !m_collections.contains(collection.id())) {
1560 const QModelIndex parentIndex = indexForCollection(collection.parentCollection());
1561 // Prepending new collections.
1562 const int row = 0;
1563 q->beginInsertRows(parentIndex, row, row);
1564
1565 m_collections.insert(collection.id(), collection);
1566 Node *node = new Node;
1567 node->id = collection.id();
1568 Q_ASSERT(collection.parentCollection() == Collection::root());
1569 node->parent = collection.parentCollection().id();
1570 node->type = Node::Collection;
1571 m_childEntities[collection.parentCollection().id()].prepend(node);
1572
1573 q->endInsertRows();
1574
1575 if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) {
1576 fetchItems(collection);
1577 }
1578
1579 Q_ASSERT(collection.isValid());
1580 fetchCollections(collection, CollectionFetchJob::Recursive);
1581 }
1582 }
1583}
1584
1585Akonadi::Collection::List EntityTreeModelPrivate::getParentCollections(const Item &item) const
1586{
1587 Collection::List list;
1588 QHashIterator<Collection::Id, QList<Node *> > iter(m_childEntities);
1589 while (iter.hasNext()) {
1590 iter.next();
1591 int nodeIndex = indexOf<Node::Item>(iter.value(), item.id());
1592 if (nodeIndex != -1 &&
1593 iter.value().at(nodeIndex)->type == Node::Item) {
1594 list << m_collections.value(iter.key());
1595 }
1596 }
1597
1598 return list;
1599}
1600
1601void EntityTreeModelPrivate::ref(Collection::Id id)
1602{
1603 m_monitor->d_ptr->ref(id);
1604}
1605
1606bool EntityTreeModelPrivate::shouldPurge(Collection::Id id)
1607{
1608 // reference counted collections should never be purged
1609 // they first have to be deref'ed until they reach 0.
1610 // if the collection is buffered, keep it.
1611 if (m_monitor->d_ptr->isMonitored(id)) {
1612 return false;
1613 }
1614
1615 // otherwise we can safely purge this item
1616 return true;
1617}
1618
1619bool EntityTreeModelPrivate::isMonitored(Collection::Id id)
1620{
1621 return m_monitor->d_ptr->isMonitored(id);
1622}
1623
1624bool EntityTreeModelPrivate::isBuffered(Collection::Id id)
1625{
1626 return m_monitor->d_ptr->m_buffer.isBuffered(id);
1627}
1628
1629void EntityTreeModelPrivate::deref(Collection::Id id)
1630{
1631 const Collection::Id bumpedId = m_monitor->d_ptr->deref(id);
1632
1633 if (bumpedId < 0) {
1634 return;
1635 }
1636
1637 //The collection has already been removed, don't purge
1638 if (!m_collections.contains(bumpedId)) {
1639 return;
1640 }
1641
1642 if (shouldPurge(bumpedId)) {
1643 purgeItems(bumpedId);
1644 }
1645}
1646
1647QList<Node *>::iterator EntityTreeModelPrivate::skipCollections(QList<Node *>::iterator it, QList<Node *>::iterator end, int *pos)
1648{
1649 for (; it != end; ++it) {
1650 if ((*it)->type == Node::Item) {
1651 break;
1652 }
1653
1654 ++(*pos);
1655 }
1656
1657 return it;
1658}
1659
1660QList<Node *>::iterator EntityTreeModelPrivate::removeItems(QList<Node *>::iterator it, QList<Node *>::iterator end, int *pos, const Collection &collection)
1661{
1662 Q_Q(EntityTreeModel);
1663
1664 QList<Node *>::iterator startIt = it;
1665
1666 // figure out how many items we will delete
1667 int start = *pos;
1668 for (; it != end; ++it) {
1669 if ((*it)->type != Node::Item) {
1670 break;
1671 }
1672
1673 ++(*pos);
1674 }
1675 it = startIt;
1676
1677 const QModelIndex parentIndex = indexForCollection(collection);
1678
1679 q->beginRemoveRows(parentIndex, start, (*pos) - 1);
1680 const int toDelete = (*pos) - start;
1681 Q_ASSERT(toDelete > 0);
1682
1683 QList<Node *> &es = m_childEntities[collection.id()];
1684 //NOTE: .erase will invalidate all iterators besides "it"!
1685 for (int i = 0; i < toDelete; ++i) {
1686 Q_ASSERT(es.count(*it) == 1);
1687 // don't keep implicitly shared data alive
1688 Q_ASSERT(m_items.contains((*it)->id));
1689 m_items.remove((*it)->id);
1690 // delete actual node
1691 delete *it;
1692 it = es.erase(it);
1693 }
1694 q->endRemoveRows();
1695
1696 return it;
1697}
1698
1699void EntityTreeModelPrivate::purgeItems(Collection::Id id)
1700{
1701 QList<Node *> &childEntities = m_childEntities[id];
1702
1703 const Collection collection = m_collections.value(id);
1704 Q_ASSERT(collection.isValid());
1705
1706 QList<Node *>::iterator begin = childEntities.begin();
1707 QList<Node *>::iterator end = childEntities.end();
1708
1709 int pos = 0;
1710 while ((begin = skipCollections(begin, end, &pos)) != end) {
1711 begin = removeItems(begin, end, &pos, collection);
1712 end = childEntities.end();
1713 }
1714 m_populatedCols.remove(id);
1715 //if an empty collection is purged and we leave it in here, itemAdded will be ignored for the collection
1716 //and the collection is never populated by fetchMore (but maybe by statistics changed?)
1717 m_collectionsWithoutItems.remove(id);
1718}
1719
1720void EntityTreeModelPrivate::dataChanged(const QModelIndex &top, const QModelIndex &bottom)
1721{
1722 Q_Q(EntityTreeModel);
1723
1724 QModelIndex rightIndex;
1725
1726 Node *node = static_cast<Node *>(bottom.internalPointer());
1727
1728 if (!node) {
1729 return;
1730 }
1731
1732 if (node->type == Node::Collection) {
1733 rightIndex = bottom.sibling(bottom.row(), q->entityColumnCount(EntityTreeModel::CollectionTreeHeaders) - 1);
1734 }
1735 if (node->type == Node::Item) {
1736 rightIndex = bottom.sibling(bottom.row(), q->entityColumnCount(EntityTreeModel::ItemListHeaders) - 1);
1737 }
1738
1739 emit q->dataChanged(top, rightIndex);
1740}
1741
1742QModelIndex EntityTreeModelPrivate::indexForCollection(const Collection &collection) const
1743{
1744 Q_Q(const EntityTreeModel);
1745
1746 if (!collection.isValid()) {
1747 return QModelIndex();
1748 }
1749
1750 if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) {
1751 return QModelIndex();
1752 }
1753
1754 // The id of the parent of Collection::root is not guaranteed to be -1 as assumed by startFirstListJob,
1755 // we ensure that we use -1 for the invalid Collection.
1756 Collection::Id parentId = -1;
1757
1758 if ((collection == m_rootCollection)) {
1759 if (m_showRootCollection) {
1760 return q->createIndex(0, 0, static_cast<void *>(m_rootNode));
1761 }
1762 return QModelIndex();
1763 }
1764
1765 if (collection == Collection::root()) {
1766 parentId = -1;
1767 } else if (collection.parentCollection().isValid()) {
1768 parentId = collection.parentCollection().id();
1769 } else {
1770 QHash<Entity::Id, QList<Node *> >::const_iterator it = m_childEntities.constBegin();
1771 const QHash<Entity::Id, QList<Node *> >::const_iterator end = m_childEntities.constEnd();
1772 for (; it != end; ++it) {
1773 const int row = indexOf<Node::Collection>(it.value(), collection.id());
1774 if (row < 0) {
1775 continue;
1776 }
1777
1778 Node *node = it.value().at(row);
1779 return q->createIndex(row, 0, static_cast<void *>(node));
1780 }
1781 return QModelIndex();
1782 }
1783
1784 const int row = indexOf<Node::Collection>(m_childEntities.value(parentId), collection.id());
1785
1786 if (row < 0) {
1787 return QModelIndex();
1788 }
1789
1790 Node *node = m_childEntities.value(parentId).at(row);
1791
1792 return q->createIndex(row, 0, static_cast<void *>(node));
1793}
1794
1795QModelIndexList EntityTreeModelPrivate::indexesForItem(const Item &item) const
1796{
1797 Q_Q(const EntityTreeModel);
1798 QModelIndexList indexes;
1799
1800 if (m_collectionFetchStrategy == EntityTreeModel::FetchNoCollections) {
1801 Q_ASSERT(m_childEntities.contains(m_rootCollection.id()));
1802 QList<Node *> nodeList = m_childEntities.value(m_rootCollection.id());
1803 const int row = indexOf<Node::Item>(nodeList, item.id());
1804 Q_ASSERT(row >= 0);
1805 Q_ASSERT(row < nodeList.size());
1806 Node *node = nodeList.at(row);
1807
1808 indexes << q->createIndex(row, 0, static_cast<void *>(node));
1809 return indexes;
1810 }
1811
1812 const Collection::List collections = getParentCollections(item);
1813
1814 foreach (const Collection &collection, collections) {
1815 const int row = indexOf<Node::Item>(m_childEntities.value(collection.id()), item.id());
1816 Q_ASSERT(row >= 0);
1817 Q_ASSERT(m_childEntities.contains(collection.id()));
1818 QList<Node *> nodeList = m_childEntities.value(collection.id());
1819 Q_ASSERT(row < nodeList.size());
1820 Node *node = nodeList.at(row);
1821
1822 indexes << q->createIndex(row, 0, static_cast<void *>(node));
1823 }
1824
1825 return indexes;
1826}
1827
1828void EntityTreeModelPrivate::beginResetModel()
1829{
1830 Q_Q(EntityTreeModel);
1831 q->beginResetModel();
1832}
1833
1834void EntityTreeModelPrivate::endResetModel()
1835{
1836 Q_Q(EntityTreeModel);
1837 foreach (Akonadi::Job *job, m_session->findChildren<Akonadi::Job *>()) {
1838 job->disconnect(q);
1839 }
1840 m_collections.clear();
1841 m_collectionsWithoutItems.clear();
1842 m_populatedCols.clear();
1843 m_items.clear();
1844
1845 foreach (const QList<Node *> &list, m_childEntities) {
1846 qDeleteAll(list);
1847 }
1848 m_childEntities.clear();
1849 m_rootNode = 0;
1850
1851 q->endResetModel();
1852 fillModel();
1853}
1854
1855void EntityTreeModelPrivate::monitoredItemsRetrieved(KJob *job)
1856{
1857 if (job->error()) {
1858 qWarning() << job->errorString();
1859 return;
1860 }
1861
1862 Q_Q(EntityTreeModel);
1863
1864 ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
1865 Q_ASSERT(fetchJob);
1866 Item::List list = fetchJob->items();
1867
1868 q->beginResetModel();
1869 foreach (const Item &item, list) {
1870 Node *node = new Node;
1871 node->id = item.id();
1872 node->parent = m_rootCollection.id();
1873 node->type = Node::Item;
1874
1875 m_childEntities[-1].append(node);
1876 m_items.insert(item.id(), item);
1877 }
1878
1879 q->endResetModel();
1880}
1881
1882void EntityTreeModelPrivate::fillModel()
1883{
1884 Q_Q(EntityTreeModel);
1885
1886 m_mimeChecker.setWantedMimeTypes(m_monitor->mimeTypesMonitored());
1887
1888 const QList<Collection> collections = m_monitor->collectionsMonitored();
1889
1890 if (collections.isEmpty() &&
1891 m_monitor->mimeTypesMonitored().isEmpty() &&
1892 m_monitor->resourcesMonitored().isEmpty() &&
1893 !m_monitor->itemsMonitoredEx().isEmpty()) {
1894 m_rootCollection = Collection(-1);
1895 m_collectionTreeFetched = true;
1896 emit q_ptr->collectionTreeFetched(collections); // there are no collections to fetch
1897
1898 Item::List items;
1899 foreach (Entity::Id id, m_monitor->itemsMonitoredEx()) {
1900 items.append(Item(id));
1901 }
1902 ItemFetchJob *itemFetch = new ItemFetchJob(items, m_session);
1903 itemFetch->setFetchScope(m_monitor->itemFetchScope());
1904 itemFetch->fetchScope().setIgnoreRetrievalErrors(true);
1905 q->connect(itemFetch, SIGNAL(finished(KJob*)), q, SLOT(monitoredItemsRetrieved(KJob*)));
1906 return;
1907 }
1908 // In case there is only a single collection monitored, we can use this
1909 // collection as root of the node tree, in all other cases
1910 // Collection::root() is used
1911 if (collections.size() == 1) {
1912 m_rootCollection = collections.first();
1913 } else {
1914 m_rootCollection = Collection::root();
1915 }
1916
1917 if (m_rootCollection == Collection::root()) {
1918 QTimer::singleShot(0, q, SLOT(startFirstListJob()));
1919 } else {
1920 Q_ASSERT(m_rootCollection.isValid());
1921 CollectionFetchJob *rootFetchJob = new CollectionFetchJob(m_rootCollection, CollectionFetchJob::Base, m_session);
1922 q->connect(rootFetchJob, SIGNAL(result(KJob*)),
1923 SLOT(rootFetchJobDone(KJob*)));
1924 ifDebug(kDebug() << ""; jobTimeTracker[rootFetchJob].start();)
1925 }
1926}
1927
1928bool EntityTreeModelPrivate::canFetchMore(const QModelIndex &parent) const
1929{
1930 const Item item = parent.data(EntityTreeModel::ItemRole).value<Item>();
1931
1932 if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) {
1933 return false;
1934 }
1935
1936 if (item.isValid()) {
1937 // items can't have more rows.
1938 // TODO: Should I use this for fetching more of an item, ie more payload parts?
1939 return false;
1940 } else {
1941 // but collections can...
1942 const Collection::Id colId = parent.data(EntityTreeModel::CollectionIdRole).toULongLong();
1943
1944 // But the root collection can't...
1945 if (Collection::root().id() == colId) {
1946 return false;
1947 }
1948
1949 // Collections which contain no items at all can't contain more
1950 if (m_collectionsWithoutItems.contains(colId)) {
1951 return false;
1952 }
1953
1954 // Don't start the same job multiple times.
1955 if (m_pendingCollectionRetrieveJobs.contains(colId)) {
1956 return false;
1957 }
1958
1959 // Can't fetch more if the collection's items have already been fetched
1960 if (m_populatedCols.contains(colId)) {
1961 return false;
1962 }
1963
1964 foreach (Node *node, m_childEntities.value(colId)) {
1965 if (Node::Item == node->type) {
1966 // Only try to fetch more from a collection if we don't already have items in it.
1967 // Otherwise we'd spend all the time listing items in collections.
1968 return false;
1969 }
1970 }
1971
1972 return true;
1973 }
1974}
1975