/* * 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 "log.h" #include "definitions.h" #include "synchronizer.h" #include "inspector.h" #include "facadefactory.h" #include "adaptorfactoryregistry.h" #include "contactpreprocessor.h" #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 "addressbook" using namespace Sink; 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 &addressbookName, const QString &addressbookPath, const QString &parentAddressbookRid) { SinkTrace() << "Creating addressbook: " << addressbookName << parentAddressbookRid; const auto remoteId = addressbookPath.toUtf8(); const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; Sink::ApplicationDomain::Addressbook addressbook; addressbook.setName(addressbookName); QHash mergeCriteria; if (!parentAddressbookRid.isEmpty()) { addressbook.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentAddressbookRid.toUtf8())); } createOrModify(bufferType, remoteId, addressbook, mergeCriteria); return remoteId; } void synchronizeAddressbooks(const KDAV2::DavCollection::List &addressbookList) { const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; SinkTrace() << "Found addressbooks " << addressbookList.size(); QVector ridList; for(const auto &f : addressbookList) { const auto &rid = getRid(f); SinkTrace() << "Found addressbook:" << rid; ridList.append(rid); createAddressbook(f.displayName(), rid, ""); } 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; } static QByteArray getRid(const KDAV2::DavItem &item) { return item.url().toDisplayString().toUtf8(); } static QByteArray getRid(const KDAV2::DavCollection &item) { return item.url().toDisplayString().toUtf8(); } KAsync::Job synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE { if (query.type() == ApplicationDomain::getTypeName()) { SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << mResourceUrl.url(); auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(mResourceUrl); auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) { if (error) { SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString(); } else { synchronizeAddressbooks(collectionsFetchJob ->collections()); } }); return job; } else if (query.type() == ApplicationDomain::getTypeName()) { SinkLogCtx(mLogCtx) << "Synchronizing contacts."; auto ridList = QSharedPointer::create(); auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(mResourceUrl); auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] { synchronizeAddressbooks(collectionsFetchJob ->collections()); return collectionsFetchJob->collections(); }) .serialEach([this, ridList](const KDAV2::DavCollection &collection) { auto collId = getRid(collection); const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId); auto ctag = collection.CTag().toLatin1(); if (ctag != syncStore().readValue(collId + "_ctagXX")) { SinkTraceCtx(mLogCtx) << "Syncing " << collId; auto cache = std::shared_ptr(new KDAV2::EtagCache()); auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), cache); const QByteArray bufferType = ENTITY_TYPE_CONTACT; QHash mergeCriteria; auto colljob = runJob(davItemsListJob).then([davItemsListJob] { return KAsync::value(davItemsListJob->items()); }) .serialEach([=] (const KDAV2::DavItem &item) { QByteArray rid = getRid(item); if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){ SinkTrace() << "Updating " << rid; auto davItemFetchJob = new KDAV2::DavItemFetchJob(item); auto itemjob = runJob(davItemFetchJob) .then([=] { const auto item = davItemFetchJob->item(); const auto rid = getRid(item); Sink::ApplicationDomain::Contact contact; contact.setVcard(item.data()); contact.setAddressbook(addressbookLocalId); createOrModify(bufferType, rid, contact, mergeCriteria); return item; }) .then([this, ridList] (const KDAV2::DavItem &item) { const auto rid = getRid(item); syncStore().writeValue(rid + "_etag", item.etag().toLatin1()); ridList->append(rid); return rid; }); return itemjob; } else { ridList->append(rid); return KAsync::value(rid); } }) .then([this, collId, ctag] () { syncStore().writeValue(collId + "_ctag", ctag); }); return colljob; } else { SinkTraceCtx(mLogCtx) << "Collection unchanged: " << ctag; // for(const auto &item : addressbook) { // ridList->append(rid); // } return KAsync::null(); } }) .then([this, ridList] () { scanForRemovals(ENTITY_TYPE_CONTACT, [&ridList](const QByteArray &remoteId) -> bool { return ridList->contains(remoteId); }); }); return job; } else { return KAsync::null(); } } KAsync::Job replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE { return KAsync::null(); } KAsync::Job replay(const ApplicationDomain::Addressbook &addressbook, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE { return KAsync::null(); } public: KDAV2::DavUrl mResourceUrl; }; DavResource::DavResource(const Sink::ResourceContext &resourceContext) : Sink::GenericResource(resourceContext) { /* * Fork KIO slaves (used in kdav), instead of starting them via klauncher. * Otherwise we have yet another runtime dependency that will i.e. not work in the docker container. */ qputenv("KDE_FORK_SLAVES", "TRUE"); auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); auto resourceUrl = QUrl::fromUserInput(config.value("server").toString()); resourceUrl.setUserName(config.value("username").toString()); resourceUrl.setPassword(config.value("password").toString()); mResourceUrl = KDAV2::DavUrl(resourceUrl, KDAV2::CardDav); auto synchronizer = QSharedPointer::create(resourceContext); synchronizer->mResourceUrl = mResourceUrl; setupSynchronizer(synchronizer); setupPreprocessors(ENTITY_TYPE_CONTACT, QVector() << new ContactPropertyExtractor); } DavResourceFactory::DavResourceFactory(QObject *parent) : Sink::ResourceFactory(parent, {Sink::ApplicationDomain::ResourceCapabilities::Contact::contact, Sink::ApplicationDomain::ResourceCapabilities::Contact::addressbook, } ) { } 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); }