diff --git a/autotests/libs/invalidatecachejobtest.cpp b/autotests/libs/invalidatecachejobtest.cpp index 9249f433f..6c6422fa3 100644 --- a/autotests/libs/invalidatecachejobtest.cpp +++ b/autotests/libs/invalidatecachejobtest.cpp @@ -1,89 +1,89 @@ /* Copyright (c) 2019 David Faure 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 "invalidatecachejob_p.h" #include #include #include #include #include "test_utils.h" #include "control.h" using namespace Akonadi; class InvalidateCacheJobTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void shouldClearPayload(); }; void InvalidateCacheJobTest::initTestCase() { AkonadiTest::checkTestIsIsolated(); Control::start(); } void InvalidateCacheJobTest::shouldClearPayload() { // Find collection by name Collection col(collectionIdFromPath(QStringLiteral("res1/foo"))); const int colId = col.id(); QVERIFY(colId > 0); // Find item with remote id "C" auto *listJob = new ItemFetchJob(Collection(colId), this); AKVERIFYEXEC(listJob); const Item::List items = listJob->items(); QVERIFY(!items.isEmpty()); - auto it = std::find_if(items.cbegin(), items.cend(), [](const Item &item) { return item.remoteId() == QLatin1String("C"); }); + auto it = std::find_if(items.cbegin(), items.cend(), [](const Item &item) { return item.remoteId() == QLatin1Char('C'); }); QVERIFY(it != items.cend()); const Item::Id itemId = it->id(); // Fetch item, from resource, with payload auto *fetchJob = new ItemFetchJob(Item(itemId), this); fetchJob->fetchScope().fetchFullPayload(); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().payloadData(), "testmailbody2"); // Invalidate cache auto *invCacheJob = new InvalidateCacheJob(Collection(colId), this); AKVERIFYEXEC(invCacheJob); // Fetch item from cache, should have no payload anymore auto *fetchFromCacheJob = new ItemFetchJob(Item(itemId), this); fetchFromCacheJob->fetchScope().fetchFullPayload(); fetchFromCacheJob->fetchScope().setCacheOnly(true); AKVERIFYEXEC(fetchFromCacheJob); QVERIFY(fetchFromCacheJob->items().first().payloadData().isEmpty()); // Fetch item from resource again auto *fetchAgainJob = new ItemFetchJob(Item(itemId), this); fetchAgainJob->fetchScope().fetchFullPayload(); AKVERIFYEXEC(fetchAgainJob); QCOMPARE(fetchAgainJob->items().first().payloadData(), "testmailbody2"); } QTEST_AKONADIMAIN(InvalidateCacheJobTest) #include "invalidatecachejobtest.moc" diff --git a/autotests/libs/itemfetchtest.cpp b/autotests/libs/itemfetchtest.cpp index aa597b090..66ff9d9d1 100644 --- a/autotests/libs/itemfetchtest.cpp +++ b/autotests/libs/itemfetchtest.cpp @@ -1,280 +1,280 @@ /* Copyright (c) 2007 Volker Krause 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 "itemfetchtest.h" #include "collectionpathresolver.h" #include "testattribute.h" #include #include #include #include #include #include using namespace Akonadi; #include QTEST_AKONADIMAIN(ItemFetchTest) void ItemFetchTest::initTestCase() { AkonadiTest::checkTestIsIsolated(); qRegisterMetaType(); AttributeFactory::registerAttribute(); } void ItemFetchTest::testFetch() { CollectionPathResolver *resolver = new CollectionPathResolver(QStringLiteral("res1"), this); AKVERIFYEXEC(resolver); int colId = resolver->collection(); // listing of an empty folder ItemFetchJob *job = new ItemFetchJob(Collection(colId), this); AKVERIFYEXEC(job); QVERIFY(job->items().isEmpty()); resolver = new CollectionPathResolver(QStringLiteral("res1/foo"), this); AKVERIFYEXEC(resolver); int colId2 = resolver->collection(); // listing of a non-empty folder job = new ItemFetchJob(Collection(colId2), this); QSignalSpy spy(job, &ItemFetchJob::itemsReceived); QVERIFY(spy.isValid()); AKVERIFYEXEC(job); Item::List items = job->items(); QCOMPARE(items.count(), 15); int count = 0; for (int i = 0; i < spy.count(); ++i) { Item::List l = spy[i][0].value(); for (int j = 0; j < l.count(); ++j) { QVERIFY(items.count() > count + j); QCOMPARE(items[count + j], l[j]); } count += l.count(); } QCOMPARE(count, items.count()); // check if the fetch response is parsed correctly (note: order is undefined) Item item; foreach (const Item &it, items) { - if (it.remoteId() == QLatin1String("A")) { + if (it.remoteId() == QLatin1Char('A')) { item = it; } } QVERIFY(item.isValid()); QCOMPARE(item.flags().count(), 3); QVERIFY(item.hasFlag("\\SEEN")); QVERIFY(item.hasFlag("\\FLAGGED")); QVERIFY(item.hasFlag("\\DRAFT")); item = Item(); foreach (const Item &it, items) { - if (it.remoteId() == QLatin1String("B")) { + if (it.remoteId() == QLatin1Char('B')) { item = it; } } QVERIFY(item.isValid()); QCOMPARE(item.flags().count(), 1); QVERIFY(item.hasFlag("\\FLAGGED")); item = Item(); foreach (const Item &it, items) { - if (it.remoteId() == QLatin1String("C")) { + if (it.remoteId() == QLatin1Char('C')) { item = it; } } QVERIFY(item.isValid()); QVERIFY(item.flags().isEmpty()); } void ItemFetchTest::testResourceRetrieval() { Item item(1); ItemFetchJob *job = new ItemFetchJob(item, this); job->fetchScope().fetchFullPayload(true); job->fetchScope().fetchAllAttributes(true); job->fetchScope().setCacheOnly(true); AKVERIFYEXEC(job); QCOMPARE(job->items().count(), 1); item = job->items().first(); QCOMPARE(item.id(), 1ll); QVERIFY(!item.remoteId().isEmpty()); QVERIFY(!item.hasPayload()); // not yet in cache QCOMPARE(item.attributes().count(), 1); job = new ItemFetchJob(item, this); job->fetchScope().fetchFullPayload(true); job->fetchScope().fetchAllAttributes(true); job->fetchScope().setCacheOnly(false); AKVERIFYEXEC(job); QCOMPARE(job->items().count(), 1); item = job->items().first(); QCOMPARE(item.id(), 1ll); QVERIFY(!item.remoteId().isEmpty()); QVERIFY(item.hasPayload()); QCOMPARE(item.attributes().count(), 1); } void ItemFetchTest::testIllegalFetch() { // fetch non-existing folder ItemFetchJob *job = new ItemFetchJob(Collection(INT_MAX), this); QVERIFY(!job->exec()); // listing of root job = new ItemFetchJob(Collection::root(), this); QVERIFY(!job->exec()); // fetch a non-existing message job = new ItemFetchJob(Item(INT_MAX), this); QVERIFY(!job->exec()); QVERIFY(job->items().isEmpty()); // fetch message with empty reference job = new ItemFetchJob(Item(), this); QVERIFY(!job->exec()); } void ItemFetchTest::testMultipartFetch_data() { QTest::addColumn("fetchFullPayload"); QTest::addColumn("fetchAllAttrs"); QTest::addColumn("fetchSinglePayload"); QTest::addColumn("fetchSingleAttr"); QTest::newRow("empty") << false << false << false << false; QTest::newRow("full") << true << true << false << false; QTest::newRow("full payload") << true << false << false << false; QTest::newRow("single payload") << false << false << true << false; QTest::newRow("single") << false << false << true << true; QTest::newRow("attr full") << false << true << false << false; QTest::newRow("attr single") << false << false << false << true; QTest::newRow("mixed cross 1") << true << false << false << true; QTest::newRow("mixed cross 2") << false << true << true << false; QTest::newRow("all") << true << true << true << true; QTest::newRow("all payload") << true << false << true << false; QTest::newRow("all attr") << false << true << true << false; } void ItemFetchTest::testMultipartFetch() { QFETCH(bool, fetchFullPayload); QFETCH(bool, fetchAllAttrs); QFETCH(bool, fetchSinglePayload); QFETCH(bool, fetchSingleAttr); CollectionPathResolver *resolver = new CollectionPathResolver(QStringLiteral("res1/foo"), this); AKVERIFYEXEC(resolver); int colId = resolver->collection(); Item item; item.setMimeType(QStringLiteral("application/octet-stream")); item.setPayload("body data"); item.attribute(Item::AddIfMissing)->data = "extra data"; ItemCreateJob *job = new ItemCreateJob(item, Collection(colId), this); AKVERIFYEXEC(job); Item ref = job->item(); ItemFetchJob *fjob = new ItemFetchJob(ref, this); fjob->setCollection(Collection(colId)); if (fetchFullPayload) { fjob->fetchScope().fetchFullPayload(); } if (fetchAllAttrs) { fjob->fetchScope().fetchAttribute(); } if (fetchSinglePayload) { fjob->fetchScope().fetchPayloadPart(Item::FullPayload); } if (fetchSingleAttr) { fjob->fetchScope().fetchAttribute(); } AKVERIFYEXEC(fjob); QCOMPARE(fjob->items().count(), 1); item = fjob->items().first(); if (fetchFullPayload || fetchSinglePayload) { QCOMPARE(item.loadedPayloadParts().count(), 1); QVERIFY(item.hasPayload()); QCOMPARE(item.payload(), QByteArray("body data")); } else { QCOMPARE(item.loadedPayloadParts().count(), 0); QVERIFY(!item.hasPayload()); } if (fetchAllAttrs || fetchSingleAttr) { QCOMPARE(item.attributes().count(), 1); QVERIFY(item.hasAttribute()); QCOMPARE(item.attribute()->data, QByteArray("extra data")); } else { QCOMPARE(item.attributes().count(), 0); } // cleanup ItemDeleteJob *djob = new ItemDeleteJob(ref, this); AKVERIFYEXEC(djob); } void ItemFetchTest::testRidFetch() { Item item; item.setRemoteId(QStringLiteral("A")); Collection col; col.setRemoteId(QStringLiteral("10")); ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"), this); AKVERIFYEXEC(select); ItemFetchJob *job = new ItemFetchJob(item, this); job->setCollection(col); AKVERIFYEXEC(job); QCOMPARE(job->items().count(), 1); item = job->items().first(); QVERIFY(item.isValid()); QCOMPARE(item.remoteId(), QString::fromLatin1("A")); QCOMPARE(item.mimeType(), QString::fromLatin1("application/octet-stream")); } void ItemFetchTest::testAncestorRetrieval() { ItemFetchJob *job = new ItemFetchJob(Item(1), this); job->fetchScope().setAncestorRetrieval(ItemFetchScope::All); AKVERIFYEXEC(job); QCOMPARE(job->items().count(), 1); const Item item = job->items().first(); QVERIFY(item.isValid()); QCOMPARE(item.remoteId(), QString::fromLatin1("A")); QCOMPARE(item.mimeType(), QString::fromLatin1("application/octet-stream")); const Collection c = item.parentCollection(); QCOMPARE(c.remoteId(), QLatin1String("10")); const Collection c2 = c.parentCollection(); QCOMPARE(c2.remoteId(), QLatin1String("6")); const Collection c3 = c2.parentCollection(); QCOMPARE(c3, Collection::root()); } diff --git a/src/core/jobs/collectionfetchjob.cpp b/src/core/jobs/collectionfetchjob.cpp index ba1b47c2c..71def70bb 100644 --- a/src/core/jobs/collectionfetchjob.cpp +++ b/src/core/jobs/collectionfetchjob.cpp @@ -1,426 +1,426 @@ /* Copyright (c) 2006 - 2007 Volker Krause 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 "collectionfetchjob.h" #include "job_p.h" #include "protocolhelper_p.h" #include "collection_p.h" #include "collectionfetchscope.h" #include "collectionutils.h" #include "private/protocol_p.h" #include "akonadicore_debug.h" #include #include #include #include using namespace Akonadi; class Akonadi::CollectionFetchJobPrivate : public JobPrivate { public: CollectionFetchJobPrivate(CollectionFetchJob *parent) : JobPrivate(parent) , mType(CollectionFetchJob::Base) { } void init() { mEmitTimer = new QTimer(q_ptr); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q_ptr->connect(mEmitTimer, SIGNAL(timeout()), q_ptr, SLOT(timeout())); } Q_DECLARE_PUBLIC(CollectionFetchJob) CollectionFetchJob::Type mType; Collection mBase; Collection::List mBaseList; Collection::List mCollections; CollectionFetchScope mScope; Collection::List mPendingCollections; QTimer *mEmitTimer = nullptr; bool mBasePrefetch = false; Collection::List mPrefetchList; void aboutToFinish() override { timeout(); } void timeout() { Q_Q(CollectionFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingCollections.isEmpty()) { if (!q->error() || mScope.ignoreRetrievalErrors()) { Q_EMIT q->collectionsReceived(mPendingCollections); } mPendingCollections.clear(); } } void subJobCollectionReceived(const Akonadi::Collection::List &collections) { mPendingCollections += collections; if (!mEmitTimer->isActive()) { mEmitTimer->start(); } } QString jobDebuggingString() const override { if (mBase.isValid()) { return QStringLiteral("Collection Id %1").arg(mBase.id()); } else if (CollectionUtils::hasValidHierarchicalRID(mBase)) { - //return QLatin1String("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1String(", ")) + QLatin1String(")"); + //return QLatin1String("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1String(", ")) + QLatin1Char(')'); return QStringLiteral("HRID chain"); } else { return QStringLiteral("Collection RemoteId %1").arg(mBase.remoteId()); } } bool jobFailed(KJob *job) { Q_Q(CollectionFetchJob); if (mScope.ignoreRetrievalErrors()) { int error = job->error(); if (error && !q->error()) { q->setError(error); q->setErrorText(job->errorText()); } if (error == Job::ConnectionFailed || error == Job::ProtocolVersionMismatch || error == Job::UserCanceled) { return true; } return false; } else { return job->error(); } } }; CollectionFetchJob::CollectionFetchJob(const Collection &collection, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); d->mBase = collection; d->mType = type; } CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = cols.first(); } else { d->mBaseList = cols; } d->mType = CollectionFetchJob::Base; } CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = cols.first(); } else { d->mBaseList = cols; } d->mType = type; } CollectionFetchJob::CollectionFetchJob(const QList &cols, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = Collection(cols.first()); } else { for (Collection::Id id : cols) { d->mBaseList.append(Collection(id)); } } d->mType = type; } CollectionFetchJob::~CollectionFetchJob() { } Akonadi::Collection::List CollectionFetchJob::collections() const { Q_D(const CollectionFetchJob); return d->mCollections; } void CollectionFetchJob::doStart() { Q_D(CollectionFetchJob); if (!d->mBaseList.isEmpty()) { if (d->mType == Recursive) { // Because doStart starts several subjobs and @p cols could contain descendants of // other elements in the list, if type is Recursive, we could end up with duplicates in the result. // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors, // Iterate over that result removing intersections and then perform the Recursive fetch on // the remainder. d->mBasePrefetch = true; // No need to connect to the collectionsReceived signal here. This job is internal. The // result needs to be filtered through filterDescendants before it is useful. new CollectionFetchJob(d->mBaseList, NonOverlappingRoots, this); } else if (d->mType == NonOverlappingRoots) { for (const Collection &col : qAsConst(d->mBaseList)) { // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated) // result needs to be filtered through filterDescendants before it is useful. CollectionFetchJob *subJob = new CollectionFetchJob(col, Base, this); subJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); } } else { for (const Collection &col : qAsConst(d->mBaseList)) { CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this); connect(subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List))); subJob->setFetchScope(fetchScope()); } } return; } if (!d->mBase.isValid() && d->mBase.remoteId().isEmpty()) { setError(Unknown); setErrorText(i18n("Invalid collection given.")); emitResult(); return; } const auto cmd = Protocol::FetchCollectionsCommandPtr::create(ProtocolHelper::entityToScope(d->mBase)); switch (d->mType) { case Base: cmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection); break; case Akonadi::CollectionFetchJob::FirstLevel: cmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection); break; case Akonadi::CollectionFetchJob::Recursive: cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections); break; default: Q_ASSERT(false); } cmd->setResource(d->mScope.resource()); cmd->setMimeTypes(d->mScope.contentMimeTypes()); switch (d->mScope.listFilter()) { case CollectionFetchScope::Display: cmd->setDisplayPref(true); break; case CollectionFetchScope::Sync: cmd->setSyncPref(true); break; case CollectionFetchScope::Index: cmd->setIndexPref(true); break; case CollectionFetchScope::Enabled: cmd->setEnabled(true); break; case CollectionFetchScope::NoFilter: break; default: Q_ASSERT(false); } cmd->setFetchStats(d->mScope.includeStatistics()); switch (d->mScope.ancestorRetrieval()) { case CollectionFetchScope::None: cmd->setAncestorsDepth(Protocol::Ancestor::NoAncestor); break; case CollectionFetchScope::Parent: cmd->setAncestorsDepth(Protocol::Ancestor::ParentAncestor); break; case CollectionFetchScope::All: cmd->setAncestorsDepth(Protocol::Ancestor::AllAncestors); break; } if (d->mScope.ancestorRetrieval() != CollectionFetchScope::None) { cmd->setAncestorsAttributes(d->mScope.ancestorFetchScope().attributes()); } d->sendCommand(cmd); } bool CollectionFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(CollectionFetchJob); if (d->mBasePrefetch || d->mType == NonOverlappingRoots) { return false; } if (!response->isResponse() || response->type() != Protocol::Command::FetchCollections) { return Job::doHandleResponse(tag, response); } const auto &resp = Protocol::cmdCast(response); // Invalid response (no ID) means this was the last response if (resp.id() == -1) { return true; } Collection collection = ProtocolHelper::parseCollection(resp, true); if (!collection.isValid()) { return false; } collection.d_ptr->resetChangeLog(); d->mCollections.append(collection); d->mPendingCollections.append(collection); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } return false; } static Collection::List filterDescendants(const Collection::List &list) { Collection::List result; QVector > ids; ids.reserve(list.count()); for (const Collection &collection : list) { QList ancestors; Collection parent = collection.parentCollection(); ancestors << parent.id(); if (parent != Collection::root()) { while (parent.parentCollection() != Collection::root()) { parent = parent.parentCollection(); QList::iterator i = std::lower_bound(ancestors.begin(), ancestors.end(), parent.id()); ancestors.insert(i, parent.id()); } } ids << ancestors; } QSet excludeList; for (const Collection &collection : list) { int i = 0; for (const QList &ancestors : qAsConst(ids)) { if (qBinaryFind(ancestors, collection.id()) != ancestors.end()) { excludeList.insert(list.at(i).id()); } ++i; } } for (const Collection &collection : list) { if (!excludeList.contains(collection.id())) { result.append(collection); } } return result; } void CollectionFetchJob::slotResult(KJob *job) { Q_D(CollectionFetchJob); CollectionFetchJob *list = qobject_cast(job); Q_ASSERT(job); if (d->mType == NonOverlappingRoots) { d->mPrefetchList += list->collections(); } else if (!d->mBasePrefetch) { d->mCollections += list->collections(); } if (d_ptr->mCurrentSubJob == job && !d->jobFailed(job)) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error during CollectionFetchJob: " << job->errorString(); } d_ptr->mCurrentSubJob = nullptr; removeSubjob(job); QTimer::singleShot(0, this, [d]() { d->startNext(); }); } else { Job::slotResult(job); } if (d->mBasePrefetch) { d->mBasePrefetch = false; const Collection::List roots = list->collections(); Q_ASSERT(!hasSubjobs()); if (!job->error()) { for (const Collection &col : roots) { CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this); connect(subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List))); subJob->setFetchScope(fetchScope()); } } // No result yet. } else if (d->mType == NonOverlappingRoots) { if (!d->jobFailed(job) && !hasSubjobs()) { const Collection::List result = filterDescendants(d->mPrefetchList); d->mPendingCollections += result; d->mCollections = result; d->delayedEmitResult(); } } else { if (!d->jobFailed(job) && !hasSubjobs()) { d->delayedEmitResult(); } } } void CollectionFetchJob::setFetchScope(const CollectionFetchScope &scope) { Q_D(CollectionFetchJob); d->mScope = scope; } CollectionFetchScope &CollectionFetchJob::fetchScope() { Q_D(CollectionFetchJob); return d->mScope; } #include "moc_collectionfetchjob.cpp"