summaryrefslogtreecommitdiffstats
path: root/examples/maildirresource/maildirresource.cpp
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2015-12-19 19:39:53 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2015-12-20 13:27:44 +0100
commitb1e5525be34850ef4d11cccbf23e118c93e93506 (patch)
tree68283fe123be3b1670eccc6a8e3711a9dda3f35e /examples/maildirresource/maildirresource.cpp
parent355ed15b1200dfb6ab5ecd320a3278105007ad9c (diff)
downloadsink-b1e5525be34850ef4d11cccbf23e118c93e93506.tar.gz
sink-b1e5525be34850ef4d11cccbf23e118c93e93506.zip
Generalized the sync algorithms and applied them to mail.
Not necessarily the smartest algorithms, but at least they work and are generally applicable.
Diffstat (limited to 'examples/maildirresource/maildirresource.cpp')
-rw-r--r--examples/maildirresource/maildirresource.cpp170
1 files changed, 84 insertions, 86 deletions
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
index 10e9046..90b4e55 100644
--- a/examples/maildirresource/maildirresource.cpp
+++ b/examples/maildirresource/maildirresource.cpp
@@ -114,7 +114,7 @@ QStringList MaildirResource::listAvailableFolders()
114 return folderList; 114 return folderList;
115} 115}
116 116
117static void createEntity(const QByteArray &akonadiId, const QByteArray &bufferType, Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) 117static void createEntity(const QByteArray &akonadiId, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback)
118{ 118{
119 flatbuffers::FlatBufferBuilder entityFbb; 119 flatbuffers::FlatBufferBuilder entityFbb;
120 adaptorFactory.createBuffer(domainObject, entityFbb); 120 adaptorFactory.createBuffer(domainObject, entityFbb);
@@ -128,7 +128,7 @@ static void createEntity(const QByteArray &akonadiId, const QByteArray &bufferTy
128 callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); 128 callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()));
129} 129}
130 130
131static void modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) 131static void modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, const Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback)
132{ 132{
133 flatbuffers::FlatBufferBuilder entityFbb; 133 flatbuffers::FlatBufferBuilder entityFbb;
134 adaptorFactory.createBuffer(domainObject, entityFbb); 134 adaptorFactory.createBuffer(domainObject, entityFbb);
@@ -172,40 +172,79 @@ static QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> getLatest(cons
172 return current; 172 return current;
173} 173}
174 174
175void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction) 175void MaildirResource::scanForRemovals(Akonadi2::Storage::Transaction &transaction, Akonadi2::Storage::Transaction &synchronizationTransaction, const QByteArray &bufferType, std::function<bool(const QByteArray &remoteId)> exists)
176{ 176{
177 const QByteArray bufferType = ENTITY_TYPE_FOLDER;
178 QStringList folderList = listAvailableFolders();
179 Trace() << "Found folders " << folderList;
180
181 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
182 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
183 auto mainDatabase = transaction.openDatabase(bufferType + ".main"); 177 auto mainDatabase = transaction.openDatabase(bufferType + ".main");
184
185 //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, 178 //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times,
186 //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, 179 //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index,
187 //but we currently fail to iterate over all entries in an index it seems. 180 //but we currently fail to iterate over all entries in an index it seems.
188 // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Akonadi2::Storage::Error &)>(), true); 181 // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Akonadi2::Storage::Error &)>(), true);
189 mainDatabase.scan("", [&folderList, this, &transaction, bufferType, &synchronizationTransaction](const QByteArray &key, const QByteArray &) { 182 mainDatabase.scan("", [this, &transaction, bufferType, &synchronizationTransaction, &exists](const QByteArray &key, const QByteArray &) {
190 auto akonadiId = Akonadi2::Storage::uidFromKey(key); 183 auto akonadiId = Akonadi2::Storage::uidFromKey(key);
184 Trace() << "Checking for removal " << key;
191 const auto remoteId = resolveLocalId(bufferType, akonadiId, synchronizationTransaction); 185 const auto remoteId = resolveLocalId(bufferType, akonadiId, synchronizationTransaction);
192 if (!folderList.contains(remoteId)) { 186 if (!remoteId.isEmpty()) {
193 Trace() << "Found a removed entity: " << akonadiId; 187 if (!exists(remoteId.toLatin1())) {
194 deleteEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, [this](const QByteArray &buffer) { 188 Trace() << "Found a removed entity: " << akonadiId;
195 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::DeleteEntityCommand, buffer); 189 deleteEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, [this](const QByteArray &buffer) {
196 }); 190 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::DeleteEntityCommand, buffer);
191 });
192 }
197 } 193 }
198 return true; 194 return true;
199 }, 195 },
200 [](const Akonadi2::Storage::Error &error) { 196 [](const Akonadi2::Storage::Error &error) {
201 }); 197 });
202 198
199}
200
201void MaildirResource::createOrModify(Akonadi2::Storage::Transaction &transaction, Akonadi2::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, const QByteArray &bufferType, const QByteArray &remoteId, const Akonadi2::ApplicationDomain::ApplicationDomainType &entity)
202{
203 auto mainDatabase = transaction.openDatabase(bufferType + ".main");
204 const auto akonadiId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction).toLatin1();
205 const auto found = mainDatabase.contains(akonadiId);
206 if (!found) {
207 Trace() << "Found a new entity: " << remoteId;
208 createEntity(akonadiId, bufferType, entity, adaptorFactory, [this](const QByteArray &buffer) {
209 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, buffer);
210 });
211 } else { //modification
212 if (auto current = getLatest(mainDatabase, akonadiId, adaptorFactory)) {
213 bool changed = false;
214 for (const auto &property : entity.changedProperties()) {
215 if (entity.getProperty(property) != current->getProperty(property)) {
216 Trace() << "Property changed " << akonadiId << property;
217 changed = true;
218 }
219 }
220 if (changed) {
221 Trace() << "Found a modified entity: " << remoteId;
222 modifyEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, entity, adaptorFactory, [this](const QByteArray &buffer) {
223 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::ModifyEntityCommand, buffer);
224 });
225 }
226 } else {
227 Warning() << "Failed to get current entity";
228 }
229 }
230}
231
232void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction)
233{
234 const QByteArray bufferType = ENTITY_TYPE_FOLDER;
235 QStringList folderList = listAvailableFolders();
236 Trace() << "Found folders " << folderList;
237
238 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
239 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
240 auto mainDatabase = transaction.openDatabase(bufferType + ".main");
241 scanForRemovals(transaction, synchronizationTransaction, bufferType, [&folderList](const QByteArray &remoteId) -> bool {
242 return folderList.contains(remoteId);
243 });
244
203 for (const auto folderPath : folderList) { 245 for (const auto folderPath : folderList) {
204 const auto remoteId = folderPath.toUtf8(); 246 const auto remoteId = folderPath.toUtf8();
205 Trace() << "Processing folder " << remoteId; 247 Trace() << "Processing folder " << remoteId;
206 const auto akonadiId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction).toLatin1();
207 const auto found = mainDatabase.contains(akonadiId);
208
209 KPIM::Maildir md(folderPath, folderPath == mMaildirPath); 248 KPIM::Maildir md(folderPath, folderPath == mMaildirPath);
210 249
211 Akonadi2::ApplicationDomain::Folder folder; 250 Akonadi2::ApplicationDomain::Folder folder;
@@ -214,31 +253,7 @@ void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transac
214 if (!md.isRoot()) { 253 if (!md.isRoot()) {
215 folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), synchronizationTransaction).toLatin1()); 254 folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), synchronizationTransaction).toLatin1());
216 } 255 }
217 256 createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder);
218 if (!found) {
219 Trace() << "Found a new entity: " << remoteId;
220 createEntity(akonadiId, bufferType, folder, *mFolderAdaptorFactory, [this](const QByteArray &buffer) {
221 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, buffer);
222 });
223 } else { //modification
224 if (auto current = getLatest(mainDatabase, akonadiId, *mFolderAdaptorFactory)) {
225 bool changed = false;
226 for (const auto &property : folder.changedProperties()) {
227 if (folder.getProperty(property) != current->getProperty(property)) {
228 Trace() << "Property changed " << akonadiId << property;
229 changed = true;
230 }
231 }
232 if (changed) {
233 Trace() << "Found a modified entity: " << remoteId;
234 modifyEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, folder, *mFolderAdaptorFactory, [this](const QByteArray &buffer) {
235 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::ModifyEntityCommand, buffer);
236 });
237 }
238 } else {
239 Warning() << "Failed to get current entity";
240 }
241 }
242 } 257 }
243} 258}
244 259
@@ -262,53 +277,36 @@ void MaildirResource::synchronizeMails(Akonadi2::Storage::Transaction &transacti
262 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite); 277 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
263 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite); 278 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
264 279
280 scanForRemovals(transaction, synchronizationTransaction, bufferType, [&listingPath](const QByteArray &remoteId) -> bool {
281 return QFile(listingPath + QDir::separator() + remoteId).exists();
282 });
283
265 while (entryIterator->hasNext()) { 284 while (entryIterator->hasNext()) {
266 QString filePath = entryIterator->next(); 285 QString filePath = entryIterator->next();
267 QString fileName = entryIterator->fileName(); 286 QString fileName = entryIterator->fileName();
268
269 const auto remoteId = fileName.toUtf8(); 287 const auto remoteId = fileName.toUtf8();
270 auto akonadiId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction); 288
271 289 KMime::Message *msg = new KMime::Message;
272 bool found = false; 290 auto filepath = listingPath + QDir::separator() + fileName;
273 transaction.openDatabase(bufferType + ".main").scan(akonadiId.toUtf8(), [&found](const QByteArray &, const QByteArray &) -> bool { 291 msg->setHead(KMime::CRLFtoLF(maildir.readEntryHeadersFromFile(filepath)));
274 found = true; 292 msg->parse();
275 return false; 293
276 }, [this](const Akonadi2::Storage::Error &error) { 294 const auto flags = maildir.readEntryFlags(fileName);
277 }, true); 295
278 296 Trace() << "Found a mail " << filePath << fileName << msg->subject(true)->asUnicodeString();
279 if (!found) { //A new entity 297
280 KMime::Message *msg = new KMime::Message; 298 Akonadi2::ApplicationDomain::Mail mail;
281 auto filepath = listingPath + QDir::separator() + fileName; 299 mail.setProperty("subject", msg->subject(true)->asUnicodeString());
282 msg->setHead(KMime::CRLFtoLF(maildir.readEntryHeadersFromFile(filepath))); 300 mail.setProperty("sender", msg->from(true)->asUnicodeString());
283 msg->parse(); 301 mail.setProperty("senderName", msg->from(true)->asUnicodeString());
284 302 mail.setProperty("date", msg->date(true)->dateTime().toString());
285 const auto flags = maildir.readEntryFlags(fileName); 303 mail.setProperty("folder", resolveRemoteId(ENTITY_TYPE_FOLDER, path, synchronizationTransaction));
286 304 mail.setProperty("mimeMessage", filepath);
287 Trace() << "Found a mail " << filePath << fileName << msg->subject(true)->asUnicodeString(); 305 mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen));
288 306 mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged));
289 Akonadi2::ApplicationDomain::Mail mail; 307
290 mail.setProperty("subject", msg->subject(true)->asUnicodeString()); 308 createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail);
291 mail.setProperty("sender", msg->from(true)->asUnicodeString());
292 mail.setProperty("senderName", msg->from(true)->asUnicodeString());
293 mail.setProperty("date", msg->date(true)->dateTime().toString());
294 mail.setProperty("folder", resolveRemoteId(ENTITY_TYPE_FOLDER, path, synchronizationTransaction));
295 mail.setProperty("mimeMessage", filepath);
296 mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen));
297 mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged));
298
299 flatbuffers::FlatBufferBuilder entityFbb;
300 mMailAdaptorFactory->createBuffer(mail, entityFbb);
301
302 Trace() << "Found a new entity: " << remoteId;
303 createEntity(akonadiId.toLatin1(), bufferType, mail, *mMailAdaptorFactory, [this](const QByteArray &buffer) {
304 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, buffer);
305 });
306 } else { //modification
307 Trace() << "Found a modified entity: " << remoteId;
308 //TODO diff and create modification if necessary
309 }
310 } 309 }
311 //TODO find items to remove
312} 310}
313 311
314KAsync::Job<void> MaildirResource::synchronizeWithSource() 312KAsync::Job<void> MaildirResource::synchronizeWithSource()