diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/davresource/CMakeLists.txt | 17 | ||||
-rw-r--r-- | examples/davresource/davresource.cpp | 572 | ||||
-rw-r--r-- | examples/davresource/davresource.h | 68 | ||||
-rw-r--r-- | examples/davresource/domainadaptor.cpp | 35 | ||||
-rw-r--r-- | examples/davresource/domainadaptor.h | 38 | ||||
-rw-r--r-- | examples/davresource/facade.cpp | 44 | ||||
-rw-r--r-- | examples/davresource/facade.h | 36 |
9 files changed, 812 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d049054..48a06f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -22,6 +22,7 @@ include(KDEInstallDirs) | |||
22 | 22 | ||
23 | find_package(Qt5 COMPONENTS REQUIRED Core Network) | 23 | find_package(Qt5 COMPONENTS REQUIRED Core Network) |
24 | find_package(KF5 COMPONENTS REQUIRED Async Mime) | 24 | find_package(KF5 COMPONENTS REQUIRED Async Mime) |
25 | find_package(KPimKDAV REQUIRED) | ||
25 | find_package(FlatBuffers REQUIRED) | 26 | find_package(FlatBuffers REQUIRED) |
26 | 27 | ||
27 | find_program(MEMORYCHECK_COMMAND valgrind) | 28 | find_program(MEMORYCHECK_COMMAND valgrind) |
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9d87b1c..abc7441 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt | |||
@@ -8,3 +8,4 @@ if (BUILD_MAILDIR) | |||
8 | add_subdirectory(imapresource) | 8 | add_subdirectory(imapresource) |
9 | endif() | 9 | endif() |
10 | add_subdirectory(mailtransportresource) | 10 | add_subdirectory(mailtransportresource) |
11 | add_subdirectory(davresource) | ||
diff --git a/examples/davresource/CMakeLists.txt b/examples/davresource/CMakeLists.txt new file mode 100644 index 0000000..d60db22 --- /dev/null +++ b/examples/davresource/CMakeLists.txt | |||
@@ -0,0 +1,17 @@ | |||
1 | project(sink_resource_dav) | ||
2 | |||
3 | add_definitions(-DQT_PLUGIN) | ||
4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | ||
5 | |||
6 | find_package(KF5 COMPONENTS REQUIRED Mime) | ||
7 | |||
8 | add_library(${PROJECT_NAME} SHARED facade.cpp davresource.cpp domainadaptor.cpp) | ||
9 | qt5_use_modules(${PROJECT_NAME} Core Network) | ||
10 | target_link_libraries(${PROJECT_NAME} sink KPim::KDAV) | ||
11 | |||
12 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) | ||
13 | |||
14 | #add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/tests/data") | ||
15 | |||
16 | #add_subdirectory(libmaildir) | ||
17 | #add_subdirectory(tests) | ||
diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp new file mode 100644 index 0000000..b7843b7 --- /dev/null +++ b/examples/davresource/davresource.cpp | |||
@@ -0,0 +1,572 @@ | |||
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 "index.h" | ||
25 | #include "log.h" | ||
26 | #include "definitions.h" | ||
27 | #include "inspection.h" | ||
28 | #include "synchronizer.h" | ||
29 | #include "inspector.h" | ||
30 | |||
31 | #include "facadefactory.h" | ||
32 | #include "adaptorfactoryregistry.h" | ||
33 | |||
34 | #include <KDAV/DavCollection> | ||
35 | #include <KDAV/DavCollectionsFetchJob> | ||
36 | #include <KDAV/DavItemsListJob> | ||
37 | #include <KDAV/DavItemFetchJob> | ||
38 | #include <KDAV/EtagCache> | ||
39 | |||
40 | #include <QDir> | ||
41 | #include <QDirIterator> | ||
42 | |||
43 | //This is the resources entity type, and not the domain type | ||
44 | #define ENTITY_TYPE_CONTACT "contact" | ||
45 | #define ENTITY_TYPE_ADDRESSBOOK "folder" | ||
46 | |||
47 | SINK_DEBUG_AREA("davresource") | ||
48 | |||
49 | using namespace Sink; | ||
50 | |||
51 | /*static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) | ||
52 | { | ||
53 | auto parts = mimeMessagePath.split('/'); | ||
54 | const auto key = parts.takeLast(); | ||
55 | const auto path = parts.join("/") + "/cur/"; | ||
56 | |||
57 | QDir dir(path); | ||
58 | const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); | ||
59 | if (list.size() != 1) { | ||
60 | SinkWarning() << "Failed to find message " << mimeMessagePath; | ||
61 | SinkWarning() << "Failed to find message " << path; | ||
62 | return QString(); | ||
63 | } | ||
64 | return list.first().filePath(); | ||
65 | } | ||
66 | |||
67 | class MaildirMailPropertyExtractor : public MailPropertyExtractor | ||
68 | { | ||
69 | protected: | ||
70 | virtual QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) const Q_DECL_OVERRIDE | ||
71 | { | ||
72 | return ::getFilePathFromMimeMessagePath(mimeMessagePath); | ||
73 | } | ||
74 | }; | ||
75 | |||
76 | class MaildirMimeMessageMover : public Sink::Preprocessor | ||
77 | { | ||
78 | public: | ||
79 | MaildirMimeMessageMover(const QByteArray &resourceInstanceIdentifier, const QString &maildirPath) : mResourceInstanceIdentifier(resourceInstanceIdentifier), mMaildirPath(maildirPath) {} | ||
80 | |||
81 | QString getPath(const QByteArray &folderIdentifier) | ||
82 | { | ||
83 | if (folderIdentifier.isEmpty()) { | ||
84 | return mMaildirPath; | ||
85 | } | ||
86 | QString folderPath; | ||
87 | const auto folder = entityStore().readLatest<ApplicationDomain::Folder>(folderIdentifier); | ||
88 | if (mMaildirPath.endsWith(folder.getName())) { | ||
89 | folderPath = mMaildirPath; | ||
90 | } else { | ||
91 | auto folderName = folder.getName(); | ||
92 | //FIXME handle non toplevel folders | ||
93 | folderPath = mMaildirPath + "/" + folderName; | ||
94 | } | ||
95 | return folderPath; | ||
96 | } | ||
97 | |||
98 | QString moveMessage(const QString &oldPath, const QByteArray &folder) | ||
99 | { | ||
100 | if (oldPath.startsWith(Sink::temporaryFileLocation())) { | ||
101 | const auto path = getPath(folder); | ||
102 | KPIM::Contactdir maildir(path, false); | ||
103 | if (!maildir.isValid(true)) { | ||
104 | SinkWarning() << "Maildir is not existing: " << path; | ||
105 | } | ||
106 | auto identifier = maildir.addEntryFromPath(oldPath); | ||
107 | return path + "/" + identifier; | ||
108 | } else { | ||
109 | //Handle moves | ||
110 | const auto path = getPath(folder); | ||
111 | KPIM::Contactdir maildir(path, false); | ||
112 | if (!maildir.isValid(true)) { | ||
113 | SinkWarning() << "Maildir is not existing: " << path; | ||
114 | } | ||
115 | auto oldIdentifier = KPIM::Contactdir::getKeyFromFile(oldPath); | ||
116 | auto pathParts = oldPath.split('/'); | ||
117 | pathParts.takeLast(); | ||
118 | auto oldDirectory = pathParts.join('/'); | ||
119 | if (oldDirectory == path) { | ||
120 | return oldPath; | ||
121 | } | ||
122 | KPIM::Contactdir oldMaildir(oldDirectory, false); | ||
123 | if (!oldMaildir.isValid(false)) { | ||
124 | SinkWarning() << "Maildir is not existing: " << path; | ||
125 | } | ||
126 | auto identifier = oldMaildir.moveEntryTo(oldIdentifier, maildir); | ||
127 | return path + "/" + identifier; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE | ||
132 | { | ||
133 | auto mail = newEntity.cast<ApplicationDomain::Contact>(); | ||
134 | const auto mimeMessage = mail.getMimeMessagePath(); | ||
135 | if (!mimeMessage.isNull()) { | ||
136 | const auto path = moveMessage(mimeMessage, mail.getFolder()); | ||
137 | auto blob = ApplicationDomain::BLOB{path}; | ||
138 | blob.isExternal = false; | ||
139 | mail.setProperty(ApplicationDomain::Contact::MimeMessage::name, QVariant::fromValue(blob)); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE | ||
144 | { | ||
145 | auto newMail = newEntity.cast<ApplicationDomain::Contact>(); | ||
146 | const ApplicationDomain::Contact oldMail{oldEntity}; | ||
147 | const auto mimeMessage = newMail.getMimeMessagePath(); | ||
148 | const auto newFolder = newMail.getFolder(); | ||
149 | const bool mimeMessageChanged = !mimeMessage.isNull() && mimeMessage != oldMail.getMimeMessagePath(); | ||
150 | const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder(); | ||
151 | if (mimeMessageChanged || folderChanged) { | ||
152 | SinkTrace() << "Moving mime message: " << mimeMessageChanged << folderChanged; | ||
153 | auto newPath = moveMessage(mimeMessage, newMail.getFolder()); | ||
154 | if (newPath != oldMail.getMimeMessagePath()) { | ||
155 | const auto oldPath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); | ||
156 | auto blob = ApplicationDomain::BLOB{newPath}; | ||
157 | blob.isExternal = false; | ||
158 | newMail.setProperty(ApplicationDomain::Contact::MimeMessage::name, QVariant::fromValue(blob)); | ||
159 | //Remove the olde mime message if there is a new one | ||
160 | QFile::remove(oldPath); | ||
161 | } | ||
162 | } | ||
163 | |||
164 | auto mimeMessagePath = newMail.getMimeMessagePath(); | ||
165 | const auto maildirPath = getPath(newMail.getFolder()); | ||
166 | KPIM::Contactdir maildir(maildirPath, false); | ||
167 | const auto file = getFilePathFromMimeMessagePath(mimeMessagePath); | ||
168 | QString identifier = KPIM::Contactdir::getKeyFromFile(file); | ||
169 | |||
170 | //get flags from | ||
171 | KPIM::Contactdir::Flags flags; | ||
172 | if (!newMail.getUnread()) { | ||
173 | flags |= KPIM::Contactdir::Seen; | ||
174 | } | ||
175 | if (newMail.getImportant()) { | ||
176 | flags |= KPIM::Contactdir::Flagged; | ||
177 | } | ||
178 | |||
179 | maildir.changeEntryFlags(identifier, flags); | ||
180 | } | ||
181 | |||
182 | void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE | ||
183 | { | ||
184 | const ApplicationDomain::Contact oldMail{oldEntity}; | ||
185 | const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); | ||
186 | QFile::remove(filePath); | ||
187 | } | ||
188 | QByteArray mResourceInstanceIdentifier; | ||
189 | QString mMaildirPath; | ||
190 | }; | ||
191 | |||
192 | class FolderPreprocessor : public Sink::Preprocessor | ||
193 | { | ||
194 | public: | ||
195 | FolderPreprocessor(const QString maildirPath) : mMaildirPath(maildirPath) {} | ||
196 | |||
197 | void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE | ||
198 | { | ||
199 | auto folderName = Sink::ApplicationDomain::Folder{newEntity}.getName(); | ||
200 | const auto path = mMaildirPath + "/" + folderName; | ||
201 | KPIM::Contactdir maildir(path, false); | ||
202 | maildir.create(); | ||
203 | } | ||
204 | |||
205 | void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE | ||
206 | { | ||
207 | } | ||
208 | |||
209 | void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE | ||
210 | { | ||
211 | } | ||
212 | QString mMaildirPath; | ||
213 | };*/ | ||
214 | |||
215 | static KAsync::Job<void> runJob(KJob *job) | ||
216 | { | ||
217 | return KAsync::start<void>([job](KAsync::Future<void> &future) { | ||
218 | QObject::connect(job, &KJob::result, [&future](KJob *job) { | ||
219 | SinkTrace() << "Job done: " << job->metaObject()->className(); | ||
220 | if (job->error()) { | ||
221 | SinkWarning() << "Job failed: " << job->errorString(); | ||
222 | future.setError(job->error(), job->errorString()); | ||
223 | } else { | ||
224 | future.setFinished(); | ||
225 | } | ||
226 | }); | ||
227 | SinkTrace() << "Starting job: " << job->metaObject()->className(); | ||
228 | job->start(); | ||
229 | }); | ||
230 | } | ||
231 | |||
232 | class ContactSynchronizer : public Sink::Synchronizer { | ||
233 | public: | ||
234 | ContactSynchronizer(const Sink::ResourceContext &resourceContext) | ||
235 | : Sink::Synchronizer(resourceContext) | ||
236 | { | ||
237 | |||
238 | } | ||
239 | |||
240 | QByteArray createAddressbook(const QString &folderName, const QString &folderPath, const QString &parentFolderRid, const QByteArray &icon) | ||
241 | { | ||
242 | SinkTrace() << "Creating addressbook: " << folderName << parentFolderRid; | ||
243 | const auto remoteId = folderPath.toUtf8(); | ||
244 | const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; | ||
245 | Sink::ApplicationDomain::Folder folder; | ||
246 | folder.setName(folderName); | ||
247 | folder.setIcon(icon); | ||
248 | QHash<QByteArray, Query::Comparator> mergeCriteria; | ||
249 | |||
250 | if (!parentFolderRid.isEmpty()) { | ||
251 | folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentFolderRid.toUtf8())); | ||
252 | } | ||
253 | createOrModify(bufferType, remoteId, folder, mergeCriteria); | ||
254 | return remoteId; | ||
255 | } | ||
256 | |||
257 | void synchronizeAddressbooks(const KDAV::DavCollection::List &folderList) | ||
258 | { | ||
259 | const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; | ||
260 | SinkTrace() << "Found addressbooks " << folderList.size(); | ||
261 | |||
262 | QVector<QByteArray> ridList; | ||
263 | for(const auto &f : folderList) { | ||
264 | const auto &rid = f.url().toDisplayString(); | ||
265 | ridList.append(rid.toUtf8()); | ||
266 | createAddressbook(f.displayName(), rid, "", "addressbook"); | ||
267 | } | ||
268 | |||
269 | scanForRemovals(bufferType, | ||
270 | [&ridList](const QByteArray &remoteId) -> bool { | ||
271 | return ridList.contains(remoteId); | ||
272 | } | ||
273 | ); | ||
274 | } | ||
275 | |||
276 | QList<Synchronizer::SyncRequest> getSyncRequests(const Sink::QueryBase &query) Q_DECL_OVERRIDE | ||
277 | { | ||
278 | QList<Synchronizer::SyncRequest> list; | ||
279 | if (!query.type().isEmpty()) { | ||
280 | //We want to synchronize something specific | ||
281 | list << Synchronizer::SyncRequest{query}; | ||
282 | } else { | ||
283 | //We want to synchronize everything | ||
284 | list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Folder>())}; | ||
285 | list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName<ApplicationDomain::Contact>())}; | ||
286 | } | ||
287 | return list; | ||
288 | } | ||
289 | |||
290 | KAsync::Job<void> synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE | ||
291 | { | ||
292 | auto job = KAsync::null<void>(); | ||
293 | |||
294 | if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Folder>()) { | ||
295 | auto collectionsFetchJob = new KDAV::DavCollectionsFetchJob(mResourceUrl); | ||
296 | job = runJob(collectionsFetchJob).syncThen<void>([this, collectionsFetchJob] { | ||
297 | synchronizeAddressbooks(collectionsFetchJob ->collections()); | ||
298 | }); | ||
299 | } else if (query.type() == ApplicationDomain::getTypeName<ApplicationDomain::Contact>()) { | ||
300 | // for one Collection/Addressbook | ||
301 | /* | ||
302 | auto cache = std::shared:ptr<KDAV::EtagCache>(new KDAV::EtagCache()); | ||
303 | foreach(const auto &item, collection) { // item is a Sink item | ||
304 | cache->setEtag(item.remoteID(), item.etag()); | ||
305 | } | ||
306 | auto job = KDAV::DavItemsListJob(davCollection.url(), cache); | ||
307 | job->exec(); | ||
308 | changedItems = job->changedItems(); | ||
309 | foreach(const auto &item, changedItems) { // item is a DavItem | ||
310 | addOrModifyItem(item); | ||
311 | } | ||
312 | removedItems = job->deletedItems(); | ||
313 | foreach(const auto &item, removedItems) { // item is a DavItem | ||
314 | deleteSinkItem(item); | ||
315 | } | ||
316 | */ | ||
317 | |||
318 | auto cache = std::shared_ptr<KDAV::EtagCache>(new KDAV::EtagCache()); | ||
319 | QVector<KDAV::DavUrl> folders; | ||
320 | if (query.hasFilter<ApplicationDomain::Mail::Folder>()) { | ||
321 | auto folderFilter = query.getFilter<ApplicationDomain::Mail::Folder>(); | ||
322 | auto localIds = resolveFilter(folderFilter); | ||
323 | auto folderRemoteIds = syncStore().resolveLocalIds(ApplicationDomain::getTypeName<ApplicationDomain::Folder>(), localIds); | ||
324 | for (const auto &r : folderRemoteIds) { | ||
325 | auto url = QUrl::fromUserInput(r); | ||
326 | url.setUserInfo(mResourceUrl.url().userInfo()); | ||
327 | folders << KDAV::DavUrl(url, mResourceUrl.protocol()); | ||
328 | } | ||
329 | } else { | ||
330 | //return KAsync::null<void>(); | ||
331 | auto url = QUrl::fromUserInput("https://apps.kolabnow.com/addressbooks/test1%40kolab.org/9290e784-c876-412f-8385-be292d64b2c6/"); | ||
332 | url.setUserInfo(mResourceUrl.url().userInfo()); | ||
333 | folders << KDAV::DavUrl(url, mResourceUrl.protocol()); | ||
334 | } | ||
335 | const auto folder = folders.first(); | ||
336 | SinkTrace() << "Syncing " << folder.toDisplayString(); | ||
337 | auto davItemsListJob = new KDAV::DavItemsListJob(folder, cache); | ||
338 | job = runJob(davItemsListJob).syncThen<void>([this, davItemsListJob, folder] { | ||
339 | const QByteArray bufferType = ENTITY_TYPE_CONTACT; | ||
340 | QHash<QByteArray, Query::Comparator> mergeCriteria; | ||
341 | QStringList ridList; | ||
342 | for(const auto &item : davItemsListJob->items()) { | ||
343 | auto davItemFetchJob = new KDAV::DavItemFetchJob(item); | ||
344 | auto job = runJob(davItemFetchJob).syncThen<void>([this, davItemFetchJob,bufferType, mergeCriteria] { | ||
345 | const auto item = davItemFetchJob->item(); | ||
346 | const QByteArray rid = item.url().toDisplayString().toUtf8(); | ||
347 | Sink::ApplicationDomain::Contact contact; | ||
348 | /*contact.setUid(""); | ||
349 | contact.setFn("fn"); | ||
350 | contact.setEmails(QByteArrayList());*/ | ||
351 | contact.setVcard(item.data()); | ||
352 | createOrModify(bufferType, rid, contact, mergeCriteria); | ||
353 | }); | ||
354 | ridList << item.url().toDisplayString(); | ||
355 | } | ||
356 | |||
357 | scanForRemovals(bufferType, | ||
358 | [&ridList](const QByteArray &remoteId) -> bool { | ||
359 | return ridList.contains(remoteId); | ||
360 | }); | ||
361 | }); | ||
362 | } | ||
363 | return job; | ||
364 | } | ||
365 | |||
366 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE | ||
367 | { | ||
368 | /* | ||
369 | if (operation == Sink::Operation_Creation) { | ||
370 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | ||
371 | SinkTrace() << "Contact created: " << remoteId; | ||
372 | return KAsync::value(remoteId.toUtf8()); | ||
373 | } else if (operation == Sink::Operation_Removal) { | ||
374 | SinkTrace() << "Removing a contact " << oldRemoteId; | ||
375 | return KAsync::null<QByteArray>(); | ||
376 | } else if (operation == Sink::Operation_Modification) { | ||
377 | SinkTrace() << "Modifying a contact: " << oldRemoteId; | ||
378 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | ||
379 | return KAsync::value(remoteId.toUtf8()); | ||
380 | }*/ | ||
381 | return KAsync::null<QByteArray>(); | ||
382 | } | ||
383 | |||
384 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Folder &folder, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE | ||
385 | { | ||
386 | /* | ||
387 | if (operation == Sink::Operation_Creation) { | ||
388 | auto folderName = folder.getName(); | ||
389 | //FIXME handle non toplevel folders | ||
390 | auto path = mMaildirPath + "/" + folderName; | ||
391 | SinkTrace() << "Creating a new folder: " << path; | ||
392 | KPIM::Contactdir maildir(path, false); | ||
393 | maildir.create(); | ||
394 | return KAsync::value(path.toUtf8()); | ||
395 | } else if (operation == Sink::Operation_Removal) { | ||
396 | const auto path = oldRemoteId; | ||
397 | SinkTrace() << "Removing a folder: " << path; | ||
398 | KPIM::Contactdir maildir(path, false); | ||
399 | maildir.remove(); | ||
400 | return KAsync::null<QByteArray>(); | ||
401 | } else if (operation == Sink::Operation_Modification) { | ||
402 | SinkWarning() << "Folder modifications are not implemented"; | ||
403 | return KAsync::value(oldRemoteId); | ||
404 | }*/ | ||
405 | return KAsync::null<QByteArray>(); | ||
406 | } | ||
407 | |||
408 | public: | ||
409 | KDAV::DavUrl mResourceUrl; | ||
410 | }; | ||
411 | |||
412 | /* | ||
413 | class MaildirInspector : public Sink::Inspector { | ||
414 | public: | ||
415 | MaildirInspector(const Sink::ResourceContext &resourceContext) | ||
416 | : Sink::Inspector(resourceContext) | ||
417 | { | ||
418 | |||
419 | } | ||
420 | protected: | ||
421 | |||
422 | KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE { | ||
423 | auto synchronizationStore = QSharedPointer<Sink::Storage::DataStore>::create(Sink::storageLocation(), mResourceContext.instanceId() + ".synchronization", Sink::Storage::DataStore::ReadOnly); | ||
424 | auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::DataStore::ReadOnly); | ||
425 | |||
426 | auto mainStore = QSharedPointer<Sink::Storage::DataStore>::create(Sink::storageLocation(), mResourceContext.instanceId(), Sink::Storage::DataStore::ReadOnly); | ||
427 | auto transaction = mainStore->createTransaction(Sink::Storage::DataStore::ReadOnly); | ||
428 | |||
429 | Sink::Storage::EntityStore entityStore(mResourceContext, {"maildirresource"}); | ||
430 | auto syncStore = QSharedPointer<SynchronizerStore>::create(synchronizationTransaction); | ||
431 | |||
432 | SinkTrace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; | ||
433 | |||
434 | if (domainType == ENTITY_TYPE_MAIL) { | ||
435 | auto mail = entityStore.readLatest<Sink::ApplicationDomain::Contact>(entityId); | ||
436 | const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | ||
437 | |||
438 | if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { | ||
439 | if (property == "unread") { | ||
440 | const auto flags = KPIM::Contactdir::readEntryFlags(filePath.split('/').last()); | ||
441 | if (expectedValue.toBool() && (flags & KPIM::Contactdir::Seen)) { | ||
442 | return KAsync::error<void>(1, "Expected unread but couldn't find it."); | ||
443 | } | ||
444 | if (!expectedValue.toBool() && !(flags & KPIM::Contactdir::Seen)) { | ||
445 | return KAsync::error<void>(1, "Expected read but couldn't find it."); | ||
446 | } | ||
447 | return KAsync::null<void>(); | ||
448 | } | ||
449 | if (property == "subject") { | ||
450 | KMime::Message *msg = new KMime::Message; | ||
451 | msg->setHead(KMime::CRLFtoLF(KPIM::Contactdir::readEntryHeadersFromFile(filePath))); | ||
452 | msg->parse(); | ||
453 | |||
454 | if (msg->subject(true)->asUnicodeString() != expectedValue.toString()) { | ||
455 | return KAsync::error<void>(1, "Subject not as expected: " + msg->subject(true)->asUnicodeString()); | ||
456 | } | ||
457 | return KAsync::null<void>(); | ||
458 | } | ||
459 | } | ||
460 | if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { | ||
461 | if (QFileInfo(filePath).exists() != expectedValue.toBool()) { | ||
462 | return KAsync::error<void>(1, "Wrong file existence: " + filePath); | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | if (domainType == ENTITY_TYPE_FOLDER) { | ||
467 | const auto remoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, entityId); | ||
468 | auto folder = entityStore.readLatest<Sink::ApplicationDomain::Folder>(entityId); | ||
469 | |||
470 | if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { | ||
471 | SinkTrace() << "Inspecting cache integrity" << remoteId; | ||
472 | if (!QDir(remoteId).exists()) { | ||
473 | return KAsync::error<void>(1, "The directory is not existing: " + remoteId); | ||
474 | } | ||
475 | |||
476 | int expectedCount = 0; | ||
477 | Index index("mail.index.folder", transaction); | ||
478 | index.lookup(entityId, [&](const QByteArray &sinkId) { | ||
479 | expectedCount++; | ||
480 | }, | ||
481 | [&](const Index::Error &error) { | ||
482 | SinkWarning() << "Error in index: " << error.message << property; | ||
483 | }); | ||
484 | |||
485 | QDir dir(remoteId + "/cur"); | ||
486 | const QFileInfoList list = dir.entryInfoList(QDir::Files); | ||
487 | if (list.size() != expectedCount) { | ||
488 | for (const auto &fileInfo : list) { | ||
489 | SinkWarning() << "Found in cache: " << fileInfo.fileName(); | ||
490 | } | ||
491 | return KAsync::error<void>(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount)); | ||
492 | } | ||
493 | } | ||
494 | if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { | ||
495 | if (!remoteId.endsWith(folder.getName().toUtf8())) { | ||
496 | return KAsync::error<void>(1, "Wrong folder name: " + remoteId); | ||
497 | } | ||
498 | //TODO we shouldn't use the remoteId here to figure out the path, it could be gone/changed already | ||
499 | if (QDir(remoteId).exists() != expectedValue.toBool()) { | ||
500 | return KAsync::error<void>(1, "Wrong folder existence: " + remoteId); | ||
501 | } | ||
502 | } | ||
503 | |||
504 | } | ||
505 | return KAsync::null<void>(); | ||
506 | } | ||
507 | };*/ | ||
508 | |||
509 | |||
510 | DavResource::DavResource(const Sink::ResourceContext &resourceContext) | ||
511 | : Sink::GenericResource(resourceContext) | ||
512 | { | ||
513 | auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); | ||
514 | auto resourceUrl = QUrl::fromUserInput(config.value("resourceUrl").toString()); | ||
515 | resourceUrl.setUserName(config.value("username").toString()); | ||
516 | resourceUrl.setPassword(config.value("password").toString()); | ||
517 | |||
518 | mResourceUrl = KDAV::DavUrl(resourceUrl, KDAV::CardDav); | ||
519 | |||
520 | auto synchronizer = QSharedPointer<ContactSynchronizer>::create(resourceContext); | ||
521 | synchronizer->mResourceUrl = mResourceUrl; | ||
522 | setupSynchronizer(synchronizer); | ||
523 | //setupInspector(QSharedPointer<MaildirInspector>::create(resourceContext)); | ||
524 | |||
525 | /* | ||
526 | setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new SpecialPurposeProcessor(resourceContext.resourceType, resourceContext.instanceId()) << new MaildirMimeMessageMover(resourceContext.instanceId(), mMaildirPath) << new MaildirMailPropertyExtractor); | ||
527 | setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new FolderPreprocessor(mMaildirPath)); | ||
528 | |||
529 | KPIM::Contactdir dir(mMaildirPath, true); | ||
530 | SinkTrace() << "Started maildir resource for maildir: " << mMaildirPath; | ||
531 | { | ||
532 | auto draftsFolder = dir.addSubFolder("Drafts"); | ||
533 | auto remoteId = synchronizer->createFolder(draftsFolder, "folder", QByteArrayList() << "drafts"); | ||
534 | auto draftsFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); | ||
535 | } | ||
536 | { | ||
537 | auto trashFolder = dir.addSubFolder("Trash"); | ||
538 | auto remoteId = synchronizer->createFolder(trashFolder, "folder", QByteArrayList() << "trash"); | ||
539 | auto trashFolderLocalId = synchronizer->syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, remoteId); | ||
540 | } | ||
541 | synchronizer->commit();*/ | ||
542 | } | ||
543 | |||
544 | |||
545 | DavResourceFactory::DavResourceFactory(QObject *parent) | ||
546 | : Sink::ResourceFactory(parent, | ||
547 | {"-folder.rename"} | ||
548 | ) | ||
549 | { | ||
550 | } | ||
551 | |||
552 | Sink::Resource *DavResourceFactory::createResource(const ResourceContext &context) | ||
553 | { | ||
554 | return new DavResource(context); | ||
555 | } | ||
556 | |||
557 | void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) | ||
558 | { | ||
559 | factory.registerFacade<Sink::ApplicationDomain::Contact, DavResourceContactFacade>(name); | ||
560 | factory.registerFacade<Sink::ApplicationDomain::Folder, DavResourceFolderFacade>(name); | ||
561 | } | ||
562 | |||
563 | void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) | ||
564 | { | ||
565 | registry.registerFactory<Sink::ApplicationDomain::Contact, ContactAdaptorFactory>(name); | ||
566 | registry.registerFactory<Sink::ApplicationDomain::Folder, AddressbookAdaptorFactory>(name); | ||
567 | } | ||
568 | |||
569 | void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) | ||
570 | { | ||
571 | DavResource::removeFromDisk(instanceIdentifier); | ||
572 | } | ||
diff --git a/examples/davresource/davresource.h b/examples/davresource/davresource.h new file mode 100644 index 0000000..3b228c2 --- /dev/null +++ b/examples/davresource/davresource.h | |||
@@ -0,0 +1,68 @@ | |||
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 | #pragma once | ||
21 | |||
22 | #include "common/genericresource.h" | ||
23 | |||
24 | #include <KDAV/DavUrl> | ||
25 | #include <Async/Async> | ||
26 | |||
27 | #include <flatbuffers/flatbuffers.h> | ||
28 | |||
29 | class ContactAdaptorFactory; | ||
30 | class AddressbookAdaptorFactory; | ||
31 | |||
32 | /** | ||
33 | * A DAV resource. | ||
34 | * | ||
35 | * Implementation details: | ||
36 | * The remoteid's have the following formats: | ||
37 | * files: full file path | ||
38 | * directories: full directory path | ||
39 | * | ||
40 | * The resource moves all messages from new to cur during sync and thus expectes all messages that are in the store to always reside in cur. | ||
41 | * The tmp directory is never directly used | ||
42 | */ | ||
43 | class DavResource : public Sink::GenericResource | ||
44 | { | ||
45 | public: | ||
46 | DavResource(const Sink::ResourceContext &resourceContext); | ||
47 | |||
48 | private: | ||
49 | QStringList listAvailableFolders(); | ||
50 | |||
51 | KDAV::DavUrl mResourceUrl; | ||
52 | }; | ||
53 | |||
54 | class DavResourceFactory : public Sink::ResourceFactory | ||
55 | { | ||
56 | Q_OBJECT | ||
57 | Q_PLUGIN_METADATA(IID "sink.davresource") | ||
58 | Q_INTERFACES(Sink::ResourceFactory) | ||
59 | |||
60 | public: | ||
61 | DavResourceFactory(QObject *parent = 0); | ||
62 | |||
63 | Sink::Resource *createResource(const Sink::ResourceContext &context) Q_DECL_OVERRIDE; | ||
64 | void registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) Q_DECL_OVERRIDE; | ||
65 | void registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) Q_DECL_OVERRIDE; | ||
66 | void removeDataFromDisk(const QByteArray &instanceIdentifier) Q_DECL_OVERRIDE; | ||
67 | }; | ||
68 | |||
diff --git a/examples/davresource/domainadaptor.cpp b/examples/davresource/domainadaptor.cpp new file mode 100644 index 0000000..861a10e --- /dev/null +++ b/examples/davresource/domainadaptor.cpp | |||
@@ -0,0 +1,35 @@ | |||
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 "domainadaptor.h" | ||
21 | |||
22 | using namespace flatbuffers; | ||
23 | |||
24 | ContactAdaptorFactory::ContactAdaptorFactory() | ||
25 | : DomainTypeAdaptorFactory() | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | AddressbookAdaptorFactory::AddressbookAdaptorFactory() | ||
31 | : DomainTypeAdaptorFactory() | ||
32 | { | ||
33 | |||
34 | } | ||
35 | |||
diff --git a/examples/davresource/domainadaptor.h b/examples/davresource/domainadaptor.h new file mode 100644 index 0000000..7e3c723 --- /dev/null +++ b/examples/davresource/domainadaptor.h | |||
@@ -0,0 +1,38 @@ | |||
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 | #pragma once | ||
20 | |||
21 | #include <common/domainadaptor.h> | ||
22 | #include "contact_generated.h" | ||
23 | #include "folder_generated.h" | ||
24 | #include "dummy_generated.h" | ||
25 | |||
26 | class ContactAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Contact, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
27 | { | ||
28 | public: | ||
29 | ContactAdaptorFactory(); | ||
30 | virtual ~ContactAdaptorFactory() {}; | ||
31 | }; | ||
32 | |||
33 | class AddressbookAdaptorFactory : public DomainTypeAdaptorFactory<Sink::ApplicationDomain::Folder, Sink::ApplicationDomain::Buffer::Dummy, Sink::ApplicationDomain::Buffer::DummyBuilder> | ||
34 | { | ||
35 | public: | ||
36 | AddressbookAdaptorFactory(); | ||
37 | virtual ~AddressbookAdaptorFactory() {}; | ||
38 | }; | ||
diff --git a/examples/davresource/facade.cpp b/examples/davresource/facade.cpp new file mode 100644 index 0000000..b56815a --- /dev/null +++ b/examples/davresource/facade.cpp | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 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 "facade.h" | ||
21 | |||
22 | #include <QDir> | ||
23 | #include <QFileInfo> | ||
24 | |||
25 | #include "query.h" | ||
26 | |||
27 | DavResourceContactFacade::DavResourceContactFacade(const Sink::ResourceContext &context) | ||
28 | : Sink::GenericFacade<Sink::ApplicationDomain::Contact>(context) | ||
29 | { | ||
30 | } | ||
31 | |||
32 | DavResourceContactFacade::~DavResourceContactFacade() | ||
33 | { | ||
34 | } | ||
35 | |||
36 | |||
37 | DavResourceFolderFacade::DavResourceFolderFacade(const Sink::ResourceContext &context) | ||
38 | : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(context) | ||
39 | { | ||
40 | } | ||
41 | |||
42 | DavResourceFolderFacade::~DavResourceFolderFacade() | ||
43 | { | ||
44 | } | ||
diff --git a/examples/davresource/facade.h b/examples/davresource/facade.h new file mode 100644 index 0000000..02bd5dc --- /dev/null +++ b/examples/davresource/facade.h | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 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 | #pragma once | ||
21 | |||
22 | #include "common/facade.h" | ||
23 | |||
24 | class DavResourceContactFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Contact> | ||
25 | { | ||
26 | public: | ||
27 | DavResourceContactFacade(const Sink::ResourceContext &context); | ||
28 | virtual ~DavResourceContactFacade(); | ||
29 | }; | ||
30 | |||
31 | class DavResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> | ||
32 | { | ||
33 | public: | ||
34 | DavResourceFolderFacade(const Sink::ResourceContext &context); | ||
35 | virtual ~DavResourceFolderFacade(); | ||
36 | }; | ||