diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -71,6 +71,7 @@ contactpreprocessor.cpp mailpreprocessor.cpp eventpreprocessor.cpp + todopreprocessor.cpp specialpurposepreprocessor.cpp datastorequery.cpp storage/entitystore.cpp @@ -105,6 +106,7 @@ domain/contact domain/addressbook domain/event + domain/todo domain/calendar domain/mail domain/folder diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h @@ -407,6 +407,17 @@ struct SINK_EXPORT Todo : public Entity { SINK_ENTITY(Todo, todo); + SINK_EXTRACTED_PROPERTY(QString, Uid, uid); + SINK_EXTRACTED_PROPERTY(QString, Summary, summary); + SINK_EXTRACTED_PROPERTY(QString, Description, description); + SINK_EXTRACTED_PROPERTY(QDateTime, CompletedDate, completedDate); + SINK_EXTRACTED_PROPERTY(QDateTime, DueDate, dueDate); + SINK_EXTRACTED_PROPERTY(QDateTime, StartDate, startDate); + SINK_EXTRACTED_PROPERTY(QString, Status, status); + SINK_EXTRACTED_PROPERTY(int, Priority, priority); + SINK_EXTRACTED_PROPERTY(QStringList, Categories, categories); + SINK_PROPERTY(QByteArray, Ical, ical); + SINK_REFERENCE_PROPERTY(Calendar, Calendar, calendar); }; struct SINK_EXPORT Folder : public Entity { @@ -506,6 +517,11 @@ static constexpr const char *calendar = "calendar"; static constexpr const char *storage = "event.storage"; }; +namespace Todo { + static constexpr const char *todo = "todo"; + static constexpr const char *calendar = "calendar"; + static constexpr const char *storage = "todo.storage"; +}; }; namespace SpecialPurpose { @@ -558,6 +574,7 @@ REGISTER_TYPE(Sink::ApplicationDomain::Contact) \ REGISTER_TYPE(Sink::ApplicationDomain::Addressbook) \ REGISTER_TYPE(Sink::ApplicationDomain::Event) \ + REGISTER_TYPE(Sink::ApplicationDomain::Todo) \ REGISTER_TYPE(Sink::ApplicationDomain::Calendar) \ REGISTER_TYPE(Sink::ApplicationDomain::Mail) \ REGISTER_TYPE(Sink::ApplicationDomain::Folder) \ diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp --- a/common/domain/applicationdomaintype.cpp +++ b/common/domain/applicationdomaintype.cpp @@ -132,6 +132,19 @@ SINK_REGISTER_PROPERTY(Event, Ical); SINK_REGISTER_PROPERTY(Event, Calendar); +SINK_REGISTER_ENTITY(Todo); +SINK_REGISTER_PROPERTY(Todo, Uid); +SINK_REGISTER_PROPERTY(Todo, Summary); +SINK_REGISTER_PROPERTY(Todo, Description); +SINK_REGISTER_PROPERTY(Todo, CompletedDate); +SINK_REGISTER_PROPERTY(Todo, DueDate); +SINK_REGISTER_PROPERTY(Todo, StartDate); +SINK_REGISTER_PROPERTY(Todo, Status); +SINK_REGISTER_PROPERTY(Todo, Priority); +SINK_REGISTER_PROPERTY(Todo, Categories); +SINK_REGISTER_PROPERTY(Todo, Ical); +SINK_REGISTER_PROPERTY(Todo, Calendar); + SINK_REGISTER_ENTITY(Calendar); SINK_REGISTER_PROPERTY(Calendar, Name); diff --git a/common/domain/applicationdomaintype_p.h b/common/domain/applicationdomaintype_p.h --- a/common/domain/applicationdomaintype_p.h +++ b/common/domain/applicationdomaintype_p.h @@ -38,6 +38,8 @@ return Func{}(std::forward(args...)); } else if (type == Sink::ApplicationDomain::getTypeName()) { return Func{}(std::forward(args...)); + } else if (type == Sink::ApplicationDomain::getTypeName()) { + return Func{}(std::forward(args...)); } else if (type == Sink::ApplicationDomain::getTypeName()) { return Func{}(std::forward(args...)); } else if (type == Sink::ApplicationDomain::getTypeName()) { diff --git a/common/domain/propertyregistry.cpp b/common/domain/propertyregistry.cpp --- a/common/domain/propertyregistry.cpp +++ b/common/domain/propertyregistry.cpp @@ -74,6 +74,12 @@ return QVariant::fromValue(result); } +template <> +QVariant parseString(const QString &s) +{ + return s.split(','); +} + template <> QVariant parseString(const QString &s) { diff --git a/common/domain/todo.fbs b/common/domain/todo.fbs new file mode 100644 --- /dev/null +++ b/common/domain/todo.fbs @@ -0,0 +1,18 @@ +namespace Sink.ApplicationDomain.Buffer; + +table Todo { + uid:string; + summary:string; + description:string; + completedDate:string; + dueDate:string; + startDate:string; + status:string; + priority:int; + categories:[string]; + ical:string; + calendar:string; +} + +root_type Todo; +file_identifier "AKFB"; diff --git a/common/domain/typeimplementations.h b/common/domain/typeimplementations.h --- a/common/domain/typeimplementations.h +++ b/common/domain/typeimplementations.h @@ -24,6 +24,7 @@ #include "mail_generated.h" #include "folder_generated.h" #include "event_generated.h" +#include "todo_generated.h" #include "calendar_generated.h" #include "contact_generated.h" #include "addressbook_generated.h" @@ -96,6 +97,17 @@ static QMap typeDatabases(); }; +template<> +class SINK_EXPORT TypeImplementation { +public: + typedef Sink::ApplicationDomain::Buffer::Todo Buffer; + typedef Sink::ApplicationDomain::Buffer::TodoBuilder BufferBuilder; + static void configure(TypeIndex &); + static void configure(PropertyMapper &); + static void configure(IndexPropertyMapper &indexPropertyMapper); + static QMap typeDatabases(); +}; + template<> class SINK_EXPORT TypeImplementation { public: diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp --- a/common/domain/typeimplementations.cpp +++ b/common/domain/typeimplementations.cpp @@ -67,6 +67,10 @@ ValueIndex > EventIndexConfig; +typedef IndexConfig + > TodoIndexConfig; + typedef IndexConfig > CalendarIndexConfig; @@ -218,6 +222,37 @@ } +void TypeImplementation::configure(TypeIndex &index) +{ + TodoIndexConfig::configure(index); +} + +QMap TypeImplementation::typeDatabases() +{ + return merge(QMap{{QByteArray{Todo::name} + ".main", 0}}, TodoIndexConfig::databases()); +} + +void TypeImplementation::configure(PropertyMapper &propertyMapper) +{ + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Uid, uid); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Summary, summary); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Description, description); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, CompletedDate, completedDate); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, DueDate, dueDate); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, StartDate, startDate); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Status, status); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Priority, priority); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Categories, categories); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Ical, ical); + SINK_REGISTER_SERIALIZER(propertyMapper, Todo, Calendar, calendar); +} + +void TypeImplementation::configure(IndexPropertyMapper &) +{ + +} + + void TypeImplementation::configure(TypeIndex &index) { CalendarIndexConfig::configure(index); diff --git a/common/propertymapper.h b/common/propertymapper.h --- a/common/propertymapper.h +++ b/common/propertymapper.h @@ -130,6 +130,14 @@ }); } + template + void addWriteMapping(void (BufferBuilder::*f)(int)) + { + addWriteMapping(T::name, [f](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function { + return [value, f](void *builder) { (static_cast(builder)->*f)(value.value()); }; + }); + } + template void addWriteMapping(void (BufferBuilder::*f)(bool)) { diff --git a/common/propertymapper.cpp b/common/propertymapper.cpp --- a/common/propertymapper.cpp +++ b/common/propertymapper.cpp @@ -81,6 +81,21 @@ return 0; } +template <> +flatbuffers::uoffset_t variantToProperty(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) +{ + if (property.isValid()) { + const auto list = property.value(); + std::vector> vector; + for (const auto &value : list) { + auto offset = fbb.CreateString(value.toStdString()); + vector.push_back(offset); + } + return fbb.CreateVector(vector).o; + } + return 0; +} + template <> flatbuffers::uoffset_t variantToProperty(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) { @@ -186,6 +201,21 @@ return QVariant(); } +template <> +QVariant propertyToVariant(const flatbuffers::Vector> *property) +{ + if (property) { + QStringList list; + for (auto it = property->begin(); it != property->end();) { + // We have to copy the memory, otherwise it would become eventually invalid + list << QString::fromStdString((*it)->str()); + it.operator++(); + } + return QVariant::fromValue(list); + } + return QVariant(); +} + template <> QVariant propertyToVariant(const Sink::ApplicationDomain::Buffer::MailContact *property) { @@ -231,6 +261,12 @@ return static_cast(property); } +template <> +QVariant propertyToVariant(uint8_t property) +{ + return static_cast(property); +} + template <> QVariant propertyToVariant(const flatbuffers::String *property) { diff --git a/common/synchronizer.h b/common/synchronizer.h --- a/common/synchronizer.h +++ b/common/synchronizer.h @@ -80,6 +80,7 @@ virtual KAsync::Job replay(const Sink::ApplicationDomain::Mail &, Sink::Operation, const QByteArray &oldRemoteId, const QList &); virtual KAsync::Job replay(const Sink::ApplicationDomain::Folder &, Sink::Operation, const QByteArray &oldRemoteId, const QList &); virtual KAsync::Job replay(const Sink::ApplicationDomain::Event &, Sink::Operation, const QByteArray &oldRemoteId, const QList &); + virtual KAsync::Job replay(const Sink::ApplicationDomain::Todo &, Sink::Operation, const QByteArray &oldRemoteId, const QList &); virtual KAsync::Job replay(const Sink::ApplicationDomain::Calendar &, Sink::Operation, const QByteArray &oldRemoteId, const QList &); protected: QString secret() const; diff --git a/common/synchronizer.cpp b/common/synchronizer.cpp --- a/common/synchronizer.cpp +++ b/common/synchronizer.cpp @@ -634,6 +634,8 @@ job = replay(store().readEntity(key), operation, oldRemoteId, modifiedProperties); } else if (type == ApplicationDomain::getTypeName()) { job = replay(store().readEntity(key), operation, oldRemoteId, modifiedProperties); + } else if (type == ApplicationDomain::getTypeName()) { + job = replay(store().readEntity(key), operation, oldRemoteId, modifiedProperties); } else if (type == ApplicationDomain::getTypeName()) { job = replay(store().readEntity(key), operation, oldRemoteId, modifiedProperties); } else { @@ -697,6 +699,11 @@ return KAsync::null(); } +KAsync::Job Synchronizer::replay(const ApplicationDomain::Todo &, Sink::Operation, const QByteArray &, const QList &) +{ + return KAsync::null(); +} + KAsync::Job Synchronizer::replay(const ApplicationDomain::Calendar &, Sink::Operation, const QByteArray &, const QList &) { return KAsync::null(); diff --git a/common/todopreprocessor.h b/common/todopreprocessor.h new file mode 100644 --- /dev/null +++ b/common/todopreprocessor.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 Christian Mollekopf + * Copyright (C) 2018 Rémi Nicole + * + * 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 "pipeline.h" +#include "sink_export.h" + +class SINK_EXPORT TodoPropertyExtractor : public Sink::EntityPreprocessor +{ + using Todo = Sink::ApplicationDomain::Todo; + +public: + virtual ~TodoPropertyExtractor() {} + virtual void newEntity(Todo &todo) Q_DECL_OVERRIDE; + virtual void modifiedEntity(const Todo &oldTodo, Todo &newTodo) Q_DECL_OVERRIDE; + +private: + static void updatedIndexedProperties(Todo &todo, const QByteArray &rawIcal); +}; diff --git a/common/todopreprocessor.cpp b/common/todopreprocessor.cpp new file mode 100644 --- /dev/null +++ b/common/todopreprocessor.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 Christian Mollekopf + * Copyright (C) 2018 Rémi Nicole + * + * 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 "todopreprocessor.h" + +#include + +void TodoPropertyExtractor::updatedIndexedProperties(Todo &todo, const QByteArray &rawIcal) +{ + auto incidence = KCalCore::ICalFormat().readIncidence(rawIcal); + + if(!incidence) { + SinkWarning() << "Invalid ICal to process, ignoring..."; + return; + } + + if(incidence->type() != KCalCore::IncidenceBase::IncidenceType::TypeTodo) { + SinkWarning() << "ICal to process is not of type `Todo`, ignoring..."; + return; + } + + auto icalTodo = dynamic_cast(incidence.data()); + // Should be guaranteed by the incidence->type() condition above. + Q_ASSERT(icalTodo); + + SinkTrace() << "Extracting properties for todo:" << icalTodo->summary(); + + todo.setExtractedUid(icalTodo->uid()); + todo.setExtractedSummary(icalTodo->summary()); + todo.setExtractedDescription(icalTodo->description()); + + // Sets invalid QDateTime if not defined + todo.setExtractedCompletedDate(icalTodo->completed()); + todo.setExtractedDueDate(icalTodo->dtDue()); + todo.setExtractedStartDate(icalTodo->dtStart()); + + todo.setExtractedStatus(icalTodo->customStatus()); + todo.setExtractedPriority(icalTodo->priority()); + todo.setExtractedCategories(icalTodo->categories()); +} + +void TodoPropertyExtractor::newEntity(Todo &todo) +{ + updatedIndexedProperties(todo, todo.getIcal()); +} + +void TodoPropertyExtractor::modifiedEntity(const Todo &oldTodo, Todo &newTodo) +{ + updatedIndexedProperties(newTodo, newTodo.getIcal()); +} diff --git a/examples/caldavresource/caldavresource.cpp b/examples/caldavresource/caldavresource.cpp --- a/examples/caldavresource/caldavresource.cpp +++ b/examples/caldavresource/caldavresource.cpp @@ -25,25 +25,29 @@ #include "applicationdomaintype.h" #include "domainadaptor.h" #include "eventpreprocessor.h" +#include "todopreprocessor.h" #include "facade.h" #include "facadefactory.h" #include #define ENTITY_TYPE_EVENT "event" +#define ENTITY_TYPE_TODO "todo" #define ENTITY_TYPE_CALENDAR "calendar" using Sink::ApplicationDomain::getTypeName; -class EventSynchronizer : public WebDavSynchronizer +class CalDAVSynchronizer : public WebDavSynchronizer { using Event = Sink::ApplicationDomain::Event; + using Todo = Sink::ApplicationDomain::Todo; using Calendar = Sink::ApplicationDomain::Calendar; public: - explicit EventSynchronizer(const Sink::ResourceContext &context) + explicit CalDAVSynchronizer(const Sink::ResourceContext &context) : WebDavSynchronizer(context, KDAV2::CalDav, getTypeName(), getTypeName()) - {} + { + } protected: void updateLocalCollections(KDAV2::DavCollection::List calendarList) Q_DECL_OVERRIDE @@ -74,21 +78,27 @@ switch (incidence->type()) { case Type::TypeEvent: { - auto remoteEvent = dynamic_cast(*incidence); - Event localEvent; localEvent.setIcal(ical); localEvent.setCalendar(calendarLocalId); - SinkTrace() << "Found an event:" << localEvent.getSummary() << "with id:" << rid; + SinkTrace() << "Found an event 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"; + case Type::TypeTodo: { + Todo localTodo; + localTodo.setIcal(ical); + localTodo.setCalendar(calendarLocalId); + + SinkTrace() << "Found a Todo with id:" << rid; + + createOrModify(ENTITY_TYPE_TODO, rid, localTodo, + /* mergeCriteria = */ QHash{}); break; + } case Type::TypeJournal: SinkWarning() << "Unimplemented add of a 'Journal' item in the Store"; break; @@ -108,58 +118,74 @@ return syncStore().resolveRemoteId(ENTITY_TYPE_CALENDAR, resourceID(calendar)); } - KAsync::Job replay(const Event &event, Sink::Operation operation, - const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + template + KAsync::Job replayItem(const Item &localItem, Sink::Operation operation, + const QByteArray &oldRemoteId, const QList &changedProperties, + const QByteArray &entityType) { - SinkLog() << "Replaying event"; + SinkLog() << "Replaying" << entityType; - KDAV2::DavItem item; + KDAV2::DavItem remoteItem; switch (operation) { case Sink::Operation_Creation: { - auto rawIcal = event.getIcal(); - if(rawIcal == "") { - return KAsync::error("No ICal in event for creation replay"); + auto rawIcal = localItem.getIcal(); + if (rawIcal == "") { + return KAsync::error("No ICal in item for creation replay"); } - auto collectionId = syncStore().resolveLocalId(ENTITY_TYPE_CALENDAR, event.getCalendar()); + auto collectionId = syncStore().resolveLocalId(ENTITY_TYPE_CALENDAR, localItem.getCalendar()); - item.setData(rawIcal); - item.setContentType("text/calendar"); - item.setUrl(urlOf(collectionId, event.getUid())); + remoteItem.setData(rawIcal); + remoteItem.setContentType("text/calendar"); + remoteItem.setUrl(urlOf(collectionId, localItem.getUid())); - SinkLog() << "Creating event:" << event.getSummary(); - return createItem(item).then([item] { return resourceID(item); }); + SinkLog() << "Creating" << entityType << ":" << localItem.getSummary(); + return createItem(remoteItem).then([remoteItem] { return resourceID(remoteItem); }); } case Sink::Operation_Removal: { // We only need the URL in the DAV item for removal - item.setUrl(urlOf(oldRemoteId)); + remoteItem.setUrl(urlOf(oldRemoteId)); - SinkLog() << "Removing event:" << oldRemoteId; - return removeItem(item).then([] { return QByteArray{}; }); + SinkLog() << "Removing" << entityType << ":" << oldRemoteId; + return removeItem(remoteItem).then([] { return QByteArray{}; }); } case Sink::Operation_Modification: - auto rawIcal = event.getIcal(); - if(rawIcal == "") { - return KAsync::error("No ICal in event for modification replay"); + auto rawIcal = localItem.getIcal(); + if (rawIcal == "") { + return KAsync::error("No ICal in item for modification replay"); } - item.setData(rawIcal); - item.setContentType("text/calendar"); - item.setUrl(urlOf(oldRemoteId)); + remoteItem.setData(rawIcal); + remoteItem.setContentType("text/calendar"); + remoteItem.setUrl(urlOf(oldRemoteId)); - SinkLog() << "Modifying event:" << event.getSummary(); + SinkLog() << "Modifying" << entityType << ":" << localItem.getSummary(); // It would be nice to check that the URL of the item hasn't // changed and move he item if it did, but since the URL is // pretty much arbitrary, whoe does that anyway? - return modifyItem(item).then([oldRemoteId] { return oldRemoteId; }); + return modifyItem(remoteItem).then([oldRemoteId] { return oldRemoteId; }); } } + KAsync::Job replay(const Event &event, Sink::Operation operation, + const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + return replayItem(event, operation, oldRemoteId, changedProperties, ENTITY_TYPE_EVENT); + } + + KAsync::Job replay(const Todo &todo, Sink::Operation operation, + const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE + { + return replayItem(todo, operation, oldRemoteId, changedProperties, ENTITY_TYPE_TODO); + } + KAsync::Job replay(const Calendar &calendar, Sink::Operation operation, const QByteArray &oldRemoteId, const QList &changedProperties) Q_DECL_OVERRIDE { + SinkLog() << "Replaying calendar"; + switch (operation) { case Sink::Operation_Creation: SinkWarning() << "Unimplemented replay of calendar creation"; @@ -180,39 +206,46 @@ CalDavResource::CalDavResource(const Sink::ResourceContext &context) : Sink::GenericResource(context) { - auto synchronizer = QSharedPointer::create(context); + auto synchronizer = QSharedPointer::create(context); setupSynchronizer(synchronizer); - setupPreprocessors(ENTITY_TYPE_EVENT, QVector() << new EventPropertyExtractor); + setupPreprocessors(ENTITY_TYPE_EVENT, QVector() << new EventPropertyExtractor); + setupPreprocessors(ENTITY_TYPE_TODO, QVector() << new TodoPropertyExtractor); } CalDavResourceFactory::CalDavResourceFactory(QObject *parent) : Sink::ResourceFactory(parent, { - Sink::ApplicationDomain::ResourceCapabilities::Event::event, Sink::ApplicationDomain::ResourceCapabilities::Event::calendar, + Sink::ApplicationDomain::ResourceCapabilities::Event::event, Sink::ApplicationDomain::ResourceCapabilities::Event::storage, + Sink::ApplicationDomain::ResourceCapabilities::Todo::todo, + Sink::ApplicationDomain::ResourceCapabilities::Todo::storage, }) -{} +{ +} Sink::Resource *CalDavResourceFactory::createResource(const Sink::ResourceContext &context) { return new CalDavResource(context); } using Sink::ApplicationDomain::Calendar; using Sink::ApplicationDomain::Event; +using Sink::ApplicationDomain::Todo; void CalDavResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory) { factory.registerFacade>(resourceName); + factory.registerFacade>(resourceName); factory.registerFacade>(resourceName); } void CalDavResourceFactory::registerAdaptorFactories( const QByteArray &resourceName, Sink::AdaptorFactoryRegistry ®istry) { registry.registerFactory>(resourceName); + registry.registerFactory>(resourceName); registry.registerFactory>(resourceName); } diff --git a/examples/caldavresource/tests/caldavtest.cpp b/examples/caldavresource/tests/caldavtest.cpp --- a/examples/caldavresource/tests/caldavtest.cpp +++ b/examples/caldavresource/tests/caldavtest.cpp @@ -22,13 +22,15 @@ using Sink::ApplicationDomain::Calendar; using Sink::ApplicationDomain::DummyResource; using Sink::ApplicationDomain::Event; +using Sink::ApplicationDomain::Todo; using Sink::ApplicationDomain::SinkResource; class CalDavTest : public QObject { Q_OBJECT - // This test assumes a calendar MyCalendar with one event in it. + // This test assumes a calendar MyCalendar with one event and one todo in + // it. const QString baseUrl = "http://localhost/dav/calendars/users/doe"; const QString username = "doe"; @@ -47,6 +49,7 @@ QByteArray mResourceInstanceIdentifier; QString addedEventUid; + QString addedTodoUid; private slots: @@ -74,6 +77,7 @@ 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))); + VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); } void testSyncCalEmpty() @@ -83,7 +87,11 @@ auto eventJob = Sink::Store::fetchAll(Sink::Query().request()) .then([](const QList &events) { QCOMPARE(events.size(), 1); }); + auto todoJob = Sink::Store::fetchAll(Sink::Query().request()) + .then([](const QList &todos) { QCOMPARE(todos.size(), 1); }); + VERIFYEXEC(eventJob); + VERIFYEXEC(todoJob); auto calendarJob = Sink::Store::fetchAll(Sink::Query().request()) .then([](const QList &calendars) { @@ -136,6 +144,44 @@ VERIFYEXEC(verifyEventJob); } + void testAddTodo() + { + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); + + auto job = Sink::Store::fetchOne({}).exec(); + job.waitForFinished(); + QVERIFY2(!job.errorCode(), "Fetching Calendar failed"); + auto calendar = job.value(); + + auto todo = QSharedPointer::create(); + todo->setSummary("Hello"); + todo->setDtStart(QDateTime::currentDateTime()); + todo->setCreated(QDateTime::currentDateTime()); + addedTodoUid = QUuid::createUuid().toString(); + todo->setUid(addedTodoUid); + + auto ical = KCalCore::ICalFormat().toICalString(todo); + Todo sinkTodo(mResourceInstanceIdentifier); + sinkTodo.setIcal(ical.toUtf8()); + sinkTodo.setCalendar(calendar); + + SinkLog() << "Adding todo"; + VERIFYEXEC(Sink::Store::create(sinkTodo)); + VERIFYEXEC(Sink::ResourceControl::flushReplayQueue(mResourceInstanceIdentifier)); + + auto verifyTodoCountJob = + Sink::Store::fetchAll(Sink::Query().request()).then([](const QList &todos) { + QCOMPARE(todos.size(), 2); + }); + VERIFYEXEC(verifyTodoCountJob); + + auto verifyTodoJob = + Sink::Store::fetchOne(Sink::Query().filter("uid", Sink::Query::Comparator(addedTodoUid))) + .then([](const Todo &todo) { QCOMPARE(todo.getSummary(), {"Hello"}); }); + VERIFYEXEC(verifyTodoJob); + } + void testModifyEvent() { VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); @@ -173,14 +219,50 @@ VERIFYEXEC(verifyEventJob); } + void testModifyTodo() + { + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); + + auto job = Sink::Store::fetchOne( + Sink::Query().filter("uid", Sink::Query::Comparator(addedTodoUid))) + .exec(); + job.waitForFinished(); + QVERIFY2(!job.errorCode(), "Fetching Todo failed"); + auto todo = job.value(); + + auto incidence = KCalCore::ICalFormat().readIncidence(todo.getIcal()); + auto caltodo = incidence.dynamicCast(); + QVERIFY2(caltodo, "Cannot convert to KCalCore todo"); + + caltodo->setSummary("Hello World!"); + auto dummy = QSharedPointer(caltodo); + auto newical = KCalCore::ICalFormat().toICalString(dummy); + + todo.setIcal(newical.toUtf8()); + + VERIFYEXEC(Sink::Store::modify(todo)); + + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); + + auto verifyTodoCountJob = Sink::Store::fetchAll({}).then( + [](const QList &todos) { QCOMPARE(todos.size(), 2); }); + VERIFYEXEC(verifyTodoCountJob); + + auto verifyTodoJob = + Sink::Store::fetchOne(Sink::Query().filter("uid", Sink::Query::Comparator(addedTodoUid))) + .then([](const Todo &todo) { QCOMPARE(todo.getSummary(), {"Hello World!"}); }); + VERIFYEXEC(verifyTodoJob); + } + void testSneakyModifyEvent() { VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); // Change the item without sink's knowledge { - qWarning() << 1; auto collection = ([this]() -> KDAV2::DavCollection { QUrl url(baseUrl); url.setUserName(username); @@ -212,22 +294,17 @@ return itemFetchJob.item(); })(); - qWarning() << 3; auto incidence = KCalCore::ICalFormat().readIncidence(davitem.data()); auto calevent = incidence.dynamicCast(); QVERIFY2(calevent, "Cannot convert to KCalCore event"); - qWarning() << 4; calevent->setSummary("Manual Hello World!"); auto newical = KCalCore::ICalFormat().toICalString(calevent); - qWarning() << 5; davitem.setData(newical.toUtf8()); KDAV2::DavItemModifyJob itemModifyJob(davitem); itemModifyJob.exec(); QVERIFY2(itemModifyJob.error() == 0, "Cannot modify item"); - - qWarning() << 6; } // Try to change the item with sink @@ -274,6 +351,26 @@ [](const QList &events) { QCOMPARE(events.size(), 1); }); VERIFYEXEC(verifyEventCountJob); } + + void testRemoveTodo() + { + VERIFYEXEC(Sink::Store::synchronize(Sink::Query().resourceFilter(mResourceInstanceIdentifier))); + VERIFYEXEC(Sink::ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); + + auto job = Sink::Store::fetchOne( + Sink::Query().filter("uid", Sink::Query::Comparator(addedTodoUid))) + .exec(); + job.waitForFinished(); + QVERIFY2(!job.errorCode(), "Fetching Todo failed"); + auto todo = job.value(); + + VERIFYEXEC(Sink::Store::remove(todo)); + VERIFYEXEC(Sink::ResourceControl::flushReplayQueue(mResourceInstanceIdentifier)); + + auto verifyTodoCountJob = Sink::Store::fetchAll({}).then( + [](const QList &todos) { QCOMPARE(todos.size(), 1); }); + VERIFYEXEC(verifyTodoCountJob); + } }; QTEST_MAIN(CalDavTest)