summaryrefslogtreecommitdiffstats
path: root/examples/webdavcommon/webdav.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/webdavcommon/webdav.cpp')
-rw-r--r--examples/webdavcommon/webdav.cpp277
1 files changed, 277 insertions, 0 deletions
diff --git a/examples/webdavcommon/webdav.cpp b/examples/webdavcommon/webdav.cpp
new file mode 100644
index 0000000..35f7fc2
--- /dev/null
+++ b/examples/webdavcommon/webdav.cpp
@@ -0,0 +1,277 @@
1/*
2 * Copyright (C) 2018 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "webdav.h"
21
22#include "applicationdomaintype.h"
23#include "resourceconfig.h"
24
25#include <KDAV2/DavCollectionsFetchJob>
26#include <KDAV2/DavItemFetchJob>
27#include <KDAV2/DavItemsListJob>
28#include <KDAV2/EtagCache>
29
30#include <QNetworkReply>
31
32static int translateDavError(KJob *job)
33{
34 using Sink::ApplicationDomain::ErrorCode;
35
36 const int responseCode = dynamic_cast<KDAV2::DavJobBase *>(job)->latestResponseCode();
37
38 switch (responseCode) {
39 case QNetworkReply::HostNotFoundError:
40 return ErrorCode::NoServerError;
41 // Since we don't login we will just not have the necessary permissions ot view the object
42 case QNetworkReply::OperationCanceledError:
43 return ErrorCode::LoginError;
44 }
45 return ErrorCode::UnknownError;
46}
47
48static KAsync::Job<void> runJob(QSharedPointer<KJob> job)
49{
50 return KAsync::start<void>([job(std::move(job))] {
51 if (job->exec()) {
52 SinkTrace() << "Job exec success";
53 } else {
54 SinkTrace() << "Job exec failure";
55 }
56 });
57
58 // For some reason, this code doesn't work
59
60 /*
61 return KAsync::start<void>([job](KAsync::Future<void> &future) {
62 QObject::connect(job, &KJob::result, [&future](KJob *job) {
63 SinkTrace() << "Job done: " << job->metaObject()->className();
64 if (job->error()) {
65 SinkWarning()
66 << "Job failed: " << job->errorString() << job->metaObject()->className()
67 << job->error() << static_cast<KDAV2::DavJobBase *>(job)->latestResponseCode();
68 future.setError(translateDavError(job), job->errorString());
69 } else {
70 future.setFinished();
71 }
72 });
73 SinkTrace() << "Starting job: " << job->metaObject()->className();
74 job->start();
75 });
76 */
77}
78
79WebDavSynchronizer::WebDavSynchronizer(const Sink::ResourceContext &context,
80 KDAV2::Protocol protocol, QByteArray collectionName, QByteArray itemName)
81 : Sink::Synchronizer(context),
82 protocol(protocol),
83 collectionName(std::move(collectionName)),
84 itemName(std::move(itemName))
85{
86 auto config = ResourceConfig::getConfiguration(context.instanceId());
87
88 server = QUrl::fromUserInput(config.value("server").toString());
89 username = config.value("username").toString();
90}
91
92QList<Sink::Synchronizer::SyncRequest> WebDavSynchronizer::getSyncRequests(const Sink::QueryBase &query)
93{
94 QList<Synchronizer::SyncRequest> list;
95 if (!query.type().isEmpty()) {
96 // We want to synchronize something specific
97 list << Synchronizer::SyncRequest{ query };
98 } else {
99 // We want to synchronize everything
100
101 // Item synchronization does the collections anyway
102 // list << Synchronizer::SyncRequest{ Sink::QueryBase(collectionName) };
103 list << Synchronizer::SyncRequest{ Sink::QueryBase(itemName) };
104 }
105 return list;
106}
107
108KAsync::Job<void> WebDavSynchronizer::synchronizeWithSource(const Sink::QueryBase &query)
109{
110 if (query.type() != collectionName && query.type() != itemName) {
111 return KAsync::null<void>();
112 }
113
114 SinkLog() << "Synchronizing" << query.type() << "through WebDAV at:" << serverUrl().url();
115
116 auto collectionsFetchJob = QSharedPointer<KDAV2::DavCollectionsFetchJob>::create(serverUrl());
117
118 auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob](const KAsync::Error &error) {
119 if (error) {
120 SinkWarning() << "Failed to synchronize collections:" << collectionsFetchJob->errorString();
121 } else {
122 updateLocalCollections(collectionsFetchJob->collections());
123 }
124
125 return collectionsFetchJob->collections();
126 });
127
128 if (query.type() == collectionName) {
129 // Do nothing more
130 return job;
131 } else if (query.type() == itemName) {
132 auto progress = QSharedPointer<int>::create(0);
133 auto total = QSharedPointer<int>::create(0);
134
135 // Will contain the resource Id of all collections to be able to scan
136 // for collections to be removed.
137 auto collectionResourceIDs = QSharedPointer<QSet<QByteArray>>::create();
138
139 // Same but for items.
140 // Quirk: may contain a collection Id (see below)
141 auto itemsResourceIDs = QSharedPointer<QSet<QByteArray>>::create();
142
143 return job
144 .serialEach([this, progress(std::move(progress)), total(std::move(total)), collectionResourceIDs,
145 itemsResourceIDs](const KDAV2::DavCollection &collection) {
146 auto collectionResourceID = resourceID(collection);
147
148 collectionResourceIDs->insert(collectionResourceID);
149
150 if (unchanged(collection)) {
151 SinkTrace() << "Collection unchanged:" << collectionResourceID;
152
153 // It seems that doing this prevent the items in the
154 // collection to be removed when doing scanForRemovals
155 // below (since the collection is unchanged, we do not go
156 // through all of its items).
157 // Behaviour copied from the previous code.
158 itemsResourceIDs->insert(collectionResourceID);
159
160 return KAsync::null<void>();
161 }
162
163 SinkTrace() << "Syncing collection:" << collectionResourceID;
164 return synchronizeCollection(collection, progress, total, itemsResourceIDs);
165 })
166 .then([this, collectionResourceIDs(std::move(collectionResourceIDs)),
167 itemsResourceIDs(std::move(itemsResourceIDs))]() {
168 scanForRemovals(collectionName, [&collectionResourceIDs](const QByteArray &remoteId) {
169 return collectionResourceIDs->contains(remoteId);
170 });
171 scanForRemovals(itemName, [&itemsResourceIDs](const QByteArray &remoteId) {
172 return itemsResourceIDs->contains(remoteId);
173 });
174 });
175 } else {
176 SinkWarning() << "Unknown query type";
177 return KAsync::null<void>();
178 }
179}
180
181KAsync::Job<void> WebDavSynchronizer::synchronizeCollection(const KDAV2::DavCollection &collection,
182 QSharedPointer<int> progress, QSharedPointer<int> total,
183 QSharedPointer<QSet<QByteArray>> itemsResourceIDs)
184{
185 auto collectionRid = resourceID(collection);
186 auto ctag = collection.CTag().toLatin1();
187
188 auto localRid = collectionLocalResourceID(collection);
189
190 // The ETag cache is useless here, since `sinkStore()` IS the cache.
191 auto cache = std::make_shared<KDAV2::EtagCache>();
192 auto davItemsListJob = QSharedPointer<KDAV2::DavItemsListJob>::create(collection.url(), std::move(cache));
193
194 return runJob(davItemsListJob)
195 .then([this, davItemsListJob, total] {
196 auto items = davItemsListJob->items();
197 *total += items.size();
198 return KAsync::value(items);
199 })
200 .serialEach([this, collectionRid, localRid, progress(std::move(progress)), total(std::move(total)),
201 itemsResourceIDs(std::move(itemsResourceIDs))](const KDAV2::DavItem &item) {
202 auto itemRid = resourceID(item);
203
204 itemsResourceIDs->insert(itemRid);
205
206 if (unchanged(item)) {
207 SinkTrace() << "Item unchanged:" << itemRid;
208 return KAsync::null<void>();
209 }
210
211 SinkTrace() << "Syncing item:" << itemRid;
212 return synchronizeItem(item, localRid, progress, total);
213 })
214 .then([this, collectionRid, ctag] {
215 // Update the local CTag to be able to tell if the collection is unchanged
216 syncStore().writeValue(collectionRid + "_ctag", ctag);
217 });
218}
219
220KAsync::Job<void> WebDavSynchronizer::synchronizeItem(const KDAV2::DavItem &item,
221 const QByteArray &collectionLocalRid, QSharedPointer<int> progress, QSharedPointer<int> total)
222{
223 auto etag = item.etag().toLatin1();
224
225 auto itemFetchJob = QSharedPointer<KDAV2::DavItemFetchJob>::create(item);
226 return runJob(itemFetchJob)
227 .then([this, itemFetchJob(std::move(itemFetchJob)), collectionLocalRid] {
228 auto item = itemFetchJob->item();
229 updateLocalItem(item, collectionLocalRid);
230 return item;
231 })
232 .then([this, etag, progress(std::move(progress)), total(std::move(total))](const KDAV2::DavItem &item) {
233 // Update the local ETag to be able to tell if the item is unchanged
234 syncStore().writeValue(resourceID(item) + "_etag", etag);
235
236 *progress += 1;
237 reportProgress(*progress, *total);
238 if ((*progress % 5) == 0) {
239 commit();
240 }
241 });
242}
243
244QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavCollection &collection)
245{
246 return collection.url().toDisplayString().toUtf8();
247}
248
249QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavItem &item)
250{
251 return item.url().toDisplayString().toUtf8();
252}
253
254bool WebDavSynchronizer::unchanged(const KDAV2::DavCollection &collection)
255{
256 auto ctag = collection.CTag().toLatin1();
257 return ctag == syncStore().readValue(resourceID(collection) + "_ctag");
258}
259
260bool WebDavSynchronizer::unchanged(const KDAV2::DavItem &item)
261{
262 auto etag = item.etag().toLatin1();
263 return etag == syncStore().readValue(resourceID(item) + "_etag");
264}
265
266KDAV2::DavUrl WebDavSynchronizer::serverUrl() const
267{
268 if (secret().isEmpty()) {
269 return {};
270 }
271
272 auto result = server;
273 result.setUserName(username);
274 result.setPassword(secret());
275
276 return KDAV2::DavUrl{ result, protocol };
277}