diff --git a/resources/google-new/calendarhandler.cpp b/resources/google-new/calendarhandler.cpp index ec6deb151..388d45781 100644 --- a/resources/google-new/calendarhandler.cpp +++ b/resources/google-new/calendarhandler.cpp @@ -1,363 +1,376 @@ /* 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::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(); } 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::itemRemoved(const Item &item) +void CalendarHandler::itemsRemoved(const Item::List &items) { - Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing event from calendar '%1'", item.parentCollection().displayName())); - qCDebug(GOOGLE_CALENDAR_LOG) << "Removing event" << item.remoteId(); - KGAPI2::Job *job = new EventDeleteJob(item.remoteId(), item.parentCollection().remoteId(), m_settings->accountPtr(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + 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::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination) +void CalendarHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) { - Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving event from calendar '%1' to calendar '%2'", collectionSource.displayName(), collectionDestination.displayName())); - qCDebug(GOOGLE_CALENDAR_LOG) << "Moving" << item.remoteId() << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); - KGAPI2::Job *job = new EventMoveJob(item.remoteId(), collectionSource.remoteId(), collectionDestination.remoteId(), m_settings->accountPtr(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + 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 de2d6e993..b27e25419 100644 --- a/resources/google-new/calendarhandler.h +++ b/resources/google-new/calendarhandler.h @@ -1,59 +1,59 @@ /* 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 itemRemoved(const Akonadi::Item &item) override; - void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) 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/contacthandler.cpp b/resources/google-new/contacthandler.cpp index eec7b1b2d..44e5cdada 100644 --- a/resources/google-new/contacthandler.cpp +++ b/resources/google-new/contacthandler.cpp @@ -1,464 +1,481 @@ /* 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 "contacthandler.h" #include "googleresource.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "googlecontacts_debug.h" #define ALLCONTACTS_REMOTEID QStringLiteral("AllContacts") #define OTHERCONTACTS_REMOTEID QStringLiteral("OtherContacts") #define MODIFIED_PROPERTY "modifiedItems" using namespace KGAPI2; using namespace Akonadi; QString ContactHandler::mimetype() { return KContacts::Addressee::mimeType(); } bool ContactHandler::canPerformTask(const Item &item) { return m_resource->canPerformTask(item, mimetype()); } QString ContactHandler::myContactsRemoteId() { return QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(m_settings->accountPtr()->accountName()))); } void ContactHandler::retrieveCollections() { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving contacts groups")); qCDebug(GOOGLE_CONTACTS_LOG) << "Retrieving contacts groups..."; m_collections.clear(); m_allCollection.setContentMimeTypes({ KContacts::Addressee::mimeType() }); m_allCollection.setName(i18n("All Contacts")); m_allCollection.setParentCollection(m_resource->rootCollection()); m_allCollection.setRemoteId(ALLCONTACTS_REMOTEID); //m_allCollection.attribute(Collection::AddIfMissing); m_allCollection.setRights(Collection::CanCreateItem |Collection::CanChangeItem |Collection::CanDeleteItem); m_collections[ ALLCONTACTS_REMOTEID ] = m_allCollection; Collection otherCollection; otherCollection.setContentMimeTypes({ KContacts::Addressee::mimeType() }); otherCollection.setName(i18n("Other Contacts")); otherCollection.setParentCollection(m_resource->rootCollection()); otherCollection.setRights(Collection::CanLinkItem |Collection::CanUnlinkItem |Collection::CanChangeItem); otherCollection.setRemoteId(OTHERCONTACTS_REMOTEID); otherCollection.setVirtual(true); auto attr = otherCollection.attribute(Collection::AddIfMissing); attr->setDisplayName(i18n("Other Contacts")); attr->setIconName(QStringLiteral("view-pim-contacts")); m_collections[ OTHERCONTACTS_REMOTEID ] = otherCollection; auto job = new ContactsGroupFetchJob(m_settings->accountPtr(), this); connect(job, &ContactFetchJob::finished, this, &ContactHandler::slotCollectionsRetrieved); } void ContactHandler::slotCollectionsRetrieved(KGAPI2::Job* job) { if (!m_resource->handleError(job)) { return; } qCDebug(GOOGLE_CONTACTS_LOG) << "Contacts groups retrieved"; const ObjectsList objects = qobject_cast(job)->items(); for (const auto &object : objects) { const ContactsGroupPtr group = object.dynamicCast(); qCDebug(GOOGLE_CONTACTS_LOG) << "Retrieved contact group:" << group->id() << "(" << group->title() << ")"; QString realName = group->title(); if (group->isSystemGroup()) { if (group->title().contains(QLatin1String("Coworkers"))) { realName = i18nc("Name of a group of contacts", "Coworkers"); } else if (group->title().contains(QLatin1String("Friends"))) { realName = i18nc("Name of a group of contacts", "Friends"); } else if (group->title().contains(QLatin1String("Family"))) { realName = i18nc("Name of a group of contacts", "Family"); } else if (group->title().contains(QLatin1String("My Contacts"))) { realName = i18nc("Name of a group of contacts", "My Contacts"); } } Collection collection; collection.setContentMimeTypes({ KContacts::Addressee::mimeType() }); collection.setName(group->id()); collection.setParentCollection(m_resource->rootCollection()); collection.setRights(Collection::CanLinkItem |Collection::CanUnlinkItem |Collection::CanChangeItem); if (!group->isSystemGroup()) { collection.setRights(collection.rights() |Collection::CanChangeCollection |Collection::CanDeleteCollection); } collection.setRemoteId(group->id()); collection.setVirtual(true); auto attr = collection.attribute(Collection::AddIfMissing); attr->setDisplayName(realName); attr->setIconName(QStringLiteral("view-pim-contacts")); m_collections[ collection.remoteId() ] = collection; } Q_EMIT collectionsRetrieved(valuesToVector(m_collections)); emitReadyStatus(); } void ContactHandler::retrieveItems(const Collection &collection) { // All items are stored in "All Contacts" collection if (collection.remoteId() != ALLCONTACTS_REMOTEID) { m_resource->itemsRetrievalDone(); return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving contacts")); qCDebug(GOOGLE_CONTACTS_LOG) << "Retreiving contacts..."; auto job = new ContactFetchJob(m_settings->accountPtr(), this); job->setFetchDeleted(true); if (!collection.remoteRevision().isEmpty()) { job->setFetchOnlyUpdated(collection.remoteRevision().toLongLong()); } connect(job, &ContactFetchJob::finished, this, &ContactHandler::slotItemsRetrieved); } void ContactHandler::slotItemsRetrieved(KGAPI2::Job *job) { if (!m_resource->handleError(job)) { return; } Item::List changedItems, removedItems; QMap groupsMap; QList changedPhotos; auto fetchJob = qobject_cast(job); bool isIncremental = (fetchJob->fetchOnlyUpdated() > 0); const ObjectsList objects = fetchJob->items(); qCDebug(GOOGLE_CONTACTS_LOG) << "Retrieved" << objects.count() << "contacts"; for (const ObjectPtr &object : objects) { const ContactPtr contact = object.dynamicCast(); Item item; item.setMimeType(KContacts::Addressee::mimeType()); item.setParentCollection(m_allCollection); item.setRemoteId(contact->uid()); item.setRemoteRevision(contact->etag()); item.setPayload(*contact.dynamicCast()); if (contact->deleted()) { qCDebug(GOOGLE_CONTACTS_LOG) << " - removed" << contact->uid(); removedItems << item; } else { qCDebug(GOOGLE_CONTACTS_LOG) << " - changed" << contact->uid(); changedItems << item; changedPhotos << contact->uid(); } const QStringList groups = contact->groups(); for (const QString &group : groups) { groupsMap[group] << item; } if (contact->groups().isEmpty()) { groupsMap[OTHERCONTACTS_REMOTEID] << item; } } if (isIncremental) { m_resource->itemsRetrievedIncremental(changedItems, removedItems); } else { m_resource->itemsRetrieved(changedItems); } for (auto iter = groupsMap.constBegin(), iterEnd = groupsMap.constEnd(); iter != iterEnd; ++iter) { new LinkJob(m_collections[iter.key()], iter.value(), this); } if (!changedPhotos.isEmpty()) { m_resource->scheduleCustomTask(this, "retrieveContactsPhotos", QVariant::fromValue(changedPhotos)); } const QDateTime local(QDateTime::currentDateTime()); const QDateTime UTC(local.toUTC()); m_allCollection.setRemoteRevision(QString::number(UTC.toSecsSinceEpoch())); new CollectionModifyJob(m_allCollection, this); emitReadyStatus(); } void ContactHandler::retrieveContactsPhotos(const QVariant &argument) { if (!m_resource->canPerformTask()) { return; } Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving contacts photos")); const QStringList changedPhotos = argument.value(); Item::List items; items.reserve(changedPhotos.size()); for (const QString contact : changedPhotos) { Item item; item.setRemoteId(contact); items << item; } auto job = new ItemFetchJob(items, this); job->setCollection(m_allCollection); job->fetchScope().fetchFullPayload(true); connect(job, &ItemFetchJob::finished, this, &ContactHandler::slotUpdatePhotosItemsRetrieved); } void ContactHandler::slotUpdatePhotosItemsRetrieved(KJob *job) { auto fetchJob = qobject_cast(job); ContactsList contacts; const Item::List items = fetchJob->items(); qCDebug(GOOGLE_CONTACTS_LOG) << "Fetched" << items.count() << "contacts for photo update"; for (const Item &item : items) { const KContacts::Addressee addressee = item.payload(); const ContactPtr contact(new Contact(addressee)); contacts << contact; } // Make sure account is still valid if (!m_resource->canPerformTask()) { return; } qCDebug(GOOGLE_CONTACTS_LOG) << "Starting fetching photos..."; auto photoJob = new ContactFetchPhotoJob(contacts, m_settings->accountPtr(), this); photoJob->setProperty("processedItems", 0); connect(photoJob, &ContactFetchPhotoJob::photoFetched, [this, items](KGAPI2::Job *job, const ContactPtr &contact){ qCDebug(GOOGLE_CONTACTS_LOG) << " - fetched photo for contact" << contact->uid(); int processedItems = job->property("processedItems").toInt(); processedItems++; job->setProperty("processedItems", processedItems); Q_EMIT percent(100.0f*processedItems / items.count()); for (const Item item : items) { if (item.remoteId() == contact->uid()) { Item newItem = item; newItem.setPayload(*contact.dynamicCast()); new ItemModifyJob(newItem, this); return; } } }); connect(photoJob, &ContactFetchPhotoJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::itemAdded(const Item &item, const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Adding contact")); auto addressee = item.payload< KContacts::Addressee >(); ContactPtr contact(new Contact(addressee)); qCDebug(GOOGLE_CONTACTS_LOG) << "Creating contact"; /* Adding contact to "My Contacts" system group explicitly */ contact->addGroup(myContactsRemoteId()); new LinkJob(m_collections[myContactsRemoteId()], { item }); auto job = new ContactCreateJob(contact, m_settings->accountPtr(), this); connect(job, &ContactCreateJob::finished, [this, item](KGAPI2::Job* job){ if (!m_resource->handleError(job)) { return; } ContactPtr contact = qobject_cast(job)->items().first().dynamicCast(); Item newItem = item; qCDebug(GOOGLE_CONTACTS_LOG) << "Contact" << contact->uid() << "created"; newItem.setRemoteId(contact->uid()); newItem.setRemoteRevision(contact->etag()); newItem.setPayload(*contact.dynamicCast()); m_resource->changeCommitted(newItem); emitReadyStatus(); }); } void ContactHandler::itemChanged(const Item &item, const QSet< QByteArray > &partIdentifiers) { Q_UNUSED(partIdentifiers); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Changing contact")); qCDebug(GOOGLE_CONTACTS_LOG) << "Changing contact" << item.remoteId(); KContacts::Addressee addressee = item.payload< KContacts::Addressee >(); ContactPtr contact(new Contact(addressee)); ContactModifyJob *modifyJob = new ContactModifyJob(contact, m_settings->accountPtr(), this); modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); connect(modifyJob, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } -void ContactHandler::itemRemoved(const Item &item) +void ContactHandler::itemsRemoved(const Item::List &items) { - Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing contact")); - qCDebug(GOOGLE_CONTACTS_LOG) << "Removing contact" << item.remoteId(); - auto job = new ContactDeleteJob(item.remoteId(), m_settings->accountPtr(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + Q_EMIT status(AgentBase::Running, i18ncp("@info:status", "Removing contact", "Removing contacts", items.count())); + QStringList contactIds; + contactIds.reserve(items.count()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contactIds), + [](const Item &item){ + return item.remoteId(); + }); + qCDebug(GOOGLE_CONTACTS_LOG) << "Removing contacts" << contactIds; + auto job = new ContactDeleteJob(contactIds, m_settings->accountPtr(), this); connect(job, &ContactDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } -void ContactHandler::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination) +void ContactHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) { m_resource->cancelTask(i18n("Moving contacts is not supported, need to link them instead.")); /* Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Moving contact")); qCDebug(GOOGLE_CONTACTS_LOG) << "Moving contact" << item.remoteId() << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); KContacts::Addressee addressee = item.payload< KContacts::Addressee >(); ContactPtr contact(new Contact(addressee)); // MyContacts -> OtherContacts if (collectionSource.remoteId() == MYCONTACTS_REMOTEID && collectionDestination.remoteId() == OTHERCONTACTS_REMOTEID) { contact->removeGroup(QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(m_settings->accountPtr()->accountName())))); // OtherContacts -> MyContacts } else if (collectionSource.remoteId() == OTHERCONTACTS_REMOTEID && collectionDestination.remoteId() == MYCONTACTS_REMOTEID) { contact->addGroup(QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(m_settings->accountPtr()->accountName())))); } else { m_resource->cancelTask(i18n("Invalid source or destination collection")); return; } auto job = new ContactModifyJob(contact, m_settings->accountPtr(), this); job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); connect(job, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished);*/ } -void ContactHandler::itemLinked(const Item &item, const Collection &collection) +void ContactHandler::itemsLinked(const Item::List &items, const Collection &collection) { - qCDebug(GOOGLE_CONTACTS_LOG) << "Linking contact" << item.remoteId() << "to group" << collection.remoteId(); - KContacts::Addressee addressee = item.payload(); - ContactPtr contact(new Contact(addressee)); - if (collection.remoteId() == OTHERCONTACTS_REMOTEID) { - contact->clearGroups(); - } else { - contact->addGroup(myContactsRemoteId()); - contact->addGroup(collection.remoteId()); - } + Q_EMIT status(AgentBase::Running, i18ncp("@info:status", "Linking %1 contact", "Linking %1 contacts", items.count())); + qCDebug(GOOGLE_CONTACTS_LOG) << "Linking" << items.count() << "contacts to group" << collection.remoteId(); - ContactModifyJob *modifyJob = new ContactModifyJob(contact, m_settings->accountPtr(), this); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + ContactsList contacts; + contacts.reserve(items.count()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), + [this, &collection](const Akonadi::Item &item){ + KContacts::Addressee addressee = item.payload(); + ContactPtr contact(new Contact(addressee)); + if (collection.remoteId() == OTHERCONTACTS_REMOTEID) { + contact->clearGroups(); + } else { + contact->addGroup(myContactsRemoteId()); + contact->addGroup(collection.remoteId()); + } + return contact; + }); + ContactModifyJob *modifyJob = new ContactModifyJob(contacts, m_settings->accountPtr(), this); connect(modifyJob, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } -void ContactHandler::itemUnlinked(const Item &item, const Collection &collection) +void ContactHandler::itemsUnlinked(const Item::List &items, const Collection &collection) { - qCDebug(GOOGLE_CONTACTS_LOG) << "Unlinking contact" << item.remoteId() << "from group" << collection.remoteId(); - KContacts::Addressee addressee = item.payload(); - ContactPtr contact(new Contact(addressee)); - if (collection.remoteId() == OTHERCONTACTS_REMOTEID) { - contact->addGroup(myContactsRemoteId()); - } else { - if (collection.remoteId() != myContactsRemoteId()) { - contact->removeGroup(collection.remoteId()); - } - } + Q_EMIT status(AgentBase::Running, i18ncp("@info:status", "Unlinking %1 contact", "Unlinking %1 contacts", items.count())); + qCDebug(GOOGLE_CONTACTS_LOG) << "Unlinking" << items.count() << "contacts from group" << collection.remoteId(); - ContactModifyJob *modifyJob = new ContactModifyJob(contact, m_settings->accountPtr(), this); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + ContactsList contacts; + contacts.reserve(items.count()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), + [this, &collection](const Akonadi::Item &item){ + KContacts::Addressee addressee = item.payload(); + ContactPtr contact(new Contact(addressee)); + if (collection.remoteId() == OTHERCONTACTS_REMOTEID) { + contact->addGroup(myContactsRemoteId()); + } else { + if (collection.remoteId() != myContactsRemoteId()) { + contact->removeGroup(collection.remoteId()); + } + } + return contact; + }); + ContactModifyJob *modifyJob = new ContactModifyJob(contacts, m_settings->accountPtr(), this); connect(modifyJob, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::collectionAdded(const Collection &collection, const Collection &parent) { Q_UNUSED(parent); Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Creating new contact group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Adding contact group" << collection.displayName(); ContactsGroupPtr group(new ContactsGroup); group->setTitle(collection.name()); group->setIsSystemGroup(false); auto job = new ContactsGroupCreateJob(group, m_settings->accountPtr(), this); connect(job, &ContactsGroupCreateJob::finished, [this, &collection](KGAPI2::Job* job){ if (!m_resource->handleError(job)) { return; } ContactsGroupPtr group = qobject_cast(job)->items().first().dynamicCast(); qCDebug(GOOGLE_CONTACTS_LOG) << "Contact group created:" << group->id(); Collection newCollection = collection; newCollection.setRemoteId(group->id()); newCollection.setContentMimeTypes(QStringList() << KContacts::Addressee::mimeType()); auto attr = newCollection.attribute(Collection::AddIfMissing); attr->setDisplayName(group->title()); attr->setIconName(QStringLiteral("view-pim-contacts")); m_collections[ newCollection.remoteId() ] = newCollection; m_resource->changeCommitted(newCollection); emitReadyStatus(); }); } void ContactHandler::collectionChanged(const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Changing contact group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Changing contact group" << collection.remoteId(); ContactsGroupPtr group(new ContactsGroup()); group->setId(collection.remoteId()); group->setTitle(collection.displayName()); auto job = new ContactsGroupModifyJob(group, m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &ContactsGroupModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::collectionRemoved(const Collection &collection) { Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing contact group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Removing contact group" << collection.remoteId(); auto job = new ContactsGroupDeleteJob(collection.remoteId(), m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &ContactsGroupDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } diff --git a/resources/google-new/contacthandler.h b/resources/google-new/contacthandler.h index c307110e4..08f5e4f5d 100644 --- a/resources/google-new/contacthandler.h +++ b/resources/google-new/contacthandler.h @@ -1,60 +1,60 @@ /* Copyright (C) 2011, 2012 Dan Vratil 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 . */ #ifndef CONTACTHANDLER_H #define CONTACTHANDLER_H #include "generichandler.h" #include class ContactHandler : public GenericHandler { Q_OBJECT 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 itemRemoved(const Akonadi::Item &item) override; - void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) override; - void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) override; - void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) 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 itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) override; + void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) 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 slotUpdatePhotosItemsRetrieved(KJob *job); void retrieveContactsPhotos(const QVariant &arguments); private: QString myContactsRemoteId(); QMap m_collections; Akonadi::Collection m_allCollection; }; #endif // CONTACTHANDLER_H diff --git a/resources/google-new/generichandler.cpp b/resources/google-new/generichandler.cpp index 6e510722d..148b2846a 100644 --- a/resources/google-new/generichandler.cpp +++ b/resources/google-new/generichandler.cpp @@ -1,45 +1,45 @@ /* Copyright (C) 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 "generichandler.h" #include "googleresource.h" GenericHandler::GenericHandler(GoogleResource *resource, GoogleSettings *settings) : m_resource(resource) , m_settings(settings) { } GenericHandler::~GenericHandler() = default; void GenericHandler::emitReadyStatus() { Q_EMIT status(Akonadi::AgentBase::Idle, i18nc("@info:status", "Ready")); } -void GenericHandler::itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) +void GenericHandler::itemsLinked(const Akonadi::Item::List &/*items*/, const Akonadi::Collection &/*collection*/) { m_resource->cancelTask(i18n("Cannot handle item linking")); } -void GenericHandler::itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) +void GenericHandler::itemsUnlinked(const Akonadi::Item::List &/*items*/, const Akonadi::Collection &/*collection*/) { m_resource->cancelTask(i18n("Cannot handle item unlinking")); } #include "moc_generichandler.cpp" diff --git a/resources/google-new/generichandler.h b/resources/google-new/generichandler.h index c719a3243..e420a44fe 100644 --- a/resources/google-new/generichandler.h +++ b/resources/google-new/generichandler.h @@ -1,71 +1,71 @@ /* Copyright (C) 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 . */ #ifndef GENERICHANDLER_H #define GENERICHANDLER_H #include #include #include #include #include namespace KGAPI2 { class Job; } class GoogleResource; class GoogleSettings; class GenericHandler : public QObject { Q_OBJECT public: typedef QSharedPointer Ptr; GenericHandler(GoogleResource *resource, GoogleSettings *settings); virtual ~GenericHandler(); virtual QString mimetype() = 0; virtual bool canPerformTask(const Akonadi::Item &item) = 0; virtual void retrieveCollections() = 0; virtual void retrieveItems(const Akonadi::Collection &collection) = 0; virtual void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) = 0; virtual void itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) = 0; - virtual void itemRemoved(const Akonadi::Item &item) = 0; - virtual void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) = 0; - virtual void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection); - virtual void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection); + virtual void itemsRemoved(const Akonadi::Item::List &items) = 0; + virtual void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) = 0; + virtual void itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); + virtual void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); virtual void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) = 0; virtual void collectionChanged(const Akonadi::Collection &collection) = 0; virtual void collectionRemoved(const Akonadi::Collection &collection) = 0; protected: GoogleResource* m_resource; GoogleSettings* m_settings; void emitReadyStatus(); Q_SIGNALS: void status(int code, const QString& message = QString()); void percent(int progress); void collectionsRetrieved(const Akonadi::Collection::List& collections); }; #endif // GENERICHANDLER_H diff --git a/resources/google-new/googleresource.cpp b/resources/google-new/googleresource.cpp index c4a6a0eee..f9b761bf7 100644 --- a/resources/google-new/googleresource.cpp +++ b/resources/google-new/googleresource.cpp @@ -1,525 +1,530 @@ /* 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 "googleresource.h" #include "googlesettings.h" #include "googlesettingsdialog.h" #include "googleresource_debug.h" #include "settingsadaptor.h" #include "calendarhandler.h" #include "contacthandler.h" #include "taskhandler.h" #include "defaultreminderattribute.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ACCESS_TOKEN_PROPERTY "AccessToken" #define CALENDARS_PROPERTY "_KGAPI2CalendarPtr" #define ROOT_COLLECTION_REMOTEID QStringLiteral("RootCollection") Q_DECLARE_METATYPE(KGAPI2::Job *) using namespace KGAPI2; using namespace Akonadi; GoogleResource::GoogleResource(const QString &id) : ResourceBase(id) - , AgentBase::ObserverV2() + , AgentBase::ObserverV3() { AttributeFactory::registerAttribute< DefaultReminderAttribute >(); connect(this, &GoogleResource::abortRequested, this, [this](){ cancelTask(i18n("Aborted")); }); connect(this, &GoogleResource::reloadConfiguration, this, &GoogleResource::reloadConfig); setNeedsNetwork(true); changeRecorder()->itemFetchScope().fetchFullPayload(true); changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All); changeRecorder()->fetchCollection(true); changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All); m_settings = new GoogleSettings(); m_settings->setWindowId(winIdForDialogs()); connect(m_settings, &GoogleSettings::accountReady, [this](bool ready){ if (accountId() > 0) { return; } if (!ready) { Q_EMIT status(Broken, i18n("Can't access KWallet")); return; } if (m_settings->accountPtr().isNull()) { Q_EMIT status(NotConfigured); return; } Q_EMIT status(Idle, i18nc("@info:status", "Ready")); synchronize(); }); Q_EMIT status(NotConfigured, i18n("Waiting for KWallet...")); updateResourceName(); m_freeBusyHandler.reset(new CalendarHandler(this, m_settings)); m_handlers << m_freeBusyHandler; m_handlers << GenericHandler::Ptr(new ContactHandler(this, m_settings)); m_handlers << GenericHandler::Ptr(new TaskHandler(this, m_settings)); for (auto handler : m_handlers) { connect(handler.data(), &GenericHandler::status, [this](int code, QString message){ Q_EMIT status(code, message); }); connect(handler.data(), &GenericHandler::percent, [this](int value){ Q_EMIT percent(value); }); connect(handler.data(), &GenericHandler::collectionsRetrieved, this, &GoogleResource::collectionsPartiallyRetrieved); } new SettingsAdaptor(m_settings); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), m_settings, QDBusConnection::ExportAdaptors); } GoogleResource::~GoogleResource() { } void GoogleResource::cleanup() { m_settings->cleanup(); ResourceBase::cleanup(); } Akonadi::Collection GoogleResource::rootCollection() const { return m_rootCollection; } void GoogleResource::configure(WId windowId) { if (!m_settings->isReady() || m_isConfiguring) { Q_EMIT configurationDialogAccepted(); return; } m_isConfiguring = true; QScopedPointer settingsDialog(new GoogleSettingsDialog(this, m_settings, windowId)); settingsDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("im-google"))); if (settingsDialog->exec() == QDialog::Accepted) { updateResourceName(); Q_EMIT configurationDialogAccepted(); if (m_settings->accountPtr().isNull()) { Q_EMIT status(NotConfigured, i18n("Configured account does not exist")); m_isConfiguring = false; return; } Q_EMIT status(Idle, i18nc("@info:status", "Ready")); synchronize(); } else { updateResourceName(); Q_EMIT configurationDialogRejected(); } m_isConfiguring = false; } QList GoogleResource::scopes() const { // TODO: determine it based on what user wants? const QList< QUrl > scopes = {Account::accountInfoScopeUrl(), Account::calendarScopeUrl(), Account::contactsScopeUrl(), Account::tasksScopeUrl()}; return scopes; } void GoogleResource::updateResourceName() { const QString accountName = m_settings->account(); setName(i18nc("%1 is account name (user@gmail.com)", "Google Groupware (%1)", accountName.isEmpty() ? i18n("not configured") : accountName)); } void GoogleResource::reloadConfig() { const AccountPtr account = m_settings->accountPtr(); if (account.isNull() || account->accountName().isEmpty()) { Q_EMIT status(NotConfigured, i18n("Configured account does not exist")); } else { Q_EMIT status(Idle, i18nc("@info:status", "Ready")); } } bool GoogleResource::handleError(KGAPI2::Job *job, bool _cancelTask) { if ((job->error() == KGAPI2::NoError) || (job->error() == KGAPI2::OK)) { return true; } qCDebug(GOOGLE_LOG) << job << job->errorString(); AccountPtr account = job->account(); if (job->error() == KGAPI2::Unauthorized) { const QList resourceScopes = scopes(); for (const QUrl &scope : resourceScopes) { if (!account->scopes().contains(scope)) { account->addScope(scope); } } AuthJob *authJob = new AuthJob(account, m_settings->clientId(), m_settings->clientSecret(), this); authJob->setProperty(JOB_PROPERTY, QVariant::fromValue(job)); connect(authJob, &AuthJob::finished, this, &GoogleResource::slotAuthJobFinished); return false; } if (_cancelTask) { cancelTask(job->errorString()); } return false; } bool GoogleResource::canPerformTask() { if (!m_settings->accountPtr() && accountId() == 0) { cancelTask(i18nc("@info:status", "Resource is not configured")); Q_EMIT status(NotConfigured, i18nc("@info:status", "Resource is not configured")); return false; } return true; } void GoogleResource::slotAuthJobFinished(KGAPI2::Job *job) { if (job->error() != KGAPI2::NoError) { cancelTask(i18n("Failed to refresh tokens")); return; } AuthJob *authJob = qobject_cast(job); AccountPtr account = authJob->account(); if (!m_settings->storeAccount(account)) { qCWarning(GOOGLE_LOG) << "Failed to store account in KWallet"; } KGAPI2::Job *otherJob = job->property(JOB_PROPERTY).value(); if (otherJob) { otherJob->setAccount(account); otherJob->restart(); } } void GoogleResource::slotGenericJobFinished(KGAPI2::Job *job) { if (!handleError(job)) { return; } qCDebug(GOOGLE_LOG) << "Job finished"; const Item item = job->property(ITEM_PROPERTY).value(); const Collection collection = job->property(COLLECTION_PROPERTY).value(); if (item.isValid()) { changeCommitted(item); } else if (collection.isValid()) { changeCommitted(collection); } else { taskDone(); } Q_EMIT status(Idle, i18nc("@info:status", "Ready")); } int GoogleResource::accountId() const { return 0; } QDateTime GoogleResource::lastCacheUpdate() const { if (m_freeBusyHandler) { return m_freeBusyHandler->lastCacheUpdate(); } return QDateTime(); } void GoogleResource::canHandleFreeBusy(const QString &email) const { if (m_freeBusyHandler) { m_freeBusyHandler->canHandleFreeBusy(email); } else { handlesFreeBusy(email, false); } } void GoogleResource::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) { if (m_freeBusyHandler) { m_freeBusyHandler->retrieveFreeBusy(email, start, end); } else { freeBusyRetrieved(email, QString(), false, QString()); } } /* * Collection handling */ void GoogleResource::retrieveCollections() { qCDebug(GOOGLE_LOG) << "Retrieve Collections"; if (!canPerformTask()) { return; } CachePolicy cachePolicy; if (m_settings->enableIntervalCheck()) { cachePolicy.setInheritFromParent(false); cachePolicy.setIntervalCheckTime(m_settings->intervalCheckTime()); } // Setting up root collection m_rootCollection = Collection(); m_rootCollection.setContentMimeTypes({ Collection::mimeType(), Collection::virtualMimeType() }); m_rootCollection.setRemoteId(ROOT_COLLECTION_REMOTEID); m_rootCollection.setName(m_settings->accountPtr()->accountName()); m_rootCollection.setParentCollection(Collection::root()); m_rootCollection.setRights(Collection::CanCreateCollection); m_rootCollection.setCachePolicy(cachePolicy); EntityDisplayAttribute *attr = m_rootCollection.attribute(Collection::AddIfMissing); attr->setDisplayName(m_settings->accountPtr()->accountName()); attr->setIconName(QStringLiteral("im-google")); m_collections = { m_rootCollection }; m_jobs = 0; for (auto handler : m_handlers) { handler->retrieveCollections(); m_jobs++; } } void GoogleResource::collectionsPartiallyRetrieved(const Collection::List& collections) { m_jobs--; m_collections << collections; if (m_jobs == 0) { qCDebug(GOOGLE_LOG) << "Collections retrieved!"; collectionsRetrieved(m_collections); } } void GoogleResource::retrieveItems(const Akonadi::Collection &collection) { if (!canPerformTask()) { return; } auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&collection](const GenericHandler::Ptr &handler){ return collection.contentMimeTypes().contains(handler->mimetype()); }); if (it != m_handlers.end()) { (*it)->retrieveItems(collection); } else { qCWarning(GOOGLE_LOG) << "Unknown collection" << collection.name(); itemsRetrieved({}); } } void GoogleResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { if (!canPerformTask()) { return; } auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&collection, &item](const GenericHandler::Ptr &handler){ return collection.contentMimeTypes().contains(handler->mimetype()) && handler->canPerformTask(item); }); if (it != m_handlers.end()) { (*it)->itemAdded(item, collection); } else { qCWarning(GOOGLE_LOG) << "Could not add item" << item.mimeType(); cancelTask(i18n("Invalid payload type")); } } void GoogleResource::itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) { Q_UNUSED(partIdentifiers); if (!canPerformTask()) { return; } auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&item](const GenericHandler::Ptr &handler){ return handler->canPerformTask(item); }); if (it != m_handlers.end()) { (*it)->itemChanged(item, partIdentifiers); } else { qCWarning(GOOGLE_LOG) << "Could not change item" << item.mimeType(); cancelTask(i18n("Invalid payload type")); } } -void GoogleResource::itemRemoved(const Akonadi::Item &item) +void GoogleResource::itemsRemoved(const Item::List &items) { if (!canPerformTask()) { return; } + // TODO: what if items have different mimetypes? + const QString mimeType = items.first().mimeType(); auto it = std::find_if(m_handlers.begin(), m_handlers.end(), - [&item](const GenericHandler::Ptr &handler){ - return handler->mimetype() == item.mimeType(); + [&mimeType](const GenericHandler::Ptr &handler){ + return handler->mimetype() == mimeType; }); if (it != m_handlers.end()) { - (*it)->itemRemoved(item); + (*it)->itemsRemoved(items); } else { - qCWarning(GOOGLE_LOG) << "Could not remove item" << item.mimeType(); + qCWarning(GOOGLE_LOG) << "Could not remove item" << mimeType; cancelTask(i18n("Invalid payload type")); } } -void GoogleResource::itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) +void GoogleResource::itemsMoved(const Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) { if (!canPerformTask()) { return; } + // TODO: what if items have different mimetypes? auto it = std::find_if(m_handlers.begin(), m_handlers.end(), - [&item](const GenericHandler::Ptr &handler){ + [&item = items.first()](const GenericHandler::Ptr &handler){ return handler->canPerformTask(item); }); if (it != m_handlers.end()) { - (*it)->itemMoved(item, collectionSource, collectionDestination); + (*it)->itemsMoved(items, collectionSource, collectionDestination); } else { - qCWarning(GOOGLE_LOG) << "Could not move item" << item.mimeType() << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); + qCWarning(GOOGLE_LOG) << "Could not move item" << items.first().mimeType() << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); cancelTask(i18n("Invalid payload type")); } } -void GoogleResource::itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) +void GoogleResource::itemsLinked(const Item::List &items, const Akonadi::Collection &collection) { if (!canPerformTask()) { return; } + // TODO: what if items have different mimetypes? auto it = std::find_if(m_handlers.begin(), m_handlers.end(), - [&item](const GenericHandler::Ptr &handler){ + [&item = items.first()](const GenericHandler::Ptr &handler){ return handler->canPerformTask(item); }); if (it != m_handlers.end()) { - (*it)->itemLinked(item, collection); + (*it)->itemsLinked(items, collection); } else { - qCWarning(GOOGLE_LOG) << "Could not link item" << item.mimeType() << "to" << collection.remoteId(); + qCWarning(GOOGLE_LOG) << "Could not link item" << items.first().mimeType() << "to" << collection.remoteId(); cancelTask(i18n("Invalid payload type")); } } -void GoogleResource::itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) +void GoogleResource::itemsUnlinked(const Item::List &items, const Akonadi::Collection &collection) { if (!canPerformTask()) { return; } + // TODO: what if items have different mimetypes? auto it = std::find_if(m_handlers.begin(), m_handlers.end(), - [&item](const GenericHandler::Ptr &handler){ + [&item = items.first()](const GenericHandler::Ptr &handler){ return handler->canPerformTask(item); }); if (it != m_handlers.end()) { - (*it)->itemUnlinked(item, collection); + (*it)->itemsUnlinked(items, collection); } else { - qCWarning(GOOGLE_LOG) << "Could not unlink item mimetype" << item.mimeType() << "from" << collection.remoteId(); + qCWarning(GOOGLE_LOG) << "Could not unlink item mimetype" << items.first().mimeType() << "from" << collection.remoteId(); cancelTask(i18n("Invalid payload type")); } } void GoogleResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { if (!canPerformTask()) { return; } auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&collection](const GenericHandler::Ptr &handler){ return collection.contentMimeTypes().contains(handler->mimetype()); }); if (it != m_handlers.end()) { (*it)->collectionAdded(collection, parent); } else { qCWarning(GOOGLE_LOG) << "Could not add collection" << collection.displayName() << "mimetypes:" << collection.contentMimeTypes(); cancelTask(i18n("Unknown collection mimetype")); } } void GoogleResource::collectionChanged(const Akonadi::Collection &collection) { if (!canPerformTask()) { return; } auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&collection](const GenericHandler::Ptr &handler){ return collection.contentMimeTypes().contains(handler->mimetype()); }); if (it != m_handlers.end()) { (*it)->collectionChanged(collection); } else { qCWarning(GOOGLE_LOG) << "Could not change collection" << collection.displayName() << "mimetypes:" << collection.contentMimeTypes(); cancelTask(i18n("Unknown collection mimetype")); } } void GoogleResource::collectionRemoved(const Akonadi::Collection &collection) { if (!canPerformTask()) { return; } auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&collection](const GenericHandler::Ptr &handler){ return collection.contentMimeTypes().contains(handler->mimetype()); }); if (it != m_handlers.end()) { (*it)->collectionRemoved(collection); } else { qCWarning(GOOGLE_LOG) << "Could not remove collection" << collection.displayName() << "mimetypes:" << collection.contentMimeTypes(); cancelTask(i18n("Unknown collection mimetype")); } } AKONADI_RESOURCE_MAIN(GoogleResource) diff --git a/resources/google-new/googleresource.h b/resources/google-new/googleresource.h index 06a956bde..b15ccae5d 100644 --- a/resources/google-new/googleresource.h +++ b/resources/google-new/googleresource.h @@ -1,127 +1,127 @@ /* Copyright (C) 2011-2013 Daniel Vrátil 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 GOOGLERESOURCE_H #define GOOGLERESOURCE_H #include #include #include #include #include "googlesettings.h" #include "generichandler.h" #include "calendarhandler.h" #include #include #define ITEM_PROPERTY "_AkonadiItem" #define COLLECTION_PROPERTY "_AkonadiCollection" #define JOB_PROPERTY "_KGAPI2Job" namespace KGAPI2 { class Job; } class GoogleSettings; -class GoogleResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV2, public Akonadi::FreeBusyProviderBase +class GoogleResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV3, public Akonadi::FreeBusyProviderBase { Q_OBJECT public: explicit GoogleResource(const QString &id); ~GoogleResource() override; QList scopes() const; void cleanup() override; public Q_SLOTS: void configure(WId windowId) override; void reloadConfig(); protected: int runConfigurationDialog(WId windowId); void updateResourceName(); // Freebusy QDateTime lastCacheUpdate() const override; void canHandleFreeBusy(const QString &email) const override; void retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) override; template bool canPerformTask(const Akonadi::Item &item, const QString &mimeType = QString()) { if (item.isValid() && !item.hasPayload()) { cancelTask(i18n("Invalid item payload.")); return false; } else if (item.isValid() && mimeType != item.mimeType()) { cancelTask(i18n("Invalid payload MIME type. Expected %1, found %2", mimeType, item.mimeType())); return false; } return canPerformTask(); } bool canPerformTask(); /** * KAccounts support abstraction. * * Returns 0 when compiled without KAccounts or not configured for KAccounts */ int accountId() const; Akonadi::Collection rootCollection() const; protected Q_SLOTS: 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 itemRemoved(const Akonadi::Item &item) override; - void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) override; - void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) override; - void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) 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 itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) override; + void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) 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; bool handleError(KGAPI2::Job *job, bool cancelTask = true); void collectionsPartiallyRetrieved(const Akonadi::Collection::List &collections); virtual void slotAuthJobFinished(KGAPI2::Job *job); virtual void slotGenericJobFinished(KGAPI2::Job *job); private: bool m_isConfiguring = false; GoogleSettings *m_settings = nullptr; Akonadi::Collection m_rootCollection; Akonadi::Collection::List m_collections; QList m_handlers; CalendarHandler::Ptr m_freeBusyHandler; int m_jobs; friend class GoogleSettingsDialog; friend class GenericHandler; friend class CalendarHandler; friend class ContactHandler; friend class TaskHandler; }; #endif // GOOGLERESOURCE_H diff --git a/resources/google-new/taskhandler.cpp b/resources/google-new/taskhandler.cpp index 1d6783a4a..88aca5135 100644 --- a/resources/google-new/taskhandler.cpp +++ b/resources/google-new/taskhandler.cpp @@ -1,399 +1,337 @@ /* 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(); 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())); - qCDebug(GOOGLE_TASKS_LOG) << "Task added to list" << collection.remoteId(); KCalendarCore::Todo::Ptr todo = item.payload(); - TaskPtr ktodo(new Task(*todo)); - ktodo->setUid(QLatin1String("")); - - if (!ktodo->relatedTo(KCalendarCore::Incidence::RelTypeParent).isEmpty()) { - Akonadi::Item parentItem; - parentItem.setRemoteId(ktodo->relatedTo(KCalendarCore::Incidence::RelTypeParent)); - qCDebug(GOOGLE_TASKS_LOG) << "Fetching task parent" << parentItem.remoteId(); - - auto job = new ItemFetchJob(parentItem, this); - job->setCollection(collection); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - job->setProperty(TASK_PROPERTY, QVariant::fromValue(ktodo)); - - connect(job, &ItemFetchJob::finished, this, &TaskHandler::slotTaskAddedSearchFinished); - return; - } else { - auto job = new TaskCreateJob(ktodo, collection.remoteId(), m_settings->accountPtr(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &KGAPI2::Job::finished, this, &TaskHandler::slotCreateJobFinished); - } -} - -void TaskHandler::slotTaskAddedSearchFinished(KJob* job) -{ - if (job->error()) { - m_resource->cancelTask(i18n("Failed to add task: %1", job->errorString())); - return; - } - ItemFetchJob *fetchJob = qobject_cast(job); - Item item = job->property(ITEM_PROPERTY).value(); - TaskPtr task = job->property(TASK_PROPERTY).value(); - - Item::List items = fetchJob->items(); - qCDebug(GOOGLE_TASKS_LOG) << "Received" << items.count() << "parents for task"; - - const QString tasksListId = item.parentCollection().remoteId(); - - // Make sure account is still valid - if (!m_resource->canPerformTask()) { - return; - } - - KGAPI2::Job *newJob = nullptr; - // The parent is not known, so give up and just store the item in Google - // without the information about parent. - // TODO: this is not necessary - if (items.isEmpty()) { - task->setRelatedTo(QString(), KCalendarCore::Incidence::RelTypeParent); - newJob = new TaskCreateJob(task, tasksListId, m_settings->accountPtr(), this); - } else { - Item matchedItem = items.first(); - qCDebug(GOOGLE_TASKS_LOG) << "Adding task with parent" << matchedItem.remoteId(); - - task->setRelatedTo(matchedItem.remoteId(), KCalendarCore::Incidence::RelTypeParent); - TaskCreateJob *createJob = new TaskCreateJob(task, tasksListId, m_settings->accountPtr(), this); - createJob->setParentItem(matchedItem.remoteId()); - newJob = createJob; - } - - newJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(newJob, &KGAPI2::Job::finished, this, &TaskHandler::slotCreateJobFinished); + 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(); - ObjectsList objects = qobject_cast(job)->items(); - Q_ASSERT(objects.count() > 0); + TaskPtr task = qobject_cast(job)->items().first().dynamicCast(); - TaskPtr task = objects.first().dynamicCast(); item.setRemoteId(task->uid()); item.setRemoteRevision(task->etag()); item.setGid(task->uid()); item.setPayload(task.dynamicCast()); m_resource->changeCommitted(item); + 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(); - TaskPtr ktodo(new Task(*todo)); - QString parentUid = todo->relatedTo(KCalendarCore::Incidence::RelTypeParent); + 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, [ktodo, item, this](KGAPI2::Job* job){ + connect(job, &TaskMoveJob::finished, [this, task, item](KGAPI2::Job* job){ if (!m_resource->handleError(job)) { return; } - qCDebug(GOOGLE_TASKS_LOG) << "Move task" << item.remoteId() << "finished, modifying..."; - auto newJob = new TaskModifyJob(ktodo, item.parentCollection().remoteId(), job->account(), this); + 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::itemRemoved(const Item &item) +void TaskHandler::itemsRemoved(const Item::List &items) { - Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Removing task from list '%1'", item.parentCollection().displayName())); - qCDebug(GOOGLE_TASKS_LOG) << "Removing task" << item.remoteId(); + 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. */ - auto job = new ItemFetchJob(item.parentCollection()); - job->setAutoDelete(true); + // TODO: what if items belong to different collections? + auto job = new ItemFetchJob(items.first().parentCollection()); job->fetchScope().fetchFullPayload(true); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &ItemFetchJob::finished, this, &TaskHandler::slotRemoveTaskFetchJobFinished); -} - -void TaskHandler::slotRemoveTaskFetchJobFinished(KJob* job) -{ - if (job->error()) { - m_resource->cancelTask(i18n("Failed to delete task: %1", job->errorString())); - return; - } - qCDebug(GOOGLE_TASKS_LOG) << "Item fetched, removing..."; - - ItemFetchJob *fetchJob = qobject_cast(job); - Item removedItem = fetchJob->property(ITEM_PROPERTY).value(); - const Item::List items = fetchJob->items(); - - Item::List detachItems; - for (Item item : items) { - if (!item.hasPayload()) { - qCDebug(GOOGLE_TASKS_LOG) << "Item " << item.remoteId() << " does not have Todo payload"; - continue; - } - - KCalendarCore::Todo::Ptr todo = item.payload(); - /* If this item is child of the item we want to remove then add it to detach list */ - if (todo->relatedTo(KCalendarCore::Incidence::RelTypeParent) == removedItem.remoteId()) { - todo->setRelatedTo(QString(), KCalendarCore::Incidence::RelTypeParent); - item.setPayload(todo); - detachItems << item; - } - } - - /* If there are no items do detach, then delete the task right now */ - if (detachItems.isEmpty()) { - slotDoRemoveTask(job); - return; - } - - /* Send modify request to detach all the sub-tasks from the task that is about to be - * removed. */ - auto modifyJob = new ItemModifyJob(detachItems); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(removedItem)); - modifyJob->setAutoDelete(true); - connect(modifyJob, &ItemModifyJob::finished, this, &TaskHandler::slotDoRemoveTask); + 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::slotDoRemoveTask(KJob *job) +void TaskHandler::slotDoRemoveTasks(const Item::List &items) { - if (job->error()) { - m_resource->cancelTask(i18n("Failed to delete task: %1", job->errorString())); - return; - } - // Make sure account is still valid if (!m_resource->canPerformTask()) { return; } - - Item item = job->property(ITEM_PROPERTY).value< Item >(); + 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 */ - auto deleteJob = new TaskDeleteJob(item.remoteId(), item.parentCollection().remoteId(), m_settings->accountPtr(), this); - deleteJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(deleteJob, &TaskDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); + // 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::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination) +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 d875f7665..0035ebf79 100644 --- a/resources/google-new/taskhandler.h +++ b/resources/google-new/taskhandler.h @@ -1,54 +1,52 @@ /* 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 itemRemoved(const Akonadi::Item &item) override; - void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) 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 slotRemoveTaskFetchJobFinished(KJob *job); - void slotTaskAddedSearchFinished(KJob* job); - void slotDoRemoveTask(KJob *job); + void slotDoRemoveTasks(const Akonadi::Item::List &items); }; #endif // TASKHANDLER_H