diff --git a/examples/webdavcommon/webdav.cpp b/examples/webdavcommon/webdav.cpp index 05b5348c..ad1af356 100644 --- a/examples/webdavcommon/webdav.cpp +++ b/examples/webdavcommon/webdav.cpp @@ -1,253 +1,270 @@ /* * Copyright (C) 2018 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 "webdav.h" #include "applicationdomaintype.h" #include "resourceconfig.h" #include #include #include #include #include static int translateDavError(KJob *job) { using Sink::ApplicationDomain::ErrorCode; const int responseCode = dynamic_cast(job)->latestResponseCode(); switch (responseCode) { case QNetworkReply::HostNotFoundError: return ErrorCode::NoServerError; // Since we don't login we will just not have the necessary permissions ot view the object case QNetworkReply::OperationCanceledError: return ErrorCode::LoginError; } return ErrorCode::UnknownError; } 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() << job->metaObject()->className() << job->error(); auto proxyError = translateDavError(job); future.setError(proxyError, job->errorString()); } else { future.setFinished(); } }); SinkTrace() << "Starting job: " << job->metaObject()->className(); job->start(); }); } +template +static KAsync::Job runJob(KJob *job, const std::function &func) +{ + return KAsync::start([job, func](KAsync::Future &future) { + QObject::connect(job, &KJob::result, [&future, func](KJob *job) { + SinkTrace() << "Job done: " << job->metaObject()->className(); + if (job->error()) { + SinkWarning() << "Job failed: " << job->errorString() << job->metaObject()->className() << job->error(); + auto proxyError = translateDavError(job); + future.setError(proxyError, job->errorString()); + } else { + future.setValue(func(job)); + future.setFinished(); + } + }); + SinkTrace() << "Starting job: " << job->metaObject()->className(); + job->start(); + }); +} + WebDavSynchronizer::WebDavSynchronizer(const Sink::ResourceContext &context, KDAV2::Protocol protocol, QByteArray collectionName, QByteArray itemName) : Sink::Synchronizer(context), protocol(protocol), collectionName(std::move(collectionName)), itemName(std::move(itemName)) { auto config = ResourceConfig::getConfiguration(context.instanceId()); server = QUrl::fromUserInput(config.value("server").toString()); username = config.value("username").toString(); } QList WebDavSynchronizer::getSyncRequests(const Sink::QueryBase &query) { QList list; if (!query.type().isEmpty()) { // We want to synchronize something specific list << Synchronizer::SyncRequest{ query }; } else { // We want to synchronize everything // Item synchronization does the collections anyway // list << Synchronizer::SyncRequest{ Sink::QueryBase(collectionName) }; list << Synchronizer::SyncRequest{ Sink::QueryBase(itemName) }; } return list; } KAsync::Job WebDavSynchronizer::synchronizeWithSource(const Sink::QueryBase &query) { if (query.type() != collectionName && query.type() != itemName) { return KAsync::null(); } SinkLog() << "Synchronizing" << query.type() << "through WebDAV at:" << serverUrl().url(); - auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob{serverUrl()}; - auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob](const KAsync::Error &error) { - if (error) { - SinkWarning() << "Failed to synchronize collections:" << error; - } else { - updateLocalCollections(collectionsFetchJob->collections()); - } - - return collectionsFetchJob->collections(); - }); + auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob{ serverUrl() }; + auto job = runJob(collectionsFetchJob, + [](KJob *job) { return static_cast(job)->collections(); }) + .then([this](const KDAV2::DavCollection::List &collections) { + updateLocalCollections(collections); + return collections; + }); if (query.type() == collectionName) { // Do nothing more return job; } else if (query.type() == itemName) { auto progress = QSharedPointer::create(0); auto total = QSharedPointer::create(0); // Will contain the resource Id of all collections to be able to scan // for collections to be removed. auto collectionResourceIDs = QSharedPointer>::create(); return job .serialEach([=](const KDAV2::DavCollection &collection) { auto collectionResourceID = resourceID(collection); collectionResourceIDs->insert(collectionResourceID); if (unchanged(collection)) { SinkTrace() << "Collection unchanged:" << collectionResourceID; return KAsync::null(); } SinkTrace() << "Syncing collection:" << collectionResourceID; auto itemsResourceIDs = QSharedPointer>::create(); return synchronizeCollection(collection, progress, total, itemsResourceIDs) .then([=] { scanForRemovals(itemName, [&itemsResourceIDs](const QByteArray &remoteId) { return itemsResourceIDs->contains(remoteId); }); }); }) .then([=]() { scanForRemovals(collectionName, [&collectionResourceIDs](const QByteArray &remoteId) { return collectionResourceIDs->contains(remoteId); }); }); } else { SinkWarning() << "Unknown query type"; return KAsync::null(); } } KAsync::Job WebDavSynchronizer::synchronizeCollection(const KDAV2::DavCollection &collection, QSharedPointer progress, QSharedPointer total, QSharedPointer> itemsResourceIDs) { auto collectionRid = resourceID(collection); auto ctag = collection.CTag().toLatin1(); auto localRid = collectionLocalResourceID(collection); // The ETag cache is useless here, since `sinkStore()` IS the cache. auto cache = std::make_shared(); auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), std::move(cache)); - return runJob(davItemsListJob) - .then([this, davItemsListJob, total] { - auto items = davItemsListJob->items(); + return runJob(davItemsListJob, + [](KJob *job) { return static_cast(job)->items(); }) + .then([this, total](const KDAV2::DavItem::List &items) { *total += items.size(); - return KAsync::value(items); + return items; }) .serialEach([this, collectionRid, localRid, progress(std::move(progress)), total(std::move(total)), itemsResourceIDs(std::move(itemsResourceIDs))](const KDAV2::DavItem &item) { auto itemRid = resourceID(item); itemsResourceIDs->insert(itemRid); if (unchanged(item)) { SinkTrace() << "Item unchanged:" << itemRid; return KAsync::null(); } SinkTrace() << "Syncing item:" << itemRid; return synchronizeItem(item, localRid, progress, total); }) .then([this, collectionRid, ctag] { // Update the local CTag to be able to tell if the collection is unchanged syncStore().writeValue(collectionRid + "_ctag", ctag); }); } KAsync::Job WebDavSynchronizer::synchronizeItem(const KDAV2::DavItem &item, const QByteArray &collectionLocalRid, QSharedPointer progress, QSharedPointer total) { auto etag = item.etag().toLatin1(); auto itemFetchJob = new KDAV2::DavItemFetchJob(item); - return runJob(itemFetchJob) - .then([this, itemFetchJob(std::move(itemFetchJob)), collectionLocalRid] { - auto item = itemFetchJob->item(); + return runJob( + itemFetchJob, [](KJob *job) { return static_cast(job)->item(); }) + .then([this, collectionLocalRid](const KDAV2::DavItem &item) { updateLocalItem(item, collectionLocalRid); return item; }) .then([this, etag, progress(std::move(progress)), total(std::move(total))](const KDAV2::DavItem &item) { // Update the local ETag to be able to tell if the item is unchanged syncStore().writeValue(resourceID(item) + "_etag", etag); *progress += 1; reportProgress(*progress, *total); if ((*progress % 5) == 0) { commit(); } }); } QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavCollection &collection) { return collection.url().toDisplayString().toUtf8(); } QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavItem &item) { return item.url().toDisplayString().toUtf8(); } bool WebDavSynchronizer::unchanged(const KDAV2::DavCollection &collection) { auto ctag = collection.CTag().toLatin1(); return ctag == syncStore().readValue(resourceID(collection) + "_ctag"); } bool WebDavSynchronizer::unchanged(const KDAV2::DavItem &item) { auto etag = item.etag().toLatin1(); return etag == syncStore().readValue(resourceID(item) + "_etag"); } KDAV2::DavUrl WebDavSynchronizer::serverUrl() const { if (secret().isEmpty()) { return {}; } auto result = server; result.setUserName(username); result.setPassword(secret()); return KDAV2::DavUrl{ result, protocol }; }