File indexing completed on 2025-01-05 04:58:36

0001 /*
0002  *   Copyright (C) 2018 Christian Mollekopf <chrigi_1@fastmail.fm>
0003  *
0004  *   This program is free software; you can redistribute it and/or modify
0005  *   it under the terms of the GNU General Public License as published by
0006  *   the Free Software Foundation; either version 2 of the License, or
0007  *   (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details.
0013  *
0014  *   You should have received a copy of the GNU General Public License
0015  *   along with this program; if not, write to the
0016  *   Free Software Foundation, Inc.,
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
0018  */
0019 
0020 #include "caldavresource.h"
0021 
0022 #include "../webdavcommon/webdav.h"
0023 
0024 #include "adaptorfactoryregistry.h"
0025 #include "applicationdomaintype.h"
0026 #include "domainadaptor.h"
0027 #include "eventpreprocessor.h"
0028 #include "todopreprocessor.h"
0029 #include "facade.h"
0030 #include "facadefactory.h"
0031 
0032 #include <QColor>
0033 
0034 #define ENTITY_TYPE_EVENT "event"
0035 #define ENTITY_TYPE_TODO "todo"
0036 #define ENTITY_TYPE_CALENDAR "calendar"
0037 
0038 using Sink::ApplicationDomain::getTypeName;
0039 using namespace Sink;
0040 
0041 class CalDAVSynchronizer : public WebDavSynchronizer
0042 {
0043     using Event = Sink::ApplicationDomain::Event;
0044     using Todo = Sink::ApplicationDomain::Todo;
0045     using Calendar = Sink::ApplicationDomain::Calendar;
0046 
0047 public:
0048     explicit CalDAVSynchronizer(const Sink::ResourceContext &context)
0049         : WebDavSynchronizer(context, KDAV2::CalDav, getTypeName<Calendar>(), {getTypeName<Event>(), getTypeName<Todo>()})
0050     {
0051     }
0052 
0053 protected:
0054     void updateLocalCollections(KDAV2::DavCollection::List calendarList) Q_DECL_OVERRIDE
0055     {
0056         SinkLog() << "Found" << calendarList.size() << "calendar(s)";
0057 
0058         QVector<QByteArray> ridList;
0059         for (const auto &remoteCalendar : calendarList) {
0060             const auto &rid = resourceID(remoteCalendar);
0061 
0062             Calendar localCalendar;
0063             localCalendar.setName(remoteCalendar.displayName());
0064             localCalendar.setColor(remoteCalendar.color().name().toLatin1());
0065 
0066             if (remoteCalendar.contentTypes() & KDAV2::DavCollection::Events) {
0067                 localCalendar.setContentTypes({"event"});
0068             }
0069             if (remoteCalendar.contentTypes() & KDAV2::DavCollection::Todos) {
0070                 localCalendar.setContentTypes({"todo"});
0071             }
0072             if (remoteCalendar.contentTypes() & KDAV2::DavCollection::Calendar) {
0073                 localCalendar.setContentTypes({"event", "todo"});
0074             }
0075 
0076             const auto sinkId = syncStore().resolveRemoteId(ENTITY_TYPE_CALENDAR, rid);
0077             const auto found = store().contains(ENTITY_TYPE_CALENDAR, sinkId);
0078             SinkLog() << "Found calendar:" << remoteCalendar.displayName() << "[" << rid << "]" << remoteCalendar.contentTypes() << (found ? " (existing)" : "");
0079 
0080             //Set default when creating, otherwise don't touch
0081             if (!found) {
0082                 localCalendar.setEnabled(false);
0083             }
0084 
0085             createOrModify(ENTITY_TYPE_CALENDAR, rid, localCalendar);
0086         }
0087     }
0088 
0089     void updateLocalItem(const KDAV2::DavItem &remoteItem, const QByteArray &calendarLocalId) Q_DECL_OVERRIDE
0090     {
0091         const auto rid = resourceID(remoteItem);
0092 
0093         const auto ical = remoteItem.data();
0094 
0095         if (ical.contains("BEGIN:VEVENT")) {
0096             Event localEvent;
0097             localEvent.setIcal(ical);
0098             localEvent.setCalendar(calendarLocalId);
0099 
0100             SinkTrace() << "Found an event with id:" << rid;
0101 
0102             createOrModify(ENTITY_TYPE_EVENT, rid, localEvent, {});
0103         } else if (ical.contains("BEGIN:VTODO")) {
0104             Todo localTodo;
0105             localTodo.setIcal(ical);
0106             localTodo.setCalendar(calendarLocalId);
0107 
0108             SinkTrace() << "Found a Todo with id:" << rid;
0109 
0110             createOrModify(ENTITY_TYPE_TODO, rid, localTodo, {});
0111         } else {
0112             SinkWarning() << "Trying to add a 'Unknown' item";
0113         }
0114     }
0115 
0116     template<typename Item>
0117     KAsync::Job<QByteArray> replayItem(const Item &localItem, Sink::Operation operation,
0118         const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties,
0119         const QByteArray &entityType)
0120     {
0121         SinkLog() << "Replaying" << entityType;
0122 
0123         KDAV2::DavItem remoteItem;
0124 
0125         switch (operation) {
0126             case Sink::Operation_Creation: {
0127                 auto rawIcal = localItem.getIcal();
0128                 if (rawIcal.isEmpty()) {
0129                     return KAsync::error<QByteArray>("No ICal in item for creation replay");
0130                 }
0131                 return createItem(rawIcal, "text/calendar", localItem.getUid().toUtf8() + ".ics", syncStore().resolveLocalId(ENTITY_TYPE_CALENDAR, localItem.getCalendar()));
0132             }
0133             case Sink::Operation_Removal: {
0134                 return removeItem(oldRemoteId);
0135             }
0136             case Sink::Operation_Modification:
0137                 auto rawIcal = localItem.getIcal();
0138                 if (rawIcal.isEmpty()) {
0139                     return KAsync::error<QByteArray>("No ICal in item for modification replay");
0140                 }
0141 
0142                 //Not pretty but all ical types happen to have a calendar property of the same name.
0143                 if (changedProperties.contains(ApplicationDomain::Event::Calendar::name)) {
0144                     return moveItem(rawIcal, "text/calendar", localItem.getUid().toUtf8() + ".ics", syncStore().resolveLocalId(ENTITY_TYPE_CALENDAR, localItem.getCalendar()), oldRemoteId);
0145                 }
0146 
0147                 return modifyItem(oldRemoteId, rawIcal, "text/calendar", syncStore().resolveLocalId(ENTITY_TYPE_CALENDAR, localItem.getCalendar()));
0148         }
0149         return KAsync::null<QByteArray>();
0150     }
0151 
0152     KAsync::Job<QByteArray> replay(const Event &event, Sink::Operation operation,
0153         const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
0154     {
0155         return replayItem(event, operation, oldRemoteId, changedProperties, ENTITY_TYPE_EVENT);
0156     }
0157 
0158     KAsync::Job<QByteArray> replay(const Todo &todo, Sink::Operation operation,
0159         const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
0160     {
0161         return replayItem(todo, operation, oldRemoteId, changedProperties, ENTITY_TYPE_TODO);
0162     }
0163 
0164     KAsync::Job<QByteArray> replay(const Calendar &calendar, Sink::Operation operation,
0165         const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE
0166     {
0167         SinkLog() << "Replaying calendar" << changedProperties;
0168 
0169         switch (operation) {
0170             case Sink::Operation_Creation: {
0171                 SinkLog() << "Replaying calendar creation";
0172                 KDAV2::DavCollection collection;
0173 
0174                 collection.setDisplayName(calendar.getName());
0175                 //Default to allowing both
0176                 collection.setContentTypes(KDAV2::DavCollection::Todos | KDAV2::DavCollection::Events);
0177                 if (calendar.getContentTypes().contains("event")) {
0178                     collection.setContentTypes(KDAV2::DavCollection::Events);
0179                 }
0180                 if (calendar.getContentTypes().contains("todo")) {
0181                     collection.setContentTypes(KDAV2::DavCollection::Todos);
0182                 }
0183 
0184                 return createCollection(collection, KDAV2::CalDav);
0185             }
0186             case Sink::Operation_Removal:
0187                 SinkLog() << "Replaying calendar removal";
0188                 return removeCollection(oldRemoteId);
0189             case Sink::Operation_Modification: {
0190                 SinkLog() << "Replaying calendar modification";
0191                 if (calendar.getEnabled() && changedProperties.contains(Calendar::Enabled::name)) {
0192                     //Trigger synchronization of that calendar
0193                     Query scope;
0194                     scope.setType<Event>();
0195                     scope.filter<Event::Calendar>(calendar);
0196                     synchronize(scope);
0197                     if (changedProperties.size() == 1) {
0198                         return KAsync::value(oldRemoteId);
0199                     }
0200                 }
0201                 KDAV2::DavCollection collection;
0202                 collection.setDisplayName(calendar.getName());
0203                 collection.setColor(QColor{QString{calendar.getColor()}});
0204                 if (calendar.getContentTypes().contains("event")) {
0205                     collection.setContentTypes(KDAV2::DavCollection::Events);
0206                 }
0207                 if (calendar.getContentTypes().contains("todo")) {
0208                     collection.setContentTypes(KDAV2::DavCollection::Todos);
0209                 }
0210                 return modifyCollection(oldRemoteId, collection);
0211             }
0212         }
0213 
0214         return KAsync::null<QByteArray>();
0215     }
0216 };
0217 
0218 class CollectionCleanupPreprocessor : public Sink::Preprocessor
0219 {
0220 public:
0221     void deletedEntity(const ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE
0222     {
0223         //Remove all events of a collection when removing the collection.
0224         const auto revision = entityStore().maxRevision();
0225         entityStore().indexLookup<ApplicationDomain::Event, ApplicationDomain::Event::Calendar>(oldEntity.identifier(), [&] (const QByteArray &identifier) {
0226             deleteEntity(ApplicationDomain::ApplicationDomainType{{}, identifier, revision, {}}, ApplicationDomain::getTypeName<ApplicationDomain::Event>(), false);
0227         });
0228         entityStore().indexLookup<ApplicationDomain::Todo, ApplicationDomain::Event::Calendar>(oldEntity.identifier(), [&] (const QByteArray &identifier) {
0229             deleteEntity(ApplicationDomain::ApplicationDomainType{{}, identifier, revision, {}}, ApplicationDomain::getTypeName<ApplicationDomain::Todo>(), false);
0230         });
0231     }
0232 };
0233 
0234 CalDavResource::CalDavResource(const Sink::ResourceContext &context)
0235     : Sink::GenericResource(context)
0236 {
0237     auto synchronizer = QSharedPointer<CalDAVSynchronizer>::create(context);
0238     setupSynchronizer(synchronizer);
0239 
0240     setupPreprocessors(ENTITY_TYPE_EVENT, {new EventPropertyExtractor});
0241     setupPreprocessors(ENTITY_TYPE_TODO, {new TodoPropertyExtractor});
0242     setupPreprocessors(ENTITY_TYPE_CALENDAR, {new CollectionCleanupPreprocessor});
0243 }
0244 
0245 CalDavResourceFactory::CalDavResourceFactory(QObject *parent)
0246     : Sink::ResourceFactory(parent, {
0247                                         Sink::ApplicationDomain::ResourceCapabilities::Event::calendar,
0248                                         Sink::ApplicationDomain::ResourceCapabilities::Event::event,
0249                                         Sink::ApplicationDomain::ResourceCapabilities::Event::storage,
0250                                         Sink::ApplicationDomain::ResourceCapabilities::Todo::todo,
0251                                         Sink::ApplicationDomain::ResourceCapabilities::Todo::storage,
0252                                     })
0253 {
0254 }
0255 
0256 Sink::Resource *CalDavResourceFactory::createResource(const Sink::ResourceContext &context)
0257 {
0258     return new CalDavResource(context);
0259 }
0260 
0261 using Sink::ApplicationDomain::Calendar;
0262 using Sink::ApplicationDomain::Event;
0263 using Sink::ApplicationDomain::Todo;
0264 
0265 void CalDavResourceFactory::registerFacades(const QByteArray &resourceName, Sink::FacadeFactory &factory)
0266 {
0267     factory.registerFacade<Event, Sink::DefaultFacade<Event>>(resourceName);
0268     factory.registerFacade<Todo, Sink::DefaultFacade<Todo>>(resourceName);
0269     factory.registerFacade<Calendar, Sink::DefaultFacade<Calendar>>(resourceName);
0270 }
0271 
0272 
0273 void CalDavResourceFactory::registerAdaptorFactories(
0274     const QByteArray &resourceName, Sink::AdaptorFactoryRegistry &registry)
0275 {
0276     registry.registerFactory<Event, DefaultAdaptorFactory<Event>>(resourceName);
0277     registry.registerFactory<Todo, DefaultAdaptorFactory<Todo>>(resourceName);
0278     registry.registerFactory<Calendar, DefaultAdaptorFactory<Calendar>>(resourceName);
0279 }
0280 
0281 void CalDavResourceFactory::removeDataFromDisk(const QByteArray &instanceIdentifier)
0282 {
0283     CalDavResource::removeFromDisk(instanceIdentifier);
0284 }