diff options
Diffstat (limited to 'examples/davresource/davresource.cpp')
-rw-r--r-- | examples/davresource/davresource.cpp | 315 |
1 files changed, 0 insertions, 315 deletions
diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp deleted file mode 100644 index fde7055..0000000 --- a/examples/davresource/davresource.cpp +++ /dev/null | |||
@@ -1,315 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 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 "davresource.h" | ||
21 | |||
22 | #include "facade.h" | ||
23 | #include "resourceconfig.h" | ||
24 | #include "log.h" | ||
25 | #include "definitions.h" | ||
26 | #include "synchronizer.h" | ||
27 | #include "inspector.h" | ||
28 | |||
29 | #include "facadefactory.h" | ||
30 | #include "adaptorfactoryregistry.h" | ||
31 | |||
32 | #include "contactpreprocessor.h" | ||
33 | |||
34 | #include <QNetworkReply> | ||
35 | #include <KDAV2/DavCollection> | ||
36 | #include <KDAV2/DavCollectionsFetchJob> | ||
37 | #include <KDAV2/DavItem> | ||
38 | #include <KDAV2/DavItemsListJob> | ||
39 | #include <KDAV2/DavItemFetchJob> | ||
40 | #include <KDAV2/EtagCache> | ||
41 | #include <KDAV2/DavJobBase> | ||
42 | |||
43 | //This is the resources entity type, and not the domain type | ||
44 | #define ENTITY_TYPE_CONTACT "contact" | ||
45 | #define ENTITY_TYPE_ADDRESSBOOK "addressbook" | ||
46 | |||
47 | using namespace Sink; | ||
48 | |||
49 | static int translateDavError(KJob *job) | ||
50 | { | ||
51 | const int responseCode = static_cast<KDAV2::DavJobBase*>(job)->latestResponseCode(); | ||
52 | |||
53 | switch (responseCode) { | ||
54 | case QNetworkReply::HostNotFoundError: | ||
55 | return ApplicationDomain::NoServerError; | ||
56 | //Since we don't login we will just not have the necessary permissions ot view the object | ||
57 | case QNetworkReply::OperationCanceledError: | ||
58 | return ApplicationDomain::LoginError; | ||
59 | } | ||
60 | return ApplicationDomain::UnknownError; | ||
61 | } | ||
62 | |||
63 | static KAsync::Job<void> runJob(KJob *job) | ||
64 | { | ||
65 | return KAsync::start<void>([job](KAsync::Future<void> &future) { | ||
66 | QObject::connect(job, &KJob::result, [&future](KJob *job) { | ||
67 | SinkTrace() << "Job done: " << job->metaObject()->className(); | ||
68 | if (job->error()) { | ||
69 | SinkWarning() << "Job failed: " << job->errorString() << job->metaObject()->className() << job->error() << static_cast<KDAV2::DavJobBase*>(job)->latestResponseCode(); | ||
70 | future.setError(translateDavError(job), job->errorString()); | ||
71 | } else { | ||
72 | future.setFinished(); | ||
73 | } | ||
74 | }); | ||
75 | SinkTrace() << "Starting job: " << job->metaObject()->className(); | ||
76 | job->start(); | ||
77 | }); | ||
78 | } | ||
79 | |||
80 | class ContactSynchronizer : public Sink::Synchronizer { | ||
81 | public: | ||
82 | ContactSynchronizer(const Sink::ResourceContext &resourceContext) | ||
83 | : Sink::Synchronizer(resourceContext) | ||
84 | { | ||
85 | |||
86 | } | ||
87 | |||
88 | QByteArray createAddressbook(const QString &addressbookName, const QString &addressbookPath, const QString &parentAddressbookRid) | ||
89 | { | ||
90 | SinkTrace() << "Creating addressbook: " << addressbookName << parentAddressbookRid; | ||
91 | const auto remoteId = addressbookPath.toUtf8(); | ||
92 | const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; | ||
93 | Sink::ApplicationDomain::Addressbook addressbook; | ||
94 | addressbook.setName(addressbookName); | ||
95 | QHash<QByteArray, Query::Comparator> mergeCriteria; | ||
96 | |||
97 | if (!parentAddressbookRid.isEmpty()) { | ||
98 | addressbook.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentAddressbookRid.toUtf8())); | ||
99 | } | ||
100 | createOrModify(bufferType, remoteId, addressbook, mergeCriteria); | ||
101 | return remoteId; | ||
102 | } | ||
103 | |||
104 | void synchronizeAddressbooks(const KDAV2::DavCollection::List &addressbookList) | ||
105 | { | ||
106 | const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; | ||
107 | SinkTrace() << "Found addressbooks " << addressbookList.size(); | ||
108 | |||
109 | QVector<QByteArray> ridList; | ||
110 | for(const auto &f : addressbookList) { | ||
111 | const auto &rid = getRid(f); | ||
112 | SinkLog() << "Found addressbook:" << rid << f.displayName(); | ||
113 | ridList.append(rid); | ||
114 | createAddressbook(f.displayName(), rid, ""); | ||
115 | } | ||
116 | |||
117 | scanForRemovals(bufferType, | ||
118 | [&ridList](const QByteArray &remoteId) -> bool { | ||
119 | return ridList.contains(remoteId); | ||
120 | } | ||
121 | ); | ||
122 | } | ||
123 | |||
124 | QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query) Q_DECL_OVERRIDE | ||
125 | { | ||
126 | QList<Synchronizer::SyncRequest> list; | ||
127 | if (!query.type().isEmpty()) { | ||
128 | //We want to synchronize something specific | ||
129 | list << Synchronizer::SyncRequest{query}; | ||
130 | } else { | ||
131 | //We want to synchronize everything | ||
132 | list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>())}; | ||
133 | list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Contact>())}; | ||
134 | } | ||
135 | return list; | ||
136 | } | ||
137 | |||
138 | static QByteArray getRid(const KDAV2::DavItem &item) | ||
139 | { | ||
140 | return item.url().toDisplayString().toUtf8(); | ||
141 | } | ||
142 | |||
143 | static QByteArray getRid(const KDAV2::DavCollection &item) | ||
144 | { | ||
145 | return item.url().toDisplayString().toUtf8(); | ||
146 | } | ||
147 | |||
148 | KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE | ||
149 | { | ||
150 | if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Addressbook>()) { | ||
151 | SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << resourceUrl().url(); | ||
152 | auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(resourceUrl()); | ||
153 | auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) { | ||
154 | if (error) { | ||
155 | SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString(); | ||
156 | } else { | ||
157 | synchronizeAddressbooks(collectionsFetchJob->collections()); | ||
158 | } | ||
159 | }); | ||
160 | return job; | ||
161 | } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) { | ||
162 | SinkLogCtx(mLogCtx) << "Synchronizing contacts."; | ||
163 | auto ridList = QSharedPointer<QByteArrayList>::create(); | ||
164 | auto total = QSharedPointer<int>::create(0); | ||
165 | auto progress = QSharedPointer<int>::create(0); | ||
166 | auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(resourceUrl()); | ||
167 | auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] { | ||
168 | synchronizeAddressbooks(collectionsFetchJob ->collections()); | ||
169 | return collectionsFetchJob->collections(); | ||
170 | }) | ||
171 | .serialEach([=](const KDAV2::DavCollection &collection) { | ||
172 | auto collId = getRid(collection); | ||
173 | const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId); | ||
174 | auto ctag = collection.CTag().toLatin1(); | ||
175 | if (ctag != syncStore().readValue(collId + "_ctagXX")) { | ||
176 | SinkTraceCtx(mLogCtx) << "Syncing " << collId; | ||
177 | auto cache = std::shared_ptr<KDAV2::EtagCache>(new KDAV2::EtagCache()); | ||
178 | auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), cache); | ||
179 | QHash<QByteArray, Query::Comparator> mergeCriteria; | ||
180 | auto colljob = runJob(davItemsListJob).then([=] { | ||
181 | const auto items = davItemsListJob->items(); | ||
182 | *total = items.size(); | ||
183 | return KAsync::value(items); | ||
184 | }) | ||
185 | .serialEach([=] (const KDAV2::DavItem &item) { | ||
186 | QByteArray rid = getRid(item); | ||
187 | if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){ | ||
188 | SinkTrace() << "Updating " << rid; | ||
189 | auto davItemFetchJob = new KDAV2::DavItemFetchJob(item); | ||
190 | auto itemjob = runJob(davItemFetchJob) | ||
191 | .then([=] { | ||
192 | const auto item = davItemFetchJob->item(); | ||
193 | const auto rid = getRid(item); | ||
194 | Sink::ApplicationDomain::Contact contact; | ||
195 | contact.setVcard(item.data()); | ||
196 | contact.setAddressbook(addressbookLocalId); | ||
197 | createOrModify(ENTITY_TYPE_CONTACT, rid, contact, mergeCriteria); | ||
198 | return item; | ||
199 | }) | ||
200 | .then([=] (const KDAV2::DavItem &item) { | ||
201 | const auto rid = getRid(item); | ||
202 | syncStore().writeValue(rid + "_etag", item.etag().toLatin1()); | ||
203 | ridList->append(rid); | ||
204 | *progress += 1; | ||
205 | reportProgress(*progress, *total, QByteArrayList{} << addressbookLocalId); | ||
206 | //commit every 5 contacts (so contacts start appearing in the UI) | ||
207 | if ((*progress % 5) == 0) { | ||
208 | commit(); | ||
209 | } | ||
210 | return rid; | ||
211 | }); | ||
212 | return itemjob; | ||
213 | } else { | ||
214 | ridList->append(rid); | ||
215 | return KAsync::value(rid); | ||
216 | } | ||
217 | }) | ||
218 | .then([=] () { | ||
219 | syncStore().writeValue(collId + "_ctag", ctag); | ||
220 | }); | ||
221 | return colljob; | ||
222 | } else { | ||
223 | SinkTraceCtx(mLogCtx) << "Collection unchanged: " << ctag; | ||
224 | // for(const auto &item : addressbook) { | ||
225 | // ridList->append(rid); | ||
226 | // } | ||
227 | return KAsync::null<void>(); | ||
228 | } | ||
229 | }) | ||
230 | .then<void>([this, ridList] () { | ||
231 | scanForRemovals(ENTITY_TYPE_CONTACT, | ||
232 | [&ridList](const QByteArray &remoteId) -> bool { | ||
233 | return ridList->contains(remoteId); | ||
234 | }); | ||
235 | }); | ||
236 | return job; | ||
237 | } else { | ||
238 | return KAsync::null<void>(); | ||
239 | } | ||
240 | } | ||
241 | |||
242 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE | ||
243 | { | ||
244 | return KAsync::null<QByteArray>(); | ||
245 | } | ||
246 | |||
247 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Addressbook &addressbook, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE | ||
248 | { | ||
249 | return KAsync::null<QByteArray>(); | ||
250 | } | ||
251 | |||
252 | KDAV2::DavUrl resourceUrl() const | ||
253 | { | ||
254 | if (secret().isEmpty()) { | ||
255 | return {}; | ||
256 | } | ||
257 | auto resourceUrl = mServer; | ||
258 | resourceUrl.setUserName(mUsername); | ||
259 | resourceUrl.setPassword(secret()); | ||
260 | return KDAV2::DavUrl{resourceUrl, KDAV2::CardDav}; | ||
261 | } | ||
262 | |||
263 | public: | ||
264 | QUrl mServer; | ||
265 | QString mUsername; | ||
266 | }; | ||
267 | |||
268 | |||
269 | DavResource::DavResource(const Sink::ResourceContext &resourceContext) | ||
270 | : Sink::GenericResource(resourceContext) | ||
271 | { | ||
272 | auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); | ||
273 | auto server = QUrl::fromUserInput(config.value("server").toString()); | ||
274 | auto username = config.value("username").toString(); | ||
275 | |||
276 | auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext); | ||
277 | synchronizer->mServer = server; | ||
278 | synchronizer->mUsername = username; | ||
279 | setupSynchronizer(synchronizer); | ||
280 | |||
281 | setupPreprocessors(ENTITY_TYPE_CONTACT, QVector<Sink::Preprocessor*>() << new ContactPropertyExtractor); | ||
282 | } | ||
283 | |||
284 | |||
285 | DavResourceFactory::DavResourceFactory(QObject *parent) | ||
286 | : Sink::ResourceFactory(parent, | ||
287 | {Sink::ApplicationDomain::ResourceCapabilities::Contact::contact, | ||
288 | Sink::ApplicationDomain::ResourceCapabilities::Contact::addressbook, | ||
289 | Sink::ApplicationDomain::ResourceCapabilities::Contact::storage | ||
290 | } | ||
291 | ) | ||
292 | { | ||
293 | } | ||
294 | |||
295 | Sink::Resource *DavResourceFactory::createResource(const ResourceContext &context) | ||
296 | { | ||
297 | return new DavResource(context); | ||
298 | } | ||
299 | |||
300 | void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) | ||
301 | { | ||
302 | factory.registerFacade<ApplicationDomain::Contact, DefaultFacade<ApplicationDomain::Contact>>(name); | ||
303 | factory.registerFacade<ApplicationDomain::Addressbook, DefaultFacade<ApplicationDomain::Addressbook>>(name); | ||
304 | } | ||
305 | |||
306 | void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) | ||
307 | { | ||
308 | registry.registerFactory<ApplicationDomain::Contact, DefaultAdaptorFactory<ApplicationDomain::Contact>>(name); | ||
309 | registry.registerFactory<ApplicationDomain::Addressbook, DefaultAdaptorFactory<ApplicationDomain::Addressbook>>(name); | ||
310 | } | ||
311 | |||
312 | void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) | ||
313 | { | ||
314 | DavResource::removeFromDisk(instanceIdentifier); | ||
315 | } | ||