diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 76579dd4..9c4d4f17 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,156 +1,157 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(domain) ecm_setup_version(${sink_VERSION} VARIABLE_PREFIX Sink VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/sink_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/SinkConfigVersion.cmake" SOVERSION 0 ) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/Sink") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/SinkConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/SinkConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/SinkConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/SinkConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) add_definitions("-fvisibility=hidden") install(EXPORT SinkTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE SinkTargets.cmake) set(storage_SRCS storage_lmdb.cpp) set(command_SRCS store.cpp secretstore.cpp notifier.cpp resourcecontrol.cpp modelresult.cpp definitions.cpp log.cpp entitybuffer.cpp facadefactory.cpp commands.cpp facade.cpp pipeline.cpp propertymapper.cpp domainadaptor.cpp resource.cpp genericresource.cpp resourceaccess.cpp queryrunner.cpp listener.cpp storage_common.cpp threadboundary.cpp messagequeue.cpp index.cpp typeindex.cpp resourcefacade.cpp resourceconfig.cpp configstore.cpp resultset.cpp domain/propertyregistry.cpp domain/applicationdomaintype.cpp domain/typeimplementations.cpp test.cpp query.cpp changereplay.cpp adaptorfactoryregistry.cpp synchronizer.cpp synchronizerstore.cpp contactpreprocessor.cpp mailpreprocessor.cpp specialpurposepreprocessor.cpp datastorequery.cpp storage/entitystore.cpp indexer.cpp mail/threadindexer.cpp mail/fulltextindexer.cpp notification.cpp commandprocessor.cpp inspector.cpp propertyparser.cpp utils.cpp fulltextindex.cpp ${storage_SRCS}) add_library(${PROJECT_NAME} SHARED ${command_SRCS}) generate_flatbuffers( ${PROJECT_NAME} commands/commandcompletion commands/createentity commands/deleteentity commands/handshake commands/modifyentity commands/revisionupdate commands/synchronize commands/notification commands/revisionreplayed commands/inspection commands/flush commands/secret domain/contact domain/addressbook domain/event + domain/calendar domain/mail domain/folder domain/dummy entity metadata queuedcommand ) generate_export_header(${PROJECT_NAME} BASE_NAME Sink EXPORT_FILE_NAME sink_export.h) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX VERSION ${Sink_VERSION} SOVERSION ${Sink_SOVERSION} EXPORT_NAME ${PROJECT_NAME} ) target_link_libraries(${PROJECT_NAME} PUBLIC KAsync Qt5::Network PRIVATE ${LMDB_LIBRARIES} Qt5::Gui KF5::Mime KF5::Contacts ${XAPIAN_LIBRARIES} ) install(TARGETS ${PROJECT_NAME} EXPORT SinkTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) add_clang_static_analysis(${PROJECT_NAME}) install(FILES store.h notifier.h resourcecontrol.h domain/applicationdomaintype.h query.h standardqueries.h inspection.h notification.h bufferadaptor.h test.h log.h flush.h secretstore.h ${CMAKE_CURRENT_BINARY_DIR}/sink_export.h DESTINATION ${INCLUDE_INSTALL_DIR}/${PROJECT_NAME} COMPONENT Devel ) diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp index ff2990dc..9a213dd5 100644 --- a/common/domain/applicationdomaintype.cpp +++ b/common/domain/applicationdomaintype.cpp @@ -1,436 +1,444 @@ /* * Copyright (C) 2014 Christian Mollekopf * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "applicationdomaintype.h" #include "log.h" #include "../bufferadaptor.h" #include "definitions.h" #include "propertyregistry.h" #include "storage.h" //for generateUid() #include "utils.h" //for generateUid() #include QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::Mail::Contact &c) { d << "Contact(" << c.name << ", " << c.emailAddress << ")"; return d; } QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::ApplicationDomainType &type) { d << "ApplicationDomainType(\n"; auto properties = [&] { if (!type.changedProperties().isEmpty()) { return type.changedProperties(); } else { return type.mAdaptor->availableProperties(); } }(); std::sort(properties.begin(), properties.end()); d << " " << "Id: " << "\t" << type.identifier() << "\n"; if (type.isAggregate()) { d << " " << "AggregateIds: " << "\t" << type.aggregatedIds() << "\n"; } d << " " << "Resource: " << "\t" << type.resourceInstanceIdentifier() << "\n"; for (const auto &property : properties) { d << " " << property << "\t" << type.getProperty(property) << "\n"; } d << ")"; return d; } QDebug Sink::ApplicationDomain::operator<< (QDebug d, const Sink::ApplicationDomain::Reference &ref) { d << ref.value; return d; } template int registerProperty() { Sink::Private::PropertyRegistry::instance().registerProperty(Sink::ApplicationDomain::getTypeName()); return 0; } #define SINK_REGISTER_ENTITY(ENTITY) \ constexpr const char *ENTITY::name; #define SINK_REGISTER_PROPERTY(ENTITYTYPE, PROPERTY) \ constexpr const char *ENTITYTYPE::PROPERTY::name; \ static int foo##ENTITYTYPE##PROPERTY = registerProperty(); namespace Sink { namespace ApplicationDomain { constexpr const char *SinkResource::name; constexpr const char *SinkAccount::name; SINK_REGISTER_ENTITY(Mail); SINK_REGISTER_PROPERTY(Mail, Sender); SINK_REGISTER_PROPERTY(Mail, To); SINK_REGISTER_PROPERTY(Mail, Cc); SINK_REGISTER_PROPERTY(Mail, Bcc); SINK_REGISTER_PROPERTY(Mail, Subject); SINK_REGISTER_PROPERTY(Mail, Date); SINK_REGISTER_PROPERTY(Mail, Unread); SINK_REGISTER_PROPERTY(Mail, Important); SINK_REGISTER_PROPERTY(Mail, Folder); SINK_REGISTER_PROPERTY(Mail, MimeMessage); SINK_REGISTER_PROPERTY(Mail, FullPayloadAvailable); SINK_REGISTER_PROPERTY(Mail, Draft); SINK_REGISTER_PROPERTY(Mail, Trash); SINK_REGISTER_PROPERTY(Mail, Sent); SINK_REGISTER_PROPERTY(Mail, MessageId); SINK_REGISTER_PROPERTY(Mail, ParentMessageId); SINK_REGISTER_PROPERTY(Mail, ThreadId); SINK_REGISTER_ENTITY(Folder); SINK_REGISTER_PROPERTY(Folder, Name); SINK_REGISTER_PROPERTY(Folder, Icon); SINK_REGISTER_PROPERTY(Folder, SpecialPurpose); SINK_REGISTER_PROPERTY(Folder, Enabled); SINK_REGISTER_PROPERTY(Folder, Parent); SINK_REGISTER_PROPERTY(Folder, Count); SINK_REGISTER_PROPERTY(Folder, FullContentAvailable); SINK_REGISTER_ENTITY(Contact); SINK_REGISTER_PROPERTY(Contact, Uid); SINK_REGISTER_PROPERTY(Contact, Fn); SINK_REGISTER_PROPERTY(Contact, Firstname); SINK_REGISTER_PROPERTY(Contact, Lastname); SINK_REGISTER_PROPERTY(Contact, Emails); SINK_REGISTER_PROPERTY(Contact, Vcard); SINK_REGISTER_PROPERTY(Contact, Addressbook); SINK_REGISTER_PROPERTY(Contact, Photo); SINK_REGISTER_ENTITY(Addressbook); SINK_REGISTER_PROPERTY(Addressbook, Name); SINK_REGISTER_PROPERTY(Addressbook, Parent); SINK_REGISTER_PROPERTY(Addressbook, LastUpdated); static const int foo = [] { QMetaType::registerEqualsComparator(); QMetaType::registerDebugStreamOperator(); QMetaType::registerConverter(); QMetaType::registerDebugStreamOperator(); qRegisterMetaTypeStreamOperators(); return 0; }(); void copyBuffer(Sink::ApplicationDomain::BufferAdaptor &buffer, Sink::ApplicationDomain::BufferAdaptor &memoryAdaptor, const QList &properties, bool pruneReferences) { auto propertiesToCopy = properties; if (properties.isEmpty()) { propertiesToCopy = buffer.availableProperties(); } for (const auto &property : propertiesToCopy) { const auto value = buffer.getProperty(property); if (pruneReferences && value.canConvert()) { continue; } else { memoryAdaptor.setProperty(property, value); } } } ApplicationDomainType::ApplicationDomainType() :mAdaptor(new MemoryBufferAdaptor()), mChangeSet(new QSet()) { } ApplicationDomainType::ApplicationDomainType(const QByteArray &resourceInstanceIdentifier) :mAdaptor(new MemoryBufferAdaptor()), mChangeSet(new QSet()), mResourceInstanceIdentifier(resourceInstanceIdentifier) { } ApplicationDomainType::ApplicationDomainType(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor) : mAdaptor(adaptor), mChangeSet(new QSet()), mResourceInstanceIdentifier(resourceInstanceIdentifier), mIdentifier(identifier), mRevision(revision) { } ApplicationDomainType::ApplicationDomainType(const ApplicationDomainType &other) : mChangeSet(new QSet()) { *this = other; } ApplicationDomainType& ApplicationDomainType::operator=(const ApplicationDomainType &other) { mAdaptor = other.mAdaptor; if (other.mChangeSet) { *mChangeSet = *other.mChangeSet; } mResourceInstanceIdentifier = other.mResourceInstanceIdentifier; mIdentifier = other.mIdentifier; mRevision = other.mRevision; mAggreatedIds = other.mAggreatedIds; return *this; } ApplicationDomainType::~ApplicationDomainType() { } QByteArray ApplicationDomainType::generateUid() { return Sink::Storage::DataStore::generateUid(); } bool ApplicationDomainType::hasProperty(const QByteArray &key) const { Q_ASSERT(mAdaptor); return mAdaptor->availableProperties().contains(key); } QVariant ApplicationDomainType::getProperty(const QByteArray &key) const { Q_ASSERT(mAdaptor); return mAdaptor->getProperty(key); } QVariantList ApplicationDomainType::getCollectedProperty(const QByteArray &key) const { Q_ASSERT(mAdaptor); return mAdaptor->getProperty(key + "Collected").toList(); } void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value) { Q_ASSERT(mAdaptor); if (!isAggregate()) { auto existing = mAdaptor->getProperty(key); if (existing.isValid() && existing == value ) { SinkTrace() << "Tried to set property that is still the same: " << key << value; return; } } mChangeSet->insert(key); mAdaptor->setProperty(key, value); } void ApplicationDomainType::setResource(const QByteArray &identifier) { mResourceInstanceIdentifier = identifier; } void ApplicationDomainType::setProperty(const QByteArray &key, const ApplicationDomainType &value) { Q_ASSERT(!value.identifier().isEmpty()); setProperty(key, QVariant::fromValue(Reference{value.identifier()})); } void ApplicationDomainType::setChangedProperties(const QSet &changeset) { *mChangeSet = changeset; } QByteArrayList ApplicationDomainType::changedProperties() const { return mChangeSet->toList(); } QByteArrayList ApplicationDomainType::availableProperties() const { Q_ASSERT(mAdaptor); return mAdaptor->availableProperties(); } qint64 ApplicationDomainType::revision() const { return mRevision; } QByteArray ApplicationDomainType::resourceInstanceIdentifier() const { return mResourceInstanceIdentifier; } QByteArray ApplicationDomainType::identifier() const { return mIdentifier; } bool ApplicationDomainType::isAggregate() const { return mAggreatedIds.size() > 1; } QVector ApplicationDomainType::aggregatedIds() const { return mAggreatedIds; } QVector &ApplicationDomainType::aggregatedIds() { return mAggreatedIds; } int ApplicationDomainType::count() const { return qMax(mAggreatedIds.size(), 1); } SinkResource::SinkResource(const QByteArray &identifier) : ApplicationDomainType("", identifier, 0, QSharedPointer(new MemoryBufferAdaptor())) { } SinkResource::SinkResource(const QByteArray &, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor) : ApplicationDomainType("", identifier, 0, adaptor) { } SinkResource::SinkResource() : ApplicationDomainType() { } SinkResource::~SinkResource() { } SinkAccount::SinkAccount(const QByteArray &identifier) : ApplicationDomainType("", identifier, 0, QSharedPointer(new MemoryBufferAdaptor())) { } SinkAccount::SinkAccount(const QByteArray &, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor) : ApplicationDomainType("", identifier, 0, adaptor) { } SinkAccount::SinkAccount() : ApplicationDomainType() { } SinkAccount::~SinkAccount() { } Identity::Identity(const QByteArray &identifier) : ApplicationDomainType("", identifier, 0, QSharedPointer(new MemoryBufferAdaptor())) { } Identity::Identity(const QByteArray &, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor) : ApplicationDomainType("", identifier, 0, adaptor) { } Identity::Identity() : ApplicationDomainType() { } Identity::~Identity() { } SinkResource DummyResource::create(const QByteArray &account) { auto &&resource = ApplicationDomainType::createEntity(); resource.setResourceType("sink.dummy"); resource.setAccount(account); return resource; } SinkResource MaildirResource::create(const QByteArray &account) { auto &&resource = ApplicationDomainType::createEntity(); resource.setResourceType("sink.maildir"); resource.setAccount(account); return resource; } SinkResource MailtransportResource::create(const QByteArray &account) { auto &&resource = ApplicationDomainType::createEntity(); resource.setResourceType("sink.mailtransport"); resource.setAccount(account); return resource; } SinkResource ImapResource::create(const QByteArray &account) { auto &&resource = ApplicationDomainType::createEntity(); resource.setResourceType("sink.imap"); resource.setAccount(account); return resource; } 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; } QByteArrayList getTypeNames() { static QByteArrayList types; if (types.isEmpty()) { #define REGISTER_TYPE(TYPE) \ types << ApplicationDomain::getTypeName(); SINK_REGISTER_TYPES() #undef REGISTER_TYPE } return types; } bool isGlobalType(const QByteArray &type) { if (type == ApplicationDomain::getTypeName() || type == ApplicationDomain::getTypeName() || type == ApplicationDomain::getTypeName()) { return true; } return false; } } } QDataStream &operator<<(QDataStream &out, const Sink::ApplicationDomain::Reference &reference) { out << reference.value; return out; } QDataStream &operator>>(QDataStream &in, Sink::ApplicationDomain::Reference &reference) { in >> reference.value; return in; } diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h index d05e981a..e05acaa9 100644 --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h @@ -1,571 +1,583 @@ /* * Copyright (C) 2014 Christian Mollekopf * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #pragma once #include "sink_export.h" #include #include #include #include #include #include "bufferadaptor.h" #define SINK_ENTITY(TYPE, LOWERCASENAME) \ static constexpr const char *name = #LOWERCASENAME; \ typedef QSharedPointer Ptr; \ using Entity::Entity; \ TYPE() = default; \ TYPE(const ApplicationDomainType &o) : Entity(o) {} \ virtual ~TYPE() = default; \ static TYPE create(const QByteArray &resource) { return createEntity(resource); }; \ #define SINK_PROPERTY(TYPE, NAME, LOWERCASENAME) \ struct NAME { \ static constexpr const char *name = #LOWERCASENAME; \ typedef TYPE Type; \ }; \ void set##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \ TYPE get##NAME() const { return getProperty(NAME::name).value(); } \ #define SINK_EXTRACTED_PROPERTY(TYPE, NAME, LOWERCASENAME) \ struct NAME { \ static constexpr const char *name = #LOWERCASENAME; \ typedef TYPE Type; \ }; \ void setExtracted##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \ TYPE get##NAME() const { return getProperty(NAME::name).value(); } \ #define SINK_STATUS_PROPERTY(TYPE, NAME, LOWERCASENAME) \ struct NAME { \ static constexpr const char *name = #LOWERCASENAME; \ typedef TYPE Type; \ }; \ void setStatus##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \ TYPE get##NAME() const { return getProperty(NAME::name).value(); } \ #define SINK_REFERENCE_PROPERTY(TYPE, NAME, LOWERCASENAME) \ struct NAME { \ static constexpr const char *name = #LOWERCASENAME; \ typedef Reference Type; \ typedef ApplicationDomain::TYPE ReferenceType; \ }; \ void set##NAME(const ApplicationDomain::TYPE &value) { setProperty(NAME::name, value); } \ void set##NAME(const QByteArray &value) { setProperty(NAME::name, QVariant::fromValue(Reference{value})); } \ QByteArray get##NAME() const { return getProperty(NAME::name).value().value; } \ #define SINK_INDEX_PROPERTY(TYPE, NAME, LOWERCASENAME) \ struct NAME { \ static constexpr const char *name = #LOWERCASENAME; \ typedef TYPE Type; \ }; \ namespace Sink { namespace ApplicationDomain { enum SINK_EXPORT ErrorCode { NoError = 0, UnknownError, NoServerError, ConnectionError, LoginError, ConfigurationError, TransmissionError, ConnectionLostError, MissingCredentialsError, ResourceCrashedError }; enum SINK_EXPORT SuccessCode { TransmissionSuccess }; enum SINK_EXPORT SyncStatus { NoSyncStatus, SyncInProgress, SyncError, SyncSuccess, NewContentAvailable }; /** * The status of an account or resource. * * It is set as follows: * * By default the status is no status. * * If a connection to the server failed the status is Offline. * * If a connection to the server could be established the status is Connected. * * If an error occurred that keeps the resource from operating (so non transient), the resource enters the error state. * * If a long running operation is started the resource goes to the busy state (and return to the previous state after that). */ enum SINK_EXPORT Status { NoStatus, OfflineStatus, ConnectedStatus, BusyStatus, ErrorStatus }; struct SINK_EXPORT Error { }; struct SINK_EXPORT Progress { }; /** * Internal type. * * Represents a reference to another entity in the same resource. */ struct Reference { Reference() = default; Reference(const Reference &) = default; Reference(const QByteArray &id) : value(id) {}; Reference(const char *id) : value(id) {}; ~Reference() = default; bool operator==(const Reference &other) const { return value == other.value; } operator QByteArray() const { return value; } QByteArray value; }; void copyBuffer(Sink::ApplicationDomain::BufferAdaptor &buffer, Sink::ApplicationDomain::BufferAdaptor &memoryAdaptor, const QList &properties = QList(), bool pruneReferences = false); /** * The domain type interface has two purposes: * * provide a unified interface to read buffers (for zero-copy reading) * * record changes to generate changesets for modifications * * ApplicationDomainTypes don't adhere to any standard and are meant to be extended frequently (hence the non-typesafe interface). */ class SINK_EXPORT ApplicationDomainType { public: typedef QSharedPointer Ptr; ApplicationDomainType(); explicit ApplicationDomainType(const QByteArray &resourceInstanceIdentifier); explicit ApplicationDomainType(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor); ApplicationDomainType(const ApplicationDomainType &other); ApplicationDomainType& operator=(const ApplicationDomainType &other); inline bool operator==(const ApplicationDomainType &other) { return other.identifier() == identifier(); } inline bool operator!=(const ApplicationDomainType &other) { return !(*this == other); } template DomainType cast() { static_assert(std::is_base_of::value, "You can only cast to base classes of ApplicationDomainType."); DomainType t = *this; t.mChangeSet = mChangeSet; return t; } /** * Returns an in memory representation of the same entity. */ template static typename DomainType::Ptr getInMemoryRepresentation(const ApplicationDomainType &domainType, const QList properties = QList()) { auto memoryAdaptor = QSharedPointer::create(); copyBuffer(*(domainType.mAdaptor), *memoryAdaptor, properties, false); //mIdentifier internally still refers to the memory-mapped memory, we need to copy the memory or it will become invalid return QSharedPointer::create(domainType.mResourceInstanceIdentifier, QByteArray(domainType.mIdentifier.constData(), domainType.mIdentifier.size()), domainType.mRevision, memoryAdaptor); } /** * Returns an in memory copy without id and resource set. */ template static typename DomainType::Ptr getInMemoryCopy(const ApplicationDomainType &domainType, const QList properties = QList()) { auto memoryAdaptor = QSharedPointer::create(); Q_ASSERT(domainType.mAdaptor); copyBuffer(*(domainType.mAdaptor), *memoryAdaptor, properties, true); return QSharedPointer::create(QByteArray{}, QByteArray{}, 0, memoryAdaptor); } static QByteArray generateUid(); template static DomainType createEntity() { DomainType object; object.mIdentifier = generateUid(); return object; } template static DomainType createEntity(const QByteArray &resourceInstanceIdentifier) { DomainType object(resourceInstanceIdentifier); object.mIdentifier = generateUid(); return object; } template static DomainType createEntity(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier) { if (identifier.isEmpty()) { return createEntity(resourceInstanceIdentifier); } DomainType object(resourceInstanceIdentifier); object.mIdentifier = identifier; return object; } template static DomainType createCopy(const QByteArray &identifier, const DomainType &original) { DomainType object(original); object.mIdentifier = identifier; //This shouldn't be necessary but is to avoid corrupt id's from Store::modify calls. object.mIdentifier.detach(); return object; } virtual ~ApplicationDomainType(); bool hasProperty(const QByteArray &key) const; QVariant getProperty(const QByteArray &key) const; QVariantList getCollectedProperty(const QByteArray &key) const; template QVariantList getCollectedProperty() const { return getCollectedProperty(Property::name); } /** * Set a property and record a changed property * * If the propery is available and did not change the call will be ignored. */ void setProperty(const QByteArray &key, const QVariant &value); /** * Convenience method to set a reference property. */ void setProperty(const QByteArray &key, const ApplicationDomainType &value); void setChangedProperties(const QSet &changeset); QByteArrayList changedProperties() const; QByteArrayList availableProperties() const; qint64 revision() const; QByteArray resourceInstanceIdentifier() const; void setResource(const QByteArray &identifier); QByteArray identifier() const; bool isAggregate() const; QVector aggregatedIds() const; QVector &aggregatedIds(); int count() const; private: friend QDebug operator<<(QDebug, const ApplicationDomainType &); QSharedPointer mAdaptor; QSharedPointer> mChangeSet; /* * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location. */ QByteArray mResourceInstanceIdentifier; QByteArray mIdentifier; qint64 mRevision; QVector mAggreatedIds; }; /* * Should this be specific to the synclistresultset, in other cases we may want to take revision and resource into account. */ inline bool operator==(const ApplicationDomainType& lhs, const ApplicationDomainType& rhs) { return lhs.identifier() == rhs.identifier() && lhs.resourceInstanceIdentifier() == rhs.resourceInstanceIdentifier(); } SINK_EXPORT QDebug operator<< (QDebug d, const ApplicationDomainType &type); SINK_EXPORT QDebug operator<< (QDebug d, const Reference &ref); struct SINK_EXPORT SinkAccount : public ApplicationDomainType { static constexpr const char *name = "account"; typedef QSharedPointer Ptr; explicit SinkAccount(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor); explicit SinkAccount(const QByteArray &identifier); SinkAccount(); virtual ~SinkAccount(); SINK_PROPERTY(QString, Name, name); SINK_PROPERTY(QString, Icon, icon); SINK_PROPERTY(QString, AccountType, type); SINK_STATUS_PROPERTY(int, Status, status); }; /** * Represents an sink resource. * * This type is used for configuration of resources, * and for creating and removing resource instances. */ struct SINK_EXPORT SinkResource : public ApplicationDomainType { static constexpr const char *name = "resource"; typedef QSharedPointer Ptr; explicit SinkResource(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor); explicit SinkResource(const QByteArray &identifier); SinkResource(); virtual ~SinkResource(); SINK_REFERENCE_PROPERTY(SinkAccount, Account, account); SINK_PROPERTY(QByteArray, ResourceType, type); SINK_PROPERTY(QByteArrayList, Capabilities, capabilities); SINK_STATUS_PROPERTY(int, Status, status); }; struct SINK_EXPORT Entity : public ApplicationDomainType { typedef QSharedPointer Ptr; using ApplicationDomainType::ApplicationDomainType; Entity() = default; Entity(const ApplicationDomainType &other) : ApplicationDomainType(other) {} virtual ~Entity() = default; }; struct SINK_EXPORT Addressbook : public Entity { SINK_ENTITY(Addressbook, addressbook); SINK_REFERENCE_PROPERTY(Addressbook, Parent, parent); SINK_PROPERTY(QString, Name, name); SINK_EXTRACTED_PROPERTY(QDateTime, LastUpdated, lastUpdated); }; struct SINK_EXPORT Contact : public Entity { struct SINK_EXPORT Email { enum Type { Undefined, Work, Home }; Type type; QString email; }; SINK_ENTITY(Contact, contact); SINK_PROPERTY(QString, Uid, uid); SINK_PROPERTY(QString, Fn, fn); SINK_PROPERTY(QString, Firstname, firstname); SINK_PROPERTY(QString, Lastname, lastname); SINK_PROPERTY(QList, Emails, emails); SINK_PROPERTY(QByteArray, Vcard, vcard); SINK_PROPERTY(QByteArray, Photo, photo); 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); SINK_PROPERTY(QString, Name, name); SINK_PROPERTY(QByteArray, Icon, icon); SINK_PROPERTY(QByteArrayList, SpecialPurpose, specialpurpose); SINK_PROPERTY(bool, Enabled, enabled); SINK_EXTRACTED_PROPERTY(QDateTime, LastUpdated, lastUpdated); SINK_EXTRACTED_PROPERTY(int, Count, count); SINK_EXTRACTED_PROPERTY(bool, FullContentAvailable, fullContentAvailable); }; struct SINK_EXPORT Mail : public Entity { struct SINK_EXPORT Contact { QString name; QString emailAddress; }; SINK_ENTITY(Mail, mail); SINK_EXTRACTED_PROPERTY(Contact, Sender, sender); SINK_EXTRACTED_PROPERTY(QList, To, to); SINK_EXTRACTED_PROPERTY(QList, Cc, cc); SINK_EXTRACTED_PROPERTY(QList, Bcc, bcc); SINK_EXTRACTED_PROPERTY(QString, Subject, subject); SINK_EXTRACTED_PROPERTY(QDateTime, Date, date); SINK_PROPERTY(bool, Unread, unread); SINK_PROPERTY(bool, Important, important); SINK_REFERENCE_PROPERTY(Folder, Folder, folder); SINK_PROPERTY(QByteArray, MimeMessage, mimeMessage); SINK_EXTRACTED_PROPERTY(bool, FullPayloadAvailable, fullPayloadAvailable); SINK_PROPERTY(bool, Draft, draft); SINK_PROPERTY(bool, Trash, trash); SINK_PROPERTY(bool, Sent, sent); SINK_EXTRACTED_PROPERTY(QByteArray, MessageId, messageId); SINK_EXTRACTED_PROPERTY(QByteArray, ParentMessageId, parentMessageId); SINK_INDEX_PROPERTY(QByteArray, ThreadId, threadId); }; SINK_EXPORT QDebug operator<< (QDebug d, const Mail::Contact &c); struct SINK_EXPORT Identity : public ApplicationDomainType { static constexpr const char *name = "identity"; typedef QSharedPointer Ptr; explicit Identity(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier, qint64 revision, const QSharedPointer &adaptor); explicit Identity(const QByteArray &identifier); Identity(); virtual ~Identity(); SINK_REFERENCE_PROPERTY(SinkAccount, Account, account); SINK_PROPERTY(QString, Name, name); SINK_PROPERTY(QString, Address, address); }; struct SINK_EXPORT DummyResource { static SinkResource create(const QByteArray &account); }; struct SINK_EXPORT MaildirResource { static SinkResource create(const QByteArray &account); }; struct SINK_EXPORT MailtransportResource { static SinkResource create(const QByteArray &account); }; struct SINK_EXPORT ImapResource { static SinkResource create(const QByteArray &account); }; struct SINK_EXPORT CardDavResource { 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"; static constexpr const char *folder = "folder"; static constexpr const char *storage = "mail.storage"; static constexpr const char *drafts = "mail.drafts"; static constexpr const char *sent = "mail.sent"; static constexpr const char *trash = "mail.trash"; static constexpr const char *transport = "mail.transport"; static constexpr const char *folderhierarchy = "mail.folderhierarchy"; }; namespace Contact { static constexpr const char *contact = "contact"; 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 { namespace Mail { static constexpr const char *inbox = "inbox"; static constexpr const char *drafts = "drafts"; static constexpr const char *trash = "trash"; static constexpr const char *sent = "sent"; }; }; /** * All types need to be registered here an MUST return a different name. * * Do not store these types to disk, they may change over time. */ template QByteArray SINK_EXPORT getTypeName() { return DomainType::name; } QByteArrayList SINK_EXPORT getTypeNames(); bool SINK_EXPORT isGlobalType(const QByteArray &type); /** * Type implementation. - * + * * Needs to be implemented for every application domain type. * Contains all non-resource specific, but type-specific code. */ template class SINK_EXPORT TypeImplementation; } } #undef SINK_ENTITY #undef SINK_PROPERTY #undef SINK_EXTRACTED_PROPERTY #undef SINK_REFERENCE_PROPERTY #undef SINK_INDEX_PROPERTY /** * This macro can be used to instantiate templates for all domain types. */ #define SINK_REGISTER_TYPES() \ 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) \ REGISTER_TYPE(Sink::ApplicationDomain::SinkAccount) \ REGISTER_TYPE(Sink::ApplicationDomain::Identity) \ SINK_EXPORT QDataStream &operator<<(QDataStream &out, const Sink::ApplicationDomain::Reference &reference); SINK_EXPORT QDataStream &operator>>(QDataStream &in, Sink::ApplicationDomain::Reference &reference); #define REGISTER_TYPE(TYPE) \ Q_DECLARE_METATYPE(TYPE) \ Q_DECLARE_METATYPE(TYPE::Ptr) SINK_REGISTER_TYPES() #undef REGISTER_TYPE Q_DECLARE_METATYPE(Sink::ApplicationDomain::ApplicationDomainType) Q_DECLARE_METATYPE(Sink::ApplicationDomain::ApplicationDomainType::Ptr) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Entity::Ptr) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Contact::Email) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Reference) diff --git a/common/domain/applicationdomaintype_p.h b/common/domain/applicationdomaintype_p.h index a60df38f..734ac3e6 100644 --- a/common/domain/applicationdomaintype_p.h +++ b/common/domain/applicationdomaintype_p.h @@ -1,51 +1,53 @@ /* * Copyright (C) 2014 Christian Mollekopf * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #pragma once #include "applicationdomaintype.h" template class Func> struct TypeHelper { const QByteArray type; TypeHelper(const QByteArray &type_) : type(type_) { } template R operator()(Args && ... args) const { 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()) { + 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 { Q_ASSERT(false); } //Silence compiler warning return Func{}(std::forward(args...)); } }; diff --git a/common/domain/calendar.fbs b/common/domain/calendar.fbs new file mode 100644 index 00000000..9788539b --- /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 index 69148ef0..68c8608b 100644 --- a/common/domain/event.fbs +++ b/common/domain/event.fbs @@ -1,11 +1,12 @@ namespace Sink.ApplicationDomain.Buffer; table Event { uid:string; summary:string; description:string; - attachment:[ubyte]; + startTime:string; + calendar:string; } root_type Event; file_identifier "AKFB"; diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp index 29da7ea4..fe70d749 100644 --- a/common/domain/typeimplementations.cpp +++ b/common/domain/typeimplementations.cpp @@ -1,211 +1,234 @@ /* * 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 "typeimplementations.h" #include #include #include #include "../propertymapper.h" #include "../typeindex.h" #include "entitybuffer.h" #include "entity_generated.h" #include "mail/threadindexer.h" #include "mail/fulltextindexer.h" #include "domainadaptor.h" #include "typeimplementations_p.h" using namespace Sink; using namespace Sink::ApplicationDomain; #define SINK_REGISTER_SERIALIZER(MAPPER, ENTITYTYPE, PROPERTY, LOWERCASEPROPERTY) \ MAPPER.addMapping(&Sink::ApplicationDomain::Buffer::ENTITYTYPE::LOWERCASEPROPERTY, &Sink::ApplicationDomain::Buffer::ENTITYTYPE##Builder::add_##LOWERCASEPROPERTY); typedef IndexConfig, ValueIndex, ValueIndex, ValueIndex, ValueIndex, SortedIndex, SecondaryIndex, SecondaryIndex, CustomSecondaryIndex, CustomSecondaryIndex > MailIndexConfig; typedef IndexConfig, ValueIndex > FolderIndexConfig; typedef IndexConfig > ContactIndexConfig; typedef IndexConfig > AddressbookIndexConfig; typedef IndexConfig > EventIndexConfig; +typedef IndexConfig + > CalendarIndexConfig; + + void TypeImplementation::configure(TypeIndex &index) { MailIndexConfig::configure(index); } QMap TypeImplementation::typeDatabases() { return merge(QMap{{QByteArray{Mail::name} + ".main", 0}}, MailIndexConfig::databases()); } void TypeImplementation::configure(IndexPropertyMapper &indexPropertyMapper) { indexPropertyMapper.addIndexLookupProperty([](TypeIndex &index, const ApplicationDomain::BufferAdaptor &entity) { auto messageId = entity.getProperty(Mail::MessageId::name); auto thread = index.secondaryLookup(messageId); if (!thread.isEmpty()) { return thread.first(); } return QByteArray{}; }); } void TypeImplementation::configure(PropertyMapper &propertyMapper) { SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Sender, sender); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, To, to); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Cc, cc); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Bcc, bcc); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Subject, subject); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Date, date); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Unread, unread); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Important, important); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Folder, folder); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, MimeMessage, mimeMessage); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, FullPayloadAvailable, fullPayloadAvailable); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Draft, draft); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Trash, trash); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, Sent, sent); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, MessageId, messageId); SINK_REGISTER_SERIALIZER(propertyMapper, Mail, ParentMessageId, parentMessageId); } void TypeImplementation::configure(TypeIndex &index) { FolderIndexConfig::configure(index); } QMap TypeImplementation::typeDatabases() { return merge(QMap{{QByteArray{Folder::name} + ".main", 0}}, FolderIndexConfig::databases()); } void TypeImplementation::configure(PropertyMapper &propertyMapper) { SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Parent, parent); SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Name, name); SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Icon, icon); SINK_REGISTER_SERIALIZER(propertyMapper, Folder, SpecialPurpose, specialpurpose); SINK_REGISTER_SERIALIZER(propertyMapper, Folder, Enabled, enabled); } void TypeImplementation::configure(IndexPropertyMapper &) { } void TypeImplementation::configure(TypeIndex &index) { ContactIndexConfig::configure(index); } QMap TypeImplementation::typeDatabases() { return merge(QMap{{QByteArray{Contact::name} + ".main", 0}}, ContactIndexConfig::databases()); } void TypeImplementation::configure(PropertyMapper &propertyMapper) { SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Uid, uid); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Fn, fn); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Emails, emails); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Vcard, vcard); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Addressbook, addressbook); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Firstname, firstname); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Lastname, lastname); SINK_REGISTER_SERIALIZER(propertyMapper, Contact, Photo, photo); } void TypeImplementation::configure(IndexPropertyMapper &) { } void TypeImplementation::configure(TypeIndex &index) { AddressbookIndexConfig::configure(index); } QMap TypeImplementation::typeDatabases() { return merge(QMap{{QByteArray{Addressbook::name} + ".main", 0}}, AddressbookIndexConfig::databases()); } void TypeImplementation::configure(PropertyMapper &propertyMapper) { SINK_REGISTER_SERIALIZER(propertyMapper, Addressbook, Parent, parent); SINK_REGISTER_SERIALIZER(propertyMapper, Addressbook, Name, name); } void TypeImplementation::configure(IndexPropertyMapper &) { } void TypeImplementation::configure(TypeIndex &index) { EventIndexConfig::configure(index); } QMap TypeImplementation::typeDatabases() { return merge(QMap{{QByteArray{Event::name} + ".main", 0}}, EventIndexConfig::databases()); } void TypeImplementation::configure(PropertyMapper &propertyMapper) { 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/common/domain/typeimplementations.h b/common/domain/typeimplementations.h index d36dfc18..7a8a602a 100644 --- a/common/domain/typeimplementations.h +++ b/common/domain/typeimplementations.h @@ -1,98 +1,110 @@ /* * 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. */ #pragma once #include "applicationdomaintype.h" #include "mail_generated.h" #include "folder_generated.h" #include "event_generated.h" +#include "calendar_generated.h" #include "contact_generated.h" #include "addressbook_generated.h" class PropertyMapper; class IndexPropertyMapper; class TypeIndex; /** * Implements all type-specific code such as updating and querying indexes. * * These are type specifiy default implementations. Theoretically a resource could implement it's own implementation. */ namespace Sink { namespace ApplicationDomain { template<> class TypeImplementation { public: typedef Sink::ApplicationDomain::Buffer::Mail Buffer; typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder; static void configure(TypeIndex &index); static void configure(PropertyMapper &propertyMapper); static void configure(IndexPropertyMapper &indexPropertyMapper); static QMap typeDatabases(); }; template<> class TypeImplementation { public: typedef Sink::ApplicationDomain::Buffer::Folder Buffer; typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder; static void configure(TypeIndex &); static void configure(PropertyMapper &); static void configure(IndexPropertyMapper &indexPropertyMapper); static QMap typeDatabases(); }; template<> class TypeImplementation { public: typedef Sink::ApplicationDomain::Buffer::Contact Buffer; typedef Sink::ApplicationDomain::Buffer::ContactBuilder BufferBuilder; static void configure(TypeIndex &); static void configure(PropertyMapper &); static void configure(IndexPropertyMapper &indexPropertyMapper); static QMap typeDatabases(); }; template<> class TypeImplementation { public: typedef Sink::ApplicationDomain::Buffer::Addressbook Buffer; typedef Sink::ApplicationDomain::Buffer::AddressbookBuilder BufferBuilder; static void configure(TypeIndex &); static void configure(PropertyMapper &); static void configure(IndexPropertyMapper &indexPropertyMapper); static QMap typeDatabases(); }; template<> class TypeImplementation { public: typedef Sink::ApplicationDomain::Buffer::Event Buffer; typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder; static void configure(TypeIndex &); static void configure(PropertyMapper &); static void configure(IndexPropertyMapper &indexPropertyMapper); 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/examples/CMakeLists.txt b/examples/CMakeLists.txt index adfb5e1f..6de489aa 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,13 +1,15 @@ # a simple dummy resource implementation add_subdirectory(dummyresource) if (BUILD_MAILDIR) # a maildir resource implementation add_subdirectory(maildirresource) # an imap resource implementation add_subdirectory(imapresource) 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 index 00000000..0057e8be --- /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 00000000..2bcdfa1b --- /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/davresource/davresource.h b/examples/caldavresource/caldavresource.h similarity index 61% copy from examples/davresource/davresource.h copy to examples/caldavresource/caldavresource.h index b4f9e5ac..58224957 100644 --- a/examples/davresource/davresource.h +++ b/examples/caldavresource/caldavresource.h @@ -1,63 +1,46 @@ /* - * 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 * 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" -#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/tests/CMakeLists.txt b/examples/caldavresource/tests/CMakeLists.txt new file mode 100644 index 00000000..d2f9b503 --- /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 00000000..f9995901 --- /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 similarity index 54% rename from examples/davresource/CMakeLists.txt rename to examples/carddavresource/CMakeLists.txt index 2351ecde..2c69d26f 100644 --- 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/carddavresource/carddavresource.cpp b/examples/carddavresource/carddavresource.cpp new file mode 100644 index 00000000..fc2b9465 --- /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.h b/examples/carddavresource/carddavresource.h similarity index 85% rename from examples/davresource/davresource.h rename to examples/carddavresource/carddavresource.h index b4f9e5ac..3c0f7073 100644 --- a/examples/davresource/davresource.h +++ b/examples/carddavresource/carddavresource.h @@ -1,63 +1,63 @@ /* * 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. */ #pragma once #include "common/genericresource.h" #include #include #include class ContactAdaptorFactory; 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; void registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) Q_DECL_OVERRIDE; void removeDataFromDisk(const QByteArray &instanceIdentifier) Q_DECL_OVERRIDE; }; diff --git a/examples/davresource/davresource.cpp b/examples/davresource/davresource.cpp deleted file mode 100644 index fde7055a..00000000 --- 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 index 275371d5..cfce6e43 100644 --- a/examples/dummyresource/resourcefactory.cpp +++ b/examples/dummyresource/resourcefactory.cpp @@ -1,211 +1,209 @@ /* * Copyright (C) 2014 Aaron Seigo * 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 "resourcefactory.h" #include "facade.h" #include "entitybuffer.h" #include "pipeline.h" #include "dummycalendar_generated.h" #include "mail_generated.h" #include "domainadaptor.h" #include "log.h" #include "dummystore.h" #include "definitions.h" #include "facadefactory.h" #include "adaptorfactoryregistry.h" #include "synchronizer.h" #include "inspector.h" #include "mailpreprocessor.h" #include "specialpurposepreprocessor.h" #include //This is the resources entity type, and not the domain type #define ENTITY_TYPE_EVENT "event" #define ENTITY_TYPE_MAIL "mail" #define ENTITY_TYPE_FOLDER "folder" using namespace Sink; class DummySynchronizer : public Sink::Synchronizer { public: DummySynchronizer(const Sink::ResourceContext &context) : Sink::Synchronizer(context) { setSecret("dummy"); } 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; } Sink::ApplicationDomain::Mail::Ptr createMail(const QByteArray &ridBuffer, const QMap &data) { auto mail = Sink::ApplicationDomain::Mail::Ptr::create(); mail->setExtractedMessageId(ridBuffer); mail->setExtractedSubject(data.value("subject").toString()); mail->setExtractedSender(Sink::ApplicationDomain::Mail::Contact{data.value("senderName").toString(), data.value("senderEmail").toString()}); mail->setExtractedDate(data.value("date").toDateTime()); mail->setFolder(syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, data.value("parentFolder").toByteArray())); mail->setUnread(data.value("unread").toBool()); mail->setImportant(data.value("important").toBool()); return mail; } Sink::ApplicationDomain::Folder::Ptr createFolder(const QByteArray &ridBuffer, const QMap &data) { auto folder = Sink::ApplicationDomain::Folder::Ptr::create(); folder->setName(data.value("name").toString()); folder->setIcon(data.value("icon").toByteArray()); if (!data.value("parent").toString().isEmpty()) { auto sinkId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, data.value("parent").toByteArray()); folder->setParent(sinkId); } return folder; } void synchronize(const QByteArray &bufferType, const QMap > &data, std::function &data)> createEntity) { auto time = QSharedPointer::create(); time->start(); //TODO find items to remove int count = 0; for (auto it = data.constBegin(); it != data.constEnd(); it++) { count++; const auto remoteId = it.key().toUtf8(); auto entity = createEntity(remoteId, it.value()); createOrModify(bufferType, remoteId, *entity); } SinkTrace() << "Sync of " << count << " entities of type " << bufferType << " done." << Sink::Log::TraceTime(time->elapsed()); } KAsync::Job synchronizeWithSource(const Sink::QueryBase &) Q_DECL_OVERRIDE { SinkLog() << " Synchronizing with the source"; SinkTrace() << "Synchronize with source and sending a notification about it"; Sink::Notification n; n.id = "connected"; n.type = Sink::Notification::Status; n.message = "We're connected"; n.code = Sink::ApplicationDomain::ConnectedStatus; emit notify(n); return KAsync::start([this]() { synchronize(ENTITY_TYPE_EVENT, DummyStore::instance().events(), [this](const QByteArray &ridBuffer, const QMap &data) { return createEvent(ridBuffer, data); }); synchronize(ENTITY_TYPE_MAIL, DummyStore::instance().mails(), [this](const QByteArray &ridBuffer, const QMap &data) { return createMail(ridBuffer, data); }); synchronize(ENTITY_TYPE_FOLDER, DummyStore::instance().folders(), [this](const QByteArray &ridBuffer, const QMap &data) { return createFolder(ridBuffer, data); }); }); } bool canReplay(const QByteArray &type, const QByteArray &key, const QByteArray &value) Q_DECL_OVERRIDE { return false; } }; class DummyInspector : public Sink::Inspector { public: DummyInspector(const Sink::ResourceContext &resourceContext) : Sink::Inspector(resourceContext) { } protected: KAsync::Job inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE { SinkTrace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; if (property == "testInspection") { if (expectedValue.toBool()) { //Success return KAsync::null(); } else { //Failure return KAsync::error(1, "Failed."); } } return KAsync::null(); } }; DummyResource::DummyResource(const Sink::ResourceContext &resourceContext, const QSharedPointer &pipeline) : Sink::GenericResource(resourceContext, pipeline) { setupSynchronizer(QSharedPointer::create(resourceContext)); setupInspector(QSharedPointer::create(resourceContext)); setupPreprocessors(ENTITY_TYPE_MAIL, QVector() << new MailPropertyExtractor << new SpecialPurposeProcessor); setupPreprocessors(ENTITY_TYPE_FOLDER, QVector()); setupPreprocessors(ENTITY_TYPE_EVENT, QVector()); } DummyResource::~DummyResource() { } DummyResourceFactory::DummyResourceFactory(QObject *parent) : Sink::ResourceFactory(parent, {Sink::ApplicationDomain::ResourceCapabilities::Mail::mail, "event", Sink::ApplicationDomain::ResourceCapabilities::Mail::folder, Sink::ApplicationDomain::ResourceCapabilities::Mail::storage, "-folder.rename", Sink::ApplicationDomain::ResourceCapabilities::Mail::sent} ) { } Sink::Resource *DummyResourceFactory::createResource(const Sink::ResourceContext &resourceContext) { return new DummyResource(resourceContext); } void DummyResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) { factory.registerFacade>(resourceName); factory.registerFacade>(resourceName); factory.registerFacade>(resourceName); } void DummyResourceFactory::registerAdaptorFactories(const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) { registry.registerFactory(resourceName); registry.registerFactory(resourceName); registry.registerFactory(resourceName); } void DummyResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier) { DummyResource::removeFromDisk(instanceIdentifier); } diff --git a/examples/webdavcommon/CMakeLists.txt b/examples/webdavcommon/CMakeLists.txt new file mode 100644 index 00000000..318756e3 --- /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.cpp b/examples/webdavcommon/webdav.cpp new file mode 100644 index 00000000..35f7fc20 --- /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/examples/webdavcommon/webdav.h b/examples/webdavcommon/webdav.h new file mode 100644 index 00000000..3a4977c8 --- /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/tests/CMakeLists.txt b/tests/CMakeLists.txt index 883a38b8..fe7866ce 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,68 +1,67 @@ add_subdirectory(hawd) set(CMAKE_AUTOMOC ON) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/hawd ${CMAKE_CURRENT_BINARY_DIR}/../examples/dummyresource ${CMAKE_CURRENT_SOURCE_DIR}/../examples/ ) add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/data") add_definitions(-DTHREADTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/threaddata") find_package(KF5 COMPONENTS REQUIRED Mime) add_library(sink_test SHARED testimplementations.cpp getrssusage.cpp mailtest.cpp mailsynctest.cpp mailthreadtest.cpp utils.cpp) target_link_libraries(sink_test sink libhawd Qt5::Core Qt5::Concurrent Qt5::Test KF5::Mime ) add_executable(dbwriter dbwriter.cpp) target_link_libraries(dbwriter sink) include(SinkTest) manual_tests ( storagebenchmark dummyresourcebenchmark mailquerybenchmark pipelinebenchmark dummyresourcewritebenchmark databasepopulationandfacadequerybenchmark ) auto_tests ( clientapitest resourceconfigtest storagetest dummyresourcetest domainadaptortest messagequeuetest indextest resourcecommunicationtest pipelinetest querytest modelinteractivitytest inspectiontest accountstest testaccounttest dummyresourcemailtest interresourcemovetest notificationtest 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) target_link_libraries(querytest sink_resource_dummy) target_link_libraries(modelinteractivitytest sink_resource_dummy) target_link_libraries(inspectiontest sink_resource_dummy) target_link_libraries(notificationtest sink_resource_dummy) diff --git a/tests/calendar.fbs b/tests/calendar.fbs deleted file mode 100644 index ef4f112c..00000000 --- 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 index 2aed0a96..60d50693 100644 --- a/tests/domainadaptortest.cpp +++ b/tests/domainadaptortest.cpp @@ -1,180 +1,178 @@ #include #include #include #include #include "dummyresource/resourcefactory.h" #include "store.h" #include "common/domainadaptor.h" #include "common/entitybuffer.h" #include "event_generated.h" #include "mail_generated.h" #include "metadata_generated.h" #include "entity_generated.h" class TestFactory : public DomainTypeAdaptorFactory { public: TestFactory() = default; }; class TestMailFactory : public DomainTypeAdaptorFactory { public: TestMailFactory() = default; }; class TestContactFactory : public DomainTypeAdaptorFactory { public: TestContactFactory() = default; }; /** * Test of domain adaptor, that it can read and write buffers. */ class DomainAdaptorTest : public QObject { Q_OBJECT private slots: void initTestCase() { } void cleanupTestCase() { } void testCreateBufferPart() { auto writeMapper = QSharedPointer::create(); Sink::ApplicationDomain::TypeImplementation::configure(*writeMapper); Sink::ApplicationDomain::Event event; event.setProperty("summary", "foo"); flatbuffers::FlatBufferBuilder fbb; auto pos = createBufferPart(event, fbb, *writeMapper); Sink::ApplicationDomain::Buffer::FinishEventBuffer(fbb, pos); flatbuffers::Verifier verifier(fbb.GetBufferPointer(), fbb.GetSize()); QVERIFY(verifier.VerifyBuffer(nullptr)); } void testAdaptor() { // Create entity buffer flatbuffers::FlatBufferBuilder metadataFbb; auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); metadataBuilder.add_revision(1); auto metadataBuffer = metadataBuilder.Finish(); Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); flatbuffers::FlatBufferBuilder m_fbb; 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); flatbuffers::FlatBufferBuilder fbb; Sink::EntityBuffer::assembleEntityBuffer( fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), m_fbb.GetBufferPointer(), m_fbb.GetSize(), m_fbb.GetBufferPointer(), m_fbb.GetSize()); // Extract entity buffer { std::string data(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); Sink::EntityBuffer buffer((void *)(data.data()), data.size()); TestFactory factory; auto adaptor = factory.createAdaptor(buffer.entity()); QCOMPARE(adaptor->getProperty("summary").toString(), QString("summary1")); } } void testMail() { auto writeMapper = QSharedPointer::create(); Sink::ApplicationDomain::TypeImplementation::configure(*writeMapper); Sink::ApplicationDomain::Mail mail; mail.setExtractedSubject("summary"); mail.setMimeMessage("foobar"); mail.setFolder("folder"); flatbuffers::FlatBufferBuilder metadataFbb; auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); metadataBuilder.add_revision(1); auto metadataBuffer = metadataBuilder.Finish(); Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); flatbuffers::FlatBufferBuilder mailFbb; auto pos = createBufferPart(mail, mailFbb, *writeMapper); Sink::ApplicationDomain::Buffer::FinishMailBuffer(mailFbb, pos); flatbuffers::FlatBufferBuilder fbb; Sink::EntityBuffer::assembleEntityBuffer( fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize()); { std::string data(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); Sink::EntityBuffer buffer((void *)(data.data()), data.size()); TestMailFactory factory; auto adaptor = factory.createAdaptor(buffer.entity()); Sink::ApplicationDomain::Mail readMail{QByteArray{}, QByteArray{}, 0, adaptor}; QCOMPARE(readMail.getSubject(), mail.getSubject()); QCOMPARE(readMail.getMimeMessage(), mail.getMimeMessage()); QCOMPARE(readMail.getFolder(), mail.getFolder()); } } void testContact() { auto writeMapper = QSharedPointer::create(); Sink::ApplicationDomain::TypeImplementation::configure(*writeMapper); auto binaryData = QByteArray::fromRawData("\xEF\xBF\xBD\x00\xEF\xBF", 5); Sink::ApplicationDomain::Contact contact; contact.setPhoto(binaryData); QVERIFY(!contact.getPhoto().isEmpty()); flatbuffers::FlatBufferBuilder metadataFbb; auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); metadataBuilder.add_revision(1); auto metadataBuffer = metadataBuilder.Finish(); Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); flatbuffers::FlatBufferBuilder mailFbb; auto pos = createBufferPart(contact, mailFbb, *writeMapper); Sink::ApplicationDomain::Buffer::FinishContactBuffer(mailFbb, pos); flatbuffers::FlatBufferBuilder fbb; Sink::EntityBuffer::assembleEntityBuffer( fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize()); { std::string data(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); Sink::EntityBuffer buffer((void *)(data.data()), data.size()); TestContactFactory factory; auto adaptor = factory.createAdaptor(buffer.entity()); Sink::ApplicationDomain::Contact readContact{QByteArray{}, QByteArray{}, 0, adaptor}; QCOMPARE(readContact.getPhoto(), contact.getPhoto()); } } }; QTEST_MAIN(DomainAdaptorTest) #include "domainadaptortest.moc" diff --git a/tests/storagebenchmark.cpp b/tests/storagebenchmark.cpp index ee336d2d..eef360ef 100644 --- a/tests/storagebenchmark.cpp +++ b/tests/storagebenchmark.cpp @@ -1,218 +1,215 @@ #include -#include "calendar_generated.h" +#include "event_generated.h" #include "hawd/dataset.h" #include "hawd/formatter.h" #include "common/storage.h" #include "common/log.h" #include #include #include #include -using namespace Calendar; +using namespace Sink::ApplicationDomain::Buffer; using namespace flatbuffers; static QByteArray createEvent() { static const size_t attachmentSize = 1024 * 2; // 2KB static uint8_t rawData[attachmentSize]; static FlatBufferBuilder fbb; fbb.Clear(); { 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); } return QByteArray::fromRawData(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); } /** * Benchmark the storage implementation. */ class StorageBenchmark : public QObject { Q_OBJECT private: // This should point to a directory on disk and not a ramdisk (since we're measuring performance) QString testDataPath; QString dbName; QString filePath; const int count = 50000; private slots: void initTestCase() { Sink::Log::setDebugOutputLevel(Sink::Log::Warning); testDataPath = "./testdb"; dbName = "test"; filePath = testDataPath + "buffer.fb"; } void cleanupTestCase() { Sink::Storage::DataStore store(testDataPath, dbName); store.removeFromDisk(); } void testWriteRead() { auto event = createEvent(); QScopedPointer store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite)); const char *keyPrefix = "key"; QTime time; time.start(); // Test db write time { auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadWrite); for (int i = 0; i < count; i++) { transaction.openDatabase().write(keyPrefix + QByteArray::number(i), event); if ((i % 10000) == 0) { transaction.commit(); transaction = store->createTransaction(Sink::Storage::DataStore::ReadWrite); } } transaction.commit(); } qreal dbWriteDuration = time.restart(); qreal dbWriteOpsPerMs = count / dbWriteDuration; // Test file write time { std::ofstream myfile; myfile.open(filePath.toStdString()); for (int i = 0; i < count; i++) { myfile << event.toStdString(); } myfile.close(); } qreal fileWriteDuration = time.restart(); qreal fileWriteOpsPerMs = count / fileWriteDuration; // Db read time { auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadOnly); auto db = transaction.openDatabase(); for (int i = 0; i < count; i++) { db.scan(keyPrefix + QByteArray::number(i), [](const QByteArray &key, const QByteArray &value) -> bool { return true; }); } } qreal readDuration = time.restart(); qreal readOpsPerMs = count / readDuration; HAWD::Dataset dataset("storage_readwrite", m_hawdState); HAWD::Dataset::Row row = dataset.row(); row.setValue("rows", count); row.setValue("dbWrite", dbWriteOpsPerMs); row.setValue("fileWrite", fileWriteOpsPerMs); row.setValue("dbRead", readOpsPerMs); dataset.insertRow(row); HAWD::Formatter::print(dataset); { Sink::Storage::DataStore store(testDataPath, dbName); QFileInfo fileInfo(filePath); HAWD::Dataset dataset("storage_sizes", m_hawdState); HAWD::Dataset::Row row = dataset.row(); row.setValue("rows", count); row.setValue("dbSize", store.diskUsage() / 1024); row.setValue("fileSize", fileInfo.size() / 1024); dataset.insertRow(row); HAWD::Formatter::print(dataset); } } void testScan() { QScopedPointer store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly)); QBENCHMARK { int hit = 0; store->createTransaction(Sink::Storage::DataStore::ReadOnly) .openDatabase() .scan("", [&](const QByteArray &key, const QByteArray &value) -> bool { if (key == "key10000") { // qDebug() << "hit"; hit++; } return true; }); QCOMPARE(hit, 1); } } void testKeyLookup() { QScopedPointer store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly)); auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadOnly); auto db = transaction.openDatabase(); QBENCHMARK { int hit = 0; db.scan("key40000", [&](const QByteArray &key, const QByteArray &value) -> bool { hit++; return true; }); QCOMPARE(hit, 1); } } void testFindLatest() { QScopedPointer store(new Sink::Storage::DataStore(testDataPath, dbName, Sink::Storage::DataStore::ReadOnly)); auto transaction = store->createTransaction(Sink::Storage::DataStore::ReadOnly); auto db = transaction.openDatabase(); QBENCHMARK { int hit = 0; db.findLatest("key40000", [&](const QByteArray &key, const QByteArray &value) -> bool { hit++; return true; }); QCOMPARE(hit, 1); } } void testBufferCreation() { HAWD::Dataset dataset("buffer_creation", m_hawdState); HAWD::Dataset::Row row = dataset.row(); QTime time; time.start(); for (int i = 0; i < count; i++) { auto event = createEvent(); } qreal bufferDuration = time.restart(); qreal opsPerMs = count / bufferDuration; row.setValue("numBuffers", count); row.setValue("time", bufferDuration); row.setValue("ops", opsPerMs); dataset.insertRow(row); HAWD::Formatter::print(dataset); } private: HAWD::State m_hawdState; }; QTEST_MAIN(StorageBenchmark) #include "storagebenchmark.moc"