diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-05-29 15:20:00 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-05-29 15:20:00 +0200 |
commit | f211ffd9aaa57fe46a792c3b005981d55dde670f (patch) | |
tree | 4a1cb4c8d2bc3072505a0518ea019969508f624f /examples/maildirresource/maildirresource.cpp | |
parent | dabd408dcd372f16c7934597db30346869cd8ad8 (diff) | |
download | sink-f211ffd9aaa57fe46a792c3b005981d55dde670f.tar.gz sink-f211ffd9aaa57fe46a792c3b005981d55dde670f.zip |
The maildirresource is back in action
Diffstat (limited to 'examples/maildirresource/maildirresource.cpp')
-rw-r--r-- | examples/maildirresource/maildirresource.cpp | 536 |
1 files changed, 280 insertions, 256 deletions
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 3f6ae54..9503971 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp | |||
@@ -36,6 +36,9 @@ | |||
36 | #include "indexupdater.h" | 36 | #include "indexupdater.h" |
37 | #include "libmaildir/maildir.h" | 37 | #include "libmaildir/maildir.h" |
38 | #include "inspection.h" | 38 | #include "inspection.h" |
39 | #include "synchronizer.h" | ||
40 | #include "sourcewriteback.h" | ||
41 | #include "adaptorfactoryregistry.h" | ||
39 | #include <QDate> | 42 | #include <QDate> |
40 | #include <QUuid> | 43 | #include <QUuid> |
41 | #include <QDir> | 44 | #include <QDir> |
@@ -82,13 +85,13 @@ public: | |||
82 | db.findLatest(folderIdentifier, [&](const QByteArray &, const QByteArray &value) { | 85 | db.findLatest(folderIdentifier, [&](const QByteArray &, const QByteArray &value) { |
83 | Sink::EntityBuffer buffer(value); | 86 | Sink::EntityBuffer buffer(value); |
84 | const Sink::Entity &entity = buffer.entity(); | 87 | const Sink::Entity &entity = buffer.entity(); |
85 | const auto adaptor = mFolderAdaptorFactory->createAdaptor(entity); | 88 | const auto adaptor = Sink::AdaptorFactoryRegistry::instance().getFactory<Sink::ApplicationDomain::Folder>(PLUGIN_NAME)->createAdaptor(entity); |
86 | auto parentFolder = adaptor->getProperty("parent").toString(); | 89 | auto parentFolder = adaptor->getProperty("parent").toString(); |
87 | if (mMaildirPath.endsWith(adaptor->getProperty("name").toString())) { | 90 | if (mMaildirPath.endsWith(adaptor->getProperty("name").toString())) { |
88 | folderPath = mMaildirPath; | 91 | folderPath = mMaildirPath; |
89 | } else { | 92 | } else { |
90 | auto folderName = adaptor->getProperty("name").toString(); | 93 | auto folderName = adaptor->getProperty("name").toString(); |
91 | //TODO handle non toplevel folders | 94 | //FIXME handle non toplevel folders |
92 | folderPath = mMaildirPath + "/" + folderName; | 95 | folderPath = mMaildirPath + "/" + folderName; |
93 | } | 96 | } |
94 | }); | 97 | }); |
@@ -140,19 +143,42 @@ public: | |||
140 | { | 143 | { |
141 | //TODO deal with moves | 144 | //TODO deal with moves |
142 | const auto mimeMessage = newEntity.getProperty("mimeMessage"); | 145 | const auto mimeMessage = newEntity.getProperty("mimeMessage"); |
143 | if (mimeMessage.isValid()) { | 146 | if (mimeMessage.isValid() && mimeMessage.toString() != oldEntity.getProperty("mimeMessage").toString()) { |
147 | //Remove the olde mime message if there is a new one | ||
148 | const auto filePath = getFilePathFromMimeMessagePath(oldEntity.getProperty("mimeMessage").toString()); | ||
149 | QFile::remove(filePath); | ||
150 | |||
144 | newEntity.setProperty("mimeMessage", moveMessage(mimeMessage.toString(), newEntity.getProperty("folder").toByteArray(), transaction)); | 151 | newEntity.setProperty("mimeMessage", moveMessage(mimeMessage.toString(), newEntity.getProperty("folder").toByteArray(), transaction)); |
152 | Trace() << "Modified message: " << filePath << oldEntity.getProperty("mimeMessage").toString(); | ||
153 | } | ||
154 | |||
155 | auto mimeMessagePath = newEntity.getProperty("mimeMessage").toString(); | ||
156 | const auto maildirPath = getPath(newEntity.getProperty("folder").toByteArray(), transaction); | ||
157 | KPIM::Maildir maildir(maildirPath, false); | ||
158 | QString identifier = KPIM::Maildir::getKeyFromFile(mimeMessagePath); | ||
159 | |||
160 | //get flags from | ||
161 | KPIM::Maildir::Flags flags; | ||
162 | if (!newEntity.getProperty("unread").toBool()) { | ||
163 | flags |= KPIM::Maildir::Seen; | ||
164 | } | ||
165 | if (newEntity.getProperty("important").toBool()) { | ||
166 | flags |= KPIM::Maildir::Flagged; | ||
145 | } | 167 | } |
168 | |||
169 | const auto newRemoteId = maildir.changeEntryFlags(identifier, flags); | ||
170 | |||
146 | updatedIndexedProperties(newEntity); | 171 | updatedIndexedProperties(newEntity); |
147 | } | 172 | } |
148 | 173 | ||
149 | void deletedEntity(const QByteArray &uid, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &oldEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE | 174 | void deletedEntity(const QByteArray &uid, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &oldEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE |
150 | { | 175 | { |
176 | const auto filePath = getFilePathFromMimeMessagePath(oldEntity.getProperty("mimeMessage").toString()); | ||
177 | QFile::remove(filePath); | ||
151 | } | 178 | } |
152 | QByteArray mDraftsFolder; | 179 | QByteArray mDraftsFolder; |
153 | QByteArray mResourceInstanceIdentifier; | 180 | QByteArray mResourceInstanceIdentifier; |
154 | QString mMaildirPath; | 181 | QString mMaildirPath; |
155 | QSharedPointer<MaildirFolderAdaptorFactory> mFolderAdaptorFactory; | ||
156 | }; | 182 | }; |
157 | 183 | ||
158 | class FolderPreprocessor : public Sink::Preprocessor | 184 | class FolderPreprocessor : public Sink::Preprocessor |
@@ -179,282 +205,271 @@ public: | |||
179 | QString mMaildirPath; | 205 | QString mMaildirPath; |
180 | }; | 206 | }; |
181 | 207 | ||
182 | MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | ||
183 | : Sink::GenericResource(instanceIdentifier, pipeline), | ||
184 | mMailAdaptorFactory(QSharedPointer<MaildirMailAdaptorFactory>::create()), | ||
185 | mFolderAdaptorFactory(QSharedPointer<MaildirFolderAdaptorFactory>::create()) | ||
186 | { | ||
187 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); | ||
188 | mMaildirPath = QDir::cleanPath(QDir::fromNativeSeparators(config.value("path").toString())); | ||
189 | //Chop a trailing slash if necessary | ||
190 | if (mMaildirPath.endsWith("/")) { | ||
191 | mMaildirPath.chop(1); | ||
192 | } | ||
193 | |||
194 | auto folderUpdater = new FolderUpdater(QByteArray()); | ||
195 | addType(ENTITY_TYPE_MAIL, mMailAdaptorFactory, | ||
196 | QVector<Sink::Preprocessor*>() << folderUpdater << new DefaultIndexUpdater<Sink::ApplicationDomain::Mail>); | ||
197 | auto folderPreprocessor = new FolderPreprocessor; | ||
198 | addType(ENTITY_TYPE_FOLDER, mFolderAdaptorFactory, | ||
199 | QVector<Sink::Preprocessor*>() << folderPreprocessor << new DefaultIndexUpdater<Sink::ApplicationDomain::Folder>); | ||
200 | 208 | ||
201 | KPIM::Maildir dir(mMaildirPath, true); | 209 | class MaildirSynchronizer : public Sink::Synchronizer { |
202 | mDraftsFolder = dir.addSubFolder("drafts"); | 210 | public: |
203 | Trace() << "Started maildir resource for maildir: " << mMaildirPath; | 211 | MaildirSynchronizer(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) |
204 | auto mainStore = QSharedPointer<Sink::Storage>::create(Sink::storageLocation(), mResourceInstanceIdentifier, Sink::Storage::ReadOnly); | 212 | : Sink::Synchronizer(resourceType, resourceInstanceIdentifier) |
205 | auto syncStore = QSharedPointer<Sink::Storage>::create(Sink::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Sink::Storage::ReadWrite); | 213 | { |
206 | auto transaction = mainStore->createTransaction(Sink::Storage::ReadOnly); | ||
207 | auto synchronizationTransaction = syncStore->createTransaction(Sink::Storage::ReadWrite); | ||
208 | |||
209 | auto remoteId = createFolder(mDraftsFolder, "folder", transaction, synchronizationTransaction); | ||
210 | auto draftsFolderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId, synchronizationTransaction); | ||
211 | synchronizationTransaction.commit(); | ||
212 | |||
213 | folderUpdater->mDraftsFolder = draftsFolderLocalId; | ||
214 | folderUpdater->mResourceInstanceIdentifier = mResourceInstanceIdentifier; | ||
215 | folderUpdater->mFolderAdaptorFactory = mFolderAdaptorFactory; | ||
216 | folderUpdater->mMaildirPath = mMaildirPath; | ||
217 | folderPreprocessor->mMaildirPath = mMaildirPath; | ||
218 | } | ||
219 | |||
220 | static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) | ||
221 | { | ||
222 | QStringList list; | ||
223 | foreach (const QString &sub, dir.subFolderList()) { | ||
224 | const KPIM::Maildir md = dir.subFolder(sub); | ||
225 | if (!md.isValid()) { | ||
226 | continue; | ||
227 | } | ||
228 | QString path = root + "/" + sub; | ||
229 | list << path; | ||
230 | list += listRecursive(path, md ); | ||
231 | } | ||
232 | return list; | ||
233 | } | ||
234 | 214 | ||
235 | QByteArray MaildirResource::createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | ||
236 | { | ||
237 | auto remoteId = folderPath.toUtf8(); | ||
238 | auto bufferType = ENTITY_TYPE_FOLDER; | ||
239 | KPIM::Maildir md(folderPath, folderPath == mMaildirPath); | ||
240 | Sink::ApplicationDomain::Folder folder; | ||
241 | folder.setProperty("name", md.name()); | ||
242 | folder.setProperty("icon", icon); | ||
243 | |||
244 | if (!md.isRoot()) { | ||
245 | folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8(), synchronizationTransaction)); | ||
246 | } | 215 | } |
247 | createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); | ||
248 | return remoteId; | ||
249 | } | ||
250 | 216 | ||
251 | QStringList MaildirResource::listAvailableFolders() | 217 | static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) |
252 | { | 218 | { |
253 | KPIM::Maildir dir(mMaildirPath, true); | 219 | QStringList list; |
254 | if (!dir.isValid()) { | 220 | foreach (const QString &sub, dir.subFolderList()) { |
255 | return QStringList(); | 221 | const KPIM::Maildir md = dir.subFolder(sub); |
222 | if (!md.isValid()) { | ||
223 | continue; | ||
224 | } | ||
225 | QString path = root + "/" + sub; | ||
226 | list << path; | ||
227 | list += listRecursive(path, md ); | ||
228 | } | ||
229 | return list; | ||
256 | } | 230 | } |
257 | QStringList folderList; | ||
258 | folderList << mMaildirPath; | ||
259 | folderList += listRecursive(mMaildirPath, dir); | ||
260 | return folderList; | ||
261 | } | ||
262 | 231 | ||
263 | void MaildirResource::synchronizeFolders(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | 232 | QByteArray createFolder(const QString &folderPath, const QByteArray &icon) |
264 | { | 233 | { |
265 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | 234 | auto remoteId = folderPath.toUtf8(); |
266 | QStringList folderList = listAvailableFolders(); | 235 | auto bufferType = ENTITY_TYPE_FOLDER; |
267 | Trace() << "Found folders " << folderList; | 236 | KPIM::Maildir md(folderPath, folderPath == mMaildirPath); |
268 | 237 | Sink::ApplicationDomain::Folder folder; | |
269 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | 238 | folder.setProperty("name", md.name()); |
270 | [&bufferType, &transaction](const std::function<void(const QByteArray &)> &callback) { | 239 | folder.setProperty("icon", icon); |
271 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | 240 | |
272 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | 241 | if (!md.isRoot()) { |
273 | //but we currently fail to iterate over all entries in an index it seems. | 242 | folder.setProperty("parent", syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8())); |
274 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); | ||
275 | auto mainDatabase = Sink::Storage::mainDatabase(transaction, bufferType); | ||
276 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { | ||
277 | callback(key); | ||
278 | return true; | ||
279 | }); | ||
280 | }, | ||
281 | [&folderList](const QByteArray &remoteId) -> bool { | ||
282 | return folderList.contains(remoteId); | ||
283 | } | 243 | } |
284 | ); | 244 | createOrModify(bufferType, remoteId, folder); |
285 | 245 | return remoteId; | |
286 | for (const auto folderPath : folderList) { | ||
287 | createFolder(folderPath, "folder", transaction, synchronizationTransaction); | ||
288 | } | 246 | } |
289 | } | ||
290 | 247 | ||
291 | void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path) | 248 | QStringList listAvailableFolders() |
292 | { | 249 | { |
293 | Trace() << "Synchronizing mails" << path; | 250 | KPIM::Maildir dir(mMaildirPath, true); |
294 | auto time = QSharedPointer<QTime>::create(); | 251 | if (!dir.isValid()) { |
295 | time->start(); | 252 | return QStringList(); |
296 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | 253 | } |
297 | 254 | QStringList folderList; | |
298 | KPIM::Maildir maildir(path, true); | 255 | folderList << mMaildirPath; |
299 | if (!maildir.isValid()) { | 256 | folderList += listRecursive(mMaildirPath, dir); |
300 | Warning() << "Failed to sync folder " << maildir.lastError(); | 257 | return folderList; |
301 | return; | ||
302 | } | 258 | } |
303 | 259 | ||
304 | Trace() << "Importing new mail."; | 260 | void synchronizeFolders() |
305 | maildir.importNewMails(); | 261 | { |
262 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | ||
263 | QStringList folderList = listAvailableFolders(); | ||
264 | Trace() << "Found folders " << folderList; | ||
265 | |||
266 | scanForRemovals(bufferType, | ||
267 | [this, &bufferType](const std::function<void(const QByteArray &)> &callback) { | ||
268 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | ||
269 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | ||
270 | //but we currently fail to iterate over all entries in an index it seems. | ||
271 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); | ||
272 | auto mainDatabase = Sink::Storage::mainDatabase(transaction(), bufferType); | ||
273 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { | ||
274 | callback(key); | ||
275 | return true; | ||
276 | }); | ||
277 | }, | ||
278 | [&folderList](const QByteArray &remoteId) -> bool { | ||
279 | return folderList.contains(remoteId); | ||
280 | } | ||
281 | ); | ||
306 | 282 | ||
307 | auto listingPath = maildir.pathToCurrent(); | 283 | for (const auto folderPath : folderList) { |
308 | auto entryIterator = QSharedPointer<QDirIterator>::create(listingPath, QDir::Files); | 284 | createFolder(folderPath, "folder"); |
309 | Trace() << "Looking into " << listingPath; | 285 | } |
286 | } | ||
310 | 287 | ||
311 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | 288 | void synchronizeMails(const QString &path) |
289 | { | ||
290 | Trace() << "Synchronizing mails" << path; | ||
291 | auto time = QSharedPointer<QTime>::create(); | ||
292 | time->start(); | ||
293 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | ||
294 | |||
295 | KPIM::Maildir maildir(path, true); | ||
296 | if (!maildir.isValid()) { | ||
297 | Warning() << "Failed to sync folder " << maildir.lastError(); | ||
298 | return; | ||
299 | } | ||
312 | 300 | ||
313 | auto property = "folder"; | 301 | Trace() << "Importing new mail."; |
314 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | 302 | maildir.importNewMails(); |
315 | [&](const std::function<void(const QByteArray &)> &callback) { | 303 | |
316 | Index index(bufferType + ".index." + property, transaction); | 304 | auto listingPath = maildir.pathToCurrent(); |
317 | index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | 305 | auto entryIterator = QSharedPointer<QDirIterator>::create(listingPath, QDir::Files); |
318 | callback(sinkId); | 306 | Trace() << "Looking into " << listingPath; |
307 | |||
308 | const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8()); | ||
309 | |||
310 | auto property = "folder"; | ||
311 | scanForRemovals(bufferType, | ||
312 | [&](const std::function<void(const QByteArray &)> &callback) { | ||
313 | Index index(bufferType + ".index." + property, transaction()); | ||
314 | index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | ||
315 | callback(sinkId); | ||
316 | }, | ||
317 | [&](const Index::Error &error) { | ||
318 | Warning() << "Error in index: " << error.message << property; | ||
319 | }); | ||
319 | }, | 320 | }, |
320 | [&](const Index::Error &error) { | 321 | [](const QByteArray &remoteId) -> bool { |
321 | Warning() << "Error in index: " << error.message << property; | 322 | return QFile(remoteId).exists(); |
322 | }); | 323 | } |
323 | }, | 324 | ); |
324 | [](const QByteArray &remoteId) -> bool { | ||
325 | return QFile(remoteId).exists(); | ||
326 | } | ||
327 | ); | ||
328 | 325 | ||
329 | mSynchronizerQueue.startTransaction(); | 326 | int count = 0; |
330 | int count = 0; | 327 | while (entryIterator->hasNext()) { |
331 | while (entryIterator->hasNext()) { | 328 | count++; |
332 | count++; | 329 | const QString filePath = QDir::fromNativeSeparators(entryIterator->next()); |
333 | const QString filePath = QDir::fromNativeSeparators(entryIterator->next()); | 330 | const QString fileName = entryIterator->fileName(); |
334 | const QString fileName = entryIterator->fileName(); | 331 | const auto remoteId = filePath.toUtf8(); |
335 | const auto remoteId = filePath.toUtf8(); | ||
336 | 332 | ||
337 | const auto flags = maildir.readEntryFlags(fileName); | 333 | const auto flags = maildir.readEntryFlags(fileName); |
338 | const auto maildirKey = maildir.getKeyFromFile(fileName); | 334 | const auto maildirKey = maildir.getKeyFromFile(fileName); |
339 | 335 | ||
340 | Trace() << "Found a mail " << filePath << " : " << fileName; | 336 | Trace() << "Found a mail " << filePath << " : " << fileName; |
341 | 337 | ||
342 | Sink::ApplicationDomain::Mail mail; | 338 | Sink::ApplicationDomain::Mail mail; |
343 | mail.setProperty("folder", folderLocalId); | 339 | mail.setProperty("folder", folderLocalId); |
344 | //We only store the directory path + key, so we facade can add the changing bits (flags) | 340 | //We only store the directory path + key, so we facade can add the changing bits (flags) |
345 | mail.setProperty("mimeMessage", KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey); | 341 | mail.setProperty("mimeMessage", KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey); |
346 | mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen)); | 342 | mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen)); |
347 | mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged)); | 343 | mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged)); |
348 | 344 | ||
349 | createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); | 345 | createOrModify(bufferType, remoteId, mail); |
346 | } | ||
347 | commitSync(); | ||
348 | const auto elapsed = time->elapsed(); | ||
349 | Log() << "Synchronized " << count << " mails in " << listingPath << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
350 | } | 350 | } |
351 | mSynchronizerQueue.commit(); | ||
352 | const auto elapsed = time->elapsed(); | ||
353 | Log() << "Synchronized " << count << " mails in " << listingPath << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
354 | 351 | ||
355 | } | 352 | KAsync::Job<void> synchronizeWithSource() |
353 | { | ||
354 | Log() << " Synchronizing"; | ||
355 | return KAsync::start<void>([this]() { | ||
356 | { | ||
357 | synchronizeFolders(); | ||
358 | //The next sync needs the folders available | ||
359 | commit(); | ||
360 | commitSync(); | ||
361 | } | ||
362 | for (const auto &folder : listAvailableFolders()) { | ||
363 | synchronizeMails(folder); | ||
364 | //Don't let the transaction grow too much | ||
365 | commit(); | ||
366 | commitSync(); | ||
367 | } | ||
368 | Log() << "Done Synchronizing"; | ||
369 | }); | ||
370 | } | ||
356 | 371 | ||
357 | KAsync::Job<void> MaildirResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | 372 | public: |
358 | { | 373 | QString mMaildirPath; |
359 | Log() << " Synchronizing"; | 374 | }; |
360 | return KAsync::start<void>([this, &mainStore, &synchronizationStore]() { | ||
361 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
362 | { | ||
363 | auto synchronizationTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
364 | synchronizeFolders(transaction, synchronizationTransaction); | ||
365 | //The next sync needs the folders available | ||
366 | synchronizationTransaction.commit(); | ||
367 | } | ||
368 | for (const auto &folder : listAvailableFolders()) { | ||
369 | auto synchronizationTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
370 | synchronizeMails(transaction, synchronizationTransaction, folder); | ||
371 | //Don't let the transaction grow too much | ||
372 | synchronizationTransaction.commit(); | ||
373 | } | ||
374 | Log() << "Done Synchronizing"; | ||
375 | }); | ||
376 | } | ||
377 | 375 | ||
378 | KAsync::Job<QByteArray> MaildirResource::replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId) | 376 | class MaildirWriteback : public Sink::SourceWriteBack |
379 | { | 377 | { |
380 | if (operation == Sink::Operation_Creation) { | 378 | public: |
381 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | 379 | MaildirWriteback(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) : Sink::SourceWriteBack(resourceType, resourceInstanceIdentifier) |
382 | Trace() << "Mail created: " << remoteId; | 380 | { |
383 | return KAsync::start<QByteArray>([=]() -> QByteArray { | ||
384 | return remoteId.toUtf8(); | ||
385 | }); | ||
386 | } else if (operation == Sink::Operation_Removal) { | ||
387 | Trace() << "Removing a mail: " << oldRemoteId; | ||
388 | QFile::remove(oldRemoteId); | ||
389 | return KAsync::null<QByteArray>(); | ||
390 | } else if (operation == Sink::Operation_Modification) { | ||
391 | Trace() << "Modifying a mail: " << oldRemoteId; | ||
392 | 381 | ||
393 | const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | 382 | } |
394 | const auto maildirPath = KPIM::Maildir::getDirectoryFromFile(filePath); | ||
395 | KPIM::Maildir maildir(maildirPath, false); | ||
396 | 383 | ||
397 | const auto messagePathParts = filePath.split("/"); | 384 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId) |
398 | if (messagePathParts.isEmpty()) { | 385 | { |
399 | Warning() << "No message path available: " << oldRemoteId; | 386 | if (operation == Sink::Operation_Creation) { |
400 | return KAsync::error<QByteArray>(1, "No message path available."); | 387 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); |
401 | } | 388 | Trace() << "Mail created: " << remoteId; |
402 | const auto newIdentifier = messagePathParts.last(); | 389 | return KAsync::start<QByteArray>([=]() -> QByteArray { |
403 | QString identifier; | 390 | return remoteId.toUtf8(); |
404 | if (newIdentifier != KPIM::Maildir::getKeyFromFile(oldRemoteId)) { | 391 | }); |
405 | //Remove the old mime message if it changed | 392 | } else if (operation == Sink::Operation_Removal) { |
406 | Trace() << "Removing old mime message: " << oldRemoteId; | 393 | Trace() << "Removing a mail: " << oldRemoteId; |
407 | QFile(oldRemoteId).remove(); | 394 | // QFile::remove(oldRemoteId); |
408 | identifier = newIdentifier; | 395 | return KAsync::null<QByteArray>(); |
409 | } else { | 396 | } else if (operation == Sink::Operation_Modification) { |
410 | //The identifier needs to contain the flags for changeEntryFlags to work | 397 | Trace() << "Modifying a mail: " << oldRemoteId; |
411 | Q_ASSERT(!oldRemoteId.split('/').isEmpty()); | 398 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); |
412 | identifier = oldRemoteId.split('/').last(); | 399 | return KAsync::start<QByteArray>([=]() -> QByteArray { |
400 | return remoteId.toUtf8(); | ||
401 | }); | ||
413 | } | 402 | } |
403 | return KAsync::null<QByteArray>(); | ||
404 | } | ||
414 | 405 | ||
415 | //get flags from | 406 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId) |
416 | KPIM::Maildir::Flags flags; | 407 | { |
417 | if (!mail.getUnread()) { | 408 | if (operation == Sink::Operation_Creation) { |
418 | flags |= KPIM::Maildir::Seen; | 409 | auto folderName = folder.getName(); |
419 | } | 410 | //FIXME handle non toplevel folders |
420 | if (mail.getImportant()) { | 411 | auto path = mMaildirPath + "/" + folderName; |
421 | flags |= KPIM::Maildir::Flagged; | 412 | Trace() << "Creating a new folder: " << path; |
413 | KPIM::Maildir maildir(path, false); | ||
414 | maildir.create(); | ||
415 | return KAsync::start<QByteArray>([=]() -> QByteArray { | ||
416 | return path.toUtf8(); | ||
417 | }); | ||
418 | } else if (operation == Sink::Operation_Removal) { | ||
419 | const auto path = oldRemoteId; | ||
420 | Trace() << "Removing a folder: " << path; | ||
421 | KPIM::Maildir maildir(path, false); | ||
422 | maildir.remove(); | ||
423 | return KAsync::null<QByteArray>(); | ||
424 | } else if (operation == Sink::Operation_Modification) { | ||
425 | Warning() << "Folder modifications are not implemented"; | ||
426 | return KAsync::start<QByteArray>([=]() -> QByteArray { | ||
427 | return oldRemoteId; | ||
428 | }); | ||
422 | } | 429 | } |
423 | 430 | return KAsync::null<QByteArray>(); | |
424 | const auto newRemoteId = maildir.changeEntryFlags(identifier, flags); | ||
425 | Warning() << "New remote id: " << QString(maildirPath + "/cur/" + newRemoteId); | ||
426 | return KAsync::start<QByteArray>([=]() -> QByteArray { | ||
427 | return QString(maildirPath + "/cur/" + newRemoteId).toUtf8(); | ||
428 | }); | ||
429 | } | 431 | } |
430 | return KAsync::null<QByteArray>(); | ||
431 | } | ||
432 | 432 | ||
433 | KAsync::Job<QByteArray> MaildirResource::replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId) | 433 | public: |
434 | QString mMaildirPath; | ||
435 | }; | ||
436 | |||
437 | |||
438 | MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | ||
439 | : Sink::GenericResource(PLUGIN_NAME, instanceIdentifier, pipeline) | ||
434 | { | 440 | { |
435 | if (operation == Sink::Operation_Creation) { | 441 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); |
436 | auto folderName = folder.getName(); | 442 | mMaildirPath = QDir::cleanPath(QDir::fromNativeSeparators(config.value("path").toString())); |
437 | //FIXME handle non toplevel folders | 443 | //Chop a trailing slash if necessary |
438 | auto path = mMaildirPath + "/" + folderName; | 444 | if (mMaildirPath.endsWith("/")) { |
439 | Trace() << "Creating a new folder: " << path; | 445 | mMaildirPath.chop(1); |
440 | KPIM::Maildir maildir(path, false); | ||
441 | maildir.create(); | ||
442 | return KAsync::start<QByteArray>([=]() -> QByteArray { | ||
443 | return path.toUtf8(); | ||
444 | }); | ||
445 | } else if (operation == Sink::Operation_Removal) { | ||
446 | const auto path = oldRemoteId; | ||
447 | Trace() << "Removing a folder: " << path; | ||
448 | KPIM::Maildir maildir(path, false); | ||
449 | maildir.remove(); | ||
450 | return KAsync::null<QByteArray>(); | ||
451 | } else if (operation == Sink::Operation_Modification) { | ||
452 | Warning() << "Folder modifications are not implemented"; | ||
453 | return KAsync::start<QByteArray>([=]() -> QByteArray { | ||
454 | return oldRemoteId; | ||
455 | }); | ||
456 | } | 446 | } |
457 | return KAsync::null<QByteArray>(); | 447 | |
448 | auto synchronizer = QSharedPointer<MaildirSynchronizer>::create(PLUGIN_NAME, instanceIdentifier); | ||
449 | synchronizer->mMaildirPath = mMaildirPath; | ||
450 | setupSynchronizer(synchronizer); | ||
451 | auto changereplay = QSharedPointer<MaildirWriteback>::create(PLUGIN_NAME, instanceIdentifier); | ||
452 | changereplay->mMaildirPath = mMaildirPath; | ||
453 | setupChangereplay(changereplay); | ||
454 | |||
455 | auto folderUpdater = new FolderUpdater(QByteArray()); | ||
456 | setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << folderUpdater << new DefaultIndexUpdater<Sink::ApplicationDomain::Mail>); | ||
457 | auto folderPreprocessor = new FolderPreprocessor; | ||
458 | setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << folderPreprocessor << new DefaultIndexUpdater<Sink::ApplicationDomain::Folder>); | ||
459 | |||
460 | KPIM::Maildir dir(mMaildirPath, true); | ||
461 | mDraftsFolder = dir.addSubFolder("drafts"); | ||
462 | Trace() << "Started maildir resource for maildir: " << mMaildirPath; | ||
463 | |||
464 | auto remoteId = synchronizer->createFolder(mDraftsFolder, "folder"); | ||
465 | auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); | ||
466 | synchronizer->commit(); | ||
467 | synchronizer->commitSync(); | ||
468 | |||
469 | folderUpdater->mDraftsFolder = draftsFolderLocalId; | ||
470 | folderUpdater->mResourceInstanceIdentifier = mResourceInstanceIdentifier; | ||
471 | folderUpdater->mMaildirPath = mMaildirPath; | ||
472 | folderPreprocessor->mMaildirPath = mMaildirPath; | ||
458 | } | 473 | } |
459 | 474 | ||
460 | void MaildirResource::removeFromDisk(const QByteArray &instanceIdentifier) | 475 | void MaildirResource::removeFromDisk(const QByteArray &instanceIdentifier) |
@@ -471,14 +486,13 @@ KAsync::Job<void> MaildirResource::inspect(int inspectionType, const QByteArray | |||
471 | auto mainStore = QSharedPointer<Sink::Storage>::create(Sink::storageLocation(), mResourceInstanceIdentifier, Sink::Storage::ReadOnly); | 486 | auto mainStore = QSharedPointer<Sink::Storage>::create(Sink::storageLocation(), mResourceInstanceIdentifier, Sink::Storage::ReadOnly); |
472 | auto transaction = mainStore->createTransaction(Sink::Storage::ReadOnly); | 487 | auto transaction = mainStore->createTransaction(Sink::Storage::ReadOnly); |
473 | 488 | ||
489 | auto entityStore = QSharedPointer<EntityStore>::create(mResourceType, mResourceInstanceIdentifier, transaction); | ||
490 | auto syncStore = QSharedPointer<RemoteIdMap>::create(synchronizationTransaction); | ||
491 | |||
474 | Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; | 492 | Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; |
475 | 493 | ||
476 | if (domainType == ENTITY_TYPE_MAIL) { | 494 | if (domainType == ENTITY_TYPE_MAIL) { |
477 | auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_MAIL); | 495 | auto mail = entityStore->read<Sink::ApplicationDomain::Mail>(entityId); |
478 | auto bufferAdaptor = getLatest(mainDatabase, entityId, *mMailAdaptorFactory); | ||
479 | Q_ASSERT(bufferAdaptor); | ||
480 | |||
481 | const Sink::ApplicationDomain::Mail mail(mResourceInstanceIdentifier, entityId, 0, bufferAdaptor); | ||
482 | const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | 496 | const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); |
483 | 497 | ||
484 | if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { | 498 | if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { |
@@ -510,13 +524,11 @@ KAsync::Job<void> MaildirResource::inspect(int inspectionType, const QByteArray | |||
510 | } | 524 | } |
511 | } | 525 | } |
512 | if (domainType == ENTITY_TYPE_FOLDER) { | 526 | if (domainType == ENTITY_TYPE_FOLDER) { |
513 | const auto remoteId = resolveLocalId(ENTITY_TYPE_FOLDER, entityId, synchronizationTransaction); | 527 | const auto remoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, entityId); |
514 | auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | 528 | auto folder = entityStore->read<Sink::ApplicationDomain::Folder>(entityId); |
515 | auto bufferAdaptor = getLatest(mainDatabase, entityId, *mMailAdaptorFactory); | ||
516 | Q_ASSERT(bufferAdaptor); | ||
517 | 529 | ||
518 | const Sink::ApplicationDomain::Folder folder(mResourceInstanceIdentifier, entityId, 0, bufferAdaptor); | ||
519 | if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { | 530 | if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { |
531 | Trace() << "Inspecting cache integrity" << remoteId; | ||
520 | if (!QDir(remoteId).exists()) { | 532 | if (!QDir(remoteId).exists()) { |
521 | return KAsync::error<void>(1, "The directory is not existing: " + remoteId); | 533 | return KAsync::error<void>(1, "The directory is not existing: " + remoteId); |
522 | } | 534 | } |
@@ -533,21 +545,27 @@ KAsync::Job<void> MaildirResource::inspect(int inspectionType, const QByteArray | |||
533 | QDir dir(remoteId + "/cur"); | 545 | QDir dir(remoteId + "/cur"); |
534 | const QFileInfoList list = dir.entryInfoList(QDir::Files); | 546 | const QFileInfoList list = dir.entryInfoList(QDir::Files); |
535 | if (list.size() != expectedCount) { | 547 | if (list.size() != expectedCount) { |
548 | for (const auto &fileInfo : list) { | ||
549 | Warning() << "Found in cache: " << fileInfo.fileName(); | ||
550 | } | ||
536 | return KAsync::error<void>(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount)); | 551 | return KAsync::error<void>(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount)); |
537 | } | 552 | } |
538 | if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { | 553 | if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { |
539 | if (!remoteId.endsWith(folder.getName().toUtf8())) { | 554 | if (!remoteId.endsWith(folder.getName().toUtf8())) { |
540 | return KAsync::error<void>(1, "Wrong folder name: " + remoteId); | 555 | return KAsync::error<void>(1, "Wrong folder name: " + remoteId); |
541 | } | 556 | } |
557 | //TODO we shouldn't use the remoteId here to figure out the path, it could be gone/changed already | ||
542 | if (QDir(remoteId).exists() != expectedValue.toBool()) { | 558 | if (QDir(remoteId).exists() != expectedValue.toBool()) { |
543 | return KAsync::error<void>(1, "Wrong folder existence: " + remoteId); | 559 | return KAsync::error<void>(1, "Wrong folder existence: " + remoteId); |
544 | } | 560 | } |
545 | } | 561 | } |
546 | } | 562 | } |
563 | |||
547 | } | 564 | } |
548 | return KAsync::null<void>(); | 565 | return KAsync::null<void>(); |
549 | } | 566 | } |
550 | 567 | ||
568 | |||
551 | MaildirResourceFactory::MaildirResourceFactory(QObject *parent) | 569 | MaildirResourceFactory::MaildirResourceFactory(QObject *parent) |
552 | : Sink::ResourceFactory(parent) | 570 | : Sink::ResourceFactory(parent) |
553 | { | 571 | { |
@@ -565,3 +583,9 @@ void MaildirResourceFactory::registerFacades(Sink::FacadeFactory &factory) | |||
565 | factory.registerFacade<Sink::ApplicationDomain::Folder, MaildirResourceFolderFacade>(PLUGIN_NAME); | 583 | factory.registerFacade<Sink::ApplicationDomain::Folder, MaildirResourceFolderFacade>(PLUGIN_NAME); |
566 | } | 584 | } |
567 | 585 | ||
586 | void MaildirResourceFactory::registerAdaptorFactories(Sink::AdaptorFactoryRegistry ®istry) | ||
587 | { | ||
588 | registry.registerFactory<Sink::ApplicationDomain::Mail, MaildirMailAdaptorFactory>(PLUGIN_NAME); | ||
589 | registry.registerFactory<Sink::ApplicationDomain::Folder, MaildirFolderAdaptorFactory>(PLUGIN_NAME); | ||
590 | } | ||
591 | |||