notificationcollector.cpp 24.9 KB
Newer Older
1
/*
2
    Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "notificationcollector.h"
#include "storage/datastore.h"
#include "storage/entity.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
23
#include "storage/collectionstatistics.h"
24
#include "handlerhelper.h"
25
26
#include "cachecleaner.h"
#include "intervalcheck.h"
27
#include "search/searchmanager.h"
28
#include "akonadi.h"
29
30
31
#include "notificationmanager.h"
#include "aggregatedfetchscope.h"
#include "selectquerybuilder.h"
32
#include "handler/itemfetchhelper.h"
33
#include "connection.h"
34
#include "shared/akranges.h"
35

36
#include "akonadiserver_debug.h"
37

38
39
#include <QScopedValueRollback>

40
using namespace Akonadi;
41
using namespace Akonadi::Server;
42

43
NotificationCollector::NotificationCollector(AkonadiServer &akonadi, DataStore *db)
44
    : mDb(db)
45
    , mAkonadi(akonadi)
46
47
48
49
50
51
52
53
54
55
56
57
58
{
    QObject::connect(db, &DataStore::transactionCommitted,
                     [this]() {
                         if (!mIgnoreTransactions) {
                             dispatchNotifications();
                         }
                     });
    QObject::connect(db, &DataStore::transactionRolledBack,
                     [this]() {
                         if (!mIgnoreTransactions) {
                             clear();
                         }
                     });
59
60
}

61
void NotificationCollector::itemAdded(const PimItem &item,
62
                                      bool seen,
63
64
                                      const Collection &collection,
                                      const QByteArray &resource)
65
{
66
    mAkonadi.searchManager().scheduleSearchUpdate();
67
    mAkonadi.collectionStatistics().itemAdded(collection, item.size(), seen);
68
    itemNotification(Protocol::ItemChangeNotification::Add, item, collection, Collection(), resource);
69
70
}

71
72
73
74
void NotificationCollector::itemChanged(const PimItem &item,
                                        const QSet<QByteArray> &changedParts,
                                        const Collection &collection,
                                        const QByteArray &resource)
75
{
76
    mAkonadi.searchManager().scheduleSearchUpdate();
77
    itemNotification(Protocol::ItemChangeNotification::Modify, item, collection, Collection(), resource, changedParts);
78
79
}

80
void NotificationCollector::itemsFlagsChanged(const PimItem::List &items,
Laurent Montel's avatar
Laurent Montel committed
81
82
        const QSet<QByteArray> &addedFlags,
        const QSet<QByteArray> &removedFlags,
Laurent Montel's avatar
Laurent Montel committed
83
84
        const Collection &collection,
        const QByteArray &resource)
85
{
86
    int seenCount = (addedFlags.contains(AKONADI_FLAG_SEEN) || addedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0);
87
    seenCount -= (removedFlags.contains(AKONADI_FLAG_SEEN) || removedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0);
88

89
    mAkonadi.collectionStatistics().itemsSeenChanged(collection, seenCount);
90
    itemNotification(Protocol::ItemChangeNotification::ModifyFlags, items, collection, Collection(), resource, QSet<QByteArray>(), addedFlags, removedFlags);
91
92
}

93
void NotificationCollector::itemsTagsChanged(const PimItem::List &items,
Laurent Montel's avatar
Laurent Montel committed
94
95
96
97
        const QSet<qint64> &addedTags,
        const QSet<qint64> &removedTags,
        const Collection &collection,
        const QByteArray &resource)
98
{
99
    itemNotification(Protocol::ItemChangeNotification::ModifyTags, items, collection, Collection(), resource, QSet<QByteArray>(), QSet<QByteArray>(), QSet<QByteArray>(), addedTags, removedTags);
100
101
}

102
void NotificationCollector::itemsRelationsChanged(const PimItem::List &items,
Laurent Montel's avatar
Laurent Montel committed
103
104
105
106
        const Relation::List &addedRelations,
        const Relation::List &removedRelations,
        const Collection &collection,
        const QByteArray &resource)
107
{
108
    itemNotification(Protocol::ItemChangeNotification::ModifyRelations, items, collection, Collection(), resource, QSet<QByteArray>(), QSet<QByteArray>(), QSet<QByteArray>(), QSet<qint64>(), QSet<qint64>(), addedRelations, removedRelations);
109
110
}

111
112
113
114
void NotificationCollector::itemsMoved(const PimItem::List &items,
                                       const Collection &collectionSrc,
                                       const Collection &collectionDest,
                                       const QByteArray &sourceResource)
115
{
116
    mAkonadi.searchManager().scheduleSearchUpdate();
117
    itemNotification(Protocol::ItemChangeNotification::Move, items, collectionSrc, collectionDest, sourceResource);
118
119
}

120
void NotificationCollector::itemsRemoved(const PimItem::List &items,
Laurent Montel's avatar
Laurent Montel committed
121
122
        const Collection &collection,
        const QByteArray &resource)
123
{
124
    itemNotification(Protocol::ItemChangeNotification::Remove, items, collection, Collection(), resource);
125
126
}

127
void NotificationCollector::itemsLinked(const PimItem::List &items, const Collection &collection)
128
{
129
    itemNotification(Protocol::ItemChangeNotification::Link, items, collection, Collection(), QByteArray());
130
131
}

132
void NotificationCollector::itemsUnlinked(const PimItem::List &items, const Collection &collection)
133
{
134
    itemNotification(Protocol::ItemChangeNotification::Unlink, items, collection, Collection(), QByteArray());
135
136
}

137
void NotificationCollector::collectionAdded(const Collection &collection,
Laurent Montel's avatar
Laurent Montel committed
138
        const QByteArray &resource)
139
{
140
    if (auto cleaner = mAkonadi.cacheCleaner()) {
141
        cleaner->collectionAdded(collection.id());
142
    }
143
    mAkonadi.intervalChecker().collectionAdded(collection.id());
144
    collectionNotification(Protocol::CollectionChangeNotification::Add, collection, collection.parentId(), -1, resource);
145
146
}

147
void NotificationCollector::collectionChanged(const Collection &collection,
Laurent Montel's avatar
Laurent Montel committed
148
149
        const QList<QByteArray> &changes,
        const QByteArray &resource)
150
{
151
    if (auto cleaner = mAkonadi.cacheCleaner()) {
152
        cleaner->collectionChanged(collection.id());
153
    }
154
    mAkonadi.intervalChecker().collectionChanged(collection.id());
155
    if (changes.contains(AKONADI_PARAM_ENABLED)) {
156
        mAkonadi.collectionStatistics().invalidateCollection(collection);
157
    }
158
    collectionNotification(Protocol::CollectionChangeNotification::Modify, collection, collection.parentId(),
Daniel Vrátil's avatar
Daniel Vrátil committed
159
                           -1, resource, changes | AkRanges::Actions::toQSet);
160
161
}

162
void NotificationCollector::collectionMoved(const Collection &collection,
Laurent Montel's avatar
Laurent Montel committed
163
164
165
        const Collection &source,
        const QByteArray &resource,
        const QByteArray &destResource)
166
{
167
    if (auto cleaner = mAkonadi.cacheCleaner()) {
168
        cleaner->collectionChanged(collection.id());
169
    }
170
    mAkonadi.intervalChecker().collectionChanged(collection.id());
171
    collectionNotification(Protocol::CollectionChangeNotification::Move, collection, source.id(), collection.parentId(), resource, QSet<QByteArray>(), destResource);
172
173
}

174
void NotificationCollector::collectionRemoved(const Collection &collection,
Laurent Montel's avatar
Laurent Montel committed
175
        const QByteArray &resource)
176
{
177
    if (auto cleaner = mAkonadi.cacheCleaner()) {
178
        cleaner->collectionRemoved(collection.id());
179
    }
180
    mAkonadi.intervalChecker().collectionRemoved(collection.id());
181
    mAkonadi.collectionStatistics().invalidateCollection(collection);
182
    collectionNotification(Protocol::CollectionChangeNotification::Remove, collection, collection.parentId(), -1, resource);
183
184
}

185
void NotificationCollector::collectionSubscribed(const Collection &collection,
Laurent Montel's avatar
Laurent Montel committed
186
        const QByteArray &resource)
187
{
188
    if (auto cleaner = mAkonadi.cacheCleaner()) {
189
        cleaner->collectionAdded(collection.id());
190
    }
191
    mAkonadi.intervalChecker().collectionAdded(collection.id());
192
    collectionNotification(Protocol::CollectionChangeNotification::Subscribe, collection, collection.parentId(), -1, resource, QSet<QByteArray>());
193
194
}

195
void NotificationCollector::collectionUnsubscribed(const Collection &collection,
Laurent Montel's avatar
Laurent Montel committed
196
        const QByteArray &resource)
197
{
198
    if (auto cleaner = mAkonadi.cacheCleaner()) {
199
        cleaner->collectionRemoved(collection.id());
200
    }
201
    mAkonadi.intervalChecker().collectionRemoved(collection.id());
202
    mAkonadi.collectionStatistics().invalidateCollection(collection);
203
    collectionNotification(Protocol::CollectionChangeNotification::Unsubscribe, collection, collection.parentId(), -1, resource, QSet<QByteArray>());
204
205
}

206
void NotificationCollector::tagAdded(const Tag &tag)
207
{
208
    tagNotification(Protocol::TagChangeNotification::Add, tag);
209
210
}

211
void NotificationCollector::tagChanged(const Tag &tag)
212
{
213
    tagNotification(Protocol::TagChangeNotification::Modify, tag);
214
215
}

216
217
void NotificationCollector::tagRemoved(const Tag &tag, const QByteArray &resource, const QString &remoteId)
{
218
    tagNotification(Protocol::TagChangeNotification::Remove, tag, resource, remoteId);
219
220
221
}

void NotificationCollector::relationAdded(const Relation &relation)
222
{
223
    relationNotification(Protocol::RelationChangeNotification::Add, relation);
224
225
226
227
}

void NotificationCollector::relationRemoved(const Relation &relation)
{
228
    relationNotification(Protocol::RelationChangeNotification::Remove, relation);
229
230
}

231
void NotificationCollector::clear()
232
{
233
    mNotifications.clear();
234
235
}

236
void NotificationCollector::setConnection(Connection *connection)
237
{
238
    mConnection = connection;
239
240
}

241
void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op,
Laurent Montel's avatar
Laurent Montel committed
242
243
244
245
246
        const PimItem &item,
        const Collection &collection,
        const Collection &collectionDest,
        const QByteArray &resource,
        const QSet<QByteArray> &parts)
247
{
248
249
250
    PimItem::List items;
    items << item;
    itemNotification(op, items, collection, collectionDest, resource, parts);
251
252
}

253
void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op,
Laurent Montel's avatar
Laurent Montel committed
254
255
256
257
258
259
260
261
262
263
264
        const PimItem::List &items,
        const Collection &collection,
        const Collection &collectionDest,
        const QByteArray &resource,
        const QSet<QByteArray> &parts,
        const QSet<QByteArray> &addedFlags,
        const QSet<QByteArray> &removedFlags,
        const QSet<qint64> &addedTags,
        const QSet<qint64> &removedTags,
        const Relation::List &addedRelations,
        const Relation::List &removedRelations)
265
{
266
267
    QMap<Entity::Id, QList<PimItem> > vCollections;

268
    if ((op == Protocol::ItemChangeNotification::Modify) ||
Laurent Montel's avatar
Laurent Montel committed
269
270
271
            (op == Protocol::ItemChangeNotification::ModifyFlags) ||
            (op == Protocol::ItemChangeNotification::ModifyTags) ||
            (op == Protocol::ItemChangeNotification::ModifyRelations)) {
272
273
274
        vCollections = DataStore::self()->virtualCollections(items);
    }

275
    auto msg = Protocol::ItemChangeNotificationPtr::create();
276
277
278
    if (mConnection) {
        msg->setSessionId(mConnection->sessionId());
    }
279
280
281
282
283
284
285
    msg->setOperation(op);

    msg->setItemParts(parts);
    msg->setAddedFlags(addedFlags);
    msg->setRemovedFlags(removedFlags);
    msg->setAddedTags(addedTags);
    msg->setRemovedTags(removedTags);
286
287
288
289
290
    if (!addedRelations.isEmpty()) {
        QSet<Protocol::ItemChangeNotification::Relation> rels;
        Q_FOREACH (const Relation &rel, addedRelations) {
            rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name()));
        }
291
        msg->setAddedRelations(rels);
292
293
294
295
296
297
    }
    if (!removedRelations.isEmpty()) {
        QSet<Protocol::ItemChangeNotification::Relation> rels;
        Q_FOREACH (const Relation &rel, removedRelations) {
            rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name()));
        }
298
        msg->setRemovedRelations(rels);
299
    }
300
301
302
303

    if (collectionDest.isValid()) {
        QByteArray destResourceName;
        destResourceName = collectionDest.resource().name().toLatin1();
304
        msg->setDestinationResource(destResourceName);
305
    }
306

307
    msg->setParentDestCollection(collectionDest.id());
308

309
    QVector<Protocol::FetchItemsResponse> ntfItems;
310
    Q_FOREACH (const PimItem &item, items) {
311
312
313
314
315
316
        Protocol::FetchItemsResponse i;
        i.setId(item.id());
        i.setRemoteId(item.remoteId());
        i.setRemoteRevision(item.remoteRevision());
        i.setMimeType(item.mimeType().name());
        ntfItems.push_back(std::move(i));
317
318
    }

319
    /* Notify all virtual collections the items are linked to. */
320
    QHash<qint64, Protocol::FetchItemsResponse> virtItems;
321
    for (const auto &ntfItem : ntfItems) {
322
        virtItems.insert(ntfItem.id(), std::move(ntfItem));
323
    }
324
325
    auto iter = vCollections.constBegin(), endIter = vCollections.constEnd();
    for (; iter != endIter; ++iter) {
326
        auto copy = Protocol::ItemChangeNotificationPtr::create(*msg);
327
        QVector<Protocol::FetchItemsResponse> items;
328
329
330
        items.reserve(iter->size());
        for (const auto &item : qAsConst(*iter)) {
            items.append(virtItems.value(item.id()));
331
        }
332
333
334
        copy->setItems(items);
        copy->setParentCollection(iter.key());
        copy->setResource(resource);
335

336
        mAkonadi.collectionStatistics().invalidateCollection(Collection::retrieveById(iter.key()));
337
338
339
        dispatchNotification(copy);
    }

340
    msg->setItems(ntfItems);
341
342
343

    Collection col;
    if (!collection.isValid()) {
344
        msg->setParentCollection(items.first().collection().id());
345
346
        col = items.first().collection();
    } else {
347
        msg->setParentCollection(collection.id());
348
349
350
351
352
        col = collection;
    }

    QByteArray res = resource;
    if (res.isEmpty()) {
353
354
355
        if (col.resourceId() <= 0) {
            col = Collection::retrieveById(col.id());
        }
356
357
        res = col.resource().name().toLatin1();
    }
358
    msg->setResource(res);
359

360
361
    // Add and ModifyFlags are handled incrementally
    // (see itemAdded() and itemsFlagsChanged())
362
363
    if (msg->operation() != Protocol::ItemChangeNotification::Add
            && msg->operation() != Protocol::ItemChangeNotification::ModifyFlags) {
364
        mAkonadi.collectionStatistics().invalidateCollection(col);
365
    }
366
    dispatchNotification(msg);
367
368
}

369
void NotificationCollector::collectionNotification(Protocol::CollectionChangeNotification::Operation op,
Laurent Montel's avatar
Laurent Montel committed
370
371
372
373
374
375
        const Collection &collection,
        Collection::Id source,
        Collection::Id destination,
        const QByteArray &resource,
        const QSet<QByteArray> &changes,
        const QByteArray &destResource)
376
{
377
378
    auto msg = Protocol::CollectionChangeNotificationPtr::create();
    msg->setOperation(op);
379
380
381
    if (mConnection) {
        msg->setSessionId(mConnection->sessionId());
    }
382
383
384
385
    msg->setParentCollection(source);
    msg->setParentDestCollection(destination);
    msg->setDestinationResource(destResource);
    msg->setChangedParts(changes);
386

387
    auto msgCollection = HandlerHelper::fetchCollectionsResponse(mAkonadi, collection);
388
    if (auto mgr = mAkonadi.notificationManager()) {
389
390
        auto fetchScope = mgr->collectionFetchScope();
        // Make sure we have all the data
391
392
        if (!fetchScope->fetchIdOnly() && msgCollection.name().isEmpty()) {
            const auto col = Collection::retrieveById(msgCollection.id());
393
394
395
396
397
398
            const auto mts = col.mimeTypes();
            QStringList mimeTypes;
            mimeTypes.reserve(mts.size());
            for (const auto &mt : mts) {
                mimeTypes.push_back(mt.name());
            }
399
            msgCollection = HandlerHelper::fetchCollectionsResponse(mAkonadi, col, {}, false, 0, {}, {}, mimeTypes);
400
401
402
403
        }
        // Get up-to-date statistics
        if (fetchScope->fetchStatistics()) {
            Collection col;
404
            col.setId(msgCollection.id());
405
            const auto stats = mAkonadi.collectionStatistics().statistics(col);
406
            msgCollection.setStatistics(Protocol::FetchCollectionStatsResponse(stats.count, stats.count - stats.read, stats.size));
407
408
409
        }
        // Get attributes
        const auto requestedAttrs = fetchScope->attributes();
410
        auto msgColAttrs = msgCollection.attributes();
411
412
413
414
415
416
        // TODO: This assumes that we have either none or all attributes in msgCollection
        if (msgColAttrs.isEmpty() && !requestedAttrs.isEmpty()) {
            SelectQueryBuilder<CollectionAttribute> qb;
            qb.addColumn(CollectionAttribute::typeFullColumnName());
            qb.addColumn(CollectionAttribute::valueFullColumnName());
            qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(),
417
                                    Query::Equals, msgCollection.id());
418
419
420
421
422
423
            Query::Condition cond(Query::Or);
            for (const auto &attr : requestedAttrs) {
                cond.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::Equals, attr);
            }
            qb.addCondition(cond);
            if (!qb.exec()) {
424
425
                qCWarning(AKONADISERVER_LOG) << "NotificationCollector failed to query attributes for Collection"
                                             << collection.name() << "(ID" << collection.id() << ")";
426
427
428
429
430
            }
            const auto attrs = qb.result();
            for (const auto &attr : attrs)  {
                msgColAttrs.insert(attr.type(), attr.value());
            }
431
            msgCollection.setAttributes(msgColAttrs);
432
433
        }
    }
434
    msg->setCollection(std::move(msgCollection));
435

Daniel Vrátil's avatar
Daniel Vrátil committed
436
    if (!collection.enabled()) {
437
        msg->addMetadata("DISABLED");
Daniel Vrátil's avatar
Daniel Vrátil committed
438
    }
439

440
441
442
443
    QByteArray res = resource;
    if (res.isEmpty()) {
        res = collection.resource().name().toLatin1();
    }
444
    msg->setResource(res);
445
446

    dispatchNotification(msg);
447
448
}

449
void NotificationCollector::tagNotification(Protocol::TagChangeNotification::Operation op,
Laurent Montel's avatar
Laurent Montel committed
450
451
        const Tag &tag,
        const QByteArray &resource,
452
        const QString &remoteId)
453
{
454
455
    auto msg = Protocol::TagChangeNotificationPtr::create();
    msg->setOperation(op);
456
457
458
    if (mConnection) {
        msg->setSessionId(mConnection->sessionId());
    }
459
    msg->setResource(resource);
460
461
    Protocol::FetchTagsResponse msgTag;
    msgTag.setId(tag.id());
462
    msgTag.setRemoteId(remoteId.toUtf8());
463
    msgTag.setParentId(tag.parentId());
464
    if (auto mgr = mAkonadi.notificationManager()) {
465
        auto fetchScope = mgr->tagFetchScope();
466
        if (!fetchScope->fetchIdOnly() && msgTag.gid().isEmpty()) {
467
            msgTag = HandlerHelper::fetchTagsResponse(Tag::retrieveById(msgTag.id()), fetchScope->toFetchScope(), mConnection);
468
469
470
        }

        const auto requestedAttrs = fetchScope->attributes();
471
        auto msgTagAttrs = msgTag.attributes();
472
473
474
475
        if (msgTagAttrs.isEmpty() && !requestedAttrs.isEmpty()) {
            SelectQueryBuilder<TagAttribute> qb;
            qb.addColumn(TagAttribute::typeFullColumnName());
            qb.addColumn(TagAttribute::valueFullColumnName());
476
            qb.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, msgTag.id());
477
478
479
480
481
482
            Query::Condition cond(Query::Or);
            for (const auto &attr : requestedAttrs) {
                cond.addValueCondition(TagAttribute::typeFullColumnName(), Query::Equals, attr);
            }
            qb.addCondition(cond);
            if (!qb.exec()) {
483
                qCWarning(AKONADISERVER_LOG) << "NotificationCollection failed to query attributes for Tag" << tag.id();
484
485
486
487
488
            }
            const auto attrs = qb.result();
            for (const auto &attr : attrs) {
                msgTagAttrs.insert(attr.type(), attr.value());
            }
489
            msgTag.setAttributes(msgTagAttrs);
490
491
        }
    }
492
    msg->setTag(std::move(msgTag));
493
494
495
496

    dispatchNotification(msg);
}

497
void NotificationCollector::relationNotification(Protocol::RelationChangeNotification::Operation op,
Laurent Montel's avatar
Laurent Montel committed
498
        const Relation &relation)
499
{
500
501
    auto msg = Protocol::RelationChangeNotificationPtr::create();
    msg->setOperation(op);
502
503
504
    if (mConnection) {
        msg->setSessionId(mConnection->sessionId());
    }
505
    msg->setRelation(HandlerHelper::fetchRelationsResponse(relation));
506

507
    dispatchNotification(msg);
508
509
}

510
511
512
513
void NotificationCollector::completeNotification(const Protocol::ChangeNotificationPtr &changeMsg)
{
    if (changeMsg->type() == Protocol::Command::ItemChangeNotification) {
        const auto msg = changeMsg.staticCast<Protocol::ItemChangeNotification>();
514
        const auto mgr = mAkonadi.notificationManager();
515
516
        if (mgr && msg->operation() != Protocol::ItemChangeNotification::Remove) {
            if (mDb->inTransaction()) {
517
518
                qCWarning(AKONADISERVER_LOG) << "NotificationCollector requested FetchHelper from within a transaction."
                                             << "Aborting since this would deadlock!";
519
520
521
522
523
524
525
526
527
528
529
                return;
            }
            auto fetchScope = mgr->itemFetchScope();
            // NOTE: Checking and retrieving missing elements for each Item manually
            // here would require a complex code (and I'm too lazy), so instead we simply
            // feed the Items to FetchHelper and retrieve them all with the setup from
            // the aggregated fetch scope. The worst case is that we re-fetch everything
            // we already have, but that's stil better than the pre-ntf-payload situation
            QVector<qint64> ids;
            const auto items = msg->items();
            ids.reserve(items.size());
530
            bool allHaveRID = true;
531
            for (const auto &item : items) {
532
                ids.push_back(item.id());
533
                allHaveRID &= !item.remoteId().isEmpty();
534
            }
535
536

            // FetchHelper may trigger ItemRetriever, which needs RemoteID. If we
537
            // don't have one (maybe because the Resource has not stored it yet,
538
539
540
            // we emit a notification without it and leave it up to the Monitor
            // to retrieve the Item on demand - we should have a RID stored in
            // Akonadi by then.
541
            if (mConnection && (allHaveRID || msg->operation() != Protocol::ItemChangeNotification::Add)) {
542
543
544
545
546

                // Prevent transactions inside FetchHelper to recursively call our slot
                QScopedValueRollback<bool> ignoreTransactions(mIgnoreTransactions);
                mIgnoreTransactions = true;
                CommandContext context;
547
548
549
                auto itemFetchScope = fetchScope->toFetchScope();
                auto tagFetchScope = mgr->tagFetchScope()->toFetchScope();
                itemFetchScope.setFetch(Protocol::ItemFetchScope::CacheOnly);
550
                ItemFetchHelper helper(mConnection, context, Scope(ids), itemFetchScope, tagFetchScope, mAkonadi);
551
552
553
                // The Item was just changed, which means the atime was
                // updated, no need to do it again a couple milliseconds later.
                helper.disableATimeUpdates();
554
555
556
557
558
559
560
                QVector<Protocol::FetchItemsResponse> fetchedItems;
                auto callback = [&fetchedItems](Protocol::FetchItemsResponse &&cmd) {
                    fetchedItems.push_back(std::move(cmd));
                };
                if (helper.fetchItems(std::move(callback))) {
                    msg->setItems(fetchedItems);
                } else {
561
                    qCWarning(AKONADISERVER_LOG) << "NotificationCollector railed to retrieve Items for notification!";
562
                }
563
            } else {
564
565
566
567
                QVector<Protocol::FetchItemsResponse> fetchedItems;
                for (const auto &item : items) {
                    Protocol::FetchItemsResponse resp;
                    resp.setId(item.id());
568
                    resp.setRevision(item.revision());
569
570
                    resp.setMimeType(item.mimeType());
                    resp.setParentId(item.parentId());
571
572
573
574
                    resp.setGid(item.gid());
                    resp.setSize(item.size());
                    resp.setMTime(item.mTime());
                    resp.setFlags(item.flags());
575
576
577
578
                    fetchedItems.push_back(std::move(resp));
                }
                msg->setItems(fetchedItems);
                msg->setMustRetrieve(true);
579
580
581
582
583
            }
        }
    }
}

584
void NotificationCollector::dispatchNotification(const Protocol::ChangeNotificationPtr &msg)
585
{
586
    if (!mDb || mDb->inTransaction()) {
587
        if (msg->type() == Protocol::Command::CollectionChangeNotification) {
588
589
590
591
            Protocol::CollectionChangeNotification::appendAndCompress(mNotifications, msg);
        } else {
            mNotifications.append(msg);
        }
592
    } else {
593
        completeNotification(msg);
594
        notify({msg});
595
    }
596
597
}

598
bool NotificationCollector::dispatchNotifications()
599
{
600
    if (!mNotifications.isEmpty()) {
601
602
603
        for (auto &ntf : mNotifications) {
            completeNotification(ntf);
        }
604
        notify(std::move(mNotifications));
605
        clear();
606
        return true;
607
    }
608
609

    return false;
610
}
611
612
613

void NotificationCollector::notify(Protocol::ChangeNotificationList msgs)
{
614
    if (auto mgr = mAkonadi.notificationManager()) {
615
616
617
618
        QMetaObject::invokeMethod(mgr, "slotNotify", Qt::QueuedConnection,
                                  Q_ARG(Akonadi::Protocol::ChangeNotificationList, msgs));
    }
}