diff --git a/examples/webdavcommon/webdav.cpp b/examples/webdavcommon/webdav.cpp index 140a0f7c..ff139512 100644 --- a/examples/webdavcommon/webdav.cpp +++ b/examples/webdavcommon/webdav.cpp @@ -1,349 +1,349 @@ /* * 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 #include #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 mCollectionType, QByteArray mEntityType) : Sink::Synchronizer(context), protocol(protocol), mCollectionType(std::move(mCollectionType)), mEntityType(std::move(mEntityType)) { auto config = ResourceConfig::getConfiguration(context.instanceId()); mServer = QUrl::fromUserInput(config.value("server").toString()); mUsername = 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(mCollectionType) }; list << Synchronizer::SyncRequest{ Sink::QueryBase(mEntityType) }; } return list; } KAsync::Job WebDavSynchronizer::synchronizeWithSource(const Sink::QueryBase &query) { if (query.type() != mCollectionType && query.type() != mEntityType) { SinkWarning() << "Received synchronization reuqest with unkown type" << query; return KAsync::null(); } SinkLog() << "Synchronizing" << query.type() << "through WebDAV at:" << serverUrl().url(); 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() == mCollectionType) { // Do nothing more return job; } else if (query.type() == mEntityType) { 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 << collection.displayName(); auto itemsResourceIDs = QSharedPointer>::create(); return synchronizeCollection(collection, progress, total, itemsResourceIDs) .then([=] { const auto collectionLocalId = collectionLocalResourceID(collection); scanForRemovals(mEntityType, [&](const std::function &callback) { //FIXME: The collection type just happens to have the same name as the parent collection property const auto collectionProperty = mCollectionType; store().indexLookup(mEntityType, collectionProperty, collectionLocalId, callback); }, [&itemsResourceIDs](const QByteArray &remoteId) { return itemsResourceIDs->contains(remoteId); }); }); }) .then([=]() { scanForRemovals(mCollectionType, [&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 localId = collectionLocalResourceID(collection); auto cache = std::make_shared(); auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), std::move(cache)); return runJob(davItemsListJob, [](KJob *job) { return static_cast(job)->items(); }) - .then([this, total, collectionUrl(collection.url())](const KDAV2::DavItem::List &items) { + .then([=](const KDAV2::DavItem::List &items) { + if (items.isEmpty()) { + return KAsync::null(); + } *total += items.size(); QStringList itemsUrls; for (const auto &item : items) { itemsUrls << item.url().url().toDisplayString(); } - return runJob(new KDAV2::DavItemsFetchJob(collectionUrl, itemsUrls), - [](KJob *job) { return static_cast(job)->items(); }); - }) - .serialEach([ - this, collectionRid, localId, 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(); - } + return runJob(new KDAV2::DavItemsFetchJob(collection.url(), itemsUrls), + [](KJob *job) { return static_cast(job)->items(); }) + .then([=] (const KDAV2::DavItem::List &items) { + for (const auto &item : items) { + auto itemRid = resourceID(item); + itemsResourceIDs->insert(itemRid); + if (unchanged(item)) { + SinkTrace() << "Item unchanged:" << itemRid; + } else { + updateLocalItemWrapper(item, localId); + } + } - updateLocalItemWrapper(item, localId); - return KAsync::null(); + }); }) .then([this, collectionRid, ctag] { // Update the local CTag to be able to tell if the collection is unchanged syncStore().writeValue(collectionRid + "_ctag", ctag); }); } void WebDavSynchronizer::updateLocalItemWrapper(const KDAV2::DavItem &item, const QByteArray &collectionLocalId) { updateLocalItem(item, collectionLocalId); // Update the local ETag to be able to tell if the item is unchanged syncStore().writeValue(resourceID(item) + "_etag", item.etag().toLatin1()); } KAsync::Job WebDavSynchronizer::synchronizeItem(const KDAV2::DavItem &item, const QByteArray &collectionLocalId, QSharedPointer progress, QSharedPointer total) { SinkTrace() << "Syncing item:" << resourceID(item); auto etag = item.etag().toLatin1(); auto itemFetchJob = new KDAV2::DavItemFetchJob(item); return runJob( itemFetchJob, [](KJob *job) { return static_cast(job)->item(); }) .then([ this, collectionLocalId, progress(std::move(progress)), total(std::move(total)) ](const KDAV2::DavItem &item) { updateLocalItemWrapper(item, collectionLocalId); *progress += 1; reportProgress(*progress, *total); if ((*progress % 5) == 0) { commit(); } }); } KAsync::Job WebDavSynchronizer::createItem(const KDAV2::DavItem &item) { auto job = new KDAV2::DavItemCreateJob(item); return runJob(job).then([] { SinkTrace() << "Done creating item"; }); } KAsync::Job WebDavSynchronizer::removeItem(const KDAV2::DavItem &item) { auto job = new KDAV2::DavItemDeleteJob(item); return runJob(job).then([] { SinkTrace() << "Done removing item"; }); } KAsync::Job WebDavSynchronizer::modifyItem(const KDAV2::DavItem &item) { auto job = new KDAV2::DavItemModifyJob(item); return runJob(job).then([] { SinkTrace() << "Done modifying item"; }); } // There is no "DavCollectionCreateJob" /* KAsync::Job WebDavSynchronizer::createCollection(const KDAV2::DavCollection &collection) { auto job = new KDAV2::DavCollectionCreateJob(collection); return runJob(job); } */ KAsync::Job WebDavSynchronizer::removeCollection(const KDAV2::DavUrl &url) { auto job = new KDAV2::DavCollectionDeleteJob(url); return runJob(job).then([] { SinkLog() << "Done removing collection"; }); } // Useless without using the `setProperty` method of DavCollectionModifyJob /* KAsync::Job WebDavSynchronizer::modifyCollection(const KDAV2::DavUrl &url) { auto job = new KDAV2::DavCollectionModifyJob(url); return runJob(job).then([] { SinkLog() << "Done modifying collection"; }); } */ QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavCollection &collection) { return collection.url().url().path().toUtf8(); } QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavItem &item) { return item.url().url().path().toUtf8(); } KDAV2::DavUrl WebDavSynchronizer::urlOf(const QByteArray &remoteId) { auto davurl = serverUrl(); auto url = davurl.url(); url.setPath(remoteId); davurl.setUrl(url); return davurl; } KDAV2::DavUrl WebDavSynchronizer::urlOf(const QByteArray &collectionRemoteId, const QString &itemPath) { return urlOf(collectionRemoteId + itemPath.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 = mServer; result.setUserName(mUsername); result.setPassword(secret()); return KDAV2::DavUrl{ result, protocol }; }