File indexing completed on 2024-11-17 04:45:00
0001 /* 0002 SPDX-FileCopyrightText: 2011-2013 Daniel Vrátil <dvratil@redhat.com> 0003 SPDX-FileCopyrightText: 2020 Igor Poboiko <igor.poboiko@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-3.0-or-later 0006 */ 0007 0008 #include "calendarhandler.h" 0009 #include "defaultreminderattribute.h" 0010 #include "googlecalendar_debug.h" 0011 #include "googleresource.h" 0012 #include "googlesettings.h" 0013 0014 #include <Akonadi/BlockAlarmsAttribute> 0015 #include <Akonadi/CollectionColorAttribute> 0016 #include <Akonadi/CollectionModifyJob> 0017 #include <Akonadi/EntityDisplayAttribute> 0018 #include <Akonadi/ItemModifyJob> 0019 0020 #include <KGAPI/Account> 0021 #include <KGAPI/Calendar/Calendar> 0022 #include <KGAPI/Calendar/CalendarCreateJob> 0023 #include <KGAPI/Calendar/CalendarDeleteJob> 0024 #include <KGAPI/Calendar/CalendarFetchJob> 0025 #include <KGAPI/Calendar/CalendarModifyJob> 0026 #include <KGAPI/Calendar/Event> 0027 #include <KGAPI/Calendar/EventCreateJob> 0028 #include <KGAPI/Calendar/EventDeleteJob> 0029 #include <KGAPI/Calendar/EventFetchJob> 0030 #include <KGAPI/Calendar/EventModifyJob> 0031 #include <KGAPI/Calendar/EventMoveJob> 0032 #include <KGAPI/Calendar/FreeBusyQueryJob> 0033 0034 #include <KCalendarCore/Calendar> 0035 #include <KCalendarCore/FreeBusy> 0036 #include <KCalendarCore/ICalFormat> 0037 0038 using namespace KGAPI2; 0039 using namespace Akonadi; 0040 0041 QString CalendarHandler::mimeType() 0042 { 0043 return KCalendarCore::Event::eventMimeType(); 0044 } 0045 0046 bool CalendarHandler::canPerformTask(const Item &item) 0047 { 0048 return GenericHandler::canPerformTask<KCalendarCore::Event::Ptr>(item); 0049 } 0050 0051 bool CalendarHandler::canPerformTask(const Item::List &items) 0052 { 0053 return GenericHandler::canPerformTask<KCalendarCore::Event::Ptr>(items); 0054 } 0055 0056 void CalendarHandler::setupCollection(Collection &collection, const CalendarPtr &calendar) 0057 { 0058 collection.setContentMimeTypes({mimeType()}); 0059 collection.setName(calendar->uid()); 0060 collection.setRemoteId(calendar->uid()); 0061 if (calendar->editable()) { 0062 collection.setRights(Collection::CanChangeCollection | Collection::CanDeleteCollection | Collection::CanCreateItem | Collection::CanChangeItem 0063 | Collection::CanDeleteItem); 0064 } else { 0065 collection.setRights(Collection::ReadOnly); 0066 } 0067 // TODO: for some reason, KOrganizer creates virtual collections 0068 // newCollection.setVirtual(false); 0069 // Setting icon 0070 auto attr = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing); 0071 attr->setDisplayName(calendar->title()); 0072 attr->setIconName(QStringLiteral("view-calendar")); 0073 // Setting color 0074 if (calendar->backgroundColor().isValid()) { 0075 auto colorAttr = collection.attribute<CollectionColorAttribute>(Collection::AddIfMissing); 0076 colorAttr->setColor(calendar->backgroundColor()); 0077 } 0078 // Setting default reminders 0079 auto reminderAttr = collection.attribute<DefaultReminderAttribute>(Collection::AddIfMissing); 0080 reminderAttr->setReminders(calendar->defaultReminders()); 0081 // Block email reminders, since Google sends them for us 0082 auto blockAlarms = collection.attribute<BlockAlarmsAttribute>(Collection::AddIfMissing); 0083 blockAlarms->blockAlarmType(KCalendarCore::Alarm::Audio, false); 0084 blockAlarms->blockAlarmType(KCalendarCore::Alarm::Display, false); 0085 blockAlarms->blockAlarmType(KCalendarCore::Alarm::Procedure, false); 0086 } 0087 0088 void CalendarHandler::retrieveCollections(const Collection &rootCollection) 0089 { 0090 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving calendars")); 0091 qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieving calendars..."; 0092 auto job = new CalendarFetchJob(m_settings->accountPtr(), this); 0093 connect(job, &CalendarFetchJob::finished, this, [this, rootCollection](KGAPI2::Job *job) { 0094 if (!m_iface->handleError(job)) { 0095 return; 0096 } 0097 qCDebug(GOOGLE_CALENDAR_LOG) << "Calendars retrieved"; 0098 0099 const ObjectsList calendars = qobject_cast<CalendarFetchJob *>(job)->items(); 0100 Collection::List collections; 0101 collections.reserve(calendars.count()); 0102 const QStringList activeCalendars = m_settings->calendars(); 0103 for (const auto &object : calendars) { 0104 const CalendarPtr &calendar = object.dynamicCast<Calendar>(); 0105 qCDebug(GOOGLE_CALENDAR_LOG) << " -" << calendar->title() << "(" << calendar->uid() << ")"; 0106 if (!activeCalendars.contains(calendar->uid())) { 0107 qCDebug(GOOGLE_CALENDAR_LOG) << "Skipping, not subscribed"; 0108 continue; 0109 } 0110 Collection collection; 0111 setupCollection(collection, calendar); 0112 collection.setParentCollection(rootCollection); 0113 collections << collection; 0114 } 0115 0116 m_iface->collectionsRetrievedFromHandler(collections); 0117 }); 0118 } 0119 0120 void CalendarHandler::retrieveItems(const Collection &collection) 0121 { 0122 qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieving events for calendar" << collection.remoteId(); 0123 const QString syncToken = collection.remoteRevision(); 0124 auto job = new EventFetchJob(collection.remoteId(), m_settings->accountPtr(), this); 0125 if (!syncToken.isEmpty()) { 0126 qCDebug(GOOGLE_CALENDAR_LOG) << "Using sync token" << syncToken; 0127 job->setSyncToken(syncToken); 0128 job->setFetchDeleted(true); 0129 } else { 0130 // No need to fetch deleted items for non-incremental update 0131 job->setFetchDeleted(false); 0132 if (!m_settings->eventsSince().isEmpty()) { 0133 const QDate date = QDate::fromString(m_settings->eventsSince(), Qt::ISODate); 0134 job->setTimeMin(QDateTime(date.startOfDay()).toSecsSinceEpoch()); 0135 } 0136 } 0137 0138 job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); 0139 connect(job, &EventFetchJob::finished, this, &CalendarHandler::slotItemsRetrieved); 0140 0141 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving events for calendar '%1'", collection.displayName())); 0142 } 0143 0144 void CalendarHandler::slotItemsRetrieved(KGAPI2::Job *job) 0145 { 0146 if (!m_iface->handleError(job)) { 0147 return; 0148 } 0149 Item::List changedItems, removedItems; 0150 auto collection = job->property(COLLECTION_PROPERTY).value<Collection>(); 0151 auto attr = collection.attribute<DefaultReminderAttribute>(); 0152 0153 auto fetchJob = qobject_cast<EventFetchJob *>(job); 0154 const ObjectsList objects = fetchJob->items(); 0155 bool isIncremental = !fetchJob->syncToken().isEmpty(); 0156 qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieved" << objects.count() << "events for calendar" << collection.remoteId(); 0157 changedItems.reserve(objects.count()); 0158 for (const ObjectPtr &object : objects) { 0159 const EventPtr event = object.dynamicCast<Event>(); 0160 if (event->useDefaultReminders() && attr) { 0161 const KCalendarCore::Alarm::List alarms = attr->alarms(event.data()); 0162 for (const KCalendarCore::Alarm::Ptr &alarm : alarms) { 0163 event->addAlarm(alarm); 0164 } 0165 } 0166 0167 Item item; 0168 item.setMimeType(mimeType()); 0169 item.setParentCollection(collection); 0170 item.setRemoteId(event->id()); 0171 item.setRemoteRevision(event->etag()); 0172 item.setPayload<KCalendarCore::Event::Ptr>(event.dynamicCast<KCalendarCore::Event>()); 0173 0174 if (event->deleted()) { 0175 qCDebug(GOOGLE_CALENDAR_LOG) << " - removed" << event->uid(); 0176 removedItems << item; 0177 } else { 0178 qCDebug(GOOGLE_CALENDAR_LOG) << " - changed" << event->uid(); 0179 changedItems << item; 0180 } 0181 } 0182 0183 if (!isIncremental) { 0184 m_iface->itemsRetrieved(changedItems); 0185 } else { 0186 m_iface->itemsRetrievedIncremental(changedItems, removedItems); 0187 } 0188 qCDebug(GOOGLE_CALENDAR_LOG) << "Next sync token:" << fetchJob->syncToken(); 0189 collection.setRemoteRevision(fetchJob->syncToken()); 0190 new CollectionModifyJob(collection, this); 0191 0192 emitReadyStatus(); 0193 } 0194 0195 void CalendarHandler::itemAdded(const Item &item, const Collection &collection) 0196 { 0197 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Adding event to calendar '%1'", collection.name())); 0198 qCDebug(GOOGLE_CALENDAR_LOG) << "Event added to calendar" << collection.remoteId(); 0199 EventPtr event(new Event(*item.payload<KCalendarCore::Event::Ptr>())); 0200 auto job = new EventCreateJob(event, collection.remoteId(), m_settings->accountPtr(), this); 0201 job->setSendUpdates(SendUpdatesPolicy::None); 0202 connect(job, &EventCreateJob::finished, this, [this, item](KGAPI2::Job *job) { 0203 if (!m_iface->handleError(job)) { 0204 return; 0205 } 0206 Item newItem(item); 0207 const EventPtr event = qobject_cast<EventCreateJob *>(job)->items().first().dynamicCast<Event>(); 0208 qCDebug(GOOGLE_CALENDAR_LOG) << "Event added"; 0209 newItem.setRemoteId(event->id()); 0210 newItem.setRemoteRevision(event->etag()); 0211 newItem.setGid(event->uid()); 0212 m_iface->itemChangeCommitted(newItem); 0213 newItem.setPayload<KCalendarCore::Event::Ptr>(event.dynamicCast<KCalendarCore::Event>()); 0214 new ItemModifyJob(newItem, this); 0215 emitReadyStatus(); 0216 }); 0217 } 0218 0219 void CalendarHandler::itemChanged(const Item &item, const QSet<QByteArray> & /*partIdentifiers*/) 0220 { 0221 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Changing event in calendar '%1'", item.parentCollection().displayName())); 0222 qCDebug(GOOGLE_CALENDAR_LOG) << "Changing event" << item.remoteId(); 0223 EventPtr event(new Event(*item.payload<KCalendarCore::Event::Ptr>())); 0224 auto job = new EventModifyJob(event, item.parentCollection().remoteId(), m_settings->accountPtr(), this); 0225 job->setSendUpdates(SendUpdatesPolicy::None); 0226 job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); 0227 connect(job, &EventModifyJob::finished, this, &CalendarHandler::slotGenericJobFinished); 0228 } 0229 0230 void CalendarHandler::itemsRemoved(const Item::List &items) 0231 { 0232 m_iface->emitStatus(AgentBase::Running, i18ncp("@info:status", "Removing %1 event", "Removing %1 events", items.count())); 0233 QStringList eventIds; 0234 eventIds.reserve(items.count()); 0235 std::transform(items.cbegin(), items.cend(), std::back_inserter(eventIds), [](const Item &item) { 0236 return item.remoteId(); 0237 }); 0238 qCDebug(GOOGLE_CALENDAR_LOG) << "Removing events:" << eventIds; 0239 auto job = new EventDeleteJob(eventIds, items.first().parentCollection().remoteId(), m_settings->accountPtr(), this); 0240 job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); 0241 connect(job, &EventDeleteJob::finished, this, &CalendarHandler::slotGenericJobFinished); 0242 } 0243 0244 void CalendarHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) 0245 { 0246 m_iface->emitStatus(AgentBase::Running, 0247 i18ncp("@info:status", 0248 "Moving %1 event from calendar '%2' to calendar '%3'", 0249 "Moving %1 events from calendar '%2' to calendar '%3'", 0250 items.count(), 0251 collectionSource.displayName(), 0252 collectionDestination.displayName())); 0253 QStringList eventIds; 0254 eventIds.reserve(items.count()); 0255 std::transform(items.cbegin(), items.cend(), std::back_inserter(eventIds), [](const Item &item) { 0256 return item.remoteId(); 0257 }); 0258 qCDebug(GOOGLE_CALENDAR_LOG) << "Moving events" << eventIds << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); 0259 auto job = new EventMoveJob(eventIds, collectionSource.remoteId(), collectionDestination.remoteId(), m_settings->accountPtr(), this); 0260 job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); 0261 connect(job, &EventMoveJob::finished, this, &CalendarHandler::slotGenericJobFinished); 0262 } 0263 0264 void CalendarHandler::collectionAdded(const Collection &collection, const Collection & /*parent*/) 0265 { 0266 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Creating calendar '%1'", collection.displayName())); 0267 qCDebug(GOOGLE_CALENDAR_LOG) << "Adding calendar" << collection.displayName(); 0268 CalendarPtr calendar(new Calendar()); 0269 calendar->setTitle(collection.displayName()); 0270 calendar->setEditable(true); 0271 0272 auto job = new CalendarCreateJob(calendar, m_settings->accountPtr(), this); 0273 connect(job, &CalendarCreateJob::finished, this, [this, collection](KGAPI2::Job *job) { 0274 if (!m_iface->handleError(job)) { 0275 return; 0276 } 0277 CalendarPtr calendar = qobject_cast<CalendarCreateJob *>(job)->items().first().dynamicCast<Calendar>(); 0278 qCDebug(GOOGLE_CALENDAR_LOG) << "Created calendar" << calendar->uid(); 0279 // Enable newly added calendar in settings, otherwise user won't see it 0280 m_settings->addCalendar(calendar->uid()); 0281 // TODO: the calendar returned by google is almost empty, i.e. it's not "editable", 0282 // does not contain the color, etc 0283 calendar->setEditable(true); 0284 // Populate remoteId & other stuff 0285 Collection newCollection(collection); 0286 setupCollection(newCollection, calendar); 0287 m_iface->collectionChangeCommitted(newCollection); 0288 emitReadyStatus(); 0289 }); 0290 } 0291 0292 void CalendarHandler::collectionChanged(const Collection &collection) 0293 { 0294 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Changing calendar '%1'", collection.displayName())); 0295 qCDebug(GOOGLE_CALENDAR_LOG) << "Changing calendar" << collection.remoteId(); 0296 CalendarPtr calendar(new Calendar()); 0297 calendar->setUid(collection.remoteId()); 0298 calendar->setTitle(collection.displayName()); 0299 calendar->setEditable(true); 0300 auto job = new CalendarModifyJob(calendar, m_settings->accountPtr(), this); 0301 job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); 0302 connect(job, &CalendarModifyJob::finished, this, &CalendarHandler::slotGenericJobFinished); 0303 } 0304 0305 void CalendarHandler::collectionRemoved(const Collection &collection) 0306 { 0307 m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Removing calendar '%1'", collection.displayName())); 0308 qCDebug(GOOGLE_CALENDAR_LOG) << "Removing calendar" << collection.remoteId(); 0309 auto job = new CalendarDeleteJob(collection.remoteId(), m_settings->accountPtr(), this); 0310 job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); 0311 connect(job, &CalendarDeleteJob::finished, this, &CalendarHandler::slotGenericJobFinished); 0312 } 0313 0314 /** 0315 * FreeBusy 0316 */ 0317 FreeBusyHandler::FreeBusyHandler(GoogleResourceStateInterface *iface, GoogleSettings *settings) 0318 : m_iface(iface) 0319 , m_settings(settings) 0320 { 0321 } 0322 0323 QDateTime FreeBusyHandler::lastCacheUpdate() const 0324 { 0325 return {}; 0326 } 0327 0328 void FreeBusyHandler::canHandleFreeBusy(const QString &email) 0329 { 0330 if (m_iface->canPerformTask()) { 0331 m_iface->handlesFreeBusy(email, false); 0332 return; 0333 } 0334 0335 auto job = new FreeBusyQueryJob(email, QDateTime::currentDateTimeUtc(), QDateTime::currentDateTimeUtc().addSecs(3600), m_settings->accountPtr(), this); 0336 connect(job, &FreeBusyQueryJob::finished, this, [this](KGAPI2::Job *job) { 0337 auto queryJob = qobject_cast<FreeBusyQueryJob *>(job); 0338 if (!m_iface->handleError(job, false)) { 0339 m_iface->handlesFreeBusy(queryJob->id(), false); 0340 return; 0341 } 0342 m_iface->handlesFreeBusy(queryJob->id(), true); 0343 }); 0344 } 0345 0346 void FreeBusyHandler::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) 0347 { 0348 if (m_iface->canPerformTask()) { 0349 m_iface->freeBusyRetrieved(email, QString(), false, QString()); 0350 return; 0351 } 0352 0353 auto job = new FreeBusyQueryJob(email, start, end, m_settings->accountPtr(), this); 0354 connect(job, &FreeBusyQueryJob::finished, this, [this](KGAPI2::Job *job) { 0355 auto queryJob = qobject_cast<FreeBusyQueryJob *>(job); 0356 0357 if (!m_iface->handleError(job, false)) { 0358 m_iface->freeBusyRetrieved(queryJob->id(), QString(), false, QString()); 0359 return; 0360 } 0361 0362 KCalendarCore::FreeBusy::Ptr fb(new KCalendarCore::FreeBusy); 0363 fb->setUid(QStringLiteral("%1%2@google.com").arg(QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMddTHHmmssZ")))); 0364 fb->setOrganizer(job->account()->accountName()); 0365 fb->addAttendee(KCalendarCore::Attendee(QString(), queryJob->id())); 0366 // FIXME: is it really sort? 0367 fb->setDateTime(QDateTime::currentDateTimeUtc(), KCalendarCore::IncidenceBase::RoleSort); 0368 const auto ranges = queryJob->busy(); 0369 for (const auto &range : ranges) { 0370 fb->addPeriod(range.busyStart, range.busyEnd); 0371 } 0372 0373 KCalendarCore::ICalFormat format; 0374 const QString fbStr = format.createScheduleMessage(fb, KCalendarCore::iTIPRequest); 0375 0376 m_iface->freeBusyRetrieved(queryJob->id(), fbStr, true, QString()); 0377 }); 0378 } 0379 0380 #include "moc_calendarhandler.cpp"