From 62c7e132e2c546566c37949f24818aceb5d57c3d Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Wed, 11 May 2016 09:59:44 +0200 Subject: Handle modifications in maildir --- examples/maildirresource/libmaildir/maildir.cpp | 2 +- examples/maildirresource/maildirresource.cpp | 113 +++++++++++++++--------- tests/maildirresourcetest.cpp | 58 ++++++++++++ 3 files changed, 129 insertions(+), 44 deletions(-) diff --git a/examples/maildirresource/libmaildir/maildir.cpp b/examples/maildirresource/libmaildir/maildir.cpp index 1018b4a..e06219a 100644 --- a/examples/maildirresource/libmaildir/maildir.cpp +++ b/examples/maildirresource/libmaildir/maildir.cpp @@ -749,7 +749,7 @@ bool Maildir::removeEntry(const QString& key) } // KeyCache *keyCache = KeyCache::self(); // keyCache->removeKey(d->path, key); - qWarning() << "Real key"; + qWarning() << "Real key" << realKey; QFile file(realKey); if (!file.remove()) { qWarning() << file.errorString() << file.error(); diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 72610a1..3b0c427 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp @@ -65,12 +65,54 @@ public: Sink::EntityBuffer buffer(value); const Sink::Entity &entity = buffer.entity(); const auto adaptor = mFolderAdaptorFactory->createAdaptor(entity); - auto folderName = adaptor->getProperty("name").toString(); - //TODO handle non toplevel folders - folderPath = mMaildirPath + "/" + folderName; + auto parentFolder = adaptor->getProperty("parent").toString(); + if (mMaildirPath.endsWith(adaptor->getProperty("name").toString())) { + folderPath = mMaildirPath; + } else { + auto folderName = adaptor->getProperty("name").toString(); + //TODO handle non toplevel folders + folderPath = mMaildirPath + "/" + folderName; + } }); return folderPath; } + + QString moveMessage(const QString &oldPath, const QByteArray &folder, Sink::Storage::Transaction &transaction) + { + if (oldPath.startsWith(Sink::temporaryFileLocation())) { + const auto path = getPath(folder, transaction); + KPIM::Maildir maildir(path, false); + if (!maildir.isValid(true)) { + qWarning() << "Maildir is not existing: " << path; + } + auto identifier = maildir.addEntryFromPath(oldPath); + return path + "/" + identifier; + } + return oldPath; + } + void updatedIndexedProperties(Sink::ApplicationDomain::BufferAdaptor &newEntity) + { + const auto mimeMessagePath = newEntity.getProperty("mimeMessage").toString(); + auto parts = mimeMessagePath.split('/'); + const auto key = parts.takeLast(); + const auto path = parts.join("/") + "/cur/"; + + QDir dir(path); + const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); + if (list.size() != 1) { + Warning() << "Failed to find message " << path << key << list.size(); + return; + } + + KMime::Message *msg = new KMime::Message; + msg->setHead(KMime::CRLFtoLF(KPIM::Maildir::readEntryHeadersFromFile(list.first().filePath()))); + msg->parse(); + + newEntity.setProperty("subject", msg->subject(true)->asUnicodeString()); + newEntity.setProperty("sender", msg->from(true)->asUnicodeString()); + newEntity.setProperty("senderName", msg->from(true)->asUnicodeString()); + newEntity.setProperty("date", msg->date(true)->dateTime()); + } void newEntity(const QByteArray &uid, qint64 revision, Sink::ApplicationDomain::BufferAdaptor &newEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE { @@ -79,46 +121,20 @@ public: } const auto mimeMessage = newEntity.getProperty("mimeMessage"); if (mimeMessage.isValid()) { - const auto oldPath = mimeMessage.toString(); - if (oldPath.startsWith(Sink::temporaryFileLocation())) { - auto folder = newEntity.getProperty("folder").toByteArray(); - const auto path = getPath(folder, transaction); - KPIM::Maildir maildir(path, false); - if (!maildir.isValid(true)) { - qWarning() << "Maildir is not existing: " << path; - } - auto identifier = maildir.addEntryFromPath(oldPath); - newEntity.setProperty("mimeMessage", path + "/" + identifier); - } - } - - { - const auto mimeMessagePath = newEntity.getProperty("mimeMessage").toString(); - auto parts = mimeMessagePath.split('/'); - const auto key = parts.takeLast(); - const auto path = parts.join("/") + "/cur/"; - - QDir dir(path); - const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); - if (list.size() != 1) { - Warning() << "Failed to find message " << path << key << list.size(); - return; - } - - KMime::Message *msg = new KMime::Message; - msg->setHead(KMime::CRLFtoLF(KPIM::Maildir::readEntryHeadersFromFile(list.first().filePath()))); - msg->parse(); - - newEntity.setProperty("subject", msg->subject(true)->asUnicodeString()); - newEntity.setProperty("sender", msg->from(true)->asUnicodeString()); - newEntity.setProperty("senderName", msg->from(true)->asUnicodeString()); - newEntity.setProperty("date", msg->date(true)->dateTime()); + newEntity.setProperty("mimeMessage", moveMessage(mimeMessage.toString(), newEntity.getProperty("folder").toByteArray(), transaction)); } + updatedIndexedProperties(newEntity); } void modifiedEntity(const QByteArray &uid, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &oldEntity, Sink::ApplicationDomain::BufferAdaptor &newEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE { + //TODO deal with moves + const auto mimeMessage = newEntity.getProperty("mimeMessage"); + if (mimeMessage.isValid()) { + newEntity.setProperty("mimeMessage", moveMessage(mimeMessage.toString(), newEntity.getProperty("folder").toByteArray(), transaction)); + } + updatedIndexedProperties(newEntity); } void deletedEntity(const QByteArray &uid, qint64 revision, const Sink::ApplicationDomain::BufferAdaptor &oldEntity, Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE @@ -419,22 +435,33 @@ KAsync::Job MaildirResource::replay(Sink::Storage &synchronizationStore, c const auto uid = Sink::Storage::uidFromKey(key); const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, uid, synchronizationTransaction); Trace() << "Modifying a mail: " << remoteId; - auto parts = remoteId.split('/'); - const auto filename = parts.takeLast(); //filename - parts.removeLast(); //cur/new folder - auto maildirPath = parts.join('/'); + const auto maildirPath = KPIM::Maildir::getDirectoryFromFile(remoteId); KPIM::Maildir maildir(maildirPath, false); const Sink::ApplicationDomain::Mail mail(mResourceInstanceIdentifier, Sink::Storage::uidFromKey(key), revision, mMailAdaptorFactory->createAdaptor(entity)); + auto newIdentifier = mail.getMimeMessagePath().split("/").last(); + QString identifier; + if (newIdentifier != KPIM::Maildir::getKeyFromFile(remoteId)) { + //Remove the old mime message if it changed + Trace() << "Removing old mime message: " << remoteId; + QFile(remoteId).remove(); + identifier = newIdentifier; + } else { + //The identifier needs to contain the flags for changeEntryFlags to work + identifier = remoteId.split('/').last(); + } //get flags from KPIM::Maildir::Flags flags; - if (!mail.getProperty("unread").toBool()) { + if (!mail.getUnread()) { flags |= KPIM::Maildir::Seen; } + if (mail.getImportant()) { + flags |= KPIM::Maildir::Flagged; + } - auto newRemoteId = maildir.changeEntryFlags(filename, flags); + const auto newRemoteId = maildir.changeEntryFlags(identifier, flags); updateRemoteId(ENTITY_TYPE_MAIL, uid, QString(maildirPath + "/cur/" + newRemoteId).toUtf8(), synchronizationTransaction); } else { Warning() << "Unkown operation" << operation; diff --git a/tests/maildirresourcetest.cpp b/tests/maildirresourcetest.cpp index 6d78242..968abde 100644 --- a/tests/maildirresourcetest.cpp +++ b/tests/maildirresourcetest.cpp @@ -466,6 +466,64 @@ private slots: QVERIFY(!result2.errorCode()); } + void testEditMail() + { + using namespace Sink; + using namespace Sink::ApplicationDomain; + + auto query = Query::ResourceFilter("org.kde.maildir.instance1"); + Store::synchronize(query).exec().waitForFinished(); + ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); + + Folder f; + + auto result = Store::fetchOne( + Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name")) + .then, Folder>([query, &f](const Folder &folder) { + f = folder; + return Store::fetchAll(Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("folder", folder) + + Query::RequestedProperties(QByteArrayList() << "folder" + << "subject" << "mimeMessage")) + .then, QList>([query](const QList &mails) { + ASYNCCOMPARE(mails.size(), 1); + auto mail = mails.first(); + auto message = KMime::Message::Ptr::create(); + message->subject(true)->fromUnicodeString("Test1", "utf8"); + message->assemble(); + mail->setMimeMessage(message->encodedContent()); + return Store::modify(*mail); + }); + }) + .exec(); + result.waitForFinished(); + QVERIFY(!result.errorCode()); + + ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); + + // Verify that we can still query for all relevant information + auto result2 = Store::fetchAll( + Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("folder", f) + Query::RequestedProperties(QByteArrayList() << "folder" + << "subject" + << "mimeMessage" + << "unread")) + .then, QList>([](const QList &mails) { + ASYNCCOMPARE(mails.size(), 1); + auto mail = mails.first(); + ASYNCCOMPARE(mail->getProperty("subject").toString(), QString("Test1")); + ASYNCVERIFY(QFileInfo(mail->getMimeMessagePath()).exists()); + return KAsync::null(); + }) + .exec(); + result2.waitForFinished(); + QVERIFY(!result2.errorCode()); + + //Ensure we didn't leave a stale message behind + auto targetPath = tempDir.path() + "/maildir1/cur"; + QDir dir(targetPath); + dir.setFilter(QDir::Files); + QTRY_COMPARE(dir.count(), static_cast(1)); + } + void testCreateDraft() { Sink::Query query; -- cgit v1.2.3