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 ®istry) 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 }