From 761328989492db9bd603c2d7f1134d20e485d2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Nicole?= Date: Tue, 27 Mar 2018 18:26:11 +0200 Subject: Add CalDAV support Summary: Notes: - Add a `webdavcommon` folder for WebDAV generic resource code - Move `davresource` to `carddaveresource` and make it use the WebDAV code - For now it tests the CalDAV resource directly on KolabNow (to be changed) - Only synchronization, not adding / changing / removing WebDAV collections or items (to be implemented) - Only events are currently supported (todo, freebusy, etc. are to be implemented but should be straightforward) Fixes T8224 Reviewers: cmollekopf Tags: #sink Maniphest Tasks: T8224 Differential Revision: https://phabricator.kde.org/D11741 --- examples/caldavresource/CMakeLists.txt | 15 +++ examples/caldavresource/caldavresource.cpp | 155 +++++++++++++++++++++++++++ examples/caldavresource/caldavresource.h | 46 ++++++++ examples/caldavresource/tests/CMakeLists.txt | 9 ++ examples/caldavresource/tests/caldavtest.cpp | 93 ++++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 examples/caldavresource/CMakeLists.txt create mode 100644 examples/caldavresource/caldavresource.cpp create mode 100644 examples/caldavresource/caldavresource.h create mode 100644 examples/caldavresource/tests/CMakeLists.txt create mode 100644 examples/caldavresource/tests/caldavtest.cpp (limited to 'examples/caldavresource') diff --git a/examples/caldavresource/CMakeLists.txt b/examples/caldavresource/CMakeLists.txt new file mode 100644 index 0000000..0057e8b --- /dev/null +++ b/examples/caldavresource/CMakeLists.txt @@ -0,0 +1,15 @@ +project(sink_resource_caldav) + +add_definitions(-DQT_PLUGIN) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +find_package(KPimKDAV2 REQUIRED) +find_package(KF5CalendarCore REQUIRED) + +add_library(${PROJECT_NAME} SHARED caldavresource.cpp) +target_link_libraries(${PROJECT_NAME} sink_webdav_common sink Qt5::Core Qt5::Network KPim::KDAV2 + KF5::CalendarCore) + +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) + +add_subdirectory(tests) diff --git a/examples/caldavresource/caldavresource.cpp b/examples/caldavresource/caldavresource.cpp new file mode 100644 index 0000000..2bcdfa1 --- /dev/null +++ b/examples/caldavresource/caldavresource.cpp @@ -0,0 +1,155 @@ +/* + * 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 "caldavresource.h" + +#include "../webdavcommon/webdav.h" + +#include "adaptorfactoryregistry.h" +#include "applicationdomaintype.h" +#include "domainadaptor.h" +#include "facade.h" +#include "facadefactory.h" + +#include + +#define ENTITY_TYPE_EVENT "event" +#define ENTITY_TYPE_CALENDAR "calendar" + +using Sink::ApplicationDomain::getTypeName; + +class EventSynchronizer : public WebDavSynchronizer +{ + using Event = Sink::ApplicationDomain::Event; + using Calendar = Sink::ApplicationDomain::Calendar; + +public: + explicit EventSynchronizer(const Sink::ResourceContext &context) + : WebDavSynchronizer(context, KDAV2::CalDav, getTypeName(), getTypeName()) + {} + +protected: + void updateLocalCollections(KDAV2::DavCollection::List calendarList) Q_DECL_OVERRIDE + { + SinkLog() << "Found" << calendarList.size() << "calendar(s)"; + + QVector ridList; + for (const auto &remoteCalendar : calendarList) { + const auto &rid = resourceID(remoteCalendar); + SinkLog() << "Found calendar:" << remoteCalendar.displayName() << "[" << rid << "]"; + + Calendar localCalendar; + localCalendar.setName(remoteCalendar.displayName()); + + createOrModify(ENTITY_TYPE_CALENDAR, rid, localCalendar, + /* mergeCriteria = */ QHash{}); + } + } + + void updateLocalItem(KDAV2::DavItem remoteItem, const QByteArray &calendarLocalId) Q_DECL_OVERRIDE + { + const auto &rid = resourceID(remoteItem); + + auto incidence = KCalCore::ICalFormat().fromString(remoteItem.data()); + + using Type = KCalCore::IncidenceBase::IncidenceType; + + switch (incidence->type()) { + case Type::TypeEvent: { + auto remoteEvent = dynamic_cast(*incidence); + + Event localEvent; + localEvent.setUid(remoteEvent.uid()); + localEvent.setSummary(remoteEvent.summary()); + localEvent.setDescription(remoteEvent.description()); + localEvent.setStartTime(remoteEvent.dtStart()); + localEvent.setCalendar(calendarLocalId); + + SinkTrace() << "Found an event:" << localEvent.getSummary() << "with id:" << rid; + + createOrModify(ENTITY_TYPE_EVENT, rid, localEvent, + /* mergeCriteria = */ QHash{}); + break; + } + case Type::TypeTodo: + SinkWarning() << "Unimplemented add of a 'Todo' item in the Store"; + break; + case Type::TypeJournal: + SinkWarning() << "Unimplemented add of a 'Journal' item in the Store"; + break; + case Type::TypeFreeBusy: + SinkWarning() << "Unimplemented add of a 'FreeBusy' item in the Store"; + break; + case Type::TypeUnknown: + SinkWarning() << "Trying to add a 'Unknown' item"; + break; + default: + break; + } + } + + QByteArray collectionLocalResourceID(const KDAV2::DavCollection &calendar) Q_DECL_OVERRIDE + { + return syncStore().resolveRemoteId(ENTITY_TYPE_CALENDAR, resourceID(calendar)); + } +}; + +CalDavResource::CalDavResource(const Sink::ResourceContext &context) + : Sink::GenericResource(context) +{ + auto synchronizer = QSharedPointer::create(context); + setupSynchronizer(synchronizer); + + // setupPreprocessors(ENTITY_TYPE_EVENT, QVector() << new EventPropertyExtractor); +} + +CalDavResourceFactory::CalDavResourceFactory(QObject *parent) + : Sink::ResourceFactory(parent, { + Sink::ApplicationDomain::ResourceCapabilities::Event::event, + Sink::ApplicationDomain::ResourceCapabilities::Event::calendar, + Sink::ApplicationDomain::ResourceCapabilities::Event::storage, + }) +{} + +Sink::Resource *CalDavResourceFactory::createResource(const Sink::ResourceContext &context) +{ + return new CalDavResource(context); +} + +using Sink::ApplicationDomain::Calendar; +using Sink::ApplicationDomain::Event; + +void CalDavResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) +{ + factory.registerFacade>(resourceName); + factory.registerFacade>(resourceName); +} + + +void CalDavResourceFactory::registerAdaptorFactories( + const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) +{ + registry.registerFactory>(resourceName); + registry.registerFactory>(resourceName); +} + +void CalDavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) +{ + CalDavResource::removeFromDisk(instanceIdentifier); +} diff --git a/examples/caldavresource/caldavresource.h b/examples/caldavresource/caldavresource.h new file mode 100644 index 0000000..5822495 --- /dev/null +++ b/examples/caldavresource/caldavresource.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include "common/genericresource.h" + +/** + * A CalDAV resource. + */ +class CalDavResource : public Sink::GenericResource +{ +public: + CalDavResource(const Sink::ResourceContext &); +}; + +class CalDavResourceFactory : public Sink::ResourceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "sink.caldav") + Q_INTERFACES(Sink::ResourceFactory) + +public: + CalDavResourceFactory(QObject *parent = nullptr); + + Sink::Resource *createResource(const Sink::ResourceContext &context) Q_DECL_OVERRIDE; + void registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) Q_DECL_OVERRIDE; + void registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) Q_DECL_OVERRIDE; + void removeDataFromDisk(const QByteArray &instanceIdentifier) Q_DECL_OVERRIDE; +}; diff --git a/examples/caldavresource/tests/CMakeLists.txt b/examples/caldavresource/tests/CMakeLists.txt new file mode 100644 index 0000000..d2f9b50 --- /dev/null +++ b/examples/caldavresource/tests/CMakeLists.txt @@ -0,0 +1,9 @@ +set(CMAKE_AUTOMOC ON) +include_directories(${CMAKE_BINARY_DIR}) + +include(SinkTest) + +auto_tests ( + caldavtest +) +target_link_libraries(caldavtest sink_resource_caldav) diff --git a/examples/caldavresource/tests/caldavtest.cpp b/examples/caldavresource/tests/caldavtest.cpp new file mode 100644 index 0000000..f999590 --- /dev/null +++ b/examples/caldavresource/tests/caldavtest.cpp @@ -0,0 +1,93 @@ +#include + +#include "../caldavresource.h" + +#include "common/resourcecontrol.h" +#include "common/secretstore.h" +#include "common/store.h" +#include "common/test.h" +#include "tests/testutils.h" + +using Sink::ApplicationDomain::Calendar; +using Sink::ApplicationDomain::DummyResource; +using Sink::ApplicationDomain::Event; +using Sink::ApplicationDomain::SinkResource; + +class CalDavTest : public QObject +{ + Q_OBJECT + + SinkResource createResource() + { + auto resource = Sink::ApplicationDomain::CalDavResource::create("account1"); + resource.setProperty("server", "http://localhost/dav/calendars/users/doe"); + resource.setProperty("username", "doe"); + Sink::SecretStore::instance().insert(resource.identifier(), "doe"); + resource.setProperty("testmode", true); + return resource; + } + + + QByteArray mResourceInstanceIdentifier; + QByteArray mStorageResource; + +private slots: + + void initTestCase() + { + Sink::Test::initTest(); + auto resource = createResource(); + QVERIFY(!resource.identifier().isEmpty()); + VERIFYEXEC(Sink::Store::create(resource)); + mResourceInstanceIdentifier = resource.identifier(); + + auto dummyResource = DummyResource::create("account1"); + VERIFYEXEC(Sink::Store::create(dummyResource)); + mStorageResource = dummyResource.identifier(); + QVERIFY(!mStorageResource.isEmpty()); + } + + void cleanup() + { + VERIFYEXEC(Sink::Store::removeDataFromDisk(mResourceInstanceIdentifier)); + VERIFYEXEC(Sink::Store::removeDataFromDisk(mStorageResource)); + } + + void init() + { + VERIFYEXEC(Sink::ResourceControl::start(mResourceInstanceIdentifier)); + } + + void testSyncCal() + { + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + // Check in the logs that it doesn't synchronize events again because same CTag + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + } + + void testSyncCalEmpty() + { + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + + auto eventJob = + Sink::Store::fetchAll(Sink::Query().request()).then([](const QList &events) { + QCOMPARE(events.size(), 14); + }); + VERIFYEXEC(eventJob); + + auto calendarJob = + Sink::Store::fetchAll(Sink::Query().request()).then([](const QList &calendars) { + QCOMPARE(calendars.size(), 2); + for (const auto &calendar : calendars) { + QVERIFY(calendar->getName() == "Calendar" || calendar->getName() == "Tasks"); + } + }); + VERIFYEXEC(calendarJob); + + SinkLog() << "Finished"; + } +}; + +QTEST_MAIN(CalDavTest) + +#include "caldavtest.moc" -- cgit v1.2.3