diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -104,6 +104,7 @@ domain/contact domain/addressbook domain/event + domain/calendar domain/mail domain/folder domain/dummy diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h @@ -389,22 +389,24 @@ SINK_REFERENCE_PROPERTY(Addressbook, Addressbook, addressbook); }; +struct SINK_EXPORT Calendar : public Entity { + SINK_ENTITY(Calendar, calendar); + SINK_PROPERTY(QString, Name, name); +}; + struct SINK_EXPORT Event : public Entity { SINK_ENTITY(Event, event); SINK_PROPERTY(QString, Uid, uid); SINK_PROPERTY(QString, Summary, summary); SINK_PROPERTY(QString, Description, description); - SINK_PROPERTY(QByteArray, Attachment, attachment); + SINK_PROPERTY(QDateTime, StartTime, startTime); + SINK_REFERENCE_PROPERTY(Calendar, Calendar, calendar); }; struct SINK_EXPORT Todo : public Entity { SINK_ENTITY(Todo, todo); }; -struct SINK_EXPORT Calendar : public Entity { - SINK_ENTITY(Calendar, calendar); -}; - struct SINK_EXPORT Folder : public Entity { SINK_ENTITY(Folder, folder); SINK_REFERENCE_PROPERTY(Folder, Parent, parent); @@ -477,6 +479,10 @@ static SinkResource create(const QByteArray &account); }; +struct SINK_EXPORT CalDavResource { + static SinkResource create(const QByteArray &account); +}; + namespace ResourceCapabilities { namespace Mail { static constexpr const char *mail = "mail"; @@ -493,6 +499,11 @@ static constexpr const char *addressbook = "addressbook"; static constexpr const char *storage = "contact.storage"; }; +namespace Event { + static constexpr const char *event = "event"; + static constexpr const char *calendar = "calendar"; + static constexpr const char *storage = "event.storage"; +}; }; namespace SpecialPurpose { @@ -522,7 +533,7 @@ /** * Type implementation. - * + * * Needs to be implemented for every application domain type. * Contains all non-resource specific, but type-specific code. */ @@ -545,6 +556,7 @@ REGISTER_TYPE(Sink::ApplicationDomain::Contact) \ REGISTER_TYPE(Sink::ApplicationDomain::Addressbook) \ REGISTER_TYPE(Sink::ApplicationDomain::Event) \ + REGISTER_TYPE(Sink::ApplicationDomain::Calendar) \ REGISTER_TYPE(Sink::ApplicationDomain::Mail) \ REGISTER_TYPE(Sink::ApplicationDomain::Folder) \ REGISTER_TYPE(Sink::ApplicationDomain::SinkResource) \ diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp --- a/common/domain/applicationdomaintype.cpp +++ b/common/domain/applicationdomaintype.cpp @@ -395,7 +395,15 @@ SinkResource CardDavResource::create(const QByteArray &account) { auto &&resource = ApplicationDomainType::createEntity(); - resource.setResourceType("sink.dav"); + resource.setResourceType("sink.carddav"); + resource.setAccount(account); + return resource; +} + +SinkResource CalDavResource::create(const QByteArray &account) +{ + auto &&resource = ApplicationDomainType::createEntity(); + resource.setResourceType("sink.caldav"); resource.setAccount(account); return resource; } diff --git a/common/domain/applicationdomaintype_p.h b/common/domain/applicationdomaintype_p.h --- a/common/domain/applicationdomaintype_p.h +++ b/common/domain/applicationdomaintype_p.h @@ -38,6 +38,8 @@ return Func{}(std::forward(args...)); } else if (type == Sink::ApplicationDomain::getTypeName()) { return Func{}(std::forward(args...)); + } else if (type == Sink::ApplicationDomain::getTypeName()) { + return Func{}(std::forward(args...)); } else if (type == Sink::ApplicationDomain::getTypeName()) { return Func{}(std::forward(args...)); } else if (type == Sink::ApplicationDomain::getTypeName()) { diff --git a/common/domain/calendar.fbs b/common/domain/calendar.fbs new file mode 100644 --- /dev/null +++ b/common/domain/calendar.fbs @@ -0,0 +1,8 @@ +namespace Sink.ApplicationDomain.Buffer; + +table Calendar { + name:string; +} + +root_type Calendar; +file_identifier "AKFB"; diff --git a/common/domain/event.fbs b/common/domain/event.fbs --- a/common/domain/event.fbs +++ b/common/domain/event.fbs @@ -4,7 +4,8 @@ uid:string; summary:string; description:string; - attachment:[ubyte]; + startTime:string; + calendar:string; } root_type Event; diff --git a/common/domain/typeimplementations.h b/common/domain/typeimplementations.h --- a/common/domain/typeimplementations.h +++ b/common/domain/typeimplementations.h @@ -23,6 +23,7 @@ #include "mail_generated.h" #include "folder_generated.h" #include "event_generated.h" +#include "calendar_generated.h" #include "contact_generated.h" #include "addressbook_generated.h" @@ -94,5 +95,16 @@ static QMap typeDatabases(); }; +template<> +class TypeImplementation { +public: + typedef Sink::ApplicationDomain::Buffer::Calendar Buffer; + typedef Sink::ApplicationDomain::Buffer::CalendarBuilder BufferBuilder; + static void configure(TypeIndex &); + static void configure(PropertyMapper &); + static void configure(IndexPropertyMapper &indexPropertyMapper); + static QMap typeDatabases(); +}; + } } diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp --- a/common/domain/typeimplementations.cpp +++ b/common/domain/typeimplementations.cpp @@ -67,6 +67,11 @@ ValueIndex > EventIndexConfig; +typedef IndexConfig + > CalendarIndexConfig; + + void TypeImplementation::configure(TypeIndex &index) { @@ -201,11 +206,29 @@ SINK_REGISTER_SERIALIZER(propertyMapper, Event, Summary, summary); SINK_REGISTER_SERIALIZER(propertyMapper, Event, Description, description); SINK_REGISTER_SERIALIZER(propertyMapper, Event, Uid, uid); - SINK_REGISTER_SERIALIZER(propertyMapper, Event, Attachment, attachment); + SINK_REGISTER_SERIALIZER(propertyMapper, Event, StartTime, startTime); + SINK_REGISTER_SERIALIZER(propertyMapper, Event, Calendar, calendar); } void TypeImplementation::configure(IndexPropertyMapper &) { } + +void TypeImplementation::configure(TypeIndex &index) +{ + CalendarIndexConfig::configure(index); +} + +QMap TypeImplementation::typeDatabases() +{ + return merge(QMap{{QByteArray{Calendar::name} + ".main", 0}}, CalendarIndexConfig::databases()); +} + +void TypeImplementation::configure(PropertyMapper &propertyMapper) +{ + SINK_REGISTER_SERIALIZER(propertyMapper, Calendar, Name, name); +} + +void TypeImplementation::configure(IndexPropertyMapper &) {} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,5 +9,7 @@ endif() add_subdirectory(mailtransportresource) if (BUILD_DAV) - add_subdirectory(davresource) + add_subdirectory(webdavcommon) + add_subdirectory(carddavresource) + add_subdirectory(caldavresource) endif() diff --git a/examples/caldavresource/CMakeLists.txt b/examples/caldavresource/CMakeLists.txt new file mode 100644 --- /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/davresource/davresource.h b/examples/caldavresource/caldavresource.h rename from examples/davresource/davresource.h rename to examples/caldavresource/caldavresource.h --- a/examples/davresource/davresource.h +++ b/examples/caldavresource/caldavresource.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Christian Mollekopf + * 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 @@ -21,43 +21,26 @@ #include "common/genericresource.h" -#include -#include - -#include - -class ContactAdaptorFactory; -class AddressbookAdaptorFactory; - /** - * A DAV resource. - * - * Implementation details: - * The remoteid's have the following formats: - * files: full file path - * directories: full directory path - * - * 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. - * The tmp directory is never directly used + * A CalDAV resource. */ -class DavResource : public Sink::GenericResource +class CalDavResource : public Sink::GenericResource { public: - DavResource(const Sink::ResourceContext &resourceContext); + CalDavResource(const Sink::ResourceContext &); }; -class DavResourceFactory : public Sink::ResourceFactory +class CalDavResourceFactory : public Sink::ResourceFactory { Q_OBJECT - Q_PLUGIN_METADATA(IID "sink.dav") + Q_PLUGIN_METADATA(IID "sink.caldav") Q_INTERFACES(Sink::ResourceFactory) public: - DavResourceFactory(QObject *parent = 0); + 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/caldavresource.cpp b/examples/caldavresource/caldavresource.cpp new file mode 100644 --- /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/tests/CMakeLists.txt b/examples/caldavresource/tests/CMakeLists.txt new file mode 100644 --- /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 --- /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" diff --git a/examples/davresource/CMakeLists.txt b/examples/carddavresource/CMakeLists.txt rename from examples/davresource/CMakeLists.txt rename to examples/carddavresource/CMakeLists.txt --- a/examples/davresource/CMakeLists.txt +++ b/examples/carddavresource/CMakeLists.txt @@ -1,11 +1,11 @@ -project(sink_resource_dav) +project(sink_resource_carddav) add_definitions(-DQT_PLUGIN) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) find_package(KPimKDAV2 REQUIRED) -add_library(${PROJECT_NAME} SHARED davresource.cpp) -target_link_libraries(${PROJECT_NAME} sink Qt5::Core Qt5::Network KPim::KDAV2) +add_library(${PROJECT_NAME} SHARED carddavresource.cpp) +target_link_libraries(${PROJECT_NAME} sink_webdav_common sink Qt5::Core Qt5::Network KPim::KDAV2) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) diff --git a/examples/davresource/davresource.h b/examples/carddavresource/carddavresource.h rename from examples/davresource/davresource.h rename to examples/carddavresource/carddavresource.h --- a/examples/davresource/davresource.h +++ b/examples/carddavresource/carddavresource.h @@ -30,30 +30,30 @@ class AddressbookAdaptorFactory; /** - * A DAV resource. - * + * A CardDAV resource. + * * Implementation details: * The remoteid's have the following formats: * files: full file path * directories: full directory path - * + * * 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. * The tmp directory is never directly used */ -class DavResource : public Sink::GenericResource +class CardDavResource : public Sink::GenericResource { public: - DavResource(const Sink::ResourceContext &resourceContext); + CardDavResource(const Sink::ResourceContext &resourceContext); }; -class DavResourceFactory : public Sink::ResourceFactory +class CardDavResourceFactory : public Sink::ResourceFactory { Q_OBJECT - Q_PLUGIN_METADATA(IID "sink.dav") + Q_PLUGIN_METADATA(IID "sink.carddav") Q_INTERFACES(Sink::ResourceFactory) public: - DavResourceFactory(QObject *parent = 0); + CardDavResourceFactory(QObject *parent = 0); Sink::Resource *createResource(const Sink::ResourceContext &context) Q_DECL_OVERRIDE; void registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) Q_DECL_OVERRIDE; diff --git a/examples/carddavresource/carddavresource.cpp b/examples/carddavresource/carddavresource.cpp new file mode 100644 --- /dev/null +++ b/examples/carddavresource/carddavresource.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2015 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 "carddavresource.h" + +#include "../webdavcommon/webdav.h" + +#include "facade.h" +#include "resourceconfig.h" +#include "log.h" +#include "definitions.h" +#include "synchronizer.h" +#include "inspector.h" + +#include "facadefactory.h" +#include "adaptorfactoryregistry.h" + +#include "contactpreprocessor.h" + +//This is the resources entity type, and not the domain type +#define ENTITY_TYPE_CONTACT "contact" +#define ENTITY_TYPE_ADDRESSBOOK "addressbook" + +using namespace Sink; + +class ContactSynchronizer : public WebDavSynchronizer +{ +public: + ContactSynchronizer(const Sink::ResourceContext &resourceContext) + : WebDavSynchronizer(resourceContext, KDAV2::CardDav, + ApplicationDomain::getTypeName(), + ApplicationDomain::getTypeName()) + {} + QByteArray createAddressbook(const QString &addressbookName, const QString &addressbookPath, const QString &parentAddressbookRid) + { + SinkTrace() << "Creating addressbook: " << addressbookName << parentAddressbookRid; + const auto remoteId = addressbookPath.toUtf8(); + const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; + Sink::ApplicationDomain::Addressbook addressbook; + addressbook.setName(addressbookName); + QHash mergeCriteria; + + if (!parentAddressbookRid.isEmpty()) { + addressbook.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentAddressbookRid.toUtf8())); + } + createOrModify(bufferType, remoteId, addressbook, mergeCriteria); + return remoteId; + } + +protected: + void updateLocalCollections(KDAV2::DavCollection::List addressbookList) Q_DECL_OVERRIDE + { + const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; + SinkTrace() << "Found" << addressbookList.size() << "addressbooks"; + + for (const auto &f : addressbookList) { + const auto &rid = resourceID(f); + SinkLog() << "Found addressbook:" << rid << f.displayName(); + createAddressbook(f.displayName(), rid, ""); + } + } + + void updateLocalItem(KDAV2::DavItem remoteContact, const QByteArray &addressbookLocalId) Q_DECL_OVERRIDE + { + Sink::ApplicationDomain::Contact localContact; + + localContact.setVcard(remoteContact.data()); + localContact.setAddressbook(addressbookLocalId); + + QHash mergeCriteria; + createOrModify(ENTITY_TYPE_CONTACT, resourceID(remoteContact), localContact, mergeCriteria); + } + + QByteArray collectionLocalResourceID(const KDAV2::DavCollection &addressbook) Q_DECL_OVERRIDE + { + return syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, resourceID(addressbook)); + } + + KAsync::Job replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + return KAsync::null(); + } + + KAsync::Job replay(const ApplicationDomain::Addressbook &addressbook, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + return KAsync::null(); + } +}; + + +CardDavResource::CardDavResource(const Sink::ResourceContext &resourceContext) + : Sink::GenericResource(resourceContext) +{ + auto synchronizer = QSharedPointer::create(resourceContext); + setupSynchronizer(synchronizer); + + setupPreprocessors(ENTITY_TYPE_CONTACT, QVector() << new ContactPropertyExtractor); +} + + +CardDavResourceFactory::CardDavResourceFactory(QObject *parent) + : Sink::ResourceFactory(parent, + {Sink::ApplicationDomain::ResourceCapabilities::Contact::contact, + Sink::ApplicationDomain::ResourceCapabilities::Contact::addressbook, + Sink::ApplicationDomain::ResourceCapabilities::Contact::storage + } + ) +{ +} + +Sink::Resource *CardDavResourceFactory::createResource(const ResourceContext &context) +{ + return new CardDavResource(context); +} + +void CardDavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) +{ + factory.registerFacade>(name); + factory.registerFacade>(name); +} + +void CardDavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) +{ + registry.registerFactory>(name); + registry.registerFactory>(name); +} + +void CardDavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) +{ + CardDavResource::removeFromDisk(instanceIdentifier); +} diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp deleted file mode 100644 --- a/examples/davresource/davresource.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2015 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 "davresource.h" - -#include "facade.h" -#include "resourceconfig.h" -#include "log.h" -#include "definitions.h" -#include "synchronizer.h" -#include "inspector.h" - -#include "facadefactory.h" -#include "adaptorfactoryregistry.h" - -#include "contactpreprocessor.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -//This is the resources entity type, and not the domain type -#define ENTITY_TYPE_CONTACT "contact" -#define ENTITY_TYPE_ADDRESSBOOK "addressbook" - -using namespace Sink; - -static int translateDavError(KJob *job) -{ - const int responseCode = static_cast(job)->latestResponseCode(); - - switch (responseCode) { - case QNetworkReply::HostNotFoundError: - return ApplicationDomain::NoServerError; - //Since we don't login we will just not have the necessary permissions ot view the object - case QNetworkReply::OperationCanceledError: - return ApplicationDomain::LoginError; - } - return ApplicationDomain::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() << static_cast(job)->latestResponseCode(); - future.setError(translateDavError(job), job->errorString()); - } else { - future.setFinished(); - } - }); - SinkTrace() << "Starting job: " << job->metaObject()->className(); - job->start(); - }); -} - -class ContactSynchronizer : public Sink::Synchronizer { -public: - ContactSynchronizer(const Sink::ResourceContext &resourceContext) - : Sink::Synchronizer(resourceContext) - { - - } - - QByteArray createAddressbook(const QString &addressbookName, const QString &addressbookPath, const QString &parentAddressbookRid) - { - SinkTrace() << "Creating addressbook: " << addressbookName << parentAddressbookRid; - const auto remoteId = addressbookPath.toUtf8(); - const auto bufferType = ENTITY_TYPE_ADDRESSBOOK; - Sink::ApplicationDomain::Addressbook addressbook; - addressbook.setName(addressbookName); - QHash mergeCriteria; - - if (!parentAddressbookRid.isEmpty()) { - addressbook.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, parentAddressbookRid.toUtf8())); - } - createOrModify(bufferType, remoteId, addressbook, mergeCriteria); - return remoteId; - } - - void synchronizeAddressbooks(const KDAV2::DavCollection::List &addressbookList) - { - const QByteArray bufferType = ENTITY_TYPE_ADDRESSBOOK; - SinkTrace() << "Found addressbooks " << addressbookList.size(); - - QVector ridList; - for(const auto &f : addressbookList) { - const auto &rid = getRid(f); - SinkLog() << "Found addressbook:" << rid << f.displayName(); - ridList.append(rid); - createAddressbook(f.displayName(), rid, ""); - } - - scanForRemovals(bufferType, - [&ridList](const QByteArray &remoteId) -> bool { - return ridList.contains(remoteId); - } - ); - } - - QList getSyncRequests(const Sink::QueryBase &query) Q_DECL_OVERRIDE - { - QList list; - if (!query.type().isEmpty()) { - //We want to synchronize something specific - list << Synchronizer::SyncRequest{query}; - } else { - //We want to synchronize everything - list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName())}; - list << Synchronizer::SyncRequest{Sink::QueryBase(ApplicationDomain::getTypeName())}; - } - return list; - } - - static QByteArray getRid(const KDAV2::DavItem &item) - { - return item.url().toDisplayString().toUtf8(); - } - - static QByteArray getRid(const KDAV2::DavCollection &item) - { - return item.url().toDisplayString().toUtf8(); - } - - KAsync::Job synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE - { - if (query.type() == ApplicationDomain::getTypeName()) { - SinkLogCtx(mLogCtx) << "Synchronizing addressbooks:" << resourceUrl().url(); - auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(resourceUrl()); - auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] (const KAsync::Error &error) { - if (error) { - SinkWarningCtx(mLogCtx) << "Failed to synchronize addressbooks." << collectionsFetchJob->errorString(); - } else { - synchronizeAddressbooks(collectionsFetchJob->collections()); - } - }); - return job; - } else if (query.type() == ApplicationDomain::getTypeName()) { - SinkLogCtx(mLogCtx) << "Synchronizing contacts."; - auto ridList = QSharedPointer::create(); - auto total = QSharedPointer::create(0); - auto progress = QSharedPointer::create(0); - auto collectionsFetchJob = new KDAV2::DavCollectionsFetchJob(resourceUrl()); - auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob] { - synchronizeAddressbooks(collectionsFetchJob ->collections()); - return collectionsFetchJob->collections(); - }) - .serialEach([=](const KDAV2::DavCollection &collection) { - auto collId = getRid(collection); - const auto addressbookLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_ADDRESSBOOK, collId); - auto ctag = collection.CTag().toLatin1(); - if (ctag != syncStore().readValue(collId + "_ctagXX")) { - SinkTraceCtx(mLogCtx) << "Syncing " << collId; - auto cache = std::shared_ptr(new KDAV2::EtagCache()); - auto davItemsListJob = new KDAV2::DavItemsListJob(collection.url(), cache); - QHash mergeCriteria; - auto colljob = runJob(davItemsListJob).then([=] { - const auto items = davItemsListJob->items(); - *total = items.size(); - return KAsync::value(items); - }) - .serialEach([=] (const KDAV2::DavItem &item) { - QByteArray rid = getRid(item); - if (item.etag().toLatin1() != syncStore().readValue(rid + "_etag")){ - SinkTrace() << "Updating " << rid; - auto davItemFetchJob = new KDAV2::DavItemFetchJob(item); - auto itemjob = runJob(davItemFetchJob) - .then([=] { - const auto item = davItemFetchJob->item(); - const auto rid = getRid(item); - Sink::ApplicationDomain::Contact contact; - contact.setVcard(item.data()); - contact.setAddressbook(addressbookLocalId); - createOrModify(ENTITY_TYPE_CONTACT, rid, contact, mergeCriteria); - return item; - }) - .then([=] (const KDAV2::DavItem &item) { - const auto rid = getRid(item); - syncStore().writeValue(rid + "_etag", item.etag().toLatin1()); - ridList->append(rid); - *progress += 1; - reportProgress(*progress, *total, QByteArrayList{} << addressbookLocalId); - //commit every 5 contacts (so contacts start appearing in the UI) - if ((*progress % 5) == 0) { - commit(); - } - return rid; - }); - return itemjob; - } else { - ridList->append(rid); - return KAsync::value(rid); - } - }) - .then([=] () { - syncStore().writeValue(collId + "_ctag", ctag); - }); - return colljob; - } else { - SinkTraceCtx(mLogCtx) << "Collection unchanged: " << ctag; - // for(const auto &item : addressbook) { - // ridList->append(rid); - // } - return KAsync::null(); - } - }) - .then([this, ridList] () { - scanForRemovals(ENTITY_TYPE_CONTACT, - [&ridList](const QByteArray &remoteId) -> bool { - return ridList->contains(remoteId); - }); - }); - return job; - } else { - return KAsync::null(); - } - } - -KAsync::Job replay(const ApplicationDomain::Contact &contact, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE - { - return KAsync::null(); - } - - KAsync::Job replay(const ApplicationDomain::Addressbook &addressbook, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE - { - return KAsync::null(); - } - - KDAV2::DavUrl resourceUrl() const - { - if (secret().isEmpty()) { - return {}; - } - auto resourceUrl = mServer; - resourceUrl.setUserName(mUsername); - resourceUrl.setPassword(secret()); - return KDAV2::DavUrl{resourceUrl, KDAV2::CardDav}; - } - -public: - QUrl mServer; - QString mUsername; -}; - - -DavResource::DavResource(const Sink::ResourceContext &resourceContext) - : Sink::GenericResource(resourceContext) -{ - auto config = ResourceConfig::getConfiguration(resourceContext.instanceId()); - auto server = QUrl::fromUserInput(config.value("server").toString()); - auto username = config.value("username").toString(); - - auto synchronizer = QSharedPointer::create(resourceContext); - synchronizer->mServer = server; - synchronizer->mUsername = username; - setupSynchronizer(synchronizer); - - setupPreprocessors(ENTITY_TYPE_CONTACT, QVector() << new ContactPropertyExtractor); -} - - -DavResourceFactory::DavResourceFactory(QObject *parent) - : Sink::ResourceFactory(parent, - {Sink::ApplicationDomain::ResourceCapabilities::Contact::contact, - Sink::ApplicationDomain::ResourceCapabilities::Contact::addressbook, - Sink::ApplicationDomain::ResourceCapabilities::Contact::storage - } - ) -{ -} - -Sink::Resource *DavResourceFactory::createResource(const ResourceContext &context) -{ - return new DavResource(context); -} - -void DavResourceFactory::registerFacades(const QByteArray &name, Sink::FacadeFactory &factory) -{ - factory.registerFacade>(name); - factory.registerFacade>(name); -} - -void DavResourceFactory::registerAdaptorFactories(const QByteArray &name, Sink::AdaptorFactoryRegistry ®istry) -{ - registry.registerFactory>(name); - registry.registerFactory>(name); -} - -void DavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) -{ - DavResource::removeFromDisk(instanceIdentifier); -} diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp --- a/examples/dummyresource/resourcefactory.cpp +++ b/examples/dummyresource/resourcefactory.cpp @@ -54,12 +54,10 @@ Sink::ApplicationDomain::Event::Ptr createEvent(const QByteArray &ridBuffer, const QMap &data) { - static uint8_t rawData[100]; auto event = Sink::ApplicationDomain::Event::Ptr::create(); event->setSummary(data.value("summary").toString()); event->setProperty("remoteId", ridBuffer); event->setDescription(data.value("description").toString()); - event->setAttachment(QByteArray::fromRawData(reinterpret_cast(rawData), 100)); return event; } diff --git a/examples/webdavcommon/CMakeLists.txt b/examples/webdavcommon/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/examples/webdavcommon/CMakeLists.txt @@ -0,0 +1,8 @@ +project(sink_webdav_common) + +set(CMAKE_CXX_STANDARD 14) + +find_package(KPimKDAV2 REQUIRED) + +add_library(${PROJECT_NAME} STATIC webdav.cpp) +target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network KPim::KDAV2) diff --git a/examples/webdavcommon/webdav.h b/examples/webdavcommon/webdav.h new file mode 100644 --- /dev/null +++ b/examples/webdavcommon/webdav.h @@ -0,0 +1,78 @@ +/* + * 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 "synchronizer.h" + +#include +#include +#include + +class WebDavSynchronizer : public Sink::Synchronizer +{ +public: + WebDavSynchronizer(const Sink::ResourceContext &, KDAV2::Protocol, QByteArray collectionName, + QByteArray itemName); + + QList getSyncRequests(const Sink::QueryBase &query) Q_DECL_OVERRIDE; + KAsync::Job synchronizeWithSource(const Sink::QueryBase &query) Q_DECL_OVERRIDE; + +protected: + /** + * Called with the list of discovered collections. It's purpose should be + * adding the said collections to the store. + */ + virtual void updateLocalCollections(KDAV2::DavCollection::List collections) = 0; + + /** + * Called when discovering a new item, or when an item has been modified. + * It's purpose should be adding the said item to the store. + * + * `collectionLocalRid` is the local resource id of the collection the item + * is in. + */ + virtual void updateLocalItem(KDAV2::DavItem item, const QByteArray &collectionLocalRid) = 0; + + /** + * Get the local resource id from a collection. + */ + virtual QByteArray collectionLocalResourceID(const KDAV2::DavCollection &collection) = 0; + + KAsync::Job synchronizeCollection(const KDAV2::DavCollection &, + QSharedPointer progress, QSharedPointer total, QSharedPointer> itemsResourceIDs); + KAsync::Job synchronizeItem(const KDAV2::DavItem &, const QByteArray &collectionLocalRid, + QSharedPointer progress, QSharedPointer total); + + static QByteArray resourceID(const KDAV2::DavCollection &); + static QByteArray resourceID(const KDAV2::DavItem &); + + bool unchanged(const KDAV2::DavCollection &); + bool unchanged(const KDAV2::DavItem &); + + KDAV2::DavUrl serverUrl() const; + +private: + KDAV2::Protocol protocol; + const QByteArray collectionName; + const QByteArray itemName; + + QUrl server; + QString username; +}; diff --git a/examples/webdavcommon/webdav.cpp b/examples/webdavcommon/webdav.cpp new file mode 100644 --- /dev/null +++ b/examples/webdavcommon/webdav.cpp @@ -0,0 +1,277 @@ +/* + * 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 + +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(QSharedPointer job) +{ + return KAsync::start([job(std::move(job))] { + if (job->exec()) { + SinkTrace() << "Job exec success"; + } else { + SinkTrace() << "Job exec failure"; + } + }); + + // For some reason, this code doesn't work + + /* + 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() << static_cast(job)->latestResponseCode(); + future.setError(translateDavError(job), job->errorString()); + } else { + future.setFinished(); + } + }); + SinkTrace() << "Starting job: " << job->metaObject()->className(); + job->start(); + }); + */ +} + +WebDavSynchronizer::WebDavSynchronizer(const Sink::ResourceContext &context, + KDAV2::Protocol protocol, QByteArray collectionName, QByteArray itemName) + : Sink::Synchronizer(context), + protocol(protocol), + collectionName(std::move(collectionName)), + itemName(std::move(itemName)) +{ + auto config = ResourceConfig::getConfiguration(context.instanceId()); + + server = QUrl::fromUserInput(config.value("server").toString()); + username = 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(collectionName) }; + list << Synchronizer::SyncRequest{ Sink::QueryBase(itemName) }; + } + return list; +} + +KAsync::Job WebDavSynchronizer::synchronizeWithSource(const Sink::QueryBase &query) +{ + if (query.type() != collectionName && query.type() != itemName) { + return KAsync::null(); + } + + SinkLog() << "Synchronizing" << query.type() << "through WebDAV at:" << serverUrl().url(); + + auto collectionsFetchJob = QSharedPointer::create(serverUrl()); + + auto job = runJob(collectionsFetchJob).then([this, collectionsFetchJob](const KAsync::Error &error) { + if (error) { + SinkWarning() << "Failed to synchronize collections:" << collectionsFetchJob->errorString(); + } else { + updateLocalCollections(collectionsFetchJob->collections()); + } + + return collectionsFetchJob->collections(); + }); + + if (query.type() == collectionName) { + // Do nothing more + return job; + } else if (query.type() == itemName) { + 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(); + + // Same but for items. + // Quirk: may contain a collection Id (see below) + auto itemsResourceIDs = QSharedPointer>::create(); + + return job + .serialEach([this, progress(std::move(progress)), total(std::move(total)), collectionResourceIDs, + itemsResourceIDs](const KDAV2::DavCollection &collection) { + auto collectionResourceID = resourceID(collection); + + collectionResourceIDs->insert(collectionResourceID); + + if (unchanged(collection)) { + SinkTrace() << "Collection unchanged:" << collectionResourceID; + + // It seems that doing this prevent the items in the + // collection to be removed when doing scanForRemovals + // below (since the collection is unchanged, we do not go + // through all of its items). + // Behaviour copied from the previous code. + itemsResourceIDs->insert(collectionResourceID); + + return KAsync::null(); + } + + SinkTrace() << "Syncing collection:" << collectionResourceID; + return synchronizeCollection(collection, progress, total, itemsResourceIDs); + }) + .then([this, collectionResourceIDs(std::move(collectionResourceIDs)), + itemsResourceIDs(std::move(itemsResourceIDs))]() { + scanForRemovals(collectionName, [&collectionResourceIDs](const QByteArray &remoteId) { + return collectionResourceIDs->contains(remoteId); + }); + scanForRemovals(itemName, [&itemsResourceIDs](const QByteArray &remoteId) { + return itemsResourceIDs->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 localRid = collectionLocalResourceID(collection); + + // The ETag cache is useless here, since `sinkStore()` IS the cache. + auto cache = std::make_shared(); + auto davItemsListJob = QSharedPointer::create(collection.url(), std::move(cache)); + + return runJob(davItemsListJob) + .then([this, davItemsListJob, total] { + auto items = davItemsListJob->items(); + *total += items.size(); + return KAsync::value(items); + }) + .serialEach([this, collectionRid, localRid, 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(); + } + + SinkTrace() << "Syncing item:" << itemRid; + return synchronizeItem(item, localRid, progress, total); + }) + .then([this, collectionRid, ctag] { + // Update the local CTag to be able to tell if the collection is unchanged + syncStore().writeValue(collectionRid + "_ctag", ctag); + }); +} + +KAsync::Job WebDavSynchronizer::synchronizeItem(const KDAV2::DavItem &item, + const QByteArray &collectionLocalRid, QSharedPointer progress, QSharedPointer total) +{ + auto etag = item.etag().toLatin1(); + + auto itemFetchJob = QSharedPointer::create(item); + return runJob(itemFetchJob) + .then([this, itemFetchJob(std::move(itemFetchJob)), collectionLocalRid] { + auto item = itemFetchJob->item(); + updateLocalItem(item, collectionLocalRid); + return item; + }) + .then([this, etag, progress(std::move(progress)), total(std::move(total))](const KDAV2::DavItem &item) { + // Update the local ETag to be able to tell if the item is unchanged + syncStore().writeValue(resourceID(item) + "_etag", etag); + + *progress += 1; + reportProgress(*progress, *total); + if ((*progress % 5) == 0) { + commit(); + } + }); +} + +QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavCollection &collection) +{ + return collection.url().toDisplayString().toUtf8(); +} + +QByteArray WebDavSynchronizer::resourceID(const KDAV2::DavItem &item) +{ + return item.url().toDisplayString().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 = server; + result.setUserName(username); + result.setPassword(secret()); + + return KDAV2::DavUrl{ result, protocol }; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,7 +58,6 @@ entitystoretest upgradetest ) -generate_flatbuffers(sink_test calendar) target_link_libraries(dummyresourcetest sink_resource_dummy) target_link_libraries(dummyresourcebenchmark sink_resource_dummy) target_link_libraries(dummyresourcewritebenchmark sink_resource_dummy) diff --git a/tests/calendar.fbs b/tests/calendar.fbs deleted file mode 100644 --- a/tests/calendar.fbs +++ /dev/null @@ -1,12 +0,0 @@ -// example IDL file - -namespace Calendar; - -table Event { - summary:string; - description:string; - attachment:[ubyte]; -} - -root_type Event; -file_identifier "AKFB"; diff --git a/tests/domainadaptortest.cpp b/tests/domainadaptortest.cpp --- a/tests/domainadaptortest.cpp +++ b/tests/domainadaptortest.cpp @@ -76,12 +76,10 @@ auto summary = m_fbb.CreateString("summary1"); auto description = m_fbb.CreateString("description"); static uint8_t rawData[100]; - auto attachment = m_fbb.CreateVector(rawData, 100); auto builder = Sink::ApplicationDomain::Buffer::EventBuilder(m_fbb); builder.add_summary(summary); builder.add_description(description); - builder.add_attachment(attachment); auto buffer = builder.Finish(); Sink::ApplicationDomain::Buffer::FinishEventBuffer(m_fbb, buffer); diff --git a/tests/storagebenchmark.cpp b/tests/storagebenchmark.cpp --- a/tests/storagebenchmark.cpp +++ b/tests/storagebenchmark.cpp @@ -1,6 +1,6 @@ #include -#include "calendar_generated.h" +#include "event_generated.h" #include "hawd/dataset.h" #include "hawd/formatter.h" @@ -13,7 +13,7 @@ #include #include -using namespace Calendar; +using namespace Sink::ApplicationDomain::Buffer; using namespace flatbuffers; static QByteArray createEvent() @@ -25,13 +25,10 @@ { uint8_t *rawDataPtr = Q_NULLPTR; auto summary = fbb.CreateString("summary"); - auto data = fbb.CreateUninitializedVector(attachmentSize, &rawDataPtr); - // auto data = fbb.CreateVector(rawData, attachmentSize); - Calendar::EventBuilder eventBuilder(fbb); + EventBuilder eventBuilder(fbb); eventBuilder.add_summary(summary); - eventBuilder.add_attachment(data); auto eventLocation = eventBuilder.Finish(); - Calendar::FinishEventBuffer(fbb, eventLocation); + FinishEventBuffer(fbb, eventLocation); memcpy((void *)rawDataPtr, rawData, attachmentSize); }