File indexing completed on 2024-06-16 05:01:08
0001 /* 0002 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0018 */ 0019 0020 #include "maildirresource.h" 0021 0022 #include "facade.h" 0023 #include "resourceconfig.h" 0024 #include "index.h" 0025 #include "log.h" 0026 #include "definitions.h" 0027 #include "libmaildir/maildir.h" 0028 #include "inspection.h" 0029 #include "synchronizer.h" 0030 #include "inspector.h" 0031 0032 #include "facadefactory.h" 0033 #include "adaptorfactoryregistry.h" 0034 0035 #include "mailpreprocessor.h" 0036 #include "specialpurposepreprocessor.h" 0037 0038 #include <QDir> 0039 #include <QDirIterator> 0040 #include <KMime/KMimeMessage> 0041 0042 //This is the resources entity type, and not the domain type 0043 #define ENTITY_TYPE_MAIL "mail" 0044 #define ENTITY_TYPE_FOLDER "folder" 0045 0046 using namespace Sink; 0047 0048 static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) 0049 { 0050 auto parts = mimeMessagePath.split('/'); 0051 const auto key = parts.takeLast(); 0052 const auto path = parts.join("/") + "/cur/"; 0053 0054 QDir dir(path); 0055 const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); 0056 if (list.size() != 1) { 0057 SinkWarning() << "Failed to find message. Property value:" << mimeMessagePath << "Assembled path: " << path; 0058 return QString(); 0059 } 0060 return list.first().filePath(); 0061 } 0062 0063 class MaildirMailPropertyExtractor : public MailPropertyExtractor 0064 { 0065 void update(Sink::ApplicationDomain::Mail &mail) 0066 { 0067 QFile file{::getFilePathFromMimeMessagePath(mail.getMimeMessage())}; 0068 if (file.open(QIODevice::ReadOnly)) { 0069 updatedIndexedProperties(mail, file.readAll()); 0070 } else { 0071 SinkWarning() << "Failed to open file message " << mail.getMimeMessage(); 0072 } 0073 } 0074 0075 protected: 0076 void newEntity(Sink::ApplicationDomain::Mail &mail) Q_DECL_OVERRIDE 0077 { 0078 update(mail); 0079 } 0080 0081 void modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail) Q_DECL_OVERRIDE 0082 { 0083 update(newMail); 0084 } 0085 }; 0086 0087 class MaildirMimeMessageMover : public Sink::Preprocessor 0088 { 0089 public: 0090 MaildirMimeMessageMover(const QByteArray &resourceInstanceIdentifier, const QString &maildirPath) : mResourceInstanceIdentifier(resourceInstanceIdentifier), mMaildirPath(maildirPath) {} 0091 0092 QString getPath(const QByteArray &folderIdentifier) 0093 { 0094 if (folderIdentifier.isEmpty()) { 0095 return mMaildirPath; 0096 } 0097 QString folderPath; 0098 const auto folder = entityStore().readLatest<ApplicationDomain::Folder>(folderIdentifier); 0099 if (mMaildirPath.endsWith(folder.getName())) { 0100 folderPath = mMaildirPath; 0101 } else { 0102 auto folderName = folder.getName(); 0103 //FIXME handle non toplevel folders 0104 folderPath = mMaildirPath + "/" + folderName; 0105 } 0106 return folderPath; 0107 } 0108 0109 QString storeMessage(const QByteArray &data, const QByteArray &folder) 0110 { 0111 const auto path = getPath(folder); 0112 KPIM::Maildir maildir(path, false); 0113 if (!maildir.isValid(true)) { 0114 SinkWarning() << "Maildir is not existing: " << path; 0115 } 0116 SinkTrace() << "Storing message: " << data; 0117 auto identifier = maildir.addEntry(data); 0118 return path + "/" + identifier; 0119 } 0120 0121 QString moveMessage(const QString &oldPath, const QByteArray &folder) 0122 { 0123 if (oldPath.startsWith(Sink::temporaryFileLocation())) { 0124 const auto path = getPath(folder); 0125 KPIM::Maildir maildir(path, false); 0126 if (!maildir.isValid(true)) { 0127 SinkWarning() << "Maildir is not existing: " << path; 0128 } 0129 auto identifier = maildir.addEntryFromPath(oldPath); 0130 return path + "/" + identifier; 0131 } else { 0132 //Handle moves 0133 const auto path = getPath(folder); 0134 KPIM::Maildir maildir(path, false); 0135 if (!maildir.isValid(true)) { 0136 SinkWarning() << "Maildir is not existing: " << path; 0137 } 0138 auto oldIdentifier = KPIM::Maildir::getKeyFromFile(oldPath); 0139 auto pathParts = oldPath.split('/'); 0140 pathParts.takeLast(); 0141 auto oldDirectory = pathParts.join('/'); 0142 if (oldDirectory == path) { 0143 return oldPath; 0144 } 0145 KPIM::Maildir oldMaildir(oldDirectory, false); 0146 if (!oldMaildir.isValid(false)) { 0147 SinkWarning() << "Maildir is not existing: " << path; 0148 } 0149 auto identifier = oldMaildir.moveEntryTo(oldIdentifier, maildir); 0150 return path + "/" + identifier; 0151 } 0152 } 0153 0154 bool isPath(const QByteArray &data) 0155 { 0156 return data.startsWith('/'); 0157 } 0158 0159 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE 0160 { 0161 auto mail = newEntity.cast<ApplicationDomain::Mail>(); 0162 const auto mimeMessage = mail.getMimeMessage(); 0163 if (!mimeMessage.isNull()) { 0164 if (isPath(mimeMessage)) { 0165 mail.setMimeMessage(moveMessage(mimeMessage, mail.getFolder()).toUtf8()); 0166 } else { 0167 mail.setMimeMessage(storeMessage(mimeMessage, mail.getFolder()).toUtf8()); 0168 } 0169 } 0170 } 0171 0172 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE 0173 { 0174 auto newMail = newEntity.cast<ApplicationDomain::Mail>(); 0175 const ApplicationDomain::Mail oldMail{oldEntity}; 0176 const auto newFolder = newMail.getFolder(); 0177 const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder(); 0178 if (!newMail.getMimeMessage().isNull() || folderChanged) { 0179 const auto data = newMail.getMimeMessage(); 0180 if (isPath(data)) { 0181 auto newPath = moveMessage(data, newMail.getFolder()); 0182 if (newPath != oldMail.getMimeMessage()) { 0183 newMail.setMimeMessage(newPath.toUtf8()); 0184 //Remove the olde mime message if there is a new one 0185 QFile::remove(getFilePathFromMimeMessagePath(oldMail.getMimeMessage())); 0186 } 0187 } else { 0188 newMail.setMimeMessage(storeMessage(data, newMail.getFolder()).toUtf8()); 0189 //Remove the olde mime message if there is a new one 0190 QFile::remove(getFilePathFromMimeMessagePath(oldMail.getMimeMessage())); 0191 } 0192 } 0193 0194 auto mimeMessagePath = newMail.getMimeMessage(); 0195 const auto maildirPath = getPath(newMail.getFolder()); 0196 KPIM::Maildir maildir(maildirPath, false); 0197 QString identifier = KPIM::Maildir::getKeyFromFile(getFilePathFromMimeMessagePath(mimeMessagePath)); 0198 0199 //get flags from 0200 KPIM::Maildir::Flags flags; 0201 if (!newMail.getUnread()) { 0202 flags |= KPIM::Maildir::Seen; 0203 } 0204 if (newMail.getImportant()) { 0205 flags |= KPIM::Maildir::Flagged; 0206 } 0207 0208 maildir.changeEntryFlags(identifier, flags); 0209 } 0210 0211 void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE 0212 { 0213 const ApplicationDomain::Mail oldMail{oldEntity}; 0214 const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessage()); 0215 QFile::remove(filePath); 0216 } 0217 QByteArray mResourceInstanceIdentifier; 0218 QString mMaildirPath; 0219 }; 0220 0221 class FolderPreprocessor : public Sink::Preprocessor 0222 { 0223 public: 0224 FolderPreprocessor(const QString maildirPath) : mMaildirPath(maildirPath) {} 0225 0226 void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE 0227 { 0228 auto folderName = Sink::ApplicationDomain::Folder{newEntity}.getName(); 0229 const auto path = mMaildirPath + "/" + folderName; 0230 KPIM::Maildir maildir(path, false); 0231 maildir.create(); 0232 } 0233 0234 void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE 0235 { 0236 } 0237 0238 void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE 0239 { 0240 } 0241 QString mMaildirPath; 0242 }; 0243 0244 class FolderCleanupPreprocessor : public Sink::Preprocessor 0245 { 0246 public: 0247 void deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE 0248 { 0249 //Remove all mails of a folder when removing the folder. 0250 const auto revision = entityStore().maxRevision(); 0251 entityStore().indexLookup<ApplicationDomain::Mail, ApplicationDomain::Mail::Folder>(oldEntity.identifier(), [&] (const QByteArray &identifier) { 0252 deleteEntity(ApplicationDomain::ApplicationDomainType{{}, identifier, revision, {}}, ApplicationDomain::getTypeName<ApplicationDomain::Mail>(), false); 0253 }); 0254 } 0255 }; 0256 0257 0258 class MaildirSynchronizer : public Sink::Synchronizer { 0259 public: 0260 MaildirSynchronizer(const Sink::ResourceContext &resourceContext) 0261 : Sink::Synchronizer(resourceContext) 0262 { 0263 setSecret("dummy"); 0264 } 0265 0266 static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) 0267 { 0268 QStringList list; 0269 foreach (const QString &sub, dir.subFolderList()) { 0270 const KPIM::Maildir md = dir.subFolder(sub); 0271 if (!md.isValid()) { 0272 continue; 0273 } 0274 QString path = root + "/" + sub; 0275 list << path; 0276 list += listRecursive(path, md ); 0277 } 0278 return list; 0279 } 0280 0281 QByteArray createFolder(const QString &folderPath, const QByteArray &icon, const QByteArrayList &specialpurpose = QByteArrayList()) 0282 { 0283 auto remoteId = folderPath.toUtf8(); 0284 auto bufferType = ENTITY_TYPE_FOLDER; 0285 KPIM::Maildir md(folderPath, folderPath == mMaildirPath); 0286 Sink::ApplicationDomain::Folder folder; 0287 folder.setName(md.name()); 0288 folder.setIcon(icon); 0289 if (!specialpurpose.isEmpty()) { 0290 folder.setSpecialPurpose(specialpurpose); 0291 } 0292 0293 if (!md.isRoot()) { 0294 folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8())); 0295 } 0296 createOrModify(bufferType, remoteId, folder); 0297 return remoteId; 0298 } 0299 0300 QStringList listAvailableFolders() 0301 { 0302 KPIM::Maildir dir(mMaildirPath, true); 0303 if (!dir.isValid()) { 0304 return QStringList(); 0305 } 0306 QStringList folderList; 0307 folderList << mMaildirPath; 0308 folderList += listRecursive(mMaildirPath, dir); 0309 return folderList; 0310 } 0311 0312 void synchronizeFolders() 0313 { 0314 const QByteArray bufferType = ENTITY_TYPE_FOLDER; 0315 QStringList folderList = listAvailableFolders(); 0316 SinkTrace() << "Found folders " << folderList; 0317 scanForRemovals(bufferType, 0318 [&folderList](const QByteArray &remoteId) -> bool { 0319 return folderList.contains(remoteId); 0320 } 0321 ); 0322 0323 for (const auto &folderPath : folderList) { 0324 createFolder(folderPath, "folder"); 0325 } 0326 } 0327 0328 void synchronizeMails(const QString &path) 0329 { 0330 SinkTrace() << "Synchronizing mails" << path; 0331 auto time = QSharedPointer<QTime>::create(); 0332 time->start(); 0333 const QByteArray bufferType = ENTITY_TYPE_MAIL; 0334 0335 KPIM::Maildir maildir(path, true); 0336 if (!maildir.isValid()) { 0337 SinkWarning() << "Failed to sync folder."; 0338 return; 0339 } 0340 0341 SinkTrace() << "Importing new mail."; 0342 maildir.importNewMails(); 0343 0344 auto listingPath = maildir.pathToCurrent(); 0345 auto entryIterator = QSharedPointer<QDirIterator>::create(listingPath, QDir::Files); 0346 SinkTrace() << "Looking into " << listingPath; 0347 0348 const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8()); 0349 0350 scanForRemovals(bufferType, 0351 [&](const std::function<void(const QByteArray &)> &callback) { 0352 store().indexLookup<ApplicationDomain::Mail, ApplicationDomain::Mail::Folder>(folderLocalId, callback); 0353 }, 0354 [](const QByteArray &remoteId) -> bool { 0355 return QFile(remoteId).exists(); 0356 } 0357 ); 0358 0359 int count = 0; 0360 while (entryIterator->hasNext()) { 0361 count++; 0362 const QString filePath = QDir::fromNativeSeparators(entryIterator->next()); 0363 const QString fileName = entryIterator->fileName(); 0364 const auto remoteId = filePath.toUtf8(); 0365 0366 const auto flags = maildir.readEntryFlags(fileName); 0367 const auto maildirKey = maildir.getKeyFromFile(fileName); 0368 0369 SinkTrace() << "Found a mail " << filePath << " : " << fileName; 0370 0371 Sink::ApplicationDomain::Mail mail; 0372 mail.setFolder(folderLocalId); 0373 //We only store the directory path + key, so we facade can add the changing bits (flags) 0374 auto path = KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey; 0375 mail.setMimeMessage(path.toUtf8()); 0376 mail.setUnread(!flags.testFlag(KPIM::Maildir::Seen)); 0377 mail.setImportant(flags.testFlag(KPIM::Maildir::Flagged)); 0378 mail.setExtractedFullPayloadAvailable(true); 0379 0380 createOrModify(bufferType, remoteId, mail); 0381 } 0382 const auto elapsed = time->elapsed(); 0383 SinkLog() << "Synchronized " << count << " mails in " << listingPath << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; 0384 } 0385 0386 QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query) Q_DECL_OVERRIDE 0387 { 0388 QList<Synchronizer::SyncRequest> list; 0389 if (!query.type().isEmpty()) { 0390 //We want to synchronize something specific 0391 list << Synchronizer::SyncRequest{query}; 0392 } else { 0393 //We want to synchronize everything 0394 list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Folder>())}; 0395 //FIXME we can't process the second synchronization before the pipeline of the first one is processed, otherwise we can't execute a query on the local data. 0396 /* list << Synchronizer::SyncRequest{Flush}; */ 0397 list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Mail>())}; 0398 } 0399 return list; 0400 } 0401 0402 KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE 0403 { 0404 auto job = KAsync::start([this] { 0405 KPIM::Maildir maildir(mMaildirPath, true); 0406 if (!maildir.isValid(false)) { 0407 return KAsync::error(ApplicationDomain::ConfigurationError, "Maildir path doesn't point to a valid maildir: " + mMaildirPath); 0408 } 0409 return KAsync::null(); 0410 }); 0411 0412 if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) { 0413 job = job.then([this] { 0414 synchronizeFolders(); 0415 }); 0416 } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Mail>()) { 0417 job = job.then([this, query] { 0418 QStringList folders; 0419 if (query.hasFilter<ApplicationDomain::Mail::Folder>()) { 0420 auto folderFilter = query.getFilter<ApplicationDomain::Mail::Folder>(); 0421 auto localIds = resolveFilter(folderFilter); 0422 auto folderRemoteIds = syncStore().resolveLocalIds(ApplicationDomain::getTypeName<ApplicationDomain::Folder>(), localIds); 0423 for (const auto &r : folderRemoteIds) { 0424 folders << r; 0425 } 0426 } else { 0427 folders = listAvailableFolders(); 0428 } 0429 for (const auto &folder : folders) { 0430 synchronizeMails(folder); 0431 //Don't let the transaction grow too much 0432 commit(); 0433 } 0434 }); 0435 } 0436 return job; 0437 } 0438 0439 KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 0440 { 0441 if (operation == Sink::Operation_Creation) { 0442 const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessage()); 0443 SinkTrace() << "Mail created: " << remoteId; 0444 return KAsync::value(remoteId.toUtf8()); 0445 } else if (operation == Sink::Operation_Removal) { 0446 SinkTrace() << "Removing a mail: " << oldRemoteId; 0447 return KAsync::null<QByteArray>(); 0448 } else if (operation == Sink::Operation_Modification) { 0449 SinkTrace() << "Modifying a mail: " << oldRemoteId; 0450 const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessage()); 0451 return KAsync::value(remoteId.toUtf8()); 0452 } 0453 return KAsync::null<QByteArray>(); 0454 } 0455 0456 KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE 0457 { 0458 if (operation == Sink::Operation_Creation) { 0459 auto folderName = folder.getName(); 0460 //FIXME handle non toplevel folders 0461 auto path = mMaildirPath + "/" + folderName; 0462 SinkTrace() << "Creating a new folder: " << path; 0463 KPIM::Maildir maildir(path, false); 0464 maildir.create(); 0465 return KAsync::value(path.toUtf8()); 0466 } else if (operation == Sink::Operation_Removal) { 0467 const auto path = oldRemoteId; 0468 SinkTrace() << "Removing a folder: " << path; 0469 KPIM::Maildir maildir(path, false); 0470 maildir.remove(); 0471 return KAsync::null<QByteArray>(); 0472 } else if (operation == Sink::Operation_Modification) { 0473 SinkWarning() << "Folder modifications are not implemented"; 0474 return KAsync::value(oldRemoteId); 0475 } 0476 return KAsync::null<QByteArray>(); 0477 } 0478 0479 public: 0480 QString mMaildirPath; 0481 }; 0482 0483 class MaildirInspector : public Sink::Inspector { 0484 public: 0485 MaildirInspector(const Sink::ResourceContext &resourceContext) 0486 : Sink::Inspector(resourceContext) 0487 { 0488 0489 } 0490 protected: 0491 0492 KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE { 0493 auto synchronizationStore = QSharedPointer<Sink::Storage::DataStore>::create(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::ReadOnly); 0494 auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::DataStore::ReadOnly); 0495 0496 auto mainStore = QSharedPointer<Sink::Storage::DataStore>::create(Sink::storageLocation(), mResourceContext.instanceId(), Sink::Storage::DataStore::ReadOnly); 0497 auto transaction = mainStore->createTransaction(Sink::Storage::DataStore::ReadOnly); 0498 0499 Sink::Storage::EntityStore entityStore(mResourceContext, {"maildirresource"}); 0500 auto syncStore = QSharedPointer<SynchronizerStore>::create(synchronizationTransaction); 0501 0502 SinkTrace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; 0503 0504 if (domainType == ENTITY_TYPE_MAIL) { 0505 auto mail = entityStore.readLatest<Sink::ApplicationDomain::Mail>(entityId); 0506 const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessage()); 0507 0508 if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { 0509 if (property == "unread") { 0510 const auto flags = KPIM::Maildir::readEntryFlags(filePath.split('/').last()); 0511 if (expectedValue.toBool() && (flags & KPIM::Maildir::Seen)) { 0512 return KAsync::error<void>(1, "Expected unread but couldn't find it."); 0513 } 0514 if (!expectedValue.toBool() && !(flags & KPIM::Maildir::Seen)) { 0515 return KAsync::error<void>(1, "Expected read but couldn't find it."); 0516 } 0517 return KAsync::null<void>(); 0518 } 0519 if (property == "subject") { 0520 auto msg = KMime::Message::Ptr(new KMime::Message); 0521 msg->setHead(KMime::CRLFtoLF(KPIM::Maildir::readEntryHeadersFromFile(filePath))); 0522 msg->parse(); 0523 0524 if (msg->subject(true)->asUnicodeString() != expectedValue.toString()) { 0525 return KAsync::error<void>(1, "Subject not as expected: " + msg->subject(true)->asUnicodeString()); 0526 } 0527 return KAsync::null<void>(); 0528 } 0529 } 0530 if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { 0531 if (QFileInfo(filePath).exists() != expectedValue.toBool()) { 0532 return KAsync::error<void>(1, "Wrong file existence: " + filePath); 0533 } 0534 } 0535 } 0536 if (domainType == ENTITY_TYPE_FOLDER) { 0537 const auto remoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, entityId); 0538 auto folder = entityStore.readLatest<Sink::ApplicationDomain::Folder>(entityId); 0539 0540 if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { 0541 SinkTrace() << "Inspecting cache integrity" << remoteId; 0542 if (!QDir(remoteId).exists()) { 0543 return KAsync::error<void>(1, "The directory is not existing: " + remoteId); 0544 } 0545 0546 int expectedCount = 0; 0547 Index index("mail.index.folder", transaction); 0548 index.lookup(entityId, [&](const QByteArray &sinkId) { 0549 expectedCount++; 0550 return true; 0551 }, 0552 [&](const Index::Error &error) { 0553 SinkWarning() << "Error in index: " << error.message << property; 0554 }); 0555 0556 QDir dir(remoteId + "/cur"); 0557 const QFileInfoList list = dir.entryInfoList(QDir::Files); 0558 if (list.size() != expectedCount) { 0559 for (const auto &fileInfo : list) { 0560 SinkWarning() << "Found in cache: " << fileInfo.fileName(); 0561 } 0562 return KAsync::error<void>(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount)); 0563 } 0564 } 0565 if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { 0566 if (!remoteId.endsWith(folder.getName().toUtf8())) { 0567 return KAsync::error<void>(1, "Wrong folder name: " + remoteId); 0568 } 0569 //TODO we shouldn't use the remoteId here to figure out the path, it could be gone/changed already 0570 if (QDir(remoteId).exists() != expectedValue.toBool()) { 0571 return KAsync::error<void>(1, "Wrong folder existence: " + remoteId); 0572 } 0573 } 0574 0575 } 0576 return KAsync::null<void>(); 0577 } 0578 }; 0579 0580 0581 MaildirResource::MaildirResource(const Sink::ResourceContext &resourceContext) 0582 : Sink::GenericResource(resourceContext) 0583 { 0584 auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); 0585 mMaildirPath = QDir::cleanPath(QDir::fromNativeSeparators(config.value("path").toString())); 0586 //Chop a trailing slash if necessary 0587 if (mMaildirPath.endsWith("/")) { 0588 mMaildirPath.chop(1); 0589 } 0590 0591 auto synchronizer = QSharedPointer<MaildirSynchronizer>::create(resourceContext); 0592 synchronizer->mMaildirPath = mMaildirPath; 0593 setupSynchronizer(synchronizer); 0594 setupInspector(QSharedPointer<MaildirInspector>::create(resourceContext)); 0595 0596 setupPreprocessors(ENTITY_TYPE_MAIL, {new SpecialPurposeProcessor, new MaildirMimeMessageMover(resourceContext.instanceId(), mMaildirPath), new MaildirMailPropertyExtractor}); 0597 setupPreprocessors(ENTITY_TYPE_FOLDER, {new FolderPreprocessor(mMaildirPath), new FolderCleanupPreprocessor}); 0598 0599 KPIM::Maildir dir(mMaildirPath, true); 0600 if (dir.isValid(false)) { 0601 { 0602 auto draftsFolder = dir.addSubFolder("Drafts"); 0603 auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts"); 0604 auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); 0605 } 0606 { 0607 auto trashFolder = dir.addSubFolder("Trash"); 0608 auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash"); 0609 auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); 0610 } 0611 synchronizer->commit(); 0612 } 0613 SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath; 0614 } 0615 0616 0617 MaildirResourceFactory::MaildirResourceFactory(QObject *parent) 0618 : Sink::ResourceFactory(parent, 0619 {Sink::ApplicationDomain::ResourceCapabilities::Mail::mail, 0620 Sink::ApplicationDomain::ResourceCapabilities::Mail::folder, 0621 Sink::ApplicationDomain::ResourceCapabilities::Mail::storage, 0622 Sink::ApplicationDomain::ResourceCapabilities::Mail::drafts, 0623 "-folder.rename", 0624 Sink::ApplicationDomain::ResourceCapabilities::Mail::trash, 0625 Sink::ApplicationDomain::ResourceCapabilities::Mail::sent} 0626 ) 0627 { 0628 } 0629 0630 Sink::Resource *MaildirResourceFactory::createResource(const ResourceContext &context) 0631 { 0632 return new MaildirResource(context); 0633 } 0634 0635 void MaildirResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) 0636 { 0637 factory.registerFacade<Sink::ApplicationDomain::Mail, MaildirResourceMailFacade>(name); 0638 factory.registerFacade<Sink::ApplicationDomain::Folder, MaildirResourceFolderFacade>(name); 0639 } 0640 0641 void MaildirResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) 0642 { 0643 registry.registerFactory<ApplicationDomain::Mail, DefaultAdaptorFactory<ApplicationDomain::Mail>>(name); 0644 registry.registerFactory<ApplicationDomain::Folder, DefaultAdaptorFactory<ApplicationDomain::Folder>>(name); 0645 } 0646 0647 void MaildirResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) 0648 { 0649 MaildirResource::removeFromDisk(instanceIdentifier); 0650 }