From a8075ac935cec172972b7bea375db2c70eb4bec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20Knau=C3=9F?= Date: Mon, 16 Jan 2017 18:40:10 +0100 Subject: starting with davresource --- examples/davresource/davresource.cpp | 572 +++++++++++++++++++++++++++++++++++ 1 file changed, 572 insertions(+) create mode 100644 examples/davresource/davresource.cpp (limited to 'examples/davresource/davresource.cpp') diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp new file mode 100644 index 0000000..b7843b7 --- /dev/null +++ b/examples/davresource/davresource.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2015 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "davresource.h" + +#include "facade.h" +#include "resourceconfig.h" +#include "index.h" +#include "log.h" +#include "definitions.h" +#include "inspection.h" +#include "synchronizer.h" +#include "inspector.h" + +#include "facadefactory.h" +#include "adaptorfactoryregistry.h" + +#include +#include +#include +#include +#include + +#include +#include + +//This is the resources entity type, and not the domain type +#define ENTITY_TYPE_CONTACT "contact" +#define ENTITY_TYPE_ADDRESSBOOK "folder" + +SINK_DEBUG_AREA("davresource") + +using namespace Sink; + +/*static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) +{ + 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) { + SinkWarning() << "Failed to find message " << mimeMessagePath; + SinkWarning() << "Failed to find message " << path; + return QString(); + } + return list.first().filePath(); +} + +class MaildirMailPropertyExtractor : public MailPropertyExtractor +{ +protected: + virtual QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) const Q_DECL_OVERRIDE + { + return ::getFilePathFromMimeMessagePath(mimeMessagePath); + } +}; + +class MaildirMimeMessageMover : public Sink::Preprocessor +{ +public: + MaildirMimeMessageMover(const QByteArray &resourceInstanceIdentifier, const QString &maildirPath) : mResourceInstanceIdentifier(resourceInstanceIdentifier), mMaildirPath(maildirPath) {} + + QString getPath(const QByteArray &folderIdentifier) + { + if (folderIdentifier.isEmpty()) { + return mMaildirPath; + } + QString folderPath; + const auto folder = entityStore().readLatest(folderIdentifier); + if (mMaildirPath.endsWith(folder.getName())) { + folderPath = mMaildirPath; + } else { + auto folderName = folder.getName(); + //FIXME handle non toplevel folders + folderPath = mMaildirPath + "/" + folderName; + } + return folderPath; + } + + QString moveMessage(const QString &oldPath, const QByteArray &folder) + { + if (oldPath.startsWith(Sink::temporaryFileLocation())) { + const auto path = getPath(folder); + KPIM::Contactdir maildir(path, false); + if (!maildir.isValid(true)) { + SinkWarning() << "Maildir is not existing: " << path; + } + auto identifier = maildir.addEntryFromPath(oldPath); + return path + "/" + identifier; + } else { + //Handle moves + const auto path = getPath(folder); + KPIM::Contactdir maildir(path, false); + if (!maildir.isValid(true)) { + SinkWarning() << "Maildir is not existing: " << path; + } + auto oldIdentifier = KPIM::Contactdir::getKeyFromFile(oldPath); + auto pathParts = oldPath.split('/'); + pathParts.takeLast(); + auto oldDirectory = pathParts.join('/'); + if (oldDirectory == path) { + return oldPath; + } + KPIM::Contactdir oldMaildir(oldDirectory, false); + if (!oldMaildir.isValid(false)) { + SinkWarning() << "Maildir is not existing: " << path; + } + auto identifier = oldMaildir.moveEntryTo(oldIdentifier, maildir); + return path + "/" + identifier; + } + } + + void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE + { + auto mail = newEntity.cast(); + const auto mimeMessage = mail.getMimeMessagePath(); + if (!mimeMessage.isNull()) { + const auto path = moveMessage(mimeMessage, mail.getFolder()); + auto blob = ApplicationDomain::BLOB{path}; + blob.isExternal = false; + mail.setProperty(ApplicationDomain::Contact::MimeMessage::name, QVariant::fromValue(blob)); + } + } + + void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE + { + auto newMail = newEntity.cast(); + const ApplicationDomain::Contact oldMail{oldEntity}; + const auto mimeMessage = newMail.getMimeMessagePath(); + const auto newFolder = newMail.getFolder(); + const bool mimeMessageChanged = !mimeMessage.isNull() && mimeMessage != oldMail.getMimeMessagePath(); + const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder(); + if (mimeMessageChanged || folderChanged) { + SinkTrace() << "Moving mime message: " << mimeMessageChanged << folderChanged; + auto newPath = moveMessage(mimeMessage, newMail.getFolder()); + if (newPath != oldMail.getMimeMessagePath()) { + const auto oldPath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); + auto blob = ApplicationDomain::BLOB{newPath}; + blob.isExternal = false; + newMail.setProperty(ApplicationDomain::Contact::MimeMessage::name, QVariant::fromValue(blob)); + //Remove the olde mime message if there is a new one + QFile::remove(oldPath); + } + } + + auto mimeMessagePath = newMail.getMimeMessagePath(); + const auto maildirPath = getPath(newMail.getFolder()); + KPIM::Contactdir maildir(maildirPath, false); + const auto file = getFilePathFromMimeMessagePath(mimeMessagePath); + QString identifier = KPIM::Contactdir::getKeyFromFile(file); + + //get flags from + KPIM::Contactdir::Flags flags; + if (!newMail.getUnread()) { + flags |= KPIM::Contactdir::Seen; + } + if (newMail.getImportant()) { + flags |= KPIM::Contactdir::Flagged; + } + + maildir.changeEntryFlags(identifier, flags); + } + + void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE + { + const ApplicationDomain::Contact oldMail{oldEntity}; + const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); + QFile::remove(filePath); + } + QByteArray mResourceInstanceIdentifier; + QString mMaildirPath; +}; + +class FolderPreprocessor : public Sink::Preprocessor +{ +public: + FolderPreprocessor(const QString maildirPath) : mMaildirPath(maildirPath) {} + + void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE + { + auto folderName = Sink::ApplicationDomain::Folder{newEntity}.getName(); + const auto path = mMaildirPath + "/" + folderName; + KPIM::Contactdir maildir(path, false); + maildir.create(); + } + + void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE + { + } + + void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE + { + } + QString mMaildirPath; +};*/ + +static KAsync::Job runJob(KJob *job) +{ + return KAsync::start([job](KAsync::Future &future) { + QObject::connect(job, &KJob::result, [&future](KJob *job) { + SinkTrace() << "Job done: " << job->metaObject()->className(); + if (job->error()) { + SinkWarning() << "Job failed: " << job->errorString(); + future.setError(job->error(), job->errorString()); + } else { + future.setFinished(); + } + }); + SinkTrace() << "Starting job: " << job->metaObject()->className(); + job->start(); + }); +} + +class ContactSynchronizer : public Sink::Synchronizer { +public: + ContactSynchronizer(const Sink::ResourceContext &resourceContext) + : Sink::Synchronizer(resourceContext) + { + + } + + QByteArray createAddressbook(const QString &folderName, const QString &folderPath, const QString &parentFolderRid, const QByteArray &icon) + { + SinkTrace() << "Creating addressbook: " << folderName << parentFolderRid; + const auto remoteId = folderPath.toUtf8(); + const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; + Sink::ApplicationDomain::Folder folder; + folder.setName(folderName); + folder.setIcon(icon); + QHash mergeCriteria; + + if (!parentFolderRid.isEmpty()) { + folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentFolderRid.toUtf8())); + } + createOrModify(bufferType, remoteId, folder, mergeCriteria); + return remoteId; + } + + void synchronizeAddressbooks(const KDAV::DavCollection::List &folderList) + { + const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; + SinkTrace() << "Found addressbooks " << folderList.size(); + + QVector ridList; + for(const auto &f : folderList) { + const auto &rid = f.url().toDisplayString(); + ridList.append(rid.toUtf8()); + createAddressbook(f.displayName(), rid, "", "addressbook"); + } + + scanForRemovals(bufferType, + [&ridList](const QByteArray &remoteId) -> bool { + return ridList.contains(remoteId); + } + ); + } + + QList getSyncRequests(const Sink::QueryBase &query) Q_DECL_OVERRIDE + { + QList list; + if (!query.type().isEmpty()) { + //We want to synchronize something specific + list << Synchronizer::SyncRequest{query}; + } else { + //We want to synchronize everything + list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName())}; + list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName())}; + } + return list; + } + + KAsync::Job synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE + { + auto job = KAsync::null(); + + if (query.type() == ApplicationDomain::getTypeName()) { + auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); + job = runJob(collectionsFetchJob).syncThen([this, collectionsFetchJob] { + synchronizeAddressbooks(collectionsFetchJob ->collections()); + }); + } else if (query.type() == ApplicationDomain::getTypeName()) { + // for one Collection/Addressbook + /* + auto cache = std::shared:ptr(new KDAV::EtagCache()); + foreach(const auto &item, collection) { // item is a Sink item + cache->setEtag(item.remoteID(), item.etag()); + } + auto job = KDAV::DavItemsListJob(davCollection.url(), cache); + job->exec(); + changedItems = job->changedItems(); + foreach(const auto &item, changedItems) { // item is a DavItem + addOrModifyItem(item); + } + removedItems = job->deletedItems(); + foreach(const auto &item, removedItems) { // item is a DavItem + deleteSinkItem(item); + } + */ + + auto cache = std::shared_ptr(new KDAV::EtagCache()); + QVector folders; + if (query.hasFilter()) { + auto folderFilter = query.getFilter(); + auto localIds = resolveFilter(folderFilter); + auto folderRemoteIds = syncStore().resolveLocalIds(ApplicationDomain::getTypeName(), localIds); + for (const auto &r : folderRemoteIds) { + auto url = QUrl::fromUserInput(r); + url.setUserInfo(mResourceUrl.url().userInfo()); + folders << KDAV::DavUrl(url, mResourceUrl.protocol()); + } + } else { + //return KAsync::null(); + auto url = QUrl::fromUserInput("https://apps.kolabnow.com/addressbooks/test1%40kolab.org/9290e784-c876-412f-8385-be292d64b2c6/"); + url.setUserInfo(mResourceUrl.url().userInfo()); + folders << KDAV::DavUrl(url, mResourceUrl.protocol()); + } + const auto folder = folders.first(); + SinkTrace() << "Syncing " << folder.toDisplayString(); + auto davItemsListJob = new KDAV::DavItemsListJob(folder, cache); + job = runJob(davItemsListJob).syncThen([this, davItemsListJob, folder] { + const QByteArray bufferType = ENTITY_TYPE_CONTACT; + QHash mergeCriteria; + QStringList ridList; + for(const auto &item : davItemsListJob->items()) { + auto davItemFetchJob = new KDAV::DavItemFetchJob(item); + auto job = runJob(davItemFetchJob).syncThen([this, davItemFetchJob,bufferType, mergeCriteria] { + const auto item = davItemFetchJob->item(); + const QByteArray rid = item.url().toDisplayString().toUtf8(); + Sink::ApplicationDomain::Contact contact; + /*contact.setUid(""); + contact.setFn("fn"); + contact.setEmails(QByteArrayList());*/ + contact.setVcard(item.data()); + createOrModify(bufferType, rid, contact, mergeCriteria); + }); + ridList << item.url().toDisplayString(); + } + + scanForRemovals(bufferType, + [&ridList](const QByteArray &remoteId) -> bool { + return ridList.contains(remoteId); + }); + }); + } + return job; +} + +KAsync::Job replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + /* + if (operation == Sink::Operation_Creation) { + const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); + SinkTrace() << "Contact created: " << remoteId; + return KAsync::value(remoteId.toUtf8()); + } else if (operation == Sink::Operation_Removal) { + SinkTrace() << "Removing a contact " << oldRemoteId; + return KAsync::null(); + } else if (operation == Sink::Operation_Modification) { + SinkTrace() << "Modifying a contact: " << oldRemoteId; + const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); + return KAsync::value(remoteId.toUtf8()); + }*/ + return KAsync::null(); + } + + KAsync::Job replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + /* + if (operation == Sink::Operation_Creation) { + auto folderName = folder.getName(); + //FIXME handle non toplevel folders + auto path = mMaildirPath + "/" + folderName; + SinkTrace() << "Creating a new folder: " << path; + KPIM::Contactdir maildir(path, false); + maildir.create(); + return KAsync::value(path.toUtf8()); + } else if (operation == Sink::Operation_Removal) { + const auto path = oldRemoteId; + SinkTrace() << "Removing a folder: " << path; + KPIM::Contactdir maildir(path, false); + maildir.remove(); + return KAsync::null(); + } else if (operation == Sink::Operation_Modification) { + SinkWarning() << "Folder modifications are not implemented"; + return KAsync::value(oldRemoteId); + }*/ + return KAsync::null(); + } + +public: + KDAV::DavUrl mResourceUrl; +}; + +/* +class MaildirInspector : public Sink::Inspector { +public: + MaildirInspector(const Sink::ResourceContext &resourceContext) + : Sink::Inspector(resourceContext) + { + + } +protected: + + KAsync::Job inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE { + auto synchronizationStore = QSharedPointer::create(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::ReadOnly); + auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::DataStore::ReadOnly); + + auto mainStore = QSharedPointer::create(Sink::storageLocation(), mResourceContext.instanceId(), Sink::Storage::DataStore::ReadOnly); + auto transaction = mainStore->createTransaction(Sink::Storage::DataStore::ReadOnly); + + Sink::Storage::EntityStore entityStore(mResourceContext, {"maildirresource"}); + auto syncStore = QSharedPointer::create(synchronizationTransaction); + + SinkTrace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; + + if (domainType == ENTITY_TYPE_MAIL) { + auto mail = entityStore.readLatest(entityId); + const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); + + if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { + if (property == "unread") { + const auto flags = KPIM::Contactdir::readEntryFlags(filePath.split('/').last()); + if (expectedValue.toBool() && (flags & KPIM::Contactdir::Seen)) { + return KAsync::error(1, "Expected unread but couldn't find it."); + } + if (!expectedValue.toBool() && !(flags & KPIM::Contactdir::Seen)) { + return KAsync::error(1, "Expected read but couldn't find it."); + } + return KAsync::null(); + } + if (property == "subject") { + KMime::Message *msg = new KMime::Message; + msg->setHead(KMime::CRLFtoLF(KPIM::Contactdir::readEntryHeadersFromFile(filePath))); + msg->parse(); + + if (msg->subject(true)->asUnicodeString() != expectedValue.toString()) { + return KAsync::error(1, "Subject not as expected: " + msg->subject(true)->asUnicodeString()); + } + return KAsync::null(); + } + } + if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { + if (QFileInfo(filePath).exists() != expectedValue.toBool()) { + return KAsync::error(1, "Wrong file existence: " + filePath); + } + } + } + if (domainType == ENTITY_TYPE_FOLDER) { + const auto remoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, entityId); + auto folder = entityStore.readLatest(entityId); + + if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { + SinkTrace() << "Inspecting cache integrity" << remoteId; + if (!QDir(remoteId).exists()) { + return KAsync::error(1, "The directory is not existing: " + remoteId); + } + + int expectedCount = 0; + Index index("mail.index.folder", transaction); + index.lookup(entityId, [&](const QByteArray &sinkId) { + expectedCount++; + }, + [&](const Index::Error &error) { + SinkWarning() << "Error in index: " << error.message << property; + }); + + QDir dir(remoteId + "/cur"); + const QFileInfoList list = dir.entryInfoList(QDir::Files); + if (list.size() != expectedCount) { + for (const auto &fileInfo : list) { + SinkWarning() << "Found in cache: " << fileInfo.fileName(); + } + return KAsync::error(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount)); + } + } + if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { + if (!remoteId.endsWith(folder.getName().toUtf8())) { + return KAsync::error(1, "Wrong folder name: " + remoteId); + } + //TODO we shouldn't use the remoteId here to figure out the path, it could be gone/changed already + if (QDir(remoteId).exists() != expectedValue.toBool()) { + return KAsync::error(1, "Wrong folder existence: " + remoteId); + } + } + + } + return KAsync::null(); + } +};*/ + + +DavResource::DavResource(const Sink::ResourceContext &resourceContext) + : Sink::GenericResource(resourceContext) +{ + auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); + auto resourceUrl = QUrl::fromUserInput(config.value("resourceUrl").toString()); + resourceUrl.setUserName(config.value("username").toString()); + resourceUrl.setPassword(config.value("password").toString()); + + mResourceUrl = KDAV::DavUrl(resourceUrl, KDAV::CardDav); + + auto synchronizer = QSharedPointer::create(resourceContext); + synchronizer->mResourceUrl = mResourceUrl; + setupSynchronizer(synchronizer); + //setupInspector(QSharedPointer::create(resourceContext)); + + /* + setupPreprocessors(ENTITY_TYPE_MAIL, QVector() << new SpecialPurposeProcessor(resourceContext.resourceType, resourceContext.instanceId()) << new MaildirMimeMessageMover(resourceContext.instanceId(), mMaildirPath) << new MaildirMailPropertyExtractor); + setupPreprocessors(ENTITY_TYPE_FOLDER, QVector() << new FolderPreprocessor(mMaildirPath)); + + KPIM::Contactdir dir(mMaildirPath, true); + SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath; + { + auto draftsFolder = dir.addSubFolder("Drafts"); + auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts"); + auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); + } + { + auto trashFolder = dir.addSubFolder("Trash"); + auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash"); + auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); + } + synchronizer->commit();*/ +} + + +DavResourceFactory::DavResourceFactory(QObject *parent) + : Sink::ResourceFactory(parent, + {"-folder.rename"} + ) +{ +} + +Sink::Resource *DavResourceFactory::createResource(const ResourceContext &context) +{ + return new DavResource(context); +} + +void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) +{ + factory.registerFacade(name); + factory.registerFacade(name); +} + +void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) +{ + registry.registerFactory(name); + registry.registerFactory(name); +} + +void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) +{ + DavResource::removeFromDisk(instanceIdentifier); +} -- cgit v1.2.3