diff options
Diffstat (limited to 'examples/dummyresource/facade.cpp')
-rw-r--r-- | examples/dummyresource/facade.cpp | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/examples/dummyresource/facade.cpp b/examples/dummyresource/facade.cpp new file mode 100644 index 0000000..e50e4f3 --- /dev/null +++ b/examples/dummyresource/facade.cpp | |||
@@ -0,0 +1,189 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "facade.h" | ||
21 | |||
22 | #include <QDebug> | ||
23 | #include <functional> | ||
24 | |||
25 | #include "common/resourceaccess.h" | ||
26 | #include "common/commands.h" | ||
27 | #include "dummycalendar_generated.h" | ||
28 | #include "event_generated.h" | ||
29 | #include "entity_generated.h" | ||
30 | #include "metadata_generated.h" | ||
31 | #include "domainadaptor.h" | ||
32 | #include <common/entitybuffer.h> | ||
33 | #include <common/index.h> | ||
34 | #include <common/log.h> | ||
35 | |||
36 | using namespace DummyCalendar; | ||
37 | using namespace flatbuffers; | ||
38 | |||
39 | |||
40 | DummyResourceFacade::DummyResourceFacade() | ||
41 | : Akonadi2::GenericFacade<Akonadi2::ApplicationDomain::Event>("org.kde.dummy"), | ||
42 | mFactory(new DummyEventAdaptorFactory) | ||
43 | { | ||
44 | } | ||
45 | |||
46 | DummyResourceFacade::~DummyResourceFacade() | ||
47 | { | ||
48 | } | ||
49 | |||
50 | Async::Job<void> DummyResourceFacade::create(const Akonadi2::ApplicationDomain::Event &domainObject) | ||
51 | { | ||
52 | flatbuffers::FlatBufferBuilder entityFbb; | ||
53 | mFactory->createBuffer(domainObject, entityFbb); | ||
54 | return sendCreateCommand("event", QByteArray::fromRawData(reinterpret_cast<const char*>(entityFbb.GetBufferPointer()), entityFbb.GetSize())); | ||
55 | } | ||
56 | |||
57 | Async::Job<void> DummyResourceFacade::modify(const Akonadi2::ApplicationDomain::Event &domainObject) | ||
58 | { | ||
59 | //Create message buffer and send to resource | ||
60 | return Async::null<void>(); | ||
61 | } | ||
62 | |||
63 | Async::Job<void> DummyResourceFacade::remove(const Akonadi2::ApplicationDomain::Event &domainObject) | ||
64 | { | ||
65 | //Create message buffer and send to resource | ||
66 | return Async::null<void>(); | ||
67 | } | ||
68 | |||
69 | static std::function<bool(const std::string &key, DummyEvent const *buffer, Akonadi2::ApplicationDomain::Buffer::Event const *local)> prepareQuery(const Akonadi2::Query &query) | ||
70 | { | ||
71 | //Compose some functions to make query matching fast. | ||
72 | //This way we can process the query once, and convert all values into something that can be compared quickly | ||
73 | std::function<bool(const std::string &key, DummyEvent const *buffer, Akonadi2::ApplicationDomain::Buffer::Event const *local)> preparedQuery; | ||
74 | if (!query.ids.isEmpty()) { | ||
75 | //Match by id | ||
76 | //TODO: for id's a direct lookup would be way faster | ||
77 | |||
78 | //We convert the id's to std::string so we don't have to convert each key during the scan. (This runs only once, and the query will be run for every key) | ||
79 | //Probably a premature optimization, but perhaps a useful technique to be investigated. | ||
80 | QVector<std::string> ids; | ||
81 | for (const auto &id : query.ids) { | ||
82 | ids << id.toStdString(); | ||
83 | } | ||
84 | preparedQuery = [ids](const std::string &key, DummyEvent const *buffer, Akonadi2::ApplicationDomain::Buffer::Event const *local) { | ||
85 | if (ids.contains(key)) { | ||
86 | return true; | ||
87 | } | ||
88 | return false; | ||
89 | }; | ||
90 | } else if (!query.propertyFilter.isEmpty()) { | ||
91 | if (query.propertyFilter.contains("uid")) { | ||
92 | const QByteArray uid = query.propertyFilter.value("uid").toByteArray(); | ||
93 | preparedQuery = [uid](const std::string &key, DummyEvent const *buffer, Akonadi2::ApplicationDomain::Buffer::Event const *local) { | ||
94 | if (local && local->uid() && (QByteArray::fromRawData(local->uid()->c_str(), local->uid()->size()) == uid)) { | ||
95 | return true; | ||
96 | } | ||
97 | return false; | ||
98 | }; | ||
99 | } | ||
100 | } else { | ||
101 | //Match everything | ||
102 | preparedQuery = [](const std::string &key, DummyEvent const *buffer, Akonadi2::ApplicationDomain::Buffer::Event const *local) { | ||
103 | return true; | ||
104 | }; | ||
105 | } | ||
106 | return preparedQuery; | ||
107 | } | ||
108 | |||
109 | void DummyResourceFacade::readValue(QSharedPointer<Akonadi2::Storage> storage, const QByteArray &key, const std::function<void(const Akonadi2::ApplicationDomain::Event::Ptr &)> &resultCallback, std::function<bool(const std::string &key, DummyEvent const *buffer, Akonadi2::ApplicationDomain::Buffer::Event const *local)> preparedQuery) | ||
110 | { | ||
111 | storage->scan(key, [=](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { | ||
112 | |||
113 | //Skip internals | ||
114 | if (Akonadi2::Storage::isInternalKey(keyValue, keySize)) { | ||
115 | return true; | ||
116 | } | ||
117 | |||
118 | //Extract buffers | ||
119 | Akonadi2::EntityBuffer buffer(dataValue, dataSize); | ||
120 | |||
121 | const auto resourceBuffer = Akonadi2::EntityBuffer::readBuffer<DummyEvent>(buffer.entity().resource()); | ||
122 | const auto localBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::ApplicationDomain::Buffer::Event>(buffer.entity().local()); | ||
123 | const auto metadataBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::Metadata>(buffer.entity().metadata()); | ||
124 | |||
125 | if ((!resourceBuffer && !localBuffer) || !metadataBuffer) { | ||
126 | qWarning() << "invalid buffer " << QByteArray::fromRawData(static_cast<char*>(keyValue), keySize); | ||
127 | return true; | ||
128 | } | ||
129 | |||
130 | //We probably only want to create all buffers after the scan | ||
131 | //TODO use adapter for query and scan? | ||
132 | if (preparedQuery && preparedQuery(std::string(static_cast<char*>(keyValue), keySize), resourceBuffer, localBuffer)) { | ||
133 | qint64 revision = metadataBuffer ? metadataBuffer->revision() : -1; | ||
134 | //This only works for a 1:1 mapping of resource to domain types. | ||
135 | //Not i.e. for tags that are stored as flags in each entity of an imap store. | ||
136 | auto adaptor = mFactory->createAdaptor(buffer.entity()); | ||
137 | //TODO only copy requested properties | ||
138 | auto memoryAdaptor = QSharedPointer<Akonadi2::ApplicationDomain::MemoryBufferAdaptor>::create(*adaptor); | ||
139 | // here we could copy additional properties that don't have a 1:1 mapping, such as separately stored tags. | ||
140 | auto event = QSharedPointer<Akonadi2::ApplicationDomain::Event>::create("org.kde.dummy", QByteArray::fromRawData(static_cast<char*>(keyValue), keySize), revision, memoryAdaptor); | ||
141 | resultCallback(event); | ||
142 | } | ||
143 | return true; | ||
144 | }, | ||
145 | [](const Akonadi2::Storage::Error &error) { | ||
146 | qWarning() << "Error during query: " << error.message; | ||
147 | }); | ||
148 | } | ||
149 | |||
150 | Async::Job<qint64> DummyResourceFacade::load(const Akonadi2::Query &query, const std::function<void(const Akonadi2::ApplicationDomain::Event::Ptr &)> &resultCallback) | ||
151 | { | ||
152 | return Async::start<qint64>([=](Async::Future<qint64> &future) { | ||
153 | //Now that the sync is complete we can execute the query | ||
154 | const auto preparedQuery = prepareQuery(query); | ||
155 | |||
156 | auto storage = QSharedPointer<Akonadi2::Storage>::create(Akonadi2::Store::storageLocation(), "org.kde.dummy"); | ||
157 | storage->setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) { | ||
158 | Warning() << "Error during query: " << error.store << error.message; | ||
159 | }); | ||
160 | |||
161 | storage->startTransaction(Akonadi2::Storage::ReadOnly); | ||
162 | const qint64 revision = storage->maxRevision(); | ||
163 | |||
164 | //Index lookups | ||
165 | QVector<QByteArray> keys; | ||
166 | if (query.propertyFilter.contains("uid")) { | ||
167 | static Index uidIndex(Akonadi2::Store::storageLocation(), "org.kde.dummy.index.uid", Akonadi2::Storage::ReadOnly); | ||
168 | uidIndex.lookup(query.propertyFilter.value("uid").toByteArray(), [&](const QByteArray &value) { | ||
169 | keys << value; | ||
170 | }, | ||
171 | [](const Index::Error &error) { | ||
172 | Warning() << "Error in index: " << error.message; | ||
173 | }); | ||
174 | } | ||
175 | |||
176 | if (keys.isEmpty()) { | ||
177 | Log() << "Executing a full scan"; | ||
178 | readValue(storage, QByteArray(), resultCallback, preparedQuery); | ||
179 | } else { | ||
180 | for (const auto &key : keys) { | ||
181 | readValue(storage, key, resultCallback, preparedQuery); | ||
182 | } | ||
183 | } | ||
184 | storage->abortTransaction(); | ||
185 | future.setValue(revision); | ||
186 | future.setFinished(); | ||
187 | }); | ||
188 | } | ||
189 | |||