diff --git a/resources/google-new/calendarhandler.cpp b/resources/google-new/calendarhandler.cpp index 97b79f521..809c3e2b5 100644 --- a/resources/google-new/calendarhandler.cpp +++ b/resources/google-new/calendarhandler.cpp @@ -1,377 +1,370 @@ /* Copyright (C) 2011-2013 Daniel Vrátil 2020 Igor Poboiko 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 3 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, see . */ #include "calendarhandler.h" #include "defaultreminderattribute.h" #include "googleresource.h" #include "googlesettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "googlecalendar_debug.h" using namespace KGAPI2; using namespace Akonadi; static constexpr uint32_t KGAPIEventVersion = 1; QString CalendarHandler::mimetype() { return KCalendarCore::Event::eventMimeType(); } bool CalendarHandler::canPerformTask(const Akonadi::Item &item) { return m_resource->canPerformTask(item, mimetype()); } void CalendarHandler::retrieveCollections() { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving calendars")); qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieving calendars..."; auto job = new CalendarFetchJob(m_settings->accountPtr(), this); connect(job, &KGAPI2::Job::finished, this, &CalendarHandler::slotCollectionsRetrieved); } void CalendarHandler::slotCollectionsRetrieved(KGAPI2::Job* job) { if (!m_resource->handleError(job)) { return; } qCDebug(GOOGLE_CALENDAR_LOG) << "Calendars retrieved"; const ObjectsList calendars = qobject_cast(job)->items(); Collection::List collections; const QStringList activeCalendars = m_settings->calendars(); for (const auto &object : calendars) { const CalendarPtr &calendar = object.dynamicCast(); qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieved calendar:" << calendar->title() << "(" << calendar->uid() << ")"; if (!activeCalendars.contains(calendar->uid())) { qCDebug(GOOGLE_CALENDAR_LOG) << "Skipping, not subscribed"; continue; } Collection collection; collection.setContentMimeTypes({ mimetype() }); collection.setName(calendar->uid()); collection.setParentCollection(m_resource->rootCollection()); collection.setRemoteId(calendar->uid()); if (calendar->editable()) { collection.setRights(Collection::CanChangeCollection |Collection::CanDeleteCollection |Collection::CanCreateItem |Collection::CanChangeItem |Collection::CanDeleteItem); } else { collection.setRights(Collection::ReadOnly); } EntityDisplayAttribute *attr = collection.attribute(Collection::AddIfMissing); attr->setDisplayName(calendar->title()); attr->setIconName(QStringLiteral("view-calendar")); auto colorAttr = collection.attribute(Collection::AddIfMissing); colorAttr->setColor(calendar->backgroundColor()); DefaultReminderAttribute *reminderAttr = collection.attribute(Collection::AddIfMissing); reminderAttr->setReminders(calendar->defaultReminders()); // Block email reminders, since Google sends them for us BlockAlarmsAttribute *blockAlarms = collection.attribute(Collection::AddIfMissing); blockAlarms->blockAlarmType(KCalendarCore::Alarm::Audio, false); blockAlarms->blockAlarmType(KCalendarCore::Alarm::Display, false); blockAlarms->blockAlarmType(KCalendarCore::Alarm::Procedure, false); collections << collection; } Q_EMIT collectionsRetrieved(collections); } void CalendarHandler::retrieveItems(const Collection &collection) { qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieving events for calendar" << collection.remoteId(); QString syncToken = collection.remoteRevision(); auto job = new EventFetchJob(collection.remoteId(), m_settings->accountPtr(), this); if (!syncToken.isEmpty()) { qCDebug(GOOGLE_CALENDAR_LOG) << "Using sync token" << syncToken; job->setSyncToken(syncToken); } else if (!m_settings->eventsSince().isEmpty()) { const QDate date = QDate::fromString(m_settings->eventsSince(), Qt::ISODate); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) job->setTimeMin(QDateTime(date).toSecsSinceEpoch()); #else job->setTimeMin(QDateTime(date.startOfDay()).toSecsSinceEpoch()); #endif } job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, this, &CalendarHandler::slotItemsRetrieved); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving events for calendar '%1'", collection.displayName())); } void CalendarHandler::slotItemsRetrieved(KGAPI2::Job *job) { if (!m_resource->handleError(job)) { return; } Item::List changedItems, removedItems; Collection collection = job->property(COLLECTION_PROPERTY).value(); DefaultReminderAttribute *attr = collection.attribute(); auto fetchJob = qobject_cast(job); const ObjectsList objects = fetchJob->items(); bool isIncremental = !fetchJob->syncToken().isEmpty(); qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieved" << objects.count() << "events for calendar" << collection.remoteId(); for (const ObjectPtr &object : objects) { const EventPtr event = object.dynamicCast(); if (event->useDefaultReminders() && attr) { const KCalendarCore::Alarm::List alarms = attr->alarms(event.data()); for (const KCalendarCore::Alarm::Ptr &alarm : alarms) { event->addAlarm(alarm); } } Item item; item.setMimeType(KCalendarCore::Event::eventMimeType()); item.setParentCollection(collection); item.setRemoteId(event->id()); item.setRemoteRevision(event->etag()); item.setPayload(event.dynamicCast()); if (event->deleted()) { qCDebug(GOOGLE_CALENDAR_LOG) << " - removed" << event->uid(); removedItems << item; } else { qCDebug(GOOGLE_CALENDAR_LOG) << " - changed" << event->uid(); changedItems << item; } } if (!isIncremental) { m_resource->itemsRetrieved(changedItems); } else { m_resource->itemsRetrievedIncremental(changedItems, removedItems); } const QDateTime local(QDateTime::currentDateTime()); const QDateTime UTC(local.toUTC()); qCDebug(GOOGLE_CALENDAR_LOG) << "Next sync token:" << fetchJob->syncToken(); collection.setRemoteRevision(fetchJob->syncToken()); new CollectionModifyJob(collection, this); emitReadyStatus(); } void CalendarHandler::itemAdded(const Item &item, const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Adding event to calendar '%1'", collection.name())); qCDebug(GOOGLE_CALENDAR_LOG) << "Event added to calendar" << collection.remoteId(); KCalendarCore::Event::Ptr event = item.payload(); EventPtr kevent(new Event(*event)); auto *job = new EventCreateJob(kevent, collection.remoteId(), m_settings->accountPtr(), this); job->setSendUpdates(SendUpdatesPolicy::None); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &KGAPI2::Job::finished, this, &CalendarHandler::slotCreateJobFinished); -} - -void CalendarHandler::slotCreateJobFinished(KGAPI2::Job* job) -{ - if (!m_resource->handleError(job)) { - return; - } - - Item item = job->property(ITEM_PROPERTY).value(); - - ObjectsList objects = qobject_cast(job)->items(); - EventPtr event = objects.first().dynamicCast(); - qCDebug(GOOGLE_CALENDAR_LOG) << "Event added"; - item.setRemoteId(event->id()); - item.setRemoteRevision(event->etag()); - item.setGid(event->uid()); - item.setPayload(event.dynamicCast()); - m_resource->changeCommitted(item); - emitReadyStatus(); + connect(job, &KGAPI2::Job::finished, [this, item](KGAPI2::Job *job){ + if (!m_resource->handleError(job)) { + return; + } + Item newItem = item; + const EventPtr event = qobject_cast(job)->items().first().dynamicCast(); + qCDebug(GOOGLE_CALENDAR_LOG) << "Event added"; + newItem.setRemoteId(event->id()); + newItem.setRemoteRevision(event->etag()); + newItem.setGid(event->uid()); + newItem.setPayload(event.dynamicCast()); + m_resource->changeCommitted(newItem); + emitReadyStatus(); + }); } void CalendarHandler::itemChanged(const Item &item, const QSet< QByteArray > &partIdentifiers) { Q_UNUSED(partIdentifiers); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Changing event in calendar '%1'", item.parentCollection().displayName())); qCDebug(GOOGLE_CALENDAR_LOG) << "Changing event" << item.remoteId(); KCalendarCore::Event::Ptr event = item.payload(); EventPtr kevent(new Event(*event)); auto job = new EventModifyJob(kevent, item.parentCollection().remoteId(), m_settings->accountPtr(), this); job->setSendUpdates(SendUpdatesPolicy::None); job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); connect(job, &EventModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void CalendarHandler::itemsRemoved(const Item::List &items) { Q_EMIT status(AgentBase::Running, i18ncp("@info:status", "Removing %1 events", "Removing %1 event", items.count())); QStringList eventIds; eventIds.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(eventIds), [](const Item &item){ return item.remoteId(); }); qCDebug(GOOGLE_CALENDAR_LOG) << "Removing events:" << eventIds; // TODO: what if events are from diferent calendars? auto job = new EventDeleteJob(eventIds, items.first().parentCollection().remoteId(), m_settings->accountPtr(), this); connect(job, &EventDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void CalendarHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) { Q_EMIT status(AgentBase::Running, i18ncp("@info:status", "Moving %1 events from calendar '%2' to calendar '%3'", "Moving %1 event from calendar '%2' to calendar '%3'", items.count(), collectionSource.displayName(), collectionDestination.displayName())); QStringList eventIds; eventIds.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(eventIds), [](const Item &item){ return item.remoteId(); }); qCDebug(GOOGLE_CALENDAR_LOG) << "Moving events" << eventIds << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); auto job = new EventMoveJob(eventIds, collectionSource.remoteId(), collectionDestination.remoteId(), m_settings->accountPtr(), this); connect(job, &EventMoveJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void CalendarHandler::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Q_UNUSED(parent); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Creating calendar '%1'", collection.displayName())); qCDebug(GOOGLE_CALENDAR_LOG) << "Adding calendar" << collection.displayName(); CalendarPtr calendar(new Calendar()); calendar->setTitle(collection.displayName()); calendar->setEditable(true); auto job = new CalendarCreateJob(calendar, m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void CalendarHandler::collectionChanged(const Akonadi::Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Changing calendar '%1'", collection.displayName())); qCDebug(GOOGLE_CALENDAR_LOG) << "Changing calendar" << collection.remoteId(); CalendarPtr calendar(new Calendar()); calendar->setUid(collection.remoteId()); calendar->setTitle(collection.displayName()); calendar->setEditable(true); auto job = new CalendarModifyJob(calendar, m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void CalendarHandler::collectionRemoved(const Akonadi::Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing calendar '%1'", collection.displayName())); qCDebug(GOOGLE_CALENDAR_LOG) << "Removing calendar" << collection.remoteId(); auto job = new CalendarDeleteJob(collection.remoteId(), m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); } QDateTime CalendarHandler::lastCacheUpdate() const { return QDateTime(); } void CalendarHandler::canHandleFreeBusy(const QString &email) const { if (m_resource->canPerformTask()) { m_resource->handlesFreeBusy(email, false); return; } auto job = new FreeBusyQueryJob(email, QDateTime::currentDateTimeUtc(), QDateTime::currentDateTimeUtc().addSecs(3600), m_settings->accountPtr(), const_cast(this)); connect(job, &KGAPI2::Job::finished, [this](KGAPI2::Job *job){ auto queryJob = qobject_cast(job); if (!m_resource->handleError(job, false)) { m_resource->handlesFreeBusy(queryJob->id(), false); return; } m_resource->handlesFreeBusy(queryJob->id(), true); }); } void CalendarHandler::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) { if (m_resource->canPerformTask()) { m_resource->freeBusyRetrieved(email, QString(), false, QString()); return; } auto job = new FreeBusyQueryJob(email, start, end, m_settings->accountPtr(), this); connect(job, &KGAPI2::Job::finished, [this](KGAPI2::Job *job) { auto queryJob = qobject_cast(job); if (!m_resource->handleError(job, false)) { m_resource->freeBusyRetrieved(queryJob->id(), QString(), false, QString()); return; } KCalendarCore::FreeBusy::Ptr fb(new KCalendarCore::FreeBusy); fb->setUid(QStringLiteral("%1%2@google.com").arg(QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMddTHHmmssZ")))); fb->setOrganizer(job->account()->accountName()); fb->addAttendee(KCalendarCore::Attendee(QString(), queryJob->id())); // FIXME: is it really sort? fb->setDateTime(QDateTime::currentDateTimeUtc(), KCalendarCore::IncidenceBase::RoleSort); for (const auto range : queryJob->busy()) { fb->addPeriod(range.busyStart, range.busyEnd); } KCalendarCore::ICalFormat format; const QString fbStr = format.createScheduleMessage(fb, KCalendarCore::iTIPRequest); m_resource->freeBusyRetrieved(queryJob->id(), fbStr, true, QString()); }); } diff --git a/resources/google-new/calendarhandler.h b/resources/google-new/calendarhandler.h index b27e25419..8e721851a 100644 --- a/resources/google-new/calendarhandler.h +++ b/resources/google-new/calendarhandler.h @@ -1,59 +1,57 @@ /* Copyright (C) 2011-2013 Daniel Vrátil 2020 Igor Pobiko 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 3 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, see . */ #ifndef CALENDARHANDLER_H #define CALENDARHANDLER_H #include "generichandler.h" #include #include class CalendarHandler : public GenericHandler { Q_OBJECT public: typedef QSharedPointer Ptr; using GenericHandler::GenericHandler; QString mimetype(); bool canPerformTask(const Akonadi::Item &item) override; void retrieveCollections() override; void retrieveItems(const Akonadi::Collection &collection) override; void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override; void itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) override; void itemsRemoved(const Akonadi::Item::List &items) override; void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) override; void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override; void collectionChanged(const Akonadi::Collection &collection) override; void collectionRemoved(const Akonadi::Collection &collection) override; // FreeBusy QDateTime lastCacheUpdate() const; void canHandleFreeBusy(const QString &email) const; void retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end); private Q_SLOTS: void slotCollectionsRetrieved(KGAPI2::Job* job); void slotItemsRetrieved(KGAPI2::Job* job); - - void slotCreateJobFinished(KGAPI2::Job* job); }; #endif // CALENDARHANDLER_H diff --git a/resources/google-new/taskhandler.cpp b/resources/google-new/taskhandler.cpp index 88aca5135..1b5e25bc9 100644 --- a/resources/google-new/taskhandler.cpp +++ b/resources/google-new/taskhandler.cpp @@ -1,337 +1,331 @@ /* Copyright (C) 2011-2013 Daniel Vrátil 2020 Igor Poboiko 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 3 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, see . */ #include "taskhandler.h" #include "googleresource.h" #include "googlesettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "googletasks_debug.h" #define TASK_PROPERTY "_KGAPI2::TaskPtr" using namespace KGAPI2; using namespace Akonadi; Q_DECLARE_METATYPE(KGAPI2::TaskPtr) QString TaskHandler::mimetype() { return KCalendarCore::Todo::todoMimeType(); } bool TaskHandler::canPerformTask(const Akonadi::Item &item) { return m_resource->canPerformTask(item, mimetype()); } void TaskHandler::retrieveCollections() { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving task lists")); qCDebug(GOOGLE_TASKS_LOG) << "Retrieving tasks..."; auto job = new TaskListFetchJob(m_settings->accountPtr(), this); connect(job, &KGAPI2::Job::finished, this, &TaskHandler::slotCollectionsRetrieved); } void TaskHandler::slotCollectionsRetrieved(KGAPI2::Job* job) { if (!m_resource->handleError(job)) { return; } qCDebug(GOOGLE_TASKS_LOG) << "Task lists retrieved"; const ObjectsList taskLists = qobject_cast(job)->items(); const QStringList activeTaskLists = m_settings->taskLists(); Collection::List collections; for (const ObjectPtr &object : taskLists) { const TaskListPtr &taskList = object.dynamicCast(); qCDebug(GOOGLE_TASKS_LOG) << "Retrieved task list:" << taskList->uid(); if (!activeTaskLists.contains(taskList->uid())) { qCDebug(GOOGLE_TASKS_LOG) << "Skipping, not subscribed"; continue; } Collection collection; collection.setContentMimeTypes(QStringList() << KCalendarCore::Todo::todoMimeType()); collection.setName(taskList->uid()); collection.setParentCollection(m_resource->rootCollection()); collection.setRemoteId(taskList->uid()); collection.setRights(Collection::CanChangeCollection |Collection::CanCreateItem |Collection::CanChangeItem |Collection::CanDeleteItem); EntityDisplayAttribute *attr = collection.attribute(Collection::AddIfMissing); attr->setDisplayName(taskList->title()); attr->setIconName(QStringLiteral("view-pim-tasks")); collections << collection; } Q_EMIT collectionsRetrieved(collections); } void TaskHandler::retrieveItems(const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving tasks for list '%1'", collection.displayName())); qCDebug(GOOGLE_TASKS_LOG) << "Retrieving tasks for list" << collection.remoteId(); // https://bugs.kde.org/show_bug.cgi?id=308122: we can only request changes in // max. last 25 days, otherwise we get an error. int lastSyncDelta = -1; if (!collection.remoteRevision().isEmpty()) { lastSyncDelta = QDateTime::currentDateTimeUtc().toSecsSinceEpoch() - collection.remoteRevision().toULongLong(); } auto job = new TaskFetchJob(collection.remoteId(), m_settings->accountPtr(), this); if (lastSyncDelta > -1 && lastSyncDelta < 25 * 25 * 3600) { job->setFetchOnlyUpdated(collection.remoteRevision().toULongLong()); } job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, this, &TaskHandler::slotItemsRetrieved); } void TaskHandler::slotItemsRetrieved(KGAPI2::Job *job) { if (!m_resource->handleError(job)) { return; } Item::List changedItems, removedItems; const ObjectsList& objects = qobject_cast(job)->items(); Collection collection = job->property(COLLECTION_PROPERTY).value(); bool isIncremental = (qobject_cast(job)->fetchOnlyUpdated() > 0); qCDebug(GOOGLE_TASKS_LOG) << "Retrieved" << objects.count() << "tasks for list" << collection.remoteId(); for (const auto &object : objects) { - TaskPtr task = object.dynamicCast(); + const TaskPtr task = object.dynamicCast(); Item item; item.setMimeType(KCalendarCore::Todo::todoMimeType()); item.setParentCollection(collection); item.setRemoteId(task->uid()); item.setRemoteRevision(task->etag()); item.setPayload(task.dynamicCast()); if (task->deleted()) { qCDebug(GOOGLE_TASKS_LOG) << " - removed" << task->uid(); removedItems << item; } else { qCDebug(GOOGLE_TASKS_LOG) << " - changed" << task->uid(); changedItems << item; } } if (isIncremental) { m_resource->itemsRetrievedIncremental(changedItems, removedItems); } else { m_resource->itemsRetrieved(changedItems); } const QDateTime local(QDateTime::currentDateTime()); const QDateTime UTC(local.toUTC()); collection.setRemoteRevision(QString::number(UTC.toSecsSinceEpoch())); new CollectionModifyJob(collection, this); emitReadyStatus(); } void TaskHandler::itemAdded(const Item &item, const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Adding event to calendar '%1'", collection.displayName())); KCalendarCore::Todo::Ptr todo = item.payload(); TaskPtr task(new Task(*todo)); const QString parentRemoteId = task->relatedTo(KCalendarCore::Incidence::RelTypeParent); qCDebug(GOOGLE_TASKS_LOG) << "Task added to list" << collection.remoteId() << "with parent" << parentRemoteId; auto job = new TaskCreateJob(task, item.parentCollection().remoteId(), m_settings->accountPtr(), this); job->setParentItem(parentRemoteId); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &KGAPI2::Job::finished, this, &TaskHandler::slotCreateJobFinished); -} - -void TaskHandler::slotCreateJobFinished(KGAPI2::Job* job) -{ - if (!m_resource->handleError(job)) { - return; - } - - Item item = job->property(ITEM_PROPERTY).value(); - TaskPtr task = qobject_cast(job)->items().first().dynamicCast(); - - item.setRemoteId(task->uid()); - item.setRemoteRevision(task->etag()); - item.setGid(task->uid()); - item.setPayload(task.dynamicCast()); - m_resource->changeCommitted(item); - - emitReadyStatus(); + connect(job, &KGAPI2::Job::finished, [this, item](KGAPI2::Job *job){ + if (!m_resource->handleError(job)) { + return; + } + Item newItem = item; + const TaskPtr task = qobject_cast(job)->items().first().dynamicCast(); + qCDebug(GOOGLE_TASKS_LOG) << "Task added"; + newItem.setRemoteId(task->uid()); + newItem.setRemoteRevision(task->etag()); + newItem.setGid(task->uid()); + newItem.setPayload(task.dynamicCast()); + m_resource->changeCommitted(newItem); + emitReadyStatus(); + }); } void TaskHandler::itemChanged(const Item &item, const QSet< QByteArray > &partIdentifiers) { Q_UNUSED(partIdentifiers); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Changing task in list '%1'", item.parentCollection().displayName())); qCDebug(GOOGLE_TASKS_LOG) << "Changing task" << item.remoteId(); KCalendarCore::Todo::Ptr todo = item.payload(); const QString parentUid = todo->relatedTo(KCalendarCore::Incidence::RelTypeParent); TaskPtr task(new Task(*todo)); // First we move it to a new parent, if there is auto job = new TaskMoveJob(item.remoteId(), item.parentCollection().remoteId(), parentUid, m_settings->accountPtr(), this); connect(job, &TaskMoveJob::finished, [this, task, item](KGAPI2::Job* job){ if (!m_resource->handleError(job)) { return; } auto newJob = new TaskModifyJob(task, item.parentCollection().remoteId(), job->account(), this); newJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); }); } void TaskHandler::itemsRemoved(const Item::List &items) { Q_EMIT status(AgentBase::Running, i18ncp("@info:status", "Removing %1 tasks", "Removing %1 task", items.count())); qCDebug(GOOGLE_TASKS_LOG) << "Removing" << items.count() << "tasks"; /* Google always automatically removes tasks with all their subtasks. In KOrganizer * by default we only remove the item we are given. For this reason we have to first * fetch all tasks, find all sub-tasks for the task being removed and detach them * from the task. Only then the task can be safely removed. */ // TODO: what if items belong to different collections? auto job = new ItemFetchJob(items.first().parentCollection()); job->fetchScope().fetchFullPayload(true); connect(job, &ItemFetchJob::finished, [this, items](KJob *job){ if (job->error()) { m_resource->cancelTask(i18n("Failed to delete task: %1", job->errorString())); return; } const Item::List fetchedItems = qobject_cast(job)->items(); Item::List detachItems; for (const Item &fetchedItem : fetchedItems) { auto todo = fetchedItem.payload(); const QString parentId = todo->relatedTo(KCalendarCore::Incidence::RelTypeParent); if (parentId.isEmpty()) { continue; } for (const Item &item : items) { if (item.remoteId() == parentId) { Item newItem = item; qCDebug(GOOGLE_TASKS_LOG) << "Detaching child" << item.remoteId() << "from" << parentId; todo->setRelatedTo(QString(), KCalendarCore::Incidence::RelTypeParent); newItem.setPayload(todo); detachItems << newItem; } } } /* If there are no items do detach, then delete the task right now */ if (detachItems.isEmpty()) { slotDoRemoveTasks(items); return; } qCDebug(GOOGLE_TASKS_LOG) << "Modifying" << detachItems.count() << "children..."; /* Send modify request to detach all the sub-tasks from the task that is about to be * removed. */ auto modifyJob = new ItemModifyJob(detachItems); connect(modifyJob, &ItemModifyJob::finished, [this, items](KJob *job){ if (job->error()) { m_resource->cancelTask(i18n("Failed to delete tasks:", job->errorString())); } slotDoRemoveTasks(items); }); }); } void TaskHandler::slotDoRemoveTasks(const Item::List &items) { // Make sure account is still valid if (!m_resource->canPerformTask()) { return; } QStringList taskIds; taskIds.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(taskIds), [](const Item &item){ return item.remoteId(); }); /* Now finally we can safely remove the task we wanted to */ // TODO: what if tasks are deleted from different collections? auto job = new TaskDeleteJob(taskIds, items.first().parentCollection().remoteId(), m_settings->accountPtr(), this); connect(job, &TaskDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void TaskHandler::itemsMoved(const Item::List &item, const Collection &collectionSource, const Collection &collectionDestination) { m_resource->cancelTask(i18n("Moving tasks between task lists is not supported")); } void TaskHandler::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Q_UNUSED(parent); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Creating new task list '%1'", collection.displayName())); qCDebug(GOOGLE_TASKS_LOG) << "Adding task list" << collection.displayName(); TaskListPtr taskList(new TaskList()); taskList->setTitle(collection.displayName()); auto job = new TaskListCreateJob(taskList, m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void TaskHandler::collectionChanged(const Akonadi::Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Changing task list '%1'", collection.displayName())); qCDebug(GOOGLE_TASKS_LOG) << "Changing task list" << collection.remoteId(); TaskListPtr taskList(new TaskList()); taskList->setUid(collection.remoteId()); taskList->setTitle(collection.displayName()); auto job = new TaskListModifyJob(taskList, m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void TaskHandler::collectionRemoved(const Akonadi::Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing task list '%1'", collection.displayName())); qCDebug(GOOGLE_TASKS_LOG) << "Removing task list" << collection.remoteId(); auto job = new TaskListDeleteJob(collection.remoteId(), m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &KGAPI2::Job::finished, m_resource, &GoogleResource::slotGenericJobFinished); } diff --git a/resources/google-new/taskhandler.h b/resources/google-new/taskhandler.h index 0035ebf79..c7182164b 100644 --- a/resources/google-new/taskhandler.h +++ b/resources/google-new/taskhandler.h @@ -1,52 +1,51 @@ /* Copyright (C) 2011-2013 Daniel Vrátil 2020 Igor Pobiko 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 3 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, see . */ #ifndef TASKHANDLER_H #define TASKHANDLER_H #include "generichandler.h" #include class TaskHandler : public GenericHandler { public: using GenericHandler::GenericHandler; QString mimetype(); bool canPerformTask(const Akonadi::Item &item) override; void retrieveCollections() override; void retrieveItems(const Akonadi::Collection &collection) override; void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override; void itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) override; void itemsRemoved(const Akonadi::Item::List &items) override; void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) override; void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override; void collectionChanged(const Akonadi::Collection &collection) override; void collectionRemoved(const Akonadi::Collection &collection) override; private Q_SLOTS: void slotCollectionsRetrieved(KGAPI2::Job* job); void slotItemsRetrieved(KGAPI2::Job* job); - void slotCreateJobFinished(KGAPI2::Job* job); void slotDoRemoveTasks(const Akonadi::Item::List &items); }; #endif // TASKHANDLER_H