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 &registry)
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 }