diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-30 10:34:57 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-30 10:34:57 +0100 |
commit | 84957496800a862aa88bb2e88da0a9b2c4e19dc2 (patch) | |
tree | 88ca1be0f86d32653b18c01892a633d5015ccf32 /examples/maildirresource/maildirresource.cpp | |
parent | 02c311e38b8b9b80814a4e8e582d5c5a56a51056 (diff) | |
download | sink-84957496800a862aa88bb2e88da0a9b2c4e19dc2.tar.gz sink-84957496800a862aa88bb2e88da0a9b2c4e19dc2.zip |
Moved all generic synchronization code to the base class.
Diffstat (limited to 'examples/maildirresource/maildirresource.cpp')
-rw-r--r-- | examples/maildirresource/maildirresource.cpp | 223 |
1 files changed, 35 insertions, 188 deletions
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 6c6c5aa..273b996 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp | |||
@@ -63,46 +63,6 @@ MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSh | |||
63 | Trace() << "Started maildir resource for maildir: " << mMaildirPath; | 63 | Trace() << "Started maildir resource for maildir: " << mMaildirPath; |
64 | } | 64 | } |
65 | 65 | ||
66 | void MaildirResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Akonadi2::Storage::Transaction &transaction) | ||
67 | { | ||
68 | Index index("rid.mapping." + bufferType, transaction); | ||
69 | Index localIndex("localid.mapping." + bufferType, transaction); | ||
70 | index.add(remoteId, localId); | ||
71 | localIndex.add(localId, remoteId); | ||
72 | } | ||
73 | |||
74 | void MaildirResource::removeRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Akonadi2::Storage::Transaction &transaction) | ||
75 | { | ||
76 | Index index("rid.mapping." + bufferType, transaction); | ||
77 | Index localIndex("localid.mapping." + bufferType, transaction); | ||
78 | index.remove(remoteId, localId); | ||
79 | localIndex.remove(localId, remoteId); | ||
80 | } | ||
81 | |||
82 | QString MaildirResource::resolveRemoteId(const QByteArray &bufferType, const QString &remoteId, Akonadi2::Storage::Transaction &transaction) | ||
83 | { | ||
84 | //Lookup local id for remote id, or insert a new pair otherwise | ||
85 | Index index("rid.mapping." + bufferType, transaction); | ||
86 | Index localIndex("localid.mapping." + bufferType, transaction); | ||
87 | QByteArray akonadiId = index.lookup(remoteId.toUtf8()); | ||
88 | if (akonadiId.isEmpty()) { | ||
89 | akonadiId = QUuid::createUuid().toString().toUtf8(); | ||
90 | index.add(remoteId.toUtf8(), akonadiId); | ||
91 | localIndex.add(akonadiId, remoteId.toUtf8()); | ||
92 | } | ||
93 | return akonadiId; | ||
94 | } | ||
95 | |||
96 | QString MaildirResource::resolveLocalId(const QByteArray &bufferType, const QByteArray &localId, Akonadi2::Storage::Transaction &transaction) | ||
97 | { | ||
98 | Index index("localid.mapping." + bufferType, transaction); | ||
99 | QByteArray remoteId = index.lookup(localId); | ||
100 | if (remoteId.isEmpty()) { | ||
101 | Warning() << "Couldn't find the remote id for " << localId; | ||
102 | } | ||
103 | return remoteId; | ||
104 | } | ||
105 | |||
106 | static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) | 66 | static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) |
107 | { | 67 | { |
108 | QStringList list; | 68 | QStringList list; |
@@ -130,136 +90,28 @@ QStringList MaildirResource::listAvailableFolders() | |||
130 | return folderList; | 90 | return folderList; |
131 | } | 91 | } |
132 | 92 | ||
133 | static void createEntity(const QByteArray &akonadiId, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) | ||
134 | { | ||
135 | //These changes are coming from the source | ||
136 | const auto replayToSource = false; | ||
137 | flatbuffers::FlatBufferBuilder entityFbb; | ||
138 | adaptorFactory.createBuffer(domainObject, entityFbb); | ||
139 | flatbuffers::FlatBufferBuilder fbb; | ||
140 | //This is the resource type and not the domain type | ||
141 | auto entityId = fbb.CreateString(akonadiId.toStdString()); | ||
142 | auto type = fbb.CreateString(bufferType.toStdString()); | ||
143 | auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize()); | ||
144 | auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta, replayToSource); | ||
145 | Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); | ||
146 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | ||
147 | } | ||
148 | |||
149 | static void modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) | ||
150 | { | ||
151 | //These changes are coming from the source | ||
152 | const auto replayToSource = false; | ||
153 | flatbuffers::FlatBufferBuilder entityFbb; | ||
154 | adaptorFactory.createBuffer(domainObject, entityFbb); | ||
155 | flatbuffers::FlatBufferBuilder fbb; | ||
156 | auto entityId = fbb.CreateString(akonadiId.toStdString()); | ||
157 | //This is the resource type and not the domain type | ||
158 | auto type = fbb.CreateString(bufferType.toStdString()); | ||
159 | auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize()); | ||
160 | //TODO removals | ||
161 | auto location = Akonadi2::Commands::CreateModifyEntity(fbb, revision, entityId, 0, type, delta, replayToSource); | ||
162 | Akonadi2::Commands::FinishModifyEntityBuffer(fbb, location); | ||
163 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | ||
164 | } | ||
165 | |||
166 | static void deleteEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, std::function<void(const QByteArray &)> callback) | ||
167 | { | ||
168 | //These changes are coming from the source | ||
169 | const auto replayToSource = false; | ||
170 | flatbuffers::FlatBufferBuilder fbb; | ||
171 | auto entityId = fbb.CreateString(akonadiId.toStdString()); | ||
172 | //This is the resource type and not the domain type | ||
173 | auto type = fbb.CreateString(bufferType.toStdString()); | ||
174 | auto location = Akonadi2::Commands::CreateDeleteEntity(fbb, revision, entityId, type, replayToSource); | ||
175 | Akonadi2::Commands::FinishDeleteEntityBuffer(fbb, location); | ||
176 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | ||
177 | } | ||
178 | |||
179 | static QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> getLatest(const Akonadi2::Storage::NamedDatabase &db, const QByteArray &uid, DomainTypeAdaptorFactoryInterface &adaptorFactory) | ||
180 | { | ||
181 | QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> current; | ||
182 | db.findLatest(uid, [¤t, &adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool { | ||
183 | Akonadi2::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); | ||
184 | if (!buffer.isValid()) { | ||
185 | Warning() << "Read invalid buffer from disk"; | ||
186 | } else { | ||
187 | current = adaptorFactory.createAdaptor(buffer.entity()); | ||
188 | } | ||
189 | return false; | ||
190 | }, | ||
191 | [](const Akonadi2::Storage::Error &error) { | ||
192 | Warning() << "Failed to read current value from storage: " << error.message; | ||
193 | }); | ||
194 | return current; | ||
195 | } | ||
196 | |||
197 | void MaildirResource::scanForRemovals(Akonadi2::Storage::Transaction &transaction, Akonadi2::Storage::Transaction &synchronizationTransaction, const QByteArray &bufferType, std::function<bool(const QByteArray &remoteId)> exists) | ||
198 | { | ||
199 | auto mainDatabase = transaction.openDatabase(bufferType + ".main"); | ||
200 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | ||
201 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | ||
202 | //but we currently fail to iterate over all entries in an index it seems. | ||
203 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Akonadi2::Storage::Error &)>(), true); | ||
204 | mainDatabase.scan("", [this, &transaction, bufferType, &synchronizationTransaction, &exists](const QByteArray &key, const QByteArray &) { | ||
205 | auto akonadiId = Akonadi2::Storage::uidFromKey(key); | ||
206 | Trace() << "Checking for removal " << key; | ||
207 | const auto remoteId = resolveLocalId(bufferType, akonadiId, synchronizationTransaction); | ||
208 | if (!remoteId.isEmpty()) { | ||
209 | if (!exists(remoteId.toLatin1())) { | ||
210 | Trace() << "Found a removed entity: " << akonadiId; | ||
211 | deleteEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, [this](const QByteArray &buffer) { | ||
212 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::DeleteEntityCommand, buffer); | ||
213 | }); | ||
214 | } | ||
215 | } | ||
216 | return true; | ||
217 | }, | ||
218 | [](const Akonadi2::Storage::Error &error) { | ||
219 | }); | ||
220 | |||
221 | } | ||
222 | |||
223 | void MaildirResource::createOrModify(Akonadi2::Storage::Transaction &transaction, Akonadi2::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, const QByteArray &bufferType, const QByteArray &remoteId, const Akonadi2::ApplicationDomain::ApplicationDomainType &entity) | ||
224 | { | ||
225 | auto mainDatabase = transaction.openDatabase(bufferType + ".main"); | ||
226 | const auto akonadiId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction).toLatin1(); | ||
227 | const auto found = mainDatabase.contains(akonadiId); | ||
228 | if (!found) { | ||
229 | Trace() << "Found a new entity: " << remoteId; | ||
230 | createEntity(akonadiId, bufferType, entity, adaptorFactory, [this](const QByteArray &buffer) { | ||
231 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, buffer); | ||
232 | }); | ||
233 | } else { //modification | ||
234 | if (auto current = getLatest(mainDatabase, akonadiId, adaptorFactory)) { | ||
235 | bool changed = false; | ||
236 | for (const auto &property : entity.changedProperties()) { | ||
237 | if (entity.getProperty(property) != current->getProperty(property)) { | ||
238 | Trace() << "Property changed " << akonadiId << property; | ||
239 | changed = true; | ||
240 | } | ||
241 | } | ||
242 | if (changed) { | ||
243 | Trace() << "Found a modified entity: " << remoteId; | ||
244 | modifyEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, entity, adaptorFactory, [this](const QByteArray &buffer) { | ||
245 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::ModifyEntityCommand, buffer); | ||
246 | }); | ||
247 | } | ||
248 | } else { | ||
249 | Warning() << "Failed to get current entity"; | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | |||
254 | void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction, Akonadi2::Storage::Transaction &synchronizationTransaction) | 93 | void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction, Akonadi2::Storage::Transaction &synchronizationTransaction) |
255 | { | 94 | { |
256 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | 95 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; |
257 | QStringList folderList = listAvailableFolders(); | 96 | QStringList folderList = listAvailableFolders(); |
258 | Trace() << "Found folders " << folderList; | 97 | Trace() << "Found folders " << folderList; |
259 | 98 | ||
260 | scanForRemovals(transaction, synchronizationTransaction, bufferType, [&folderList](const QByteArray &remoteId) -> bool { | 99 | scanForRemovals(transaction, synchronizationTransaction, bufferType, |
261 | return folderList.contains(remoteId); | 100 | [&bufferType, &transaction](const std::function<void(const QByteArray &)> &callback) { |
262 | }); | 101 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, |
102 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | ||
103 | //but we currently fail to iterate over all entries in an index it seems. | ||
104 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Akonadi2::Storage::Error &)>(), true); | ||
105 | auto mainDatabase = transaction.openDatabase(bufferType + ".main"); | ||
106 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { | ||
107 | callback(key); | ||
108 | return true; | ||
109 | }); | ||
110 | }, | ||
111 | [&folderList](const QByteArray &remoteId) -> bool { | ||
112 | return folderList.contains(remoteId); | ||
113 | } | ||
114 | ); | ||
263 | 115 | ||
264 | for (const auto folderPath : folderList) { | 116 | for (const auto folderPath : folderList) { |
265 | const auto remoteId = folderPath.toUtf8(); | 117 | const auto remoteId = folderPath.toUtf8(); |
@@ -270,7 +122,7 @@ void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transac | |||
270 | folder.setProperty("name", md.name()); | 122 | folder.setProperty("name", md.name()); |
271 | folder.setProperty("icon", "folder"); | 123 | folder.setProperty("icon", "folder"); |
272 | if (!md.isRoot()) { | 124 | if (!md.isRoot()) { |
273 | folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), synchronizationTransaction).toLatin1()); | 125 | folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8(), synchronizationTransaction)); |
274 | } | 126 | } |
275 | createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); | 127 | createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); |
276 | } | 128 | } |
@@ -293,28 +145,23 @@ void MaildirResource::synchronizeMails(Akonadi2::Storage::Transaction &transacti | |||
293 | 145 | ||
294 | QFileInfo entryInfo; | 146 | QFileInfo entryInfo; |
295 | 147 | ||
296 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path, synchronizationTransaction); | 148 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); |
297 | |||
298 | auto exists = [&listingPath](const QByteArray &remoteId) -> bool { | ||
299 | return QFile(listingPath + "/" + remoteId).exists(); | ||
300 | }; | ||
301 | 149 | ||
302 | auto property = "folder"; | 150 | auto property = "folder"; |
303 | Index index(bufferType + ".index." + property, transaction); | 151 | scanForRemovals(transaction, synchronizationTransaction, bufferType, |
304 | index.lookup(folderLocalId.toLatin1(), [&](const QByteArray &akonadiId) { | 152 | [&](const std::function<void(const QByteArray &)> &callback) { |
305 | const auto remoteId = resolveLocalId(bufferType, akonadiId, synchronizationTransaction); | 153 | Index index(bufferType + ".index." + property, transaction); |
306 | if (!remoteId.isEmpty()) { | 154 | index.lookup(folderLocalId, [&](const QByteArray &akonadiId) { |
307 | if (!exists(remoteId.toLatin1())) { | 155 | callback(akonadiId); |
308 | Trace() << "Found a removed entity: " << akonadiId; | 156 | }, |
309 | deleteEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, [this](const QByteArray &buffer) { | 157 | [&](const Index::Error &error) { |
310 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::DeleteEntityCommand, buffer); | 158 | Warning() << "Error in index: " << error.message << property; |
311 | }); | 159 | }); |
312 | } | 160 | }, |
161 | [&listingPath](const QByteArray &remoteId) -> bool { | ||
162 | return QFile(listingPath + "/" + remoteId).exists(); | ||
313 | } | 163 | } |
314 | }, | 164 | ); |
315 | [property](const Index::Error &error) { | ||
316 | Warning() << "Error in index: " << error.message << property; | ||
317 | }); | ||
318 | 165 | ||
319 | while (entryIterator->hasNext()) { | 166 | while (entryIterator->hasNext()) { |
320 | QString filePath = entryIterator->next(); | 167 | QString filePath = entryIterator->next(); |
@@ -398,7 +245,7 @@ KAsync::Job<void> MaildirResource::replay(const QByteArray &type, const QByteArr | |||
398 | Trace() << "Removing a folder: " << path; | 245 | Trace() << "Removing a folder: " << path; |
399 | KPIM::Maildir maildir(path, false); | 246 | KPIM::Maildir maildir(path, false); |
400 | maildir.remove(); | 247 | maildir.remove(); |
401 | removeRemoteId(ENTITY_TYPE_FOLDER, uid, remoteId.toUtf8(), synchronizationTransaction); | 248 | removeRemoteId(ENTITY_TYPE_FOLDER, uid, remoteId, synchronizationTransaction); |
402 | } else if (operation == Akonadi2::Operation_Modification) { | 249 | } else if (operation == Akonadi2::Operation_Modification) { |
403 | Warning() << "Folder modifications are not implemented"; | 250 | Warning() << "Folder modifications are not implemented"; |
404 | } else { | 251 | } else { |
@@ -419,9 +266,9 @@ KAsync::Job<void> MaildirResource::replay(const QByteArray &type, const QByteArr | |||
419 | auto parentFolder = mail.getProperty("folder").toByteArray(); | 266 | auto parentFolder = mail.getProperty("folder").toByteArray(); |
420 | QByteArray parentFolderRemoteId; | 267 | QByteArray parentFolderRemoteId; |
421 | if (!parentFolder.isEmpty()) { | 268 | if (!parentFolder.isEmpty()) { |
422 | parentFolderRemoteId = resolveLocalId(ENTITY_TYPE_FOLDER, parentFolder, synchronizationTransaction).toLatin1(); | 269 | parentFolderRemoteId = resolveLocalId(ENTITY_TYPE_FOLDER, parentFolder, synchronizationTransaction); |
423 | } else { | 270 | } else { |
424 | parentFolderRemoteId = mMaildirPath.toLatin1(); | 271 | parentFolderRemoteId = mMaildirPath.toUtf8(); |
425 | } | 272 | } |
426 | const auto parentFolderPath = parentFolderRemoteId; | 273 | const auto parentFolderPath = parentFolderRemoteId; |
427 | KPIM::Maildir maildir(parentFolderPath, false); | 274 | KPIM::Maildir maildir(parentFolderPath, false); |
@@ -439,7 +286,7 @@ KAsync::Job<void> MaildirResource::replay(const QByteArray &type, const QByteArr | |||
439 | KPIM::Maildir maildir(parentFolderPath, false); | 286 | KPIM::Maildir maildir(parentFolderPath, false); |
440 | Trace() << "Removing a mail: " << remoteId; | 287 | Trace() << "Removing a mail: " << remoteId; |
441 | maildir.removeEntry(remoteId); | 288 | maildir.removeEntry(remoteId); |
442 | removeRemoteId(ENTITY_TYPE_MAIL, uid, remoteId.toUtf8(), synchronizationTransaction); | 289 | removeRemoteId(ENTITY_TYPE_MAIL, uid, remoteId, synchronizationTransaction); |
443 | } else if (operation == Akonadi2::Operation_Modification) { | 290 | } else if (operation == Akonadi2::Operation_Modification) { |
444 | Warning() << "Mail modifications are not implemented"; | 291 | Warning() << "Mail modifications are not implemented"; |
445 | } else { | 292 | } else { |