diff --git a/autotests/libs/protocolhelpertest.cpp b/autotests/libs/protocolhelpertest.cpp index 93a04f432..56dd3a57d 100644 --- a/autotests/libs/protocolhelpertest.cpp +++ b/autotests/libs/protocolhelpertest.cpp @@ -1,332 +1,333 @@ /* 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" +#include "attributestorage.cpp" using namespace Akonadi; Q_DECLARE_METATYPE(Scope) Q_DECLARE_METATYPE(QVector) 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"); { 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::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::ItemFetchScope fs; fs.setFetch(Protocol::ItemFetchScope::Flags | Protocol::ItemFetchScope::Size); QTest::newRow("minimal") << scope << fs; } } void testItemFetchScopeToProtocol() { QFETCH(ItemFetchScope, scope); 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/libs/tagtest.cpp b/autotests/libs/tagtest.cpp index ef1ea15f3..4a0601df1 100644 --- a/autotests/libs/tagtest.cpp +++ b/autotests/libs/tagtest.cpp @@ -1,895 +1,952 @@ /* 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 "test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; class TagTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testTag(); void testCreateFetch(); void testRID(); void testDelete(); void testDeleteRIDIsolation(); void testModify(); void testModifyFromResource(); void testCreateMerge(); void testAttributes(); void testTagItem(); void testCreateItem(); void testRIDIsolation(); void testFetchTagIdWithItem(); void testFetchFullTagWithItem(); void testModifyItemWithTagByGID(); void testModifyItemWithTagByRID(); void testMonitor(); void testTagAttributeConfusionBug(); void testFetchItemsByTag(); + void tagModifyJobShouldOnlySendModifiedAttributes(); }; void TagTest::initTestCase() { AkonadiTest::checkTestIsIsolated(); AkonadiTest::setAllResourcesOffline(); AttributeFactory::registerAttribute(); qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType(); // Delete the default Knut tag - it's interfering with this test TagFetchJob *fetchJob = new TagFetchJob(this); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->tags().first(), this); AKVERIFYEXEC(deleteJob); } void TagTest::testTag() { Tag tag1; Tag tag2; // Invalid tags are equal QVERIFY(tag1 == tag2); // Invalid tags with different GIDs are not equal tag1.setGid("GID1"); QVERIFY(tag1 != tag2); tag2.setGid("GID2"); QVERIFY(tag1 != tag2); // Invalid tags with equal GIDs are equal tag1.setGid("GID2"); QVERIFY(tag1 == tag2); // Valid tags with different IDs are not equal tag1 = Tag(1); tag2 = Tag(2); QVERIFY(tag1 != tag2); // Valid tags with different IDs and equal GIDs are still not equal tag1.setGid("GID1"); tag2.setGid("GID1"); QVERIFY(tag1 != tag2); // Valid tags with equal ID are equal regardless of GIDs tag2 = Tag(1); tag2.setGid("GID2"); QVERIFY(tag1 == tag2); } void TagTest::testCreateFetch() { Tag tag; tag.setGid("gid"); tag.setType("mytype"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); { TagFetchJob *fetchJob = new TagFetchJob(this); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid")); QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype")); TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->tags().first(), this); AKVERIFYEXEC(deleteJob); } { TagFetchJob *fetchJob = new TagFetchJob(this); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 0); } } void TagTest::testRID() { { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0")); AKVERIFYEXEC(select); } Tag tag; tag.setGid("gid"); tag.setType("mytype"); tag.setRemoteId("rid"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); { TagFetchJob *fetchJob = new TagFetchJob(this); fetchJob->fetchScope().setFetchRemoteId(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid")); QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype")); QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid")); TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->tags().first(), this); AKVERIFYEXEC(deleteJob); } { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("")); AKVERIFYEXEC(select); } } void TagTest::testRIDIsolation() { { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0")); AKVERIFYEXEC(select); } Tag tag; tag.setGid("gid"); tag.setType("mytype"); tag.setRemoteId("rid_0"); TagCreateJob *createJob = new TagCreateJob(tag, this); AKVERIFYEXEC(createJob); QVERIFY(createJob->tag().isValid()); qint64 tagId; { TagFetchJob *fetchJob = new TagFetchJob(this); fetchJob->fetchScope().setFetchRemoteId(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().count(), 1); QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid")); QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype")); QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid_0")); tagId = fetchJob->tags().first().id(); } { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_1")); AKVERIFYEXEC(select); } tag.setRemoteId("rid_1"); createJob = new TagCreateJob(tag, this); createJob->setMergeIfExisting(true); AKVERIFYEXEC(createJob); QVERIFY(createJob->tag().isValid()); { TagFetchJob *fetchJob = new TagFetchJob(this); fetchJob->fetchScope().setFetchRemoteId(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().count(), 1); QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid")); QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype")); QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid_1")); QCOMPARE(fetchJob->tags().first().id(), tagId); } TagDeleteJob *deleteJob = new TagDeleteJob(Tag(tagId), this); AKVERIFYEXEC(deleteJob); { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("")); AKVERIFYEXEC(select); } } void TagTest::testDelete() { Akonadi::Monitor monitor; monitor.setTypeMonitored(Monitor::Tags); QSignalSpy spy(&monitor, SIGNAL(tagRemoved(Akonadi::Tag))); Tag tag1; { tag1.setGid("tag1"); TagCreateJob *createjob = new TagCreateJob(tag1, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag1 = createjob->tag(); } Tag tag2; { tag2.setGid("tag2"); TagCreateJob *createjob = new TagCreateJob(tag2, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag2 = createjob->tag(); } { TagDeleteJob *deleteJob = new TagDeleteJob(tag1, this); AKVERIFYEXEC(deleteJob); } { TagFetchJob *fetchJob = new TagFetchJob(this); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QCOMPARE(fetchJob->tags().first().gid(), tag2.gid()); } { TagDeleteJob *deleteJob = new TagDeleteJob(tag2, this); AKVERIFYEXEC(deleteJob); } // Collect Remove notification, so that they don't interfere with testDeleteRIDIsolation QTRY_VERIFY(!spy.isEmpty()); } void TagTest::testDeleteRIDIsolation() { Tag tag; tag.setGid("gid"); tag.setType("mytype"); tag.setRemoteId("rid_0"); { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0")); AKVERIFYEXEC(select); TagCreateJob *createJob = new TagCreateJob(tag, this); AKVERIFYEXEC(createJob); QVERIFY(createJob->tag().isValid()); tag.setId(createJob->tag().id()); } tag.setRemoteId("rid_1"); { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_1")); AKVERIFYEXEC(select); TagCreateJob *createJob = new TagCreateJob(tag, this); createJob->setMergeIfExisting(true); AKVERIFYEXEC(createJob); QVERIFY(createJob->tag().isValid()); } Akonadi::Monitor monitor; monitor.setTypeMonitored(Akonadi::Monitor::Tags); QSignalSpy signalSpy(&monitor, SIGNAL(tagRemoved(Akonadi::Tag))); TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); // Other tests notifications might interfere due to notification compression on server QTRY_VERIFY(signalSpy.count() >= 1); Tag removedTag; while (!signalSpy.isEmpty()) { const Tag t = signalSpy.takeFirst().takeFirst().value(); if (t.id() == tag.id()) { removedTag = t; break; } } QVERIFY(removedTag.isValid()); QVERIFY(removedTag.remoteId().isEmpty()); { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral(""), this); AKVERIFYEXEC(select); } } void TagTest::testModify() { Tag tag; { tag.setGid("gid"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag = createjob->tag(); } //We can add an attribute { Akonadi::TagAttribute *attr = tag.attribute(Tag::AddIfMissing); attr->setDisplayName(QStringLiteral("display name")); - tag.addAttribute(attr); tag.setParent(Tag(0)); tag.setType("mytype"); TagModifyJob *modJob = new TagModifyJob(tag, this); AKVERIFYEXEC(modJob); TagFetchJob *fetchJob = new TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QVERIFY(fetchJob->tags().first().hasAttribute()); } //We can update an attribute { Akonadi::TagAttribute *attr = tag.attribute(Tag::AddIfMissing); attr->setDisplayName(QStringLiteral("display name2")); TagModifyJob *modJob = new TagModifyJob(tag, this); AKVERIFYEXEC(modJob); TagFetchJob *fetchJob = new TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QVERIFY(fetchJob->tags().first().hasAttribute()); QCOMPARE(fetchJob->tags().first().attribute()->displayName(), attr->displayName()); } //We can clear an attribute { tag.removeAttribute(); TagModifyJob *modJob = new TagModifyJob(tag, this); AKVERIFYEXEC(modJob); TagFetchJob *fetchJob = new TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QVERIFY(!fetchJob->tags().first().hasAttribute()); } TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } void TagTest::testModifyFromResource() { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0")); AKVERIFYEXEC(select); Tag tag; { tag.setGid("gid"); tag.setRemoteId("rid"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag = createjob->tag(); } { tag.setRemoteId(QByteArray("")); TagModifyJob *modJob = new TagModifyJob(tag, this); AKVERIFYEXEC(modJob); // The tag is removed on the server, because we just removed the last // RemoteID TagFetchJob *fetchJob = new TagFetchJob(this); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 0); } } void TagTest::testCreateMerge() { Tag tag; { tag.setGid("gid"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag = createjob->tag(); } { Tag tag2; tag2.setGid("gid"); TagCreateJob *createjob = new TagCreateJob(tag2, this); createjob->setMergeIfExisting(true); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); QCOMPARE(createjob->tag().id(), tag.id()); } TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } void TagTest::testAttributes() { Tag tag; { tag.setGid("gid2"); TagAttribute *attr = tag.attribute(Tag::AddIfMissing); attr->setDisplayName(QStringLiteral("name")); attr->setInToolbar(true); - tag.addAttribute(attr); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag = createjob->tag(); { TagFetchJob *fetchJob = new TagFetchJob(createjob->tag(), this); fetchJob->fetchScope().fetchAttribute(); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QVERIFY(fetchJob->tags().first().hasAttribute()); //we need to clone because the returned attribute is just a reference and destroyed on the next line //FIXME we should find a better solution for this (like returning a smart pointer or value object) QScopedPointer tagAttr(fetchJob->tags().first().attribute()->clone()); QVERIFY(tagAttr); QCOMPARE(tagAttr->displayName(), QStringLiteral("name")); QCOMPARE(tagAttr->inToolbar(), true); } } //Try fetching multiple items Tag tag2; { tag2.setGid("gid22"); TagAttribute *attr = tag.attribute(Tag::AddIfMissing)->clone(); attr->setDisplayName(QStringLiteral("name2")); attr->setInToolbar(true); tag2.addAttribute(attr); TagCreateJob *createjob = new TagCreateJob(tag2, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); tag2 = createjob->tag(); { TagFetchJob *fetchJob = new TagFetchJob(Tag::List() << tag << tag2, this); fetchJob->fetchScope().fetchAttribute(); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 2); QVERIFY(fetchJob->tags().at(0).hasAttribute()); QVERIFY(fetchJob->tags().at(1).hasAttribute()); } } TagDeleteJob *deleteJob = new TagDeleteJob(Tag::List() << tag << tag2, this); AKVERIFYEXEC(deleteJob); } void TagTest::testTagItem() { Akonadi::Monitor monitor; monitor.itemFetchScope().setFetchTags(true); monitor.setAllMonitored(true); const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); Tag tag; { TagCreateJob *createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this); AKVERIFYEXEC(createjob); tag = createjob->tag(); } Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); } item1.setTag(tag); QSignalSpy tagsSpy(&monitor, SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet,QSet))); QVERIFY(tagsSpy.isValid()); ItemModifyJob *modJob = new ItemModifyJob(item1, this); AKVERIFYEXEC(modJob); QTRY_VERIFY(tagsSpy.count() >= 1); QTRY_COMPARE(tagsSpy.last().first().value().first().id(), item1.id()); QTRY_COMPARE(tagsSpy.last().at(1).value< QSet >().size(), 1); //1 added tag ItemFetchJob *fetchJob = new ItemFetchJob(item1, this); fetchJob->fetchScope().setFetchTags(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().tags().size(), 1); TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } void TagTest::testCreateItem() { // Akonadi::Monitor monitor; // monitor.itemFetchScope().setFetchTags(true); // monitor.setAllMonitored(true); const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); Tag tag; { TagCreateJob *createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this); AKVERIFYEXEC(createjob); tag = createjob->tag(); } // QSignalSpy tagsSpy(&monitor, SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet,QSet))); // QVERIFY(tagsSpy.isValid()); Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); item1.setTag(tag); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); } // QTRY_VERIFY(tagsSpy.count() >= 1); // QTest::qWait(10); // kDebug() << tagsSpy.count(); // QTRY_COMPARE(tagsSpy.last().first().value().first().id(), item1.id()); // QTRY_COMPARE(tagsSpy.last().at(1).value< QSet >().size(), 1); //1 added tag ItemFetchJob *fetchJob = new ItemFetchJob(item1, this); fetchJob->fetchScope().setFetchTags(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().tags().size(), 1); TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } void TagTest::testFetchTagIdWithItem() { const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); Tag tag; { TagCreateJob *createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this); AKVERIFYEXEC(createjob); tag = createjob->tag(); } Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); item1.setTag(tag); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); } ItemFetchJob *fetchJob = new ItemFetchJob(item1, this); fetchJob->fetchScope().setFetchTags(true); fetchJob->fetchScope().tagFetchScope().setFetchIdOnly(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().tags().size(), 1); Tag t = fetchJob->items().first().tags().first(); QCOMPARE(t.id(), tag.id()); QVERIFY(t.gid().isEmpty()); TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } void TagTest::testFetchFullTagWithItem() { const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); Tag tag; { TagCreateJob *createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this); AKVERIFYEXEC(createjob); tag = createjob->tag(); } Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); //FIXME This should also be possible with create, but isn't item1.setTag(tag); } ItemModifyJob *modJob = new ItemModifyJob(item1, this); AKVERIFYEXEC(modJob); ItemFetchJob *fetchJob = new ItemFetchJob(item1, this); fetchJob->fetchScope().setFetchTags(true); fetchJob->fetchScope().tagFetchScope().setFetchIdOnly(false); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().tags().size(), 1); Tag t = fetchJob->items().first().tags().first(); QCOMPARE(t, tag); QVERIFY(!t.gid().isEmpty()); TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } void TagTest::testModifyItemWithTagByGID() { const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); { Tag tag; tag.setGid("gid2"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); } Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); } Tag tag; tag.setGid("gid2"); item1.setTag(tag); ItemModifyJob *modJob = new ItemModifyJob(item1, this); AKVERIFYEXEC(modJob); ItemFetchJob *fetchJob = new ItemFetchJob(item1, this); fetchJob->fetchScope().setFetchTags(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().tags().size(), 1); TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->items().first().tags().first(), this); AKVERIFYEXEC(deleteJob); } void TagTest::testModifyItemWithTagByRID() { { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0")); AKVERIFYEXEC(select); } const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); Tag tag3; { tag3.setGid("gid3"); tag3.setRemoteId("rid3"); TagCreateJob *createjob = new TagCreateJob(tag3, this); AKVERIFYEXEC(createjob); tag3 = createjob->tag(); } Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); } Tag tag; tag.setRemoteId("rid2"); item1.setTag(tag); ItemModifyJob *modJob = new ItemModifyJob(item1, this); AKVERIFYEXEC(modJob); ItemFetchJob *fetchJob = new ItemFetchJob(item1, this); fetchJob->fetchScope().setFetchTags(true); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().first().tags().size(), 1); { TagDeleteJob *deleteJob = new TagDeleteJob(fetchJob->items().first().tags().first(), this); AKVERIFYEXEC(deleteJob); } { TagDeleteJob *deleteJob = new TagDeleteJob(tag3, this); AKVERIFYEXEC(deleteJob); } { ResourceSelectJob *select = new ResourceSelectJob(QStringLiteral("")); AKVERIFYEXEC(select); } } void TagTest::testMonitor() { Akonadi::Monitor monitor; monitor.setTypeMonitored(Akonadi::Monitor::Tags); monitor.tagFetchScope().fetchAttribute(); QTest::qWait(10); // give Monitor time to upload settings Akonadi::Tag createdTag; { QSignalSpy addedSpy(&monitor, SIGNAL(tagAdded(Akonadi::Tag))); QVERIFY(addedSpy.isValid()); Tag tag; tag.setGid("gid2"); tag.setName(QStringLiteral("name2")); tag.setType("type2"); TagCreateJob *createjob = new TagCreateJob(tag, this); AKVERIFYEXEC(createjob); createdTag = createjob->tag(); QCOMPARE(createdTag.type(), tag.type()); QCOMPARE(createdTag.name(), tag.name()); QCOMPARE(createdTag.gid(), tag.gid()); //We usually pick up signals from the previous tests as well (due to server-side notification caching) QTRY_VERIFY(addedSpy.count() >= 1); QTRY_COMPARE(addedSpy.last().first().value().id(), createdTag.id()); const Akonadi::Tag notifiedTag = addedSpy.last().first().value(); QCOMPARE(notifiedTag.type(), createdTag.type()); QCOMPARE(notifiedTag.gid(), createdTag.gid()); QVERIFY(notifiedTag.hasAttribute()); QCOMPARE(notifiedTag.name(), createdTag.name()); // requires the TagAttribute } { QSignalSpy modifiedSpy(&monitor, SIGNAL(tagChanged(Akonadi::Tag))); QVERIFY(modifiedSpy.isValid()); createdTag.setName(QStringLiteral("name3")); TagModifyJob *modJob = new TagModifyJob(createdTag, this); AKVERIFYEXEC(modJob); //We usually pick up signals from the previous tests as well (due to server-side notification caching) QTRY_VERIFY(modifiedSpy.count() >= 1); QTRY_COMPARE(modifiedSpy.last().first().value().id(), createdTag.id()); const Akonadi::Tag notifiedTag = modifiedSpy.last().first().value(); QCOMPARE(notifiedTag.type(), createdTag.type()); QCOMPARE(notifiedTag.gid(), createdTag.gid()); QVERIFY(notifiedTag.hasAttribute()); QCOMPARE(notifiedTag.name(), createdTag.name()); // requires the TagAttribute } { QSignalSpy removedSpy(&monitor, SIGNAL(tagRemoved(Akonadi::Tag))); QVERIFY(removedSpy.isValid()); TagDeleteJob *deletejob = new TagDeleteJob(createdTag, this); AKVERIFYEXEC(deletejob); QTRY_VERIFY(removedSpy.count() >= 1); QTRY_COMPARE(removedSpy.last().first().value().id(), createdTag.id()); const Akonadi::Tag notifiedTag = removedSpy.last().first().value(); QCOMPARE(notifiedTag.type(), createdTag.type()); QCOMPARE(notifiedTag.gid(), createdTag.gid()); QVERIFY(notifiedTag.hasAttribute()); QCOMPARE(notifiedTag.name(), createdTag.name()); // requires the TagAttribute } } void TagTest::testTagAttributeConfusionBug() { // Create two tags Tag firstTag; { firstTag.setGid("gid"); firstTag.setName(QStringLiteral("display name")); TagCreateJob *createjob = new TagCreateJob(firstTag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); firstTag = createjob->tag(); } Tag secondTag; { secondTag.setGid("AnotherGID"); secondTag.setName(QStringLiteral("another name")); TagCreateJob *createjob = new TagCreateJob(secondTag, this); AKVERIFYEXEC(createjob); QVERIFY(createjob->tag().isValid()); secondTag = createjob->tag(); } Akonadi::Monitor monitor; monitor.setTypeMonitored(Akonadi::Monitor::Tags); const QList firstTagIdList{ firstTag.id() }; // Modify attribute on the first tag // and check the notification { QSignalSpy modifiedSpy(&monitor, &Akonadi::Monitor::tagChanged); firstTag.setName(QStringLiteral("renamed")); TagModifyJob *modJob = new TagModifyJob(firstTag, this); AKVERIFYEXEC(modJob); TagFetchJob *fetchJob = new TagFetchJob(firstTagIdList, this); QVERIFY(fetchJob->fetchScope().fetchAllAttributes()); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->tags().size(), 1); QCOMPARE(fetchJob->tags().first().name(), firstTag.name()); QTRY_VERIFY(modifiedSpy.count() >= 1); QTRY_COMPARE(modifiedSpy.last().first().value().id(), firstTag.id()); const Akonadi::Tag notifiedTag = modifiedSpy.last().first().value(); QCOMPARE(notifiedTag.name(), firstTag.name()); } // Cleanup TagDeleteJob *deleteJob = new TagDeleteJob(firstTag, this); AKVERIFYEXEC(deleteJob); TagDeleteJob *anotherDeleteJob = new TagDeleteJob(secondTag, this); AKVERIFYEXEC(anotherDeleteJob); } void TagTest::testFetchItemsByTag() { const Collection res3 = Collection(collectionIdFromPath(QStringLiteral("res3"))); Tag tag; { TagCreateJob *createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this); AKVERIFYEXEC(createjob); tag = createjob->tag(); } Item item1; { item1.setMimeType(QStringLiteral("application/octet-stream")); ItemCreateJob *append = new ItemCreateJob(item1, res3, this); AKVERIFYEXEC(append); item1 = append->item(); //FIXME This should also be possible with create, but isn't item1.setTag(tag); } ItemModifyJob *modJob = new ItemModifyJob(item1, this); AKVERIFYEXEC(modJob); ItemFetchJob *fetchJob = new ItemFetchJob(tag, this); AKVERIFYEXEC(fetchJob); QCOMPARE(fetchJob->items().size(), 1); Item i = fetchJob->items().first(); QCOMPARE(i, item1); TagDeleteJob *deleteJob = new TagDeleteJob(tag, this); AKVERIFYEXEC(deleteJob); } +void TagTest::tagModifyJobShouldOnlySendModifiedAttributes() +{ + // Given a tag with an attribute + Tag tag(QStringLiteral("tagWithAttr")); + auto *attr = new Akonadi::TagAttribute; + attr->setDisplayName(QStringLiteral("display name")); + tag.addAttribute(attr); + { + auto *createjob = new TagCreateJob(tag, this); + AKVERIFYEXEC(createjob); + tag = createjob->tag(); + } + + // When one job modifies this attribute, and another one does an unrelated modify job + Tag attrModTag(tag.id()); + Akonadi::TagAttribute *modAttr = attrModTag.attribute(Tag::AddIfMissing); + modAttr->setDisplayName(QStringLiteral("modified")); + TagModifyJob *attrModJob = new TagModifyJob(attrModTag, this); + AKVERIFYEXEC(attrModJob); + + tag.setType(Tag::GENERIC); + // this job shouldn't send the old attribute again + auto *modJob = new TagModifyJob(tag, this); + AKVERIFYEXEC(modJob); + + // Then the tag should have both the modified attribute and the modified type + { + auto *fetchJob = new TagFetchJob(this); + fetchJob->fetchScope().fetchAttribute(); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->tags().size(), 1); + const Tag fetchedTag = fetchJob->tags().at(0); + QVERIFY(fetchedTag.hasAttribute()); + QCOMPARE(fetchedTag.attribute()->displayName(), QStringLiteral("modified")); + QCOMPARE(fetchedTag.type(), Tag::GENERIC); + } + + // And when adding a new attribute next to the old one + auto *attr2 = AttributeFactory::createAttribute("SecondType"); + tag.addAttribute(attr2); + // this job shouldn't send the old attribute again + auto *modJob2 = new TagModifyJob(tag, this); + AKVERIFYEXEC(modJob2); + + // Then the tag should have the modified attribute and the second one + { + auto *fetchJob = new TagFetchJob(this); + fetchJob->fetchScope().setFetchAllAttributes(true); + AKVERIFYEXEC(fetchJob); + QCOMPARE(fetchJob->tags().size(), 1); + const Tag fetchedTag = fetchJob->tags().at(0); + QVERIFY(fetchedTag.hasAttribute()); + QCOMPARE(fetchedTag.attribute()->displayName(), QStringLiteral("modified")); + QCOMPARE(fetchedTag.type(), Tag::GENERIC); + QVERIFY(fetchedTag.attribute("SecondType")); + } +} + #include "tagtest.moc" QTEST_AKONADIMAIN(TagTest) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0e24e3c38..64061be62 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,350 +1,351 @@ set(akonadicore_base_SRCS agentconfigurationbase.cpp agentconfigurationfactorybase.cpp agentconfigurationmanager.cpp agentinstance.cpp agentmanager.cpp agenttype.cpp asyncselectionhandler.cpp attribute.cpp attributefactory.cpp + attributestorage.cpp braveheart.cpp cachepolicy.cpp changemediator_p.cpp changenotification.cpp changenotificationdependenciesfactory.cpp changerecorder.cpp changerecorder_p.cpp changerecorderjournal.cpp connection.cpp collection.cpp collectioncolorattribute.cpp collectionfetchscope.cpp collectionpathresolver.cpp collectionquotaattribute.cpp collectionquotaattribute.cpp collectionrightsattribute.cpp collectionstatistics.cpp collectionsync.cpp conflicthandler.cpp collectionidentificationattribute.cpp control.cpp entityannotationsattribute.cpp entitycache.cpp entitydeletedattribute.cpp entitydeletedattribute.cpp entitydisplayattribute.cpp entityhiddenattribute.cpp exception.cpp favoritecollectionattribute.cpp firstrun.cpp gidextractor.cpp indexpolicyattribute.cpp item.cpp itemchangelog.cpp itemfetchscope.cpp itemmonitor.cpp itemserializer.cpp itemserializerplugin.cpp itemsync.cpp mimetypechecker.cpp monitor.cpp monitor_p.cpp newmailnotifierattribute.cpp notificationsource_p.cpp notificationsubscriber.cpp partfetcher.cpp pastehelper.cpp persistentsearchattribute.cpp pluginloader.cpp pop3resourceattribute.cpp protocolhelper.cpp remotelog.cpp relation.cpp relationsync.cpp searchquery.cpp servermanager.cpp session.cpp sessionthread.cpp specialcollectionattribute.cpp specialcollections.cpp tag.cpp tagattribute.cpp tagfetchscope.cpp tagsync.cpp trashsettings.cpp typepluginloader.cpp ) ecm_generate_headers(AkonadiCore_base_HEADERS HEADER_NAMES AbstractDifferencesReporter AgentConfigurationBase AgentConfigurationFactoryBase AgentInstance AgentManager AgentType Attribute AttributeFactory CachePolicy ChangeNotification ChangeRecorder Collection CollectionColorAttribute CollectionFetchScope CollectionQuotaAttribute CollectionStatistics CollectionUtils CollectionIdentificationAttribute Control DifferencesAlgorithmInterface EntityAnnotationsAttribute EntityDeletedAttribute EntityDisplayAttribute EntityHiddenAttribute ExceptionBase FavoriteCollectionAttribute GidExtractorInterface IndexPolicyAttribute Item ItemFetchScope ItemMonitor ItemSerializerPlugin ItemSync MimeTypeChecker NewMailNotifierAttribute NotificationSubscriber Monitor PartFetcher PersistentSearchAttribute Pop3ResourceAttribute Relation SearchQuery ServerManager Session SpecialCollections SpecialCollectionAttribute Supertrait Tag TagAttribute TagFetchScope TrashSettings CollectionPathResolver REQUIRED_HEADERS AkonadiCore_base_HEADERS ) set(akonadicore_models_SRCS models/agentfilterproxymodel.cpp models/agentinstancemodel.cpp models/agenttypemodel.cpp models/collectionfilterproxymodel.cpp models/collectionmodel.cpp models/collectionmodel_p.cpp models/entitymimetypefiltermodel.cpp models/entityorderproxymodel.cpp models/entityrightsfiltermodel.cpp models/entitytreemodel.cpp models/entitytreemodel_p.cpp models/favoritecollectionsmodel.cpp models/itemmodel.cpp models/recursivecollectionfilterproxymodel.cpp models/selectionproxymodel.cpp models/statisticsproxymodel.cpp models/subscriptionmodel.cpp models/tagmodel.cpp models/tagmodel_p.cpp models/trashfilterproxymodel.cpp ) ecm_generate_headers(AkonadiCore_models_HEADERS HEADER_NAMES AgentFilterProxyModel AgentInstanceModel AgentTypeModel CollectionFilterProxyModel EntityMimeTypeFilterModel EntityOrderProxyModel EntityRightsFilterModel EntityTreeModel FavoriteCollectionsModel ItemModel RecursiveCollectionFilterProxyModel SelectionProxyModel StatisticsProxyModel TagModel TrashFilterProxyModel REQUIRED_HEADERS AkonadiCore_models_HEADERS RELATIVE models ) set(akonadicore_jobs_SRCS jobs/agentinstancecreatejob.cpp jobs/collectionattributessynchronizationjob.cpp jobs/collectioncopyjob.cpp jobs/collectioncreatejob.cpp jobs/collectiondeletejob.cpp jobs/collectionfetchjob.cpp jobs/collectionmodifyjob.cpp jobs/collectionmovejob.cpp jobs/collectionstatisticsjob.cpp jobs/invalidatecachejob.cpp jobs/itemcopyjob.cpp jobs/itemcreatejob.cpp jobs/itemdeletejob.cpp jobs/itemfetchjob.cpp jobs/itemmodifyjob.cpp jobs/itemmovejob.cpp jobs/itemsearchjob.cpp jobs/job.cpp jobs/kjobprivatebase.cpp jobs/linkjob.cpp jobs/recursiveitemfetchjob.cpp jobs/resourceselectjob.cpp jobs/resourcesynchronizationjob.cpp jobs/relationfetchjob.cpp jobs/relationcreatejob.cpp jobs/relationdeletejob.cpp jobs/searchcreatejob.cpp jobs/searchresultjob.cpp jobs/specialcollectionsdiscoveryjob.cpp jobs/specialcollectionshelperjobs.cpp jobs/specialcollectionsrequestjob.cpp jobs/subscriptionjob.cpp jobs/tagcreatejob.cpp jobs/tagdeletejob.cpp jobs/tagfetchjob.cpp jobs/tagmodifyjob.cpp jobs/transactionjobs.cpp jobs/transactionsequence.cpp jobs/trashjob.cpp jobs/trashrestorejob.cpp jobs/unlinkjob.cpp ) ecm_generate_headers(AkonadiCore_jobs_HEADERS HEADER_NAMES AgentInstanceCreateJob CollectionAttributesSynchronizationJob CollectionCopyJob CollectionCreateJob CollectionDeleteJob CollectionFetchJob CollectionModifyJob CollectionMoveJob CollectionStatisticsJob ItemCopyJob ItemCreateJob ItemDeleteJob ItemFetchJob ItemModifyJob ItemMoveJob ItemSearchJob Job LinkJob RecursiveItemFetchJob ResourceSynchronizationJob RelationFetchJob RelationCreateJob RelationDeleteJob SearchCreateJob SpecialCollectionsDiscoveryJob SpecialCollectionsRequestJob TagCreateJob TagDeleteJob TagFetchJob TagModifyJob TransactionJobs TransactionSequence TrashJob TrashRestoreJob UnlinkJob REQUIRED_HEADERS AkonadiCore_jobs_HEADERS RELATIVE jobs ) set(akonadicore_dbus_xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.NotificationManager.xml) qt5_add_dbus_interface(akonadicore_dbus_SRCS ${akonadicore_dbus_xml} notificationmanagerinterface) set(akonadicore_dbus_xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.NotificationSource.xml) set_source_files_properties(${akonadicore_dbus_xml} PROPERTIES INCLUDE "${Akonadi_SOURCE_DIR}/src/private/protocol_p.h" ) qt5_add_dbus_interface(akonadicore_dbus_SRCS ${akonadicore_dbus_xml} notificationsourceinterface) qt5_add_dbus_interfaces(akonadicore_dbus_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.AgentManager.xml) qt5_add_dbus_interfaces(akonadicore_dbus_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Tracer.xml) qt5_add_dbus_interfaces(akonadicore_dbus_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Control.xml) set(akonadicore_SRCS ${akonadicore_base_SRCS} ${akonadicore_jobs_SRCS} ${akonadicore_models_SRCS} ${akonadicore_dbus_SRCS} ) ecm_qt_declare_logging_category(akonadicore_SRCS HEADER akonadicore_debug.h IDENTIFIER AKONADICORE_LOG CATEGORY_NAME org.kde.pim.akonadicore) add_library(KF5AkonadiCore ${akonadicore_SRCS}) generate_export_header(KF5AkonadiCore BASE_NAME akonadicore) add_library(KF5::AkonadiCore ALIAS KF5AkonadiCore) target_include_directories(KF5AkonadiCore INTERFACE "$") target_include_directories(KF5AkonadiCore PUBLIC "$") target_include_directories(KF5AkonadiCore PUBLIC "$") target_include_directories(KF5AkonadiCore PUBLIC "$") kde_target_enable_exceptions(KF5AkonadiCore PUBLIC) target_link_libraries(KF5AkonadiCore PUBLIC KF5::CoreAddons # for KJob KF5::ItemModels Qt5::Gui # for QColor PRIVATE Qt5::Network Qt5::Widgets KF5::AkonadiPrivate KF5::DBusAddons KF5::I18n KF5::IconThemes KF5::ConfigCore KF5AkonadiPrivate akonadi_shared ) set_target_properties(KF5AkonadiCore PROPERTIES VERSION ${AKONADI_VERSION_STRING} SOVERSION ${AKONADI_SOVERSION} EXPORT_NAME AkonadiCore ) ecm_generate_pri_file(BASE_NAME AkonadiCore LIB_NAME KF5AkonadiCore DEPS "ItemModels CoreAddons" FILENAME_VAR PRI_FILENAME ) install(TARGETS KF5AkonadiCore EXPORT KF5AkonadiTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akonadicore_export.h ${AkonadiCore_base_HEADERS} ${AkonadiCore_models_HEADERS} ${AkonadiCore_jobs_HEADERS} ${AkonadiCore_HEADERS} qtest_akonadi.h itempayloadinternals_p.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/AkonadiCore COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR} ) install( FILES kcfg2dbus.xsl DESTINATION ${KDE_INSTALL_DATADIR_KF5}/akonadi ) diff --git a/src/core/attributestorage.cpp b/src/core/attributestorage.cpp new file mode 100644 index 000000000..0d6793f9f --- /dev/null +++ b/src/core/attributestorage.cpp @@ -0,0 +1,137 @@ +/* + Copyright (c) 2019 David Faure + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "attributestorage_p.h" + +using namespace Akonadi; + +AttributeStorage::AttributeStorage() +{ +} + +AttributeStorage::AttributeStorage(const AttributeStorage &other) + : mModifiedAttributes(other.mModifiedAttributes), + mDeletedAttributes(other.mDeletedAttributes) +{ + for (Attribute *attr : qAsConst(other.mAttributes)) { + mAttributes.insert(attr->type(), attr->clone()); + } +} + +AttributeStorage &AttributeStorage::operator=(const AttributeStorage &other) +{ + AttributeStorage copy(other); + swap(copy); + return *this; +} + +void AttributeStorage::swap(AttributeStorage &other) noexcept +{ + using std::swap; + swap(other.mAttributes, mAttributes); + swap(other.mModifiedAttributes, mModifiedAttributes); + swap(other.mDeletedAttributes, mDeletedAttributes); +} + +AttributeStorage::~AttributeStorage() +{ + qDeleteAll(mAttributes); +} + +void AttributeStorage::addAttribute(Attribute *attr) +{ + Q_ASSERT(attr); + const QByteArray type = attr->type(); + Attribute *existing = mAttributes.value(type); + if (existing) { + if (attr == existing) { + return; + } + mAttributes.remove(type); + delete existing; + } + mAttributes.insert(type, attr); + markAttributeModified(type); +} + +void AttributeStorage::removeAttribute(const QByteArray &type) +{ + mModifiedAttributes.erase(type); + mDeletedAttributes.insert(type); + delete mAttributes.take(type); +} + +bool AttributeStorage::hasAttribute(const QByteArray &type) const +{ + return mAttributes.contains(type); +} + +Attribute::List AttributeStorage::attributes() const +{ + return mAttributes.values(); +} + +void AttributeStorage::clearAttributes() +{ + for (Attribute *attr : qAsConst(mAttributes)) { + mDeletedAttributes.insert(attr->type()); + delete attr; + } + mAttributes.clear(); + mModifiedAttributes.clear(); +} + +Attribute *AttributeStorage::attribute(const QByteArray &type) const +{ + return mAttributes.value(type); +} + +void AttributeStorage::markAttributeModified(const QByteArray &type) +{ + mDeletedAttributes.remove(type); + mModifiedAttributes.insert(type); +} + +void AttributeStorage::resetChangeLog() +{ + mModifiedAttributes.clear(); + mDeletedAttributes.clear(); +} + +QSet AttributeStorage::deletedAttributes() const +{ + return mDeletedAttributes; +} + +bool AttributeStorage::hasModifiedAttributes() const +{ + return !mModifiedAttributes.empty(); +} + +std::vector AttributeStorage::modifiedAttributes() const +{ + std::vector ret; + ret.reserve(mModifiedAttributes.size()); + for (const auto &type : mModifiedAttributes) { + Attribute *attr = mAttributes.value(type); + Q_ASSERT(attr); + ret.push_back(attr); + } + return ret; +} diff --git a/src/core/attributestorage_p.h b/src/core/attributestorage_p.h new file mode 100644 index 000000000..e38f255f7 --- /dev/null +++ b/src/core/attributestorage_p.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2019 David Faure + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef ATTRIBUTESTORAGE_P_H +#define ATTRIBUTESTORAGE_P_H + +#include +#include +#include +#include +#include "attribute.h" + +namespace Akonadi { + +/** + * The AttributeStorage class is used by Collection, Item, Tag... + * to store a set of attributes, remembering modifications. + * I.e. it knows which attributes have been added or removed + * compared to the initial set (e.g. fetched from server). + */ +class AttributeStorage +{ +public: + AttributeStorage(); + AttributeStorage(const AttributeStorage &other); + AttributeStorage& operator=(const AttributeStorage &other); + void swap(AttributeStorage &other) noexcept; + ~AttributeStorage(); + + void addAttribute(Attribute *attr); + void removeAttribute(const QByteArray &type); + bool hasAttribute(const QByteArray &type) const; + Attribute::List attributes() const; + void clearAttributes(); + Attribute *attribute(const QByteArray &type) const; + void markAttributeModified(const QByteArray &type); + void resetChangeLog(); + + QSet deletedAttributes() const; + bool hasModifiedAttributes() const; + std::vector modifiedAttributes() const; + +private: + QHash mAttributes; + std::set mModifiedAttributes; + QSet mDeletedAttributes; +}; + +} + +#endif // ATTRIBUTESTORAGE_P_H diff --git a/src/core/collection.cpp b/src/core/collection.cpp index 9bb43f8da..9fe833913 100644 --- a/src/core/collection.cpp +++ b/src/core/collection.cpp @@ -1,468 +1,452 @@ /* 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 "collection.h" #include "collection_p.h" #include "attributefactory.h" #include "cachepolicy.h" #include "collectionrightsattribute_p.h" #include "collectionstatistics.h" #include "entitydisplayattribute.h" #include #include #include #include #include #include using namespace Akonadi; Q_GLOBAL_STATIC(Akonadi::Collection, s_defaultParentCollection) uint Akonadi::qHash(const Akonadi::Collection &collection) { return ::qHash(collection.id()); } /** * Helper method for assignment operator and copy constructor. */ static void assignCollectionPrivate(QSharedDataPointer &one, const QSharedDataPointer &other) { // We can't simply do one = other here, we have to use a temp. // Otherwise ProtocolHelperTest::testParentCollectionAfterCollectionParsing() // will break. // // The reason are assignments like // col = col.parentCollection() // // Here, parentCollection() actually returns a reference to a pointer owned // by col. So when col (or rather, it's private class) is deleted, the pointer // to the parent collection and therefore the reference becomes invalid. // // With a single-line assignment here, the parent collection would be deleted // before it is assigned, and therefore the resulting object would point to // uninitalized memory. QSharedDataPointer temp = other; one = temp; } class CollectionRoot : public Collection { public: CollectionRoot() : Collection(0) { setContentMimeTypes({ Collection::mimeType() }); // The root collection is read-only for the users setRights(Collection::ReadOnly); } }; Q_GLOBAL_STATIC(CollectionRoot, s_root) Collection::Collection() : d_ptr(new CollectionPrivate) { static int lastId = -1; d_ptr->mId = lastId--; } Collection::Collection(Id id) : d_ptr(new CollectionPrivate(id)) { } Collection::Collection(const Collection &other) { assignCollectionPrivate(d_ptr, other.d_ptr); } Collection::~Collection() { } void Collection::setId(Collection::Id identifier) { d_ptr->mId = identifier; } Collection::Id Collection::id() const { return d_ptr->mId; } void Collection::setRemoteId(const QString &id) { d_ptr->mRemoteId = id; } QString Collection::remoteId() const { return d_ptr->mRemoteId; } void Collection::setRemoteRevision(const QString &revision) { d_ptr->mRemoteRevision = revision; } QString Collection::remoteRevision() const { return d_ptr->mRemoteRevision; } bool Collection::isValid() const { return (d_ptr->mId >= 0); } bool Collection::operator==(const Collection &other) const { // Invalid collections are the same, no matter what their internal ID is return (!isValid() && !other.isValid()) || (d_ptr->mId == other.d_ptr->mId); } bool Akonadi::Collection::operator!=(const Collection &other) const { return (isValid() || other.isValid()) && (d_ptr->mId != other.d_ptr->mId); } Collection &Collection ::operator=(const Collection &other) { if (this != &other) { assignCollectionPrivate(d_ptr, other.d_ptr); } return *this; } bool Akonadi::Collection::operator<(const Collection &other) const { return d_ptr->mId < other.d_ptr->mId; } void Collection::addAttribute(Attribute *attr) { - Q_ASSERT(attr); - Attribute *existing = d_ptr->mAttributes.value(attr->type()); - if (existing) { - if (attr == existing) { - return; - } - d_ptr->mAttributes.remove(attr->type()); - delete existing; - } - d_ptr->mAttributes.insert(attr->type(), attr); - d_ptr->mDeletedAttributes.remove(attr->type()); - d_ptr->attributesChanged = true; + d_ptr->mAttributeStorage.addAttribute(attr); } void Collection::removeAttribute(const QByteArray &type) { - d_ptr->mDeletedAttributes.insert(type); - delete d_ptr->mAttributes.take(type); + d_ptr->mAttributeStorage.removeAttribute(type); } bool Collection::hasAttribute(const QByteArray &type) const { - return d_ptr->mAttributes.contains(type); + return d_ptr->mAttributeStorage.hasAttribute(type); } Attribute::List Collection::attributes() const { - return d_ptr->mAttributes.values(); + return d_ptr->mAttributeStorage.attributes(); } void Akonadi::Collection::clearAttributes() { - for (Attribute *attr : qAsConst(d_ptr->mAttributes)) { - d_ptr->mDeletedAttributes.insert(attr->type()); - delete attr; - } - d_ptr->mAttributes.clear(); + return d_ptr->mAttributeStorage.clearAttributes(); } Attribute *Collection::attribute(const QByteArray &type) const { - return d_ptr->mAttributes.value(type); + return d_ptr->mAttributeStorage.attribute(type); } Collection &Collection::parentCollection() { if (!d_ptr->mParent) { d_ptr->mParent = new Collection(); } return *(d_ptr->mParent); } Collection Collection::parentCollection() const { if (!d_ptr->mParent) { return *(s_defaultParentCollection); } else { return *(d_ptr->mParent); } } void Collection::setParentCollection(const Collection &parent) { delete d_ptr->mParent; d_ptr->mParent = new Collection(parent); } QString Collection::name() const { return d_ptr->name; } QString Collection::displayName() const { const EntityDisplayAttribute *const attr = attribute(); const QString displayName = attr ? attr->displayName() : QString(); return !displayName.isEmpty() ? displayName : d_ptr->name; } void Collection::setName(const QString &name) { d_ptr->name = name; } Collection::Rights Collection::rights() const { CollectionRightsAttribute *attr = attribute(); if (attr) { return attr->rights(); } else { return AllRights; } } void Collection::setRights(Rights rights) { CollectionRightsAttribute *attr = attribute(AddIfMissing); attr->setRights(rights); } QStringList Collection::contentMimeTypes() const { return d_ptr->contentTypes; } void Collection::setContentMimeTypes(const QStringList &types) { if (d_ptr->contentTypes != types) { d_ptr->contentTypes = types; d_ptr->contentTypesChanged = true; } } QUrl Collection::url(UrlType type) const { QUrlQuery query; query.addQueryItem(QStringLiteral("collection"), QString::number(id())); if (type == UrlWithName) { query.addQueryItem(QStringLiteral("name"), name()); } QUrl url; url.setScheme(QStringLiteral("akonadi")); url.setQuery(query); return url; } Collection Collection::fromUrl(const QUrl &url) { if (url.scheme() != QLatin1String("akonadi")) { return Collection(); } const QString colStr = QUrlQuery(url).queryItemValue(QStringLiteral("collection")); bool ok = false; Collection::Id colId = colStr.toLongLong(&ok); if (!ok) { return Collection(); } if (colId == 0) { return Collection::root(); } return Collection(colId); } Collection Collection::root() { return *s_root; } QString Collection::mimeType() { return QStringLiteral("inode/directory"); } QString Akonadi::Collection::virtualMimeType() { return QStringLiteral("application/x-vnd.akonadi.collection.virtual"); } QString Collection::resource() const { return d_ptr->resource; } void Collection::setResource(const QString &resource) { d_ptr->resource = resource; } QDebug operator <<(QDebug d, const Akonadi::Collection &collection) { return d << "Collection ID:" << collection.id() << " remote ID:" << collection.remoteId() << endl << " name:" << collection.name() << endl << " url:" << collection.url() << endl << " parent:" << collection.parentCollection().id() << collection.parentCollection().remoteId() << endl << " resource:" << collection.resource() << endl << " rights:" << collection.rights() << endl << " contents mime type:" << collection.contentMimeTypes() << endl << " isVirtual:" << collection.isVirtual() << endl << " " << collection.cachePolicy() << endl << " " << collection.statistics(); } CollectionStatistics Collection::statistics() const { return d_ptr->statistics; } void Collection::setStatistics(const CollectionStatistics &statistics) { d_ptr->statistics = statistics; } CachePolicy Collection::cachePolicy() const { return d_ptr->cachePolicy; } void Collection::setCachePolicy(const CachePolicy &cachePolicy) { d_ptr->cachePolicy = cachePolicy; d_ptr->cachePolicyChanged = true; } bool Collection::isVirtual() const { return d_ptr->isVirtual; } void Akonadi::Collection::setVirtual(bool isVirtual) { d_ptr->isVirtual = isVirtual; } void Collection::setEnabled(bool enabled) { d_ptr->enabledChanged = true; d_ptr->enabled = enabled; } bool Collection::enabled() const { return d_ptr->enabled; } void Collection::setLocalListPreference(Collection::ListPurpose purpose, Collection::ListPreference preference) { switch (purpose) { case ListDisplay: d_ptr->displayPreference = preference; break; case ListSync: d_ptr->syncPreference = preference; break; case ListIndex: d_ptr->indexPreference = preference; break; } d_ptr->listPreferenceChanged = true; } Collection::ListPreference Collection::localListPreference(Collection::ListPurpose purpose) const { switch (purpose) { case ListDisplay: return d_ptr->displayPreference; case ListSync: return d_ptr->syncPreference; case ListIndex: return d_ptr->indexPreference; } return ListDefault; } bool Collection::shouldList(Collection::ListPurpose purpose) const { if (localListPreference(purpose) == ListDefault) { return enabled() || referenced(); } return (localListPreference(purpose) == ListEnabled); } void Collection::setShouldList(ListPurpose purpose, bool list) { if (localListPreference(purpose) == ListDefault) { setEnabled(list); } else { setLocalListPreference(purpose, list ? ListEnabled : ListDisabled); } } void Collection::setReferenced(bool referenced) { d_ptr->referencedChanged = true; d_ptr->referenced = referenced; } bool Collection::referenced() const { return d_ptr->referenced; } void Collection::setKeepLocalChanges(const QSet &parts) { d_ptr->keepLocalChanges = parts; } QSet Collection::keepLocalChanges() const { return d_ptr->keepLocalChanges; } -void Collection::markAttributesChanged() +void Collection::markAttributeModified(const QByteArray &type) { - d_ptr->attributesChanged = true; + d_ptr->mAttributeStorage.markAttributeModified(type); } diff --git a/src/core/collection.h b/src/core/collection.h index 50c09261e..2b5780cba 100644 --- a/src/core/collection.h +++ b/src/core/collection.h @@ -1,627 +1,627 @@ /* 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. */ #ifndef AKONADI_COLLECTION_H #define AKONADI_COLLECTION_H #include "akonadicore_export.h" #include "attribute.h" #include #include #include class QUrl; namespace Akonadi { class CachePolicy; class CollectionPrivate; class CollectionStatistics; /** * @short Represents a collection of PIM items. * * This class represents a collection of PIM items, such as a folder on a mail- or * groupware-server. * * Collections are hierarchical, i.e., they may have a parent collection. * * @code * * using namespace Akonadi; * * // fetching all collections recursive, starting at the root collection * CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); * connect( job, SIGNAL(result(KJob*)), SLOT(fetchFinished(KJob*)) ); * * ... * * MyClass::fetchFinished( KJob *job ) * { * if ( job->error() ) { * qDebug() << "Error occurred"; * return; * } * * CollectionFetchJob *fetchJob = qobject_cast( job ); * * const Collection::List collections = fetchJob->collections(); * foreach ( const Collection &collection, collections ) { * qDebug() << "Name:" << collection.name(); * } * } * * @endcode * * @author Volker Krause */ class AKONADICORE_EXPORT Collection { public: /** * Describes the unique id type. */ typedef qint64 Id; /** * Describes a list of collections. */ typedef QVector List; /** * Describes rights of a collection. */ enum Right { ReadOnly = 0x0, ///< Can only read items or subcollection of this collection CanChangeItem = 0x1, ///< Can change items in this collection CanCreateItem = 0x2, ///< Can create new items in this collection CanDeleteItem = 0x4, ///< Can delete items in this collection CanChangeCollection = 0x8, ///< Can change this collection CanCreateCollection = 0x10, ///< Can create new subcollections in this collection CanDeleteCollection = 0x20, ///< Can delete this collection CanLinkItem = 0x40, ///< Can create links to existing items in this virtual collection @since 4.4 CanUnlinkItem = 0x80, ///< Can remove links to items in this virtual collection @since 4.4 AllRights = (CanChangeItem | CanCreateItem | CanDeleteItem | CanChangeCollection | CanCreateCollection | CanDeleteCollection) ///< Has all rights on this storage collection }; Q_DECLARE_FLAGS(Rights, Right) /** * Creates an invalid collection. */ Collection(); /** * Create a new collection. * * @param id The unique identifier of the collection. */ explicit Collection(Id id); /** * Destroys the collection. */ ~Collection(); /** * Creates a collection from an @p other collection. */ Collection(const Collection &other); /** * Creates a collection from the given @p url. */ static Collection fromUrl(const QUrl &url); /** * Sets the unique @p identifier of the collection. */ void setId(Id identifier); /** * Returns the unique identifier of the collection. */ Q_REQUIRED_RESULT Id id() const; /** * Sets the remote @p id of the collection. */ void setRemoteId(const QString &id); /** * Returns the remote id of the collection. */ Q_REQUIRED_RESULT QString remoteId() const; /** * Sets the remote @p revision of the collection. * @param revision the collections's remote revision * The remote revision can be used by resources to store some * revision information of the backend to detect changes there. * * @note This method is supposed to be used by resources only. * @since 4.5 */ void setRemoteRevision(const QString &revision); /** * Returns the remote revision of the collection. * * @note This method is supposed to be used by resources only. * @since 4.5 */ Q_REQUIRED_RESULT QString remoteRevision() const; /** * Returns whether the collection is valid. */ Q_REQUIRED_RESULT bool isValid() const; /** * Returns whether this collections's id equals the * id of the @p other collection. */ Q_REQUIRED_RESULT bool operator==(const Collection &other) const; /** * Returns whether the collection's id does not equal the id * of the @p other collection. */ Q_REQUIRED_RESULT bool operator!=(const Collection &other) const; /** * Assigns the @p other to this collection and returns a reference to this * collection. * @param other the collection to assign */ Collection &operator=(const Collection &other); /** * @internal For use with containers only. * * @since 4.8 */ Q_REQUIRED_RESULT bool operator<(const Collection &other) const; /** * Returns the parent collection of this object. * @note This will of course only return a useful value if it was explictely retrieved * from the Akonadi server. * @since 4.4 */ Q_REQUIRED_RESULT Collection parentCollection() const; /** * Returns a reference to the parent collection of this object. * @note This will of course only return a useful value if it was explictely retrieved * from the Akonadi server. * @since 4.4 */ Q_REQUIRED_RESULT Collection &parentCollection(); /** * Set the parent collection of this object. * @note Calling this method has no immediate effect for the object itself, * such as being moved to another collection. * It is mainly relevant to provide a context for RID-based operations * inside resources. * @param parent The parent collection. * @since 4.4 */ void setParentCollection(const Collection &parent); /** * Adds an attribute to the collection. * * If an attribute of the same type name already exists, it is deleted and * replaced with the new one. * * @param attribute The new attribute. * * @note The collection takes the ownership of the attribute. */ void addAttribute(Attribute *attribute); /** * Removes and deletes the attribute of the given type @p name. */ void removeAttribute(const QByteArray &name); /** * Returns @c true if the collection has an attribute of the given type @p name, * false otherwise. */ bool hasAttribute(const QByteArray &name) const; /** * Returns a list of all attributes of the collection. */ Q_REQUIRED_RESULT Attribute::List attributes() const; /** * Removes and deletes all attributes of the collection. */ void clearAttributes(); /** * Returns the attribute of the given type @p name if available, 0 otherwise. */ Attribute *attribute(const QByteArray &name) const; /** * Describes the options that can be passed to access attributes. */ enum CreateOption { AddIfMissing ///< Creates the attribute if it is missing }; /** * Returns the attribute of the requested type. * If the collection has no attribute of that type yet, a new one * is created and added to the entity. * * @param option The create options. */ template inline T *attribute(CreateOption option); /** * Returns the attribute of the requested type or 0 if it is not available. */ template inline T *attribute() const; /** * Removes and deletes the attribute of the requested type. */ template inline void removeAttribute(); /** * Returns whether the collection has an attribute of the requested type. */ template inline bool hasAttribute() const; /** * Returns the i18n'ed name of the collection. */ Q_REQUIRED_RESULT QString name() const; /** * Returns the display name (EntityDisplayAttribute::displayName()) if set, * and Collection::name() otherwise. For human-readable strings this is preferred * over Collection::name(). * * @since 4.11 */ Q_REQUIRED_RESULT QString displayName() const; /** * Sets the i18n'ed name of the collection. * * @param name The new collection name. */ void setName(const QString &name); /** * Returns the rights the user has on the collection. */ Q_REQUIRED_RESULT Rights rights() const; /** * Sets the @p rights the user has on the collection. */ void setRights(Rights rights); /** * Returns a list of possible content mimetypes, * e.g. message/rfc822, x-akonadi/collection for a mail folder that * supports sub-folders. */ Q_REQUIRED_RESULT QStringList contentMimeTypes() const; /** * Sets the list of possible content mime @p types. */ void setContentMimeTypes(const QStringList &types); /** * Returns the root collection. */ Q_REQUIRED_RESULT static Collection root(); /** * Returns the mimetype used for collections. */ Q_REQUIRED_RESULT static QString mimeType(); /** * Returns the mimetype used for virtual collections * * @since 4.11 */ Q_REQUIRED_RESULT static QString virtualMimeType(); /** * Returns the identifier of the resource owning the collection. */ Q_REQUIRED_RESULT QString resource() const; /** * Sets the @p identifier of the resource owning the collection. */ void setResource(const QString &identifier); /** * Returns the cache policy of the collection. */ Q_REQUIRED_RESULT CachePolicy cachePolicy() const; /** * Sets the cache @p policy of the collection. */ void setCachePolicy(const CachePolicy &policy); /** * Returns the collection statistics of the collection. */ Q_REQUIRED_RESULT CollectionStatistics statistics() const; /** * Sets the collection @p statistics for the collection. */ void setStatistics(const CollectionStatistics &statistics); /** * Describes the type of url which is returned in url(). * * @since 4.7 */ enum UrlType { UrlShort = 0, ///< A short url which contains the identifier only (equivalent to url()) UrlWithName = 1 ///< A url with identifier and name }; /** * Returns the url of the collection. * @param type the type of url * @since 4.7 */ Q_REQUIRED_RESULT QUrl url(UrlType type = UrlShort) const; /** * Returns whether the collection is virtual, for example a search collection. * * @since 4.6 */ Q_REQUIRED_RESULT bool isVirtual() const; /** * Sets whether the collection is virtual or not. * Virtual collections can't be converted to non-virtual and vice versa. * @param isVirtual virtual collection if @c true, otherwise a normal collection * @since 4.10 */ void setVirtual(bool isVirtual); /** * Sets the collection's enabled state. * * Use this mechanism to set if a collection should be available * to the user or not. * * This can be used in conjunction with the local list preference for finer grained control * to define if a collection should be included depending on the purpose. * * For example: A collection is by default enabled, meaning it is displayed to the user, synchronized by the resource, * and indexed by the indexer. A disabled collection on the other hand is not displayed, synchronized or indexed. * The local list preference allows to locally override that default value for each purpose individually. * * The enabled state can be synchronized by backends. * E.g. an imap resource may synchronize this with the subscription state. * * @since 4.14 * @see setLocalListPreference, setShouldList */ void setEnabled(bool enabled); /** * Returns the collection's enabled state. * @since 4.14 * @see localListPreference */ Q_REQUIRED_RESULT bool enabled() const; /** * Describes the list preference value * * @since 4.14 */ enum ListPreference { ListEnabled, ///< Enable collection for specified purpose ListDisabled, ///< Disable collection for specified purpose ListDefault ///< Fallback to enabled state }; /** * Describes the purpose of the listing * * @since 4.14 */ enum ListPurpose { ListSync, ///< Listing for synchronization ListDisplay, ///< Listing for display to the user ListIndex ///< Listing for indexing the content }; /** * Sets the local list preference for the specified purpose. * * The local list preference overrides the enabled state unless set to ListDefault. * In case of ListDefault the enabled state should be taken as fallback (shouldList() implements this logic). * * The default value is ListDefault. * * @since 4.14 * @see shouldList, setEnabled */ void setLocalListPreference(ListPurpose purpose, ListPreference preference); /** * Returns the local list preference for the specified purpose. * @since 4.14 * @see setLocalListPreference */ Q_REQUIRED_RESULT ListPreference localListPreference(ListPurpose purpose) const; /** * Returns whether the collection should be listed or not for the specified purpose * Takes enabled state and local preference into account. * * @since 4.14 * @see setLocalListPreference, setEnabled */ Q_REQUIRED_RESULT bool shouldList(ListPurpose purpose) const; /** * Sets whether the collection should be listed or not for the specified purpose. * Takes enabled state and local preference into account. * * Use this instead of sestEnabled and setLocalListPreference to automatically set * the right setting. * * @since 4.14 * @see setLocalListPreference, setEnabled */ void setShouldList(ListPurpose purpose, bool shouldList); /** * Sets a collection to be referenced. * * A referenced collection is temporarily shown and synchronized even when disabled. * A reference is only valid for the duration of a session, and is automatically removed afterwards. * * Referenced collections are only visible if explicitly monitored in the ETM. * * @since 4.14 */ void setReferenced(bool referenced); /** * Returns the referenced state of the collection. * @since 4.14 */ Q_REQUIRED_RESULT bool referenced() const; /** * Set during sync to indicate that the provided parts are only default values; * @since 4.15 */ void setKeepLocalChanges(const QSet &parts); /** * Returns what parts are only default values. */ QSet keepLocalChanges() const; private: friend class CollectionCreateJob; friend class CollectionFetchJob; friend class CollectionModifyJob; friend class ProtocolHelper; - void markAttributesChanged(); + void markAttributeModified(const QByteArray &type); //@cond PRIVATE QSharedDataPointer d_ptr; friend class CollectionPrivate; //@endcond }; AKONADICORE_EXPORT uint qHash(const Akonadi::Collection &collection); template inline T *Akonadi::Collection::attribute(Collection::CreateOption option) { Q_UNUSED(option); - const T dummy; - if (hasAttribute(dummy.type())) { - T *attr = dynamic_cast(attribute(dummy.type())); + const QByteArray type = T().type(); + if (hasAttribute(type)) { + T *attr = dynamic_cast(attribute(type)); if (attr) { - markAttributesChanged(); + markAttributeModified(type); return attr; } //Reuse 5250 - qWarning() << "Found attribute of unknown type" << dummy.type() + qWarning() << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; } T *attr = new T(); addAttribute(attr); return attr; } template inline T *Akonadi::Collection::attribute() const { const T dummy; if (hasAttribute(dummy.type())) { T *attr = dynamic_cast(attribute(dummy.type())); if (attr) { return attr; } //reuse 5250 qWarning() << "Found attribute of unknown type" << dummy.type() << ". Did you forget to call AttributeFactory::registerAttribute()?"; } return nullptr; } template inline void Akonadi::Collection::removeAttribute() { const T dummy; removeAttribute(dummy.type()); } template inline bool Akonadi::Collection::hasAttribute() const { const T dummy; return hasAttribute(dummy.type()); } } // namespace Akonadi /** * Allows to output a collection for debugging purposes. */ AKONADICORE_EXPORT QDebug operator<<(QDebug d, const Akonadi::Collection &collection); Q_DECLARE_METATYPE(Akonadi::Collection) Q_DECLARE_METATYPE(Akonadi::Collection::List) Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Collection::Rights) Q_DECLARE_TYPEINFO(Akonadi::Collection, Q_MOVABLE_TYPE); #endif diff --git a/src/core/collection_p.h b/src/core/collection_p.h index a4fed674c..4cc68eac6 100644 --- a/src/core/collection_p.h +++ b/src/core/collection_p.h @@ -1,147 +1,139 @@ /* Copyright (c) 2006 - 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_COLLECTION_P_H #define AKONADI_COLLECTION_P_H #include "collection.h" #include "cachepolicy.h" #include "collectionstatistics.h" +#include "attributestorage_p.h" #include "qstringlist.h" #include using namespace Akonadi; /** * @internal */ class Akonadi::CollectionPrivate : public QSharedData { public: CollectionPrivate(Collection::Id id = -1) : QSharedData() , displayPreference(Collection::ListDefault) , syncPreference(Collection::ListDefault) , indexPreference(Collection::ListDefault) , listPreferenceChanged(false) , enabled(true) , enabledChanged(false) , referenced(false) , referencedChanged(false) , contentTypesChanged(false) , cachePolicyChanged(false) - , attributesChanged(false) , isVirtual(false) , mId(id) , mParent(nullptr) { } CollectionPrivate(const CollectionPrivate &other) : QSharedData(other) , mParent(nullptr) { mId = other.mId; mRemoteId = other.mRemoteId; mRemoteRevision = other.mRemoteRevision; - for (Attribute *attr : qAsConst(other.mAttributes)) { - mAttributes.insert(attr->type(), attr->clone()); - } - mDeletedAttributes = other.mDeletedAttributes; + mAttributeStorage = other.mAttributeStorage; if (other.mParent) { mParent = new Collection(*(other.mParent)); } name = other.name; resource = other.resource; statistics = other.statistics; contentTypes = other.contentTypes; cachePolicy = other.cachePolicy; contentTypesChanged = other.contentTypesChanged; cachePolicyChanged = other.cachePolicyChanged; isVirtual = other.isVirtual; enabled = other.enabled; enabledChanged = other.enabledChanged; displayPreference = other.displayPreference; syncPreference = other.syncPreference; indexPreference = other.indexPreference; listPreferenceChanged = other.listPreferenceChanged; referenced = other.referenced; referencedChanged = other.referencedChanged; keepLocalChanges = other.keepLocalChanges; - attributesChanged = other.attributesChanged; } ~CollectionPrivate() { - qDeleteAll(mAttributes); delete mParent; } void resetChangeLog() { contentTypesChanged = false; cachePolicyChanged = false; enabledChanged = false; listPreferenceChanged = false; referencedChanged = false; - attributesChanged = false; - mDeletedAttributes.clear(); + mAttributeStorage.resetChangeLog(); } static Collection newRoot() { Collection rootCollection(0); rootCollection.setContentMimeTypes({ Collection::mimeType() }); return rootCollection; } // Make use of the 4-bytes padding from QSharedData Collection::ListPreference displayPreference: 2; Collection::ListPreference syncPreference: 2; Collection::ListPreference indexPreference: 2; bool listPreferenceChanged: 1; bool enabled: 1; bool enabledChanged: 1; bool referenced: 1; bool referencedChanged: 1; bool contentTypesChanged: 1; bool cachePolicyChanged: 1; - bool attributesChanged : 1; bool isVirtual: 1; // 2 bytes padding here Collection::Id mId; QString mRemoteId; QString mRemoteRevision; - QHash mAttributes; - QSet mDeletedAttributes; mutable Collection *mParent; + AttributeStorage mAttributeStorage; QString name; QString resource; CollectionStatistics statistics; QStringList contentTypes; static const Collection root; CachePolicy cachePolicy; QSet keepLocalChanges; }; #endif diff --git a/src/core/jobs/collectionmodifyjob.cpp b/src/core/jobs/collectionmodifyjob.cpp index 3b0a00f0b..12d98709d 100644 --- a/src/core/jobs/collectionmodifyjob.cpp +++ b/src/core/jobs/collectionmodifyjob.cpp @@ -1,142 +1,142 @@ /* Copyright (c) 2006 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 "collectionmodifyjob.h" #include "changemediator_p.h" #include "collection_p.h" #include "collectionstatistics.h" #include "job_p.h" #include "protocolhelper_p.h" #include "private/protocol_p.h" #include "persistentsearchattribute.h" using namespace Akonadi; class Akonadi::CollectionModifyJobPrivate : public JobPrivate { public: CollectionModifyJobPrivate(CollectionModifyJob *parent) : JobPrivate(parent) { } QString jobDebuggingString() const override { return QStringLiteral("Collection Id %1").arg(mCollection.id()); } Collection mCollection; }; CollectionModifyJob::CollectionModifyJob(const Collection &collection, QObject *parent) : Job(new CollectionModifyJobPrivate(this), parent) { Q_D(CollectionModifyJob); d->mCollection = collection; } CollectionModifyJob::~CollectionModifyJob() { } void CollectionModifyJob::doStart() { Q_D(CollectionModifyJob); Protocol::ModifyCollectionCommandPtr cmd; try { cmd = Protocol::ModifyCollectionCommandPtr::create(ProtocolHelper::entityToScope(d->mCollection)); } catch (const std::exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); emitResult(); return; } if (d->mCollection.d_ptr->contentTypesChanged) { cmd->setMimeTypes(d->mCollection.contentMimeTypes()); } if (d->mCollection.parentCollection().id() >= 0) { cmd->setParentId(d->mCollection.parentCollection().id()); } const QString &collectionName = d->mCollection.name(); if (!collectionName.isEmpty()) { cmd->setName(collectionName); } if (!d->mCollection.remoteId().isNull()) { cmd->setRemoteId(d->mCollection.remoteId()); } if (!d->mCollection.remoteRevision().isNull()) { cmd->setRemoteRevision(d->mCollection.remoteRevision()); } if (d->mCollection.d_ptr->cachePolicyChanged) { cmd->setCachePolicy(ProtocolHelper::cachePolicyToProtocol(d->mCollection.cachePolicy())); } if (d->mCollection.d_ptr->enabledChanged) { cmd->setEnabled(d->mCollection.enabled()); } if (d->mCollection.d_ptr->listPreferenceChanged) { cmd->setDisplayPref(ProtocolHelper::listPreference(d->mCollection.localListPreference(Collection::ListDisplay))); cmd->setSyncPref(ProtocolHelper::listPreference(d->mCollection.localListPreference(Collection::ListSync))); cmd->setIndexPref(ProtocolHelper::listPreference(d->mCollection.localListPreference(Collection::ListIndex))); } if (d->mCollection.d_ptr->referencedChanged) { cmd->setReferenced(d->mCollection.referenced()); } - if (d->mCollection.d_ptr->attributesChanged) { - cmd->setAttributes(ProtocolHelper::attributesToProtocol(d->mCollection)); + if (d->mCollection.d_ptr->mAttributeStorage.hasModifiedAttributes()) { + cmd->setAttributes(ProtocolHelper::attributesToProtocol(d->mCollection.d_ptr->mAttributeStorage.modifiedAttributes())); } if (auto attr = d->mCollection.attribute()) { cmd->setPersistentSearchCollections(attr->queryCollections()); cmd->setPersistentSearchQuery(attr->queryString()); cmd->setPersistentSearchRecursive(attr->isRecursive()); cmd->setPersistentSearchRemote(attr->isRemoteSearchEnabled()); } - if (!d->mCollection.d_ptr->mDeletedAttributes.isEmpty()) { - cmd->setRemovedAttributes(d->mCollection.d_ptr->mDeletedAttributes); + if (!d->mCollection.d_ptr->mAttributeStorage.deletedAttributes().empty()) { + cmd->setRemovedAttributes(d->mCollection.d_ptr->mAttributeStorage.deletedAttributes()); } if (cmd->modifiedParts() == Protocol::ModifyCollectionCommand::None) { emitResult(); return; } d->sendCommand(cmd); ChangeMediator::invalidateCollection(d->mCollection); } bool CollectionModifyJob::doHandleResponse(qint64 tag, const Akonadi::Protocol::CommandPtr &response) { Q_D(CollectionModifyJob); if (!response->isResponse() || response->type() != Protocol::Command::ModifyCollection) { return Job::doHandleResponse(tag, response); } d->mCollection.d_ptr->resetChangeLog(); return true; } Collection CollectionModifyJob::collection() const { const Q_D(CollectionModifyJob); return d->mCollection; } diff --git a/src/core/jobs/tagmodifyjob.cpp b/src/core/jobs/tagmodifyjob.cpp index bc7e14747..b073dbcf1 100644 --- a/src/core/jobs/tagmodifyjob.cpp +++ b/src/core/jobs/tagmodifyjob.cpp @@ -1,95 +1,97 @@ /* 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 "tagmodifyjob.h" #include "job_p.h" #include "tag.h" #include "tag_p.h" #include "protocolhelper_p.h" #include "changemediator_p.h" using namespace Akonadi; class Akonadi::TagModifyJobPrivate : public JobPrivate { public: TagModifyJobPrivate(TagModifyJob *parent) : JobPrivate(parent) { } QString jobDebuggingString() const override; Tag mTag; }; QString Akonadi::TagModifyJobPrivate::jobDebuggingString() const { return QStringLiteral("Modify Tag: %1").arg(mTag.name()); } TagModifyJob::TagModifyJob(const Akonadi::Tag &tag, QObject *parent) : Job(new TagModifyJobPrivate(this), parent) { Q_D(TagModifyJob); d->mTag = tag; } void TagModifyJob::doStart() { Q_D(TagModifyJob); auto cmd = Protocol::ModifyTagCommandPtr::create(d->mTag.id()); if (!d->mTag.remoteId().isNull()) { cmd->setRemoteId(d->mTag.remoteId()); } if (!d->mTag.type().isEmpty()) { cmd->setType(d->mTag.type()); } if (d->mTag.parent().isValid() && !d->mTag.isImmutable()) { cmd->setParentId(d->mTag.parent().id()); } - if (!d->mTag.d_ptr->mDeletedAttributes.isEmpty()) { - cmd->setRemovedAttributes(d->mTag.d_ptr->mDeletedAttributes); + if (!d->mTag.d_ptr->mAttributeStorage.deletedAttributes().isEmpty()) { + cmd->setRemovedAttributes(d->mTag.d_ptr->mAttributeStorage.deletedAttributes()); + } + if (d->mTag.d_ptr->mAttributeStorage.hasModifiedAttributes()) { + cmd->setAttributes(ProtocolHelper::attributesToProtocol(d->mTag.d_ptr->mAttributeStorage.modifiedAttributes())); } - - cmd->setAttributes(ProtocolHelper::attributesToProtocol(d->mTag)); d->sendCommand(cmd); } bool TagModifyJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { - //Q_D(TagModifyJob); + Q_D(TagModifyJob); if (response->isResponse()) { if (response->type() == Protocol::Command::FetchTags) { // Tag was modified, we ignore the response for now return false; } else if (response->type() == Protocol::Command::DeleteTag) { // The tag was deleted/merged return false; } else if (response->type() == Protocol::Command::ModifyTag) { // Done. + d->mTag.d_ptr->resetChangeLog(); return true; } } return Job::doHandleResponse(tag, response); } diff --git a/src/core/protocolhelper.cpp b/src/core/protocolhelper.cpp index 4d4c34c76..4a89e6523 100644 --- a/src/core/protocolhelper.cpp +++ b/src/core/protocolhelper.cpp @@ -1,773 +1,786 @@ /* 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 "tag_p.h" #include "exceptionbase.h" #include "itemserializer_p.h" #include "itemserializerplugin.h" #include "servermanager.h" #include "tagfetchscope.h" #include "persistentsearchattribute.h" #include "itemfetchscope.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); } +Protocol::Attributes ProtocolHelper::attributesToProtocol(const std::vector &modifiedAttributes, bool ns) +{ + Protocol::Attributes attributes; + for (const Attribute *attr : modifiedAttributes) { + attributes.insert(ProtocolHelper::encodePartIdentifier(ns ? ProtocolHelper::PartAttribute : ProtocolHelper::PartGlobal, attr->type()), + attr->serialized()); + } + return attributes; +} + 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; } Tag ProtocolHelper::parseTag(const Protocol::FetchTagsResponse &data) { Tag tag(data.id()); tag.setRemoteId(data.remoteId()); tag.setGid(data.gid()); tag.setType(data.type()); tag.setParent(Tag(data.parentId())); parseAttributes(data.attributes(), &tag); + tag.d_ptr->resetChangeLog(); return tag; } 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::ItemFetchScope ProtocolHelper::itemFetchScopeToProtocol(const ItemFetchScope &fetchScope) { 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::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::ItemFetchScope::RemoteID, fetchScope.fetchRemoteIdentification()); fs.setFetch(Protocol::ItemFetchScope::RemoteRevision, fetchScope.fetchRemoteIdentification()); fs.setFetch(Protocol::ItemFetchScope::GID, fetchScope.fetchGid()); fs.setFetch(Protocol::ItemFetchScope::Tags, fetchScope.fetchTags()); 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::ItemFetchScope::ParentAncestor: ifs.setAncestorRetrieval(ItemFetchScope::Parent); break; case Protocol::ItemFetchScope::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); } 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; } Protocol::CollectionFetchScope ProtocolHelper::collectionFetchScopeToProtocol(const CollectionFetchScope &fetchScope) { Protocol::CollectionFetchScope cfs; switch (fetchScope.listFilter()) { case CollectionFetchScope::NoFilter: cfs.setListFilter(Protocol::CollectionFetchScope::NoFilter); break; case CollectionFetchScope::Display: cfs.setListFilter(Protocol::CollectionFetchScope::Display); break; case CollectionFetchScope::Sync: cfs.setListFilter(Protocol::CollectionFetchScope::Sync); break; case CollectionFetchScope::Index: cfs.setListFilter(Protocol::CollectionFetchScope::Index); break; case CollectionFetchScope::Enabled: cfs.setListFilter(Protocol::CollectionFetchScope::Enabled); break; } cfs.setIncludeStatistics(fetchScope.includeStatistics()); cfs.setResource(fetchScope.resource()); cfs.setContentMimeTypes(fetchScope.contentMimeTypes()); cfs.setAttributes(fetchScope.attributes()); cfs.setFetchIdOnly(fetchScope.fetchIdOnly()); switch (fetchScope.ancestorRetrieval()) { case CollectionFetchScope::None: cfs.setAncestorRetrieval(Protocol::CollectionFetchScope::None); break; case CollectionFetchScope::Parent: cfs.setAncestorRetrieval(Protocol::CollectionFetchScope::Parent); break; case CollectionFetchScope::All: cfs.setAncestorRetrieval(Protocol::CollectionFetchScope::All); break; } if (cfs.ancestorRetrieval() != Protocol::CollectionFetchScope::None) { cfs.setAncestorAttributes(fetchScope.ancestorFetchScope().attributes()); cfs.setAncestorFetchIdOnly(fetchScope.ancestorFetchScope().fetchIdOnly()); } cfs.setIgnoreRetrievalErrors(fetchScope.ignoreRetrievalErrors()); return cfs; } CollectionFetchScope ProtocolHelper::parseCollectionFetchScope(const Protocol::CollectionFetchScope &fetchScope) { CollectionFetchScope cfs; switch (fetchScope.listFilter()) { case Protocol::CollectionFetchScope::NoFilter: cfs.setListFilter(CollectionFetchScope::NoFilter); break; case Protocol::CollectionFetchScope::Display: cfs.setListFilter(CollectionFetchScope::Display); break; case Protocol::CollectionFetchScope::Sync: cfs.setListFilter(CollectionFetchScope::Sync); break; case Protocol::CollectionFetchScope::Index: cfs.setListFilter(CollectionFetchScope::Index); break; case Protocol::CollectionFetchScope::Enabled: cfs.setListFilter(CollectionFetchScope::Enabled); break; } cfs.setIncludeStatistics(fetchScope.includeStatistics()); cfs.setResource(fetchScope.resource()); cfs.setContentMimeTypes(fetchScope.contentMimeTypes()); switch (fetchScope.ancestorRetrieval()) { case Protocol::CollectionFetchScope::None: cfs.setAncestorRetrieval(CollectionFetchScope::None); break; case Protocol::CollectionFetchScope::Parent: cfs.setAncestorRetrieval(CollectionFetchScope::Parent); break; case Protocol::CollectionFetchScope::All: cfs.setAncestorRetrieval(CollectionFetchScope::All); break; } if (cfs.ancestorRetrieval() != CollectionFetchScope::None) { cfs.ancestorFetchScope().setFetchIdOnly(fetchScope.ancestorFetchIdOnly()); const auto attrs = fetchScope.ancestorAttributes(); for (const auto attr : attrs) { cfs.ancestorFetchScope().fetchAttribute(attr, true); } } const auto attrs = fetchScope.attributes(); for (const auto &attr : attrs) { cfs.fetchAttribute(attr, true); } cfs.setFetchIdOnly(fetchScope.fetchIdOnly()); cfs.setIgnoreRetrievalErrors(fetchScope.ignoreRetrievalErrors()); return cfs; } Protocol::TagFetchScope ProtocolHelper::tagFetchScopeToProtocol(const TagFetchScope &fetchScope) { Protocol::TagFetchScope tfs; tfs.setFetchIdOnly(fetchScope.fetchIdOnly()); tfs.setAttributes(fetchScope.attributes()); tfs.setFetchRemoteID(fetchScope.fetchRemoteId()); tfs.setFetchAllAttributes(fetchScope.fetchAllAttributes()); return tfs; } TagFetchScope ProtocolHelper::parseTagFetchScope(const Protocol::TagFetchScope &fetchScope) { TagFetchScope tfs; tfs.setFetchIdOnly(fetchScope.fetchIdOnly()); tfs.setFetchRemoteId(fetchScope.fetchRemoteID()); tfs.setFetchAllAttributes(fetchScope.fetchAllAttributes()); const auto attrs = fetchScope.attributes(); for (const auto &attr : attrs) { tfs.fetchAttribute(attr, true); } return tfs; } 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, const Akonadi::ItemFetchScope *fetchScope, ProtocolHelperValuePool *valuePool) { Item item; item.setId(data.id()); item.setRevision(data.revision()); if (!fetchScope || fetchScope->fetchRemoteIdentification()) { 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 ((!fetchScope || fetchScope->fetchTags()) && !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 ((!fetchScope || fetchScope->fetchRelations()) && !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 ((!fetchScope || fetchScope->fetchVirtualReferences()) && !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: if (fetchScope && !fetchScope->fullPayload() && !fetchScope->payloadParts().contains(plainKey)) { continue; } 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: { if (fetchScope && !fetchScope->allAttributes() && !fetchScope->attributes().contains(plainKey)) { continue; } 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); + tag.d_ptr->resetChangeLog(); 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 f96e89e11..111b20f2b 100644 --- a/src/core/protocolhelper_p.h +++ b/src/core/protocolhelper_p.h @@ -1,345 +1,347 @@ /* 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 "collectionfetchscope.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 +#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 Tag parseTag(const Protocol::FetchTagsResponse &data); 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); + static Protocol::Attributes attributesToProtocol(const std::vector &modifiedAttributes, 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::ItemFetchScope itemFetchScopeToProtocol(const ItemFetchScope &fetchScope); static ItemFetchScope parseItemFetchScope(const Protocol::ItemFetchScope &fetchScope); static Protocol::CollectionFetchScope collectionFetchScopeToProtocol(const CollectionFetchScope &fetchScope); static CollectionFetchScope parseCollectionFetchScope(const Protocol::CollectionFetchScope &fetchScope); static Protocol::TagFetchScope tagFetchScopeToProtocol(const TagFetchScope &fetchScope); static TagFetchScope parseTagFetchScope(const Protocol::TagFetchScope &fetchScope); /** * Parses a single line from an item fetch job result into an Item object. * FIXME: std::optional */ static Item parseItemFetchResult(const Protocol::FetchItemsResponse &data, const ItemFetchScope *fetchScope = nullptr, 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/core/tag.cpp b/src/core/tag.cpp index fbada6ab8..131eadccf 100644 --- a/src/core/tag.cpp +++ b/src/core/tag.cpp @@ -1,270 +1,260 @@ /* 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 "tag.h" #include "tag_p.h" #include "akonadicore_debug.h" #include "tagattribute.h" #include #include using namespace Akonadi; const char Akonadi::Tag::PLAIN[] = "PLAIN"; const char Akonadi::Tag::GENERIC[] = "GENERIC"; uint Akonadi::qHash(const Tag &tag) { return ::qHash(tag.id()); } Tag::Tag() : d_ptr(new TagPrivate) { } Tag::Tag(Tag::Id id) : d_ptr(new TagPrivate) { d_ptr->id = id; } Tag::Tag(const QString &name) : d_ptr(new TagPrivate) { d_ptr->gid = name.toUtf8(); d_ptr->type = PLAIN; } Tag::Tag(const Tag &other) : d_ptr(other.d_ptr) { } Tag::~Tag() { } Tag &Tag::operator=(const Tag &other) { if (this != &other) { d_ptr = other.d_ptr; } return *this; } bool Tag::operator==(const Tag &other) const { // Valid tags are equal if their IDs are equal if (isValid() && other.isValid()) { return d_ptr->id == other.d_ptr->id; } // Invalid tags are equal if their GIDs are non empty but equal if (!d_ptr->gid.isEmpty() || !other.d_ptr->gid.isEmpty()) { return d_ptr->gid == other.d_ptr->gid; } // Invalid tags are equal if both are invalid return !isValid() && !other.isValid(); } bool Tag::operator!=(const Tag &other) const { return !operator==(other); } Tag Tag::fromUrl(const QUrl &url) { if (url.scheme() != QLatin1String("akonadi")) { return Tag(); } const QString tagStr = QUrlQuery(url).queryItemValue(QStringLiteral("tag")); bool ok = false; Tag::Id itemId = tagStr.toLongLong(&ok); if (!ok) { return Tag(); } return Tag(itemId); } QUrl Tag::url() const { QUrlQuery query; query.addQueryItem(QStringLiteral("tag"), QString::number(id())); QUrl url; url.setScheme(QStringLiteral("akonadi")); url.setQuery(query); return url; } void Tag::addAttribute(Attribute *attr) { - const QByteArray typeAttr = attr->type(); - Attribute *existing = d_ptr->mAttributes.value(typeAttr); - if (existing) { - if (attr == existing) { - return; - } - d_ptr->mAttributes.remove(typeAttr); - delete existing; - } - d_ptr->mAttributes.insert(typeAttr, attr); - d_ptr->mDeletedAttributes.remove(typeAttr); + d_ptr->mAttributeStorage.addAttribute(attr); } void Tag::removeAttribute(const QByteArray &type) { - d_ptr->mDeletedAttributes.insert(type); - delete d_ptr->mAttributes.take(type); + d_ptr->mAttributeStorage.removeAttribute(type); } bool Tag::hasAttribute(const QByteArray &type) const { - return d_ptr->mAttributes.contains(type); + return d_ptr->mAttributeStorage.hasAttribute(type); } Attribute::List Tag::attributes() const { - return d_ptr->mAttributes.values(); + return d_ptr->mAttributeStorage.attributes(); } void Tag::clearAttributes() { - for (Attribute *attr : qAsConst(d_ptr->mAttributes)) { - d_ptr->mDeletedAttributes.insert(attr->type()); - delete attr; - } - d_ptr->mAttributes.clear(); + d_ptr->mAttributeStorage.clearAttributes(); } Attribute *Tag::attribute(const QByteArray &type) const { - return d_ptr->mAttributes.value(type); + return d_ptr->mAttributeStorage.attribute(type); } void Tag::setId(Tag::Id identifier) { d_ptr->id = identifier; } Tag::Id Tag::id() const { return d_ptr->id; } void Tag::setGid(const QByteArray &gid) { d_ptr->gid = gid; } QByteArray Tag::gid() const { return d_ptr->gid; } void Tag::setRemoteId(const QByteArray &remoteId) { d_ptr->remoteId = remoteId; } QByteArray Tag::remoteId() const { return d_ptr->remoteId; } void Tag::setName(const QString &name) { if (!name.isEmpty()) { TagAttribute *const attr = attribute(Tag::AddIfMissing); attr->setDisplayName(name); } } QString Tag::name() const { const TagAttribute *const attr = attribute(); const QString displayName = attr ? attr->displayName() : QString(); return !displayName.isEmpty() ? displayName : QString::fromUtf8(d_ptr->gid); } void Tag::setParent(const Tag &parent) { d_ptr->parent.reset(new Tag(parent)); } Tag Tag::parent() const { if (!d_ptr->parent) { return Tag(); } return *d_ptr->parent; } void Tag::setType(const QByteArray &type) { d_ptr->type = type; } QByteArray Tag::type() const { return d_ptr->type; } bool Tag::isValid() const { return d_ptr->id >= 0; } bool Tag::isImmutable() const { return (d_ptr->type.isEmpty() || d_ptr->type == PLAIN); } QDebug &operator<<(QDebug &debug, const Tag &tag) { QDebugStateSaver saver(debug); debug.nospace() << "Akonadi::Tag(ID " << tag.id() << ", GID " << tag.gid() << ", parent tag ID " << tag.parent().id() << ")"; return debug; } Tag Tag::genericTag(const QString &name) { Tag tag; tag.d_ptr->type = GENERIC; tag.d_ptr->gid = QUuid::createUuid().toByteArray().mid(1, 36); tag.setName(name); return tag; } bool Tag::checkAttribute(Attribute *attr, const QByteArray &type) const { if (attr) { return true; } qCWarning(AKONADICORE_LOG) << "Found attribute of unknown type" << type << ". Did you forget to call AttributeFactory::registerAttribute()?"; return false; } + +void Tag::markAttributeModified(const QByteArray &type) +{ + d_ptr->mAttributeStorage.markAttributeModified(type); +} diff --git a/src/core/tag.h b/src/core/tag.h index 52f6d1c53..3c220b22c 100644 --- a/src/core/tag.h +++ b/src/core/tag.h @@ -1,266 +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. */ #ifndef AKONADI_TAG_H #define AKONADI_TAG_H #include "akonadicore_export.h" #include "attribute.h" #include #include #include #include namespace Akonadi { class TagModifyJob; class TagPrivate; /** * An Akonadi Tag. */ class AKONADICORE_EXPORT Tag { public: typedef QVector List; typedef qint64 Id; /** * The PLAIN type has the following properties: * * gid == displayName * * immutable * * no hierarchy (no parent) * * PLAIN tags are general purpose tags that are easy to map by backends. */ static const char PLAIN[]; /** * The GENERIC type has the following properties: * * mutable * * gid is RFC 4122 compatible * * no hierarchy (no parent) * * GENERIC tags are general purpose tags, that are used, if you can change tag name. */ static const char GENERIC[]; Tag(); explicit Tag(Id id); /** * Creates a PLAIN tag */ explicit Tag(const QString &name); Tag(const Tag &other); ~Tag(); Tag &operator=(const Tag &); //Avoid slicing bool operator==(const Tag &) const; bool operator!=(const Tag &) const; static Tag fromUrl(const QUrl &url); /** * Adds an attribute to the entity. * * If an attribute of the same type name already exists, it is deleted and * replaced with the new one. * * @param attribute The new attribute. * * @note The entity takes the ownership of the attribute. */ void addAttribute(Attribute *attribute); /** * Removes and deletes the attribute of the given type @p name. */ void removeAttribute(const QByteArray &name); /** * Returns @c true if the entity has an attribute of the given type @p name, * false otherwise. */ bool hasAttribute(const QByteArray &name) const; /** * Returns a list of all attributes of the entity. */ Attribute::List attributes() const; /** * Removes and deletes all attributes of the entity. */ void clearAttributes(); /** * Returns the attribute of the given type @p name if available, 0 otherwise. */ Attribute *attribute(const QByteArray &name) const; /** * Describes the options that can be passed to access attributes. */ enum CreateOption { AddIfMissing ///< Creates the attribute if it is missing }; /** * Returns the attribute of the requested type. * If the entity has no attribute of that type yet, a new one * is created and added to the entity. * * @param option The create options. */ template inline T *attribute(CreateOption option); /** * Returns the attribute of the requested type or 0 if it is not available. */ template inline T *attribute() const; /** * Removes and deletes the attribute of the requested type. */ template inline void removeAttribute(); /** * Returns whether the entity has an attribute of the requested type. */ template inline bool hasAttribute() const; /** * Returns the url of the tag. */ QUrl url() const; /** * Sets the unique @p identifier of the tag. */ void setId(Id identifier); /** * Returns the unique identifier of the tag. */ Id id() const; void setGid(const QByteArray &gid); QByteArray gid() const; void setRemoteId(const QByteArray &remoteId); QByteArray remoteId() const; void setType(const QByteArray &type); QByteArray type() const; void setName(const QString &name); QString name() const; void setParent(const Tag &parent); Tag parent() const; bool isValid() const; /** * Returns true if the tag is immutable (cannot be modified after creation). * Note that the immutability does not affect the attributes. */ bool isImmutable() const; /** * Returns a GENERIC tag with the given name and a valid gid */ static Tag genericTag(const QString &name); private: bool checkAttribute(Attribute *attr, const QByteArray &type) const; + void markAttributeModified(const QByteArray &type); //@cond PRIVATE friend class TagModifyJob; + friend class TagFetchJob; + friend class ProtocolHelper; QSharedDataPointer d_ptr; //@endcond }; AKONADICORE_EXPORT uint qHash(const Akonadi::Tag &); template inline T *Tag::attribute(CreateOption option) { Q_UNUSED(option); - const T dummy; - if (hasAttribute(dummy.type())) { - T *attr = dynamic_cast(attribute(dummy.type())); - if (checkAttribute(attr, dummy.type())) { + const QByteArray type = T().type(); + if (hasAttribute(type)) { + T *attr = dynamic_cast(attribute(type)); + if (checkAttribute(attr, type)) { + markAttributeModified(type); return attr; } } T *attr = new T(); addAttribute(attr); return attr; } template inline T *Tag::attribute() const { const T dummy; if (hasAttribute(dummy.type())) { T *attr = dynamic_cast(attribute(dummy.type())); if (checkAttribute(attr, dummy.type())) { return attr; } } return nullptr; } template inline void Tag::removeAttribute() { const T dummy; removeAttribute(dummy.type()); } template inline bool Tag::hasAttribute() const { const T dummy; return hasAttribute(dummy.type()); } } // namespace Akonadi AKONADICORE_EXPORT QDebug &operator<<(QDebug &debug, const Akonadi::Tag &tag); Q_DECLARE_METATYPE(Akonadi::Tag) Q_DECLARE_METATYPE(Akonadi::Tag::List) Q_DECLARE_METATYPE(QSet) Q_DECLARE_TYPEINFO(Akonadi::Tag, Q_MOVABLE_TYPE); #endif diff --git a/src/core/tag_p.h b/src/core/tag_p.h index 1692f968e..ef7999b13 100644 --- a/src/core/tag_p.h +++ b/src/core/tag_p.h @@ -1,71 +1,72 @@ /* Copyright (c) 2014 Christian Mollekopf 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 TAG_P_H #define TAG_P_H +#include "attributestorage_p.h" #include "tag.h" namespace Akonadi { class TagPrivate : public QSharedData { public: TagPrivate() : id(-1) { } TagPrivate(const TagPrivate &other) : QSharedData(other) { id = other.id; gid = other.gid; remoteId = other.remoteId; if (other.parent) { parent.reset(new Tag(*other.parent)); } type = other.type; - for (Attribute *attr : qAsConst(other.mAttributes)) { - mAttributes.insert(attr->type(), attr->clone()); - } - mDeletedAttributes = other.mDeletedAttributes; + mAttributeStorage = other.mAttributeStorage; } ~TagPrivate() { - qDeleteAll(mAttributes); + } + + void resetChangeLog() + { + mAttributeStorage.resetChangeLog(); } // 4 bytes padding here (after QSharedData) Tag::Id id; QByteArray gid; QByteArray remoteId; QScopedPointer parent; QByteArray type; - QHash mAttributes; - QSet mDeletedAttributes; + AttributeStorage mAttributeStorage; }; } #endif