diff --git a/autotests/libs/protocolhelpertest.cpp b/autotests/libs/protocolhelpertest.cpp index 1e4b98f99..93a04f432 100644 --- a/autotests/libs/protocolhelpertest.cpp +++ b/autotests/libs/protocolhelpertest.cpp @@ -1,332 +1,332 @@ /* Copyright (c) 2009 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 "test_utils.h" #include "protocolhelper.cpp" using namespace Akonadi; Q_DECLARE_METATYPE(Scope) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(Protocol::FetchScope) +Q_DECLARE_METATYPE(Protocol::ItemFetchScope) class ProtocolHelperTest : public QObject { Q_OBJECT private Q_SLOTS: void testItemSetToByteArray_data() { QTest::addColumn("items"); QTest::addColumn("result"); QTest::addColumn("shouldThrow"); Item u1; u1.setId(1); Item u2; u2.setId(2); Item u3; u3.setId(3); Item r1; r1.setRemoteId(QStringLiteral("A")); Item r2; r2.setRemoteId(QStringLiteral("B")); Item h1; h1.setRemoteId(QStringLiteral("H1")); h1.setParentCollection(Collection::root()); Item h2; h2.setRemoteId(QStringLiteral("H2a")); h2.parentCollection().setRemoteId(QStringLiteral("H2b")); h2.parentCollection().setParentCollection(Collection::root()); Item h3; h3.setRemoteId(QStringLiteral("H3a")); h3.parentCollection().setRemoteId(QStringLiteral("H3b")); QTest::newRow("empty") << Item::List() << Scope() << true; QTest::newRow("single uid") << (Item::List() << u1) << Scope(1) << false; QTest::newRow("multi uid") << (Item::List() << u1 << u3) << Scope(QVector { 1, 3 }) << false; QTest::newRow("block uid") << (Item::List() << u1 << u2 << u3) << Scope(ImapInterval(1, 3)) << false; QTest::newRow("single rid") << (Item::List() << r1) << Scope(Scope::Rid, { QStringLiteral("A") }) << false; QTest::newRow("multi rid") << (Item::List() << r1 << r2) << Scope(Scope::Rid, { QStringLiteral("A"), QStringLiteral("B") }) << false; QTest::newRow("invalid") << (Item::List() << Item()) << Scope() << true; QTest::newRow("mixed") << (Item::List() << u1 << r1) << Scope() << true; QTest::newRow("single hrid") << (Item::List() << h1) << Scope({ Scope::HRID(-1, QStringLiteral("H1")), Scope::HRID(0) }) << false; QTest::newRow("single hrid 2") << (Item::List() << h2) << Scope({ Scope::HRID(-1, QStringLiteral("H2a")), Scope::HRID(-2, QStringLiteral("H2b")), Scope::HRID(0) }) << false; QTest::newRow("mixed hrid/rid") << (Item::List() << h1 << r1) << Scope(Scope::Rid, { QStringLiteral("H1"), QStringLiteral("A") }) << false; QTest::newRow("unterminated hrid") << (Item::List() << h3) << Scope(Scope::Rid, { QStringLiteral("H3a") }) << false; } void testItemSetToByteArray() { QFETCH(Item::List, items); QFETCH(Scope, result); QFETCH(bool, shouldThrow); bool didThrow = false; try { const Scope scope = ProtocolHelper::entitySetToScope(items); QCOMPARE(scope, result); } catch (const std::exception &e) { qDebug() << e.what(); didThrow = true; } QCOMPARE(didThrow, shouldThrow); } void testAncestorParsing_data() { QTest::addColumn>("input"); QTest::addColumn("parent"); QTest::newRow("top-level") << QVector { Protocol::Ancestor(0) } << Collection::root(); Protocol::Ancestor a1(42); a1.setRemoteId(QStringLiteral("net")); Collection c1; c1.setRemoteId(QStringLiteral("net")); c1.setId(42); c1.setParentCollection(Collection::root()); QTest::newRow("till's obscure folder") << QVector { a1, Protocol::Ancestor(0) } << c1; } void testAncestorParsing() { QFETCH(QVector, input); QFETCH(Collection, parent); Item i; ProtocolHelper::parseAncestors(input, &i); QCOMPARE(i.parentCollection().id(), parent.id()); QCOMPARE(i.parentCollection().remoteId(), parent.remoteId()); } void testCollectionParsing_data() { QTest::addColumn("input"); QTest::addColumn("collection"); Collection c1; c1.setId(2); c1.setRemoteId(QStringLiteral("r2")); c1.parentCollection().setId(1); c1.setName(QStringLiteral("n2")); { Protocol::FetchCollectionsResponse resp(2); resp.setParentId(1); resp.setRemoteId(QStringLiteral("r2")); resp.setName(QStringLiteral("n2")); QTest::newRow("no ancestors") << resp << c1; } { Protocol::FetchCollectionsResponse resp(3); resp.setParentId(2); resp.setRemoteId(QStringLiteral("r3")); resp.setAncestors({ Protocol::Ancestor(2, QStringLiteral("r2")), Protocol::Ancestor(1, QStringLiteral("r1")), Protocol::Ancestor(0) }); Collection c2; c2.setId(3); c2.setRemoteId(QStringLiteral("r3")); c2.parentCollection().setId(2); c2.parentCollection().setRemoteId(QStringLiteral("r2")); c2.parentCollection().parentCollection().setId(1); c2.parentCollection().parentCollection().setRemoteId(QStringLiteral("r1")); c2.parentCollection().parentCollection().setParentCollection(Collection::root()); QTest::newRow("ancestors") << resp << c2; } } void testCollectionParsing() { QFETCH(Protocol::FetchCollectionsResponse, input); QFETCH(Collection, collection); Collection parsedCollection = ProtocolHelper::parseCollection(input); QCOMPARE(parsedCollection.name(), collection.name()); while (collection.isValid() || parsedCollection.isValid()) { QCOMPARE(parsedCollection.id(), collection.id()); QCOMPARE(parsedCollection.remoteId(), collection.remoteId()); const Collection p1(parsedCollection.parentCollection()); const Collection p2(collection.parentCollection()); parsedCollection = p1; collection = p2; qDebug() << p1.isValid() << p2.isValid(); } } void testParentCollectionAfterCollectionParsing() { Protocol::FetchCollectionsResponse resp(111); resp.setParentId(222); resp.setRemoteId(QStringLiteral("A")); resp.setAncestors({ Protocol::Ancestor(222), Protocol::Ancestor(333), Protocol::Ancestor(0) }); Collection parsedCollection = ProtocolHelper::parseCollection(resp); QList ids; ids << 111 << 222 << 333 << 0; int i = 0; Collection col = parsedCollection; while (col.isValid()) { QCOMPARE(col.id(), ids[i++]); col = col.parentCollection(); } QCOMPARE(i, 4); } void testHRidToScope_data() { QTest::addColumn("collection"); QTest::addColumn("result"); QTest::newRow("empty") << Collection() << Scope(); { Scope scope; scope.setHRidChain({ Scope::HRID(0) }); QTest::newRow("root") << Collection::root() << scope; } Collection c; c.setId(1); c.setParentCollection(Collection::root()); c.setRemoteId(QStringLiteral("r1")); { Scope scope; scope.setHRidChain({ Scope::HRID(1, QStringLiteral("r1")), Scope::HRID(0) }); QTest::newRow("one level") << c << scope; } { Collection c2; c2.setId(2); c2.setParentCollection(c); c2.setRemoteId(QStringLiteral("r2")); Scope scope; scope.setHRidChain({ Scope::HRID(2, QStringLiteral("r2")), Scope::HRID(1, QStringLiteral("r1")), Scope::HRID(0) }); QTest::newRow("two level ok") << c2 << scope; } } void testHRidToScope() { QFETCH(Collection, collection); QFETCH(Scope, result); QCOMPARE(ProtocolHelper::hierarchicalRidToScope(collection), result); } void testItemFetchScopeToProtocol_data() { QTest::addColumn("scope"); - QTest::addColumn("result"); + QTest::addColumn("result"); { - Protocol::FetchScope fs; - fs.setFetch(Protocol::FetchScope::Flags | - Protocol::FetchScope::Size | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::MTime); + Protocol::ItemFetchScope fs; + fs.setFetch(Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::MTime); QTest::newRow("empty") << ItemFetchScope() << fs; } { ItemFetchScope scope; scope.fetchAllAttributes(); scope.fetchFullPayload(); scope.setAncestorRetrieval(Akonadi::ItemFetchScope::All); scope.setIgnoreRetrievalErrors(true); - Protocol::FetchScope fs; - fs.setFetch(Protocol::FetchScope::FullPayload | - Protocol::FetchScope::AllAttributes | - Protocol::FetchScope::Flags | - Protocol::FetchScope::Size | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::MTime | - Protocol::FetchScope::IgnoreErrors); - fs.setAncestorDepth(Protocol::FetchScope::AllAncestors); + Protocol::ItemFetchScope fs; + fs.setFetch(Protocol::ItemFetchScope::FullPayload | + Protocol::ItemFetchScope::AllAttributes | + Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::MTime | + Protocol::ItemFetchScope::IgnoreErrors); + fs.setAncestorDepth(Protocol::ItemFetchScope::AllAncestors); QTest::newRow("full") << scope << fs; } { ItemFetchScope scope; scope.setFetchModificationTime(false); scope.setFetchRemoteIdentification(false); - Protocol::FetchScope fs; - fs.setFetch(Protocol::FetchScope::Flags | - Protocol::FetchScope::Size); + Protocol::ItemFetchScope fs; + fs.setFetch(Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size); QTest::newRow("minimal") << scope << fs; } } void testItemFetchScopeToProtocol() { QFETCH(ItemFetchScope, scope); - QFETCH(Protocol::FetchScope, result); + QFETCH(Protocol::ItemFetchScope, result); QCOMPARE(ProtocolHelper::itemFetchScopeToProtocol(scope), result); } void testTagParsing_data() { QTest::addColumn("input"); QTest::addColumn("expected"); QTest::newRow("invalid") << Protocol::FetchTagsResponse(-1) << Tag(); Protocol::FetchTagsResponse response(15); response.setGid("TAG13GID"); response.setRemoteId("TAG13RID"); response.setParentId(-1); response.setType("PLAIN"); response.setAttributes({ { "TAGAttribute", "MyAttribute" } }); Tag tag(15); tag.setGid("TAG13GID"); tag.setRemoteId("TAG13RID"); tag.setType("PLAIN"); auto attr = AttributeFactory::createAttribute("TAGAttribute"); attr->deserialize("MyAttribute"); tag.addAttribute(attr); QTest::newRow("valid with invalid parent") << response << tag; response.setParentId(15); tag.setParent(Tag(15)); QTest::newRow("valid with valid parent") << response << tag; } void testTagParsing() { QFETCH(Protocol::FetchTagsResponse, input); QFETCH(Tag, expected); const Tag tag = ProtocolHelper::parseTagFetchResult(input); QCOMPARE(tag.id(), expected.id()); QCOMPARE(tag.gid(), expected.gid()); QCOMPARE(tag.remoteId(), expected.remoteId()); QCOMPARE(tag.type(), expected.type()); QCOMPARE(tag.parent(), expected.parent()); QCOMPARE(tag.attributes().size(), expected.attributes().size()); for (int i = 0; i < tag.attributes().size(); ++i) { Attribute *attr = tag.attributes().at(i); Attribute *expectedAttr = expected.attributes().at(i); QCOMPARE(attr->type(), expectedAttr->type()); QCOMPARE(attr->serialized(), expectedAttr->serialized()); } } }; QTEST_MAIN(ProtocolHelperTest) #include "protocolhelpertest.moc" diff --git a/autotests/private/protocoltest.cpp b/autotests/private/protocoltest.cpp index 9079eafd5..5cc7498e5 100644 --- a/autotests/private/protocoltest.cpp +++ b/autotests/private/protocoltest.cpp @@ -1,707 +1,707 @@ /* * Copyright (C) 2015 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "protocoltest.h" #include "private/scope_p.h" #include using namespace Akonadi; using namespace Akonadi::Protocol; void ProtocolTest::testProtocolVersion() { // So, this test started failing because you bumped protocol version in // protocol.cpp. That means that you most probably changed something // in the protocol. Before you just bump the number here to make the test // pass, please please pretty please make sure to extend the respective // test somewhere below to cover the change you've made. Protocol is the // most critical part of Akonadi and we can't afford not having it tested // properly. // // If it wasn't you who broke it, please go find that person who was too // lazy to extend the test case and beat them with a stick. -- Dan - QCOMPARE(Akonadi::Protocol::version(), 58); + QCOMPARE(Akonadi::Protocol::version(), 59); } void ProtocolTest::testFactory_data() { QTest::addColumn("type"); QTest::addColumn("response"); QTest::addColumn("success"); QTest::newRow("invalid cmd") << Command::Invalid << false << false; QTest::newRow("invalid resp") << Command::Invalid << true << false; QTest::newRow("hello cmd") << Command::Hello << false << false; QTest::newRow("hello resp") << Command::Hello << true << true; QTest::newRow("login cmd") << Command::Login << false << true; QTest::newRow("login resp") << Command::Login << true << true; QTest::newRow("logout cmd") << Command::Logout << false << true; QTest::newRow("logout resp") << Command::Logout << true << true; QTest::newRow("transaction cmd") << Command::Transaction << false << true; QTest::newRow("transaction resp") << Command::Transaction << true << true; QTest::newRow("createItem cmd") << Command::CreateItem << false << true; QTest::newRow("createItem resp") << Command::CreateItem << true << true; QTest::newRow("copyItems cmd") << Command::CopyItems << false << true; QTest::newRow("copyItems resp") << Command::CopyItems << true << true; QTest::newRow("deleteItems cmd") << Command::DeleteItems << false << true; QTest::newRow("deleteItems resp") << Command::DeleteItems << true << true; QTest::newRow("fetchItems cmd") << Command::FetchItems << false << true; QTest::newRow("fetchItems resp") << Command::FetchItems << true << true; QTest::newRow("linkItems cmd") << Command::LinkItems << false << true; QTest::newRow("linkItems resp") << Command::LinkItems << true << true; QTest::newRow("modifyItems cmd") << Command::ModifyItems << false << true; QTest::newRow("modifyItems resp") << Command::ModifyItems << true << true; QTest::newRow("moveItems cmd") << Command::MoveItems << false << true; QTest::newRow("moveItems resp") << Command::MoveItems << true << true; QTest::newRow("createCollection cmd") << Command::CreateCollection << false << true; QTest::newRow("createCollection resp") << Command::CreateCollection << true << true; QTest::newRow("copyCollection cmd") << Command::CopyCollection << false << true; QTest::newRow("copyCollection resp") << Command::CopyCollection << true << true; QTest::newRow("deleteCollection cmd") << Command::DeleteCollection << false << true; QTest::newRow("deleteCollection resp") << Command::DeleteCollection << true << true; QTest::newRow("fetchCollections cmd") << Command::FetchCollections << false << true; QTest::newRow("fetchCollections resp") << Command::FetchCollections << true << true; QTest::newRow("fetchCollectionStats cmd") << Command::FetchCollectionStats << false << true; QTest::newRow("fetchCollectionStats resp") << Command::FetchCollectionStats << false << true; QTest::newRow("modifyCollection cmd") << Command::ModifyCollection << false << true; QTest::newRow("modifyCollection resp") << Command::ModifyCollection << true << true; QTest::newRow("moveCollection cmd") << Command::MoveCollection << false << true; QTest::newRow("moveCollection resp") << Command::MoveCollection << true << true; QTest::newRow("search cmd") << Command::Search << false << true; QTest::newRow("search resp") << Command::Search << true << true; QTest::newRow("searchResult cmd") << Command::SearchResult << false << true; QTest::newRow("searchResult resp") << Command::SearchResult << true << true; QTest::newRow("storeSearch cmd") << Command::StoreSearch << false << true; QTest::newRow("storeSearch resp") << Command::StoreSearch << true << true; QTest::newRow("createTag cmd") << Command::CreateTag << false << true; QTest::newRow("createTag resp") << Command::CreateTag << true << true; QTest::newRow("deleteTag cmd") << Command::DeleteTag << false << true; QTest::newRow("deleteTag resp") << Command::DeleteTag << true << true; QTest::newRow("fetchTags cmd") << Command::FetchTags << false << true; QTest::newRow("fetchTags resp") << Command::FetchTags << true << true; QTest::newRow("modifyTag cmd") << Command::ModifyTag << false << true; QTest::newRow("modifyTag resp") << Command::ModifyTag << true << true; QTest::newRow("fetchRelations cmd") << Command::FetchRelations << false << true; QTest::newRow("fetchRelations resp") << Command::FetchRelations << true << true; QTest::newRow("modifyRelation cmd") << Command::ModifyRelation << false << true; QTest::newRow("modifyRelation resp") << Command::ModifyRelation << true << true; QTest::newRow("removeRelations cmd") << Command::RemoveRelations << false << true; QTest::newRow("removeRelations resp") << Command::RemoveRelations << true << true; QTest::newRow("selectResource cmd") << Command::SelectResource << false << true; QTest::newRow("selectResource resp") << Command::SelectResource << true << true; QTest::newRow("streamPayload cmd") << Command::StreamPayload << false << true; QTest::newRow("streamPayload resp") << Command::StreamPayload << true << true; QTest::newRow("itemChangeNotification cmd") << Command::ItemChangeNotification << false << true; QTest::newRow("itemChangeNotification resp") << Command::ItemChangeNotification << true << false; QTest::newRow("collectionChangeNotification cmd") << Command::CollectionChangeNotification << false << true; QTest::newRow("collectionChangeNotification resp") << Command::CollectionChangeNotification << true << false; QTest::newRow("tagChangeNotification cmd") << Command::TagChangeNotification << false << true; QTest::newRow("tagChangENotification resp") << Command::TagChangeNotification << true << false; QTest::newRow("relationChangeNotification cmd") << Command::RelationChangeNotification << false << true; QTest::newRow("relationChangeNotification resp") << Command::RelationChangeNotification << true << false; QTest::newRow("_responseBit cmd") << Command::_ResponseBit << false << false; QTest::newRow("_responseBit resp") << Command::_ResponseBit << true << false; } void ProtocolTest::testFactory() { QFETCH(Command::Type, type); QFETCH(bool, response); QFETCH(bool, success); CommandPtr result; if (response) { result = Factory::response(type); } else { result = Factory::command(type); } QCOMPARE(result->isValid(), success); QCOMPARE(result->isResponse(), response); if (success) { QCOMPARE(result->type(), type); } } void ProtocolTest::testCommand() { // There is no way to construct a valid Command directly auto cmd = CommandPtr::create(); QCOMPARE(cmd->type(), Command::Invalid); QVERIFY(!cmd->isValid()); QVERIFY(!cmd->isResponse()); CommandPtr cmdTest = serializeAndDeserialize(cmd); QCOMPARE(cmdTest->type(), Command::Invalid); QVERIFY(!cmd->isValid()); QVERIFY(!cmd->isResponse()); } void ProtocolTest::testResponse_data() { QTest::addColumn("isError"); QTest::addColumn("errorCode"); QTest::addColumn("errorString"); QTest::newRow("no error") << false << 0 << QString(); QTest::newRow("error") << true << 10 << QStringLiteral("Oh noes, there was an error!"); } void ProtocolTest::testResponse() { QFETCH(bool, isError); QFETCH(int, errorCode); QFETCH(QString, errorString); Response response; if (isError) { response.setError(errorCode, errorString); } const auto res = serializeAndDeserialize(ResponsePtr::create(response)); QCOMPARE(res->type(), Command::Invalid); QVERIFY(!res->isValid()); QVERIFY(res->isResponse()); QCOMPARE(res->isError(), isError); QCOMPARE(res->errorCode(), errorCode); QCOMPARE(res->errorMessage(), errorString); QVERIFY(*res == response); const bool notEquals = (*res != response); QVERIFY(!notEquals); } void ProtocolTest::testAncestor() { Ancestor in; in.setId(42); in.setRemoteId(QStringLiteral("remoteId")); in.setName(QStringLiteral("Col 42")); in.setAttributes({{ "Attr1", "Val 1" }, { "Attr2", "Röndom útéef řetězec" }}); const Ancestor out = serializeAndDeserialize(in); QCOMPARE(out.id(), 42); QCOMPARE(out.remoteId(), QStringLiteral("remoteId")); QCOMPARE(out.name(), QStringLiteral("Col 42")); QCOMPARE(out.attributes(), Attributes({{ "Attr1", "Val 1" }, { "Attr2", "Röndom útéef řetězec" }})); QVERIFY(out == in); const bool notEquals = (out != in); QVERIFY(!notEquals); } void ProtocolTest::testFetchScope_data() { QTest::addColumn("fullPayload"); QTest::addColumn>("requestedParts"); QTest::addColumn>("expectedParts"); QTest::addColumn>("expectedPayloads"); QTest::newRow("full payload (via flag") << true << QVector{ "PLD:HEAD", "ATR:MYATR" } << QVector{ "PLD:HEAD", "ATR:MYATR", "PLD:RFC822" } << QVector{ "PLD:HEAD", "PLD:RFC822" }; QTest::newRow("full payload (via part name") << false << QVector{ "PLD:HEAD", "ATR:MYATR", "PLD:RFC822" } << QVector{ "PLD:HEAD", "ATR:MYATR", "PLD:RFC822" } << QVector{ "PLD:HEAD", "PLD:RFC822" }; QTest::newRow("full payload (via both") << true << QVector{ "PLD:HEAD", "ATR:MYATR", "PLD:RFC822" } << QVector{ "PLD:HEAD", "ATR:MYATR", "PLD:RFC822" } << QVector{ "PLD:HEAD", "PLD:RFC822" }; QTest::newRow("without full payload") << false << QVector{ "PLD:HEAD", "ATR:MYATR" } << QVector{ "PLD:HEAD", "ATR:MYATR" } << QVector{ "PLD:HEAD" }; } void ProtocolTest::testFetchScope() { QFETCH(bool, fullPayload); QFETCH(QVector, requestedParts); QFETCH(QVector, expectedParts); QFETCH(QVector, expectedPayloads); - FetchScope in; - for (int i = FetchScope::CacheOnly; i <= FetchScope::VirtReferences; i = i << 1) { - QVERIFY(!in.fetch(static_cast(i))); + ItemFetchScope in; + for (int i = ItemFetchScope::CacheOnly; i <= ItemFetchScope::VirtReferences; i = i << 1) { + QVERIFY(!in.fetch(static_cast(i))); } - QVERIFY(in.fetch(FetchScope::None)); + QVERIFY(in.fetch(ItemFetchScope::None)); in.setRequestedParts(requestedParts); in.setChangedSince(QDateTime(QDate(2015, 8, 10), QTime(23, 52, 20), Qt::UTC)); in.setTagFetchScope({ "TAGID" }); - in.setAncestorDepth(FetchScope::AllAncestors); - in.setFetch(FetchScope::CacheOnly); - in.setFetch(FetchScope::CheckCachedPayloadPartsOnly); - in.setFetch(FetchScope::FullPayload, fullPayload); - in.setFetch(FetchScope::AllAttributes); - in.setFetch(FetchScope::Size); - in.setFetch(FetchScope::MTime); - in.setFetch(FetchScope::RemoteRevision); - in.setFetch(FetchScope::IgnoreErrors); - in.setFetch(FetchScope::Flags); - in.setFetch(FetchScope::RemoteID); - in.setFetch(FetchScope::GID); - in.setFetch(FetchScope::Tags); - in.setFetch(FetchScope::Relations); - in.setFetch(FetchScope::VirtReferences); - - const FetchScope out = serializeAndDeserialize(in); + in.setAncestorDepth(ItemFetchScope::AllAncestors); + in.setFetch(ItemFetchScope::CacheOnly); + in.setFetch(ItemFetchScope::CheckCachedPayloadPartsOnly); + in.setFetch(ItemFetchScope::FullPayload, fullPayload); + in.setFetch(ItemFetchScope::AllAttributes); + in.setFetch(ItemFetchScope::Size); + in.setFetch(ItemFetchScope::MTime); + in.setFetch(ItemFetchScope::RemoteRevision); + in.setFetch(ItemFetchScope::IgnoreErrors); + in.setFetch(ItemFetchScope::Flags); + in.setFetch(ItemFetchScope::RemoteID); + in.setFetch(ItemFetchScope::GID); + in.setFetch(ItemFetchScope::Tags); + in.setFetch(ItemFetchScope::Relations); + in.setFetch(ItemFetchScope::VirtReferences); + + const ItemFetchScope out = serializeAndDeserialize(in); QCOMPARE(out.requestedParts(), expectedParts); QCOMPARE(out.requestedPayloads(), expectedPayloads); QCOMPARE(out.changedSince(), QDateTime(QDate(2015, 8, 10), QTime(23, 52, 20), Qt::UTC)); QCOMPARE(out.tagFetchScope(), QSet{ "TAGID" }); - QCOMPARE(out.ancestorDepth(), FetchScope::AllAncestors); - QCOMPARE(out.fetch(FetchScope::None), false); + QCOMPARE(out.ancestorDepth(), ItemFetchScope::AllAncestors); + QCOMPARE(out.fetch(ItemFetchScope::None), false); QCOMPARE(out.cacheOnly(), true); QCOMPARE(out.checkCachedPayloadPartsOnly(), true); QCOMPARE(out.fullPayload(), fullPayload); QCOMPARE(out.allAttributes(), true); QCOMPARE(out.fetchSize(), true); QCOMPARE(out.fetchMTime(), true); QCOMPARE(out.fetchRemoteRevision(), true); QCOMPARE(out.ignoreErrors(), true); QCOMPARE(out.fetchFlags(), true); QCOMPARE(out.fetchRemoteId(), true); QCOMPARE(out.fetchGID(), true); QCOMPARE(out.fetchRelations(), true); QCOMPARE(out.fetchVirtualReferences(), true); } void ProtocolTest::testScopeContext_data() { QTest::addColumn("colId"); QTest::addColumn("colRid"); QTest::addColumn("tagId"); QTest::addColumn("tagRid"); QTest::newRow("collection - id") << 42ll << QString() << 0ll << QString(); QTest::newRow("collection - rid") << 0ll << QStringLiteral("rid") << 0ll << QString(); QTest::newRow("collection - both") << 42ll << QStringLiteral("rid") << 0ll << QString(); QTest::newRow("tag - id") << 0ll << QString() << 42ll << QString(); QTest::newRow("tag - rid") << 0ll << QString() << 0ll << QStringLiteral("rid"); QTest::newRow("tag - both") << 0ll << QString() << 42ll << QStringLiteral("rid"); QTest::newRow("both - id") << 42ll << QString() << 10ll << QString(); QTest::newRow("both - rid") << 0ll << QStringLiteral("colRid") << 0ll << QStringLiteral("tagRid"); QTest::newRow("col - id, tag - rid") << 42ll << QString() << 0ll << QStringLiteral("tagRid"); QTest::newRow("col - rid, tag - id") << 0ll << QStringLiteral("colRid") << 42ll << QString(); QTest::newRow("both - both") << 42ll << QStringLiteral("colRid") << 10ll << QStringLiteral("tagRid"); } void ProtocolTest::testScopeContext() { QFETCH(qint64, colId); QFETCH(QString, colRid); QFETCH(qint64, tagId); QFETCH(QString, tagRid); const bool hasColId = colId > 0; const bool hasColRid = !colRid.isEmpty(); const bool hasTagId = tagId > 0; const bool hasTagRid = !tagRid.isEmpty(); ScopeContext in; QVERIFY(in.isEmpty()); if (hasColId) { in.setContext(ScopeContext::Collection, colId); } if (hasColRid) { in.setContext(ScopeContext::Collection, colRid); } if (hasTagId) { in.setContext(ScopeContext::Tag, tagId); } if (hasTagRid) { in.setContext(ScopeContext::Tag, tagRid); } QCOMPARE(in.hasContextId(ScopeContext::Any), false); QCOMPARE(in.hasContextRID(ScopeContext::Any), false); QEXPECT_FAIL("collection - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(in.hasContextId(ScopeContext::Collection), hasColId); QCOMPARE(in.hasContextRID(ScopeContext::Collection), hasColRid); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("tag - both", "Cannot set both ID and RID context", Continue); QCOMPARE(in.hasContextId(ScopeContext::Tag), hasTagId); QCOMPARE(in.hasContextRID(ScopeContext::Tag), hasTagRid); QVERIFY(!in.isEmpty()); ScopeContext out = serializeAndDeserialize(in); QCOMPARE(out.isEmpty(), false); QEXPECT_FAIL("collection - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.hasContextId(ScopeContext::Collection), hasColId); QEXPECT_FAIL("collection - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.contextId(ScopeContext::Collection), colId); QCOMPARE(out.hasContextRID(ScopeContext::Collection), hasColRid); QCOMPARE(out.contextRID(ScopeContext::Collection), colRid); QEXPECT_FAIL("tag - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.hasContextId(ScopeContext::Tag), hasTagId); QEXPECT_FAIL("tag - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.contextId(ScopeContext::Tag), tagId); QCOMPARE(out.hasContextRID(ScopeContext::Tag), hasTagRid); QCOMPARE(out.contextRID(ScopeContext::Tag), tagRid); QCOMPARE(out, in); const bool notEquals = (out != in); QVERIFY(!notEquals); // Clearing "any" should not do anything out.clearContext(ScopeContext::Any); QEXPECT_FAIL("collection - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.hasContextId(ScopeContext::Collection), hasColId); QEXPECT_FAIL("collection - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.contextId(ScopeContext::Collection), colId); QCOMPARE(out.hasContextRID(ScopeContext::Collection), hasColRid); QCOMPARE(out.contextRID(ScopeContext::Collection), colRid); QEXPECT_FAIL("tag - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.hasContextId(ScopeContext::Tag), hasTagId); QEXPECT_FAIL("tag - both", "Cannot set both ID and RID context", Continue); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(out.contextId(ScopeContext::Tag), tagId); QCOMPARE(out.hasContextRID(ScopeContext::Tag), hasTagRid); QCOMPARE(out.contextRID(ScopeContext::Tag), tagRid); if (hasColId || hasColRid) { ScopeContext clear = out; clear.clearContext(ScopeContext::Collection); QCOMPARE(clear.hasContextId(ScopeContext::Collection), false); QCOMPARE(clear.hasContextRID(ScopeContext::Collection), false); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(clear.hasContextId(ScopeContext::Tag), hasTagId); QCOMPARE(clear.hasContextRID(ScopeContext::Tag), hasTagRid); } if (hasTagId || hasTagRid) { ScopeContext clear = out; clear.clearContext(ScopeContext::Tag); QEXPECT_FAIL("both - both", "Cannot set both ID and RID context", Continue); QCOMPARE(clear.hasContextId(ScopeContext::Collection), hasColId); QCOMPARE(clear.hasContextRID(ScopeContext::Collection), hasColRid); QCOMPARE(clear.hasContextId(ScopeContext::Tag), false); QCOMPARE(clear.hasContextRID(ScopeContext::Tag), false); } out.clearContext(ScopeContext::Collection); out.clearContext(ScopeContext::Tag); QVERIFY(out.isEmpty()); } void ProtocolTest::testPartMetaData() { PartMetaData in; in.setName("PLD:HEAD"); in.setSize(42); in.setVersion(1); in.setStorageType(PartMetaData::External); const PartMetaData out = serializeAndDeserialize(in); QCOMPARE(out.name(), QByteArray("PLD:HEAD")); QCOMPARE(out.size(), 42); QCOMPARE(out.version(), 1); QCOMPARE(out.storageType(), PartMetaData::External); QCOMPARE(out, in); const bool notEquals = (in != out); QVERIFY(!notEquals); } void ProtocolTest::testCachePolicy() { CachePolicy in; in.setInherit(true); in.setCheckInterval(42); in.setCacheTimeout(10); in.setSyncOnDemand(true); in.setLocalParts({ QStringLiteral("PLD:HEAD"), QStringLiteral("PLD:ENVELOPE") }); const CachePolicy out = serializeAndDeserialize(in); QCOMPARE(out.inherit(), true); QCOMPARE(out.checkInterval(), 42); QCOMPARE(out.cacheTimeout(), 10); QCOMPARE(out.syncOnDemand(), true); QCOMPARE(out.localParts(), QStringList() << QStringLiteral("PLD:HEAD") << QStringLiteral("PLD:ENVELOPE")); QCOMPARE(out, in); const bool notEquals = (out != in); QVERIFY(!notEquals); } void ProtocolTest::testHelloResponse() { HelloResponse in; QVERIFY(in.isResponse()); QVERIFY(in.isValid()); QVERIFY(!in.isError()); in.setServerName(QStringLiteral("AkonadiTest")); in.setMessage(QStringLiteral("Oh, hello there!")); in.setProtocolVersion(42); in.setError(10, QStringLiteral("Ooops")); const auto out = serializeAndDeserialize(HelloResponsePtr::create(in)); QVERIFY(out->isValid()); QVERIFY(out->isResponse()); QVERIFY(out->isError()); QCOMPARE(out->errorCode(), 10); QCOMPARE(out->errorMessage(), QStringLiteral("Ooops")); QCOMPARE(out->serverName(), QStringLiteral("AkonadiTest")); QCOMPARE(out->message(), QStringLiteral("Oh, hello there!")); QCOMPARE(out->protocolVersion(), 42); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testLoginCommand() { LoginCommand in; QVERIFY(!in.isResponse()); QVERIFY(in.isValid()); in.setSessionId("MySession-123-notifications"); const auto out = serializeAndDeserialize(LoginCommandPtr::create(in)); QVERIFY(out->isValid()); QVERIFY(!out->isResponse()); QCOMPARE(out->sessionId(), QByteArray("MySession-123-notifications")); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testLoginResponse() { LoginResponse in; QVERIFY(in.isResponse()); QVERIFY(in.isValid()); QVERIFY(!in.isError()); in.setError(42, QStringLiteral("Ooops")); const auto out = serializeAndDeserialize(LoginResponsePtr::create(in)); QVERIFY(out->isValid()); QVERIFY(out->isResponse()); QVERIFY(out->isError()); QCOMPARE(out->errorCode(), 42); QCOMPARE(out->errorMessage(), QStringLiteral("Ooops")); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testLogoutCommand() { LogoutCommand in; QVERIFY(!in.isResponse()); QVERIFY(in.isValid()); const auto out = serializeAndDeserialize(LogoutCommandPtr::create(in)); QVERIFY(!out->isResponse()); QVERIFY(out->isValid()); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testLogoutResponse() { LogoutResponse in; QVERIFY(in.isResponse()); QVERIFY(in.isValid()); QVERIFY(!in.isError()); in.setError(42, QStringLiteral("Ooops")); const auto out = serializeAndDeserialize(LogoutResponsePtr::create(in)); QVERIFY(out->isValid()); QVERIFY(out->isResponse()); QVERIFY(out->isError()); QCOMPARE(out->errorCode(), 42); QCOMPARE(out->errorMessage(), QStringLiteral("Ooops")); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testTransactionCommand() { TransactionCommand in; QVERIFY(!in.isResponse()); QVERIFY(in.isValid()); in.setMode(TransactionCommand::Begin); const auto out = serializeAndDeserialize(TransactionCommandPtr::create(in)); QVERIFY(out->isValid()); QVERIFY(!out->isResponse()); QCOMPARE(out->mode(), TransactionCommand::Begin); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testTransactionResponse() { TransactionResponse in; QVERIFY(in.isResponse()); QVERIFY(in.isValid()); QVERIFY(!in.isError()); in.setError(42, QStringLiteral("Ooops")); const auto out = serializeAndDeserialize(TransactionResponsePtr::create(in)); QVERIFY(out->isValid()); QVERIFY(out->isResponse()); QVERIFY(out->isError()); QCOMPARE(out->errorCode(), 42); QCOMPARE(out->errorMessage(), QStringLiteral("Ooops")); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testCreateItemCommand() { Scope addedTags(QVector{ 3, 4 }); Scope removedTags(QVector{ 5, 6 }); Attributes attrs{ { "ATTR1", "MyAttr" }, { "ATTR2", "Můj chlupaťoučký kůň" } }; QSet parts{ "PLD:HEAD", "PLD:ENVELOPE" }; CreateItemCommand in; QVERIFY(!in.isResponse()); QVERIFY(in.isValid()); QCOMPARE(in.mergeModes(), CreateItemCommand::None); in.setMergeModes(CreateItemCommand::MergeModes(CreateItemCommand::GID | CreateItemCommand::RemoteID)); in.setCollection(Scope(1)); in.setItemSize(100); in.setMimeType(QStringLiteral("text/directory")); in.setGid(QStringLiteral("GID")); in.setRemoteId(QStringLiteral("RID")); in.setRemoteRevision(QStringLiteral("RREV")); in.setDateTime(QDateTime(QDate(2015, 8, 11), QTime(14, 32, 21), Qt::UTC)); in.setFlags({ "\\SEEN", "FLAG" }); in.setFlagsOverwritten(true); in.setAddedFlags({ "FLAG2" }); in.setRemovedFlags({ "FLAG3" }); in.setTags(Scope(2)); in.setAddedTags(addedTags); in.setRemovedTags(removedTags); in.setAttributes(attrs); in.setParts(parts); const auto out = serializeAndDeserialize(CreateItemCommandPtr::create(in)); QVERIFY(out->isValid()); QVERIFY(!out->isResponse()); QCOMPARE(out->mergeModes(), CreateItemCommand::GID | CreateItemCommand::RemoteID); QCOMPARE(out->collection(), Scope(1)); QCOMPARE(out->itemSize(), 100); QCOMPARE(out->mimeType(), QStringLiteral("text/directory")); QCOMPARE(out->gid(), QStringLiteral("GID")); QCOMPARE(out->remoteId(), QStringLiteral("RID")); QCOMPARE(out->remoteRevision(), QStringLiteral("RREV")); QCOMPARE(out->dateTime(), QDateTime(QDate(2015, 8, 11), QTime(14, 32, 21), Qt::UTC)); QCOMPARE(out->flags(), QSet() << "\\SEEN" << "FLAG"); QCOMPARE(out->flagsOverwritten(), true); QCOMPARE(out->addedFlags(), QSet{ "FLAG2" }); QCOMPARE(out->removedFlags(), QSet{ "FLAG3" }); QCOMPARE(out->tags(), Scope(2)); QCOMPARE(out->addedTags(), addedTags); QCOMPARE(out->removedTags(), removedTags); QCOMPARE(out->attributes(), attrs); QCOMPARE(out->parts(), parts); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testCreateItemResponse() { CreateItemResponse in; QVERIFY(in.isResponse()); QVERIFY(in.isValid()); QVERIFY(!in.isError()); in.setError(42, QStringLiteral("Ooops")); const auto out = serializeAndDeserialize(CreateItemResponsePtr::create(in)); QVERIFY(out->isValid()); QVERIFY(out->isResponse()); QVERIFY(out->isError()); QCOMPARE(out->errorCode(), 42); QCOMPARE(out->errorMessage(), QStringLiteral("Ooops")); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testCopyItemsCommand() { const Scope items(QVector{ 1, 2, 3, 10 }); CopyItemsCommand in; QVERIFY(in.isValid()); QVERIFY(!in.isResponse()); in.setItems(items); in.setDestination(Scope(42)); const auto out = serializeAndDeserialize(CopyItemsCommandPtr::create(in)); QVERIFY(out->isValid()); QVERIFY(!out->isResponse()); QCOMPARE(out->items(), items); QCOMPARE(out->destination(), Scope(42)); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } void ProtocolTest::testCopyItemsResponse() { CopyItemsResponse in; QVERIFY(in.isResponse()); QVERIFY(in.isValid()); QVERIFY(!in.isError()); in.setError(42, QStringLiteral("Ooops")); const auto out = serializeAndDeserialize(CopyItemsResponsePtr::create(in)); QVERIFY(out->isValid()); QVERIFY(out->isResponse()); QVERIFY(out->isError()); QCOMPARE(out->errorCode(), 42); QCOMPARE(out->errorMessage(), QStringLiteral("Ooops")); QCOMPARE(*out, in); const bool notEquals = (*out != in); QVERIFY(!notEquals); } QTEST_MAIN(ProtocolTest) diff --git a/autotests/server/fetchhandlertest.cpp b/autotests/server/fetchhandlertest.cpp index b19751a6b..699a7beed 100644 --- a/autotests/server/fetchhandlertest.cpp +++ b/autotests/server/fetchhandlertest.cpp @@ -1,270 +1,270 @@ /* Copyright (c) 2014 Christian Mollekopf 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 #include #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include "dbinitializer.h" #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(Akonadi::Server::Tag::List) Q_DECLARE_METATYPE(Akonadi::Server::Tag) class FetchHandlerTest : public QObject { Q_OBJECT public: FetchHandlerTest() : QObject() { qRegisterMetaType(); try { FakeAkonadiServer::instance()->setPopulateDb(false); FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~FetchHandlerTest() { FakeAkonadiServer::instance()->quit(); } Protocol::FetchItemsCommandPtr createCommand(const Scope &scope, const Protocol::ScopeContext &ctx = Protocol::ScopeContext()) { auto cmd = Protocol::FetchItemsCommandPtr::create(scope, ctx); - cmd->fetchScope().setFetch(Protocol::FetchScope::IgnoreErrors); + cmd->fetchScope().setFetch(Protocol::ItemFetchScope::IgnoreErrors); return cmd; } Protocol::FetchItemsResponsePtr createResponse(const PimItem &item) { auto resp = Protocol::FetchItemsResponsePtr::create(item.id()); resp->setMimeType(item.mimeType().name()); resp->setParentId(item.collectionId()); return resp; } QScopedPointer initializer; private Q_SLOTS: void testFetch_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col = initializer->createCollection("root"); PimItem item1 = initializer->createItem("item1", col); PimItem item2 = initializer->createItem("item2", col); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(item1.id())) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); QTest::newRow("basic fetch") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Collection, col.id()))) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item2)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); QTest::newRow("collection context") << scenarios; } } void testFetch() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testFetchByTag_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col = initializer->createCollection("root"); PimItem item1 = initializer->createItem("item1", col); PimItem item2 = initializer->createItem("item2", col); Tag tag; TagType type; type.setName(QStringLiteral("PLAIN")); type.insert(); tag.setTagType(type); tag.setGid(QStringLiteral("gid")); tag.insert(); item1.addTag(tag); item1.update(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Tag, tag.id()))) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); QTest::newRow("fetch by tag") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("testresource")) << TestScenario::create(5, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Tag, tag.id()))) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); QTest::newRow("uid fetch by tag from resource") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("testresource")) << TestScenario::create(5, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Collection, col.id()))) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item2)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); QTest::newRow("fetch collection") << scenarios; } } void testFetchByTag() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testFetchCommandContext_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); PimItem item1 = initializer->createItem("item1", col1); Collection col2 = initializer->createCollection("col2"); Tag tag; TagType type; type.setName(QStringLiteral("PLAIN")); type.insert(); tag.setTagType(type); tag.setGid(QStringLiteral("gid")); tag.insert(); item1.addTag(tag); item1.update(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("testresource")) << TestScenario::create(5, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Collection, col2.id()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Tag, tag.id()))) << TestScenario::create(6, TestScenario::ServerCmd, createResponse(item1)) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); //Special case that used to be broken due to persistent command context QTest::newRow("fetch by tag after collection") << scenarios; } } void testFetchCommandContext() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testList_data() { QElapsedTimer timer; initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); timer.start(); QList items; for (int i = 0; i < 1000; i++) { items.append(initializer->createItem(QString::number(i).toLatin1().constData(),col1)); } qDebug() << timer.nsecsElapsed()/1.0e6 << "ms"; timer.start(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(ImapSet::all(), Protocol::ScopeContext(Protocol::ScopeContext::Collection, col1.id()))); while (!items.isEmpty()) { const PimItem &item = items.takeLast(); scenarios << TestScenario::create(5, TestScenario::ServerCmd, createResponse(item)); } scenarios << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchItemsResponsePtr::create()); QTest::newRow("complete list") << scenarios; } qDebug() << timer.nsecsElapsed()/1.0e6 << "ms"; } void testList() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); //StorageDebugger::instance()->enableSQLDebugging(true); //StorageDebugger::instance()->writeToFile(QStringLiteral("sqllog.txt")); FakeAkonadiServer::instance()->runTest(); } }; AKTEST_FAKESERVER_MAIN(FetchHandlerTest) #include "fetchhandlertest.moc" diff --git a/src/core/monitor.cpp b/src/core/monitor.cpp index 6c38b011a..df838fd68 100644 --- a/src/core/monitor.cpp +++ b/src/core/monitor.cpp @@ -1,363 +1,366 @@ /* 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 "monitor.h" #include "monitor_p.h" #include "changemediator_p.h" #include "collectionfetchscope.h" #include "itemfetchjob.h" #include "session.h" #include #include using namespace Akonadi; Monitor::Monitor(QObject *parent) : QObject(parent) , d_ptr(new MonitorPrivate(nullptr, this)) { d_ptr->init(); d_ptr->connectToNotificationManager(); ChangeMediator::registerMonitor(this); } //@cond PRIVATE Monitor::Monitor(MonitorPrivate *d, QObject *parent) : QObject(parent) , d_ptr(d) { d_ptr->init(); d_ptr->connectToNotificationManager(); ChangeMediator::registerMonitor(this); } //@endcond Monitor::~Monitor() { ChangeMediator::unregisterMonitor(this); delete d_ptr; } void Monitor::setCollectionMonitored(const Collection &collection, bool monitored) { Q_D(Monitor); if (!d->collections.contains(collection) && monitored) { d->collections << collection; d->pendingModification.startMonitoringCollection(collection.id()); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->collections.removeAll(collection)) { d->pendingModification.stopMonitoringCollection(collection.id()); d->scheduleSubscriptionUpdate(); } } emit collectionMonitored(collection, monitored); } void Monitor::setItemMonitored(const Item &item, bool monitored) { Q_D(Monitor); if (!d->items.contains(item.id()) && monitored) { d->items.insert(item.id()); d->pendingModification.startMonitoringItem(item.id()); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->items.remove(item.id())) { d->pendingModification.stopMonitoringItem(item.id()); d->scheduleSubscriptionUpdate(); } } emit itemMonitored(item, monitored); } void Monitor::setResourceMonitored(const QByteArray &resource, bool monitored) { Q_D(Monitor); if (!d->resources.contains(resource) && monitored) { d->resources.insert(resource); d->pendingModification.startMonitoringResource(resource); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->resources.remove(resource)) { d->pendingModification.stopMonitoringResource(resource); d->scheduleSubscriptionUpdate(); } } emit resourceMonitored(resource, monitored); } void Monitor::setMimeTypeMonitored(const QString &mimetype, bool monitored) { Q_D(Monitor); if (!d->mimetypes.contains(mimetype) && monitored) { d->mimetypes.insert(mimetype); d->pendingModification.startMonitoringMimeType(mimetype); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->mimetypes.remove(mimetype)) { d->pendingModification.stopMonitoringMimeType(mimetype); d->scheduleSubscriptionUpdate(); } } emit mimeTypeMonitored(mimetype, monitored); } void Monitor::setTagMonitored(const Akonadi::Tag &tag, bool monitored) { Q_D(Monitor); if (!d->tags.contains(tag.id()) && monitored) { d->tags.insert(tag.id()); d->pendingModification.startMonitoringTag(tag.id()); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->tags.remove(tag.id())) { d->pendingModification.stopMonitoringTag(tag.id()); d->scheduleSubscriptionUpdate(); } } emit tagMonitored(tag, monitored); } void Monitor::setTypeMonitored(Monitor::Type type, bool monitored) { Q_D(Monitor); if (!d->types.contains(type) && monitored) { d->types.insert(type); d->pendingModification.startMonitoringType(static_cast(type)); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->types.remove(type)) { d->pendingModification.stopMonitoringType(static_cast(type)); d->scheduleSubscriptionUpdate(); } } emit typeMonitored(type, monitored); } void Akonadi::Monitor::setAllMonitored(bool monitored) { Q_D(Monitor); if (d->monitorAll == monitored) { return; } d->monitorAll = monitored; d->pendingModification.setAllMonitored(monitored); d->scheduleSubscriptionUpdate(); emit allMonitored(monitored); } void Monitor::setExclusive(bool exclusive) { Q_D(Monitor); d->exclusive = exclusive; d->pendingModification.setIsExclusive(exclusive); d->scheduleSubscriptionUpdate(); } bool Monitor::exclusive() const { Q_D(const Monitor); return d->exclusive; } void Monitor::ignoreSession(Session *session) { Q_D(Monitor); if (!d->sessions.contains(session->sessionId())) { d->sessions << session->sessionId(); connect(session, SIGNAL(destroyed(QObject*)), this, SLOT(slotSessionDestroyed(QObject*))); d->pendingModification.startIgnoringSession(session->sessionId()); d->scheduleSubscriptionUpdate(); } } void Monitor::fetchCollection(bool enable) { Q_D(Monitor); d->fetchCollection = enable; } void Monitor::fetchCollectionStatistics(bool enable) { Q_D(Monitor); d->fetchCollectionStatistics = enable; } void Monitor::setItemFetchScope(const ItemFetchScope &fetchScope) { Q_D(Monitor); d->mItemFetchScope = fetchScope; + d->scheduleSubscriptionUpdate(); } ItemFetchScope &Monitor::itemFetchScope() { Q_D(Monitor); + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::ItemFetchScope; + d->scheduleSubscriptionUpdate(); return d->mItemFetchScope; } void Monitor::fetchChangedOnly(bool enable) { Q_D(Monitor); d->mFetchChangedOnly = enable; } void Monitor::setCollectionFetchScope(const CollectionFetchScope &fetchScope) { Q_D(Monitor); d->mCollectionFetchScope = fetchScope; } CollectionFetchScope &Monitor::collectionFetchScope() { Q_D(Monitor); return d->mCollectionFetchScope; } void Monitor::setTagFetchScope(const TagFetchScope &fetchScope) { Q_D(Monitor); d->mTagFetchScope = fetchScope; } TagFetchScope &Monitor::tagFetchScope() { Q_D(Monitor); return d->mTagFetchScope; } Akonadi::Collection::List Monitor::collectionsMonitored() const { Q_D(const Monitor); return d->collections; } QVector Monitor::itemsMonitoredEx() const { Q_D(const Monitor); QVector result; result.reserve(d->items.size()); std::copy(d->items.begin(), d->items.end(), std::back_inserter(result)); return result; } int Monitor::numItemsMonitored() const { Q_D(const Monitor); return d->items.size(); } QVector Monitor::tagsMonitored() const { Q_D(const Monitor); QVector result; result.reserve(d->tags.size()); std::copy(d->tags.begin(), d->tags.end(), std::back_inserter(result)); return result; } QVector Monitor::typesMonitored() const { Q_D(const Monitor); QVector result; result.reserve(d->types.size()); std::copy(d->types.begin(), d->types.end(), std::back_inserter(result)); return result; } QStringList Monitor::mimeTypesMonitored() const { Q_D(const Monitor); return d->mimetypes.toList(); } int Monitor::numMimeTypesMonitored() const { Q_D(const Monitor); return d->mimetypes.count(); } QList Monitor::resourcesMonitored() const { Q_D(const Monitor); return d->resources.toList(); } int Monitor::numResourcesMonitored() const { Q_D(const Monitor); return d->resources.count(); } bool Monitor::isAllMonitored() const { Q_D(const Monitor); return d->monitorAll; } void Monitor::setSession(Akonadi::Session *session) { Q_D(Monitor); if (session == d->session) { return; } if (!session) { d->session = Session::defaultSession(); } else { d->session = session; } d->itemCache->setSession(d->session); d->collectionCache->setSession(d->session); d->tagCache->setSession(d->session); // Reconnect with a new session d->connectToNotificationManager(); } Session *Monitor::session() const { Q_D(const Monitor); return d->session; } void Monitor::setCollectionMoveTranslationEnabled(bool enabled) { Q_D(Monitor); d->collectionMoveTranslationEnabled = enabled; } #include "moc_monitor.cpp" diff --git a/src/core/monitor_p.cpp b/src/core/monitor_p.cpp index 967c85a03..5758beaca 100644 --- a/src/core/monitor_p.cpp +++ b/src/core/monitor_p.cpp @@ -1,1447 +1,1455 @@ /* Copyright (c) 2007 Tobias Koenig 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. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmanagerinterface.h" #include "session.h" #include "changemediator_p.h" #include "vectorhelper.h" #include "akonadicore_debug.h" #include "notificationsubscriber.h" #include "changenotification.h" +#include "protocolhelper_p.h" using namespace Akonadi; class operation; static const int PipelineSize = 5; MonitorPrivate::MonitorPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, Monitor *parent) : q_ptr(parent) , dependenciesFactory(dependenciesFactory_ ? dependenciesFactory_ : new ChangeNotificationDependenciesFactory) , ntfConnection(nullptr) , monitorAll(false) , exclusive(false) , mFetchChangedOnly(false) , session(Session::defaultSession()) , collectionCache(nullptr) , itemCache(nullptr) , tagCache(nullptr) , mCommandBuffer(parent, "handleCommands") + , pendingModificationChanges(Protocol::ModifySubscriptionCommand::None) , pendingModificationTimer(nullptr) , monitorReady(false) , fetchCollection(false) , fetchCollectionStatistics(false) , collectionMoveTranslationEnabled(true) , useRefCounting(false) { } MonitorPrivate::~MonitorPrivate() { disconnectFromNotificationManager(); delete dependenciesFactory; delete collectionCache; delete itemCache; delete tagCache; } void MonitorPrivate::init() { // needs to be at least 3x pipeline size for the collection move case collectionCache = dependenciesFactory->createCollectionCache(3 * PipelineSize, session); // needs to be at least 1x pipeline size itemCache = dependenciesFactory->createItemListCache(PipelineSize, session); // 20 tags looks like a reasonable amount to keep around tagCache = dependenciesFactory->createTagListCache(20, session); QObject::connect(collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(tagCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)), q_ptr, SLOT(serverStateChanged(Akonadi::ServerManager::State))); statisticsCompressionTimer.setSingleShot(true); statisticsCompressionTimer.setInterval(500); QObject::connect(&statisticsCompressionTimer, SIGNAL(timeout()), q_ptr, SLOT(slotFlushRecentlyChangedCollections())); } bool MonitorPrivate::connectToNotificationManager() { if (ntfConnection) { ntfConnection->deleteLater(); ntfConnection = nullptr; } if (!session) { return false; } ntfConnection = dependenciesFactory->createNotificationConnection(session, &mCommandBuffer); if (!ntfConnection) { return false; } pendingModification = Protocol::ModifySubscriptionCommand(); for (const auto &col : qAsConst(collections)) { pendingModification.startMonitoringCollection(col.id()); } for (const auto &res : qAsConst(resources)) { pendingModification.startMonitoringResource(res); } for (auto itemId : qAsConst(items)) { pendingModification.startMonitoringItem(itemId); } for (auto tagId : qAsConst(tags)) { pendingModification.startMonitoringTag(tagId); } for (auto type : qAsConst(types)) { pendingModification.startMonitoringType(static_cast(type)); } for (const auto &mimetype : qAsConst(mimetypes)) { pendingModification.startMonitoringMimeType(mimetype); } for (const auto &session : qAsConst(sessions)) { pendingModification.startIgnoringSession(session); } pendingModification.setAllMonitored(monitorAll); pendingModification.setIsExclusive(exclusive); ntfConnection->reconnect(); return true; } void MonitorPrivate::disconnectFromNotificationManager() { if (ntfConnection) { ntfConnection->disconnect(q_ptr); dependenciesFactory->destroyNotificationConnection(session, ntfConnection.data()); } } void MonitorPrivate::serverStateChanged(ServerManager::State state) { if (state == ServerManager::Running) { connectToNotificationManager(); } } void MonitorPrivate::invalidateCollectionCache(qint64 id) { collectionCache->update(id, mCollectionFetchScope); } void MonitorPrivate::invalidateItemCache(qint64 id) { itemCache->update(QList() << id, mItemFetchScope); } void MonitorPrivate::invalidateTagCache(qint64 id) { tagCache->update({ id }, mTagFetchScope); } int MonitorPrivate::pipelineSize() const { return PipelineSize; } void MonitorPrivate::scheduleSubscriptionUpdate() { if (pendingModificationTimer || !monitorReady) { return; } pendingModificationTimer = new QTimer(); pendingModificationTimer->setSingleShot(true); pendingModificationTimer->setInterval(0); pendingModificationTimer->start(); q_ptr->connect(pendingModificationTimer, SIGNAL(timeout()), q_ptr, SLOT(slotUpdateSubscription())); } void MonitorPrivate::slotUpdateSubscription() { delete pendingModificationTimer; pendingModificationTimer = nullptr; if (ntfConnection) { + if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::ItemFetchScope) { + pendingModification.setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); + } + pendingModificationChanges = Protocol::ModifySubscriptionCommand::None; + ntfConnection->sendCommand(3, Protocol::ModifySubscriptionCommandPtr::create(pendingModification)); pendingModification = Protocol::ModifySubscriptionCommand(); } } bool MonitorPrivate::isLazilyIgnored(const Protocol::ChangeNotificationPtr &msg, bool allowModifyFlagsConversion) const { if (msg->type() == Protocol::Command::CollectionChangeNotification) { // Lazy fetching can only affects items. return false; } if (msg->type() == Protocol::Command::TagChangeNotification) { const auto op = Protocol::cmdCast(msg).operation(); return ((op == Protocol::TagChangeNotification::Add && q_ptr->receivers(SIGNAL(tagAdded(Akonadi::Tag))) == 0) || (op == Protocol::TagChangeNotification::Modify && q_ptr->receivers(SIGNAL(tagChanged(Akonadi::Tag))) == 0) || (op == Protocol::TagChangeNotification::Remove && q_ptr->receivers(SIGNAL(tagRemoved(Akonadi::Tag))) == 0)); } if (!fetchCollectionStatistics && msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); const auto op = itemNtf.operation(); if ((op == Protocol::ItemChangeNotification::Add && q_ptr->receivers(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))) == 0) || (op == Protocol::ItemChangeNotification::Remove && q_ptr->receivers(SIGNAL(itemRemoved(Akonadi::Item))) == 0 && q_ptr->receivers(SIGNAL(itemsRemoved(Akonadi::Item::List))) == 0) || (op == Protocol::ItemChangeNotification::Modify && q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) == 0) || (op == Protocol::ItemChangeNotification::ModifyFlags && (q_ptr->receivers(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))) == 0 // Newly delivered ModifyFlags notifications will be converted to // itemChanged(item, "FLAGS") for legacy clients. && (!allowModifyFlagsConversion || q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) == 0))) || (op == Protocol::ItemChangeNotification::ModifyTags && q_ptr->receivers(SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet,QSet))) == 0) || (op == Protocol::ItemChangeNotification::Move && q_ptr->receivers(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection))) == 0) || (op == Protocol::ItemChangeNotification::Link && q_ptr->receivers(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection))) == 0) || (op == Protocol::ItemChangeNotification::Unlink && q_ptr->receivers(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection))) == 0)) { return true; } if (!useRefCounting) { return false; } const Collection::Id parentCollectionId = itemNtf.parentCollection(); if ((op == Protocol::ItemChangeNotification::Add) || (op == Protocol::ItemChangeNotification::Remove) || (op == Protocol::ItemChangeNotification::Modify) || (op == Protocol::ItemChangeNotification::ModifyFlags) || (op == Protocol::ItemChangeNotification::ModifyTags) || (op == Protocol::ItemChangeNotification::Link) || (op == Protocol::ItemChangeNotification::Unlink)) { if (isMonitored(parentCollectionId)) { return false; } } if (op == Protocol::ItemChangeNotification::Move) { if (!isMonitored(parentCollectionId) && !isMonitored(itemNtf.parentDestCollection())) { return true; } // We can't ignore the move. It must be transformed later into a removal or insertion. return false; } return true; } return false; } void MonitorPrivate::checkBatchSupport(const Protocol::ChangeNotificationPtr &msg, bool &needsSplit, bool &batchSupported) const { if (msg->type() != Protocol::Command::ItemChangeNotification) { needsSplit = false; batchSupported = false; return; } const auto &itemNtf = Protocol::cmdCast(msg); const bool isBatch = (itemNtf.items().count() > 1); switch (itemNtf.operation()) { case Protocol::ItemChangeNotification::Add: needsSplit = isBatch; batchSupported = false; return; case Protocol::ItemChangeNotification::Modify: needsSplit = isBatch; batchSupported = false; return; case Protocol::ItemChangeNotification::ModifyFlags: batchSupported = q_ptr->receivers(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))) > 0; needsSplit = isBatch && !batchSupported && q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) > 0; return; case Protocol::ItemChangeNotification::ModifyTags: // Tags were added after batch notifications, so they are always supported batchSupported = true; needsSplit = false; return; case Protocol::ItemChangeNotification::ModifyRelations: // Relations were added after batch notifications, so they are always supported batchSupported = true; needsSplit = false; return; case Protocol::ItemChangeNotification::Move: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection))) > 0; return; case Protocol::ItemChangeNotification::Remove: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemRemoved(Akonadi::Item))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsRemoved(Akonadi::Item::List))) > 0; return; case Protocol::ItemChangeNotification::Link: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection))) > 0; return; case Protocol::ItemChangeNotification::Unlink: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection))) > 0; return; default: needsSplit = isBatch; batchSupported = false; qCDebug(AKONADICORE_LOG) << "Unknown operation type" << itemNtf.operation() << "in item change notification"; return; } } Protocol::ChangeNotificationList MonitorPrivate::splitMessage(const Protocol::ItemChangeNotification &msg, bool legacy) const { Protocol::ChangeNotificationList list; Protocol::ItemChangeNotification baseMsg; baseMsg.setSessionId(msg.sessionId()); if (legacy && msg.operation() == Protocol::ItemChangeNotification::ModifyFlags) { baseMsg.setOperation(Protocol::ItemChangeNotification::Modify); baseMsg.setItemParts(QSet() << "FLAGS"); } else { baseMsg.setOperation(msg.operation()); baseMsg.setItemParts(msg.itemParts()); } baseMsg.setParentCollection(msg.parentCollection()); baseMsg.setParentDestCollection(msg.parentDestCollection()); baseMsg.setResource(msg.resource()); baseMsg.setDestinationResource(msg.destinationResource()); baseMsg.setAddedFlags(msg.addedFlags()); baseMsg.setRemovedFlags(msg.removedFlags()); baseMsg.setAddedTags(msg.addedTags()); baseMsg.setRemovedTags(msg.removedTags()); const auto items = msg.items(); list.reserve(items.count()); for (const Protocol::ItemChangeNotification::Item &item : items) { auto copy = Protocol::ItemChangeNotificationPtr::create(baseMsg); copy->setItems({ { item.id, item.remoteId, item.remoteRevision, item.mimeType } }); list << copy; } return list; } bool MonitorPrivate::fetchCollections() const { return fetchCollection; } bool MonitorPrivate::fetchItems() const { return !mItemFetchScope.isEmpty(); } bool MonitorPrivate::ensureDataAvailable(const Protocol::ChangeNotificationPtr &msg) { bool allCached = true; if (msg->type() == Protocol::Command::TagChangeNotification) { return tagCache->ensureCached({ Protocol::cmdCast(msg).id() }, mTagFetchScope); } if (msg->type() == Protocol::Command::RelationChangeNotification) { return true; } if (msg->type() == Protocol::Command::SubscriptionChangeNotification) { return true; } if (msg->type() == Protocol::Command::DebugChangeNotification) { return true; } if (msg->type() == Protocol::Command::CollectionChangeNotification && Protocol::cmdCast(msg).operation() == Protocol::CollectionChangeNotification::Remove) { // For collection removals the collection is gone already, so we can't fetch it, // but we have to at least obtain the ancestor chain. const qint64 parentCollection = Protocol::cmdCast(msg).parentCollection(); return parentCollection <= -1 || collectionCache->ensureCached(parentCollection, mCollectionFetchScope); } if (fetchCollections()) { const qint64 parentCollection = (msg->type() == Protocol::Command::ItemChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : (msg->type() == Protocol::Command::CollectionChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : -1; if (parentCollection > -1 && !collectionCache->ensureCached(parentCollection, mCollectionFetchScope)) { allCached = false; } qint64 parentDestCollection = -1; if ((msg->type() == Protocol::Command::ItemChangeNotification) && (Protocol::cmdCast(msg).operation() == Protocol::ItemChangeNotification::Move)) { parentDestCollection = Protocol::cmdCast(msg).parentDestCollection(); } else if ((msg->type() == Protocol::Command::CollectionChangeNotification) && (Protocol::cmdCast(msg).operation() == Protocol::CollectionChangeNotification::Move)) { parentDestCollection = Protocol::cmdCast(msg).parentDestCollection(); } if (parentDestCollection > -1 && !collectionCache->ensureCached(parentDestCollection, mCollectionFetchScope)) { allCached = false; } } if (msg->isRemove()) { return allCached; } if (msg->type() == Protocol::Command::ItemChangeNotification && fetchItems()) { ItemFetchScope scope(mItemFetchScope); const auto &itemNtf = Protocol::cmdCast(msg); if (mFetchChangedOnly && (itemNtf.operation() == Protocol::ItemChangeNotification::Modify || itemNtf.operation() == Protocol::ItemChangeNotification::ModifyFlags)) { bool fullPayloadWasRequested = scope.fullPayload(); scope.fetchFullPayload(false); const QSet requestedPayloadParts = scope.payloadParts(); for (const QByteArray &part : requestedPayloadParts) { scope.fetchPayloadPart(part, false); } bool allAttributesWereRequested = scope.allAttributes(); const QSet requestedAttrParts = scope.attributes(); for (const QByteArray &part : requestedAttrParts) { scope.fetchAttribute(part, false); } const QSet changedParts = itemNtf.itemParts(); for (const QByteArray &part : changedParts) { if (part.startsWith("PLD:") && //krazy:exclude=strings since QByteArray (fullPayloadWasRequested || requestedPayloadParts.contains(part))) { scope.fetchPayloadPart(part.mid(4), true); } if (part.startsWith("ATR:") && //krazy:exclude=strings since QByteArray (allAttributesWereRequested || requestedAttrParts.contains(part))) { scope.fetchAttribute(part.mid(4), true); } } } if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), scope)) { allCached = false; } // Make sure all tags for ModifyTags operation are in cache too if (itemNtf.operation() == Protocol::ItemChangeNotification::ModifyTags) { if (!tagCache->ensureCached((itemNtf.addedTags() + itemNtf.removedTags()).toList(), mTagFetchScope)) { allCached = false; } } } else if (msg->type() == Protocol::Command::CollectionChangeNotification && fetchCollections()) { const qint64 colId = Protocol::cmdCast(msg).id(); if (!collectionCache->ensureCached(colId, mCollectionFetchScope)) { allCached = false; } } return allCached; } bool MonitorPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) { bool someoneWasListening = false; if (msg->type() == Protocol::Command::TagChangeNotification) { const auto &tagNtf = Protocol::cmdCast(msg); //In case of a Remove notification this will return a list of invalid entities (we'll deal later with them) const Tag::List tags = tagCache->retrieve({ tagNtf.id() }); someoneWasListening = emitTagNotification(tagNtf, tags.isEmpty() ? Tag() : tags[0]); } else if (msg->type() == Protocol::Command::RelationChangeNotification) { const auto &relNtf = Protocol::cmdCast(msg); Relation rel; rel.setLeft(Akonadi::Item(relNtf.leftItem())); rel.setRight(Akonadi::Item(relNtf.rightItem())); rel.setType(relNtf.type().toLatin1()); rel.setRemoteId(relNtf.remoteId().toLatin1()); someoneWasListening = emitRelationNotification(relNtf, rel); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); const Collection parent = collectionCache->retrieve(colNtf.parentCollection()); Collection destParent; if (colNtf.operation() == Protocol::CollectionChangeNotification::Move) { destParent = collectionCache->retrieve(colNtf.parentDestCollection()); } //For removals this will retrieve an invalid collection. We'll deal with that in emitCollectionNotification const Collection col = collectionCache->retrieve(colNtf.id()); //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (col.isValid() || colNtf.operation() == Protocol::CollectionChangeNotification::Remove || !fetchCollections()) { someoneWasListening = emitCollectionNotification(colNtf, col, parent, destParent); } } else if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); const Collection parent = collectionCache->retrieve(itemNtf.parentCollection()); Collection destParent; if (itemNtf.operation() == Protocol::ItemChangeNotification::Move) { destParent = collectionCache->retrieve(itemNtf.parentDestCollection()); } //For removals this will retrieve an empty set. We'll deal with that in emitItemNotification const Item::List items = itemCache->retrieve(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (!items.isEmpty() || itemNtf.operation() == Protocol::ItemChangeNotification::Remove || !fetchItems()) { someoneWasListening = emitItemsNotification(itemNtf, items, parent, destParent); } } else if (msg->type() == Protocol::Command::SubscriptionChangeNotification) { const auto &subNtf = Protocol::cmdCast(msg); NotificationSubscriber subscriber; subscriber.setSubscriber(subNtf.subscriber()); subscriber.setSessionId(subNtf.sessionId()); subscriber.setMonitoredCollections(subNtf.collections()); subscriber.setMonitoredItems(subNtf.items()); subscriber.setMonitoredTags(subNtf.tags()); QSet monitorTypes; Q_FOREACH (auto type, subNtf.types()) { monitorTypes.insert(static_cast(type)); } subscriber.setMonitoredTypes(monitorTypes); subscriber.setMonitoredMimeTypes(subNtf.mimeTypes()); subscriber.setMonitoredResources(subNtf.resources()); subscriber.setIgnoredSessions(subNtf.ignoredSessions()); subscriber.setIsAllMonitored(subNtf.allMonitored()); subscriber.setIsExclusive(subNtf.exclusive()); + subscriber.setItemFetchScope(ProtocolHelper::parseItemFetchScope(subNtf.itemFetchScope())); someoneWasListening = emitSubscriptionChangeNotification(subNtf, subscriber); } else if (msg->type() == Protocol::Command::DebugChangeNotification) { const auto &changeNtf = Protocol::cmdCast(msg); ChangeNotification notification; notification.setListeners(changeNtf.listeners()); notification.setTimestamp(QDateTime::fromMSecsSinceEpoch(changeNtf.timestamp())); notification.setNotification(changeNtf.notification()); switch (changeNtf.notification()->type()) { case Protocol::Command::ItemChangeNotification: notification.setType(ChangeNotification::Items); break; case Protocol::Command::CollectionChangeNotification: notification.setType(ChangeNotification::Collection); break; case Protocol::Command::TagChangeNotification: notification.setType(ChangeNotification::Tag); break; case Protocol::Command::RelationChangeNotification: notification.setType(ChangeNotification::Relation); break; case Protocol::Command::SubscriptionChangeNotification: notification.setType(ChangeNotification::Subscription); break; default: Q_ASSERT(false); // huh? return false; } someoneWasListening = emitDebugChangeNotification(changeNtf, notification); } return someoneWasListening; } void MonitorPrivate::updatePendingStatistics(const Protocol::ChangeNotificationPtr &msg) { if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); notifyCollectionStatisticsWatchers(itemNtf.parentCollection(), itemNtf.resource()); // FIXME use the proper resource of the target collection, for cross resource moves notifyCollectionStatisticsWatchers(itemNtf.parentDestCollection(), itemNtf.destinationResource()); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); if (colNtf.operation() == Protocol::CollectionChangeNotification::Remove) { // no need for statistics updates anymore recentlyChangedCollections.remove(colNtf.id()); } } } void MonitorPrivate::slotSessionDestroyed(QObject *object) { Session *objectSession = qobject_cast(object); if (objectSession) { sessions.removeAll(objectSession->sessionId()); pendingModification.stopIgnoringSession(objectSession->sessionId()); scheduleSubscriptionUpdate(); } } void MonitorPrivate::slotStatisticsChangedFinished(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast(job); Q_ASSERT(statisticsJob->collection().isValid()); emit q_ptr->collectionStatisticsChanged(statisticsJob->collection().id(), statisticsJob->statistics()); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { for (Collection::Id collection : qAsConst(recentlyChangedCollections)) { Q_ASSERT(collection >= 0); if (fetchCollectionStatistics) { fetchStatistics(collection); } else { static const CollectionStatistics dummyStatistics; emit q_ptr->collectionStatisticsChanged(collection, dummyStatistics); } } recentlyChangedCollections.clear(); } int MonitorPrivate::translateAndCompress(QQueue ¬ificationQueue, const Protocol::ChangeNotificationPtr &msg) { // Always handle tags and relations if (msg->type() == Protocol::Command::TagChangeNotification || msg->type() == Protocol::Command::RelationChangeNotification) { notificationQueue.enqueue(msg); return 1; } // We have to split moves into insert or remove if the source or destination // is not monitored. if (!msg->isMove()) { notificationQueue.enqueue(msg); return 1; } bool sourceWatched = false; bool destWatched = false; if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); if (useRefCounting) { sourceWatched = isMonitored(itemNtf.parentCollection()); destWatched = isMonitored(itemNtf.parentDestCollection()); } else { if (!resources.isEmpty()) { sourceWatched = resources.contains(itemNtf.resource()); destWatched = isMoveDestinationResourceMonitored(itemNtf); } if (!sourceWatched) { sourceWatched = isCollectionMonitored(itemNtf.parentCollection()); } if (!destWatched) { destWatched = isCollectionMonitored(itemNtf.parentDestCollection()); } } } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); if (!resources.isEmpty()) { sourceWatched = resources.contains(colNtf.resource()); destWatched = isMoveDestinationResourceMonitored(colNtf); } if (!sourceWatched) { sourceWatched = isCollectionMonitored(colNtf.parentCollection()); } if (!destWatched) { destWatched = isCollectionMonitored(colNtf.parentDestCollection()); } } else { Q_ASSERT(false); return 0; } if (!sourceWatched && !destWatched) { return 0; } if ((sourceWatched && destWatched) || (!collectionMoveTranslationEnabled && msg->type() == Protocol::Command::CollectionChangeNotification)) { notificationQueue.enqueue(msg); return 1; } if (sourceWatched) { if (msg->type() == Protocol::Command::ItemChangeNotification) { auto removalMessage = Protocol::ItemChangeNotificationPtr::create( Protocol::cmdCast(msg)); removalMessage->setOperation(Protocol::ItemChangeNotification::Remove); removalMessage->setParentDestCollection(-1); notificationQueue.enqueue(removalMessage); return 1; } else { auto removalMessage = Protocol::CollectionChangeNotificationPtr::create( Protocol::cmdCast(msg)); removalMessage->setOperation(Protocol::CollectionChangeNotification::Remove); removalMessage->setParentDestCollection(-1); notificationQueue.enqueue(removalMessage); return 1; } } // Transform into an insertion if (msg->type() == Protocol::Command::ItemChangeNotification) { auto insertionMessage = Protocol::ItemChangeNotificationPtr::create( Protocol::cmdCast(msg)); insertionMessage->setOperation(Protocol::ItemChangeNotification::Add); insertionMessage->setParentCollection(insertionMessage->parentDestCollection()); insertionMessage->setParentDestCollection(-1); // We don't support batch insertion, so we have to do it one by one const auto split = splitMessage(*insertionMessage, false); for (const Protocol::ChangeNotificationPtr &insertion : split) { notificationQueue.enqueue(insertion); } return split.count(); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { auto insertionMessage = Protocol::CollectionChangeNotificationPtr::create( Protocol::cmdCast(msg)); insertionMessage->setOperation(Protocol::CollectionChangeNotification::Add); insertionMessage->setParentCollection(insertionMessage->parentDestCollection()); insertionMessage->setParentDestCollection(-1); notificationQueue.enqueue(insertionMessage); return 1; } Q_ASSERT(false); return 0; } void MonitorPrivate::handleCommands() { Q_Q(Monitor); CommandBufferLocker lock(&mCommandBuffer); CommandBufferNotifyBlocker notify(&mCommandBuffer); while (!mCommandBuffer.isEmpty()) { const auto cmd = mCommandBuffer.dequeue(); lock.unlock(); const auto command = cmd.command; if (command->isResponse()) { switch (command->type()) { case Protocol::Command::Hello: { qCDebug(AKONADICORE_LOG) << q_ptr << "Connected to notification bus"; QByteArray subname; if (!q->objectName().isEmpty()) { subname = q->objectName().toLatin1(); } else { subname = session->sessionId(); } subname += " - " + QByteArray::number(quintptr(q)); qCDebug(AKONADICORE_LOG) << q_ptr << "Subscribing as \"" << subname << "\""; auto subCmd = Protocol::CreateSubscriptionCommandPtr::create(subname, session->sessionId()); ntfConnection->sendCommand(2, subCmd); break; } case Protocol::Command::CreateSubscription: { auto msubCmd = Protocol::ModifySubscriptionCommandPtr::create(pendingModification); pendingModification = Protocol::ModifySubscriptionCommand(); ntfConnection->sendCommand(3, msubCmd); break; } case Protocol::Command::ModifySubscription: // TODO: Handle errors if (!monitorReady) { monitorReady = true; Q_EMIT q_ptr->monitorReady(); } break; default: qCWarning(AKONADICORE_LOG) << "Received an unexpected response on Notification stream: " << Protocol::debugString(command); break; } } else { switch (command->type()) { case Protocol::Command::ItemChangeNotification: case Protocol::Command::CollectionChangeNotification: case Protocol::Command::TagChangeNotification: case Protocol::Command::RelationChangeNotification: case Protocol::Command::SubscriptionChangeNotification: case Protocol::Command::DebugChangeNotification: slotNotify(command.staticCast()); break; default: qCWarning(AKONADICORE_LOG) << "Received an unexpected message on Notification stream:" << Protocol::debugString(command); break; } } lock.relock(); } } /* server notification --> ?accepted --> pendingNotifications --> ?dataAvailable --> emit | | x --> discard x --> pipeline fetchJobDone --> pipeline ?dataAvailable --> emit */ void MonitorPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) { int appendedMessages = 0; int modifiedMessages = 0; int erasedMessages = 0; invalidateCaches(msg); updatePendingStatistics(msg); bool needsSplit = true; bool supportsBatch = false; if (isLazilyIgnored(msg, true)) { return; } checkBatchSupport(msg, needsSplit, supportsBatch); const bool isModifyFlags = (msg->type() == Protocol::Command::ItemChangeNotification && Protocol::cmdCast(msg).operation() == Protocol::ItemChangeNotification::ModifyFlags); if (supportsBatch || (!needsSplit && !supportsBatch && !isModifyFlags) || msg->type() == Protocol::Command::CollectionChangeNotification) { // Make sure the batch msg is always queued before the split notifications const int oldSize = pendingNotifications.size(); const int appended = translateAndCompress(pendingNotifications, msg); if (appended > 0) { appendedMessages += appended; } else { ++modifiedMessages; } // translateAndCompress can remove an existing "modify" when msg is a "delete". // Or it can merge two ModifyFlags and return false. // We need to detect such removals, for ChangeRecorder. if (pendingNotifications.count() != oldSize + appended) { ++erasedMessages; // this count isn't exact, but it doesn't matter } } else if (needsSplit) { // If it's not queued at least make sure we fetch all the items from split // notifications in one go. if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto items = Protocol::cmdCast(msg).items(); itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(items), mItemFetchScope); } } // if the message contains more items, but we need to emit single-item notification, // split the message into one message per item and queue them // if the message contains only one item, but batches are not supported // (and thus neither is flagsModified), splitMessage() will convert the // notification to regular Modify with "FLAGS" part changed if (needsSplit || (!needsSplit && !supportsBatch && isModifyFlags)) { // Make sure inter-resource move notifications are translated into // Add/Remove notifications if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); if (itemNtf.operation() == Protocol::ItemChangeNotification::Move && itemNtf.resource() != itemNtf.destinationResource()) { if (needsSplit) { const Protocol::ChangeNotificationList split = splitMessage(itemNtf, !supportsBatch); for (const auto &splitMsg : split) { appendedMessages += translateAndCompress(pendingNotifications, splitMsg); } } else { appendedMessages += translateAndCompress(pendingNotifications, msg); } } else { const Protocol::ChangeNotificationList split = splitMessage(itemNtf, !supportsBatch); pendingNotifications << split.toList(); appendedMessages += split.count(); } } } // tell ChangeRecorder (even if 0 appended, the compression could have made changes to existing messages) if (appendedMessages > 0 || modifiedMessages > 0 || erasedMessages > 0) { if (erasedMessages > 0) { notificationsErased(); } else { notificationsEnqueued(appendedMessages); } } dispatchNotifications(); } void MonitorPrivate::flushPipeline() { while (!pipeline.isEmpty()) { const auto msg = pipeline.head(); if (ensureDataAvailable(msg)) { // dequeue should be before emit, otherwise stuff might happen (like dataAvailable // being called again) and we end up dequeuing an empty pipeline pipeline.dequeue(); emitNotification(msg); } else { break; } } } void MonitorPrivate::dataAvailable() { flushPipeline(); dispatchNotifications(); } void MonitorPrivate::dispatchNotifications() { // Note that this code is not used in a ChangeRecorder (pipelineSize==0) while (pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty()) { const auto msg = pendingNotifications.dequeue(); if (ensureDataAvailable(msg) && pipeline.isEmpty()) { emitNotification(msg); } else { pipeline.enqueue(msg); } } } static Relation::List extractRelations(const QSet &rels) { Relation::List relations; if (rels.isEmpty()) { return relations; } relations.reserve(rels.size()); for (const auto &rel : rels) { relations.push_back(Relation(rel.type.toLatin1(), Akonadi::Item(rel.leftId), Akonadi::Item(rel.rightId))); } return relations; } bool MonitorPrivate::emitItemsNotification(const Protocol::ItemChangeNotification &msg_, const Item::List &items, const Collection &collection, const Collection &collectionDest) { Protocol::ItemChangeNotification msg = msg_; Collection col = collection; Collection colDest = collectionDest; if (!col.isValid()) { col = Collection(msg.parentCollection()); col.setResource(QString::fromUtf8(msg.resource())); } if (!colDest.isValid()) { colDest = Collection(msg.parentDestCollection()); // HACK: destination resource is delivered in the parts field... if (!msg.itemParts().isEmpty()) { colDest.setResource(QString::fromLatin1(*(msg.itemParts().cbegin()))); } } const QSet addedFlags = msg.addedFlags(); const QSet removedFlags = msg.removedFlags(); Relation::List addedRelations, removedRelations; if (msg.operation() == Protocol::ItemChangeNotification::ModifyRelations) { addedRelations = extractRelations(msg.addedRelations()); removedRelations = extractRelations(msg.removedRelations()); } Tag::List addedTags, removedTags; if (msg.operation() == Protocol::ItemChangeNotification::ModifyTags) { addedTags = tagCache->retrieve(msg.addedTags().toList()); removedTags = tagCache->retrieve(msg.removedTags().toList()); } auto msgItems = msg.items(); Item::List its = items; QMutableVectorIterator iter(its); while (iter.hasNext()) { Item it = iter.next(); if (it.isValid()) { const auto msgItem = std::find_if(msgItems.begin(), msgItems.end(), [&it](const Protocol::ChangeNotification::Item &i) { return i.id == it.id(); }); if (msg.operation() == Protocol::ItemChangeNotification::Remove) { it.setRemoteId(msgItem->remoteId); it.setRemoteRevision(msgItem->remoteRevision); it.setMimeType(msgItem->mimeType); } else if (msg.operation() == Protocol::ItemChangeNotification::Move) { // For moves we remove the RID from the PimItemTable to prevent // RID conflict during merge (see T3904 in Phab), so restore the // RID from notification. // TODO: Should we do this for all items with empty RID? Right now // I only know about this usecase. it.setRemoteId(msgItem->remoteId); } if (!it.parentCollection().isValid()) { if (msg.operation() == Protocol::ItemChangeNotification::Move) { it.setParentCollection(colDest); } else { it.setParentCollection(col); } } else { // item has a valid parent collection, most likely due to retrieved ancestors // still, collection might contain extra info, so inject that if (it.parentCollection() == col) { const Collection oldParent = it.parentCollection(); if (oldParent.parentCollection().isValid() && !col.parentCollection().isValid()) { col.setParentCollection(oldParent.parentCollection()); // preserve ancestor chain } it.setParentCollection(col); } else { // If one client does a modify followed by a move we have to make sure that the // AgentBase::itemChanged() in another client always sees the parent collection // of the item before it has been moved. if (msg.operation() != Protocol::ItemChangeNotification::Move) { it.setParentCollection(col); } else { it.setParentCollection(colDest); } } } iter.setValue(it); msgItems.erase(msgItem); } else { // remove the invalid item iter.remove(); } } its.reserve(its.size() + msgItems.size()); // Now reconstruct any items there were left in msgItems Q_FOREACH (const Protocol::ItemChangeNotification::Item &msgItem, msgItems) { Item it(msgItem.id); it.setRemoteId(msgItem.remoteId); it.setRemoteRevision(msgItem.remoteRevision); it.setMimeType(msgItem.mimeType); if (msg.operation() == Protocol::ItemChangeNotification::Move) { it.setParentCollection(colDest); } else { it.setParentCollection(col); } its << it; } bool handled = false; switch (msg.operation()) { case Protocol::ItemChangeNotification::Add: if (q_ptr->receivers(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemAdded(its.first(), col); return true; } return false; case Protocol::ItemChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemChanged(its.first(), msg.itemParts()); return true; } return false; case Protocol::ItemChangeNotification::ModifyFlags: if (q_ptr->receivers(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))) > 0) { emit q_ptr->itemsFlagsChanged(its, msg.addedFlags(), msg.removedFlags()); handled = true; } return handled; case Protocol::ItemChangeNotification::Move: if (q_ptr->receivers(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemMoved(its.first(), col, colDest); handled = true; } if (q_ptr->receivers(SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection))) > 0) { emit q_ptr->itemsMoved(its, col, colDest); handled = true; } return handled; case Protocol::ItemChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(itemRemoved(Akonadi::Item))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemRemoved(its.first()); handled = true; } if (q_ptr->receivers(SIGNAL(itemsRemoved(Akonadi::Item::List))) > 0) { emit q_ptr->itemsRemoved(its); handled = true; } return handled; case Protocol::ItemChangeNotification::Link: if (q_ptr->receivers(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemLinked(its.first(), col); handled = true; } if (q_ptr->receivers(SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection))) > 0) { emit q_ptr->itemsLinked(its, col); handled = true; } return handled; case Protocol::ItemChangeNotification::Unlink: if (q_ptr->receivers(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemUnlinked(its.first(), col); handled = true; } if (q_ptr->receivers(SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection))) > 0) { emit q_ptr->itemsUnlinked(its, col); handled = true; } return handled; case Protocol::ItemChangeNotification::ModifyTags: if (q_ptr->receivers(SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet,QSet))) > 0) { emit q_ptr->itemsTagsChanged(its, Akonadi::vectorToSet(addedTags), Akonadi::vectorToSet(removedTags)); return true; } return false; case Protocol::ItemChangeNotification::ModifyRelations: if (q_ptr->receivers(SIGNAL(itemsRelationsChanged(Akonadi::Item::List,Akonadi::Relation::List,Akonadi::Relation::List))) > 0) { emit q_ptr->itemsRelationsChanged(its, addedRelations, removedRelations); return true; } return false; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in item change notification"; } return false; } bool MonitorPrivate::emitCollectionNotification(const Protocol::CollectionChangeNotification &msg, const Collection &col, const Collection &par, const Collection &dest) { Collection parent = par; if (!parent.isValid()) { parent = Collection(msg.parentCollection()); } Collection destination = dest; if (!destination.isValid()) { destination = Collection(msg.parentDestCollection()); } Collection collection = col; if (!collection.isValid() || msg.operation() == Protocol::CollectionChangeNotification::Remove) { collection = Collection(msg.id()); collection.setResource(QString::fromUtf8(msg.resource())); collection.setRemoteId(msg.remoteId()); } if (!collection.parentCollection().isValid()) { if (msg.operation() == Protocol::CollectionChangeNotification::Move) { collection.setParentCollection(destination); } else { collection.setParentCollection(parent); } } switch (msg.operation()) { case Protocol::CollectionChangeNotification::Add: if (q_ptr->receivers(SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection))) == 0) { return false; } emit q_ptr->collectionAdded(collection, parent); return true; case Protocol::CollectionChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(collectionChanged(Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(collectionChanged(Akonadi::Collection,QSet))) == 0) { return false; } emit q_ptr->collectionChanged(collection); emit q_ptr->collectionChanged(collection, msg.changedParts()); return true; case Protocol::CollectionChangeNotification::Move: if (q_ptr->receivers(SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))) == 0) { return false; } emit q_ptr->collectionMoved(collection, parent, destination); return true; case Protocol::CollectionChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(collectionRemoved(Akonadi::Collection))) == 0) { return false; } emit q_ptr->collectionRemoved(collection); return true; case Protocol::CollectionChangeNotification::Subscribe: if (q_ptr->receivers(SIGNAL(collectionSubscribed(Akonadi::Collection,Akonadi::Collection))) == 0) { return false; } if (!monitorAll) { // ### why?? emit q_ptr->collectionSubscribed(collection, parent); } return true; case Protocol::CollectionChangeNotification::Unsubscribe: if (q_ptr->receivers(SIGNAL(collectionUnsubscribed(Akonadi::Collection))) == 0) { return false; } if (!monitorAll) { // ### why?? emit q_ptr->collectionUnsubscribed(collection); } return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in collection change notification"; } return false; } bool MonitorPrivate::emitTagNotification(const Protocol::TagChangeNotification &msg, const Tag &tag) { Tag validTag; if (msg.operation() == Protocol::TagChangeNotification::Remove) { //In case of a removed signal the cache entry was already invalidated, and we therefore received an empty list of tags validTag = Tag(msg.id()); validTag.setRemoteId(msg.remoteId().toLatin1()); } else { validTag = tag; } if (!validTag.isValid()) { return false; } switch (msg.operation()) { case Protocol::TagChangeNotification::Add: if (q_ptr->receivers(SIGNAL(tagAdded(Akonadi::Tag))) == 0) { return false; } Q_EMIT q_ptr->tagAdded(validTag); return true; case Protocol::TagChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(tagChanged(Akonadi::Tag))) == 0) { return false; } Q_EMIT q_ptr->tagChanged(validTag); return true; case Protocol::TagChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(tagRemoved(Akonadi::Tag))) == 0) { return false; } Q_EMIT q_ptr->tagRemoved(validTag); return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; } return false; } bool MonitorPrivate::emitRelationNotification(const Protocol::RelationChangeNotification &msg, const Relation &relation) { if (!relation.isValid()) { return false; } switch (msg.operation()) { case Protocol::RelationChangeNotification::Add: if (q_ptr->receivers(SIGNAL(relationAdded(Akonadi::Relation))) == 0) { return false; } Q_EMIT q_ptr->relationAdded(relation); return true; case Protocol::RelationChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(relationRemoved(Akonadi::Relation))) == 0) { return false; } Q_EMIT q_ptr->relationRemoved(relation); return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; } return false; } bool MonitorPrivate::emitSubscriptionChangeNotification(const Protocol::SubscriptionChangeNotification &msg, const Akonadi::NotificationSubscriber &subscriber) { if (!subscriber.isValid()) { return false; } switch (msg.operation()) { case Protocol::SubscriptionChangeNotification::Add: if (q_ptr->receivers(SIGNAL(notificationSubscriberAdded(Akonadi::NotificationSubscriber))) == 0) { return false; } Q_EMIT q_ptr->notificationSubscriberAdded(subscriber); return true; case Protocol::SubscriptionChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(notificationSubscriberChanged(Akonadi::NotificationSubscriber))) == 0) { return false; } Q_EMIT q_ptr->notificationSubscriberChanged(subscriber); return true; case Protocol::SubscriptionChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(notificationSubscriberRemoved(Akonadi::NotificationSubscriber))) == 0) { return false; } Q_EMIT q_ptr->notificationSubscriberRemoved(subscriber); return true; default: break; } return false; } bool MonitorPrivate::emitDebugChangeNotification(const Protocol::DebugChangeNotification &msg, const ChangeNotification &ntf) { Q_UNUSED(msg); if (!ntf.isValid()) { return false; } if (q_ptr->receivers(SIGNAL(debugNotification(Akonadi::ChangeNotification))) == 0) { return false; } Q_EMIT q_ptr->debugNotification(ntf); return true; } void MonitorPrivate::invalidateCaches(const Protocol::ChangeNotificationPtr &msg) { // remove invalidates // modify removes the cache entry, as we need to re-fetch // And subscription modify the visibility of the collection by the collectionFetchScope. switch (msg->type()) { case Protocol::Command::CollectionChangeNotification: { const auto &colNtf = Protocol::cmdCast(msg); switch (colNtf.operation()) { case Protocol::CollectionChangeNotification::Modify: case Protocol::CollectionChangeNotification::Move: case Protocol::CollectionChangeNotification::Subscribe: collectionCache->update(colNtf.id(), mCollectionFetchScope); break; case Protocol::CollectionChangeNotification::Remove: collectionCache->invalidate(colNtf.id()); break; default: break; } } break; case Protocol::Command::ItemChangeNotification: { const auto &itemNtf = Protocol::cmdCast(msg); switch (itemNtf.operation()) { case Protocol::ItemChangeNotification::Modify: case Protocol::ItemChangeNotification::ModifyFlags: case Protocol::ItemChangeNotification::ModifyTags: case Protocol::ItemChangeNotification::ModifyRelations: case Protocol::ItemChangeNotification::Move: itemCache->update(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), mItemFetchScope); break; case Protocol::ItemChangeNotification::Remove: itemCache->invalidate(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); break; default: break; } } break; case Protocol::Command::TagChangeNotification: { const auto &tagNtf = Protocol::cmdCast(msg); switch (tagNtf.operation()) { case Protocol::TagChangeNotification::Modify: tagCache->update({ tagNtf.id() }, mTagFetchScope); break; case Protocol::TagChangeNotification::Remove: tagCache->invalidate({ tagNtf.id() }); break; default: break; } } break; default: break; } } void MonitorPrivate::invalidateCache(const Collection &col) { collectionCache->update(col.id(), mCollectionFetchScope); } void MonitorPrivate::ref(Collection::Id id) { if (!refCountMap.contains(id)) { refCountMap.insert(id, 0); } ++refCountMap[id]; if (m_buffer.isBuffered(id)) { m_buffer.purge(id); } } Akonadi::Collection::Id MonitorPrivate::deref(Collection::Id id) { Q_ASSERT(refCountMap.contains(id)); if (--refCountMap[id] == 0) { refCountMap.remove(id); return m_buffer.buffer(id); } return -1; } void MonitorPrivate::PurgeBuffer::purge(Collection::Id id) { m_buffer.removeOne(id); } Akonadi::Collection::Id MonitorPrivate::PurgeBuffer::buffer(Collection::Id id) { // Ensure that we don't put a duplicate @p id into the buffer. purge(id); Collection::Id bumpedId = -1; if (m_buffer.size() == MAXBUFFERSIZE) { bumpedId = m_buffer.dequeue(); purge(bumpedId); } m_buffer.enqueue(id); return bumpedId; } int MonitorPrivate::PurgeBuffer::buffersize() { return MAXBUFFERSIZE; } bool MonitorPrivate::isMonitored(Collection::Id colId) const { if (!useRefCounting) { return true; } return refCountMap.contains(colId) || m_buffer.isBuffered(colId); } void MonitorPrivate::notifyCollectionStatisticsWatchers(Collection::Id collection, const QByteArray &resource) { if (collection > 0 && (monitorAll || isCollectionMonitored(collection) || resources.contains(resource))) { recentlyChangedCollections.insert(collection); if (!statisticsCompressionTimer.isActive()) { statisticsCompressionTimer.start(); } } } // @endcond diff --git a/src/core/monitor_p.h b/src/core/monitor_p.h index b213cf92c..447058d99 100644 --- a/src/core/monitor_p.h +++ b/src/core/monitor_p.h @@ -1,332 +1,333 @@ /* Copyright (c) 2007 Tobias Koenig 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. */ #ifndef AKONADI_MONITOR_P_H #define AKONADI_MONITOR_P_H #include "akonadicore_export.h" #include "monitor.h" #include "collection.h" #include "collectionstatisticsjob.h" #include "collectionfetchscope.h" #include "item.h" #include "itemfetchscope.h" #include "tagfetchscope.h" #include "job.h" #include "entitycache_p.h" #include "servermanager.h" #include "changenotificationdependenciesfactory_p.h" #include "connection_p.h" #include "commandbuffer_p.h" #include "private/protocol_p.h" #include #include #include #include #include namespace Akonadi { class Monitor; class ChangeNotification; /** * @internal */ class AKONADICORE_EXPORT MonitorPrivate { public: MonitorPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, Monitor *parent); virtual ~MonitorPrivate(); void init(); Monitor *q_ptr; Q_DECLARE_PUBLIC(Monitor) ChangeNotificationDependenciesFactory *dependenciesFactory = nullptr; QPointer ntfConnection; Collection::List collections; QSet resources; QSet items; QSet tags; QSet types; QSet mimetypes; bool monitorAll; bool exclusive; QList sessions; ItemFetchScope mItemFetchScope; TagFetchScope mTagFetchScope; CollectionFetchScope mCollectionFetchScope; bool mFetchChangedOnly; Session *session = nullptr; CollectionCache *collectionCache = nullptr; ItemListCache *itemCache = nullptr; TagListCache *tagCache = nullptr; QMimeDatabase mimeDatabase; CommandBuffer mCommandBuffer; + Protocol::ModifySubscriptionCommand::ModifiedParts pendingModificationChanges; Protocol::ModifySubscriptionCommand pendingModification; QTimer *pendingModificationTimer; bool monitorReady; // The waiting list QQueue pendingNotifications; // The messages for which data is currently being fetched QQueue pipeline; // In a pure Monitor, the pipeline contains items that were dequeued from pendingNotifications. // The ordering [pipeline] [pendingNotifications] is kept at all times. // [] [A B C] -> [A B] [C] -> [B] [C] -> [B C] [] -> [C] [] -> [] // In a ChangeRecorder, the pipeline contains one item only, and not dequeued yet. // [] [A B C] -> [A] [A B C] -> [] [A B C] -> (changeProcessed) [] [B C] -> [B] [B C] etc... bool fetchCollection; bool fetchCollectionStatistics; bool collectionMoveTranslationEnabled; // Virtual methods for ChangeRecorder virtual void notificationsEnqueued(int) { } virtual void notificationsErased() { } // Virtual so it can be overridden in FakeMonitor. virtual bool connectToNotificationManager(); void disconnectFromNotificationManager(); void dispatchNotifications(); void flushPipeline(); bool ensureDataAvailable(const Protocol::ChangeNotificationPtr &msg); /** * Sends out the change notification @p msg. * @param msg the change notification to send * @return @c true if the notification was actually send to someone, @c false if no one was listening. */ virtual bool emitNotification(const Protocol::ChangeNotificationPtr &msg); void updatePendingStatistics(const Protocol::ChangeNotificationPtr &msg); void invalidateCaches(const Protocol::ChangeNotificationPtr &msg); /** Used by ResourceBase to inform us about collection changes before the notifications are emitted, needed to avoid the missing RID race on change replay. */ void invalidateCache(const Collection &col); /// Virtual so that ChangeRecorder can set it to 0 and handle the pipeline itself virtual int pipelineSize() const; // private Q_SLOTS void dataAvailable(); void slotSessionDestroyed(QObject *object); void slotStatisticsChangedFinished(KJob *job); void slotFlushRecentlyChangedCollections(); /** Returns whether a message was appended to @p notificationQueue */ int translateAndCompress(QQueue ¬ificationQueue, const Protocol::ChangeNotificationPtr &msg); void handleCommands(); virtual void slotNotify(const Protocol::ChangeNotificationPtr &msg); /** * Sends out a change notification for an item. * @return @c true if the notification was actually send to someone, @c false if no one was listening. */ bool emitItemsNotification(const Protocol::ItemChangeNotification &msg, const Item::List &items = Item::List(), const Collection &collection = Collection(), const Collection &collectionDest = Collection()); /** * Sends out a change notification for a collection. * @return @c true if the notification was actually send to someone, @c false if no one was listening. */ bool emitCollectionNotification(const Protocol::CollectionChangeNotification &msg, const Collection &col = Collection(), const Collection &par = Collection(), const Collection &dest = Collection()); bool emitTagNotification(const Protocol::TagChangeNotification &msg, const Tag &tags); bool emitRelationNotification(const Protocol::RelationChangeNotification &msg, const Relation &relation); bool emitSubscriptionChangeNotification(const Protocol::SubscriptionChangeNotification &msg, const NotificationSubscriber &subscriber); bool emitDebugChangeNotification(const Protocol::DebugChangeNotification &msg, const ChangeNotification &ntf); void serverStateChanged(Akonadi::ServerManager::State state); /** * This method is called by the ChangeMediator to enforce an invalidation of the passed collection. */ void invalidateCollectionCache(qint64 collectionId); /** * This method is called by the ChangeMediator to enforce an invalidation of the passed item. */ void invalidateItemCache(qint64 itemId); /** * This method is called by the ChangeMediator to enforce an invalidation of the passed tag. */ void invalidateTagCache(qint64 tagId); void scheduleSubscriptionUpdate(); void slotUpdateSubscription(); /** @brief Class used to determine when to purge items in a Collection The buffer method can be used to buffer a Collection. This may cause another Collection to be purged if it is removed from the buffer. The purge method is used to purge a Collection from the buffer, but not the model. This is used for example, to not buffer Collections anymore if they get referenced, and to ensure that one Collection does not appear twice in the buffer. Check whether a Collection is buffered using the isBuffered method. */ class AKONADI_TESTS_EXPORT PurgeBuffer { // Buffer the most recent 10 unreferenced Collections static const int MAXBUFFERSIZE = 10; public: explicit PurgeBuffer() { } /** Adds @p id to the Collections to be buffered @returns The collection id which was removed form the buffer or -1 if none. */ Collection::Id buffer(Collection::Id id); /** Removes @p id from the Collections being buffered */ void purge(Collection::Id id); bool isBuffered(Collection::Id id) const { return m_buffer.contains(id); } static int buffersize(); private: QQueue m_buffer; } m_buffer; QHash refCountMap; bool useRefCounting; void ref(Collection::Id id); Collection::Id deref(Collection::Id id); /** * Returns true if the collection is monitored by monitor. * * A collection is always monitored if useRefCounting is false. * If ref counting is used, the collection is only monitored, * if the collection is either in refCountMap or m_buffer. * If ref counting is used and the collection is not in refCountMap or m_buffer, * no updates for the contained items are emitted, because they are lazily ignored. */ bool isMonitored(Collection::Id colId) const; private: // collections that need a statistics update QSet recentlyChangedCollections; QTimer statisticsCompressionTimer; /** @returns True if @p msg should be ignored. Otherwise appropriate signals are emitted for it. */ bool isLazilyIgnored(const Protocol::ChangeNotificationPtr &msg, bool allowModifyFlagsConversion = false) const; /** Sets @p needsSplit to True when @p msg contains more than one item and there's at least one listener that does not support batch operations. Sets @p batchSupported to True when there's at least one listener that supports batch operations. */ void checkBatchSupport(const Protocol::ChangeNotificationPtr &msg, bool &needsSplit, bool &batchSupported) const; Protocol::ChangeNotificationList splitMessage(const Protocol::ItemChangeNotification &msg, bool legacy) const; bool isCollectionMonitored(Collection::Id collection) const { if (collection < 0) { return false; } if (collections.contains(Collection(collection))) { return true; } if (collections.contains(Collection::root())) { return true; } return false; } bool isMimeTypeMonitored(const QString &mimetype) const { if (mimetypes.contains(mimetype)) { return true; } const QMimeType mimeType = mimeDatabase.mimeTypeForName(mimetype); if (!mimeType.isValid()) { return false; } for (const QString &mt : mimetypes) { if (mimeType.inherits(mt)) { return true; } } return false; } template bool isMoveDestinationResourceMonitored(const T &msg) const { if (msg.operation() != T::Move) { return false; } return resources.contains(msg.destinationResource()); } void fetchStatistics(Collection::Id colId) { CollectionStatisticsJob *job = new CollectionStatisticsJob(Collection(colId), session); QObject::connect(job, SIGNAL(result(KJob *)), q_ptr, SLOT(slotStatisticsChangedFinished(KJob *))); } void notifyCollectionStatisticsWatchers(Collection::Id collection, const QByteArray &resource); bool fetchCollections() const; bool fetchItems() const; }; } #endif diff --git a/src/core/notificationsubscriber.cpp b/src/core/notificationsubscriber.cpp index 15dbdf5fb..3b0283195 100644 --- a/src/core/notificationsubscriber.cpp +++ b/src/core/notificationsubscriber.cpp @@ -1,198 +1,211 @@ /* Copyright (c) 2016 Daniel Vrátil 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 "notificationsubscriber.h" +#include "itemfetchscope.h" namespace Akonadi { class AKONADICORE_NO_EXPORT NotificationSubscriber::Private : public QSharedData { public: explicit Private() : QSharedData() , isAllMonitored(false) , isExclusive(false) {} Private(const Private &other) : QSharedData(other) , subscriber(other.subscriber) , sessionId(other.sessionId) , collections(other.collections) , items(other.items) , tags(other.tags) , types(other.types) , mimeTypes(other.mimeTypes) , resources(other.resources) , ignoredSessions(other.ignoredSessions) + , itemFetchScope(other.itemFetchScope) , isAllMonitored(other.isAllMonitored) , isExclusive(other.isExclusive) {} QByteArray subscriber; QByteArray sessionId; QSet collections; QSet items; QSet tags; QSet types; QSet mimeTypes; QSet resources; QSet ignoredSessions; + ItemFetchScope itemFetchScope; bool isAllMonitored; bool isExclusive; }; } using namespace Akonadi; NotificationSubscriber::NotificationSubscriber() : d(new Private) { } NotificationSubscriber::NotificationSubscriber(const NotificationSubscriber &other) : d(other.d) { } NotificationSubscriber::~NotificationSubscriber() { } NotificationSubscriber &NotificationSubscriber::operator=(const NotificationSubscriber &other) { d = other.d; return *this; } bool NotificationSubscriber::isValid() const { return !d->subscriber.isEmpty(); } QByteArray NotificationSubscriber::subscriber() const { return d->subscriber; } void NotificationSubscriber::setSubscriber(const QByteArray &subscriber) { d->subscriber = subscriber; } QByteArray NotificationSubscriber::sessionId() const { return d->sessionId; } void NotificationSubscriber::setSessionId(const QByteArray &sessionId) { d->sessionId = sessionId; } QSet NotificationSubscriber::monitoredCollections() const { return d->collections; } void NotificationSubscriber::setMonitoredCollections(const QSet &collections) { d->collections = collections; } QSet NotificationSubscriber::monitoredItems() const { return d->items; } void NotificationSubscriber::setMonitoredItems(const QSet &items) { d->items = items; } QSet NotificationSubscriber::monitoredTags() const { return d->tags; } void NotificationSubscriber::setMonitoredTags(const QSet &tags) { d->tags = tags; } QSet NotificationSubscriber::monitoredTypes() const { return d->types; } void NotificationSubscriber::setMonitoredTypes(const QSet &types) { d->types = types; } QSet NotificationSubscriber::monitoredMimeTypes() const { return d->mimeTypes; } void NotificationSubscriber::setMonitoredMimeTypes(const QSet &mimeTypes) { d->mimeTypes = mimeTypes; } QSet NotificationSubscriber::monitoredResources() const { return d->resources; } void NotificationSubscriber::setMonitoredResources(const QSet &resources) { d->resources = resources; } QSet NotificationSubscriber::ignoredSessions() const { return d->ignoredSessions; } void NotificationSubscriber::setIgnoredSessions(const QSet &ignoredSessions) { d->ignoredSessions = ignoredSessions; } bool NotificationSubscriber::isAllMonitored() const { return d->isAllMonitored; } void NotificationSubscriber::setIsAllMonitored(bool isAllMonitored) { d->isAllMonitored = isAllMonitored; } bool NotificationSubscriber::isExclusive() const { return d->isExclusive; } void NotificationSubscriber::setIsExclusive(bool isExclusive) { d->isExclusive = isExclusive; } + +ItemFetchScope NotificationSubscriber::itemFetchScope() const +{ + return d->itemFetchScope; +} + +void NotificationSubscriber::setItemFetchScope(const ItemFetchScope &itemFetchScope) +{ + d->itemFetchScope = itemFetchScope; +} diff --git a/src/core/notificationsubscriber.h b/src/core/notificationsubscriber.h index a7fb489a7..517bece0a 100644 --- a/src/core/notificationsubscriber.h +++ b/src/core/notificationsubscriber.h @@ -1,81 +1,84 @@ /* Copyright (c) 2016 Daniel Vrátil 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. */ #ifndef AKONADI_NOTIFICATIONSUBSCRIBER_H #define AKONADI_NOTIFICATIONSUBSCRIBER_H #include #include "monitor.h" #include namespace Akonadi { class AKONADICORE_EXPORT NotificationSubscriber { public: explicit NotificationSubscriber(); NotificationSubscriber(const NotificationSubscriber &other); ~NotificationSubscriber(); NotificationSubscriber &operator=(const NotificationSubscriber &other); bool isValid() const; QByteArray subscriber() const; void setSubscriber(const QByteArray &subscriber); QByteArray sessionId() const; void setSessionId(const QByteArray &sessionId); QSet monitoredCollections() const; void setMonitoredCollections(const QSet &collections); QSet monitoredItems() const; void setMonitoredItems(const QSet &items); QSet monitoredTags() const; void setMonitoredTags(const QSet &tags); QSet monitoredTypes() const; void setMonitoredTypes(const QSet &type); QSet monitoredMimeTypes() const; void setMonitoredMimeTypes(const QSet &mimeTypes); QSet monitoredResources() const; void setMonitoredResources(const QSet &resources); QSet ignoredSessions() const; void setIgnoredSessions(const QSet &ignoredSessions); bool isAllMonitored() const; void setIsAllMonitored(bool isAllMonitored); bool isExclusive() const; void setIsExclusive(bool isExclusive); + ItemFetchScope itemFetchScope() const; + void setItemFetchScope(const ItemFetchScope &itemFetchScope); + private: class Private; QSharedDataPointer d; }; } #endif diff --git a/src/core/protocolhelper.cpp b/src/core/protocolhelper.cpp index 3224b5186..c60f7bac7 100644 --- a/src/core/protocolhelper.cpp +++ b/src/core/protocolhelper.cpp @@ -1,581 +1,652 @@ /* Copyright (c) 2008 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 "protocolhelper_p.h" #include "akonadicore_debug.h" #include "attributefactory.h" #include "collectionstatistics.h" #include "item_p.h" #include "collection_p.h" #include "exceptionbase.h" #include "itemserializer_p.h" #include "itemserializerplugin.h" #include "servermanager.h" #include "tagfetchscope.h" #include "persistentsearchattribute.h" #include "private/protocol_p.h" #include "private/externalpartstorage_p.h" #include #include using namespace Akonadi; CachePolicy ProtocolHelper::parseCachePolicy(const Protocol::CachePolicy &policy) { CachePolicy cp; cp.setCacheTimeout(policy.cacheTimeout()); cp.setIntervalCheckTime(policy.checkInterval()); cp.setInheritFromParent(policy.inherit()); cp.setSyncOnDemand(policy.syncOnDemand()); cp.setLocalParts(policy.localParts()); return cp; } Protocol::CachePolicy ProtocolHelper::cachePolicyToProtocol(const CachePolicy &policy) { Protocol::CachePolicy proto; proto.setCacheTimeout(policy.cacheTimeout()); proto.setCheckInterval(policy.intervalCheckTime()); proto.setInherit(policy.inheritFromParent()); proto.setSyncOnDemand(policy.syncOnDemand()); proto.setLocalParts(policy.localParts()); return proto; } template inline static void parseAttributesImpl(const Protocol::Attributes &attributes, T *entity) { for (auto iter = attributes.cbegin(), end = attributes.cend(); iter != end; ++iter) { Attribute *attribute = AttributeFactory::createAttribute(iter.key()); if (!attribute) { qCWarning(AKONADICORE_LOG) << "Warning: unknown attribute" << iter.key(); continue; } attribute->deserialize(iter.value()); entity->addAttribute(attribute); } } template inline static void parseAncestorsCachedImpl(const QVector &ancestors, T *entity, Collection::Id parentCollection, ProtocolHelperValuePool *pool) { if (!pool || parentCollection == -1) { // if no pool or parent collection id is provided we can't cache anything, so continue as usual ProtocolHelper::parseAncestors(ancestors, entity); return; } if (pool->ancestorCollections.contains(parentCollection)) { // ancestor chain is cached already, so use the cached value entity->setParentCollection(pool->ancestorCollections.value(parentCollection)); } else { // not cached yet, parse the chain ProtocolHelper::parseAncestors(ancestors, entity); pool->ancestorCollections.insert(parentCollection, entity->parentCollection()); } } template inline static Protocol::Attributes attributesToProtocolImpl(const T &entity, bool ns) { Protocol::Attributes attributes; Q_FOREACH (const Attribute *attr, entity.attributes()) { attributes.insert(ProtocolHelper::encodePartIdentifier(ns ? ProtocolHelper::PartAttribute : ProtocolHelper::PartGlobal, attr->type()), attr->serialized()); } return attributes; } void ProtocolHelper::parseAncestorsCached(const QVector &ancestors, Item *item, Collection::Id parentCollection, ProtocolHelperValuePool *pool) { parseAncestorsCachedImpl(ancestors, item, parentCollection, pool); } void ProtocolHelper::parseAncestorsCached(const QVector &ancestors, Collection *collection, Collection::Id parentCollection, ProtocolHelperValuePool *pool) { parseAncestorsCachedImpl(ancestors, collection, parentCollection, pool); } void ProtocolHelper::parseAncestors(const QVector &ancestors, Item *item) { Collection fakeCollection; parseAncestors(ancestors, &fakeCollection); item->setParentCollection(fakeCollection.parentCollection()); } void ProtocolHelper::parseAncestors(const QVector &ancestors, Collection *collection) { static const Collection::Id rootCollectionId = Collection::root().id(); QList parentIds; Collection *current = collection; for (const Protocol::Ancestor &ancestor : ancestors) { if (ancestor.id() == rootCollectionId) { current->setParentCollection(Collection::root()); break; } Akonadi::Collection parentCollection(ancestor.id()); parentCollection.setName(ancestor.name()); parentCollection.setRemoteId(ancestor.remoteId()); parseAttributesImpl(ancestor.attributes(), &parentCollection); current->setParentCollection(parentCollection); current = ¤t->parentCollection(); } } static Collection::ListPreference parsePreference(Tristate value) { switch (value) { case Tristate::True: return Collection::ListEnabled; case Tristate::False: return Collection::ListDisabled; case Tristate::Undefined: return Collection::ListDefault; } Q_ASSERT(false); return Collection::ListDefault; } CollectionStatistics ProtocolHelper::parseCollectionStatistics(const Protocol::FetchCollectionStatsResponse &stats) { CollectionStatistics cs; cs.setCount(stats.count()); cs.setSize(stats.size()); cs.setUnreadCount(stats.unseen()); return cs; } void ProtocolHelper::parseAttributes(const Protocol::Attributes &attributes, Item *item) { parseAttributesImpl(attributes, item); } void ProtocolHelper::parseAttributes(const Protocol::Attributes &attributes, Collection *collection) { parseAttributesImpl(attributes, collection); } void ProtocolHelper::parseAttributes(const Protocol::Attributes &attributes, Tag *tag) { parseAttributesImpl(attributes, tag); } Protocol::Attributes ProtocolHelper::attributesToProtocol(const Item &item, bool ns) { return attributesToProtocolImpl(item, ns); } Protocol::Attributes ProtocolHelper::attributesToProtocol(const Collection &collection, bool ns) { return attributesToProtocolImpl(collection, ns); } Protocol::Attributes ProtocolHelper::attributesToProtocol(const Tag &tag, bool ns) { return attributesToProtocolImpl(tag, ns); } Collection ProtocolHelper::parseCollection(const Protocol::FetchCollectionsResponse &data, bool requireParent) { Collection collection(data.id()); if (requireParent) { collection.setParentCollection(Collection(data.parentId())); } collection.setName(data.name()); collection.setRemoteId(data.remoteId()); collection.setRemoteRevision(data.remoteRevision()); collection.setResource(data.resource()); collection.setContentMimeTypes(data.mimeTypes()); collection.setVirtual(data.isVirtual()); collection.setStatistics(parseCollectionStatistics(data.statistics())); collection.setCachePolicy(parseCachePolicy(data.cachePolicy())); parseAncestors(data.ancestors(), &collection); collection.setEnabled(data.enabled()); collection.setLocalListPreference(Collection::ListDisplay, parsePreference(data.displayPref())); collection.setLocalListPreference(Collection::ListIndex, parsePreference(data.indexPref())); collection.setLocalListPreference(Collection::ListSync, parsePreference(data.syncPref())); collection.setReferenced(data.referenced()); if (!data.searchQuery().isEmpty()) { auto attr = collection.attribute(Collection::AddIfMissing); attr->setQueryString(data.searchQuery()); QVector cols; cols.reserve(data.searchCollections().size()); foreach (auto id, data.searchCollections()) { cols.push_back(Collection(id)); } attr->setQueryCollections(cols); } parseAttributes(data.attributes(), &collection); collection.d_ptr->resetChangeLog(); return collection; } QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray &label) { switch (ns) { case PartGlobal: return label; case PartPayload: return "PLD:" + label; case PartAttribute: return "ATR:" + label; default: Q_ASSERT(false); } return QByteArray(); } QByteArray ProtocolHelper::decodePartIdentifier(const QByteArray &data, PartNamespace &ns) { if (data.startsWith("PLD:")) { //krazy:exclude=strings ns = PartPayload; return data.mid(4); } else if (data.startsWith("ATR:")) { //krazy:exclude=strings ns = PartAttribute; return data.mid(4); } else { ns = PartGlobal; return data; } } Protocol::ScopeContext ProtocolHelper::commandContextToProtocol(const Akonadi::Collection &collection, const Akonadi::Tag &tag, const Item::List &requestedItems) { Protocol::ScopeContext ctx; if (tag.isValid()) { ctx.setContext(Protocol::ScopeContext::Tag, tag.id()); } if (collection == Collection::root()) { if (requestedItems.isEmpty() && !tag.isValid()) { // collection content listing throw Exception("Cannot perform item operations on root collection."); } } else { if (collection.isValid()) { ctx.setContext(Protocol::ScopeContext::Collection, collection.id()); } else if (!collection.remoteId().isEmpty()) { ctx.setContext(Protocol::ScopeContext::Collection, collection.remoteId()); } } return ctx; } Scope ProtocolHelper::hierarchicalRidToScope(const Collection &col) { if (col == Collection::root()) { return Scope({ Scope::HRID(0) }); } if (col.remoteId().isEmpty()) { return Scope(); } QVector chain; Collection c = col; while (!c.remoteId().isEmpty()) { chain.append(Scope::HRID(c.id(), c.remoteId())); c = c.parentCollection(); } return Scope(chain + QVector { Scope::HRID(0) }); } Scope ProtocolHelper::hierarchicalRidToScope(const Item &item) { return Scope(QVector({ Scope::HRID(item.id(), item.remoteId()) }) + hierarchicalRidToScope(item.parentCollection()).hridChain()); } -Protocol::FetchScope ProtocolHelper::itemFetchScopeToProtocol(const ItemFetchScope &fetchScope) +Protocol::ItemFetchScope ProtocolHelper::itemFetchScopeToProtocol(const ItemFetchScope &fetchScope) { - Protocol::FetchScope fs; + Protocol::ItemFetchScope fs; QVector parts; parts.reserve(fetchScope.payloadParts().size() + fetchScope.attributes().size()); Q_FOREACH (const QByteArray &part, fetchScope.payloadParts()) { parts << ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, part); } Q_FOREACH (const QByteArray &part, fetchScope.attributes()) { parts << ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartAttribute, part); } fs.setRequestedParts(parts); // The default scope - fs.setFetch(Protocol::FetchScope::Flags | - Protocol::FetchScope::Size | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::MTime); - - fs.setFetch(Protocol::FetchScope::FullPayload, fetchScope.fullPayload()); - fs.setFetch(Protocol::FetchScope::AllAttributes, fetchScope.allAttributes()); - fs.setFetch(Protocol::FetchScope::CacheOnly, fetchScope.cacheOnly()); - fs.setFetch(Protocol::FetchScope::CheckCachedPayloadPartsOnly, fetchScope.checkForCachedPayloadPartsOnly()); - fs.setFetch(Protocol::FetchScope::IgnoreErrors, fetchScope.ignoreRetrievalErrors()); - if (fetchScope.ancestorRetrieval() != ItemFetchScope::None) { - switch (fetchScope.ancestorRetrieval()) { - case ItemFetchScope::Parent: - fs.setAncestorDepth(Protocol::FetchScope::ParentAncestor); - break; - case ItemFetchScope::All: - fs.setAncestorDepth(Protocol::FetchScope::AllAncestors); - break; - default: - Q_ASSERT(false); - } - } else { - fs.setAncestorDepth(Protocol::FetchScope::NoAncestor); + fs.setFetch(Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::MTime); + + fs.setFetch(Protocol::ItemFetchScope::FullPayload, fetchScope.fullPayload()); + fs.setFetch(Protocol::ItemFetchScope::AllAttributes, fetchScope.allAttributes()); + fs.setFetch(Protocol::ItemFetchScope::CacheOnly, fetchScope.cacheOnly()); + fs.setFetch(Protocol::ItemFetchScope::CheckCachedPayloadPartsOnly, fetchScope.checkForCachedPayloadPartsOnly()); + fs.setFetch(Protocol::ItemFetchScope::IgnoreErrors, fetchScope.ignoreRetrievalErrors()); + switch (fetchScope.ancestorRetrieval()) { + case ItemFetchScope::Parent: + fs.setAncestorDepth(Protocol::ItemFetchScope::ParentAncestor); + break; + case ItemFetchScope::All: + fs.setAncestorDepth(Protocol::ItemFetchScope::AllAncestors); + break; + case ItemFetchScope::None: + fs.setAncestorDepth(Protocol::ItemFetchScope::NoAncestor); + break; + default: + Q_ASSERT(false); + break; } if (fetchScope.fetchChangedSince().isValid()) { fs.setChangedSince(fetchScope.fetchChangedSince()); } - fs.setFetch(Protocol::FetchScope::RemoteID, fetchScope.fetchRemoteIdentification()); - fs.setFetch(Protocol::FetchScope::RemoteRevision, fetchScope.fetchRemoteIdentification()); - fs.setFetch(Protocol::FetchScope::GID, fetchScope.fetchGid()); + fs.setFetch(Protocol::ItemFetchScope::RemoteID, fetchScope.fetchRemoteIdentification()); + fs.setFetch(Protocol::ItemFetchScope::RemoteRevision, fetchScope.fetchRemoteIdentification()); + fs.setFetch(Protocol::ItemFetchScope::GID, fetchScope.fetchGid()); if (fetchScope.fetchTags()) { - fs.setFetch(Protocol::FetchScope::Tags); + fs.setFetch(Protocol::ItemFetchScope::Tags); if (!fetchScope.tagFetchScope().fetchIdOnly()) { if (fetchScope.tagFetchScope().attributes().isEmpty()) { fs.setTagFetchScope({ "ALL" }); } else { fs.setTagFetchScope(fetchScope.tagFetchScope().attributes()); } } } - fs.setFetch(Protocol::FetchScope::VirtReferences, fetchScope.fetchVirtualReferences()); - fs.setFetch(Protocol::FetchScope::MTime, fetchScope.fetchModificationTime()); - fs.setFetch(Protocol::FetchScope::Relations, fetchScope.fetchRelations()); + fs.setFetch(Protocol::ItemFetchScope::VirtReferences, fetchScope.fetchVirtualReferences()); + fs.setFetch(Protocol::ItemFetchScope::MTime, fetchScope.fetchModificationTime()); + fs.setFetch(Protocol::ItemFetchScope::Relations, fetchScope.fetchRelations()); return fs; } +ItemFetchScope ProtocolHelper::parseItemFetchScope(const Protocol::ItemFetchScope &fetchScope) +{ + ItemFetchScope ifs; + Q_FOREACH (const auto &part, fetchScope.requestedParts()) { + if (part.startsWith("PLD:")) { + ifs.fetchPayloadPart(part.mid(4), true); + } else if (part.startsWith("ATR:")) { + ifs.fetchAttribute(part.mid(4), true); + } + } + + if (fetchScope.fetch(Protocol::ItemFetchScope::FullPayload)) { + ifs.fetchFullPayload(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::AllAttributes)) { + ifs.fetchAllAttributes(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::CacheOnly)) { + ifs.setCacheOnly(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::CheckCachedPayloadPartsOnly)) { + ifs.setCheckForCachedPayloadPartsOnly(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::IgnoreErrors)) { + ifs.setIgnoreRetrievalErrors(true); + } + switch (fetchScope.ancestorDepth()) { + case Protocol::Ancestor::ParentAncestor: + ifs.setAncestorRetrieval(ItemFetchScope::Parent); + break; + case Protocol::Ancestor::AllAncestors: + ifs.setAncestorRetrieval(ItemFetchScope::All); + break; + default: + ifs.setAncestorRetrieval(ItemFetchScope::None); + break; + } + if (fetchScope.changedSince().isValid()) { + ifs.setFetchChangedSince(fetchScope.changedSince()); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::RemoteID) || fetchScope.fetch(Protocol::ItemFetchScope::RemoteRevision)) { + ifs.setFetchRemoteIdentification(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::GID)) { + ifs.setFetchGid(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::Tags)) { + ifs.setFetchTags(true); + const auto tfs = fetchScope.tagFetchScope(); + if (tfs.isEmpty()) { + ifs.tagFetchScope().setFetchIdOnly(true); + } else if (QSet({ "ALL" }) != tfs) { + for (const auto &attr : tfs) { + ifs.tagFetchScope().fetchAttribute(attr, true); + } + } + } + if (fetchScope.fetch(Protocol::ItemFetchScope::VirtReferences)) { + ifs.setFetchVirtualReferences(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::MTime)) { + ifs.setFetchModificationTime(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::Relations)) { + ifs.setFetchRelations(true); + } + + return ifs; +} + + static Item::Flags convertFlags(const QVector &flags, ProtocolHelperValuePool *valuePool) { #if __cplusplus >= 201103L || defined(__GNUC__) || defined(__clang__) // When the compiler supports thread-safe static initialization (mandated by the C++11 memory model) // then use it to share the common case of a single-item set only containing the \SEEN flag. // NOTE: GCC and clang has threadsafe static initialization for some time now, even without C++11. if (flags.size() == 1 && flags.first() == "\\SEEN") { static const Item::Flags sharedSeen = Item::Flags() << QByteArray("\\SEEN"); return sharedSeen; } #endif Item::Flags convertedFlags; convertedFlags.reserve(flags.size()); for (const QByteArray &flag : flags) { if (valuePool) { convertedFlags.insert(valuePool->flagPool.sharedValue(flag)); } else { convertedFlags.insert(flag); } } return convertedFlags; } Item ProtocolHelper::parseItemFetchResult(const Protocol::FetchItemsResponse &data, ProtocolHelperValuePool *valuePool) { Item item; item.setId(data.id()); item.setRevision(data.revision()); item.setRemoteId(data.remoteId()); item.setRemoteRevision(data.remoteRevision()); item.setGid(data.gid()); item.setStorageCollectionId(data.parentId()); if (valuePool) { item.setMimeType(valuePool->mimeTypePool.sharedValue(data.mimeType())); } else { item.setMimeType(data.mimeType()); } if (!item.isValid()) { return Item(); } item.setFlags(convertFlags(data.flags(), valuePool)); if (!data.tags().isEmpty()) { Tag::List tags; tags.reserve(data.tags().size()); Q_FOREACH (const Protocol::FetchTagsResponse &tag, data.tags()) { tags.append(parseTagFetchResult(tag)); } item.setTags(tags); } if (!data.relations().isEmpty()) { Relation::List relations; relations.reserve(data.relations().size()); Q_FOREACH (const Protocol::FetchRelationsResponse &rel, data.relations()) { relations.append(parseRelationFetchResult(rel)); } item.d_ptr->mRelations = relations; } if (!data.virtualReferences().isEmpty()) { Collection::List virtRefs; virtRefs.reserve(data.virtualReferences().size()); Q_FOREACH (qint64 colId, data.virtualReferences()) { virtRefs.append(Collection(colId)); } item.setVirtualReferences(virtRefs); } if (!data.cachedParts().isEmpty()) { QSet cp; cp.reserve(data.cachedParts().size()); Q_FOREACH (const QByteArray &ba, data.cachedParts()) { cp.insert(ba); } item.setCachedPayloadParts(cp); } item.setSize(data.size()); item.setModificationTime(data.mTime()); parseAncestorsCached(data.ancestors(), &item, data.parentId(), valuePool); Q_FOREACH (const Protocol::StreamPayloadResponse &part, data.parts()) { ProtocolHelper::PartNamespace ns; const QByteArray plainKey = decodePartIdentifier(part.payloadName(), ns); const auto metaData = part.metaData(); switch (ns) { case ProtocolHelper::PartPayload: ItemSerializer::deserialize(item, plainKey, part.data(), metaData.version(), static_cast(metaData.storageType())); if (metaData.storageType() == Protocol::PartMetaData::Foreign) { item.d_ptr->mPayloadPath = QString::fromUtf8(part.data()); } break; case ProtocolHelper::PartAttribute: { Attribute *attr = AttributeFactory::createAttribute(plainKey); Q_ASSERT(attr); if (metaData.storageType() == Protocol::PartMetaData::External) { const QString filename = ExternalPartStorage::resolveAbsolutePath(part.data()); QFile file(filename); if (file.open(QFile::ReadOnly)) { attr->deserialize(file.readAll()); } else { qCWarning(AKONADICORE_LOG) << "Failed to open attribute file: " << filename; delete attr; attr = nullptr; } } else { attr->deserialize(part.data()); } if (attr) { item.addAttribute(attr); } break; } case ProtocolHelper::PartGlobal: default: qCWarning(AKONADICORE_LOG) << "Unknown item part type:" << part.payloadName(); } } item.d_ptr->resetChangeLog(); return item; } Tag ProtocolHelper::parseTagFetchResult(const Protocol::FetchTagsResponse &data) { Tag tag; tag.setId(data.id()); tag.setGid(data.gid()); tag.setRemoteId(data.remoteId()); tag.setType(data.type()); tag.setParent(data.parentId() > 0 ? Tag(data.parentId()) : Tag()); parseAttributes(data.attributes(), &tag); return tag; } Relation ProtocolHelper::parseRelationFetchResult(const Protocol::FetchRelationsResponse &data) { Relation relation; relation.setLeft(Item(data.left())); relation.setRight(Item(data.right())); relation.setRemoteId(data.remoteId()); relation.setType(data.type()); return relation; } bool ProtocolHelper::streamPayloadToFile(const QString &fileName, const QByteArray &data, QByteArray &error) { const QString filePath = ExternalPartStorage::resolveAbsolutePath(fileName); qCDebug(AKONADICORE_LOG) << filePath << fileName; if (!filePath.startsWith(ExternalPartStorage::akonadiStoragePath())) { qCWarning(AKONADICORE_LOG) << "Invalid file path" << fileName; error = "Invalid file path"; return false; } QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(AKONADICORE_LOG) << "Failed to open destination payload file" << file.errorString(); error = "Failed to store payload into file"; return false; } if (file.write(data) != data.size()) { qCWarning(AKONADICORE_LOG) << "Failed to write all payload data to file"; error = "Failed to store payload into file"; return false; } qCDebug(AKONADICORE_LOG) << "Wrote" << data.size() << "bytes to " << file.fileName(); // Make sure stuff is written to disk file.close(); return true; } Akonadi::Tristate ProtocolHelper::listPreference(Collection::ListPreference pref) { switch (pref) { case Collection::ListEnabled: return Tristate::True; case Collection::ListDisabled: return Tristate::False; case Collection::ListDefault: return Tristate::Undefined; } Q_ASSERT(false); return Tristate::Undefined; } diff --git a/src/core/protocolhelper_p.h b/src/core/protocolhelper_p.h index 593404056..75c557233 100644 --- a/src/core/protocolhelper_p.h +++ b/src/core/protocolhelper_p.h @@ -1,339 +1,340 @@ /* Copyright (c) 2008 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. */ #ifndef AKONADI_PROTOCOLHELPER_P_H #define AKONADI_PROTOCOLHELPER_P_H #include "cachepolicy.h" #include "collection.h" #include "collectionutils.h" #include "item.h" #include "itemfetchscope.h" #include "sharedvaluepool_p.h" #include "tag.h" #include "private/imapparser_p.h" #include "private/protocol_p.h" #include "private/scope_p.h" #include "private/tristate_p.h" #include #include #include #include #include namespace Akonadi { struct ProtocolHelperValuePool { typedef Internal::SharedValuePool FlagPool; typedef Internal::SharedValuePool MimeTypePool; FlagPool flagPool; MimeTypePool mimeTypePool; QHash ancestorCollections; }; /** @internal Helper methods for converting between libakonadi objects and their protocol representation. @todo Add unit tests for this. @todo Use exceptions for a useful error handling */ class ProtocolHelper { public: /** Part namespaces. */ enum PartNamespace { PartGlobal, PartPayload, PartAttribute }; /** Parse a cache policy definition. @param policy The parsed cache policy. @returns Akonadi::CachePolicy */ static CachePolicy parseCachePolicy(const Protocol::CachePolicy &policy); /** Convert a cache policy object into its protocol representation. */ static Protocol::CachePolicy cachePolicyToProtocol(const CachePolicy &policy); /** Convert a ancestor chain from its protocol representation into an Item object. */ static void parseAncestors(const QVector &ancestors, Item *item); /** Convert a ancestor chain from its protocol representation into a Collection object. */ static void parseAncestors(const QVector &ancestors, Collection *collection); /** Convert a ancestor chain from its protocol representation into an Item object. This method allows to pass a @p valuePool which acts as cache, so ancestor paths for the same @p parentCollection don't have to be parsed twice. */ static void parseAncestorsCached(const QVector &ancestors, Item *item, Collection::Id parentCollection, ProtocolHelperValuePool *valuePool = nullptr); /** Convert a ancestor chain from its protocol representation into an Collection object. This method allows to pass a @p valuePool which acts as cache, so ancestor paths for the same @p parentCollection don't have to be parsed twice. */ static void parseAncestorsCached(const QVector &ancestors, Collection *collection, Collection::Id parentCollection, ProtocolHelperValuePool *valuePool = nullptr); /** Parse a collection description. @param data The input data. @param requireParent Whether or not we require a parent as part of the data. @returns The parsed collection */ static Collection parseCollection(const Protocol::FetchCollectionsResponse &data, bool requireParent = true); static void parseAttributes(const Protocol::Attributes &attributes, Item *item); static void parseAttributes(const Protocol::Attributes &attributes, Collection *collection); static void parseAttributes(const Protocol::Attributes &attributes, Tag *entity); static CollectionStatistics parseCollectionStatistics(const Protocol::FetchCollectionStatsResponse &stats); /** Convert attributes to their protocol representation. */ static Protocol::Attributes attributesToProtocol(const Item &item, bool ns = false); static Protocol::Attributes attributesToProtocol(const Collection &collection, bool ns = false); static Protocol::Attributes attributesToProtocol(const Tag &entity, bool ns = false); /** Encodes part label and namespace. */ static QByteArray encodePartIdentifier(PartNamespace ns, const QByteArray &label); /** Decode part label and namespace. */ static QByteArray decodePartIdentifier(const QByteArray &data, PartNamespace &ns); /** Converts the given set of items into a protocol representation. @throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers. */ template class Container> static Scope entitySetToScope(const Container &_objects) { if (_objects.isEmpty()) { throw Exception("No objects specified"); } Container objects(_objects); using namespace std::placeholders; std::sort(objects.begin(), objects.end(), [](const T & a, const T & b) -> bool { return a.id() < b.id(); }); if (objects.at(0).isValid()) { QVector uids; uids.reserve(objects.size()); for (const T &object : objects) { uids << object.id(); } ImapSet set; set.add(uids); return Scope(set); } if (entitySetHasGID(_objects)) { return entitySetToGID(_objects); } if (!entitySetHasRemoteIdentifier(_objects, std::mem_fn(&T::remoteId))) { throw Exception("No remote identifier specified"); } // check if we have RIDs or HRIDs if (entitySetHasHRID(_objects)) { return hierarchicalRidToScope(objects.first()); } return entitySetToRemoteIdentifier(Scope::Rid, _objects, std::mem_fn(&T::remoteId)); } static Protocol::ScopeContext commandContextToProtocol(const Akonadi::Collection &collection, const Akonadi::Tag &tag, const Item::List &requestedItems); /** Converts the given object identifier into a protocol representation. @throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers. */ template static Scope entityToScope(const T &object) { return entitySetToScope(QVector() << object); } /** Converts the given collection's hierarchical RID into a protocol representation. Assumes @p col has a valid hierarchical RID, so check that before! */ static Scope hierarchicalRidToScope(const Collection &col); /** Converts the HRID of the given item into an ASAP protocol representation. Assumes @p item has a valid HRID. */ static Scope hierarchicalRidToScope(const Item &item); static Scope hierarchicalRidToScope(const Tag &/*tag*/) { assert(false); return Scope(); } /** Converts a given ItemFetchScope object into a protocol representation. */ - static Protocol::FetchScope itemFetchScopeToProtocol(const ItemFetchScope &fetchScope); + static Protocol::ItemFetchScope itemFetchScopeToProtocol(const ItemFetchScope &fetchScope); + static ItemFetchScope parseItemFetchScope(const Protocol::ItemFetchScope &fetchScope); /** Converts a given TagFetchScope object into a protocol representation. */ static QVector tagFetchScopeToProtocol(const TagFetchScope &fetchScope); /** Parses a single line from an item fetch job result into an Item object. */ static Item parseItemFetchResult(const Protocol::FetchItemsResponse &data, ProtocolHelperValuePool *valuePool = nullptr); static Tag parseTagFetchResult(const Protocol::FetchTagsResponse &data); static Relation parseRelationFetchResult(const Protocol::FetchRelationsResponse &data); static bool streamPayloadToFile(const QString &file, const QByteArray &data, QByteArray &error); static Akonadi::Tristate listPreference(const Collection::ListPreference pref); private: template class Container> inline static typename std::enable_if < !std::is_same::value, bool >::type entitySetHasGID(const Container &objects) { return entitySetHasRemoteIdentifier(objects, std::mem_fn(&T::gid)); } template class Container> inline static typename std::enable_if::value, bool>::type entitySetHasGID(const Container &/*objects*/, int * /*dummy*/ = nullptr) { return false; } template class Container> inline static typename std::enable_if < !std::is_same::value, Scope >::type entitySetToGID(const Container &objects) { return entitySetToRemoteIdentifier(Scope::Gid, objects, std::mem_fn(&T::gid)); } template class Container> inline static typename std::enable_if::value, Scope>::type entitySetToGID(const Container &/*objects*/, int * /*dummy*/ = nullptr) { return Scope(); } template class Container, typename RIDFunc> inline static bool entitySetHasRemoteIdentifier(const Container &objects, const RIDFunc &ridFunc) { return std::find_if(objects.constBegin(), objects.constEnd(), [ = ](const T & obj) { return ridFunc(obj).isEmpty(); }) == objects.constEnd(); } template class Container, typename RIDFunc> inline static typename std::enable_if::value, Scope>::type entitySetToRemoteIdentifier(Scope::SelectionScope scope, const Container &objects, const RIDFunc &ridFunc) { QStringList rids; rids.reserve(objects.size()); std::transform(objects.cbegin(), objects.cend(), std::back_inserter(rids), [ = ](const T & obj) -> QString { return ridFunc(obj); }); return Scope(scope, rids); } template class Container, typename RIDFunc> inline static typename std::enable_if::value, Scope>::type entitySetToRemoteIdentifier(Scope::SelectionScope scope, const Container &objects, const RIDFunc &ridFunc, int * /*dummy*/ = nullptr) { QStringList rids; rids.reserve(objects.size()); std::transform(objects.cbegin(), objects.cend(), std::back_inserter(rids), [ = ](const T & obj) -> QString { return QString::fromLatin1(ridFunc(obj)); }); return Scope(scope, rids); } template class Container> inline static typename std::enable_if < !std::is_same::value, bool >::type entitySetHasHRID(const Container &objects) { return objects.size() == 1 && std::find_if(objects.constBegin(), objects.constEnd(), [](const T & obj) -> bool { return !CollectionUtils::hasValidHierarchicalRID(obj); }) == objects.constEnd(); // ### HRID sets are not yet specified } template class Container> inline static typename std::enable_if::value, bool>::type entitySetHasHRID(const Container &/*objects*/, int * /*dummy*/ = nullptr) { return false; } }; } #endif diff --git a/src/private/protocol.cpp b/src/private/protocol.cpp index 500458365..accf4f89a 100644 --- a/src/private/protocol.cpp +++ b/src/private/protocol.cpp @@ -1,808 +1,808 @@ /* * Copyright (c) 2015 Daniel Vrátil * Copyright (c) 2016 Daniel Vrátil * * 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 "protocol_p.h" #include "scope_p.h" #include "imapset_p.h" #include "datastream_p_p.h" #include #include #include #include #undef AKONADI_DECLARE_PRIVATE #define AKONADI_DECLARE_PRIVATE(Class) \ inline Class##Private* Class::d_func() {\ return reinterpret_cast(d_ptr.data()); \ } \ inline const Class##Private* Class::d_func() const {\ return reinterpret_cast(d_ptr.constData()); \ } #define COMPARE(prop) \ (prop == ((decltype(this)) other)->prop) namespace Akonadi { namespace Protocol { QDebug operator<<(QDebug _dbg, Command::Type type) { QDebug dbg(_dbg.noquote()); switch (type) { case Command::Invalid: return dbg << "Invalid"; case Command::Hello: return dbg << "Hello"; case Command::Login: return dbg << "Login"; case Command::Logout: return dbg << "Logout"; case Command::Transaction: return dbg << "Transaction"; case Command::CreateItem: return dbg << "CreateItem"; case Command::CopyItems: return dbg << "CopyItems"; case Command::DeleteItems: return dbg << "DeleteItems"; case Command::FetchItems: return dbg << "FetchItems"; case Command::LinkItems: return dbg << "LinkItems"; case Command::ModifyItems: return dbg << "ModifyItems"; case Command::MoveItems: return dbg << "MoveItems"; case Command::CreateCollection: return dbg << "CreateCollection"; case Command::CopyCollection: return dbg << "CopyCollection"; case Command::DeleteCollection: return dbg << "DeleteCollection"; case Command::FetchCollections: return dbg << "FetchCollections"; case Command::FetchCollectionStats: return dbg << "FetchCollectionStats"; case Command::ModifyCollection: return dbg << "ModifyCollection"; case Command::MoveCollection: return dbg << "MoveCollection"; case Command::Search: return dbg << "Search"; case Command::SearchResult: return dbg << "SearchResult"; case Command::StoreSearch: return dbg << "StoreSearch"; case Command::CreateTag: return dbg << "CreateTag"; case Command::DeleteTag: return dbg << "DeleteTag"; case Command::FetchTags: return dbg << "FetchTags"; case Command::ModifyTag: return dbg << "ModifyTag"; case Command::FetchRelations: return dbg << "FetchRelations"; case Command::ModifyRelation: return dbg << "ModifyRelation"; case Command::RemoveRelations: return dbg << "RemoveRelations"; case Command::SelectResource: return dbg << "SelectResource"; case Command::StreamPayload: return dbg << "StreamPayload"; case Command::ItemChangeNotification: return dbg << "ItemChangeNotification"; case Command::CollectionChangeNotification: return dbg << "CollectionChangeNotification"; case Command::TagChangeNotification: return dbg << "TagChangeNotification"; case Command::RelationChangeNotification: return dbg << "RelationChangeNotification"; case Command::SubscriptionChangeNotification: return dbg << "SubscriptionChangeNotification"; case Command::DebugChangeNotification: return dbg << "DebugChangeNotification"; case Command::CreateSubscription: return dbg << "CreateSubscription"; case Command::ModifySubscription: return dbg << "ModifySubscription"; case Command::_ResponseBit: Q_ASSERT(false); return dbg << static_cast(type); } Q_ASSERT(false); return dbg << static_cast(type); } template DataStream &operator<<(DataStream &stream, const QSharedPointer &ptr) { Protocol::serialize(stream.device(), ptr); return stream; } template DataStream &operator>>(DataStream &stream, QSharedPointer &ptr) { ptr = Protocol::deserialize(stream.device()).staticCast(); return stream; } /******************************************************************************/ Command::Command() : mType(Invalid) { } Command::Command(const Command &other) : mType(other.mType) { } Command::Command(quint8 type) : mType(type) { } Command::~Command() { } Command& Command::operator=(const Command &other) { mType = other.mType; return *this; } bool Command::operator==(const Command &other) const { return mType == other.mType; } DataStream &operator<<(DataStream &stream, const Command &cmd) { return stream << cmd.mType; } DataStream &operator>>(DataStream &stream, Command &cmd) { return stream >> cmd.mType; } QDebug operator<<(QDebug dbg, const Command &cmd) { return dbg.noquote() << ((cmd.mType & Command::_ResponseBit) ? "Response:" : "Command:") << static_cast(cmd.mType & ~Command::_ResponseBit) << "\n"; } /******************************************************************************/ Response::Response() : Response(Command::Invalid) { } Response::Response(const Response &other) : Command(other) , mErrorCode(other.mErrorCode) , mErrorMsg(other.mErrorMsg) { } Response::Response(Command::Type type) : Command(type | Command::_ResponseBit) , mErrorCode(0) { } Response &Response::operator=(const Response &other) { Command::operator=(other); mErrorMsg = other.mErrorMsg; mErrorCode = other.mErrorCode; return *this; } bool Response::operator==(const Response &other) const { return *static_cast(this) == static_cast(other) && mErrorCode == other.mErrorCode && mErrorMsg == other.mErrorMsg; } DataStream &operator<<(DataStream &stream, const Response &cmd) { return stream << static_cast(cmd) << cmd.mErrorCode << cmd.mErrorMsg; } DataStream &operator>>(DataStream &stream, Response &cmd) { return stream >> static_cast(cmd) >> cmd.mErrorCode >> cmd.mErrorMsg; } QDebug operator<<(QDebug dbg, const Response &resp) { return dbg.noquote() << static_cast(resp) << "Error code:" << resp.mErrorCode << "\n" << "Error msg:" << resp.mErrorMsg << "\n"; } /******************************************************************************/ class FactoryPrivate { public: typedef CommandPtr (*CommandFactoryFunc)(); typedef ResponsePtr (*ResponseFactoryFunc)(); FactoryPrivate() { // Session management registerType(); registerType(); registerType(); // Transactions registerType(); // Items registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Collections registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Search registerType(); registerType(); registerType(); // Tag registerType(); registerType(); registerType(); registerType(); // Relation registerType(); registerType(); registerType(); // Resources registerType(); // Other...? registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); } // clang has problem resolving the right qHash() overload for Command::Type, // so use its underlying integer type instead QHash::type, QPair> registrar; private: template static CommandPtr commandFactoryFunc() { return QSharedPointer::create(); } template static ResponsePtr responseFactoryFunc() { return QSharedPointer::create(); } template void registerType() { CommandFactoryFunc cmdFunc = &commandFactoryFunc; ResponseFactoryFunc respFunc = &responseFactoryFunc; registrar.insert(T, qMakePair(cmdFunc, respFunc)); } }; Q_GLOBAL_STATIC(FactoryPrivate, sFactoryPrivate) CommandPtr Factory::command(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->first(); } ResponsePtr Factory::response(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->second(); } /******************************************************************************/ /******************************************************************************/ -FetchScope::FetchScope() +ItemFetchScope::ItemFetchScope() : mAncestorDepth(NoAncestor) , mFlags(None) { } -FetchScope::FetchScope(const FetchScope &other) +ItemFetchScope::ItemFetchScope(const ItemFetchScope &other) : mAncestorDepth(other.mAncestorDepth) , mFlags(other.mFlags) , mRequestedParts(other.mRequestedParts) , mChangedSince(other.mChangedSince) , mTagFetchScope(other.mTagFetchScope) { } -FetchScope::~FetchScope() +ItemFetchScope::~ItemFetchScope() { } -FetchScope &FetchScope::operator=(const FetchScope &other) +ItemFetchScope &ItemFetchScope::operator=(const ItemFetchScope &other) { mAncestorDepth = other.mAncestorDepth; mFlags = other.mFlags; mRequestedParts = other.mRequestedParts; mChangedSince = other.mChangedSince; mTagFetchScope = other.mTagFetchScope; return *this; } -bool FetchScope::operator==(const FetchScope &other) const +bool ItemFetchScope::operator==(const ItemFetchScope &other) const { return mRequestedParts == other.mRequestedParts && mChangedSince == other.mChangedSince && mTagFetchScope == other.mTagFetchScope && mAncestorDepth == other.mAncestorDepth && mFlags == other.mFlags; } -QVector FetchScope::requestedPayloads() const +QVector ItemFetchScope::requestedPayloads() const { QVector rv; std::copy_if(mRequestedParts.begin(), mRequestedParts.end(), std::back_inserter(rv), [](const QByteArray &ba) { return ba.startsWith("PLD:"); }); return rv; } -void FetchScope::setFetch(FetchFlags attributes, bool fetch) +void ItemFetchScope::setFetch(FetchFlags attributes, bool fetch) { if (fetch) { mFlags |= attributes; if (attributes & FullPayload) { if (!mRequestedParts.contains(AKONADI_PARAM_PLD_RFC822)) { mRequestedParts << AKONADI_PARAM_PLD_RFC822; } } } else { mFlags &= ~attributes; } } -bool FetchScope::fetch(FetchFlags flags) const +bool ItemFetchScope::fetch(FetchFlags flags) const { if (flags == None) { return mFlags == None; } else { return mFlags & flags; } } -QDebug operator<<(QDebug dbg, FetchScope::AncestorDepth depth) +QDebug operator<<(QDebug dbg, ItemFetchScope::AncestorDepth depth) { switch (depth) { - case FetchScope::NoAncestor: + case ItemFetchScope::NoAncestor: return dbg << "No ancestor"; - case FetchScope::ParentAncestor: + case ItemFetchScope::ParentAncestor: return dbg << "Parent ancestor"; - case FetchScope::AllAncestors: + case ItemFetchScope::AllAncestors: return dbg << "All ancestors"; } Q_UNREACHABLE(); } -DataStream &operator<<(DataStream &stream, const FetchScope &scope) +DataStream &operator<<(DataStream &stream, const ItemFetchScope &scope) { return stream << scope.mRequestedParts << scope.mChangedSince << scope.mTagFetchScope << scope.mAncestorDepth << scope.mFlags; } -DataStream &operator>>(DataStream &stream, FetchScope &scope) +DataStream &operator>>(DataStream &stream, ItemFetchScope &scope) { return stream >> scope.mRequestedParts >> scope.mChangedSince >> scope.mTagFetchScope >> scope.mAncestorDepth >> scope.mFlags; } -QDebug operator<<(QDebug dbg, const FetchScope &scope) +QDebug operator<<(QDebug dbg, const ItemFetchScope &scope) { return dbg.noquote() << "FetchScope(\n" << "Fetch Flags:" << scope.mFlags << "\n" << "Tag Fetch Scope:" << scope.mTagFetchScope << "\n" << "Changed Since:" << scope.mChangedSince << "\n" << "Ancestor Depth:" << scope.mAncestorDepth << "\n" << "Requested Parts:" << scope.mRequestedParts << ")\n"; } /******************************************************************************/ ScopeContext::ScopeContext() { } ScopeContext::ScopeContext(Type type, qint64 id) { if (type == ScopeContext::Tag) { mTagCtx = id; } else if (type == ScopeContext::Collection) { mColCtx = id; } } ScopeContext::ScopeContext(Type type, const QString &ctx) { if (type == ScopeContext::Tag) { mTagCtx = ctx; } else if (type == ScopeContext::Collection) { mColCtx = ctx; } } ScopeContext::ScopeContext(const ScopeContext &other) : mColCtx(other.mColCtx) , mTagCtx(other.mTagCtx) { } ScopeContext::~ScopeContext() { } ScopeContext &ScopeContext::operator=(const ScopeContext &other) { mColCtx = other.mColCtx; mTagCtx = other.mTagCtx; return *this; } bool ScopeContext::operator==(const ScopeContext &other) const { return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx; } DataStream &operator<<(DataStream &stream, const ScopeContext &context) { // We don't have a custom generic DataStream streaming operator for QVariant // because it's very hard, esp. without access to QVariant private // stuff, so we have have to decompose it manually here. QVariant::Type vType = context.mColCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mColCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mColCtx.toString(); } vType = context.mTagCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mTagCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mTagCtx.toString(); } return stream; } DataStream &operator>>(DataStream &stream, ScopeContext &context) { QVariant::Type vType; qint64 id; QString rid; for (ScopeContext::Type type : { ScopeContext::Collection, ScopeContext::Tag }) { stream >> vType; if (vType == QVariant::LongLong) { stream >> id; context.setContext(type, id); } else if (vType == QVariant::String) { stream >> rid; context.setContext(type, rid); } } return stream; } QDebug operator<<(QDebug _dbg, const ScopeContext &ctx) { QDebug dbg(_dbg.noquote()); dbg << "ScopeContext("; if (ctx.isEmpty()) { dbg << "empty"; } else if (ctx.hasContextId(ScopeContext::Tag)) { dbg << "Tag ID:" << ctx.contextId(ScopeContext::Tag); } else if (ctx.hasContextId(ScopeContext::Collection)) { dbg << "Col ID:" << ctx.contextId(ScopeContext::Collection); } else if (ctx.hasContextRID(ScopeContext::Tag)) { dbg << "Tag RID:" << ctx.contextRID(ScopeContext::Tag); } else if (ctx.hasContextRID(ScopeContext::Collection)) { dbg << "Col RID:" << ctx.contextRID(ScopeContext::Collection); } return dbg << ")\n"; } /******************************************************************************/ ChangeNotification::ChangeNotification(Command::Type type) : Command(type) { } ChangeNotification::ChangeNotification(const ChangeNotification &other) : Command(other) , mSessionId(other.mSessionId) , mMetaData(other.mMetaData) { } ChangeNotification &ChangeNotification::operator=(const ChangeNotification &other) { *static_cast(this) = static_cast(other); mSessionId = other.mSessionId; mMetaData = other.mMetaData; return *this; } bool ChangeNotification::operator==(const ChangeNotification &other) const { return static_cast(*this) == other && mSessionId == other.mSessionId; // metadata are not compared } bool ChangeNotification::isRemove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Remove; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Remove; case Command::TagChangeNotification: return static_cast(this)->operation() == TagChangeNotification::Remove; case Command::RelationChangeNotification: return static_cast(this)->operation() == RelationChangeNotification::Remove; case Command::SubscriptionChangeNotification: return static_cast(this)->operation() == SubscriptionChangeNotification::Remove; case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::isMove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Move; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Move; case Command::TagChangeNotification: case Command::RelationChangeNotification: case Command::SubscriptionChangeNotification: case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg) { //It is likely that compressable notifications are within the last few notifications, so avoid searching a list that is potentially huge static const int maxCompressionSearchLength = 10; int searchCounter = 0; // There are often multiple Collection Modify notifications in the queue, // so we optimize for this case. if (msg->type() == Command::CollectionChangeNotification) { const auto &cmsg = Protocol::cmdCast(msg); if (cmsg.operation() == CollectionChangeNotification::Modify) { // We are iterating from end, since there's higher probability of finding // matching notification for (auto iter = list.end(), begin = list.begin(); iter != begin;) { --iter; auto &it = Protocol::cmdCast(*iter); if (cmsg.id() == it.id() && cmsg.remoteId() == it.remoteId() && cmsg.remoteRevision() == it.remoteRevision() && cmsg.resource() == it.resource() && cmsg.destinationResource() == it.destinationResource() && cmsg.parentCollection() == it.parentCollection() && cmsg.parentDestCollection() == it.parentDestCollection()) { // both are modifications, merge them together and drop the new one if (cmsg.operation() == CollectionChangeNotification::Modify && it.operation() == CollectionChangeNotification::Modify) { const auto parts = it.changedParts(); it.setChangedParts(parts + cmsg.changedParts()); return false; } // we found Add notification, which means we can drop this modification if (it.operation() == CollectionChangeNotification::Add) { return false; } } searchCounter++; if (searchCounter >= maxCompressionSearchLength) { break; } } } } // All other cases are just append, as the compression becomes too expensive in large batches list.append(msg); return true; } DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf) { return stream << static_cast(ntf) << ntf.mSessionId; } DataStream &operator>>(DataStream &stream, ChangeNotification &ntf) { return stream >> static_cast(ntf) >> ntf.mSessionId; } QDebug operator<<(QDebug dbg, const ChangeNotification &ntf) { return dbg.noquote() << static_cast(ntf) << "Session:" << ntf.mSessionId << "\n" << "MetaData:" << ntf.mMetaData << "\n"; } DataStream &operator>>(DataStream &stream, ChangeNotification::Item &item) { return stream >> item.id >> item.mimeType >> item.remoteId >> item.remoteRevision; } DataStream &operator<<(DataStream &stream, const ChangeNotification::Item &item) { return stream << item.id << item.mimeType << item.remoteId << item.remoteRevision; } QDebug operator<<(QDebug _dbg, const ChangeNotification::Item &item) { QDebug dbg(_dbg.noquote()); return dbg << "Item:" << item.id << "(RID:" << item.remoteId << ", RREV:" << item.remoteRevision << ", mimetype: " << item.mimeType; } DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation) { return stream >> relation.type >> relation.leftId >> relation.rightId; } DataStream &operator<<(DataStream &stream, const ChangeNotification::Relation &relation) { return stream << relation.type << relation.leftId << relation.rightId; } QDebug operator<<(QDebug _dbg, const ChangeNotification::Relation &rel) { QDebug dbg(_dbg.noquote()); return dbg << "Left: " << rel.leftId << ", Right:" << rel.rightId << ", Type: " << rel.type; } } // namespace Protocol } // namespace Akonadi /******************************************************************************/ // Here comes the generated protocol implementation #include "protocol_gen.cpp" /******************************************************************************/ diff --git a/src/private/protocol.xml b/src/private/protocol.xml index eff3e20e4..11ce2f6bf 100644 --- a/src/private/protocol.xml +++ b/src/private/protocol.xml @@ -1,1068 +1,1073 @@ - + - + - + - + - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/private/protocol_p.h b/src/private/protocol_p.h index d79ea7c53..4e8bc8c4b 100644 --- a/src/private/protocol_p.h +++ b/src/private/protocol_p.h @@ -1,658 +1,658 @@ /* Copyright (c) 2007 Volker Krause Copyright (c) 2015 Daniel Vrátil Copyright (c) 2016 Daniel Vrátil 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. */ #ifndef AKONADI_PROTOCOL_COMMON_P_H #define AKONADI_PROTOCOL_COMMON_P_H #include "akonadiprivate_export.h" #include #include #include #include #include #include "tristate_p.h" #include "scope_p.h" /** @file protocol_p.h Shared constants used in the communication protocol between the Akonadi server and its clients. */ namespace Akonadi { namespace Protocol { class Factory; class DataStream; class Command; class Response; -class FetchScope; +class ItemFetchScope; class ScopeContext; class ChangeNotification; using Attributes = QMap; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Command &cmd); using CommandPtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Command { public: enum Type : quint8 { Invalid = 0, // Session management Hello = 1, Login, Logout, // Transactions Transaction = 10, // Items CreateItem = 20, CopyItems, DeleteItems, FetchItems, LinkItems, ModifyItems, MoveItems, // Collections CreateCollection = 40, CopyCollection, DeleteCollection, FetchCollections, FetchCollectionStats, ModifyCollection, MoveCollection, // Search Search = 60, SearchResult, StoreSearch, // Tag CreateTag = 70, DeleteTag, FetchTags, ModifyTag, // Relation FetchRelations = 80, ModifyRelation, RemoveRelations, // Resources SelectResource = 90, // Other StreamPayload = 100, // Notifications ItemChangeNotification = 110, CollectionChangeNotification, TagChangeNotification, RelationChangeNotification, SubscriptionChangeNotification, DebugChangeNotification, CreateSubscription, ModifySubscription, // _MaxValue = 127 _ResponseBit = 0x80 // reserved }; explicit Command(); explicit Command(const Command &other); ~Command(); Command &operator=(const Command &other); bool operator==(const Command &other) const; inline bool operator!=(const Command &other) const { return !operator==(other); } inline Type type() const { return static_cast(mType & ~_ResponseBit); } inline bool isValid() const { return type() != Invalid; } inline bool isResponse() const { return mType & _ResponseBit; } protected: explicit Command(quint8 type); quint8 mType; // unused 7 bytes private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(::QDebug dbg, const Akonadi::Protocol::Command &cmd); }; } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::Command::Type) Q_DECLARE_METATYPE(Akonadi::Protocol::CommandPtr) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &response); using ResponsePtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Response : public Command { public: explicit Response(); explicit Response(const Response &other); Response &operator=(const Response &other); inline void setError(int code, const QString &message) { mErrorCode = code; mErrorMsg = message; } bool operator==(const Response &other) const; inline bool operator!=(const Response &other) const { return !operator==(other); } inline bool isError() const { return mErrorCode > 0; } inline int errorCode() const { return mErrorCode; } inline QString errorMessage() const { return mErrorMsg; } protected: explicit Response(Command::Type type); int mErrorCode; QString mErrorMsg; private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &cmd); }; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { template inline const X &cmdCast(const QSharedPointer &p) { return static_cast(*p); } template inline X &cmdCast(QSharedPointer &p) { return static_cast(*p); } class AKONADIPRIVATE_EXPORT Factory { public: static CommandPtr command(Command::Type type); static ResponsePtr response(Command::Type type); private: template friend AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); }; AKONADIPRIVATE_EXPORT void serialize(QIODevice *device, const CommandPtr &command); AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); AKONADIPRIVATE_EXPORT QString debugString(const Command &command); AKONADIPRIVATE_EXPORT inline QString debugString(const CommandPtr &command) { return debugString(*command); } } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, - const Akonadi::Protocol::FetchScope &scope); + const Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, - Akonadi::Protocol::FetchScope &scope); -AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::FetchScope &scope); + Akonadi::Protocol::ItemFetchScope &scope); +AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); -class AKONADIPRIVATE_EXPORT FetchScope +class AKONADIPRIVATE_EXPORT ItemFetchScope { public: enum FetchFlag : int { None = 0, CacheOnly = 1 << 0, CheckCachedPayloadPartsOnly = 1 << 1, FullPayload = 1 << 2, AllAttributes = 1 << 3, Size = 1 << 4, MTime = 1 << 5, RemoteRevision = 1 << 6, IgnoreErrors = 1 << 7, Flags = 1 << 8, RemoteID = 1 << 9, GID = 1 << 10, Tags = 1 << 11, Relations = 1 << 12, VirtReferences = 1 << 13 }; Q_DECLARE_FLAGS(FetchFlags, FetchFlag) enum AncestorDepth : ushort { NoAncestor, ParentAncestor, AllAncestors }; - explicit FetchScope(); - FetchScope(const FetchScope &other); - ~FetchScope(); + explicit ItemFetchScope(); + ItemFetchScope(const ItemFetchScope &other); + ~ItemFetchScope(); - FetchScope &operator=(const FetchScope &other); + ItemFetchScope &operator=(const ItemFetchScope &other); - bool operator==(const FetchScope &other) const; - inline bool operator!=(const FetchScope &other) const { return !operator==(other); } + bool operator==(const ItemFetchScope &other) const; + inline bool operator!=(const ItemFetchScope &other) const { return !operator==(other); } inline void setRequestedParts(const QVector &requestedParts) { mRequestedParts = requestedParts; } inline QVector requestedParts() const { return mRequestedParts; } QVector requestedPayloads() const; inline void setChangedSince(const QDateTime &changedSince) { mChangedSince = changedSince; } inline QDateTime changedSince() const { return mChangedSince; } inline void setTagFetchScope(const QSet &tagFetchScope) { mTagFetchScope = tagFetchScope; } inline QSet tagFetchScope() const { return mTagFetchScope; } inline void setAncestorDepth(AncestorDepth depth) { mAncestorDepth = depth; } inline AncestorDepth ancestorDepth() const { return mAncestorDepth; } inline bool cacheOnly() const { return mFlags & CacheOnly; } inline bool checkCachedPayloadPartsOnly() const { return mFlags & CheckCachedPayloadPartsOnly; } inline bool fullPayload() const { return mFlags & FullPayload; } inline bool allAttributes() const { return mFlags & AllAttributes; } inline bool fetchSize() const { return mFlags & Size; } inline bool fetchMTime() const { return mFlags & MTime; } inline bool fetchRemoteRevision() const { return mFlags & RemoteRevision; } inline bool ignoreErrors() const { return mFlags & IgnoreErrors; } inline bool fetchFlags() const { return mFlags & Flags; } inline bool fetchRemoteId() const { return mFlags & RemoteID; } inline bool fetchGID() const { return mFlags & GID; } inline bool fetchTags() const { return mFlags & Tags; } inline bool fetchRelations() const { return mFlags & Relations; } inline bool fetchVirtualReferences() const { return mFlags & VirtReferences; } void setFetch(FetchFlags attributes, bool fetch = true); bool fetch(FetchFlags flags) const; private: AncestorDepth mAncestorDepth; // 2 bytes free FetchFlags mFlags; QVector mRequestedParts; QDateTime mChangedSince; QSet mTagFetchScope; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, - const Akonadi::Protocol::FetchScope &scope); + const Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, - Akonadi::Protocol::FetchScope &scope); - friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::FetchScope &scope); + Akonadi::Protocol::ItemFetchScope &scope); + friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); }; } // namespace Protocol } // namespace Akonadi -Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Protocol::FetchScope::FetchFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Protocol::ItemFetchScope::FetchFlags) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); class AKONADIPRIVATE_EXPORT ScopeContext { public: enum Type : uchar { Any = 0, Collection, Tag }; explicit ScopeContext(); ScopeContext(Type type, qint64 id); ScopeContext(Type type, const QString &id); ScopeContext(const ScopeContext &other); ~ScopeContext(); ScopeContext &operator=(const ScopeContext &other); bool operator==(const ScopeContext &other) const; inline bool operator!=(const ScopeContext &other) const { return !operator==(other); } inline bool isEmpty() const { return mColCtx.isNull() && mTagCtx.isNull(); } inline void setContext(Type type, qint64 id) { setCtx(type, id); } inline void setContext(Type type, const QString &id) { setCtx(type, id); } inline void clearContext(Type type) { setCtx(type, QVariant()); } inline bool hasContextId(Type type) const { return ctx(type).type() == QVariant::LongLong; } inline qint64 contextId(Type type) const { return hasContextId(type) ? ctx(type).toLongLong() : 0; } inline bool hasContextRID(Type type) const { return ctx(type).type() == QVariant::String; } inline QString contextRID(Type type) const { return hasContextRID(type) ? ctx(type).toString() : QString(); } private: QVariant mColCtx; QVariant mTagCtx; inline QVariant ctx(Type type) const { return type == Collection ? mColCtx : type == Tag ? mTagCtx : QVariant(); } inline void setCtx(Type type, const QVariant &v) { if (type == Collection) { mColCtx = v; } else if (type == Tag) { mTagCtx = v; } } friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); }; } // namespace Protocol } // namespace akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); using ChangeNotificationPtr = QSharedPointer; using ChangeNotificationList = QVector; class AKONADIPRIVATE_EXPORT ChangeNotification : public Command { public: class Item { public: inline Item() : id(-1) {} inline Item(qint64 id, const QString &remoteId, const QString &remoteRevision, const QString &mimeType) : id(id) , remoteId(remoteId) , remoteRevision(remoteRevision) , mimeType(mimeType) {} inline bool operator==(const Item &other) const { return id == other.id && remoteId == other.remoteId && remoteRevision == other.remoteRevision && mimeType == other.mimeType; } qint64 id; QString remoteId; QString remoteRevision; QString mimeType; }; inline static QList itemsToUids(const QVector &items) { QList rv; rv.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(rv), [](const Item &item) { return item.id; }); return rv; } class Relation { public: inline Relation() : leftId(-1) , rightId(-1) { } inline Relation(qint64 leftId, qint64 rightId, const QString &type) : leftId(leftId) , rightId(rightId) , type(type) { } inline bool operator==(const Relation &other) const { return leftId == other.leftId && rightId == other.rightId && type == other.type; } qint64 leftId; qint64 rightId; QString type; }; ChangeNotification &operator=(const ChangeNotification &other); bool operator==(const ChangeNotification &other) const; inline bool operator!=(const ChangeNotification &other) const { return !operator==(other); } bool isRemove() const; bool isMove() const; inline QByteArray sessionId() const { return mSessionId; } inline void setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } inline void addMetadata(const QByteArray &metadata) { mMetaData << metadata; } inline void removeMetadata(const QByteArray &metadata) { mMetaData.removeAll(metadata); } QVector metadata() const { return mMetaData; } static bool appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg); protected: explicit ChangeNotification(Command::Type type); ChangeNotification(const ChangeNotification &other); QByteArray mSessionId; // For internal use only: Akonadi server can add some additional information // that might be useful when evaluating the notification for example, but // it is never transferred to clients QVector mMetaData; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); }; inline uint qHash(const ChangeNotification::Relation &rel) { return ::qHash(rel.leftId + rel.rightId); } // TODO: Internalize? AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification::Item &item); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification::Item &item); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification::Relation &relation); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification::Relation &relation); } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationPtr) Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationList) /******************************************************************************/ // Here comes the actual generated Protocol. See protocol.xml for definitions, // and genprotocol folder for the generator. #include "protocol_gen.h" /******************************************************************************/ // Command parameters #define AKONADI_PARAM_ATR "ATR:" #define AKONADI_PARAM_CACHEPOLICY "CACHEPOLICY" #define AKONADI_PARAM_DISPLAY "DISPLAY" #define AKONADI_PARAM_ENABLED "ENABLED" #define AKONADI_PARAM_FLAGS "FLAGS" #define AKONADI_PARAM_TAGS "TAGS" #define AKONADI_PARAM_GID "GID" #define AKONADI_PARAM_INDEX "INDEX" #define AKONADI_PARAM_MIMETYPE "MIMETYPE" #define AKONADI_PARAM_NAME "NAME" #define AKONADI_PARAM_PARENT "PARENT" #define AKONADI_PARAM_PERSISTENTSEARCH "PERSISTENTSEARCH" #define AKONADI_PARAM_PLD "PLD:" #define AKONADI_PARAM_PLD_RFC822 "PLD:RFC822" #define AKONADI_PARAM_RECURSIVE "RECURSIVE" #define AKONADI_PARAM_REFERENCED "REFERENCED" #define AKONADI_PARAM_REMOTE "REMOTE" #define AKONADI_PARAM_REMOTEID "REMOTEID" #define AKONADI_PARAM_REMOTEREVISION "REMOTEREVISION" #define AKONADI_PARAM_REVISION "REV" #define AKONADI_PARAM_SIZE "SIZE" #define AKONADI_PARAM_SYNC "SYNC" #define AKONADI_PARAM_TAG "TAG" #define AKONADI_PARAM_TYPE "TYPE" #define AKONADI_PARAM_VIRTUAL "VIRTUAL" // Flags #define AKONADI_FLAG_GID "\\Gid" #define AKONADI_FLAG_IGNORED "$IGNORED" #define AKONADI_FLAG_MIMETYPE "\\MimeType" #define AKONADI_FLAG_REMOTEID "\\RemoteId" #define AKONADI_FLAG_REMOTEREVISION "\\RemoteRevision" #define AKONADI_FLAG_TAG "\\Tag" #define AKONADI_FLAG_RTAG "\\RTag" #define AKONADI_FLAG_SEEN "\\SEEN" // Attributes #define AKONADI_ATTRIBUTE_HIDDEN "ATR:HIDDEN" #define AKONADI_ATTRIBUTE_MESSAGES "MESSAGES" #define AKONADI_ATTRIBUTE_UNSEEN "UNSEEN" // special resource names #define AKONADI_SEARCH_RESOURCE "akonadi_search_resource" #endif diff --git a/src/server/handler/akappend.cpp b/src/server/handler/akappend.cpp index 7bba2baa0..b5bec3b10 100644 --- a/src/server/handler/akappend.cpp +++ b/src/server/handler/akappend.cpp @@ -1,456 +1,456 @@ /*************************************************************************** * Copyright (C) 2007 by Robert Zwerus * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "akappend.h" #include "fetchhelper.h" #include "connection.h" #include "preprocessormanager.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/parttypehelper.h" #include "storage/dbconfig.h" #include "storage/partstreamer.h" #include "storage/parthelper.h" #include "storage/selectquerybuilder.h" #include #include //std::accumulate using namespace Akonadi; using namespace Akonadi::Server; static QVector localFlagsToPreserve = QVector() << "$ATTACHMENT" << "$INVITATION" << "$ENCRYPTED" << "$SIGNED" << "$WATCHED"; bool AkAppend::buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, Collection &parentCol) { parentCol = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!parentCol.isValid()) { return failureResponse(QStringLiteral("Invalid parent collection")); } if (parentCol.isVirtual()) { return failureResponse(QStringLiteral("Cannot append item into virtual collection")); } MimeType mimeType = MimeType::retrieveByNameOrCreate(cmd.mimeType()); if (!mimeType.isValid()) { return failureResponse(QStringLiteral("Unable to create mimetype '") % cmd.mimeType() % QStringLiteral("'.")); } item.setRev(0); item.setSize(cmd.itemSize()); item.setMimeTypeId(mimeType.id()); item.setCollectionId(parentCol.id()); item.setDatetime(cmd.dateTime()); if (cmd.remoteId().isEmpty()) { // from application item.setDirty(true); } else { // from resource item.setRemoteId(cmd.remoteId()); item.setDirty(false); } item.setRemoteRevision(cmd.remoteRevision()); item.setGid(cmd.gid()); item.setAtime(QDateTime::currentDateTimeUtc()); return true; } bool AkAppend::insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, const Collection &parentCol) { if (!item.datetime().isValid()) { item.setDatetime(QDateTime::currentDateTimeUtc()); } if (!item.insert()) { return failureResponse(QStringLiteral("Failed to append item")); } // set message flags const QSet flags = cmd.mergeModes() == Protocol::CreateItemCommand::None ? cmd.flags() : cmd.addedFlags(); if (!flags.isEmpty()) { // This will hit an entry in cache inserted there in buildPimItem() const Flag::List flagList = HandlerHelper::resolveFlags(flags); bool flagsChanged = false; if (!DataStore::self()->appendItemsFlags(PimItem::List() << item, flagList, &flagsChanged, false, parentCol, true)) { return failureResponse("Unable to append item flags."); } } const Scope tags = cmd.mergeModes() == Protocol::CreateItemCommand::None ? cmd.tags() : cmd.addedTags(); if (!tags.isEmpty()) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); bool tagsChanged = false; if (!DataStore::self()->appendItemsTags(PimItem::List() << item, tagList, &tagsChanged, false, parentCol, true)) { return failureResponse(QStringLiteral("Unable to append item tags.")); } } // Handle individual parts qint64 partSizes = 0; PartStreamer streamer(connection(), item, this); connect(&streamer, &PartStreamer::responseAvailable, this, static_cast(&Handler::sendResponse)); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; if (!streamer.stream(true, partName, partSize)) { return failureResponse(streamer.error()); } partSizes += partSize; } const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { if (!streamer.streamAttribute(true, iter.key(), iter.value())) { return failureResponse(streamer.error()); } } // TODO: Try to avoid this addition query if (partSizes > item.size()) { item.setSize(partSizes); item.update(); } // Preprocessing if (PreprocessorManager::instance()->isActive()) { Part hiddenAttribute; hiddenAttribute.setPimItemId(item.id()); hiddenAttribute.setPartType(PartTypeHelper::fromFqName(QStringLiteral(AKONADI_ATTRIBUTE_HIDDEN))); hiddenAttribute.setData(QByteArray()); hiddenAttribute.setDatasize(0); // TODO: Handle errors? Technically, this is not a critical issue as no data are lost PartHelper::insert(&hiddenAttribute); } const bool seen = flags.contains(AKONADI_FLAG_SEEN) || flags.contains(AKONADI_FLAG_IGNORED); notify(item, seen, item.collection()); sendResponse(item, Protocol::CreateItemCommand::None); return true; } bool AkAppend::mergeItem(const Protocol::CreateItemCommand &cmd, PimItem &newItem, PimItem ¤tItem, const Collection &parentCol) { bool needsUpdate = false; QSet changedParts; if (!newItem.remoteId().isEmpty() && currentItem.remoteId() != newItem.remoteId()) { currentItem.setRemoteId(newItem.remoteId()); changedParts.insert(AKONADI_PARAM_REMOTEID); needsUpdate = true; } if (!newItem.remoteRevision().isEmpty() && currentItem.remoteRevision() != newItem.remoteRevision()) { currentItem.setRemoteRevision(newItem.remoteRevision()); changedParts.insert(AKONADI_PARAM_REMOTEREVISION); needsUpdate = true; } if (!newItem.gid().isEmpty() && currentItem.gid() != newItem.gid()) { currentItem.setGid(newItem.gid()); changedParts.insert(AKONADI_PARAM_GID); needsUpdate = true; } if (newItem.datetime().isValid() && newItem.datetime() != currentItem.datetime()) { currentItem.setDatetime(newItem.datetime()); needsUpdate = true; } if (newItem.size() > 0 && newItem.size() != currentItem.size()) { currentItem.setSize(newItem.size()); needsUpdate = true; } const Collection col = Collection::retrieveById(parentCol.id()); if (cmd.flags().isEmpty() && !cmd.flagsOverwritten()) { bool flagsAdded = false, flagsRemoved = false; if (!cmd.addedFlags().isEmpty()) { const Flag::List addedFlags = HandlerHelper::resolveFlags(cmd.addedFlags()); DataStore::self()->appendItemsFlags(PimItem::List() << currentItem, addedFlags, &flagsAdded, true, col, true); } if (!cmd.removedFlags().isEmpty()) { const Flag::List removedFlags = HandlerHelper::resolveFlags(cmd.removedFlags()); DataStore::self()->removeItemsFlags(PimItem::List() << currentItem, removedFlags, &flagsRemoved, col, true); } if (flagsAdded || flagsRemoved) { changedParts.insert(AKONADI_PARAM_FLAGS); needsUpdate = true; } } else { bool flagsChanged = false; QSet flagNames = cmd.flags(); // Make sure we don't overwrite some local-only flags that can't come // through from Resource during ItemSync, like $ATTACHMENT, because the // resource is not aware of them (they are usually assigned by client // upon inspecting the payload) Q_FOREACH (const Flag ¤tFlag, currentItem.flags()) { const QByteArray currentFlagName = currentFlag.name().toLatin1(); if (localFlagsToPreserve.contains(currentFlagName)) { flagNames.insert(currentFlagName); } } const Flag::List flags = HandlerHelper::resolveFlags(flagNames); DataStore::self()->setItemsFlags(PimItem::List() << currentItem, flags, &flagsChanged, col, true); if (flagsChanged) { changedParts.insert(AKONADI_PARAM_FLAGS); needsUpdate = true; } } if (cmd.tags().isEmpty()) { bool tagsAdded = false, tagsRemoved = false; if (!cmd.addedTags().isEmpty()) { const Tag::List addedTags = HandlerHelper::tagsFromScope(cmd.addedTags(), connection()); DataStore::self()->appendItemsTags(PimItem::List() << currentItem, addedTags, &tagsAdded, true, col, true); } if (!cmd.removedTags().isEmpty()) { const Tag::List removedTags = HandlerHelper::tagsFromScope(cmd.removedTags(), connection()); DataStore::self()->removeItemsTags(PimItem::List() << currentItem, removedTags, &tagsRemoved, true); } if (tagsAdded || tagsRemoved) { changedParts.insert(AKONADI_PARAM_TAGS); needsUpdate = true; } } else { bool tagsChanged = false; const Tag::List tags = HandlerHelper::tagsFromScope(cmd.tags(), connection()); DataStore::self()->setItemsTags(PimItem::List() << currentItem, tags, &tagsChanged, true); if (tagsChanged) { changedParts.insert(AKONADI_PARAM_TAGS); needsUpdate = true; } } const Part::List existingParts = Part::retrieveFiltered(Part::pimItemIdColumn(), currentItem.id()); QMap partsSizes; for (const Part &part : existingParts) { partsSizes.insert(PartTypeHelper::fullName(part.partType()).toLatin1(), part.datasize()); } PartStreamer streamer(connection(), currentItem); connect(&streamer, &PartStreamer::responseAvailable, this, static_cast(&Handler::sendResponse)); Q_FOREACH (const QByteArray &partName, cmd.parts()) { bool changed = false; qint64 partSize = 0; if (!streamer.stream(true, partName, partSize, &changed)) { return failureResponse(streamer.error()); } if (changed) { changedParts.insert(partName); partsSizes.insert(partName, partSize); needsUpdate = true; } } const qint64 size = std::accumulate(partsSizes.begin(), partsSizes.end(), 0); if (size > currentItem.size()) { currentItem.setSize(size); needsUpdate = true; } if (needsUpdate) { currentItem.setRev(qMax(newItem.rev(), currentItem.rev()) + 1); currentItem.setAtime(QDateTime::currentDateTimeUtc()); // Only mark dirty when merged from application currentItem.setDirty(!connection()->context()->resource().isValid()); // Store all changes if (!currentItem.update()) { return failureResponse("Failed to store merged item"); } notify(currentItem, currentItem.collection(), changedParts); } sendResponse(currentItem, cmd.mergeModes()); return true; } bool AkAppend::sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes) { if (mergeModes & Protocol::CreateItemCommand::Silent || mergeModes & Protocol::CreateItemCommand::None) { auto resp = Protocol::FetchItemsResponsePtr::create(); resp->setId(item.id()); resp->setMTime(item.datetime()); Handler::sendResponse(resp); return true; } - Protocol::FetchScope fetchScope; - fetchScope.setAncestorDepth(Protocol::FetchScope::ParentAncestor); - fetchScope.setFetch(Protocol::FetchScope::AllAttributes | - Protocol::FetchScope::FullPayload | - Protocol::FetchScope::CacheOnly | - Protocol::FetchScope::Flags | - Protocol::FetchScope::GID | - Protocol::FetchScope::MTime | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::Size | - Protocol::FetchScope::Tags); + Protocol::ItemFetchScope fetchScope; + fetchScope.setAncestorDepth(Protocol::ItemFetchScope::ParentAncestor); + fetchScope.setFetch(Protocol::ItemFetchScope::AllAttributes | + Protocol::ItemFetchScope::FullPayload | + Protocol::ItemFetchScope::CacheOnly | + Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::GID | + Protocol::ItemFetchScope::MTime | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::Tags); fetchScope.setTagFetchScope({ "GID" }); ImapSet set; set.add(QVector() << item.id()); Scope scope; scope.setUidSet(set); FetchHelper fetchHelper(connection(), scope, fetchScope); if (!fetchHelper.fetchItems()) { return failureResponse("Failed to retrieve item"); } return true; } bool AkAppend::notify(const PimItem &item, bool seen, const Collection &collection) { DataStore::self()->notificationCollector()->itemAdded(item, seen, collection); if (PreprocessorManager::instance()->isActive()) { // enqueue the item for preprocessing PreprocessorManager::instance()->beginHandleItem(item, DataStore::self()); } return true; } bool AkAppend::notify(const PimItem &item, const Collection &collection, const QSet &changedParts) { if (!changedParts.isEmpty()) { DataStore::self()->notificationCollector()->itemChanged(item, changedParts, collection); } return true; } bool AkAppend::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); // FIXME: The streaming/reading of all item parts can hold the transaction for // unnecessary long time -> should we wrap the PimItem into one transaction // and try to insert Parts independently? In case we fail to insert a part, // it's not a problem as it can be re-fetched at any time, except for attributes. DataStore *db = DataStore::self(); Transaction transaction(db, QStringLiteral("AKAPPEND")); ExternalPartStorageTransaction storageTrx; PimItem item; Collection parentCol; if (!buildPimItem(cmd, item, parentCol)) { return false; } if (cmd.mergeModes() == Protocol::CreateItemCommand::None) { if (!insertItem(cmd, item, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse(QStringLiteral("Failed to commit transaction")); } storageTrx.commit(); } else { // Merging is always restricted to the same collection SelectQueryBuilder qb; qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, parentCol.id()); Query::Condition rootCondition(Query::Or); Query::Condition mergeCondition(Query::And); if (cmd.mergeModes() & Protocol::CreateItemCommand::GID) { mergeCondition.addValueCondition(PimItem::gidColumn(), Query::Equals, item.gid()); } if (cmd.mergeModes() & Protocol::CreateItemCommand::RemoteID) { mergeCondition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, item.remoteId()); } rootCondition.addCondition(mergeCondition); // If an Item with matching RID but empty GID exists during GID merge, // merge into this item instead of creating a new one if (cmd.mergeModes() & Protocol::CreateItemCommand::GID && !item.remoteId().isEmpty()) { mergeCondition = Query::Condition(Query::And); mergeCondition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, item.remoteId()); mergeCondition.addValueCondition(PimItem::gidColumn(), Query::Equals, QStringLiteral("")); rootCondition.addCondition(mergeCondition); } qb.addCondition(rootCondition); if (!qb.exec()) { return failureResponse("Failed to query database for item"); } const QVector result = qb.result(); if (result.isEmpty()) { // No item with such GID/RID exists, so call AkAppend::insert() and behave // like if this was a new item if (!insertItem(cmd, item, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse("Failed to commit transaction"); } storageTrx.commit(); } else if (result.count() == 1) { // Item with matching GID/RID combination exists, so merge this item into it // and send itemChanged() PimItem existingItem = result.at(0); if (!mergeItem(cmd, item, existingItem, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse("Failed to commit transaction"); } storageTrx.commit(); } else { qCDebug(AKONADISERVER_LOG) << "Multiple merge candidates:"; for (const PimItem &item : result) { qCDebug(AKONADISERVER_LOG) << "\tID:" << item.id() << ", RID:" << item.remoteId() << ", GID:" << item.gid() << ", Collection:" << item.collection().name() << "(" << item.collectionId() << ")" << ", Resource:" << item.collection().resource().name() << "(" << item.collection().resourceId() << ")"; } // Nor GID or RID are guaranteed to be unique, so make sure we don't merge // something we don't want return failureResponse(QStringLiteral("Multiple merge candidates, aborting")); } } return successResponse(); } diff --git a/src/server/handler/fetchhelper.cpp b/src/server/handler/fetchhelper.cpp index fba5594ad..c77bccf4e 100644 --- a/src/server/handler/fetchhelper.cpp +++ b/src/server/handler/fetchhelper.cpp @@ -1,725 +1,725 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "fetchhelper.h" #include "akonadi.h" #include "connection.h" #include "handler.h" #include "handlerhelper.h" #include "storage/selectquerybuilder.h" #include "storage/itemqueryhelper.h" #include "storage/itemretrievalmanager.h" #include "storage/itemretrievalrequest.h" #include "storage/parthelper.h" #include "storage/parttypehelper.h" #include "storage/transaction.h" #include "utils.h" #include "intervalcheck.h" #include "agentmanagerinterface.h" #include "dbusconnectionpool.h" #include "tagfetchhelper.h" #include "relationfetch.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; #define ENABLE_FETCH_PROFILING 0 #if ENABLE_FETCH_PROFILING #define BEGIN_TIMER(name) \ QElapsedTimer name##Timer; \ name##Timer.start(); #define END_TIMER(name) \ const double name##Elapsed = name##Timer.nsecsElapsed() / 1000000.0; #define PROF_INC(name) \ ++name; #else #define BEGIN_TIMER(name) #define END_TIMER(name) #define PROF_INC(name) #endif FetchHelper::FetchHelper(Connection *connection, const Scope &scope, - const Protocol::FetchScope &fetchScope) + const Protocol::ItemFetchScope &fetchScope) : mConnection(connection) , mScope(scope) , mFetchScope(fetchScope) { std::fill(mItemQueryColumnMap, mItemQueryColumnMap + ItemQueryColumnCount, -1); } enum PartQueryColumns { PartQueryPimIdColumn, PartQueryTypeIdColumn, PartQueryDataColumn, PartQueryStorageColumn, PartQueryVersionColumn, PartQueryDataSizeColumn }; QSqlQuery FetchHelper::buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs) { ///TODO: merge with ItemQuery QueryBuilder partQuery(PimItem::tableName()); if (!partList.isEmpty() || allPayload || allAttrs) { partQuery.addJoin(QueryBuilder::InnerJoin, Part::tableName(), PimItem::idFullColumnName(), Part::pimItemIdFullColumnName()); partQuery.addColumn(PimItem::idFullColumnName()); partQuery.addColumn(Part::partTypeIdFullColumnName()); partQuery.addColumn(Part::dataFullColumnName()); partQuery.addColumn(Part::storageFullColumnName()); partQuery.addColumn(Part::versionFullColumnName()); partQuery.addColumn(Part::datasizeFullColumnName()); partQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!partList.isEmpty() || allPayload || allAttrs) { Query::Condition cond(Query::Or); for (const QByteArray &b : qAsConst(partList)) { if (b.startsWith("PLD") || b.startsWith("ATR")) { cond.addValueCondition(Part::partTypeIdFullColumnName(), Query::Equals, PartTypeHelper::fromFqName(b).id()); } } if (allPayload || allAttrs) { partQuery.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName()); if (allPayload) { cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("PLD")); } if (allAttrs) { cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("ATR")); } } partQuery.addCondition(cond); } ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), partQuery); if (!partQuery.exec()) { throw HandlerException("Unable to list item parts"); } partQuery.query().next(); } return partQuery.query(); } QSqlQuery FetchHelper::buildItemQuery() { QueryBuilder itemQuery(PimItem::tableName()); int column = 0; #define ADD_COLUMN(colName, colId) { itemQuery.addColumn( colName ); mItemQueryColumnMap[colId] = column++; } ADD_COLUMN(PimItem::idFullColumnName(), ItemQueryPimItemIdColumn); if (mFetchScope.fetchRemoteId()) { ADD_COLUMN(PimItem::remoteIdFullColumnName(), ItemQueryPimItemRidColumn) } ADD_COLUMN(PimItem::mimeTypeIdFullColumnName(), ItemQueryMimeTypeIdColumn) ADD_COLUMN(PimItem::revFullColumnName(), ItemQueryRevColumn) if (mFetchScope.fetchRemoteRevision()) { ADD_COLUMN(PimItem::remoteRevisionFullColumnName(), ItemQueryRemoteRevisionColumn) } if (mFetchScope.fetchSize()) { ADD_COLUMN(PimItem::sizeFullColumnName(), ItemQuerySizeColumn) } if (mFetchScope.fetchMTime()) { ADD_COLUMN(PimItem::datetimeFullColumnName(), ItemQueryDatetimeColumn) } ADD_COLUMN(PimItem::collectionIdFullColumnName(), ItemQueryCollectionIdColumn) if (mFetchScope.fetchGID()) { ADD_COLUMN(PimItem::gidFullColumnName(), ItemQueryPimItemGidColumn) } #undef ADD_COLUMN itemQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), itemQuery); if (mFetchScope.changedSince().isValid()) { itemQuery.addValueCondition(PimItem::datetimeFullColumnName(), Query::GreaterOrEqual, mFetchScope.changedSince().toUTC()); } if (!itemQuery.exec()) { throw HandlerException("Unable to list items"); } itemQuery.query().next(); return itemQuery.query(); } enum FlagQueryColumns { FlagQueryPimItemIdColumn, FlagQueryFlagIdColumn }; QSqlQuery FetchHelper::buildFlagQuery() { QueryBuilder flagQuery(PimItem::tableName()); flagQuery.addJoin(QueryBuilder::InnerJoin, PimItemFlagRelation::tableName(), PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName()); flagQuery.addColumn(PimItem::idFullColumnName()); flagQuery.addColumn(PimItemFlagRelation::rightFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), flagQuery); flagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!flagQuery.exec()) { throw HandlerException("Unable to retrieve item flags"); } flagQuery.query().next(); return flagQuery.query(); } enum TagQueryColumns { TagQueryItemIdColumn, TagQueryTagIdColumn, }; QSqlQuery FetchHelper::buildTagQuery() { QueryBuilder tagQuery(PimItem::tableName()); tagQuery.addJoin(QueryBuilder::InnerJoin, PimItemTagRelation::tableName(), PimItem::idFullColumnName(), PimItemTagRelation::leftFullColumnName()); tagQuery.addJoin(QueryBuilder::InnerJoin, Tag::tableName(), Tag::idFullColumnName(), PimItemTagRelation::rightFullColumnName()); tagQuery.addColumn(PimItem::idFullColumnName()); tagQuery.addColumn(Tag::idFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), tagQuery); tagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!tagQuery.exec()) { throw HandlerException("Unable to retrieve item tags"); } tagQuery.query().next(); return tagQuery.query(); } enum VRefQueryColumns { VRefQueryCollectionIdColumn, VRefQueryItemIdColumn }; QSqlQuery FetchHelper::buildVRefQuery() { QueryBuilder vRefQuery(PimItem::tableName()); vRefQuery.addJoin(QueryBuilder::LeftJoin, CollectionPimItemRelation::tableName(), CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::leftFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::rightFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), vRefQuery); vRefQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!vRefQuery.exec()) { throw HandlerException("Unable to retrieve virtual references"); } vRefQuery.query().next(); return vRefQuery.query(); } bool FetchHelper::isScopeLocal(const Scope &scope) { // The only agent allowed to override local scope is the Baloo Indexer if (!mConnection->sessionId().startsWith("akonadi_indexing_agent")) { return false; } // Get list of all resources that own all items in the scope QueryBuilder qb(PimItem::tableName(), QueryBuilder::Select); qb.setDistinct(true); qb.addColumn(Resource::nameFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); ItemQueryHelper::scopeToQuery(scope, mConnection->context(), qb); if (mConnection->context()->resource().isValid()) { qb.addValueCondition(Resource::nameFullColumnName(), Query::NotEquals, mConnection->context()->resource().name()); } if (!qb.exec()) { throw HandlerException("Failed to query database"); return false; } // If there is more than one resource, i.e. this is a fetch from multiple // collections, then don't bother and just return FALSE. This case is aimed // specifically on Baloo, which fetches items from each collection independently, // so it will pass this check. QSqlQuery query = qb.query(); if (query.size() != 1) { return false; } query.next(); const QString resourceName = query.value(0).toString(); org::freedesktop::Akonadi::AgentManager manager(DBus::serviceName(DBus::Control), QStringLiteral("/AgentManager"), DBusConnectionPool::threadConnection()); const QString typeIdentifier = manager.agentInstanceType(resourceName); const QVariantMap properties = manager.agentCustomProperties(typeIdentifier); return properties.value(QStringLiteral("HasLocalStorage"), false).toBool(); } bool FetchHelper::fetchItems() { BEGIN_TIMER(fetch) // retrieve missing parts // HACK: isScopeLocal() is a workaround for resources that have cache expiration // because when the cache expires, Baloo is not able to content of the items. So // we allow fetch of items that belong to local resources (like maildir) to ignore // cacheOnly and retrieve missing parts from the resource. However ItemRetriever // is painfully slow with many items and is generally designed to fetch a few // messages, not all of them. In the long term, we need a better way to do this. BEGIN_TIMER(itemRetriever) BEGIN_TIMER(scopeLocal) #if ENABLE_FETCH_PROFILING double scopeLocalElapsed = 0; #endif if (!mFetchScope.cacheOnly() || isScopeLocal(mScope)) { #if ENABLE_FETCH_PROFILING scopeLocalElapsed = scopeLocalTimer.elapsed(); #endif // trigger a collection sync if configured to do so triggerOnDemandFetch(); // Prepare for a call to ItemRetriever::exec(); // From a resource perspective the only parts that can be fetched are payloads. ItemRetriever retriever(mConnection); retriever.setScope(mScope); retriever.setRetrieveParts(mFetchScope.requestedPayloads()); retriever.setRetrieveFullPayload(mFetchScope.fullPayload()); retriever.setChangedSince(mFetchScope.changedSince()); if (!retriever.exec() && !mFetchScope.ignoreErrors()) { // There we go, retrieve the missing parts from the resource. if (mConnection->context()->resource().isValid()) { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1, resource %2) : %3") .arg(mConnection->context()->collectionId()) .arg(mConnection->context()->resource().id()) .arg(QString::fromLatin1(retriever.lastError()))); } else { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1) : %2") .arg(mConnection->context()->collectionId()) .arg(QString::fromLatin1(retriever.lastError()))); } } } END_TIMER(itemRetriever) BEGIN_TIMER(items) QSqlQuery itemQuery = buildItemQuery(); END_TIMER(items) // error if query did not find any item and scope is not listing items but // a request for a specific item if (!itemQuery.isValid()) { if (mFetchScope.ignoreErrors()) { return true; } switch (mScope.scope()) { case Scope::Uid: // fall through case Scope::Rid: // fall through case Scope::HierarchicalRid: // fall through case Scope::Gid: throw HandlerException("Item query returned empty result set"); break; default: break; } } // build part query if needed BEGIN_TIMER(parts) QSqlQuery partQuery(DataStore::self()->database()); if (!mFetchScope.requestedParts().isEmpty() || mFetchScope.fullPayload() || mFetchScope.allAttributes()) { partQuery = buildPartQuery(mFetchScope.requestedParts(), mFetchScope.fullPayload(), mFetchScope.allAttributes()); } END_TIMER(parts) // build flag query if needed BEGIN_TIMER(flags) QSqlQuery flagQuery(DataStore::self()->database()); if (mFetchScope.fetchFlags()) { flagQuery = buildFlagQuery(); } END_TIMER(flags) // build tag query if needed BEGIN_TIMER(tags) QSqlQuery tagQuery(DataStore::self()->database()); if (mFetchScope.fetchTags()) { tagQuery = buildTagQuery(); } END_TIMER(tags) BEGIN_TIMER(vRefs) QSqlQuery vRefQuery(DataStore::self()->database()); if (mFetchScope.fetchVirtualReferences()) { vRefQuery = buildVRefQuery(); } END_TIMER(vRefs) #if ENABLE_FETCH_PROFILING int itemsCount = 0; int flagsCount = 0; int partsCount = 0; int tagsCount = 0; int vRefsCount = 0; #endif BEGIN_TIMER(processing) QHash flagIdNameCache; QHash mimeTypeIdNameCache; QHash partTypeIdNameCache; while (itemQuery.isValid()) { PROF_INC(itemsCount) const qint64 pimItemId = extractQueryResult(itemQuery, ItemQueryPimItemIdColumn).toLongLong(); const int pimItemRev = extractQueryResult(itemQuery, ItemQueryRevColumn).toInt(); auto response = Protocol::FetchItemsResponsePtr::create(); response->setId(pimItemId); response->setRevision(pimItemRev); const qint64 mimeTypeId = extractQueryResult(itemQuery, ItemQueryMimeTypeIdColumn).toLongLong(); auto mtIter = mimeTypeIdNameCache.find(mimeTypeId); if (mtIter == mimeTypeIdNameCache.end()) { mtIter = mimeTypeIdNameCache.insert(mimeTypeId, MimeType::retrieveById(mimeTypeId).name()); } response->setMimeType(mtIter.value()); if (mFetchScope.fetchRemoteId()) { response->setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString()); } response->setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong()); if (mFetchScope.fetchSize()) { response->setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong()); } if (mFetchScope.fetchMTime()) { response->setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn))); } if (mFetchScope.fetchRemoteRevision()) { response->setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString()); } if (mFetchScope.fetchGID()) { response->setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString()); } if (mFetchScope.fetchFlags()) { QVector flags; while (flagQuery.isValid()) { const qint64 id = flagQuery.value(FlagQueryPimItemIdColumn).toLongLong(); if (id > pimItemId) { flagQuery.next(); continue; } else if (id < pimItemId) { break; } const qint64 flagId = flagQuery.value(FlagQueryFlagIdColumn).toLongLong(); auto flagNameIter = flagIdNameCache.find(flagId); if (flagNameIter == flagIdNameCache.end()) { flagNameIter = flagIdNameCache.insert(flagId, Flag::retrieveById(flagId).name().toUtf8()); } flags << flagNameIter.value(); flagQuery.next(); } response->setFlags(flags); } if (mFetchScope.fetchTags()) { QVector tagIds; QVector tags; //We don't take the fetch scope into account yet. It's either id only or the full tag. const bool fullTagsRequested = !mFetchScope.tagFetchScope().isEmpty(); while (tagQuery.isValid()) { PROF_INC(tagsCount) const qint64 id = tagQuery.value(TagQueryItemIdColumn).toLongLong(); if (id > pimItemId) { tagQuery.next(); continue; } else if (id < pimItemId) { break; } tagIds << tagQuery.value(TagQueryTagIdColumn).toLongLong(); tagQuery.next(); } tags.reserve(tagIds.count()); if (!fullTagsRequested) { for (qint64 tagId : qAsConst(tagIds)) { Protocol::FetchTagsResponse resp; resp.setId(tagId); tags << resp; } } else { for (qint64 tagId : qAsConst(tagIds)) { tags << *HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId)); } } response->setTags(tags); } if (mFetchScope.fetchVirtualReferences()) { QVector vRefs; while (vRefQuery.isValid()) { PROF_INC(vRefsCount) const qint64 id = vRefQuery.value(VRefQueryItemIdColumn).toLongLong(); if (id > pimItemId) { vRefQuery.next(); continue; } else if (id < pimItemId) { break; } vRefs << vRefQuery.value(VRefQueryCollectionIdColumn).toLongLong(); vRefQuery.next(); } response->setVirtualReferences(vRefs); } if (mFetchScope.fetchRelations()) { SelectQueryBuilder qb; Query::Condition condition; condition.setSubQueryMode(Query::Or); condition.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, pimItemId); condition.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, pimItemId); qb.addCondition(condition); qb.addGroupColumns(QStringList() << Relation::leftIdColumn() << Relation::rightIdColumn() << Relation::typeIdColumn() << Relation::remoteIdColumn()); if (!qb.exec()) { throw HandlerException("Unable to list item relations"); } QVector relations; const auto result = qb.result(); relations.reserve(result.size()); for (const Relation &rel : result) { relations << *HandlerHelper::fetchRelationsResponse(rel); } response->setRelations(relations); } - if (mFetchScope.ancestorDepth() != Protocol::FetchScope::NoAncestor) { + if (mFetchScope.ancestorDepth() != Protocol::ItemFetchScope::NoAncestor) { response->setAncestors(ancestorsForItem(response->parentId())); } bool skipItem = false; QVector cachedParts; QVector parts; while (partQuery.isValid()) { PROF_INC(partsCount) const qint64 id = partQuery.value(PartQueryPimIdColumn).toLongLong(); if (id > pimItemId) { partQuery.next(); continue; } else if (id < pimItemId) { break; } const qint64 partTypeId = partQuery.value(PartQueryTypeIdColumn).toLongLong(); auto ptIter = partTypeIdNameCache.find(partTypeId); if (ptIter == partTypeIdNameCache.end()) { ptIter = partTypeIdNameCache.insert(partTypeId, PartTypeHelper::fullName(PartType::retrieveById(partTypeId)).toUtf8()); } Protocol::PartMetaData metaPart; Protocol::StreamPayloadResponse partData; partData.setPayloadName(ptIter.value()); metaPart.setName(ptIter.value()); metaPart.setVersion(partQuery.value(PartQueryVersionColumn).toInt()); metaPart.setSize(partQuery.value(PartQueryDataSizeColumn).toLongLong()); const QByteArray data = Utils::variantToByteArray(partQuery.value(PartQueryDataColumn)); if (mFetchScope.checkCachedPayloadPartsOnly()) { if (!data.isEmpty()) { cachedParts << ptIter.value(); } partQuery.next(); } else { if (mFetchScope.ignoreErrors() && data.isEmpty()) { //We wanted the payload, couldn't get it, and are ignoring errors. Skip the item. //This is not an error though, it's fine to have empty payload parts (to denote existing but not cached parts) //qCDebug(AKONADISERVER_LOG) << "item" << id << "has an empty payload part in parttable for part" << partName; skipItem = true; break; } metaPart.setStorageType(static_cast( partQuery.value(PartQueryStorageColumn).toInt())); if (data.isEmpty()) { partData.setData(QByteArray("")); } else { partData.setData(data); } partData.setMetaData(metaPart); if (mFetchScope.requestedParts().contains(ptIter.value()) || mFetchScope.fullPayload() || mFetchScope.allAttributes()) { parts.append(partData); } partQuery.next(); } } response->setParts(parts); if (skipItem) { itemQuery.next(); continue; } if (mFetchScope.checkCachedPayloadPartsOnly()) { response->setCachedParts(cachedParts); } mConnection->sendResponse(response); itemQuery.next(); } END_TIMER(processing) // update atime (only if the payload was actually requested, otherwise a simple resource sync prevents cache clearing) BEGIN_TIMER(aTime) if (needsAccessTimeUpdate(mFetchScope.requestedParts()) || mFetchScope.fullPayload()) { updateItemAccessTime(); } END_TIMER(aTime) END_TIMER(fetch) #if ENABLE_FETCH_PROFILING qCDebug(AKONADISERVER_LOG) << "FetchHelper execution stats:"; qCDebug(AKONADISERVER_LOG) << "\tItems query:" << itemsElapsed << "ms," << itemsCount << " items in total"; qCDebug(AKONADISERVER_LOG) << "\tFlags query:" << flagsElapsed << "ms, " << flagsCount << " flags in total"; qCDebug(AKONADISERVER_LOG) << "\tParts query:" << partsElapsed << "ms, " << partsCount << " parts in total"; qCDebug(AKONADISERVER_LOG) << "\tTags query: " << tagsElapsed << "ms, " << tagsCount << " tags in total"; qCDebug(AKONADISERVER_LOG) << "\tVRefs query:" << vRefsElapsed << "ms, " << vRefsCount << " vRefs in total"; qCDebug(AKONADISERVER_LOG) << "\t------------"; qCDebug(AKONADISERVER_LOG) << "\tItem retriever:" << itemRetrieverElapsed << "ms (scope local:" << scopeLocalElapsed << "ms)"; qCDebug(AKONADISERVER_LOG) << "\tTotal query:" << (itemsElapsed + flagsElapsed + partsElapsed + tagsElapsed + vRefsElapsed) << "ms"; qCDebug(AKONADISERVER_LOG) << "\tTotal processing: " << processingElapsed << "ms"; qCDebug(AKONADISERVER_LOG) << "\tATime update:" << aTimeElapsed << "ms"; qCDebug(AKONADISERVER_LOG) << "\t============"; qCDebug(AKONADISERVER_LOG) << "\tTotal FETCH:" << fetchElapsed << "ms"; qCDebug(AKONADISERVER_LOG); qCDebug(AKONADISERVER_LOG); #endif return true; } bool FetchHelper::needsAccessTimeUpdate(const QVector &parts) { // TODO technically we should compare the part list with the cache policy of // the parent collection of the retrieved items, but that's kinda expensive // Only updating the atime if the full payload was requested is a good // approximation though. return parts.contains(AKONADI_PARAM_PLD_RFC822); } void FetchHelper::updateItemAccessTime() { Transaction transaction(mConnection->storageBackend(), QStringLiteral("update atime")); QueryBuilder qb(PimItem::tableName(), QueryBuilder::Update); qb.setColumnValue(PimItem::atimeColumn(), QDateTime::currentDateTimeUtc()); ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), qb); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Unable to update item access time"; } else { transaction.commit(); } } void FetchHelper::triggerOnDemandFetch() { if (mConnection->context()->collectionId() <= 0 || mFetchScope.cacheOnly()) { return; } Collection collection = mConnection->context()->collection(); // HACK: don't trigger on-demand syncing if the resource is the one triggering it if (mConnection->sessionId() == collection.resource().name().toLatin1()) { return; } DataStore *store = mConnection->storageBackend(); store->activeCachePolicy(collection); if (!collection.cachePolicySyncOnDemand()) { return; } if (AkonadiServer::instance()->intervalChecker()) { AkonadiServer::instance()->intervalChecker()->requestCollectionSync(collection); } } QVector FetchHelper::ancestorsForItem(Collection::Id parentColId) { - if (mFetchScope.ancestorDepth() == Protocol::FetchScope::NoAncestor || parentColId == 0) { + if (mFetchScope.ancestorDepth() == Protocol::ItemFetchScope::NoAncestor || parentColId == 0) { return QVector(); } const auto it = mAncestorCache.constFind(parentColId); if (it != mAncestorCache.cend()) { return *it; } QVector ancestors; Collection col = Collection::retrieveById(parentColId); - const int depthNum = mFetchScope.ancestorDepth() == Protocol::FetchScope::ParentAncestor ? 1 : INT_MAX; + const int depthNum = mFetchScope.ancestorDepth() == Protocol::ItemFetchScope::ParentAncestor ? 1 : INT_MAX; for (int i = 0; i < depthNum; ++i) { if (!col.isValid()) { Protocol::Ancestor ancestor; ancestor.setId(0); ancestors << ancestor; break; } Protocol::Ancestor ancestor; ancestor.setId(col.id()); ancestor.setRemoteId(col.remoteId()); ancestors << ancestor; col = col.parent(); } mAncestorCache.insert(parentColId, ancestors); return ancestors; } QVariant FetchHelper::extractQueryResult(const QSqlQuery &query, FetchHelper::ItemQueryColumns column) const { const int colId = mItemQueryColumnMap[column]; Q_ASSERT(colId >= 0); return query.value(colId); } diff --git a/src/server/handler/fetchhelper.h b/src/server/handler/fetchhelper.h index 95153628b..10262dea3 100644 --- a/src/server/handler/fetchhelper.h +++ b/src/server/handler/fetchhelper.h @@ -1,93 +1,93 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_FETCHHELPER_H #define AKONADI_FETCHHELPER_H #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include #include #include class FetchHelperTest; namespace Akonadi { namespace Server { class Connection; class FetchHelper : public QObject { Q_OBJECT public: - FetchHelper(Connection *connection, const Scope &scope, const Protocol::FetchScope &fetchScope); + FetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); bool fetchItems(); private: enum ItemQueryColumns { ItemQueryPimItemIdColumn, ItemQueryPimItemRidColumn, ItemQueryMimeTypeIdColumn, ItemQueryRevColumn, ItemQueryRemoteRevisionColumn, ItemQuerySizeColumn, ItemQueryDatetimeColumn, ItemQueryCollectionIdColumn, ItemQueryPimItemGidColumn, ItemQueryColumnCount }; void updateItemAccessTime(); void triggerOnDemandFetch(); QSqlQuery buildItemQuery(); QSqlQuery buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs); QSqlQuery buildFlagQuery(); QSqlQuery buildTagQuery(); QSqlQuery buildVRefQuery(); QVector ancestorsForItem(Collection::Id parentColId); static bool needsAccessTimeUpdate(const QVector &parts); QVariant extractQueryResult(const QSqlQuery &query, ItemQueryColumns column) const; bool isScopeLocal(const Scope &scope); static QByteArray tagsToByteArray(const Tag::List &tags); static QByteArray relationsToByteArray(const Relation::List &relations); private: Connection *mConnection = nullptr; QHash> mAncestorCache; Scope mScope; - Protocol::FetchScope mFetchScope; + Protocol::ItemFetchScope mFetchScope; int mItemQueryColumnMap[ItemQueryColumnCount]; friend class ::FetchHelperTest; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/search.h b/src/server/handler/search.h index 8c7b51ce0..8133d7f26 100644 --- a/src/server/handler/search.h +++ b/src/server/handler/search.h @@ -1,55 +1,55 @@ /*************************************************************************** * Copyright (C) 2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISEARCH_H #define AKONADISEARCH_H #include "handler.h" #include namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search commands. */ class Search : public Handler { Q_OBJECT public: bool parseStream() override; private Q_SLOTS: void slotResultsAvailable(const QSet &results); private: - Protocol::FetchScope mFetchScope; + Protocol::ItemFetchScope mFetchScope; QSet mAllResults; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/notificationsubscriber.cpp b/src/server/notificationsubscriber.cpp index 6f3e571f8..62c31ed03 100644 --- a/src/server/notificationsubscriber.cpp +++ b/src/server/notificationsubscriber.cpp @@ -1,684 +1,688 @@ /* Copyright (c) 2015 Daniel Vrátil 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 "notificationsubscriber.h" #include "akonadiserver_debug.h" #include "notificationmanager.h" #include "collectionreferencemanager.h" #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; QMimeDatabase NotificationSubscriber::sMimeDatabase; #define TRACE_NTF(x) //#define TRACE_NTF(x) qCDebug(AKONADISERVER_LOG) << mSubscriber << x NotificationSubscriber::NotificationSubscriber(NotificationManager *manager) : QObject() , mManager(manager) , mSocket(nullptr) , mAllMonitored(false) , mExclusive(false) , mNotificationDebugging(false) { } NotificationSubscriber::NotificationSubscriber(NotificationManager *manager, quintptr socketDescriptor) : NotificationSubscriber(manager) { mSocket = new QLocalSocket(this); connect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->setSocketDescriptor(socketDescriptor); const SchemaVersion schema = SchemaVersion::retrieveAll().first(); auto hello = Protocol::HelloResponsePtr::create(); hello->setServerName(QStringLiteral("Akonadi")); hello->setMessage(QStringLiteral("Not really IMAP server")); hello->setProtocolVersion(Protocol::version()); hello->setGeneration(schema.generation()); writeCommand(0, hello); } NotificationSubscriber::~NotificationSubscriber() { QMutexLocker locker(&mLock); if (mNotificationDebugging) { Q_EMIT notificationDebuggingChanged(false); } } void NotificationSubscriber::handleIncomingData() { while (mSocket->bytesAvailable() > (int) sizeof(qint64)) { QDataStream stream(mSocket); // Ignored atm qint64 tag = -1; stream >> tag; Protocol::CommandPtr cmd; try { cmd = Protocol::deserialize(mSocket); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); disconnectSubscriber(); return; } catch (const std::exception &e) { qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); disconnectSubscriber(); return; } if (cmd->type() == Protocol::Command::Invalid) { qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; disconnectSubscriber(); return; } switch (cmd->type()) { case Protocol::Command::CreateSubscription: registerSubscriber(Protocol::cmdCast(cmd)); writeCommand(tag, Protocol::CreateSubscriptionResponsePtr::create()); break; case Protocol::Command::ModifySubscription: if (mSubscriber.isEmpty()) { qCWarning(AKONADISERVER_LOG) << "Received ModifySubscription command before RegisterSubscriber"; disconnectSubscriber(); return; } modifySubscription(Protocol::cmdCast(cmd)); writeCommand(tag, Protocol::ModifySubscriptionResponsePtr::create()); break; case Protocol::Command::Logout: disconnectSubscriber(); break; default: qCWarning(AKONADISERVER_LOG) << "Invalid command" << cmd->type() << "received by NotificationSubscriber" << mSubscriber; disconnectSubscriber(); break; } } } void NotificationSubscriber::socketDisconnected() { qCDebug(AKONADISERVER_LOG) << "Subscriber" << mSubscriber << "disconnected"; disconnectSubscriber(); } void NotificationSubscriber::disconnectSubscriber() { QMutexLocker locker(&mLock); if (mManager) { auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); changeNtf->setSubscriber(mSubscriber); changeNtf->setSessionId(mSession); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); mManager->slotNotify({ changeNtf }); } disconnect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->close(); mManager->forgetSubscriber(this); deleteLater(); } void NotificationSubscriber::registerSubscriber(const Protocol::CreateSubscriptionCommand &command) { QMutexLocker locker(&mLock); qCDebug(AKONADISERVER_LOG) << "Subscriber" << this << "identified as" << command.subscriberName(); mSubscriber = command.subscriberName(); mSession = command.session(); if (mManager) { auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); changeNtf->setSubscriber(mSubscriber); changeNtf->setSessionId(mSession); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Add); mManager->slotNotify({ changeNtf }); } } void NotificationSubscriber::modifySubscription(const Protocol::ModifySubscriptionCommand &command) { QMutexLocker locker(&mLock); const auto modifiedParts = command.modifiedParts(); #define START_MONITORING(type) \ (modifiedParts & Protocol::ModifySubscriptionCommand::ModifiedParts( \ Protocol::ModifySubscriptionCommand::type | Protocol::ModifySubscriptionCommand::Add)) #define STOP_MONITORING(type) \ (modifiedParts & Protocol::ModifySubscriptionCommand::ModifiedParts( \ Protocol::ModifySubscriptionCommand::type | Protocol::ModifySubscriptionCommand::Remove)) #define APPEND(set, newItems) \ Q_FOREACH (const auto &entity, newItems) { \ set.insert(entity); \ } #define REMOVE(set, items) \ Q_FOREACH (const auto &entity, items) { \ set.remove(entity); \ } if (START_MONITORING(Types)) { APPEND(mMonitoredTypes, command.startMonitoringTypes()) } if (STOP_MONITORING(Types)) { REMOVE(mMonitoredTypes, command.stopMonitoringTypes()) } if (START_MONITORING(Collections)) { APPEND(mMonitoredCollections, command.startMonitoringCollections()) } if (STOP_MONITORING(Collections)) { REMOVE(mMonitoredCollections, command.stopMonitoringCollections()) } if (START_MONITORING(Items)) { APPEND(mMonitoredItems, command.startMonitoringItems()) } if (STOP_MONITORING(Items)) { REMOVE(mMonitoredItems, command.stopMonitoringItems()) } if (START_MONITORING(Tags)) { APPEND(mMonitoredTags, command.startMonitoringTags()) } if (STOP_MONITORING(Tags)) { REMOVE(mMonitoredTags, command.stopMonitoringTags()) } if (START_MONITORING(Resources)) { APPEND(mMonitoredResources, command.startMonitoringResources()) } if (STOP_MONITORING(Resources)) { REMOVE(mMonitoredResources, command.stopMonitoringResources()) } if (START_MONITORING(MimeTypes)) { APPEND(mMonitoredMimeTypes, command.startMonitoringMimeTypes()) } if (STOP_MONITORING(MimeTypes)) { REMOVE(mMonitoredMimeTypes, command.stopMonitoringMimeTypes()) } if (START_MONITORING(Sessions)) { APPEND(mIgnoredSessions, command.startIgnoringSessions()) } if (STOP_MONITORING(Sessions)) { REMOVE(mIgnoredSessions, command.stopIgnoringSessions()) } if (modifiedParts & Protocol::ModifySubscriptionCommand::AllFlag) { mAllMonitored = command.allMonitored(); } if (modifiedParts & Protocol::ModifySubscriptionCommand::ExclusiveFlag) { mExclusive = command.isExclusive(); } + if (modifiedParts & Protocol::ModifySubscriptionCommand::ItemFetchScope) { + mFetchScope = command.itemFetchScope(); + } if (mManager) { if (modifiedParts & Protocol::ModifySubscriptionCommand::Types) { // Did the caller just subscribed to subscription changes? if (command.startMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::SubscriptionChanges)) { // If yes, then send them list of all existing subscribers Q_FOREACH (const NotificationSubscriber *subscriber, mManager->mSubscribers) { // Send them back to caller if (subscriber) { QMetaObject::invokeMethod(this, "notify", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, subscriber->toChangeNotification())); } } } if (command.startMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::ChangeNotifications)) { if (!mNotificationDebugging) { mNotificationDebugging = true; Q_EMIT notificationDebuggingChanged(true); } } else if (command.stopMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::ChangeNotifications)) { if (mNotificationDebugging) { mNotificationDebugging = false; Q_EMIT notificationDebuggingChanged(false); } } } // Emit subscription change notification auto changeNtf = toChangeNotification(); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Modify); mManager->slotNotify({ changeNtf }); } #undef START_MONITORING #undef STOP_MONITORING #undef APPEND #undef REMOVE } Protocol::SubscriptionChangeNotificationPtr NotificationSubscriber::toChangeNotification() const { // Assumes mLock being locked by caller auto ntf = Protocol::SubscriptionChangeNotificationPtr::create(); ntf->setSessionId(mSession); ntf->setSubscriber(mSubscriber); ntf->setOperation(Protocol::SubscriptionChangeNotification::Add); ntf->setCollections(mMonitoredCollections); ntf->setItems(mMonitoredItems); ntf->setTags(mMonitoredTags); ntf->setTypes(mMonitoredTypes); ntf->setMimeTypes(mMonitoredMimeTypes); ntf->setResources(mMonitoredResources); ntf->setIgnoredSessions(mIgnoredSessions); ntf->setAllMonitored(mAllMonitored); ntf->setExclusive(mExclusive); + ntf->setItemFetchScope(mFetchScope); return ntf; } bool NotificationSubscriber::isCollectionMonitored(Entity::Id id) const { // Assumes mLock being locked by caller if (id < 0) { return false; } else if (mMonitoredCollections.contains(id)) { return true; } else if (mMonitoredCollections.contains(0)) { return true; } return false; } bool NotificationSubscriber::isMimeTypeMonitored(const QString &mimeType) const { // Assumes mLock being locked by caller const QMimeType mt = sMimeDatabase.mimeTypeForName(mimeType); if (mMonitoredMimeTypes.contains(mimeType)) { return true; } const QStringList lst = mt.aliases(); for (const QString &alias : lst) { if (mMonitoredMimeTypes.contains(alias)) { return true; } } return false; } bool NotificationSubscriber::isMoveDestinationResourceMonitored(const Protocol::ItemChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.operation() != Protocol::ItemChangeNotification::Move) { return false; } return mMonitoredResources.contains(msg.destinationResource()); } bool NotificationSubscriber::isMoveDestinationResourceMonitored(const Protocol::CollectionChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.operation() != Protocol::CollectionChangeNotification::Move) { return false; } return mMonitoredResources.contains(msg.destinationResource()); } bool NotificationSubscriber::acceptsItemNotification(const Protocol::ItemChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.items().isEmpty()) { return false; } if (CollectionReferenceManager::instance()->isReferenced(msg.parentCollection())) { //We always want notifications that affect the parent resource (like an item added to a referenced collection) const bool notificationForParentResource = (mSession == msg.resource()); const bool accepts = mExclusive || isCollectionMonitored(msg.parentCollection()) || isMoveDestinationResourceMonitored(msg) || notificationForParentResource; TRACE_NTF("ACCEPTS ITEM: parent col referenced" << "exclusive:" << mExclusive << "," << "parent monitored:" << isCollectionMonitored(notification.parentCollection()) << "," << "destination monitored:" << isMoveDestinationResourceMonitored(notification) << "," << "ntf for parent resource:" << notificationForParentResource << ":" << "ACCEPTED:" << accepts); return accepts; } if (mAllMonitored) { TRACE_NTF("ACCEPTS ITEM: all monitored"); return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::ItemChanges)) { TRACE_NTF("ACCEPTS ITEM: REJECTED - Item changes not monitored"); return false; } // we have a resource or mimetype filter if (!mMonitoredResources.isEmpty() || !mMonitoredMimeTypes.isEmpty()) { if (mMonitoredResources.contains(msg.resource())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - resource monitored"); return true; } if (isMoveDestinationResourceMonitored(msg)) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: move destination monitored"); return true; } Q_FOREACH (const auto &item, msg.items()) { if (isMimeTypeMonitored(item.mimeType)) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - mimetype monitored"); return true; } } TRACE_NTF("ACCEPTS ITEM: REJECTED: resource nor mimetype monitored"); return false; } // we explicitly monitor that item or the collections it's in Q_FOREACH (const auto &item, msg.items()) { if (mMonitoredItems.contains(item.id)) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: item explicitly monitored"); return true; } } if (isCollectionMonitored(msg.parentCollection())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: parent collection monitored"); return true; } if (isCollectionMonitored(msg.parentDestCollection())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: destination collection monitored"); return true; } TRACE_NTF("ACCEPTS ITEM: REJECTED"); return false; } bool NotificationSubscriber::acceptsCollectionNotification(const Protocol::CollectionChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.id() < 0) { return false; } // HACK: We need to dispatch notifications about disabled collections to SOME // agents (that's what we have the exclusive subscription for) - but because // querying each Collection from database would be expensive, we use the // metadata hack to transfer this information from NotificationCollector if (msg.metadata().contains("DISABLED") && (msg.operation() != Protocol::CollectionChangeNotification::Unsubscribe) && !msg.changedParts().contains("ENABLED")) { // Exclusive subscriber always gets it if (mExclusive) { return true; } //Deliver the notification if referenced from this session if (CollectionReferenceManager::instance()->isReferenced(msg.id(), mSession)) { return true; } //Exclusive subscribers still want the notification if (mExclusive && CollectionReferenceManager::instance()->isReferenced(msg.id())) { return true; } //The session belonging to this monitor referenced or dereferenced the collection. We always want this notification. //The referencemanager no longer holds a reference, so we have to check this way. if (msg.changedParts().contains(AKONADI_PARAM_REFERENCED) && mSession == msg.sessionId()) { return true; } // If the collection is not referenced, monitored or the subscriber is not // exclusive (i.e. if we got here), then the subscriber does not care about // this one, so drop it return false; } if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::CollectionChanges)) { return false; } // we have a resource filter if (!mMonitoredResources.isEmpty()) { const bool resourceMatches = mMonitoredResources.contains(msg.resource()) || isMoveDestinationResourceMonitored(msg); // a bit hacky, but match the behaviour from the item case, // if resource is the only thing we are filtering on, stop here, and if the resource filter matched, of course if (mMonitoredMimeTypes.isEmpty() || resourceMatches) { return resourceMatches; } // else continue } // we explicitly monitor that colleciton, or all of them if (isCollectionMonitored(msg.id())) { return true; } return isCollectionMonitored(msg.parentCollection()) || isCollectionMonitored(msg.parentDestCollection()); } bool NotificationSubscriber::acceptsTagNotification(const Protocol::TagChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.id() < 0) { return false; } // Special handling for Tag removal notifications: When a Tag is removed, // a notification is emitted for each Resource that owns the tag (i.e. // each resource that owns a Tag RID - Tag RIDs are resource-specific). // Additionally then we send one more notification without any RID that is // destined for regular applications (which don't know anything about Tag RIDs) if (msg.operation() == Protocol::TagChangeNotification::Remove) { // HACK: Since have no way to determine which resource this NotificationSource // belongs to, we are abusing the fact that each resource ignores it's own // main session, which is called the same name as the resource. // If there are any ignored sessions, but this notification does not have // a specific resource set, then we ignore it, as this notification is // for clients, not resources (does not have tag RID) if (!mIgnoredSessions.isEmpty() && msg.resource().isEmpty()) { return false; } // If this source ignores a session (i.e. we assume it is a resource), // but this notification is for another resource, then we ignore it if (!msg.resource().isEmpty() && !mIgnoredSessions.contains(msg.resource())) { return false; } // Now we got here, which means that this notification either has empty // resource, i.e. it is destined for a client applications, or it's // destined for resource that we *think* (see the hack above) this // NotificationSource belongs too. Which means we approve this notification, // but it can still be discarded in the generic Tag notification filter // below } if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::TagChanges)) { return false; } if (mMonitoredTags.isEmpty()) { return true; } if (mMonitoredTags.contains(msg.id())) { return true; } return false; } bool NotificationSubscriber::acceptsRelationNotification(const Protocol::RelationChangeNotification &msg) const { // Assumes mLock being locked by caller Q_UNUSED(msg); if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::RelationChanges)) { return false; } return true; } bool NotificationSubscriber::acceptsSubscriptionNotification(const Protocol::SubscriptionChangeNotification &msg) const { // Assumes mLock being locked by caller Q_UNUSED(msg); // Unlike other types, subscription notifications must be explicitly enabled // by caller and are excluded from "monitor all" as well return mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::SubscriptionChanges); } bool NotificationSubscriber::acceptsDebugChangeNotification(const Protocol::DebugChangeNotification &msg) const { // Assumes mLock being locked by caller // We should never end up sending debug notification about a debug notification. // This could get very messy very quickly... Q_ASSERT(msg.notification()->type() != Protocol::Command::DebugChangeNotification); if (msg.notification()->type() == Protocol::Command::DebugChangeNotification) { return false; } // Unlike other types, debug change notifications must be explicitly enabled // by caller and are excluded from "monitor all" as well return mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::ChangeNotifications); } bool NotificationSubscriber::acceptsNotification(const Protocol::ChangeNotification &msg) const { // Assumes mLock being locked // Uninitialized subscriber gets nothing if (mSubscriber.isEmpty()) { return false; } // session is ignored // TODO: Should this afect SubscriptionChangeNotification and DebugChangeNotification? if (mIgnoredSessions.contains(msg.sessionId())) { return false; } switch (msg.type()) { case Protocol::Command::ItemChangeNotification: return acceptsItemNotification(static_cast(msg)); case Protocol::Command::CollectionChangeNotification: return acceptsCollectionNotification(static_cast(msg)); case Protocol::Command::TagChangeNotification: return acceptsTagNotification(static_cast(msg)); case Protocol::Command::RelationChangeNotification: return acceptsRelationNotification(static_cast(msg)); case Protocol::Command::SubscriptionChangeNotification: return acceptsSubscriptionNotification(static_cast(msg)); case Protocol::Command::DebugChangeNotification: return acceptsDebugChangeNotification(static_cast(msg)); default: qCDebug(AKONADISERVER_LOG) << "Received invalid change notification!"; return false; } } bool NotificationSubscriber::notify(const Protocol::ChangeNotificationPtr ¬ification) { // Guard against this object being deleted while we are waiting for the lock QPointer ptr(this); QMutexLocker locker(&mLock); if (!ptr) { return false; } if (acceptsNotification(*notification)) { QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, notification)); return true; } return false; } void NotificationSubscriber::writeNotification(const Protocol::ChangeNotificationPtr ¬ification) { // tag chosen by fair dice roll writeCommand(4, notification); } void NotificationSubscriber::writeCommand(qint64 tag, const Protocol::CommandPtr &cmd) { Q_ASSERT(QThread::currentThread() == thread()); QDataStream stream(mSocket); stream << tag; try { Protocol::serialize(mSocket, cmd); if (!mSocket->waitForBytesWritten()) { if (mSocket->state() == QLocalSocket::ConnectedState) { qCWarning(AKONADISERVER_LOG) << "Notification socket write timeout!"; } else { // client has disconnected, just discard the message } } } catch (const ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "Notification protocol exception:" << e.what(); } } diff --git a/src/server/notificationsubscriber.h b/src/server/notificationsubscriber.h index 82b4a7ece..c4786cb72 100644 --- a/src/server/notificationsubscriber.h +++ b/src/server/notificationsubscriber.h @@ -1,120 +1,121 @@ /* Copyright (c) 2015 Daniel Vrátil 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. */ #ifndef NOTIFICATIONSUBSCRIBER_H #define NOTIFICATIONSUBSCRIBER_H #include #include #include #include #include #include "entities.h" class QLocalSocket; namespace Akonadi { namespace Server { class NotificationManager; class NotificationSubscriber : public QObject { Q_OBJECT public: explicit NotificationSubscriber(NotificationManager *manager, quintptr socketDescriptor); ~NotificationSubscriber(); inline QByteArray subscriber() const { return mSubscriber; } QLocalSocket *socket() const { return mSocket; } void handleIncomingData(); public Q_SLOTS: bool notify(const Akonadi::Protocol::ChangeNotificationPtr ¬ification); private Q_SLOTS: void socketDisconnected(); Q_SIGNALS: void notificationDebuggingChanged(bool enabled); protected: void registerSubscriber(const Protocol::CreateSubscriptionCommand &command); void modifySubscription(const Protocol::ModifySubscriptionCommand &command); void disconnectSubscriber(); private: bool acceptsNotification(const Protocol::ChangeNotification ¬ification) const; bool acceptsItemNotification(const Protocol::ItemChangeNotification ¬ification) const; bool acceptsCollectionNotification(const Protocol::CollectionChangeNotification ¬ification) const; bool acceptsTagNotification(const Protocol::TagChangeNotification ¬ification) const; bool acceptsRelationNotification(const Protocol::RelationChangeNotification ¬ification) const; bool acceptsSubscriptionNotification(const Protocol::SubscriptionChangeNotification ¬ification) const; bool acceptsDebugChangeNotification(const Protocol::DebugChangeNotification ¬ification) const; bool isCollectionMonitored(Entity::Id id) const; bool isMimeTypeMonitored(const QString &mimeType) const; bool isMoveDestinationResourceMonitored(const Protocol::ItemChangeNotification &msg) const; bool isMoveDestinationResourceMonitored(const Protocol::CollectionChangeNotification &msg) const; Protocol::SubscriptionChangeNotificationPtr toChangeNotification() const; protected Q_SLOTS: virtual void writeNotification(const Akonadi::Protocol::ChangeNotificationPtr ¬ification); protected: explicit NotificationSubscriber(NotificationManager *manager = nullptr); void writeCommand(qint64 tag, const Protocol::CommandPtr &cmd); mutable QMutex mLock; NotificationManager *mManager = nullptr; QLocalSocket *mSocket = nullptr; QByteArray mSubscriber; QSet mMonitoredCollections; QSet mMonitoredItems; QSet mMonitoredTags; QSet mMonitoredTypes; QSet mMonitoredMimeTypes; QSet mMonitoredResources; QSet mIgnoredSessions; QByteArray mSession; + Protocol::ItemFetchScope mFetchScope; bool mAllMonitored; bool mExclusive; bool mNotificationDebugging; static QMimeDatabase sMimeDatabase; }; } // namespace Server } // namespace Akonadi #endif