diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -52,7 +52,7 @@ add_subdirectory( openxchange ) add_subdirectory( pop3 ) -add_subdirectory( google ) +add_subdirectory( google-groupware ) add_subdirectory( shared ) add_subdirectory( birthdays ) diff --git a/resources/google-groupware/CMakeLists.txt b/resources/google-groupware/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/resources/google-groupware/CMakeLists.txt @@ -0,0 +1,89 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_google_resource\") + +set(googleresource_SRCS + googleresource.cpp + googlesettings.cpp + googlesettingsdialog.cpp + defaultreminderattribute.cpp + googleresourcestate.cpp + generichandler.cpp + calendarhandler.cpp + taskhandler.cpp + contacthandler.cpp) + +ecm_qt_declare_logging_category(googleresource_SRCS + HEADER googleresource_debug.h + IDENTIFIER GOOGLE_LOG + CATEGORY_NAME org.kde.pim.google + DESCRIPTION "resource google (kdepim-runtime)" + EXPORT KDEPIMRUNTIME) +ecm_qt_declare_logging_category(googleresource_SRCS + HEADER googlecalendar_debug.h + IDENTIFIER GOOGLE_CALENDAR_LOG + CATEGORY_NAME org.kde.pim.google.calendar + DESCRIPTION "resource google calendar (kdepim-runtime)" + EXPORT KDEPIMRUNTIME) +ecm_qt_declare_logging_category(googleresource_SRCS + HEADER googletasks_debug.h + IDENTIFIER GOOGLE_TASKS_LOG + CATEGORY_NAME org.kde.pim.google.tasks + DESCRIPTION "resource google tasks (kdepim-runtime)" + EXPORT KDEPIMRUNTIME) +ecm_qt_declare_logging_category(googleresource_SRCS + HEADER googlecontacts_debug.h + IDENTIFIER GOOGLE_CONTACTS_LOG + CATEGORY_NAME org.kde.pim.google.contacts + DESCRIPTION "resource google contacts (kdepim-runtime)" + EXPORT KDEPIMRUNTIME) + +ki18n_wrap_ui(googleresource_SRCS googlesettingsdialog.ui) + +kconfig_add_kcfg_files(googleresource_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfgc) + +kcfg_generate_dbus_interface( + ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfg + org.kde.Akonadi.Google.Settings +) + +qt5_add_dbus_adaptor(googleresource_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.Google.Settings.xml + ${CMAKE_CURRENT_SOURCE_DIR}/googlesettings.h GoogleSettings +) + +add_executable(akonadi_google_resource ${googleresource_SRCS}) + +if( APPLE ) + set_target_properties(akonadi_google_resource PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../../Info.plist.template + ) + set_target_properties(akonadi_google_resource PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.google" + ) + set_target_properties(akonadi_google_resource PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Google Resource" + ) +endif() + +target_link_libraries(akonadi_google_resource + KF5::AkonadiCalendar + KF5::AkonadiCore + KF5::AkonadiAgentBase + KF5::CalendarCore + KF5::Contacts + KF5::Wallet + KF5::I18n + KF5::WindowSystem + KF5::Completion + KF5::TextWidgets + KPim::GAPICalendar + KPim::GAPIContacts + KPim::GAPICore + KPim::GAPITasks +) + +install(TARGETS akonadi_google_resource ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +install( + FILES googleresource.desktop + DESTINATION "${KDE_INSTALL_DATAROOTDIR}/akonadi/agents" +) diff --git a/resources/google-groupware/calendarhandler.h b/resources/google-groupware/calendarhandler.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/calendarhandler.h @@ -0,0 +1,69 @@ +/* + 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: + using GenericHandler::GenericHandler; + + QString mimeType() override; + bool canPerformTask(const Akonadi::Item &item) override; + bool canPerformTask(const Akonadi::Item::List &items) override; + + void retrieveCollections(const Akonadi::Collection &rootCollection) 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 slotItemsRetrieved(KGAPI2::Job *job); +private: + void setupCollection(Akonadi::Collection &collection, const KGAPI2::CalendarPtr &group); +}; + +class FreeBusyHandler : public QObject +{ + Q_OBJECT +public: + typedef std::unique_ptr Ptr; + + FreeBusyHandler(GoogleResourceStateInterface *iface, GoogleSettings *settings); + + QDateTime lastCacheUpdate() const; + void canHandleFreeBusy(const QString &email); + void retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end); +private: + GoogleResourceStateInterface *m_iface = nullptr; + GoogleSettings *m_settings = nullptr; +}; + +#endif // CALENDARHANDLER_H diff --git a/resources/google-groupware/calendarhandler.cpp b/resources/google-groupware/calendarhandler.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/calendarhandler.cpp @@ -0,0 +1,400 @@ +/* + 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 "googlecalendar_debug.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace KGAPI2; +using namespace Akonadi; + +static constexpr uint32_t KGAPIEventVersion = 1; + +QString CalendarHandler::mimeType() +{ + return KCalendarCore::Event::eventMimeType(); +} + +bool CalendarHandler::canPerformTask(const Item &item) +{ + return GenericHandler::canPerformTask(item); +} + +bool CalendarHandler::canPerformTask(const Item::List &items) +{ + return GenericHandler::canPerformTask(items); +} + +void CalendarHandler::setupCollection(Collection &collection, const CalendarPtr &calendar) +{ + collection.setContentMimeTypes({ mimeType() }); + collection.setName(calendar->uid()); + collection.setRemoteId(calendar->uid()); + if (calendar->editable()) { + collection.setRights(Collection::CanChangeCollection + |Collection::CanDeleteCollection + |Collection::CanCreateItem + |Collection::CanChangeItem + |Collection::CanDeleteItem); + } else { + collection.setRights(Collection::ReadOnly); + } + // TODO: for some reason, KOrganizer creates virtual collections + //newCollection.setVirtual(false); + // Setting icon + auto attr = collection.attribute(Collection::AddIfMissing); + attr->setDisplayName(calendar->title()); + attr->setIconName(QStringLiteral("view-calendar")); + // Setting color + if (calendar->backgroundColor().isValid()) { + auto colorAttr = collection.attribute(Collection::AddIfMissing); + colorAttr->setColor(calendar->backgroundColor()); + } + // Setting default reminders + auto reminderAttr = collection.attribute(Collection::AddIfMissing); + reminderAttr->setReminders(calendar->defaultReminders()); + // Block email reminders, since Google sends them for us + auto blockAlarms = collection.attribute(Collection::AddIfMissing); + blockAlarms->blockAlarmType(KCalendarCore::Alarm::Audio, false); + blockAlarms->blockAlarmType(KCalendarCore::Alarm::Display, false); + blockAlarms->blockAlarmType(KCalendarCore::Alarm::Procedure, false); +} + +void CalendarHandler::retrieveCollections(const Collection &rootCollection) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving calendars")); + qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieving calendars..."; + auto job = new CalendarFetchJob(m_settings->accountPtr(), this); + connect(job, &CalendarFetchJob::finished, this, [this, rootCollection](KGAPI2::Job *job){ + if (!m_iface->handleError(job)) { + return; + } + qCDebug(GOOGLE_CALENDAR_LOG) << "Calendars retrieved"; + + const ObjectsList calendars = qobject_cast(job)->items(); + Collection::List collections; + collections.reserve(calendars.count()); + const QStringList activeCalendars = m_settings->calendars(); + for (const auto &object : calendars) { + const CalendarPtr &calendar = object.dynamicCast(); + qCDebug(GOOGLE_CALENDAR_LOG) << " -" << calendar->title() << "(" << calendar->uid() << ")"; + if (!activeCalendars.contains(calendar->uid())) { + qCDebug(GOOGLE_CALENDAR_LOG) << "Skipping, not subscribed"; + continue; + } + Collection collection; + setupCollection(collection, calendar); + collection.setParentCollection(rootCollection); + collections << collection; + } + + m_iface->collectionsRetrievedFromHandler(collections); + }); +} + +void CalendarHandler::retrieveItems(const Collection &collection) +{ + qCDebug(GOOGLE_CALENDAR_LOG) << "Retrieving events for calendar" << collection.remoteId(); + const 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); + job->setFetchDeleted(true); + } else { + // No need to fetch deleted items for non-incremental update + job->setFetchDeleted(false); + 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, &EventFetchJob::finished, this, &CalendarHandler::slotItemsRetrieved); + + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving events for calendar '%1'", collection.displayName())); +} + +void CalendarHandler::slotItemsRetrieved(KGAPI2::Job *job) +{ + if (!m_iface->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( mimeType() ); + 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_iface->itemsRetrieved(changedItems); + } else { + m_iface->itemsRetrievedIncremental(changedItems, removedItems); + } + 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) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Adding event to calendar '%1'", collection.name())); + qCDebug(GOOGLE_CALENDAR_LOG) << "Event added to calendar" << collection.remoteId(); + EventPtr event(new Event(*item.payload())); + auto *job = new EventCreateJob(event, collection.remoteId(), m_settings->accountPtr(), this); + job->setSendUpdates(SendUpdatesPolicy::None); + connect(job, &EventCreateJob::finished, this, [this, item](KGAPI2::Job *job){ + if (!m_iface->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()); + m_iface->itemChangeCommitted(newItem); + newItem.setPayload(event.dynamicCast()); + new ItemModifyJob(newItem, this); + emitReadyStatus(); + }); +} + +void CalendarHandler::itemChanged(const Item &item, const QSet< QByteArray > &/*partIdentifiers*/) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Changing event in calendar '%1'", item.parentCollection().displayName())); + qCDebug(GOOGLE_CALENDAR_LOG) << "Changing event" << item.remoteId(); + EventPtr event(new Event(*item.payload())); + auto job = new EventModifyJob(event, item.parentCollection().remoteId(), m_settings->accountPtr(), this); + job->setSendUpdates(SendUpdatesPolicy::None); + job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + connect(job, &EventModifyJob::finished, this, &CalendarHandler::slotGenericJobFinished); +} + +void CalendarHandler::itemsRemoved(const Item::List &items) +{ + m_iface->emitStatus(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; + auto job = new EventDeleteJob(eventIds, items.first().parentCollection().remoteId(), m_settings->accountPtr(), this); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &EventDeleteJob::finished, this, &CalendarHandler::slotGenericJobFinished); +} + +void CalendarHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) +{ + m_iface->emitStatus(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); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &EventMoveJob::finished, this, &CalendarHandler::slotGenericJobFinished); +} + +void CalendarHandler::collectionAdded(const Collection &collection, const Collection &/*parent*/) +{ + m_iface->emitStatus(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); + connect(job, &CalendarCreateJob::finished, this, [this, collection](KGAPI2::Job *job){ + if (!m_iface->handleError(job)) { + return; + } + CalendarPtr calendar = qobject_cast(job)->items().first().dynamicCast(); + qCDebug(GOOGLE_CALENDAR_LOG) << "Created calendar" << calendar->uid(); + // Enable newly added calendar in settings, otherwise user won't see it + m_settings->addCalendar(calendar->uid()); + // TODO: the calendar returned by google is almost empty, i.e. it's not "editable", + // does not contain the color, etc + calendar->setEditable(true); + // Populate remoteId & other stuff + Collection newCollection(collection); + setupCollection(newCollection, calendar); + m_iface->collectionChangeCommitted(newCollection); + emitReadyStatus(); + }); +} + +void CalendarHandler::collectionChanged(const Collection &collection) +{ + m_iface->emitStatus(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, &CalendarModifyJob::finished, this, &CalendarHandler::slotGenericJobFinished); +} + +void CalendarHandler::collectionRemoved(const Collection &collection) +{ + m_iface->emitStatus(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, &CalendarDeleteJob::finished, this, &CalendarHandler::slotGenericJobFinished); +} + + +/** + * FreeBusy + */ +FreeBusyHandler::FreeBusyHandler(GoogleResourceStateInterface *iface, GoogleSettings *settings) + : m_iface(iface) + , m_settings(settings) +{ +} + +QDateTime FreeBusyHandler::lastCacheUpdate() const +{ + return QDateTime(); +} + +void FreeBusyHandler::canHandleFreeBusy(const QString &email) +{ + if (m_iface->canPerformTask()) { + m_iface->handlesFreeBusy(email, false); + return; + } + + auto job = new FreeBusyQueryJob(email, + QDateTime::currentDateTimeUtc(), + QDateTime::currentDateTimeUtc().addSecs(3600), + m_settings->accountPtr(), + this); + connect(job, &FreeBusyQueryJob::finished, this, [this](KGAPI2::Job *job){ + auto queryJob = qobject_cast(job); + if (!m_iface->handleError(job, false)) { + m_iface->handlesFreeBusy(queryJob->id(), false); + return; + } + m_iface->handlesFreeBusy(queryJob->id(), true); + }); +} + +void FreeBusyHandler::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) +{ + if (m_iface->canPerformTask()) { + m_iface->freeBusyRetrieved(email, QString(), false, QString()); + return; + } + + auto job = new FreeBusyQueryJob(email, start, end, m_settings->accountPtr(), this); + connect(job, &FreeBusyQueryJob::finished, this, [this](KGAPI2::Job *job) { + auto queryJob = qobject_cast(job); + + if (!m_iface->handleError(job, false)) { + m_iface->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); + const auto ranges = queryJob->busy(); + for (const auto &range : ranges) { + fb->addPeriod(range.busyStart, range.busyEnd); + } + + KCalendarCore::ICalFormat format; + const QString fbStr = format.createScheduleMessage(fb, KCalendarCore::iTIPRequest); + + m_iface->freeBusyRetrieved(queryJob->id(), fbStr, true, QString()); + }); +} diff --git a/resources/google-groupware/contacthandler.h b/resources/google-groupware/contacthandler.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/contacthandler.h @@ -0,0 +1,61 @@ +/* + 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 +#include + + +class ContactHandler : public GenericHandler +{ + Q_OBJECT +public: + using GenericHandler::GenericHandler; + + QString mimeType() override; + bool canPerformTask(const Akonadi::Item &item) override; + bool canPerformTask(const Akonadi::Item::List &items) override; + + void retrieveCollections(const Akonadi::Collection &rootCollection) 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 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 slotItemsRetrieved(KGAPI2::Job *job); + void slotUpdatePhotosItemsRetrieved(KJob *job); + void retrieveContactsPhotos(const QVariant &arguments); +private: + QString myContactsRemoteId() const; + void setupCollection(Akonadi::Collection &collection, const KGAPI2::ContactsGroupPtr &group); + QMap m_collections; +}; + +#endif // CONTACTHANDLER_H diff --git a/resources/google-groupware/contacthandler.cpp b/resources/google-groupware/contacthandler.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/contacthandler.cpp @@ -0,0 +1,488 @@ +/* + 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 "googlesettings.h" + +#include "googlecontacts_debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OTHERCONTACTS_REMOTEID QStringLiteral("OtherContacts") + +using namespace KGAPI2; +using namespace Akonadi; + +QString ContactHandler::mimeType() +{ + return KContacts::Addressee::mimeType(); +} + +bool ContactHandler::canPerformTask(const Item &item) +{ + return GenericHandler::canPerformTask(item); +} + +bool ContactHandler::canPerformTask(const Item::List &items) +{ + return GenericHandler::canPerformTask(items); +} + +QString ContactHandler::myContactsRemoteId() const +{ + return QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(m_settings->accountPtr()->accountName()))); +} + +void ContactHandler::setupCollection(Collection &collection, const ContactsGroupPtr &group) +{ + collection.setContentMimeTypes({ mimeType() }); + collection.setName(group->id()); + collection.setRemoteId(group->id()); + + 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"); + } + } + + // "My Contacts" is the only one not virtual + if (group->id() == myContactsRemoteId()) { + collection.setRights(Collection::CanCreateItem + |Collection::CanChangeItem + |Collection::CanDeleteItem); + } else { + collection.setRights(Collection::CanLinkItem + |Collection::CanUnlinkItem + |Collection::CanChangeItem); + collection.setVirtual(true); + if (!group->isSystemGroup()) { + collection.setRights(collection.rights() + |Collection::CanChangeCollection + |Collection::CanDeleteCollection); + } + } + + auto attr = collection.attribute(Collection::AddIfMissing); + attr->setDisplayName(realName); + attr->setIconName(QStringLiteral("view-pim-contacts")); +} + +void ContactHandler::retrieveCollections(const Collection &rootCollection) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving contacts groups")); + qCDebug(GOOGLE_CONTACTS_LOG) << "Retrieving contacts groups..."; + m_collections.clear(); + + Collection otherCollection; + otherCollection.setContentMimeTypes({ mimeType() }); + otherCollection.setName(i18n("Other Contacts")); + otherCollection.setParentCollection(rootCollection); + otherCollection.setRights(Collection::CanCreateItem + |Collection::CanChangeItem + |Collection::CanDeleteItem); + otherCollection.setRemoteId(OTHERCONTACTS_REMOTEID); + + auto attr = otherCollection.attribute(Collection::AddIfMissing); + attr->setDisplayName(i18n("Other Contacts")); + attr->setIconName(QStringLiteral("view-pim-contacts")); + + m_iface->collectionsRetrieved({ otherCollection }); + m_collections[ OTHERCONTACTS_REMOTEID ] = otherCollection; + + auto job = new ContactsGroupFetchJob(m_settings->accountPtr(), this); + connect(job, &ContactFetchJob::finished, this, [this, rootCollection](KGAPI2::Job *job){ + if (!m_iface->handleError(job)) { + return; + } + qCDebug(GOOGLE_CONTACTS_LOG) << "Contacts groups retrieved"; + + const ObjectsList objects = qobject_cast(job)->items(); + Collection::List collections; + collections.reserve(objects.count()); + std::transform(objects.cbegin(), objects.cend(), std::back_inserter(collections), + [this, &rootCollection](const ObjectPtr &object){ + const ContactsGroupPtr group = object.dynamicCast(); + qCDebug(GOOGLE_CONTACTS_LOG) << " -" << group->title() << "(" << group->id() << ")"; + Collection collection; + setupCollection(collection, group); + collection.setParentCollection(rootCollection); + m_collections[ collection.remoteId() ] = collection; + return collection; + }); + m_iface->collectionsRetrievedFromHandler(collections); + }); +} + +void ContactHandler::retrieveItems(const Collection &collection) +{ + // Contacts are stored inside "My Contacts" and "Other Contacts" only + if ((collection.remoteId() != OTHERCONTACTS_REMOTEID) + && (collection.remoteId() != myContactsRemoteId())) { + m_iface->itemsRetrievalDone(); + return; + } + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving contacts for group '%1'", collection.displayName())); + qCDebug(GOOGLE_CONTACTS_LOG) << "Retreiving contacts for group" << collection.remoteId() << "..."; + + auto job = new ContactFetchJob(m_settings->accountPtr(), this); + if (!collection.remoteRevision().isEmpty()) { + job->setFetchOnlyUpdated(collection.remoteRevision().toLongLong()); + job->setFetchDeleted(true); + } else { + // No need to fetch deleted items for a non-incremental update + job->setFetchDeleted(false); + } + connect(job, &ContactFetchJob::finished, this, &ContactHandler::slotItemsRetrieved); +} + +void ContactHandler::slotItemsRetrieved(KGAPI2::Job *job) +{ + if (!m_iface->handleError(job)) { + return; + } + + Collection collection = m_iface->currentCollection(); + + Item::List changedItems, removedItems; + QHash groupsMap; + QStringList 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( mimeType() ); + item.setParentCollection(collection); + item.setRemoteId(contact->uid()); + item.setRemoteRevision(contact->etag()); + item.setPayload(*contact.dynamicCast()); + + if (contact->deleted() + || (collection.remoteId() == OTHERCONTACTS_REMOTEID && !contact->groups().isEmpty()) + || (collection.remoteId() == myContactsRemoteId() && contact->groups().isEmpty())) { + 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) { + // We don't link contacts to "My Contacts" + if (group != myContactsRemoteId()) { + groupsMap[group] << item; + } + } + } + + if (isIncremental) { + m_iface->itemsRetrievedIncremental(changedItems, removedItems); + } else { + m_iface->itemsRetrieved(changedItems); + } + + for (auto iter = groupsMap.constBegin(), iterEnd = groupsMap.constEnd(); iter != iterEnd; ++iter) { + new LinkJob(m_collections[iter.key()], iter.value(), this); + } + // TODO: unlink if the group was removed! + + if (!changedPhotos.isEmpty()) { + QVariantMap map; + map[QStringLiteral("collection")] = QVariant::fromValue(collection); + map[QStringLiteral("modified")] = QVariant::fromValue(changedPhotos); + m_iface->scheduleCustomTask(this, "retrieveContactsPhotos", map); + } + + const QDateTime local(QDateTime::currentDateTime()); + const QDateTime UTC(local.toUTC()); + + collection.setRemoteRevision(QString::number(UTC.toSecsSinceEpoch())); + new CollectionModifyJob(collection, this); + + emitReadyStatus(); +} + +void ContactHandler::retrieveContactsPhotos(const QVariant &argument) +{ + if (!m_iface->canPerformTask()) { + return; + } + const auto map = argument.value(); + const auto collection = map[QStringLiteral("collection")].value(); + const auto changedPhotos = map[QStringLiteral("modified")].toStringList(); + m_iface->emitStatus(AgentBase::Running, i18ncp("@info:status", "Retrieving %1 contacts photos for group '%2'", + "Retrieving %1 contact photo for group '%2'", + changedPhotos.count(), collection.displayName())); + + Item::List items; + items.reserve(changedPhotos.size()); + std::transform(changedPhotos.cbegin(), changedPhotos.cend(), std::back_inserter(items), + [](const QString &contact){ + Item item; + item.setRemoteId(contact); + return item; + }); + auto job = new ItemFetchJob(items, this); + job->setCollection(collection); + job->fetchScope().fetchFullPayload(true); + connect(job, &ItemFetchJob::finished, this, &ContactHandler::slotUpdatePhotosItemsRetrieved); +} + +void ContactHandler::slotUpdatePhotosItemsRetrieved(KJob *job) +{ + // Make sure account is still valid + if (!m_iface->canPerformTask()) { + return; + } + const Item::List items = qobject_cast(job)->items(); + qCDebug(GOOGLE_CONTACTS_LOG) << "Fetched" << items.count() << "contacts for photo update"; + ContactsList contacts; + contacts.reserve(items.size()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), + [](const Item &item){ + return ContactPtr(new Contact(item.payload())); + }); + + auto photoJob = new ContactFetchPhotoJob(contacts, m_settings->accountPtr(), this); + photoJob->setProperty("processedItems", 0); + connect(photoJob, &ContactFetchPhotoJob::photoFetched, this, [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); + m_iface->emitPercent(100.0f*processedItems / items.count()); + + auto it = std::find_if(items.cbegin(), items.cend(), [&contact](const Item &item){ + return item.remoteId() == contact->uid(); + }); + if (it != items.cend()) { + Item newItem(*it); + newItem.setPayload(*contact.dynamicCast()); + new ItemModifyJob(newItem, this); + } + }); + + connect(photoJob, &ContactFetchPhotoJob::finished, this, &ContactHandler::slotGenericJobFinished); +} + +void ContactHandler::itemAdded(const Item &item, const Collection &collection) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Adding contact to group '%1'", collection.displayName())); + ContactPtr contact(new Contact(item.payload())); + qCDebug(GOOGLE_CONTACTS_LOG) << "Creating contact"; + + if (collection.remoteId() == myContactsRemoteId()) { + contact->addGroup(myContactsRemoteId()); + } + + auto job = new ContactCreateJob(contact, m_settings->accountPtr(), this); + connect(job, &ContactCreateJob::finished, this, [this, item](KGAPI2::Job *job){ + if (!m_iface->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()); + m_iface->itemChangeCommitted(newItem); + newItem.setPayload(*contact.dynamicCast()); + new ItemModifyJob(newItem, this); + emitReadyStatus(); + }); +} + +void ContactHandler::itemChanged(const Item &item, const QSet< QByteArray > &/*partIdentifiers*/) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Changing contact")); + qCDebug(GOOGLE_CONTACTS_LOG) << "Changing contact" << item.remoteId(); + ContactPtr contact(new Contact(item.payload())); + auto job = new ContactModifyJob(contact, m_settings->accountPtr(), this); + job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + connect(job, &ContactModifyJob::finished, this, &ContactHandler::slotGenericJobFinished); +} + +void ContactHandler::itemsRemoved(const Item::List &items) +{ + m_iface->emitStatus(AgentBase::Running, i18ncp("@info:status", "Removing %1 contacts", "Removing %1 contact", 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); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &ContactDeleteJob::finished, this, &ContactHandler::slotGenericJobFinished); +} + +void ContactHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) +{ + qCDebug(GOOGLE_CONTACTS_LOG) << "Moving contacts from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); + if (!(((collectionSource.remoteId() == myContactsRemoteId()) && (collectionDestination.remoteId() == OTHERCONTACTS_REMOTEID)) || + ((collectionSource.remoteId() == OTHERCONTACTS_REMOTEID) && (collectionDestination.remoteId() == myContactsRemoteId())))) { + m_iface->cancelTask(i18n("Invalid source or destination collection")); + } + + m_iface->emitStatus(AgentBase::Running, i18ncp("@info:status", "Moving %1 contacts from group '%2' to '%3'", + "Moving %1 contact from group '%2' to '%3'", + items.count(), collectionSource.remoteId(), collectionDestination.remoteId())); + ContactsList contacts; + contacts.reserve(items.count()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), + [this, &collectionSource, &collectionDestination](const Item &item){ + ContactPtr contact(new Contact(item.payload())); + // MyContacts -> OtherContacts + if (collectionSource.remoteId() == myContactsRemoteId() + && collectionDestination.remoteId() == OTHERCONTACTS_REMOTEID) { + contact->clearGroups(); + // OtherContacts -> MyContacts + } else if (collectionSource.remoteId() == OTHERCONTACTS_REMOTEID + && collectionDestination.remoteId() == myContactsRemoteId()) { + contact->addGroup(myContactsRemoteId()); + } + + return contact; + }); + qCDebug(GOOGLE_CONTACTS_LOG) << "Moving contacts from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); + auto job = new ContactModifyJob(contacts, m_settings->accountPtr(), this); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &ContactModifyJob::finished, this, &ContactHandler::slotGenericJobFinished); +} + +void ContactHandler::itemsLinked(const Item::List &items, const Collection &collection) +{ + m_iface->emitStatus(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(); + + ContactsList contacts; + contacts.reserve(items.count()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), + [this, &collection](const Item &item){ + ContactPtr contact(new Contact(item.payload())); + contact->addGroup(collection.remoteId()); + return contact; + }); + auto job = new ContactModifyJob(contacts, m_settings->accountPtr(), this); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &ContactModifyJob::finished, this, &ContactHandler::slotGenericJobFinished); +} + +void ContactHandler::itemsUnlinked(const Item::List &items, const Collection &collection) +{ + m_iface->emitStatus(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(); + + ContactsList contacts; + contacts.reserve(items.count()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), + [this, &collection](const Item &item){ + ContactPtr contact(new Contact(item.payload())); + contact->removeGroup(collection.remoteId()); + return contact; + }); + auto job = new ContactModifyJob(contacts, m_settings->accountPtr(), this); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &ContactModifyJob::finished, this, &ContactHandler::slotGenericJobFinished); +} + + +void ContactHandler::collectionAdded(const Collection &collection, const Collection & /*parent*/) +{ + m_iface->emitStatus(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, [this, collection](KGAPI2::Job *job){ + if (!m_iface->handleError(job)) { + return; + } + + ContactsGroupPtr group = qobject_cast(job)->items().first().dynamicCast(); + qCDebug(GOOGLE_CONTACTS_LOG) << "Contact group created:" << group->id(); + Collection newCollection(collection); + setupCollection(newCollection, group); + m_collections[ newCollection.remoteId() ] = newCollection; + m_iface->collectionChangeCommitted(newCollection); + emitReadyStatus(); + }); +} + +void ContactHandler::collectionChanged(const Collection &collection) +{ + m_iface->emitStatus(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, this, &ContactHandler::slotGenericJobFinished); +} + +void ContactHandler::collectionRemoved(const Collection &collection) +{ + m_iface->emitStatus(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, this, &ContactHandler::slotGenericJobFinished); +} diff --git a/resources/google/calendar/defaultreminderattribute.h b/resources/google-groupware/defaultreminderattribute.h rename from resources/google/calendar/defaultreminderattribute.h rename to resources/google-groupware/defaultreminderattribute.h diff --git a/resources/google/calendar/defaultreminderattribute.cpp b/resources/google-groupware/defaultreminderattribute.cpp rename from resources/google/calendar/defaultreminderattribute.cpp rename to resources/google-groupware/defaultreminderattribute.cpp --- a/resources/google/calendar/defaultreminderattribute.cpp +++ b/resources/google-groupware/defaultreminderattribute.cpp @@ -48,7 +48,7 @@ return; } - QVariant var = json.toVariant(); + const QVariant var = json.toVariant(); const QVariantList list = var.toList(); for (const QVariant &l : list) { QVariantMap reminder = l.toMap(); diff --git a/resources/google-groupware/generichandler.h b/resources/google-groupware/generichandler.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/generichandler.h @@ -0,0 +1,104 @@ +/* + 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 + +#include + +#include "googleresourcestateinterface.h" + +#define ITEM_PROPERTY "_AkonadiItem" +#define ITEMS_PROPERTY "_AkonadiItems" +#define COLLECTION_PROPERTY "_AkonadiCollection" + +namespace KGAPI2 { + class Job; +} + +class GoogleSettings; + +class GenericHandler : public QObject +{ + Q_OBJECT +public: + typedef std::unique_ptr Ptr; + + GenericHandler(GoogleResourceStateInterface *iface, GoogleSettings *settings); + virtual ~GenericHandler(); + + virtual QString mimeType() = 0; + + virtual void retrieveCollections(const Akonadi::Collection &rootCollection) = 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 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; + + /* + * Helper function for various handlers + */ + template + bool canPerformTask(const Akonadi::Item &item) + { + if (item.isValid() && (!item.hasPayload() || item.mimeType() != mimeType())) { + m_iface->cancelTask(i18n("Invalid item.")); + return false; + } + return m_iface->canPerformTask(); + } + + template + bool canPerformTask(const Akonadi::Item::List &items) + { + if (std::any_of(items.cbegin(), items.cend(), [this](const Akonadi::Item &item){ + return item.isValid() && (!item.hasPayload() || item.mimeType() != mimeType()); + })) { + m_iface->cancelTask(i18n("Invalid item.")); + return false; + } + return m_iface->canPerformTask(); + } + + virtual bool canPerformTask(const Akonadi::Item &item) = 0; + virtual bool canPerformTask(const Akonadi::Item::List &items) = 0; +protected Q_SLOTS: + void slotGenericJobFinished(KGAPI2::Job *job); +protected: + void emitReadyStatus(); + + GoogleResourceStateInterface *m_iface = nullptr; + GoogleSettings *m_settings = nullptr; +}; + +#endif // GENERICHANDLER_H diff --git a/resources/google-groupware/generichandler.cpp b/resources/google-groupware/generichandler.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/generichandler.cpp @@ -0,0 +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 . +*/ + +#include "generichandler.h" +#include "googleresourcestateinterface.h" +#include "googleresource_debug.h" + +#include + +#include + +GenericHandler::GenericHandler(GoogleResourceStateInterface *iface, GoogleSettings *settings) + : m_iface(iface) + , m_settings(settings) +{ +} + +GenericHandler::~GenericHandler() = default; + +void GenericHandler::itemsLinked(const Akonadi::Item::List &/*items*/, const Akonadi::Collection &/*collection*/) +{ + m_iface->cancelTask(i18n("Cannot handle item linking")); +} + +void GenericHandler::itemsUnlinked(const Akonadi::Item::List &/*items*/, const Akonadi::Collection &/*collection*/) +{ + m_iface->cancelTask(i18n("Cannot handle item unlinking")); +} + +void GenericHandler::slotGenericJobFinished(KGAPI2::Job *job) +{ + if (!m_iface->handleError(job)) { + return; + } + if (job->property(ITEM_PROPERTY).isValid()) { + qCDebug(GOOGLE_LOG) << "Item change committed"; + m_iface->itemChangeCommitted(job->property(ITEM_PROPERTY).value()); + } else if (job->property(ITEMS_PROPERTY).isValid()) { + qCDebug(GOOGLE_LOG) << "Items changes committed"; + m_iface->itemsChangesCommitted(job->property(ITEMS_PROPERTY).value()); + } else if (job->property(COLLECTION_PROPERTY).isValid()) { + qCDebug(GOOGLE_LOG) << "Collection change committed"; + m_iface->collectionChangeCommitted(job->property(COLLECTION_PROPERTY).value()); + } else { + qCDebug(GOOGLE_LOG) << "Task done"; + m_iface->taskDone(); + } + + emitReadyStatus(); +} + +void GenericHandler::emitReadyStatus() +{ + m_iface->emitStatus(Akonadi::AgentBase::Idle, i18nc("@status", "Ready")); +} + +#include "moc_generichandler.cpp" diff --git a/resources/google-groupware/googleresource.h b/resources/google-groupware/googleresource.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googleresource.h @@ -0,0 +1,109 @@ +/* + 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 "calendarhandler.h" +#include "generichandler.h" + +#include + +#define JOB_PROPERTY "_KGAPI2Job" + +namespace KGAPI2 { +class Job; +} + +class GoogleSettings; +class GoogleResourceState; + +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; + + bool canPerformTask(); + /** + * KAccounts support abstraction. + * + * Returns 0 when compiled without KAccounts or not configured for KAccounts + */ + int accountId() const; + + void emitReadyStatus(); + void collectionsRetrievedFromHandler(const Akonadi::Collection::List &collections); +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 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 slotAuthJobFinished(KGAPI2::Job *job); +private: + bool m_isConfiguring = false; + GoogleSettings *m_settings = nullptr; + Akonadi::Collection m_rootCollection; + + GoogleResourceState *m_iface; + + std::vector m_handlers; + FreeBusyHandler::Ptr m_freeBusyHandler; + int m_jobs; + + friend class GoogleSettingsDialog; + friend class GoogleResourceState; + + GenericHandler *fetchHandlerByMimetype(const QString &mimeType); + GenericHandler *fetchHandlerForCollection(const Akonadi::Collection &collection); +}; + +#endif // GOOGLERESOURCE_H diff --git a/resources/google-groupware/googleresource.cpp b/resources/google-groupware/googleresource.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googleresource.cpp @@ -0,0 +1,495 @@ +/* + 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 "googleresourcestate.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 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::ObserverV3() + , m_iface(new GoogleResourceState(this)) +{ + AttributeFactory::registerAttribute< DefaultReminderAttribute >(); + + 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, [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; + } + emitReadyStatus(); + synchronize(); + }); + + Q_EMIT status(NotConfigured, i18n("Waiting for KWallet...")); + updateResourceName(); + + m_freeBusyHandler.reset(new FreeBusyHandler(m_iface, m_settings)); + m_handlers.clear(); + m_handlers.push_back(GenericHandler::Ptr(new CalendarHandler(m_iface, m_settings))); + m_handlers.push_back(GenericHandler::Ptr(new ContactHandler(m_iface, m_settings))); + m_handlers.push_back(GenericHandler::Ptr(new TaskHandler(m_iface, m_settings))); + + new SettingsAdaptor(m_settings); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), + m_settings, QDBusConnection::ExportAdaptors); +} + +GoogleResource::~GoogleResource() +{ + delete m_iface; +} + +void GoogleResource::cleanup() +{ + m_settings->cleanup(); + ResourceBase::cleanup(); +} + +void GoogleResource::emitReadyStatus() +{ + Q_EMIT status(Idle, i18nc("@info:status", "Ready")); +} + +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; + } + + emitReadyStatus(); + 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 { + emitReadyStatus(); + } +} + +bool GoogleResource::handleError(KGAPI2::Job *job, bool _cancelTask) +{ + if ((job->error() == KGAPI2::NoError) || (job->error() == KGAPI2::OK)) { + return true; + } + qCWarning(GOOGLE_LOG) << "Got error:" << 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(); + } +} + +int GoogleResource::accountId() const +{ + return 0; +} + +GenericHandler *GoogleResource::fetchHandlerByMimetype(const QString &mimeType) +{ + auto it = std::find_if(m_handlers.cbegin(), m_handlers.cend(), + [&mimeType](const GenericHandler::Ptr &handler){ + return handler->mimeType() == mimeType; + }); + + if (it != m_handlers.cend()) { + return it->get(); + } else { + return nullptr; + } +} + +GenericHandler *GoogleResource::fetchHandlerForCollection(const Akonadi::Collection &collection) +{ + auto it = std::find_if(m_handlers.cbegin(), m_handlers.cend(), + [&collection](const GenericHandler::Ptr &handler){ + return collection.contentMimeTypes().contains(handler->mimeType()); + }); + if (it != m_handlers.cend()) { + return it->get(); + } else { + return nullptr; + } +} + +/* + * FreeBusy handling + */ + +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() +{ + if (!canPerformTask()) { + return; + } + qCDebug(GOOGLE_LOG) << "Retrieve Collections"; + + setCollectionStreamingEnabled(true); + 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")); + + collectionsRetrieved({ m_rootCollection }); + + m_jobs = m_handlers.size(); + for (const auto &handler : m_handlers) { + handler->retrieveCollections(m_rootCollection); + } +} + +void GoogleResource::collectionsRetrievedFromHandler(const Collection::List &collections) +{ + collectionsRetrieved(collections); + m_jobs--; + if (m_jobs == 0) { + qCDebug(GOOGLE_LOG) << "All collections retrieved!"; + collectionsRetrievalDone(); + //taskDone(); // ??? + emitReadyStatus(); + } +} + +void GoogleResource::retrieveItems(const Collection &collection) +{ + if (!canPerformTask()) { + return; + } + + auto handler = fetchHandlerForCollection(collection); + if (handler) { + handler->retrieveItems(collection); + } else { + qCWarning(GOOGLE_LOG) << "Unknown collection" << collection.name(); + itemsRetrieved({}); + } +} + +void GoogleResource::itemAdded(const Item &item, const Collection &collection) +{ + if (!canPerformTask()) { + return; + } + + auto handler = fetchHandlerByMimetype(item.mimeType()); + if (handler && handler->canPerformTask(item)) { + handler->itemAdded(item, collection); + } else { + qCWarning(GOOGLE_LOG) << "Could not add item" << item.mimeType(); + cancelTask(i18n("Invalid payload type")); + } +} + +void GoogleResource::itemChanged(const Item &item, const QSet< QByteArray > &partIdentifiers) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerByMimetype(item.mimeType()); + if (handler && handler->canPerformTask(item)) { + handler->itemChanged(item, partIdentifiers); + } else { + qCWarning(GOOGLE_LOG) << "Could not change item" << item.mimeType(); + cancelTask(i18n("Invalid payload type")); + } +} + +void GoogleResource::itemsRemoved(const Item::List &items) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerByMimetype(items.first().mimeType()); + if (handler && handler->canPerformTask(items)) { + handler->itemsRemoved(items); + } else { + qCWarning(GOOGLE_LOG) << "Could not remove item" << items.first().mimeType(); + cancelTask(i18n("Invalid payload type")); + } +} + +void GoogleResource::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerByMimetype(items.first().mimeType()); + if (handler && handler->canPerformTask(items)) { + handler->itemsMoved(items, collectionSource, collectionDestination); + } else if (!handler) { + qCWarning(GOOGLE_LOG) << "Could not move item" << items.first().mimeType() << "from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); + cancelTask(i18n("Invalid payload type")); + } +} + +void GoogleResource::itemsLinked(const Item::List &items, const Collection &collection) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerByMimetype(items.first().mimeType()); + if (handler && handler->canPerformTask(items)) { + handler->itemsLinked(items, collection); + } else if (!handler) { + qCWarning(GOOGLE_LOG) << "Could not link item" << items.first().mimeType() << "to" << collection.remoteId(); + cancelTask(i18n("Invalid payload type")); + } +} + +void GoogleResource::itemsUnlinked(const Item::List &items, const Collection &collection) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerByMimetype(items.first().mimeType()); + if (handler && handler->canPerformTask(items)) { + handler->itemsUnlinked(items, collection); + } else if (!handler) { + qCWarning(GOOGLE_LOG) << "Could not unlink item mimetype" << items.first().mimeType() << "from" << collection.remoteId(); + cancelTask(i18n("Invalid payload type")); + } +} + +void GoogleResource::collectionAdded(const Collection &collection, const Collection &parent) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerForCollection(collection); + if (handler) { + handler->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 Collection &collection) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerForCollection(collection); + if (handler) { + handler->collectionChanged(collection); + } else { + qCWarning(GOOGLE_LOG) << "Could not change collection" << collection.displayName() << "mimetypes:" << collection.contentMimeTypes(); + cancelTask(i18n("Unknown collection mimetype")); + } +} + +void GoogleResource::collectionRemoved(const Collection &collection) +{ + if (!canPerformTask()) { + return; + } + auto handler = fetchHandlerForCollection(collection); + if (handler) { + handler->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-groupware/googleresource.desktop b/resources/google-groupware/googleresource.desktop new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googleresource.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Google Groupware +Comment=Access your Google Calendars, Contacts and Tasks from KDE +Type=AkonadiResource +Exec=akonadi_google_resource +X-Akonadi-MimeTypes=text/calendar,text/directory,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.freebusy +X-Akonadi-Capabilities=Resource,FreeBusyProvider +X-Akonadi-Identifier=akonadi_google_resource +X-Akonadi-Custom-KAccounts=google-contacts,google-calendar +Icon=im-google diff --git a/resources/google-groupware/googleresourcestate.h b/resources/google-groupware/googleresourcestate.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googleresourcestate.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2020 Igor Poboiko + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef GOOGLERESOURCESTATE_H +#define GOOGLERESOURCESTATE_H + +#include "googleresourcestateinterface.h" + +class GoogleResource; + +class GoogleResourceState : public GoogleResourceStateInterface +{ +public: + explicit GoogleResourceState(GoogleResource *resource); + ~GoogleResourceState() = default; + + // Items handling + void itemRetrieved(const Akonadi::Item &item) override; + void itemsRetrieved(const Akonadi::Item::List &items) override; + void itemsRetrievedIncremental(const Akonadi::Item::List &changed, const Akonadi::Item::List &removed) override; + void itemsRetrievalDone() override; + void setTotalItems(int) override; + void itemChangeCommitted(const Akonadi::Item &item) override; + void itemsChangesCommitted(const Akonadi::Item::List &items) override; + Akonadi::Item::List currentItems() override; + + // Collections handling + void collectionsRetrieved(const Akonadi::Collection::List &collections) override; + void collectionAttributesRetrieved(const Akonadi::Collection &collection) override; + void collectionChangeCommitted(const Akonadi::Collection &collection) override; + void collectionsRetrievedFromHandler(const Akonadi::Collection::List &collections) override; + Akonadi::Collection currentCollection() override; + + // Tags handling + void tagsRetrieved(const Akonadi::Tag::List &tags, const QHash &) override; + void tagChangeCommitted(const Akonadi::Tag &tag) override; + + // Relations handling + void relationsRetrieved(const Akonadi::Relation::List &tags) override; + + // FreeBusy handling + void freeBusyRetrieved(const QString &email, const QString &freeBusy, bool success, const QString &errorText) override; + void handlesFreeBusy(const QString &email, bool handles) override; + + // Result reporting + void changeProcessed() override; + void cancelTask(const QString &errorString) override; + void deferTask() override; + void taskDone() override; + + void emitStatus(int status, const QString &message) override; + void emitError(const QString &message) override; + void emitWarning(const QString &message) override; + void emitPercent(int percent) override; + + // Other + void scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument) override; + + // Google-specific stuff + bool canPerformTask() override; + bool handleError(KGAPI2::Job *job, bool _cancelTask) override; +private: + GoogleResource *m_resource = nullptr; +}; + +#endif // GOOGLERESOURCESTATE_H diff --git a/resources/google-groupware/googleresourcestate.cpp b/resources/google-groupware/googleresourcestate.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googleresourcestate.cpp @@ -0,0 +1,182 @@ +/* + Copyright (c) 2020 Igor Poboiko + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "googleresourcestate.h" +#include "googleresource.h" + +using namespace Akonadi; + +GoogleResourceState::GoogleResourceState(GoogleResource *resource) + : m_resource(resource) +{ +} + +// Items handling +void GoogleResourceState::itemRetrieved(const Item &item) +{ + m_resource->itemRetrieved(item); +} + +void GoogleResourceState::itemsRetrieved(const Item::List &items) +{ + m_resource->itemsRetrieved(items); +} + +void GoogleResourceState::itemsRetrievedIncremental(const Item::List &changed, const Item::List &removed) +{ + m_resource->itemsRetrievedIncremental(changed, removed); +} + +void GoogleResourceState::itemsRetrievalDone() +{ + m_resource->itemsRetrievalDone(); +} + +void GoogleResourceState::setTotalItems(int amount) +{ + m_resource->setTotalItems(amount); + +} +void GoogleResourceState::itemChangeCommitted(const Item &item) +{ + m_resource->changeCommitted(item); +} + +void GoogleResourceState::itemsChangesCommitted(const Item::List &items) +{ + m_resource->changesCommitted(items); +} + +Item::List GoogleResourceState::currentItems() +{ + return m_resource->currentItems(); +} + +// Collections handling +void GoogleResourceState::collectionsRetrieved(const Collection::List &collections) +{ + m_resource->collectionsRetrieved(collections); +} + +void GoogleResourceState::collectionAttributesRetrieved(const Collection &collection) +{ + m_resource->collectionAttributesRetrieved(collection); +} + +void GoogleResourceState::collectionChangeCommitted(const Collection &collection) +{ + m_resource->changeCommitted(collection); +} + +void GoogleResourceState::collectionsRetrievedFromHandler(const Collection::List &collections) +{ + m_resource->collectionsRetrievedFromHandler(collections); +} + +Collection GoogleResourceState::currentCollection() +{ + return m_resource->currentCollection(); +} + +// Tags handling +void GoogleResourceState::tagsRetrieved(const Tag::List &tags, const QHash &tagMembers) +{ + m_resource->tagsRetrieved(tags, tagMembers); +} + +void GoogleResourceState::tagChangeCommitted(const Tag &tag) +{ + m_resource->changeCommitted(tag); +} + + +// Relations handling +void GoogleResourceState::relationsRetrieved(const Relation::List &relations) +{ + m_resource->relationsRetrieved(relations); +} + + +// FreeBusy handling +void GoogleResourceState::freeBusyRetrieved(const QString &email, const QString &freeBusy, bool success, const QString &errorText = QString()) +{ + m_resource->freeBusyRetrieved(email, freeBusy, success, errorText); +} + +void GoogleResourceState::handlesFreeBusy(const QString &email, bool handles) +{ + m_resource->handlesFreeBusy(email, handles); +} + + +// Result reporting +void GoogleResourceState::changeProcessed() +{ + m_resource->changeProcessed(); +} + +void GoogleResourceState::cancelTask(const QString &errorString) +{ + m_resource->cancelTask(errorString); +} + +void GoogleResourceState::deferTask() +{ + m_resource->deferTask(); +} + +void GoogleResourceState::taskDone() +{ + m_resource->taskDone(); +} + +void GoogleResourceState::emitStatus(int status, const QString &message) +{ + Q_EMIT m_resource->status(status, message); +} + +void GoogleResourceState::emitError(const QString &message) +{ + Q_EMIT m_resource->error(message); +} + +void GoogleResourceState::emitWarning(const QString &message) +{ + Q_EMIT m_resource->warning(message); +} + +void GoogleResourceState::emitPercent(int percent) +{ + Q_EMIT m_resource->percent(percent); +} + +bool GoogleResourceState::canPerformTask() +{ + return m_resource->canPerformTask(); +} + +bool GoogleResourceState::handleError(KGAPI2::Job *job, bool _cancelTask) +{ + return m_resource->handleError(job, _cancelTask); +} + +void GoogleResourceState::scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument) +{ + return m_resource->scheduleCustomTask(receiver, method, argument); +} diff --git a/resources/google-groupware/googleresourcestateinterface.h b/resources/google-groupware/googleresourcestateinterface.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googleresourcestateinterface.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2020 Igor Poboiko + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef GOOGLERESOURCESTATEINTERFACE_H +#define GOOGLERESOURCESTATEINTERFACE_H + +#include "resourcestateinterface.h" + +namespace KGAPI2 +{ +class Job; +} + +/** + * This is a ResourceStateInterface with some specific for Google Resource bits + */ +class GoogleResourceStateInterface : public ResourceStateInterface +{ +public: + /** + * Returns whether the resource is operational (i.e. account is configured) + */ + virtual bool canPerformTask() = 0; + + /** + * Handles an error (if any) for a job. It includes cancelling a task + * (if there was an error), or retrying to authenticate (if necessary) + */ + virtual bool handleError(KGAPI2::Job *job, bool _cancelTask = true) = 0; + + /** + * Each handler use this to report that it has finished collection fetching + */ + virtual void collectionsRetrievedFromHandler(const Akonadi::Collection::List &collections) = 0; + + /** + * FreeBusy + */ + virtual void freeBusyRetrieved(const QString &email, const QString &freeBusy, bool success, const QString &errorText = QString()) = 0; + virtual void handlesFreeBusy(const QString &email, bool handles) = 0; + + virtual void scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument) = 0; +}; + +#endif // GOOGLERESOURCESTATEINTERFACE_H diff --git a/resources/google/common/googlesettings.h b/resources/google-groupware/googlesettings.h rename from resources/google/common/googlesettings.h rename to resources/google-groupware/googlesettings.h --- a/resources/google/common/googlesettings.h +++ b/resources/google-groupware/googlesettings.h @@ -1,5 +1,6 @@ /* Copyright (C) 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 @@ -21,34 +22,56 @@ #include "settingsbase.h" #include +#include + +#include + +namespace KWallet { +class Wallet; +} /** * @brief Settings object * * Provides read-only access to application clientId and * clientSecret and read-write access to accessToken and - * refreshToken. + * refreshToken. Interacts with KWallet. */ class GoogleSettings : public SettingsBase { Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.Akonadi.Google.ExtendedSettings") public: GoogleSettings(); void setWindowId(WId id); void setResourceId(const QString &resourceIdentifier); QString appId() const; - QString clientId() const; QString clientSecret() const; - virtual QString account() const; - virtual void setAccount(const QString &account); + void addCalendar(const QString &calendar); + void addTaskList(const QString &taskList); + KGAPI2::AccountPtr accountPtr(); + // Wallet + bool isReady() const; + bool storeAccount(KGAPI2::AccountPtr account); + void cleanup(); +Q_SIGNALS: + void accountReady(bool ready); + void accountChanged(); +private Q_SLOTS: + void slotWalletOpened(bool success); private: WId m_winId; QString m_resourceId; + bool m_isReady = false; + KGAPI2::AccountPtr m_account; + QPointer m_wallet; + + KGAPI2::AccountPtr fetchAccountFromWallet(const QString &accountName); }; #endif // GOOGLESETTINGS_H diff --git a/resources/google-groupware/googlesettings.cpp b/resources/google-groupware/googlesettings.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googlesettings.cpp @@ -0,0 +1,183 @@ +/* + Copyright (C) 2011-2013 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 . +*/ + +#include "googlesettings.h" +#include "settingsbase.h" +#include "googleresource_debug.h" + +#include +#include +#include + +using namespace KWallet; +using namespace KGAPI2; + +static const QString googleWalletFolder = QStringLiteral("Akonadi Google"); + +GoogleSettings::GoogleSettings() + : m_winId(0) +{ + m_wallet = Wallet::openWallet(Wallet::NetworkWallet(), + m_winId, Wallet::Asynchronous); + if (m_wallet) { + connect(m_wallet.data(), &Wallet::walletOpened, this, &GoogleSettings::slotWalletOpened); + } else { + qCWarning(GOOGLE_LOG) << "Failed to open wallet!"; + } +} + +void GoogleSettings::slotWalletOpened(bool success) +{ + if (!success) { + qCWarning(GOOGLE_LOG) << "Failed to open wallet!"; + Q_EMIT accountReady(false); + return; + } + + if (!m_wallet->hasFolder(googleWalletFolder) + && !m_wallet->createFolder(googleWalletFolder)) { + qCWarning(GOOGLE_LOG) << "Failed to create wallet folder" << googleWalletFolder; + Q_EMIT accountReady(false); + return; + } + + if (!m_wallet->setFolder(googleWalletFolder)) { + qWarning() << "Failed to open wallet folder" << googleWalletFolder; + Q_EMIT accountReady(false); + return; + } + qCDebug(GOOGLE_LOG) << "Wallet opened, reading" << account(); + if (!account().isEmpty()) { + m_account = fetchAccountFromWallet(account()); + } + m_isReady = true; + Q_EMIT accountReady(true); +} + +KGAPI2::AccountPtr GoogleSettings::fetchAccountFromWallet(const QString &accountName) +{ + if (!m_wallet->entryList().contains(accountName)) { + qCDebug(GOOGLE_LOG) << "Account" << accountName << "not found in KWallet"; + return AccountPtr(); + } + + QMap map; + m_wallet->readMap(accountName, map); + +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + const QStringList scopes = map[QStringLiteral("scopes")].split(QLatin1Char(','), QString::SkipEmptyParts); +#else + const QStringList scopes = map[QStringLiteral("scopes")].split(QLatin1Char(','), Qt::SkipEmptyParts); +#endif + QList scopeUrls; + scopeUrls.reserve(scopes.count()); + for (const QString &scope : scopes) { + scopeUrls << QUrl(scope); + } + AccountPtr account(new Account(accountName, + map[QStringLiteral("accessToken")], + map[QStringLiteral("refreshToken")], + scopeUrls)); + return account; +} + +bool GoogleSettings::storeAccount(AccountPtr account) +{ + // Removing the old one (if present) + if (m_account && (account->accountName() != m_account->accountName())) { + cleanup(); + } + // Populating the new one + m_account = account; + + QStringList scopes; + const QList urlScopes = m_account->scopes(); + scopes.reserve(urlScopes.count()); + for (const QUrl &url : urlScopes) { + scopes << url.toString(); + } + + QMap map; + map[QStringLiteral("accessToken")] = m_account->accessToken(); + map[QStringLiteral("refreshToken")] = m_account->refreshToken(); + map[QStringLiteral("scopes")] = scopes.join(QLatin1Char(',')); + // Removing previous junk (if present) + cleanup(); + if (m_wallet->writeMap(m_account->accountName(), map) != 0) { + qCWarning(GOOGLE_LOG) << "Failed to write new account entry to wallet"; + return false; + } + SettingsBase::setAccount(m_account->accountName()); + m_isReady = true; + return true; +} + +void GoogleSettings::cleanup() +{ + if (m_account && m_wallet) { + m_wallet->removeEntry(m_account->accountName()); + } +} + +void GoogleSettings::addCalendar(const QString &calendar) +{ + if (calendars().isEmpty() || calendars().contains(calendar)) { + return; + } + setCalendars(calendars() << calendar); + save(); +} + +void GoogleSettings::addTaskList(const QString &taskList) +{ + if (calendars().isEmpty() || taskLists().contains(taskList)) { + return; + } + setTaskLists(taskLists() << taskList); + save(); +} + +QString GoogleSettings::clientId() const +{ + return QStringLiteral("554041944266.apps.googleusercontent.com"); +} + +QString GoogleSettings::clientSecret() const +{ + return QStringLiteral("mdT1DjzohxN3npUUzkENT0gO"); +} + +bool GoogleSettings::isReady() const +{ + return m_isReady; +} + +AccountPtr GoogleSettings::accountPtr() +{ + return m_account; +} + +void GoogleSettings::setWindowId(WId id) +{ + m_winId = id; +} + +void GoogleSettings::setResourceId(const QString &resourceIdentificator) +{ + m_resourceId = resourceIdentificator; +} diff --git a/resources/google/common/googlesettingsdialog.h b/resources/google-groupware/googlesettingsdialog.h rename from resources/google/common/googlesettingsdialog.h rename to resources/google-groupware/googlesettingsdialog.h --- a/resources/google/common/googlesettingsdialog.h +++ b/resources/google-groupware/googlesettingsdialog.h @@ -1,5 +1,6 @@ /* Copyright (C) 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 @@ -19,64 +20,37 @@ #define GOOGLESETTINGSDIALOG_H #include - #include +namespace Ui { + class GoogleSettingsDialog; +} namespace KGAPI2 { -class Job; + class Job; } - class GoogleResource; class GoogleSettings; -class GoogleAccountManager; - -class QGroupBox; -class QComboBox; -class QCheckBox; -class KPluralHandlingSpinBox; -class QPushButton; -class QVBoxLayout; class GoogleSettingsDialog : public QDialog { Q_OBJECT - public: - explicit GoogleSettingsDialog(GoogleAccountManager *accountManager, WId wId, GoogleResource *parent); - ~GoogleSettingsDialog() override; - - GoogleAccountManager *accountManager() const; - KGAPI2::AccountPtr currentAccount() const; - -public Q_SLOTS: - void reloadAccounts(); - -Q_SIGNALS: - void currentAccountChanged(const QString &accountName); - + explicit GoogleSettingsDialog(GoogleResource *resource, GoogleSettings *settings, WId wId); + ~GoogleSettingsDialog(); protected: bool handleError(KGAPI2::Job *job); - virtual void saveSettings() = 0; - QVBoxLayout *mainLayout() const; - + void accountChanged(); +private: + GoogleResource *m_resource; + GoogleSettings *m_settings; + Ui::GoogleSettingsDialog *m_ui = nullptr; + KGAPI2::AccountPtr m_account; private Q_SLOTS: - void slotSaveSettings(); - void slotAddAccountClicked(); - void slotRemoveAccountClicked(); + void slotConfigure(); void slotAuthJobFinished(KGAPI2::Job *job); - void slotAccountAuthenticated(KGAPI2::Job *job); - -private: - GoogleResource *m_parentResource = nullptr; - GoogleAccountManager *m_accountManager = nullptr; - - QGroupBox *m_accGroupBox = nullptr; - QPushButton *m_addAccButton = nullptr; - QPushButton *m_removeAccButton = nullptr; - QComboBox *m_accComboBox = nullptr; - QCheckBox *m_enableRefresh = nullptr; - KPluralHandlingSpinBox *m_refreshSpinBox = nullptr; - QVBoxLayout *m_mainLayout = nullptr; + void slotSaveSettings(); + void slotReloadCalendars(); + void slotReloadTaskLists(); }; #endif // GOOGLESETTINGSDIALOG_H diff --git a/resources/google-groupware/googlesettingsdialog.cpp b/resources/google-groupware/googlesettingsdialog.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googlesettingsdialog.cpp @@ -0,0 +1,290 @@ +/* + Copyright (C) 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 "googlesettingsdialog.h" +#include "ui_googlesettingsdialog.h" +#include "googlesettings.h" +#include "googleresource.h" +#include "googleresource_debug.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KGAPI2; + +GoogleSettingsDialog::GoogleSettingsDialog(GoogleResource *resource, GoogleSettings *settings, WId wId) + : QDialog() + , m_resource(resource) + , m_settings(settings) +{ + if (wId) { + setAttribute(Qt::WA_NativeWindow, true); + KWindowSystem::setMainWindow(windowHandle(), wId); + } + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + QWidget *mainWidget = new QWidget(this); + mainLayout->addWidget(mainWidget); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + okButton->setDefault(true); + okButton->setShortcut(Qt::CTRL | Qt::Key_Return); + mainLayout->addWidget(buttonBox); + + m_ui = new Ui::GoogleSettingsDialog; + m_ui->setupUi(mainWidget); + + m_ui->refreshSpinBox->setSuffix(ki18np(" minute", " minutes")); + m_ui->enableRefresh->setChecked(m_settings->enableIntervalCheck()); + m_ui->refreshSpinBox->setEnabled(m_settings->enableIntervalCheck()); + m_ui->refreshSpinBox->setValue(m_settings->intervalCheckTime()); + + m_ui->eventsLimitCombo->setMaximumDate(QDate::currentDate()); + m_ui->eventsLimitCombo->setMinimumDate(QDate::fromString(QStringLiteral("2000-01-01"), Qt::ISODate)); + m_ui->eventsLimitCombo->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate + |KDateComboBox::DatePicker | KDateComboBox::WarnOnInvalid); + if (m_settings->eventsSince().isEmpty()) { + const QString ds = QStringLiteral("%1-01-01").arg(QString::number(QDate::currentDate().year() - 3)); + m_ui->eventsLimitCombo->setDate(QDate::fromString(ds, Qt::ISODate)); + } else { + m_ui->eventsLimitCombo->setDate(QDate::fromString(m_settings->eventsSince(), Qt::ISODate)); + } + + connect(buttonBox, &QDialogButtonBox::accepted, this, &GoogleSettingsDialog::slotSaveSettings); + connect(buttonBox, &QDialogButtonBox::rejected, this, &GoogleSettingsDialog::reject); + connect(m_ui->reloadCalendarsBtn, &QPushButton::clicked, this, &GoogleSettingsDialog::slotReloadCalendars); + connect(m_ui->reloadTaskListsBtn, &QPushButton::clicked, this, &GoogleSettingsDialog::slotReloadTaskLists); + connect(m_ui->configureBtn, &QPushButton::clicked, this, &GoogleSettingsDialog::slotConfigure); + if (m_settings->isReady()) { + m_account = m_settings->accountPtr(); + } + connect(m_settings, &GoogleSettings::accountReady, this, [this](bool ready){ + if (ready) { + m_account = m_settings->accountPtr(); + accountChanged(); + } + }); + QMetaObject::invokeMethod(this, &GoogleSettingsDialog::accountChanged, Qt::QueuedConnection); +} + +GoogleSettingsDialog::~GoogleSettingsDialog() +{ + delete m_ui; +} + +bool GoogleSettingsDialog::handleError(Job *job) +{ + if ((job->error() == KGAPI2::NoError) || (job->error() == KGAPI2::OK)) { + return true; + } + + if (job->error() == KGAPI2::Unauthorized) { + qCDebug(GOOGLE_LOG) << job << job->errorString(); + const QList resourceScopes = m_resource->scopes(); + for (const QUrl &scope : resourceScopes) { + if (!m_account->scopes().contains(scope)) { + m_account->addScope(scope); + } + } + + AuthJob *authJob = new AuthJob(m_account, m_settings->clientId(), + m_settings->clientSecret(), this); + authJob->setProperty(JOB_PROPERTY, QVariant::fromValue(job)); + connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAuthJobFinished); + + return false; + } + + KMessageBox::sorry(this, job->errorString()); + return false; +} + +void GoogleSettingsDialog::accountChanged() +{ + if (!m_account) { + m_ui->accountLabel->setText(i18n("not configured")); + m_ui->calendarsBox->setDisabled(true); + m_ui->calendarsList->clear(); + m_ui->taskListsBox->setDisabled(true); + m_ui->taskListsList->clear(); + return; + } + m_ui->accountLabel->setText(QStringLiteral("%1").arg(m_account->accountName())); + slotReloadCalendars(); + slotReloadTaskLists(); +} + +void GoogleSettingsDialog::slotConfigure() +{ + m_account = AccountPtr(new Account()); + const QList resourceScopes = m_resource->scopes(); + for (const QUrl &scope : resourceScopes) { + if (!m_account->scopes().contains(scope)) { + m_account->addScope(scope); + } + } + AuthJob *authJob = new AuthJob(m_account, + m_settings->clientId(), + m_settings->clientSecret()); + connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAuthJobFinished); +} + +void GoogleSettingsDialog::slotAuthJobFinished(Job *job) +{ + auto authJob = qobject_cast(job); + m_account = authJob->account(); + if (authJob->error() != KGAPI2::NoError) { + KMessageBox::sorry(this, authJob->errorString()); + return; + } + accountChanged(); + + auto otherJob = job->property(JOB_PROPERTY).value(); + if (otherJob) { + otherJob->setAccount(m_account); + otherJob->restart(); + } +} + +void GoogleSettingsDialog::slotSaveSettings() +{ + if (!m_account || !m_settings->storeAccount(m_account)) { + m_settings->setAccount(QString()); + m_settings->setEnableIntervalCheck(m_ui->enableRefresh->isChecked()); + m_settings->setIntervalCheckTime(m_ui->refreshSpinBox->value()); + m_settings->setCalendars({}); + m_settings->setTaskLists({}); + m_settings->setEventsSince(QString()); + m_settings->save(); + return; + } + m_settings->setAccount(m_account->accountName()); + m_settings->setEnableIntervalCheck(m_ui->enableRefresh->isChecked()); + m_settings->setIntervalCheckTime(m_ui->refreshSpinBox->value()); + + QStringList calendars; + for (int i = 0; i < m_ui->calendarsList->count(); i++) { + QListWidgetItem *item = m_ui->calendarsList->item(i); + + if (item->checkState() == Qt::Checked) { + calendars.append(item->data(Qt::UserRole).toString()); + } + } + m_settings->setCalendars(calendars); + + if (m_ui->eventsLimitCombo->isValid()) { + m_settings->setEventsSince(m_ui->eventsLimitCombo->date().toString(Qt::ISODate)); + } + + QStringList taskLists; + for (int i = 0; i < m_ui->taskListsList->count(); i++) { + QListWidgetItem *item = m_ui->taskListsList->item(i); + + if (item->checkState() == Qt::Checked) { + taskLists.append(item->data(Qt::UserRole).toString()); + } + } + m_settings->setTaskLists(taskLists); + m_settings->save(); + + accept(); +} + +void GoogleSettingsDialog::slotReloadCalendars() +{ + m_ui->calendarsBox->setDisabled(true); + m_ui->calendarsList->clear(); + + if (!m_account) { + return; + } + + auto fetchJob = new CalendarFetchJob(m_account, this); + connect(fetchJob, &CalendarFetchJob::finished, this, [this](Job *job){ + if (!handleError(job) || !m_account) { + m_ui->calendarsBox->setEnabled(false); + return; + } + + const ObjectsList objects = qobject_cast(job)->items(); + + QStringList activeCalendars; + if (m_account->accountName() == m_settings->account()) { + activeCalendars = m_settings->calendars(); + } + m_ui->calendarsList->clear(); + for (const ObjectPtr &object : objects) { + const CalendarPtr calendar = object.dynamicCast(); + + QListWidgetItem *item = new QListWidgetItem(calendar->title()); + item->setData(Qt::UserRole, calendar->uid()); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); + item->setCheckState((activeCalendars.isEmpty() || activeCalendars.contains(calendar->uid())) ? Qt::Checked : Qt::Unchecked); + m_ui->calendarsList->addItem(item); + } + + m_ui->calendarsBox->setEnabled(true); + }); +} + +void GoogleSettingsDialog::slotReloadTaskLists() +{ + if (!m_account) { + return; + } + + m_ui->taskListsBox->setDisabled(true); + m_ui->taskListsList->clear(); + + auto job = new TaskListFetchJob(m_account, this); + connect(job, &TaskListFetchJob::finished, this, [this](KGAPI2::Job *job){ + if (!handleError(job) || !m_account) { + m_ui->taskListsBox->setDisabled(true); + return; + } + + const ObjectsList objects = qobject_cast(job)->items(); + + QStringList activeTaskLists; + if (m_account->accountName() == m_settings->account()) { + activeTaskLists = m_settings->taskLists(); + } + m_ui->taskListsList->clear(); + for (const ObjectPtr &object : objects) { + const TaskListPtr taskList = object.dynamicCast(); + + QListWidgetItem *item = new QListWidgetItem(taskList->title()); + item->setData(Qt::UserRole, taskList->uid()); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); + item->setCheckState((activeTaskLists.isEmpty() || activeTaskLists.contains(taskList->uid())) ? Qt::Checked : Qt::Unchecked); + m_ui->taskListsList->addItem(item); + } + + m_ui->taskListsBox->setEnabled(true); + + }); +} diff --git a/resources/google-groupware/googlesettingsdialog.ui b/resources/google-groupware/googlesettingsdialog.ui new file mode 100644 --- /dev/null +++ b/resources/google-groupware/googlesettingsdialog.ui @@ -0,0 +1,182 @@ + + + GoogleSettingsDialog + + + + 0 + 0 + 584 + 680 + + + + + + + + + Account: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + <b>not confgured</b> + + + + + + + Configure... + + + + + + + + + Refresh + + + false + + + + + + Refresh interval: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + refreshSpinBox + + + + + + + 10 + + + 720 + + + 30 + + + + + + + Enable interval refresh + + + + + + + + + + Calendars + + + + + + + + + Fetch only events since + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + eventsLimitCombo + + + + + + + + + + Reload + + + + .. + + + + + + + + + + Tasklists + + + + + + + + + Reload + + + + .. + + + + + + + + + + + KPluralHandlingSpinBox + QSpinBox +
kpluralhandlingspinbox.h
+
+ + KDateComboBox + QComboBox +
kdatecombobox.h
+
+
+ + + + enableRefresh + toggled(bool) + refreshSpinBox + setEnabled(bool) + + + 182 + 99 + + + 312 + 128 + + + + +
diff --git a/resources/google-groupware/resourcestateinterface.h b/resources/google-groupware/resourcestateinterface.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/resourcestateinterface.h @@ -0,0 +1,75 @@ +/* + Copyright (c) 2020 Igor Poboiko + Copyright (c) 2010 Klarälvdalens Datakonsult AB, + a KDAB Group company + Author: Kevin Ottens + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef RESOURCESTATEINTERFACE_H +#define RESOURCESTATEINTERFACE_H + +#include +#include +#include + +/** + * This is a generic interface for ResourceBase class + */ +class ResourceStateInterface +{ +public: + typedef QSharedPointer Ptr; + + virtual ~ResourceStateInterface() = default; + + // Items handling + virtual void itemRetrieved(const Akonadi::Item &item) = 0; + virtual void itemsRetrieved(const Akonadi::Item::List &items) = 0; + virtual void itemsRetrievedIncremental(const Akonadi::Item::List &changed, const Akonadi::Item::List &removed) = 0; + virtual void itemsRetrievalDone() = 0; + virtual void setTotalItems(int) = 0; + virtual void itemChangeCommitted(const Akonadi::Item &item) = 0; + virtual void itemsChangesCommitted(const Akonadi::Item::List &items) = 0; + virtual Akonadi::Item::List currentItems() = 0; + + // Collections handling + virtual void collectionsRetrieved(const Akonadi::Collection::List &collections) = 0; + virtual void collectionAttributesRetrieved(const Akonadi::Collection &collection) = 0; + virtual void collectionChangeCommitted(const Akonadi::Collection &collection) = 0; + virtual Akonadi::Collection currentCollection() = 0; + + // Tags handling + virtual void tagsRetrieved(const Akonadi::Tag::List &tags, const QHash &) = 0; + virtual void tagChangeCommitted(const Akonadi::Tag &tag) = 0; + + // Relations handling + virtual void relationsRetrieved(const Akonadi::Relation::List &tags) = 0; + + // Result reporting + virtual void changeProcessed() = 0; + virtual void cancelTask(const QString &errorString) = 0; + virtual void deferTask() = 0; + virtual void taskDone() = 0; + + virtual void emitStatus(int status, const QString &message) = 0; + virtual void emitError(const QString &message) = 0; + virtual void emitWarning(const QString &message) = 0; + virtual void emitPercent(int percent) = 0; +}; + +#endif // RESOURCESTATEINTERFACE_H diff --git a/resources/google/calendar/settingsbase.kcfg b/resources/google-groupware/settingsbase.kcfg rename from resources/google/calendar/settingsbase.kcfg rename to resources/google-groupware/settingsbase.kcfg diff --git a/resources/google/calendar/settingsbase.kcfgc b/resources/google-groupware/settingsbase.kcfgc rename from resources/google/calendar/settingsbase.kcfgc rename to resources/google-groupware/settingsbase.kcfgc diff --git a/resources/google-groupware/taskhandler.h b/resources/google-groupware/taskhandler.h new file mode 100644 --- /dev/null +++ b/resources/google-groupware/taskhandler.h @@ -0,0 +1,53 @@ +/* + 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() override; + bool canPerformTask(const Akonadi::Item &item) override; + bool canPerformTask(const Akonadi::Item::List &items) override; + + void retrieveCollections(const Akonadi::Collection &rootCollection) 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 slotItemsRetrieved(KGAPI2::Job *job); +private: + void setupCollection(Akonadi::Collection &colleciton, const KGAPI2::TaskListPtr &taskList); + void doRemoveTasks(const Akonadi::Item::List &items); +}; + +#endif // TASKHANDLER_H diff --git a/resources/google-groupware/taskhandler.cpp b/resources/google-groupware/taskhandler.cpp new file mode 100644 --- /dev/null +++ b/resources/google-groupware/taskhandler.cpp @@ -0,0 +1,356 @@ +/* + 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 "googletasks_debug.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TASK_PROPERTY "_KGAPI2::TaskPtr" + +using namespace KGAPI2; +using namespace Akonadi; + +QString TaskHandler::mimeType() +{ + return KCalendarCore::Todo::todoMimeType(); +} + +bool TaskHandler::canPerformTask(const Item &item) +{ + return GenericHandler::canPerformTask(item); +} + +bool TaskHandler::canPerformTask(const Item::List &items) +{ + return GenericHandler::canPerformTask(items); +} + +void TaskHandler::setupCollection(Collection &collection, const TaskListPtr &taskList) +{ + collection.setContentMimeTypes({ mimeType() }); + collection.setName(taskList->uid()); + 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")); +} + +void TaskHandler::retrieveCollections(const Collection &rootCollection) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Retrieving task lists")); + qCDebug(GOOGLE_TASKS_LOG) << "Retrieving tasks..."; + auto job = new TaskListFetchJob(m_settings->accountPtr(), this); + connect(job, &TaskListFetchJob::finished, this, [this, rootCollection](KGAPI2::Job *job){ + if (!m_iface->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) << " -" << taskList->title() << "(" << taskList->uid() << ")"; + + if (!activeTaskLists.contains(taskList->uid())) { + qCDebug(GOOGLE_TASKS_LOG) << "Skipping, not subscribed"; + continue; + } + + Collection collection; + setupCollection(collection, taskList); + collection.setParentCollection(rootCollection); + collections << collection; + } + + m_iface->collectionsRetrievedFromHandler(collections); + }); +} + +void TaskHandler::retrieveItems(const Collection &collection) +{ + m_iface->emitStatus(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->setFetchDeleted(true); + } else { + // No need to fetch deleted items for non-incremental update + job->setFetchDeleted(false); + } + job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); + connect(job, &TaskFetchJob::finished, this, &TaskHandler::slotItemsRetrieved); +} + +void TaskHandler::slotItemsRetrieved(KGAPI2::Job *job) +{ + if (!m_iface->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) { + const TaskPtr task = object.dynamicCast(); + + Item item; + item.setMimeType( mimeType() ); + 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_iface->itemsRetrievedIncremental(changedItems, removedItems); + } else { + m_iface->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) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Adding event to calendar '%1'", collection.displayName())); + TaskPtr task(new Task(*item.payload())); + 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); + connect(job, &TaskCreateJob::finished, this, [this, item](KGAPI2::Job *job){ + if (!m_iface->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()); + m_iface->itemChangeCommitted(newItem); + newItem.setPayload(task.dynamicCast()); + new ItemModifyJob(newItem, this); + emitReadyStatus(); + }); +} + +void TaskHandler::itemChanged(const Item &item, const QSet< QByteArray > &/*partIdentifiers*/) +{ + m_iface->emitStatus(AgentBase::Running, i18nc("@info:status", "Changing task in list '%1'", item.parentCollection().displayName())); + qCDebug(GOOGLE_TASKS_LOG) << "Changing task" << item.remoteId(); + + auto todo = item.payload(); + const QString parentUid = todo->relatedTo(KCalendarCore::Incidence::RelTypeParent); + // 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, [this, todo, item](KGAPI2::Job *job){ + if (!m_iface->handleError(job)) { + return; + } + TaskPtr task(new Task(*todo)); + auto newJob = new TaskModifyJob(task, item.parentCollection().remoteId(), job->account(), this); + newJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); + connect(newJob, &TaskModifyJob::finished, this, &TaskHandler::slotGenericJobFinished); + }); +} + +void TaskHandler::itemsRemoved(const Item::List &items) +{ + m_iface->emitStatus(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(items.first().parentCollection()); + job->fetchScope().fetchFullPayload(true); + connect(job, &ItemFetchJob::finished, this, [this, items](KJob *job){ + if (job->error()) { + m_iface->cancelTask(i18n("Failed to delete task: %1", job->errorString())); + return; + } + const Item::List fetchedItems = qobject_cast(job)->items(); + Item::List detachItems; + TasksList detachTasks; + for (const Item &fetchedItem : fetchedItems) { + auto todo = fetchedItem.payload(); + TaskPtr task(new Task(*todo)); + const QString parentId = task->relatedTo(KCalendarCore::Incidence::RelTypeParent); + if (parentId.isEmpty()) { + continue; + } + + auto it = std::find_if(items.cbegin(), items.cend(), [&parentId](const Item &item){ + return item.remoteId() == parentId; + }); + if (it != items.cend()) { + Item newItem(fetchedItem); + qCDebug(GOOGLE_TASKS_LOG) << "Detaching child" << newItem.remoteId() << "from" << parentId; + todo->setRelatedTo(QString(), KCalendarCore::Incidence::RelTypeParent); + newItem.setPayload(todo); + detachItems << newItem; + detachTasks << task; + } + } + /* If there are no items do detach, then delete the task right now */ + if (detachItems.isEmpty()) { + doRemoveTasks(items); + return; + } + + qCDebug(GOOGLE_TASKS_LOG) << "Reparenting" << detachItems.count() << "children..."; + auto moveJob = new TaskMoveJob(detachTasks, items.first().parentCollection().remoteId(), + QString(), m_settings->accountPtr(), this); + connect(moveJob, &TaskMoveJob::finished, this, [this, items, detachItems](KGAPI2::Job *job){ + if (job->error()) { + m_iface->cancelTask(i18n("Failed to reparent subtasks: %1", job->errorString())); + return; + } + // Update items inside Akonadi DB too + new ItemModifyJob(detachItems); + // Perform actual removal + doRemoveTasks(items); + }); + }); +} + +void TaskHandler::doRemoveTasks(const Item::List &items) +{ + // Make sure account is still valid + if (!m_iface->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 */ + auto job = new TaskDeleteJob(taskIds, items.first().parentCollection().remoteId(), m_settings->accountPtr(), this); + job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); + connect(job, &TaskDeleteJob::finished, this, &TaskHandler::slotGenericJobFinished); +} + +void TaskHandler::itemsMoved(const Item::List &/*item*/, const Collection &/*collectionSource*/, const Collection &/*collectionDestination*/) +{ + m_iface->cancelTask(i18n("Moving tasks between task lists is not supported")); +} + +void TaskHandler::collectionAdded(const Collection &collection, const Collection &/*parent*/) +{ + m_iface->emitStatus(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); + connect(job, &TaskListCreateJob::finished, this, [this, collection](KGAPI2::Job *job){ + if (!m_iface->handleError(job)) { + return; + } + + TaskListPtr taskList = qobject_cast(job)->items().first().dynamicCast(); + qCDebug(GOOGLE_TASKS_LOG) << "Task list created:" << taskList->uid(); + // Enable newly added task list in settings + m_settings->addTaskList(taskList->uid()); + // Populate remoteId & other stuff + Collection newCollection(collection); + setupCollection(newCollection, taskList); + m_iface->collectionChangeCommitted(newCollection); + emitReadyStatus(); + }); +} + +void TaskHandler::collectionChanged(const Collection &collection) +{ + m_iface->emitStatus(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, &TaskListModifyJob::finished, this, &TaskHandler::slotGenericJobFinished); +} + +void TaskHandler::collectionRemoved(const Collection &collection) +{ + m_iface->emitStatus(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, &TaskListDeleteJob::finished, this, &TaskHandler::slotGenericJobFinished); +} diff --git a/resources/google/CMakeLists.txt b/resources/google/CMakeLists.txt deleted file mode 100644 --- a/resources/google/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - - -add_subdirectory(calendar) -add_subdirectory(contacts) - diff --git a/resources/google/calendar/CMakeLists.txt b/resources/google/calendar/CMakeLists.txt deleted file mode 100644 --- a/resources/google/calendar/CMakeLists.txt +++ /dev/null @@ -1,72 +0,0 @@ - -add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_googlecalendar_resource\") - - -set(calendarresource_SRCS - calendarresource.cpp - defaultreminderattribute.cpp - settings.cpp - settingsdialog.cpp - ../common/googleresource.cpp - ../common/googleaccountmanager.cpp - ../common/googlesettings.cpp - ../common/googlesettingsdialog.cpp - ../common/kgapiversionattribute.cpp - ${accounts_SRCS} - ) -ecm_qt_declare_logging_category(calendarresource_SRCS HEADER googlecalendarresource_debug.h IDENTIFIER GOOGLE_CALENDAR_LOG CATEGORY_NAME org.kde.pim.google.calendar - DESCRIPTION "resource google calendar (kdepim-runtime)" - EXPORT KDEPIMRUNTIME - ) - - -kconfig_add_kcfg_files(calendarresource_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfgc) - -kcfg_generate_dbus_interface( - ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfg - org.kde.Akonadi.GoogleCalendar.Settings - ) - -qt5_add_dbus_adaptor(calendarresource_SRCS - ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.GoogleCalendar.Settings.xml - ${CMAKE_CURRENT_SOURCE_DIR}/settings.h Settings - ) - -add_executable(akonadi_googlecalendar_resource ${calendarresource_SRCS}) - -if( APPLE ) - set_target_properties(akonadi_googlecalendar_resource PROPERTIES - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../../Info.plist.template - ) - set_target_properties(akonadi_googlecalendar_resource PROPERTIES - MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.googlecalendar" - ) - set_target_properties(akonadi_googlecalendar_resource PROPERTIES - MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Google Calendar Resource" - ) -endif() - - - - -target_link_libraries(akonadi_googlecalendar_resource - KF5::AkonadiCore - KF5::CalendarCore - KF5::AkonadiCalendar - KPim::GAPICalendar - KPim::GAPICore - KPim::GAPITasks - KF5::AkonadiAgentBase - KF5::Wallet - KF5::I18n - KF5::WindowSystem - KF5::Completion - KF5::TextWidgets - ) - -install(TARGETS akonadi_googlecalendar_resource ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) - -install( - FILES googlecalendarresource.desktop - DESTINATION "${KDE_INSTALL_DATAROOTDIR}/akonadi/agents" - ) diff --git a/resources/google/calendar/Messages.sh b/resources/google/calendar/Messages.sh deleted file mode 100644 --- a/resources/google/calendar/Messages.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /usr/bin/env bash -$EXTRACTRC *.ui *.kcfg >> rc.cpp -$XGETTEXT *.cpp -o $podir/akonadi_googlecalendar_resource.pot -$XGETTEXT ../common/* -j -o $podir/akonadi_googlecalendar_resource.pot diff --git a/resources/google/calendar/calendarresource.h b/resources/google/calendar/calendarresource.h deleted file mode 100644 --- a/resources/google/calendar/calendarresource.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - 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 GOOGLE_CALENDAR_CALENDARRESOURCE_H -#define GOOGLE_CALENDAR_CALENDARRESOURCE_H - -#include "common/googleresource.h" - -#include -#include -#include - -class CalendarResource : public GoogleResource, public Akonadi::FreeBusyProviderBase -{ - Q_OBJECT -public: - explicit CalendarResource(const QString &id); - ~CalendarResource() override; - -public: - using GoogleResource::collectionChanged; // So we don't trigger -Woverloaded-virtual - GoogleSettings *settings() const override; - QList< QUrl > scopes() const override; - -protected: - // Freebusy - QDateTime lastCacheUpdate() const override; - void canHandleFreeBusy(const QString &email) const override; - void retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) override; - -protected: - using ResourceBase::retrieveItems; // Suppress -Woverload-virtual - -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 collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override; - void collectionChanged(const Akonadi::Collection &collection) override; - void collectionRemoved(const Akonadi::Collection &collection) override; - - void slotItemsRetrieved(KGAPI2::Job *job); - void slotCollectionsRetrieved(KGAPI2::Job *job); - void slotCalendarsRetrieved(KGAPI2::Job *job); - void slotRemoveTaskFetchJobFinished(KJob *job); - void slotDoRemoveTask(KJob *job); - void slotModifyTaskReparentFinished(KGAPI2::Job *job); - void slotTaskAddedSearchFinished(KJob *); - void slotCreateJobFinished(KGAPI2::Job *job); - - void slotCanHandleFreeBusyJobFinished(KGAPI2::Job *job); - void slotRetrieveFreeBusyJobFinished(KGAPI2::Job *job); - -protected: - int runConfigurationDialog(WId windowId) override; - void updateResourceName() override; - -private: - QMap m_collections; - Akonadi::Collection m_rootCollection; -}; - -#endif // CALENDARRESOURCE_H diff --git a/resources/google/calendar/calendarresource.cpp b/resources/google/calendar/calendarresource.cpp deleted file mode 100644 --- a/resources/google/calendar/calendarresource.cpp +++ /dev/null @@ -1,794 +0,0 @@ -/* - 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 . -*/ - -#include "calendarresource.h" -#include "defaultreminderattribute.h" -#include "settings.h" -#include "settingsdialog.h" -#include "googlecalendarresource_debug.h" -#include "common/kgapiversionattribute.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define ROOT_COLLECTION_REMOTEID QStringLiteral("RootCollection") -#define CALENDARS_PROPERTY "_KGAPI2CalendarPtr" -#define TASK_PROPERTY "_KGAPI2::TaskPtr" - -Q_DECLARE_METATYPE(KGAPI2::ObjectsList) -Q_DECLARE_METATYPE(KGAPI2::TaskPtr) - -namespace { -static constexpr uint32_t KGAPIEventVersion = 1; -} - -using namespace Akonadi; -using namespace KGAPI2; - -CalendarResource::CalendarResource(const QString &id) - : GoogleResource(id) -{ - AttributeFactory::registerAttribute< DefaultReminderAttribute >(); - AttributeFactory::registerAttribute(); - updateResourceName(); -} - -CalendarResource::~CalendarResource() -{ -} - -GoogleSettings *CalendarResource::settings() const -{ - return Settings::self(); -} - -int CalendarResource::runConfigurationDialog(WId windowId) -{ - QScopedPointer settingsDialog(new SettingsDialog(accountManager(), windowId, this)); - settingsDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("im-google"))); - - return settingsDialog->exec(); -} - -void CalendarResource::updateResourceName() -{ - const QString accountName = Settings::self()->account(); - setName(i18nc("%1 is account name (user@gmail.com)", "Google Calendars and Tasks (%1)", accountName.isEmpty() ? i18n("not configured") : accountName)); -} - -QList< QUrl > CalendarResource::scopes() const -{ - const QList scopes = { Account::calendarScopeUrl(), Account::tasksScopeUrl()}; - - return scopes; -} - -void CalendarResource::retrieveItems(const Akonadi::Collection &collection) -{ - if (!canPerformTask()) { - return; - } - - // 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(); - } - - KGAPI2::Job *job = nullptr; - if (collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())) { - if (!collection.hasAttribute() || collection.attribute()->version() != KGAPIEventVersion) { - lastSyncDelta = -1; - } - - EventFetchJob *fetchJob = new EventFetchJob(collection.remoteId(), account(), this); - if (lastSyncDelta > -1 && lastSyncDelta < 25 * 24 * 3600) { - fetchJob->setFetchOnlyUpdated(collection.remoteRevision().toULongLong()); - } - if (!Settings::self()->eventsSince().isEmpty()) { - const QDate date = QDate::fromString(Settings::self()->eventsSince(), Qt::ISODate); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - fetchJob->setTimeMin(QDateTime(date).toSecsSinceEpoch()); -#else - fetchJob->setTimeMin(QDateTime(date.startOfDay()).toSecsSinceEpoch()); -#endif - } - job = fetchJob; - } else if (collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType())) { - TaskFetchJob *fetchJob = new TaskFetchJob(collection.remoteId(), account(), this); - if (lastSyncDelta > -1 && lastSyncDelta < 25 * 25 * 3600) { - fetchJob->setFetchOnlyUpdated(collection.remoteRevision().toULongLong()); - } - job = fetchJob; - } else { - itemsRetrieved(Item::List()); - return; - } - - job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(job, &KGAPI2::Job::progress, this, &CalendarResource::emitPercent); - connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotItemsRetrieved); -} - -void CalendarResource::retrieveCollections() -{ - if (!canPerformTask()) { - return; - } - - CalendarFetchJob *fetchJob = new CalendarFetchJob(account(), this); - connect(fetchJob, &EventFetchJob::finished, this, &CalendarResource::slotCalendarsRetrieved); -} - -void CalendarResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) -{ - if ((!collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType()) - && !collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType())) - || (!canPerformTask(item, KCalendarCore::Event::eventMimeType()) - && !canPerformTask(item, KCalendarCore::Todo::todoMimeType()))) { - return; - } - - if (collection.parentCollection() == Akonadi::Collection::root()) { - cancelTask(i18n("The top-level collection cannot contain any tasks or events")); - return; - } - - KGAPI2::Job *job = nullptr; - if (item.hasPayload()) { - KCalendarCore::Event::Ptr event = item.payload(); - EventPtr kevent(new Event(*event)); - auto cjob = new EventCreateJob(kevent, collection.remoteId(), account(), this); - cjob->setSendUpdates(SendUpdatesPolicy::None); - job = cjob; - } else if (item.hasPayload()) { - 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.setGid(ktodo->relatedTo(KCalendarCore::Incidence::RelTypeParent)); - - ItemFetchJob *fetchJob = new ItemFetchJob(parentItem, this); - fetchJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - fetchJob->setProperty(TASK_PROPERTY, QVariant::fromValue(ktodo)); - - connect(fetchJob, &ItemFetchJob::finished, this, &CalendarResource::slotTaskAddedSearchFinished); - return; - } else { - job = new TaskCreateJob(ktodo, collection.remoteId(), account(), this); - } - } else { - cancelTask(i18n("Invalid payload type")); - return; - } - - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &CreateJob::finished, this, &CalendarResource::slotCreateJobFinished); -} - -void CalendarResource::itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) -{ - Q_UNUSED(partIdentifiers); - - if (!canPerformTask(item, KCalendarCore::Event::eventMimeType()) - && !canPerformTask(item, KCalendarCore::Todo::todoMimeType())) { - return; - } - - KGAPI2::Job *job = nullptr; - if (item.hasPayload()) { - KCalendarCore::Event::Ptr event = item.payload(); - EventPtr kevent(new Event(*event)); - - auto mjob = new EventModifyJob(kevent, item.parentCollection().remoteId(), account(), this); - mjob->setSendUpdates(SendUpdatesPolicy::None); - connect(mjob, &EventModifyJob::finished, this, &CalendarResource::slotGenericJobFinished); - job = mjob; - } else if (item.hasPayload()) { - KCalendarCore::Todo::Ptr todo = item.payload(); - //FIXME unused ktodo ? - //TaskPtr ktodo(new Task(*todo)); - QString parentUid = todo->relatedTo(KCalendarCore::Incidence::RelTypeParent); - job = new TaskMoveJob(item.remoteId(), item.parentCollection().remoteId(), parentUid, account(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &TaskMoveJob::finished, this, &CalendarResource::slotModifyTaskReparentFinished); - } else { - cancelTask(i18n("Invalid payload type")); - return; - } - - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); -} - -void CalendarResource::itemRemoved(const Akonadi::Item &item) -{ - if (!canPerformTask()) { - return; - } - - if (item.mimeType() == KCalendarCore::Event::eventMimeType()) { - KGAPI2::Job *job = new EventDeleteJob(item.remoteId(), item.parentCollection().remoteId(), account(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &EventDeleteJob::finished, this, &CalendarResource::slotGenericJobFinished); - } else if (item.mimeType() == KCalendarCore::Todo::todoMimeType()) { - /* 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. */ - ItemFetchJob *fetchJob = new ItemFetchJob(item.parentCollection()); - fetchJob->setAutoDelete(true); - fetchJob->fetchScope().fetchFullPayload(true); - fetchJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(fetchJob, &ItemFetchJob::finished, this, &CalendarResource::slotRemoveTaskFetchJobFinished); - fetchJob->start(); - } else { - cancelTask(i18n("Invalid payload type. Expected event or todo, got %1", item.mimeType())); - } -} - -void CalendarResource::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination) -{ - if (!canPerformTask()) { - return; - } - - if (collectionDestination.parentCollection() == Akonadi::Collection::root()) { - cancelTask(i18n("The top-level collection cannot contain any tasks or events")); - return; - } - - if (item.mimeType() != KCalendarCore::Event::eventMimeType()) { - cancelTask(i18n("Moving tasks between task lists is not supported")); - return; - } - - KGAPI2::Job *job = new EventMoveJob(item.remoteId(), collectionSource.remoteId(), - collectionDestination.remoteId(), account(), - this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &EventMoveJob::finished, this, &CalendarResource::slotGenericJobFinished); -} - -void CalendarResource::collectionAdded(const Collection &collection, const Collection &parent) -{ - Q_UNUSED(parent) - - if (!canPerformTask()) { - return; - } - - KGAPI2::Job *job = nullptr; - if (collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())) { - CalendarPtr calendar(new Calendar()); - calendar->setTitle(collection.displayName()); - calendar->setEditable(true); - job = new CalendarCreateJob(calendar, account(), this); - } - if (collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType())) { - TaskListPtr taskList(new TaskList()); - taskList->setTitle(collection.displayName()); - - job = new TaskListCreateJob(taskList, account(), this); - } else { - cancelTask(i18n("Unknown collection mimetype")); - return; - } - - job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished); -} - -void CalendarResource::collectionChanged(const Collection &collection) -{ - if (!canPerformTask()) { - return; - } - - KGAPI2::Job *job = nullptr; - if (collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())) { - CalendarPtr calendar(new Calendar()); - calendar->setUid(collection.remoteId()); - calendar->setTitle(collection.displayName()); - calendar->setEditable(true); - job = new CalendarModifyJob(calendar, account(), this); - } - if (collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType())) { - TaskListPtr taskList(new TaskList()); - taskList->setUid(collection.remoteId()); - taskList->setTitle(collection.displayName()); - job = new TaskListModifyJob(taskList, account(), this); - } else { - cancelTask(i18n("Unknown collection mimetype")); - return; - } - - job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished); -} - -void CalendarResource::collectionRemoved(const Collection &collection) -{ - if (!canPerformTask()) { - return; - } - - KGAPI2::Job *job = nullptr; - if (collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())) { - job = new CalendarDeleteJob(collection.remoteId(), account(), this); - } else if (collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType())) { - job = new TaskListDeleteJob(collection.remoteId(), account(), this); - } else { - cancelTask(i18n("Unknown collection mimetype")); - return; - } - - job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished); -} - -void CalendarResource::slotCalendarsRetrieved(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - CalendarFetchJob *calendarJob = qobject_cast(job); - ObjectsList objects = calendarJob->items(); - calendarJob->deleteLater(); - - TaskListFetchJob *fetchJob = new TaskListFetchJob(job->account(), this); - fetchJob->setProperty(CALENDARS_PROPERTY, QVariant::fromValue(objects)); - connect(fetchJob, &EventFetchJob::finished, this, &CalendarResource::slotCollectionsRetrieved); -} - -void CalendarResource::slotCollectionsRetrieved(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - TaskListFetchJob *fetchJob = qobject_cast(job); - ObjectsList calendars = fetchJob->property(CALENDARS_PROPERTY).value(); - ObjectsList taskLists = fetchJob->items(); - - CachePolicy cachePolicy; - if (Settings::self()->enableIntervalCheck()) { - cachePolicy.setInheritFromParent(false); - cachePolicy.setIntervalCheckTime(Settings::self()->intervalCheckTime()); - } - - m_rootCollection = Collection(); - m_rootCollection.setContentMimeTypes(QStringList() << Collection::mimeType()); - m_rootCollection.setRemoteId(ROOT_COLLECTION_REMOTEID); - m_rootCollection.setName(fetchJob->account()->accountName()); - m_rootCollection.setParentCollection(Collection::root()); - m_rootCollection.setRights(Collection::CanCreateCollection); - m_rootCollection.setCachePolicy(cachePolicy); - - EntityDisplayAttribute *attr = m_rootCollection.attribute(Collection::AddIfMissing); - attr->setDisplayName(fetchJob->account()->accountName()); - attr->setIconName(QStringLiteral("im-google")); - - m_collections[ ROOT_COLLECTION_REMOTEID ] = m_rootCollection; - - const QStringList activeCalendars = Settings::self()->calendars(); - for (const ObjectPtr &object : qAsConst(calendars)) { - const CalendarPtr &calendar = object.dynamicCast(); - - if (!activeCalendars.contains(calendar->uid())) { - continue; - } - - Collection collection; - collection.setContentMimeTypes(QStringList() << KCalendarCore::Event::eventMimeType()); - collection.setName(calendar->uid()); - collection.setParentCollection(m_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); - - m_collections[ collection.remoteId() ] = collection; - } - - const QStringList activeTaskLists = Settings::self()->taskLists(); - for (const ObjectPtr &object : qAsConst(taskLists)) { - const TaskListPtr &taskList = object.dynamicCast(); - - if (!activeTaskLists.contains(taskList->uid())) { - continue; - } - - Collection collection; - collection.setContentMimeTypes(QStringList() << KCalendarCore::Todo::todoMimeType()); - collection.setName(taskList->uid()); - collection.setParentCollection(m_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")); - - m_collections[ collection.remoteId() ] = collection; - } - - collectionsRetrieved(Akonadi::valuesToVector(m_collections)); - - job->deleteLater(); -} - -void CalendarResource::slotItemsRetrieved(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - Item::List changedItems, removedItems; - Collection collection = job->property(COLLECTION_PROPERTY).value(); - DefaultReminderAttribute *attr = collection.attribute(); - bool isIncremental = false; - - ObjectsList objects = qobject_cast(job)->items(); - if (collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())) { - isIncremental = (qobject_cast(job)->fetchOnlyUpdated() > 0); - Q_FOREACH (const ObjectPtr &object, objects) { - 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()) { - removedItems << item; - } else { - changedItems << item; - } - } - - if (!isIncremental) { - collection.attribute(Collection::AddIfMissing)->setVersion(KGAPIEventVersion); - } - } else if (collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType())) { - isIncremental = (qobject_cast(job)->fetchOnlyUpdated() > 0); - - Q_FOREACH (const ObjectPtr &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()) { - removedItems << item; - } else { - changedItems << item; - } - } - } - - if (isIncremental) { - itemsRetrievedIncremental(changedItems, removedItems); - } else { - itemsRetrieved(changedItems); - } - const QDateTime local(QDateTime::currentDateTime()); - const QDateTime UTC(local.toUTC()); - - collection.setRemoteRevision(QString::number(UTC.toSecsSinceEpoch())); - new CollectionModifyJob(collection, this); - - job->deleteLater(); -} - -void CalendarResource::slotModifyTaskReparentFinished(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - Item item = job->property(ITEM_PROPERTY).value(); - KCalendarCore::Todo::Ptr todo = item.payload(); - TaskPtr ktodo(new Task(*todo.data())); - - job = new TaskModifyJob(ktodo, item.parentCollection().remoteId(), job->account(), this); - job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished); -} - -void CalendarResource::slotRemoveTaskFetchJobFinished(KJob *job) -{ - if (job->error()) { - cancelTask(i18n("Failed to delete task: %1", job->errorString())); - return; - } - - ItemFetchJob *fetchJob = qobject_cast(job); - Item removedItem = fetchJob->property(ITEM_PROPERTY).value(); - - Item::List detachItems; - - const Item::List items = fetchJob->items(); - for (Item item : items) { - if (!item.hasPayload()) { - qCDebug(GOOGLE_CALENDAR_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. */ - ItemModifyJob *modifyJob = new ItemModifyJob(detachItems); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(removedItem)); - modifyJob->setAutoDelete(true); - connect(modifyJob, &ItemModifyJob::finished, this, &CalendarResource::slotDoRemoveTask); -} - -void CalendarResource::slotDoRemoveTask(KJob *job) -{ - if (job->error()) { - cancelTask(i18n("Failed to delete task: %1", job->errorString())); - return; - } - - // Make sure account is still valid - if (!canPerformTask()) { - return; - } - - Item item = job->property(ITEM_PROPERTY).value< Item >(); - - /* Now finally we can safely remove the task we wanted to */ - TaskDeleteJob *deleteJob = new TaskDeleteJob(item.remoteId(), item.parentCollection().remoteId(), account(), this); - deleteJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(deleteJob, &TaskDeleteJob::finished, this, &CalendarResource::slotGenericJobFinished); -} - -void CalendarResource::slotTaskAddedSearchFinished(KJob *job) -{ - 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_CALENDAR_LOG) << "Parent query returned" << items.count() << "results"; - - const QString tasksListId = item.parentCollection().remoteId(); - - // Make sure account is still valid - if (!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. - if (items.isEmpty()) { - task->setRelatedTo(QString(), KCalendarCore::Incidence::RelTypeParent); - newJob = new TaskCreateJob(task, tasksListId, account(), this); - } else { - Item matchedItem = items.first(); - - task->setRelatedTo(matchedItem.remoteId(), KCalendarCore::Incidence::RelTypeParent); - TaskCreateJob *createJob = new TaskCreateJob(task, tasksListId, account(), this); - createJob->setParentItem(matchedItem.remoteId()); - newJob = createJob; - } - - newJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(newJob, &KGAPI2::Job::finished, this, &CalendarResource::slotCreateJobFinished); -} - -void CalendarResource::slotCreateJobFinished(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - Item item = job->property(ITEM_PROPERTY).value(); - - CreateJob *createJob = qobject_cast(job); - ObjectsList objects = createJob->items(); - Q_ASSERT(objects.count() > 0); - - if (item.mimeType() == KCalendarCore::Event::eventMimeType()) { - EventPtr event = objects.first().dynamicCast(); - item.setRemoteId(event->id()); - item.setRemoteRevision(event->etag()); - item.setGid(event->uid()); - changeCommitted(item); - item.setPayload(event.dynamicCast()); - new ItemModifyJob(item, this); - } else if (item.mimeType() == KCalendarCore::Todo::todoMimeType()) { - TaskPtr task = objects.first().dynamicCast(); - item.setRemoteId(task->uid()); - item.setRemoteRevision(task->etag()); - item.setGid(task->uid()); - changeCommitted(item); - item.setPayload(task.dynamicCast()); - new ItemModifyJob(item, this); - } -} - -QDateTime CalendarResource::lastCacheUpdate() const -{ - return QDateTime(); -} - -void CalendarResource::canHandleFreeBusy(const QString &email) const -{ - if (!const_cast(this)->canPerformTask()) { - handlesFreeBusy(email, false); - return; - } - - auto job = new KGAPI2::FreeBusyQueryJob(email, - QDateTime::currentDateTimeUtc(), - QDateTime::currentDateTimeUtc().addSecs(3600), - const_cast(this)->account(), - const_cast(this)); - connect(job, &KGAPI2::Job::finished, - this, &CalendarResource::slotCanHandleFreeBusyJobFinished); -} - -void CalendarResource::slotCanHandleFreeBusyJobFinished(KGAPI2::Job *job) -{ - auto queryJob = qobject_cast(job); - - if (!handleError(job, false)) { - handlesFreeBusy(queryJob->id(), false); - return; - } - - handlesFreeBusy(queryJob->id(), true); -} - -void CalendarResource::retrieveFreeBusy(const QString &email, const QDateTime &start, const QDateTime &end) -{ - if (!const_cast(this)->canPerformTask()) { - freeBusyRetrieved(email, QString(), false, QString()); - return; - } - - auto job = new KGAPI2::FreeBusyQueryJob(email, start, end, account(), this); - connect(job, &KGAPI2::Job::finished, - this, &CalendarResource::slotRetrieveFreeBusyJobFinished); -} - -void CalendarResource::slotRetrieveFreeBusyJobFinished(KGAPI2::Job *job) -{ - auto queryJob = qobject_cast(job); - - if (!handleError(job, false)) { - 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(account()->accountName()); - fb->addAttendee(KCalendarCore::Attendee(QString(), queryJob->id())); - // FIXME: is it really sort? - fb->setDateTime(QDateTime::currentDateTimeUtc(), KCalendarCore::IncidenceBase::RoleSort); - - Q_FOREACH (const KGAPI2::FreeBusyQueryJob::BusyRange &range, queryJob->busy()) { - fb->addPeriod(range.busyStart, range.busyEnd); - } - - KCalendarCore::ICalFormat format; - const QString fbStr = format.createScheduleMessage(fb, KCalendarCore::iTIPRequest); - - freeBusyRetrieved(queryJob->id(), fbStr, true, QString()); -} - -AKONADI_RESOURCE_MAIN(CalendarResource) diff --git a/resources/google/calendar/googlecalendarresource.desktop b/resources/google/calendar/googlecalendarresource.desktop deleted file mode 100644 --- a/resources/google/calendar/googlecalendarresource.desktop +++ /dev/null @@ -1,92 +0,0 @@ -[Desktop Entry] -Name=Google Calendars and Tasks -Name[ar]=تقاويم ومهامّ «غوغل» -Name[bg]=Календари и задачи в Google -Name[bs]=Google kalendari i zadaće -Name[ca]=Calendaris i tasques de Google -Name[ca@valencia]=Calendaris i tasques de Google -Name[cs]=Kalendáře a úkoly Google -Name[da]=Google kalendere og opgaver -Name[de]=Google-Kalender und -Aufgaben -Name[el]=Google ημερολόγια και εργασίες -Name[en_GB]=Google Calendars and Tasks -Name[es]=Calendarios Google y tareas -Name[et]=Google'i kalendrid ja ülesanded -Name[fi]=Google-kalenterit ja -tehtävät -Name[fr]=Agendas et tâches Google -Name[gl]=Google Calendars and Tasks -Name[hu]=Google naptárak és feladatok -Name[ia]=Calendarios e cargas de Google -Name[it]=Calendari e Attività Google -Name[kk]=Google күнтізбелер мен тапсырмалар -Name[km]=ភារកិច្ច និង​ប្រតិទិន Google -Name[ko]=Google 캘린더 및 할 일 -Name[lt]=Google kalendoriai ir užduotys -Name[lv]=Google kalendāri un uzdevumi -Name[nb]=Google kalendere og gjøremål -Name[nds]=Google-Kalenners un -Opgaven -Name[nl]=Google agenda's en taken -Name[pl]=Kalendarze i zadania Google -Name[pt]=Calendários e Tarefas do Google -Name[pt_BR]=Calendários e tarefas do Google -Name[ru]=Задачи и календари Google -Name[sk]=Google kalendáre a úlohy -Name[sl]=Koledarji in opravila Google -Name[sr]=Гуглови календари и задаци -Name[sr@ijekavian]=Гуглови календари и задаци -Name[sr@ijekavianlatin]=Googleovi kalendari i zadaci -Name[sr@latin]=Googleovi kalendari i zadaci -Name[sv]=Google kalendrar och uppgifter -Name[tr]=Google Takvimleri ve Görevleri -Name[uk]=Календарі та записи завдань Google -Name[x-test]=xxGoogle Calendars and Tasksxx -Name[zh_CN]=Google 日历和任务 -Name[zh_TW]=Google 行事曆與工作 -Comment=Access your Google Calendars and Tasks from KDE -Comment[ar]=توصّل إلى تقاويم ومهام «غوغل» من «كدي» -Comment[bg]=Достъп до календарите и задачите ви в Google от KDE -Comment[bs]=Pristupite svojim Google kalendaru i zadacima iz KDE -Comment[ca]=Accediu als calendaris i tasques de Google des del KDE -Comment[ca@valencia]=Accediu als calendaris i tasques de Google des del KDE -Comment[da]=Tilgå dine Google kalendere og opgaver fra KDE -Comment[de]=Greifen Sie in KDE auf Ihre Google-Kalender und -Aufgaben zu -Comment[el]=Αποκτήστε πρόσβαση στα Google ημερολόγια και τις εργασίες σας από το KDE -Comment[en_GB]=Access your Google Calendars and Tasks from KDE -Comment[es]=Acceda a sus calendarios Google y tareas desde KDE -Comment[et]=Oma Google'i kalendrite ja ülesannete kasutamine otse KDE-st -Comment[fi]=Pääsy KDE:sta Googlen kalentereihisi ja tehtäviisi -Comment[fr]=Accès à vos agendas et listes de tâches Google depuis KDE -Comment[gl]=Acceda aos seus calendarios e tarefas de Google desde KDE. -Comment[hu]=A Google naptárának és feladatainak elérése a KDE-ből -Comment[ia]=Accede a tu Calendarios e Cargas de Google ab KDE -Comment[it]=Accedi ai tuoi calendari e attività Google da KDE -Comment[kk]=Google күнтізбелер мен тапсырмаларға KDE-ден қатынау -Comment[km]=ដំណើរការ​ប្រតិទិន Google របស់​អ្នក និង​ភារកិច្ច​ពី KDE -Comment[ko]=KDE에서 Google 캘린더 및 할 일에 접근합니다 -Comment[lt]=Pasiekite savo Google kalendorius ir užduotis iš KDE -Comment[lv]=Piekļūstiet saviem Google kalendāriem un uzdevumiem no KDE -Comment[nb]=Bruk dine Google-kalendere og gjøremål fra KDE -Comment[nds]=Ut KDE op Dien Google-Kalenners un -Opgaven togriepen -Comment[nl]=Heb toegang tot uw Google agenda's en taken vanuit KDE -Comment[pl]=Daje dostęp do twoich kalendarzy i zadań Google z KDE -Comment[pt]=Aceda aos seus calendários e tarefas da Google a partir do KDE -Comment[pt_BR]=Acesse seus calendários e tarefas do Google a partir do KDE -Comment[ru]=Доступ к задачам и календарям Google из KDE -Comment[sk]=Pristupuje k vašim Google kalendárom a úlohám z KDE -Comment[sl]=Dostopajte do svojih koledarjev in opravil Google -Comment[sr]=Приступите својим календарима и задацима на Гуглу из КДЕ‑а -Comment[sr@ijekavian]=Приступите својим календарима и задацима на Гуглу из КДЕ‑а -Comment[sr@ijekavianlatin]=Pristupite svojim kalendarima i zadacima na Googleu iz KDE‑a -Comment[sr@latin]=Pristupite svojim kalendarima i zadacima na Googleu iz KDE‑a -Comment[sv]=Kom åt Google kalendrar och uppgifter från KDE -Comment[tr]=Google Takvimlerinize ve Görevlerinize KDE'den erişin -Comment[uk]=Доступ до ваших календарів і записів завдань Google з KDE -Comment[x-test]=xxAccess your Google Calendars and Tasks from KDExx -Comment[zh_CN]=在 KDE 中访问您的 Google 日历和任务 -Comment[zh_TW]=用 KDE 存取您的 Google 行事曆與工作 -Type=AkonadiResource -Exec=akonadi_googlecalendar_resource -X-Akonadi-MimeTypes=text/calendar,application/x-vnd.akonadi.calendar.event,application/x-vnd.akonadi.calendar.todo,application/x-vnd.akonadi.calendar.freebusy -X-Akonadi-Capabilities=Resource,FreeBusyProvider -X-Akonadi-Identifier=akonadi_googlecalendar_resource -Icon=im-google diff --git a/resources/google/calendar/settings.h b/resources/google/calendar/settings.h deleted file mode 100644 --- a/resources/google/calendar/settings.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (C) 2011, 2012 Dan Vratil - - 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 GOOGLE_CALENDAR_SETTINGS_H -#define GOOGLE_CALENDAR_SETTINGS_H - -#include "common/googlesettings.h" - -class Settings : public GoogleSettings -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.Akonadi.GoogleCalendar.ExtendedSettings") -public: - Settings(); - static Settings *self(); -}; - -#endif // SETTINGS_H diff --git a/resources/google/calendar/settings.cpp b/resources/google/calendar/settings.cpp deleted file mode 100644 --- a/resources/google/calendar/settings.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (C) 2011, 2012 Dan Vratil - - 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 "settings.h" -#include "settingsadaptor.h" - -#include - -class SettingsHelper -{ -public: - SettingsHelper() : q(nullptr) - { - } - - ~SettingsHelper() - { - delete q; - q = nullptr; - } - - Settings *q; -}; - -Q_GLOBAL_STATIC(SettingsHelper, s_globalSettings) - -Settings::Settings() - : GoogleSettings() -{ - Q_ASSERT(!s_globalSettings->q); - s_globalSettings->q = this; - - new SettingsAdaptor(this); - QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), this, - QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents); -} - -Settings *Settings::self() -{ - if (!s_globalSettings->q) { - new Settings; - s_globalSettings->q->load(); - } - - return s_globalSettings->q; -} diff --git a/resources/google/calendar/settingsdialog.h b/resources/google/calendar/settingsdialog.h deleted file mode 100644 --- a/resources/google/calendar/settingsdialog.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - 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 GOOGLE_CALENDAR_SETTINGSDIALOG_H -#define GOOGLE_CALENDAR_SETTINGSDIALOG_H - -#include "common/googlesettingsdialog.h" - -class QListWidget; -class QLabel; -class KDateComboBox; - -class SettingsDialog : public GoogleSettingsDialog -{ - Q_OBJECT -public: - explicit SettingsDialog(GoogleAccountManager *accountManager, WId windowId, GoogleResource *parent); - ~SettingsDialog(); - -private Q_SLOTS: - void slotReloadCalendars(); - void slotReloadTaskLists(); - void slotCurrentAccountChanged(const QString &accountName); - - void slotTaskListsRetrieved(KGAPI2::Job *job); - void slotCalendarsRetrieved(KGAPI2::Job *job); - - void saveSettings() override; - -private: - QGroupBox *m_calendarsBox = nullptr; - QListWidget *m_calendarsList = nullptr; - QPushButton *m_reloadCalendarsBtn = nullptr; - QLabel *m_eventsLimitLabel = nullptr; - KDateComboBox *m_eventsLimitCombo = nullptr; - - QGroupBox *m_taskListsBox = nullptr; - QListWidget *m_taskListsList = nullptr; - QPushButton *m_reloadTaskListsBtn = nullptr; -}; - -#endif // SETTINGSDIALOG_H diff --git a/resources/google/calendar/settingsdialog.cpp b/resources/google/calendar/settingsdialog.cpp deleted file mode 100644 --- a/resources/google/calendar/settingsdialog.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - 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 . -*/ - -#include "settingsdialog.h" -#include "settings.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using namespace KGAPI2; - -SettingsDialog::SettingsDialog(GoogleAccountManager *accountManager, WId windowId, GoogleResource *parent) - : GoogleSettingsDialog(accountManager, windowId, parent) -{ - connect(this, &SettingsDialog::currentAccountChanged, this, &SettingsDialog::slotCurrentAccountChanged); - - m_calendarsBox = new QGroupBox(i18n("Calendars"), this); - mainLayout()->addWidget(m_calendarsBox); - - QVBoxLayout *vbox = new QVBoxLayout(m_calendarsBox); - - m_calendarsList = new QListWidget(m_calendarsBox); - vbox->addWidget(m_calendarsList, 1); - - m_reloadCalendarsBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload"), m_calendarsBox); - vbox->addWidget(m_reloadCalendarsBtn); - connect(m_reloadCalendarsBtn, &QPushButton::clicked, this, &SettingsDialog::slotReloadCalendars); - - QHBoxLayout *hbox = new QHBoxLayout; - vbox->addLayout(hbox); - - m_eventsLimitLabel = new QLabel(i18nc("Followed by a date picker widget", "&Fetch only new events since"), this); - hbox->addWidget(m_eventsLimitLabel); - - m_eventsLimitCombo = new KDateComboBox(this); - m_eventsLimitLabel->setBuddy(m_eventsLimitCombo); - m_eventsLimitCombo->setMaximumDate(QDate::currentDate()); - m_eventsLimitCombo->setMinimumDate(QDate::fromString(QStringLiteral("2000-01-01"), Qt::ISODate)); - m_eventsLimitCombo->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate - |KDateComboBox::DatePicker | KDateComboBox::WarnOnInvalid); - if (Settings::self()->eventsSince().isEmpty()) { - const QString ds = QStringLiteral("%1-01-01").arg(QString::number(QDate::currentDate().year() - 3)); - m_eventsLimitCombo->setDate(QDate::fromString(ds, Qt::ISODate)); - } else { - m_eventsLimitCombo->setDate(QDate::fromString(Settings::self()->eventsSince(), Qt::ISODate)); - } - hbox->addWidget(m_eventsLimitCombo); - - m_taskListsBox = new QGroupBox(i18n("Tasklists"), this); - mainLayout()->addWidget(m_taskListsBox); - - vbox = new QVBoxLayout(m_taskListsBox); - - m_taskListsList = new QListWidget(m_taskListsBox); - vbox->addWidget(m_taskListsList, 1); - - m_reloadTaskListsBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload"), m_taskListsBox); - vbox->addWidget(m_reloadTaskListsBtn); - connect(m_reloadTaskListsBtn, &QPushButton::clicked, this, &SettingsDialog::slotReloadTaskLists); -} - -SettingsDialog::~SettingsDialog() -{ -} - -void SettingsDialog::saveSettings() -{ - const AccountPtr account = currentAccount(); - if (!currentAccount()) { - Settings::self()->setAccount(QString()); - Settings::self()->setCalendars(QStringList()); - Settings::self()->setTaskLists(QStringList()); - Settings::self()->setEventsSince(QString()); - Settings::self()->save(); - return; - } - - Settings::self()->setAccount(account->accountName()); - - QStringList calendars; - for (int i = 0; i < m_calendarsList->count(); i++) { - QListWidgetItem *item = m_calendarsList->item(i); - - if (item->checkState() == Qt::Checked) { - calendars.append(item->data(Qt::UserRole).toString()); - } - } - Settings::self()->setCalendars(calendars); - if (m_eventsLimitCombo->isValid()) { - Settings::self()->setEventsSince(m_eventsLimitCombo->date().toString(Qt::ISODate)); - } - - QStringList taskLists; - for (int i = 0; i < m_taskListsList->count(); i++) { - QListWidgetItem *item = m_taskListsList->item(i); - - if (item->checkState() == Qt::Checked) { - taskLists.append(item->data(Qt::UserRole).toString()); - } - } - Settings::self()->setTaskLists(taskLists); - - Settings::self()->save(); -} - -void SettingsDialog::slotCurrentAccountChanged(const QString &accountName) -{ - if (accountName.isEmpty()) { - m_taskListsBox->setDisabled(true); - m_taskListsList->clear(); - m_calendarsBox->setDisabled(true); - m_calendarsList->clear(); - return; - } - - slotReloadCalendars(); - slotReloadTaskLists(); -} - -void SettingsDialog::slotReloadCalendars() -{ - const AccountPtr account = currentAccount(); - if (!account) { - return; - } - - CalendarFetchJob *fetchJob = new CalendarFetchJob(account, this); - connect(fetchJob, &CalendarFetchJob::finished, this, &SettingsDialog::slotCalendarsRetrieved); - - m_calendarsBox->setDisabled(true); - m_calendarsList->clear(); -} - -void SettingsDialog::slotReloadTaskLists() -{ - const AccountPtr account = currentAccount(); - if (!account) { - return; - } - - TaskListFetchJob *fetchJob = new TaskListFetchJob(account, this); - connect(fetchJob, &TaskListFetchJob::finished, this, &SettingsDialog::slotTaskListsRetrieved); - - m_taskListsBox->setDisabled(true); - m_taskListsList->clear(); -} - -void SettingsDialog::slotCalendarsRetrieved(Job *job) -{ - if (!handleError(job) || !currentAccount()) { - m_calendarsBox->setEnabled(true); - return; - } - - FetchJob *fetchJob = qobject_cast(job); - const ObjectsList objects = fetchJob->items(); - - QStringList activeCalendars; - if (currentAccount()->accountName() == Settings::self()->account()) { - activeCalendars = Settings::self()->calendars(); - } - m_calendarsList->clear(); - for (const ObjectPtr &object : objects) { - CalendarPtr calendar = object.dynamicCast(); - - QListWidgetItem *item = new QListWidgetItem(calendar->title()); - item->setData(Qt::UserRole, calendar->uid()); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); - item->setCheckState((activeCalendars.isEmpty() || activeCalendars.contains(calendar->uid())) ? Qt::Checked : Qt::Unchecked); - m_calendarsList->addItem(item); - } - - m_calendarsBox->setEnabled(true); -} - -void SettingsDialog::slotTaskListsRetrieved(Job *job) -{ - if (!handleError(job) || !currentAccount()) { - m_taskListsBox->setEnabled(true); - return; - } - - FetchJob *fetchJob = qobject_cast(job); - const ObjectsList objects = fetchJob->items(); - - QStringList activeTaskLists; - if (currentAccount()->accountName() == Settings::self()->account()) { - activeTaskLists = Settings::self()->taskLists(); - } - m_taskListsList->clear(); - for (const ObjectPtr &object : objects) { - TaskListPtr taskList = object.dynamicCast(); - - QListWidgetItem *item = new QListWidgetItem(taskList->title()); - item->setData(Qt::UserRole, taskList->uid()); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); - item->setCheckState((activeTaskLists.isEmpty() || activeTaskLists.contains(taskList->uid())) ? Qt::Checked : Qt::Unchecked); - m_taskListsList->addItem(item); - } - - m_taskListsBox->setEnabled(true); -} diff --git a/resources/google/common/googleaccountmanager.h b/resources/google/common/googleaccountmanager.h deleted file mode 100644 --- a/resources/google/common/googleaccountmanager.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 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 2 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#ifndef GOOGLEACCOUNTMANAGER_H -#define GOOGLEACCOUNTMANAGER_H - -#include -#include -#include - -#include -#include - -namespace KWallet { -class Wallet; -} - -class GoogleAccountManager : public QObject -{ - Q_OBJECT - -public: - explicit GoogleAccountManager(QObject *parent = nullptr); - ~GoogleAccountManager() override; - - bool isReady() const; - - bool storeAccount(const KGAPI2::AccountPtr &account); - KGAPI2::AccountPtr findAccount(const QString &accountName) const; - bool removeAccount(const QString &accountName); - KGAPI2::AccountsList listAccounts() const; - - void cleanup(const QString &accountName); - -Q_SIGNALS: - void managerReady(bool ready); - void accountAdded(const KGAPI2::AccountPtr &account); - void accountChanged(const KGAPI2::AccountPtr &account); - void accountRemoved(const QString &accountName); - -private Q_SLOTS: - void initManager(); - void slotWalletOpened(bool success); - void slotWalletClosed(); - void slotFolderUpdated(const QString &folder); - -private: - KGAPI2::AccountPtr findAccountInWallet(const QString &accountName) const; - bool m_isReady; - QPointer m_wallet; - mutable QMap m_accounts; -}; - -#endif // GOOGLEACCOUNTMANAGER_H diff --git a/resources/google/common/googleaccountmanager.cpp b/resources/google/common/googleaccountmanager.cpp deleted file mode 100644 --- a/resources/google/common/googleaccountmanager.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 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 2 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include "googleaccountmanager.h" - -#include -#include - -#define WALLET_FOLDER QStringLiteral("Akonadi Google") - -using namespace KGAPI2; - -GoogleAccountManager::GoogleAccountManager(QObject *parent) - : QObject(parent) - , m_isReady(false) -{ - QMetaObject::invokeMethod(this, &GoogleAccountManager::initManager, Qt::QueuedConnection); -} - -GoogleAccountManager::~GoogleAccountManager() -{ - delete m_wallet; -} - -bool GoogleAccountManager::isReady() const -{ - return m_isReady; -} - -void GoogleAccountManager::initManager() -{ - delete m_wallet; - - // FIXME: Don't use synchronous wallet - // With asynchronous wallet however we are unable to read any data from it - // in when slotWalletOpened() is called on walletOpened() signal - m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), - 0, KWallet::Wallet::Synchronous); - slotWalletOpened(m_wallet != nullptr); - if (m_wallet) { - connect(m_wallet.data(), &KWallet::Wallet::folderUpdated, this, &GoogleAccountManager::slotFolderUpdated); - connect(m_wallet.data(), &KWallet::Wallet::walletClosed, this, &GoogleAccountManager::slotWalletClosed); - } -} - -void GoogleAccountManager::slotWalletOpened(bool success) -{ - if (!success) { - qWarning() << "Failed to open wallet"; - Q_EMIT managerReady(false); - return; - } - - if (!m_wallet->hasFolder(WALLET_FOLDER)) { - if (!m_wallet->createFolder(WALLET_FOLDER)) { - qWarning() << "Failed to create KWallet folder " << WALLET_FOLDER; - Q_EMIT managerReady(false); - return; - } - } - - if (!m_wallet->setFolder(WALLET_FOLDER)) { - qWarning() << "Failed to open KWallet folder" << WALLET_FOLDER; - Q_EMIT managerReady(false); - return; - } - - // Populate the cache now - const QStringList accountNames = m_wallet->entryList(); - for (const QString &accountName : accountNames) { - m_accounts[accountName] = findAccountInWallet(accountName); - } - - m_isReady = true; - Q_EMIT managerReady(true); -} - -void GoogleAccountManager::slotWalletClosed() -{ - m_isReady = false; - delete m_wallet; -} - -void GoogleAccountManager::slotFolderUpdated(const QString &folder) -{ - // We are interested only in the "Akonadi Google" folder - if (folder != WALLET_FOLDER) { - return; - } - - QStringList walletEntries = m_wallet->entryList(); - - Q_FOREACH (const AccountPtr &account, m_accounts) { //no using foreach - AccountPtr changedAccount = findAccountInWallet(account->accountName()); - if (changedAccount.isNull()) { - walletEntries.removeOne(account->accountName()); - m_accounts.remove(account->accountName()); - Q_EMIT accountRemoved(account->accountName()); - continue; - } - - if ((account->accessToken() != changedAccount->accessToken()) - || (account->refreshToken() != changedAccount->refreshToken()) - || (account->scopes() != changedAccount->scopes())) { - walletEntries.removeOne(account->accountName()); - m_accounts[account->accountName()] = changedAccount; - Q_EMIT accountChanged(changedAccount); - } - } - - for (const QString &accountName : qAsConst(walletEntries)) { - const AccountPtr newAccount = findAccountInWallet(accountName); - - m_accounts[newAccount->accountName()] = newAccount; - Q_EMIT accountAdded(newAccount); - } -} - -AccountPtr GoogleAccountManager::findAccount(const QString &accountName) const -{ - if (!m_isReady) { - qWarning() << "Manager is not ready!"; - return AccountPtr(); - } - - if (m_accounts.contains(accountName)) { - return m_accounts[accountName]; - } - - AccountPtr account = findAccountInWallet(accountName); - if (account.isNull()) { - return AccountPtr(); - } - - m_accounts[accountName] = account; - return account; -} - -AccountPtr GoogleAccountManager::findAccountInWallet(const QString &accountName) const -{ - if (!m_wallet->entryList().contains(accountName)) { - qDebug() << "Account" << accountName << "not found in KWallet"; - return AccountPtr(); - } - - QMap map; - m_wallet->readMap(accountName, map); - -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - const QStringList scopes = map[QStringLiteral("scopes")].split(QLatin1Char(','), QString::SkipEmptyParts); -#else - const QStringList scopes = map[QStringLiteral("scopes")].split(QLatin1Char(','), Qt::SkipEmptyParts); -#endif - QList scopeUrls; - scopeUrls.reserve(scopes.count()); - for (const QString &scope : scopes) { - scopeUrls << QUrl(scope); - } - AccountPtr account(new Account(accountName, - map[QStringLiteral("accessToken")], - map[QStringLiteral("refreshToken")], - scopeUrls)); - - return account; -} - -bool GoogleAccountManager::storeAccount(const AccountPtr &account) -{ - if (!m_isReady) { - qWarning() << "Manager is not ready!"; - return false; - } - - QStringList scopes; - const QList urlScopes = account->scopes(); - scopes.reserve(urlScopes.count()); - for (const QUrl &url : urlScopes) { - scopes << url.toString(); - } - - QMap map; - map[QStringLiteral("accessToken")] = account->accessToken(); - map[QStringLiteral("refreshToken")] = account->refreshToken(); - map[QStringLiteral("scopes")] = scopes.join(QLatin1Char(',')); - - if (m_wallet->writeMap(account->accountName(), map) == 0) { - m_accounts[account->accountName()] = account; - return true; - } - - return false; -} - -bool GoogleAccountManager::removeAccount(const QString &accountName) -{ - if (!m_isReady) { - qWarning() << "Manager is not ready"; - return false; - } - - if (!m_accounts.contains(accountName)) { - return true; - } - - if (m_wallet->removeEntry(accountName) != 0) { - qWarning() << "Failed to remove account from KWallet"; - return false; - } - - m_accounts.remove(accountName); - return true; -} - -AccountsList GoogleAccountManager::listAccounts() const -{ - if (!m_isReady) { - qWarning() << "Manager is not ready"; - return AccountsList(); - } - - return m_accounts.values(); -} - -void GoogleAccountManager::cleanup(const QString &accountName) -{ - removeAccount(accountName); -} diff --git a/resources/google/common/googleresource.h b/resources/google/common/googleresource.h deleted file mode 100644 --- a/resources/google/common/googleresource.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - 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 "googleaccountmanager.h" - -#include - -#include - -#define ITEM_PROPERTY "_AkonadiItem" -#define ITEMLIST_PROPERTY "_AkonadiItemList" -#define COLLECTION_PROPERTY "_AkonadiCollection" -#define JOB_PROPERTY "_KGAPI2Job" - -namespace KGAPI2 { -class Job; -} - -class GoogleSettings; - -class GoogleResource : public Akonadi::ResourceBase, public Akonadi::AgentBase::ObserverV2 -{ - Q_OBJECT - -public: - explicit GoogleResource(const QString &id); - ~GoogleResource() override; - - virtual GoogleSettings *settings() const = 0; - virtual QList scopes() const = 0; - - void cleanup() override; - -public Q_SLOTS: - void configure(WId windowId) override; - - void reloadConfig(); - -protected Q_SLOTS: - bool retrieveItem(const Akonadi::Item &item, const QSet< QByteArray > &parts) override; - - bool handleError(KGAPI2::Job *job, bool cancelTask = true); - - virtual void slotAuthJobFinished(KGAPI2::Job *job); - virtual void slotGenericJobFinished(KGAPI2::Job *job); - - void emitPercent(KGAPI2::Job *job, int processedCount, int totalCount); - - virtual void slotAbortRequested(); - virtual void slotAccountManagerReady(bool success); - virtual void slotAccountChanged(const KGAPI2::AccountPtr &account); - virtual void slotAccountRemoved(const QString &accountName); - -protected: - bool configureKGAPIAccount(const KGAPI2::AccountPtr &account); - void updateAccountToken(const KGAPI2::AccountPtr &account, KGAPI2::Job *restartJob = nullptr); - - 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(); - - KGAPI2::AccountPtr account() const; - /** - * KAccounts support abstraction. - * - * Returns 0 when compiled without KAccounts or not configured for KAccounts - */ - int accountId() const; - - GoogleAccountManager *accountManager() const; - - void aboutToQuit() override; - - virtual int runConfigurationDialog(WId windowId) = 0; - virtual void updateResourceName() = 0; - -private: - void abort(); - - bool m_isConfiguring = false; - GoogleAccountManager *m_accountMgr = nullptr; - KGAPI2::AccountPtr m_account; -}; - -#endif // GOOGLERESOURCE_H diff --git a/resources/google/common/googleresource.cpp b/resources/google/common/googleresource.cpp deleted file mode 100644 --- a/resources/google/common/googleresource.cpp +++ /dev/null @@ -1,318 +0,0 @@ -/* - 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 . -*/ - -#include "googleresource.h" -#include "googlesettings.h" - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#define ACCESS_TOKEN_PROPERTY "AccessToken" - -Q_DECLARE_METATYPE(KGAPI2::Job *) - -using namespace KGAPI2; -using namespace Akonadi; - -GoogleResource::GoogleResource(const QString &id) - : ResourceBase(id) - , AgentBase::ObserverV2() - , m_isConfiguring(false) -{ - connect(this, &GoogleResource::abortRequested, this, &GoogleResource::slotAbortRequested); - 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_accountMgr = new GoogleAccountManager(this); - connect(m_accountMgr, &GoogleAccountManager::accountChanged, this, &GoogleResource::slotAccountChanged); - connect(m_accountMgr, &GoogleAccountManager::accountRemoved, this, &GoogleResource::slotAccountRemoved); - connect(m_accountMgr, &GoogleAccountManager::managerReady, this, &GoogleResource::slotAccountManagerReady); - - Q_EMIT status(NotConfigured, i18n("Waiting for KWallet...")); -} - -GoogleResource::~GoogleResource() -{ -} - -void GoogleResource::cleanup() -{ - accountManager()->cleanup(settings()->account()); - ResourceBase::cleanup(); -} - -AccountPtr GoogleResource::account() const -{ - return m_account; -} - -GoogleAccountManager *GoogleResource::accountManager() const -{ - return m_accountMgr; -} - -void GoogleResource::aboutToQuit() -{ - slotAbortRequested(); -} - -void GoogleResource::abort() -{ - cancelTask(i18n("Aborted")); -} - -void GoogleResource::slotAbortRequested() -{ - abort(); -} - -void GoogleResource::configure(WId windowId) -{ - if (!m_accountMgr->isReady() || m_isConfiguring) { - Q_EMIT configurationDialogAccepted(); - return; - } - - m_isConfiguring = true; - if (runConfigurationDialog(windowId) == QDialog::Accepted) { - updateResourceName(); - - Q_EMIT configurationDialogAccepted(); - - m_account = accountManager()->findAccount(settings()->account()); - if (m_account.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; -} - -void GoogleResource::updateAccountToken(const AccountPtr &account, KGAPI2::Job *restartJob) -{ - if (!settings()->account().isEmpty()) { - AuthJob *authJob = new AuthJob(account, settings()->clientId(), settings()->clientSecret(), this); - authJob->setProperty(JOB_PROPERTY, QVariant::fromValue(restartJob)); - connect(authJob, &AuthJob::finished, this, &GoogleResource::slotAuthJobFinished); - } -} - -void GoogleResource::reloadConfig() -{ - const QString accountName = settings()->account(); - - if (!accountName.isEmpty()) { - if (!configureKGAPIAccount(m_accountMgr->findAccount(accountName))) { - Q_EMIT status(NotConfigured, i18n("Configured account does not exist")); - return; - } - } else { - Q_EMIT status(NotConfigured); - return; - } - - Q_EMIT status(Idle, i18nc("@info:status", "Ready")); -} - -bool GoogleResource::configureKGAPIAccount(const AccountPtr &account) -{ - m_account = account; - return !m_account.isNull(); -} - -void GoogleResource::slotAccountManagerReady(bool ready) -{ - // If the resource have already been configured for KAccounts, then use that - if (accountId() > 0) { - return; - } - - qDebug() << ready; - if (!ready) { - Q_EMIT status(Broken, i18n("Can't access KWallet")); - return; - } - - const QString accountName = settings()->account(); - if (accountName.isEmpty()) { - Q_EMIT status(NotConfigured); - return; - } - - m_account = m_accountMgr->findAccount(accountName); - if (m_account.isNull()) { - Q_EMIT status(NotConfigured, i18n("Configured account does not exist")); - return; - } - - Q_EMIT status(Idle, i18nc("@info:status", "Ready")); - synchronize(); -} - -void GoogleResource::slotAccountChanged(const AccountPtr &account) -{ - // We don't care when using KAccounts - if (accountId() > 0) { - return; - } - - m_account = account; -} - -void GoogleResource::slotAccountRemoved(const QString &accountName) -{ - // We don't care when using KAccounts - if (accountId() > 0) { - return; - } - - if (m_account && m_account->accountName() != accountName) { - return; - } - - Q_EMIT status(NotConfigured, i18n("Configured account has been removed")); - m_account.clear(); - settings()->setAccount(QString()); -} - -bool GoogleResource::handleError(KGAPI2::Job *job, bool _cancelTask) -{ - if ((job->error() == KGAPI2::NoError) || (job->error() == KGAPI2::OK)) { - return true; - } - - if (job->error() == KGAPI2::Unauthorized) { - qDebug() << job << job->errorString(); - - const QList resourceScopes = scopes(); - for (const QUrl &scope : resourceScopes) { - if (!m_account->scopes().contains(scope)) { - m_account->addScope(scope); - } - } - - updateAccountToken(m_account, job); - return false; - } - - if (_cancelTask) { - cancelTask(job->errorString()); - } - job->deleteLater(); - return false; -} - -bool GoogleResource::canPerformTask() -{ - if (!m_account && 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) -{ - qDebug(); - - if (job->error() != KGAPI2::NoError) { - cancelTask(i18n("Failed to refresh tokens")); - return; - } - - AuthJob *authJob = qobject_cast(job); - m_account = authJob->account(); - if (!m_accountMgr->storeAccount(m_account)) { - qWarning() << "Failed to store account in KWallet"; - } - - KGAPI2::Job *otherJob = job->property(JOB_PROPERTY).value(); - if (otherJob) { - otherJob->setAccount(m_account); - otherJob->restart(); - } - - job->deleteLater(); -} - -void GoogleResource::slotGenericJobFinished(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - 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")); - - job->deleteLater(); -} - -void GoogleResource::emitPercent(KGAPI2::Job *job, int processedItems, int totalItems) -{ - Q_UNUSED(job); - - Q_EMIT percent(((float)processedItems / (float)totalItems) * 100); -} - -bool GoogleResource::retrieveItem(const Item &item, const QSet< QByteArray > &parts) -{ - Q_UNUSED(parts); - - /* We don't support fetching parts, the item is already fully stored. */ - itemRetrieved(item); - - return true; -} - -int GoogleResource::accountId() const -{ - return 0; -} diff --git a/resources/google/common/googlesettings.cpp b/resources/google/common/googlesettings.cpp deleted file mode 100644 --- a/resources/google/common/googlesettings.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright (C) 2011-2013 Dan Vratil - - 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 "googlesettings.h" -#include "settingsbase.h" - -GoogleSettings::GoogleSettings() - : m_winId(0) -{ -} - -QString GoogleSettings::clientId() const -{ - return QStringLiteral("554041944266.apps.googleusercontent.com"); -} - -QString GoogleSettings::clientSecret() const -{ - return QStringLiteral("mdT1DjzohxN3npUUzkENT0gO"); -} - -void GoogleSettings::setWindowId(WId id) -{ - m_winId = id; -} - -void GoogleSettings::setResourceId(const QString &resourceIdentificator) -{ - m_resourceId = resourceIdentificator; -} - -QString GoogleSettings::account() const -{ - return SettingsBase::account(); -} - -void GoogleSettings::setAccount(const QString &account) -{ - SettingsBase::setAccount(account); -} diff --git a/resources/google/common/googlesettingsdialog.cpp b/resources/google/common/googlesettingsdialog.cpp deleted file mode 100644 --- a/resources/google/common/googlesettingsdialog.cpp +++ /dev/null @@ -1,266 +0,0 @@ -/* - Copyright (C) 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 . -*/ - -#include "googlesettingsdialog.h" -#include "googleaccountmanager.h" -#include "googlesettings.h" -#include "googleresource.h" -#include "settings.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -Q_DECLARE_METATYPE(KGAPI2::Job *) - -using namespace KGAPI2; - -GoogleSettingsDialog::GoogleSettingsDialog(GoogleAccountManager *accountManager, WId wId, GoogleResource *parent) - : QDialog() - , m_parentResource(parent) - , m_accountManager(accountManager) -{ - setAttribute(Qt::WA_NativeWindow, true); - KWindowSystem::setMainWindow(windowHandle(), wId); - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - QVBoxLayout *topLayout = new QVBoxLayout(this); - QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); - okButton->setDefault(true); - okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, &QDialogButtonBox::accepted, this, &GoogleSettingsDialog::slotSaveSettings); - connect(buttonBox, &QDialogButtonBox::rejected, this, &GoogleSettingsDialog::reject); - - QWidget *widget = new QWidget(this); - topLayout->addWidget(widget); - topLayout->addWidget(buttonBox); - QVBoxLayout *mainLayout = new QVBoxLayout(widget); - mainLayout->setContentsMargins(0, 0, 0, 0); - m_mainLayout = mainLayout; - - m_accGroupBox = new QGroupBox(i18n("Accounts"), this); - mainLayout->addWidget(m_accGroupBox); - QHBoxLayout *accLayout = new QHBoxLayout(m_accGroupBox); - - m_accComboBox = new QComboBox(m_accGroupBox); - accLayout->addWidget(m_accComboBox, 1); - connect(m_accComboBox, QOverload::of(&QComboBox::currentTextChanged), this, &GoogleSettingsDialog::currentAccountChanged); - - m_addAccButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("&Add"), m_accGroupBox); - accLayout->addWidget(m_addAccButton); - connect(m_addAccButton, &QPushButton::clicked, this, &GoogleSettingsDialog::slotAddAccountClicked); - - m_removeAccButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove-user")), i18n("&Remove"), m_accGroupBox); - accLayout->addWidget(m_removeAccButton); - connect(m_removeAccButton, &QPushButton::clicked, this, &GoogleSettingsDialog::slotRemoveAccountClicked); - - QGroupBox *refreshBox = new QGroupBox(i18n("Refresh"), this); - mainLayout->addWidget(refreshBox); - QGridLayout *refreshLayout = new QGridLayout(refreshBox); - - m_enableRefresh = new QCheckBox(i18n("Enable interval refresh"), refreshBox); - m_enableRefresh->setChecked(Settings::self()->enableIntervalCheck()); - refreshLayout->addWidget(m_enableRefresh, 0, 0, 1, 2); - - QLabel *label = new QLabel(i18n("Refresh interval:")); - refreshLayout->addWidget(label, 1, 0); - m_refreshSpinBox = new KPluralHandlingSpinBox(this); - m_refreshSpinBox->setMaximum(720); - m_refreshSpinBox->setMinimum(10); - m_refreshSpinBox->setSingleStep(1); - m_refreshSpinBox->setValue(30); - m_refreshSpinBox->setDisplayIntegerBase(10); - m_refreshSpinBox->setSuffix(ki18np(" minute", " minutes")); - m_refreshSpinBox->setEnabled(Settings::self()->enableIntervalCheck()); - refreshLayout->addWidget(m_refreshSpinBox, 1, 1); - connect(m_enableRefresh, &QCheckBox::toggled, m_refreshSpinBox, &KPluralHandlingSpinBox::setEnabled); - - if (m_enableRefresh->isEnabled()) { - m_refreshSpinBox->setValue(Settings::self()->intervalCheckTime()); - } - QMetaObject::invokeMethod(this, &GoogleSettingsDialog::reloadAccounts, Qt::QueuedConnection); -} - -GoogleSettingsDialog::~GoogleSettingsDialog() -{ -} - -QVBoxLayout *GoogleSettingsDialog::mainLayout() const -{ - return m_mainLayout; -} - -GoogleAccountManager *GoogleSettingsDialog::accountManager() const -{ - return m_accountManager; -} - -KGAPI2::AccountPtr GoogleSettingsDialog::currentAccount() const -{ - return m_accountManager->findAccount(m_accComboBox->currentText()); -} - -void GoogleSettingsDialog::reloadAccounts() -{ - disconnect(m_accComboBox, QOverload::of(&QComboBox::currentTextChanged), this, &GoogleSettingsDialog::currentAccountChanged); - - m_accComboBox->clear(); - - const AccountsList accounts = m_accountManager->listAccounts(); - for (const AccountPtr &account : accounts) { - m_accComboBox->addItem(account->accountName()); - } - - int index = m_accComboBox->findText(m_parentResource->settings()->account(), Qt::MatchExactly); - if (index > -1) { - m_accComboBox->setCurrentIndex(index); - } - - connect(m_accComboBox, QOverload::of(&QComboBox::currentTextChanged), this, &GoogleSettingsDialog::currentAccountChanged); - - m_removeAccButton->setEnabled(m_accComboBox->count() > 0); - Q_EMIT currentAccountChanged(m_accComboBox->currentText()); -} - -void GoogleSettingsDialog::slotAddAccountClicked() -{ - AccountPtr account(new Account()); - // FIXME: We need a proper API for this - account->addScope(Account::contactsScopeUrl()); - account->addScope(Account::calendarScopeUrl()); - account->addScope(Account::tasksScopeUrl()); - account->addScope(Account::accountInfoEmailScopeUrl()); - account->addScope(Account::accountInfoScopeUrl()); - - AuthJob *authJob = new AuthJob(account, - m_parentResource->settings()->clientId(), - m_parentResource->settings()->clientSecret()); - connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAccountAuthenticated); -} - -void GoogleSettingsDialog::slotRemoveAccountClicked() -{ - const AccountPtr account = currentAccount(); - if (!account) { - return; - } - - if (KMessageBox::warningYesNo( - this, - i18n("Do you really want to revoke access to account %1?" - "

This will revoke access to all resources using this account!

", - account->accountName()), - i18n("Revoke Access?"), - KStandardGuiItem::yes(), - KStandardGuiItem::no(), - QString(), - KMessageBox::Dangerous) != KMessageBox::Yes) { - return; - } - - m_accountManager->removeAccount(account->accountName()); - reloadAccounts(); -} - -void GoogleSettingsDialog::slotAccountAuthenticated(Job *job) -{ - AuthJob *authJob = qobject_cast(job); - const AccountPtr account = authJob->account(); - - if (authJob->error() != KGAPI2::NoError) { - KMessageBox::sorry(this, authJob->errorString()); - return; - } - - if (!m_accountManager->storeAccount(account)) { - qWarning() << "Failed to add account to KWallet"; - } - - reloadAccounts(); -} - -bool GoogleSettingsDialog::handleError(Job *job) -{ - if ((job->error() == KGAPI2::NoError) || (job->error() == KGAPI2::OK)) { - return true; - } - - if (job->error() == KGAPI2::Unauthorized) { - qDebug() << job << job->errorString(); - const AccountPtr account = currentAccount(); - const QList resourceScopes = m_parentResource->scopes(); - for (const QUrl &scope : resourceScopes) { - if (!account->scopes().contains(scope)) { - account->addScope(scope); - } - } - - AuthJob *authJob = new AuthJob(account, m_parentResource->settings()->clientId(), - m_parentResource->settings()->clientSecret(), this); - authJob->setProperty(JOB_PROPERTY, QVariant::fromValue(job)); - connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAuthJobFinished); - - return false; - } - - KMessageBox::sorry(this, job->errorString()); - job->deleteLater(); - return false; -} - -void GoogleSettingsDialog::slotAuthJobFinished(Job *job) -{ - qDebug(); - - if (job->error() != KGAPI2::NoError) { - KMessageBox::sorry(this, job->errorString()); - return; - } - - AuthJob *authJob = qobject_cast(job); - const AccountPtr account = authJob->account(); - if (!m_accountManager->storeAccount(account)) { - qWarning() << "Failed to store account in KWallet"; - } - - KGAPI2::Job *otherJob = job->property(JOB_PROPERTY).value(); - otherJob->setAccount(account); - otherJob->restart(); - - job->deleteLater(); -} - -void GoogleSettingsDialog::slotSaveSettings() -{ - Settings::self()->setEnableIntervalCheck(m_enableRefresh->isChecked()); - Settings::self()->setIntervalCheckTime(m_refreshSpinBox->value()); - - saveSettings(); - accept(); -} diff --git a/resources/google/common/kgapiversionattribute.h b/resources/google/common/kgapiversionattribute.h deleted file mode 100644 --- a/resources/google/common/kgapiversionattribute.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright (C) 2019 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 KGAPIVERSIONATTRIBUTE -#define KGAPIVERSIONATTRIBUTE - -#include - -class KGAPIVersionAttribute : public Akonadi::Attribute -{ -public: - explicit KGAPIVersionAttribute() = default; - explicit KGAPIVersionAttribute(uint32_t version); - ~KGAPIVersionAttribute() override = default; - - uint32_t version() const - { - return mVersion; - } - - void setVersion(uint32_t version) - { - mVersion = version; - } - - QByteArray type() const override; - Akonadi::Attribute *clone() const override; - QByteArray serialized() const override; - void deserialize(const QByteArray &data) override; - -private: - uint32_t mVersion = 0; -}; - -#endif diff --git a/resources/google/common/kgapiversionattribute.cpp b/resources/google/common/kgapiversionattribute.cpp deleted file mode 100644 --- a/resources/google/common/kgapiversionattribute.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright (C) 2019 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 . -*/ - -#include "kgapiversionattribute.h" - -KGAPIVersionAttribute::KGAPIVersionAttribute(uint32_t version) - : mVersion(version) -{ -} - -QByteArray KGAPIVersionAttribute::type() const -{ - return "KGAPIVersionAttribute"; -} - -Akonadi::Attribute *KGAPIVersionAttribute::clone() const -{ - return new KGAPIVersionAttribute(mVersion); -} - -void KGAPIVersionAttribute::deserialize(const QByteArray &data) -{ - mVersion = data.toUInt(); -} - -QByteArray KGAPIVersionAttribute::serialized() const -{ - return QByteArray::number(mVersion); -} diff --git a/resources/google/contacts/CMakeLists.txt b/resources/google/contacts/CMakeLists.txt deleted file mode 100644 --- a/resources/google/contacts/CMakeLists.txt +++ /dev/null @@ -1,62 +0,0 @@ - -add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_googlecontacts_resource\") - -set(contactsresource_SRCS - contactsresource.cpp - settings.cpp - settingsdialog.cpp - ../common/googlesettings.cpp - ../common/googleresource.cpp - ../common/googleaccountmanager.cpp - ../common/googlesettingsdialog.cpp - ${accounts_SRCS} -) - -kconfig_add_kcfg_files(contactsresource_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfgc) - -kcfg_generate_dbus_interface( - ${CMAKE_CURRENT_SOURCE_DIR}/settingsbase.kcfg - org.kde.Akonadi.GoogleContacts.Settings -) - -qt5_add_dbus_adaptor(contactsresource_SRCS - ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.GoogleContacts.Settings.xml - ${CMAKE_CURRENT_SOURCE_DIR}/settings.h Settings -) - -add_executable(akonadi_googlecontacts_resource ${contactsresource_SRCS}) - -if( APPLE ) - set_target_properties(akonadi_googlecontacts_resource PROPERTIES - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../../Info.plist.template - ) - set_target_properties(akonadi_googlecontacts_resource PROPERTIES - MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.Akonadi.googlecontacts" - ) - set_target_properties(akonadi_googlecontacts_resource PROPERTIES - MACOSX_BUNDLE_BUNDLE_NAME "KDE Akonadi Google Contacts Resource" - ) -endif() - - -target_link_libraries(akonadi_googlecontacts_resource - KF5::AkonadiCore - KF5::Contacts - KPim::GAPICore - KPim::GAPIContacts - KF5::AkonadiAgentBase - KF5::Wallet - KF5::I18n - KF5::WindowSystem - KF5::Completion - KF5::WidgetsAddons - KF5::TextWidgets - Qt5::DBus -) - -install(TARGETS akonadi_googlecontacts_resource ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) - -install( - FILES googlecontactsresource.desktop - DESTINATION "${KDE_INSTALL_DATAROOTDIR}/akonadi/agents" -) diff --git a/resources/google/contacts/Messages.sh b/resources/google/contacts/Messages.sh deleted file mode 100644 --- a/resources/google/contacts/Messages.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /usr/bin/env bash -$EXTRACTRC *.ui *.kcfg >> rc.cpp -$XGETTEXT *.cpp -o $podir/akonadi_googlecontacts_resource.pot -$XGETTEXT ../common/* -j -o $podir/akonadi_googlecontacts_resource.pot diff --git a/resources/google/contacts/contactsresource.h b/resources/google/contacts/contactsresource.h deleted file mode 100644 --- a/resources/google/contacts/contactsresource.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright (C) 2011, 2012 Dan Vratil - - 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 GOOGLE_CONTACTS_CONTACTSRESOURCE_H -#define GOOGLE_CONTACTS_CONTACTSRESOURCE_H - -#include "common/googleresource.h" - -#include -#include - -class GoogleSettings; -namespace KGAPI2 { -class Job; -} -class KJob; - -class ContactsResource : public GoogleResource -{ - Q_OBJECT - -public: - using GoogleResource::collectionChanged; // So we don't trigger -Woverloaded-virtual - explicit ContactsResource(const QString &id); - - ~ContactsResource() override; - -protected: - using ResourceBase::retrieveItems; // Suppress -Woverload-virtual - -protected Q_SLOTS: - void retrieveCollections() override; - void retrieveItems(const Akonadi::Collection &collection) override; - virtual void retrieveContactsPhotos(const QVariant &argument); - - void itemRemoved(const Akonadi::Item &item) override; - void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override; - void itemChanged(const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers) override; - void itemMoved(const Akonadi::Item &item, 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; - - void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) override; - void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) override; - - void slotItemsRetrieved(KGAPI2::Job *job); - void slotCollectionsRetrieved(KGAPI2::Job *job); - - void slotUpdatePhotosItemsRetrieved(KJob *job); - void slotUpdatePhotoFinished(KGAPI2::Job *job, const KGAPI2::ContactPtr &contact); - - void slotCreateJobFinished(KGAPI2::Job *job); - - GoogleSettings *settings() const override; - int runConfigurationDialog(WId windowId) override; - void updateResourceName() override; - QList< QUrl > scopes() const override; - -private: - - QMap m_collections; - Akonadi::Collection m_rootCollection; -}; - -#endif // CONTACTSRESOURCE_H diff --git a/resources/google/contacts/contactsresource.cpp b/resources/google/contacts/contactsresource.cpp deleted file mode 100644 --- a/resources/google/contacts/contactsresource.cpp +++ /dev/null @@ -1,554 +0,0 @@ -/* - 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 . -*/ - -#include "contactsresource.h" -#include "settingsdialog.h" -#include "settings.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MYCONTACTS_REMOTEID QStringLiteral("MyContacts") -#define OTHERCONTACTS_REMOTEID QStringLiteral("OtherContacts") - -Q_DECLARE_METATYPE(KGAPI2::Job *) -Q_DECLARE_METATYPE(QList) - -using namespace Akonadi; -using namespace KGAPI2; - -ContactsResource::ContactsResource(const QString &id) - : GoogleResource(id) -{ - updateResourceName(); -} - -ContactsResource::~ContactsResource() -{ -} - -GoogleSettings *ContactsResource::settings() const -{ - return Settings::self(); -} - -int ContactsResource::runConfigurationDialog(WId windowId) -{ - QScopedPointer settingsDialog(new SettingsDialog(accountManager(), windowId, this)); - settingsDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("im-google"))); - - return settingsDialog->exec(); -} - -void ContactsResource::updateResourceName() -{ - const QString accountName = Settings::self()->account(); - setName(i18nc("%1 is account name (user@gmail.com)", "Google Contacts (%1)", accountName.isEmpty() ? i18n("not configured") : accountName)); -} - -QList< QUrl > ContactsResource::scopes() const -{ - const QList< QUrl > scopes = {Account::contactsScopeUrl(), Account::accountInfoScopeUrl()}; - return scopes; -} - -void ContactsResource::retrieveItems(const Collection &collection) -{ - if (!canPerformTask()) { - return; - } - - // All items are only in top-level collection and Other Contacts collection - if ((collection.remoteId() != m_rootCollection.remoteId()) - && (collection.remoteId() != OTHERCONTACTS_REMOTEID)) { - itemsRetrievalDone(); - return; - } - - ContactFetchJob *fetchJob = new ContactFetchJob(account(), this); - fetchJob->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - fetchJob->setFetchDeleted(true); - if (!collection.remoteRevision().isEmpty()) { - fetchJob->setFetchOnlyUpdated(collection.remoteRevision().toLongLong()); - } - connect(fetchJob, &ContactFetchJob::progress, this, &ContactsResource::emitPercent); - connect(fetchJob, &ContactFetchJob::finished, this, &ContactsResource::slotItemsRetrieved); -} - -void ContactsResource::retrieveContactsPhotos(const QVariant &arguments) -{ - if (!canPerformTask()) { - return; - } - - const QVariantMap map = arguments.toMap(); - const Collection collection = map[ QStringLiteral("collection") ].value(); - ItemFetchJob *itemFetchJob = new ItemFetchJob(collection, this); - itemFetchJob->setProperty("modifiedItems", map[ QStringLiteral("modifiedItems") ]); - itemFetchJob->fetchScope().fetchFullPayload(true); - connect(itemFetchJob, &ItemFetchJob::finished, this, &ContactsResource::slotUpdatePhotosItemsRetrieved); - Q_EMIT status(Running, i18nc("@info:status", "Retrieving photos")); -} - -void ContactsResource::retrieveCollections() -{ - if (!canPerformTask()) { - return; - } - - ContactsGroupFetchJob *fetchJob = new ContactsGroupFetchJob(account(), this); - connect(fetchJob, &ContactFetchJob::progress, this, &ContactsResource::emitPercent); - connect(fetchJob, &ContactFetchJob::finished, this, &ContactsResource::slotCollectionsRetrieved); -} - -void ContactsResource::itemAdded(const Item &item, const Collection &collection) -{ - if (!canPerformTask(item, KContacts::Addressee::mimeType())) { - return; - } - - KContacts::Addressee addressee = item.payload< KContacts::Addressee >(); - ContactPtr contact(new Contact(addressee)); - - /* If the contact has been moved into My Contacts group then modify the membership */ - if (collection.remoteId() == MYCONTACTS_REMOTEID) { - contact->addGroup(QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(account()->accountName())))); - } - - /* If the contact has been moved to Other Contacts then remove all groups */ - if (collection.remoteId() == OTHERCONTACTS_REMOTEID) { - contact->clearGroups(); - } - - ContactCreateJob *createJob = new ContactCreateJob(contact, account(), this); - createJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(createJob, &ContactCreateJob::progress, this, &ContactsResource::emitPercent); - connect(createJob, &ContactCreateJob::finished, this, &ContactsResource::slotCreateJobFinished); -} - -void ContactsResource::itemChanged(const Item &item, const QSet< QByteArray > &partIdentifiers) -{ - Q_UNUSED(partIdentifiers); - - if (!canPerformTask(item, KContacts::Addressee::mimeType())) { - return; - } - - KContacts::Addressee addressee = item.payload< KContacts::Addressee >(); - ContactPtr contact(new Contact(addressee)); - - if (item.parentCollection().remoteId() == MYCONTACTS_REMOTEID) { - contact->addGroup(QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(account()->accountName())))); - } - - ContactModifyJob *modifyJob = new ContactModifyJob(contact, account(), this); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(modifyJob, &ContactModifyJob::progress, this, &ContactsResource::emitPercent); - connect(modifyJob, &ContactModifyJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination) -{ - if (!canPerformTask(item, KContacts::Addressee::mimeType())) { - return; - } - - 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(account()->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(account()->accountName())))); - } else { - cancelTask(i18n("Invalid source or destination collection")); - return; - } - - ContactModifyJob *modifyJob = new ContactModifyJob(contact, account(), this); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(modifyJob, &ContactModifyJob::progress, this, &ContactsResource::emitPercent); - connect(modifyJob, &ContactModifyJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::itemRemoved(const Item &item) -{ - if (!canPerformTask()) { - return; - } - - ContactDeleteJob *deleteJob = new ContactDeleteJob(item.remoteId(), account(), this); - deleteJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(deleteJob, &ContactDeleteJob::progress, this, &ContactsResource::emitPercent); - connect(deleteJob, &ContactDeleteJob::finished, this, &ContactsResource::slotGenericJobFinished); - - Q_EMIT status(Running, i18nc("@info:status", "Removing contact")); -} - -void ContactsResource::itemLinked(const Item &item, const Collection &collection) -{ - if (!canPerformTask(item, KContacts::Addressee::mimeType())) { - return; - } - - KContacts::Addressee addressee = item.payload(); - ContactPtr contact(new Contact(addressee)); - - contact->addGroup(collection.remoteId()); - - ContactModifyJob *modifyJob = new ContactModifyJob(contact, account(), this); - connect(modifyJob, &ContactModifyJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::itemUnlinked(const Item &item, const Collection &collection) -{ - if (!canPerformTask(item, KContacts::Addressee::mimeType())) { - return; - } - - KContacts::Addressee addressee = item.payload(); - ContactPtr contact(new Contact(addressee)); - - contact->removeGroup(collection.remoteId()); - - ContactModifyJob *modifyJob = new ContactModifyJob(contact, account(), this); - modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); - connect(modifyJob, &ContactModifyJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) -{ - Q_UNUSED(parent); - - if (!canPerformTask()) { - return; - } - - ContactsGroupPtr group(new ContactsGroup); - group->setTitle(collection.name()); - group->setIsSystemGroup(false); - - ContactsGroupCreateJob *createJob = new ContactsGroupCreateJob(group, account(), this); - createJob->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(createJob, &ContactsGroupCreateJob::finished, this, &ContactsResource::slotCreateJobFinished); -} - -void ContactsResource::collectionChanged(const Akonadi::Collection &collection) -{ - if (!canPerformTask()) { - return; - } - - ContactsGroupPtr group(new ContactsGroup()); - group->setId(collection.remoteId()); - - group->setTitle(collection.displayName()); - - ContactsGroupModifyJob *modifyJob = new ContactsGroupModifyJob(group, account(), this); - modifyJob->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(modifyJob, &ContactsGroupModifyJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::collectionRemoved(const Akonadi::Collection &collection) -{ - if (!canPerformTask()) { - return; - } - - ContactsGroupDeleteJob *deleteJob = new ContactsGroupDeleteJob(collection.remoteId(), account(), this); - deleteJob->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); - connect(deleteJob, &ContactsGroupDeleteJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::slotCollectionsRetrieved(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - ContactsGroupFetchJob *fetchJob = qobject_cast(job); - const ObjectsList objects = fetchJob->items(); - - CachePolicy cachePolicy; - if (Settings::self()->enableIntervalCheck()) { - cachePolicy.setInheritFromParent(false); - cachePolicy.setIntervalCheckTime(Settings::self()->intervalCheckTime()); - } - - m_rootCollection = Collection(); - m_rootCollection.setContentMimeTypes({Collection::virtualMimeType(), Collection::mimeType(), KContacts::Addressee::mimeType()}); - m_rootCollection.setRemoteId(MYCONTACTS_REMOTEID); - m_rootCollection.setName(fetchJob->account()->accountName()); - m_rootCollection.setParentCollection(Collection::root()); - m_rootCollection.setCachePolicy(cachePolicy); - m_rootCollection.setRights(Collection::CanCreateItem - |Collection::CanChangeItem - |Collection::CanDeleteItem); - - EntityDisplayAttribute *attr = m_rootCollection.attribute(Collection::AddIfMissing); - attr->setDisplayName(fetchJob->account()->accountName()); - attr->setIconName(QStringLiteral("im-google")); - - m_collections[ MYCONTACTS_REMOTEID ] = m_rootCollection; - - for (const ObjectPtr &object : qAsConst(objects)) { - const ContactsGroupPtr group = object.dynamicCast(); - - 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"))) { - // Yes, skip My Contacts group, we store "My Contacts" in root collection - continue; - } - } else { - if (group->title().contains(QLatin1String("Other Contacts"))) { - realName = i18nc("Name of a group of contacts", "Other Contacts"); - } - } - - Collection collection; - collection.setContentMimeTypes(QStringList() << KContacts::Addressee::mimeType()); - collection.setName(group->id()); - collection.setParentCollection(m_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); - - EntityDisplayAttribute *collAttr = collection.attribute(Collection::AddIfMissing); - collAttr->setDisplayName(realName); - collAttr->setIconName(QStringLiteral("view-pim-contacts")); - - m_collections[ collection.remoteId() ] = collection; - } - - Collection otherCollection; - otherCollection.setContentMimeTypes(QStringList() << KContacts::Addressee::mimeType()); - otherCollection.setName(i18n("Other Contacts")); - otherCollection.setParentCollection(m_rootCollection); - otherCollection.setRights(Collection::CanCreateItem - |Collection::CanChangeItem - |Collection::CanDeleteItem); - otherCollection.setRemoteId(OTHERCONTACTS_REMOTEID); - - attr = otherCollection.attribute(Collection::AddIfMissing); - attr->setDisplayName(i18n("Other Contacts")); - attr->setIconName(QStringLiteral("view-pim-contacts")); - m_collections[ OTHERCONTACTS_REMOTEID ] = otherCollection; - - collectionsRetrieved(Akonadi::valuesToVector(m_collections)); - job->deleteLater(); -} - -void ContactsResource::slotItemsRetrieved(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - ContactFetchJob *fetchJob = qobject_cast(job); - - Collection collection = fetchJob->property(COLLECTION_PROPERTY).value(); - - Item::List changedItems, removedItems; - QMap groupsMap; - QList changedPhotos; - const ObjectsList objects = fetchJob->items(); - for (const ObjectPtr &object : objects) { - const ContactPtr contact = object.dynamicCast(); - - if (((collection.remoteId() == OTHERCONTACTS_REMOTEID) && !contact->groups().isEmpty()) - || ((collection.remoteId() == MYCONTACTS_REMOTEID) && contact->groups().isEmpty())) { - continue; - } - - Item item; - item.setMimeType(KContacts::Addressee::mimeType()); - item.setParentCollection(m_collections[MYCONTACTS_REMOTEID]); - item.setRemoteId(contact->uid()); - item.setRemoteRevision(contact->etag()); - item.setPayload(*contact.dynamicCast()); - - if (contact->deleted()) { - removedItems << item; - } else { - changedItems << item; - changedPhotos << contact->uid(); - } - - const QStringList groups = contact->groups(); - for (const QString &group : groups) { - groupsMap[group] << item; - } - } - - itemsRetrievedIncremental(changedItems, removedItems); - - for (QMap::ConstIterator iter = groupsMap.constBegin(), iterEnd = groupsMap.constEnd(); iter != iterEnd; ++iter) { - new LinkJob(m_collections[iter.key()], iter.value(), this); - } - - QVariantMap map; - map[QStringLiteral("collection")] = QVariant::fromValue(collection); - map[QStringLiteral("modifiedItems")] = QVariant::fromValue(changedPhotos); - scheduleCustomTask(this, "retrieveContactsPhotos", map); - - const QDateTime local(QDateTime::currentDateTime()); - const QDateTime UTC(local.toUTC()); - - collection.setRemoteRevision(QString::number(UTC.toSecsSinceEpoch())); - new CollectionModifyJob(collection, this); - - job->deleteLater(); -} - -void ContactsResource::slotUpdatePhotosItemsRetrieved(KJob *job) -{ - ItemFetchJob *fetchJob = qobject_cast(job); - const QList modifiedItems = fetchJob->property("modifiedItems").value< QList >(); - ContactsList contacts; - - const Item::List items = fetchJob->items(); - for (const Item &item : items) { - if (modifiedItems.contains(item.remoteId())) { - const KContacts::Addressee addressee = item.payload(); - const ContactPtr contact(new Contact(addressee)); - contacts << contact; - } - } - - // Make sure account is still valid - if (!canPerformTask()) { - return; - } - - ContactFetchPhotoJob *photoJob = new ContactFetchPhotoJob(contacts, account(), this); - photoJob->setProperty(ITEMLIST_PROPERTY, QVariant::fromValue(items)); - photoJob->setProperty("processedItems", 0); - connect(photoJob, &ContactFetchPhotoJob::photoFetched, this, &ContactsResource::slotUpdatePhotoFinished); - connect(photoJob, &ContactFetchPhotoJob::finished, this, &ContactsResource::slotGenericJobFinished); -} - -void ContactsResource::slotUpdatePhotoFinished(KGAPI2::Job *job, const ContactPtr &contact) -{ - Item::List items = job->property(ITEMLIST_PROPERTY).value(); - - int processedItems = job->property("processedItems").toInt(); - processedItems++; - job->setProperty("processedItems", processedItems); - emitPercent(job, processedItems, items.count()); - - for (Item item : qAsConst(items)) { - if (item.remoteId() == contact->uid()) { - item.setPayload(*contact.dynamicCast()); - new ItemModifyJob(item, this); - return; - } - } -} - -void ContactsResource::slotCreateJobFinished(KGAPI2::Job *job) -{ - if (!handleError(job)) { - return; - } - - Item item = job->property(ITEM_PROPERTY).value(); - Collection collection = job->property(COLLECTION_PROPERTY).value(); - if (item.isValid()) { - ContactCreateJob *createJob = qobject_cast(job); - Q_ASSERT(createJob->items().count() == 1); - ContactPtr contact = createJob->items().at(0).dynamicCast(); - - item.setRemoteId(contact->uid()); - item.setRemoteRevision(contact->etag()); - changeCommitted(item); - /** - * An Addressee inside Akonadi DB has a default UID, which differs from - * one obtained from Google, so we end up having a confision between remoteId - * and UID. Since changeCommitted does not update the payload, we need to - * update it here explicitly. - */ - item.setPayload(*contact.dynamicCast()); - new ItemModifyJob(item); - } else if (collection.isValid()) { - ContactsGroupCreateJob *createJob = qobject_cast(job); - Q_ASSERT(createJob->items().count() == 1); - ContactsGroupPtr group = createJob->items().at(0).dynamicCast(); - - collection.setRemoteId(group->id()); - collection.setContentMimeTypes(QStringList() << KContacts::Addressee::mimeType()); - - EntityDisplayAttribute *attr = collection.attribute(Collection::AddIfMissing); - attr->setDisplayName(group->title()); - attr->setIconName(QStringLiteral("view-pim-contacts")); - - m_collections[ collection.remoteId() ] = collection; - - changeCommitted(collection); - } - - job->deleteLater(); -} - -AKONADI_RESOURCE_MAIN(ContactsResource) diff --git a/resources/google/contacts/googlecontactsresource.desktop b/resources/google/contacts/googlecontactsresource.desktop deleted file mode 100644 --- a/resources/google/contacts/googlecontactsresource.desktop +++ /dev/null @@ -1,95 +0,0 @@ -[Desktop Entry] -Name=Google Contacts -Name[ar]=متراسلو غوغل -Name[bg]=Контакти в Google -Name[bs]=Google kontakti -Name[ca]=Contactes de Google -Name[ca@valencia]=Contactes de Google -Name[cs]=Kontakty Google -Name[da]=Google-kontakter -Name[de]=Google-Kontakte -Name[el]=Google Επαφές -Name[en_GB]=Google Contacts -Name[es]=Contactos Google -Name[et]=Google'i kontaktid -Name[fi]=Google-yhteystiedot -Name[fr]=Contacts Google -Name[ga]=Teagmhálacha Google -Name[gl]=Google Contacts -Name[hu]=Google névjegyek -Name[ia]=Contactos de Google -Name[it]=Contatti Google -Name[kk]=Google контакттары -Name[km]=ទំនាក់ទំនង Google -Name[ko]=Google 연락처 -Name[lt]=Google kontaktai -Name[lv]=Google kontakti -Name[nb]=Google-kontakter -Name[nds]=Google-Kontakten -Name[nl]=Google contactpersonen -Name[pl]=Kontakty Google -Name[pt]=Contactos do Google -Name[pt_BR]=Contatos do Google -Name[ru]=Контакты Google -Name[sk]=Google kontakty -Name[sl]=Stiki Google -Name[sr]=Гуглови контакти -Name[sr@ijekavian]=Гуглови контакти -Name[sr@ijekavianlatin]=Googleovi kontakti -Name[sr@latin]=Googleovi kontakti -Name[sv]=Google kontakter -Name[tr]=Google Kişileri -Name[ug]=Google ئالاقەداشلىرى -Name[uk]=Контакти Google -Name[x-test]=xxGoogle Contactsxx -Name[zh_CN]=Google 联系人 -Name[zh_TW]=Google 聯絡人 -Comment=Access your Google Contacts from KDE -Comment[ar]=توصّل إلى متراسلي «غوغل» من «كدي» -Comment[bg]=Достъп до контактите ви в Google от KDE -Comment[bs]=Pristupite svojim Google kontaktima iz KDE -Comment[ca]=Accediu als contactes de Google des del KDE -Comment[ca@valencia]=Accediu als contactes de Google des del KDE -Comment[da]=Tilgå dine Google-kontakter fra KDE -Comment[de]=Greifen Sie in KDE auf Google-Kontakte zu -Comment[el]=Αποκτήστε πρόσβαση στις Google επαφές σας από το KDE -Comment[en_GB]=Access your Google Contacts from KDE -Comment[es]=Acceda a sus contactos Google desde KDE -Comment[et]=Oma Google'i kontaktide kasutamine otse KDE-st -Comment[fi]=Pääsy KDE:sta Googlen yhteystietoihisi -Comment[fr]=Accès à vos contacts Google depuis KDE -Comment[gl]=Acceda aos seus contactos de Google desde KDE. -Comment[hu]=A Google névjegyeinek elérése a KDE-ből -Comment[ia]=Accede a tu Contactos de Google ab KDE -Comment[it]=Accedi ai tuoi contatti Google da KDE -Comment[kk]=Google контакттарына KDE-ден қатынау -Comment[km]=ចូល​ដំណើរការ​ទំនាក់ទំនង Google របស់​អ្នក​ពី KDE -Comment[ko]=KDE에서 Google 연락처에 접근합니다 -Comment[lt]=Pasiekite savo Google kontaktus iš KDE -Comment[lv]=Piekļūstiet saviem Google kontaktiem no KDE -Comment[nb]=Bruk dine Google-kontakter fra KDE -Comment[nds]=Ut KDE op Dien Google-Kontakten togriepen -Comment[nl]=Heb toegang tot uw Google contactpersonen vanuit KDE -Comment[pl]=Daje dostęp do twoich kontaktów Google z KDE -Comment[pt]=Aceda aos seus contactos da Google a partir do KDE -Comment[pt_BR]=Acesse seus contatos do Google a partir do KDE -Comment[ru]=Доступ к контактам Google из KDE -Comment[sk]=Pristupuje k vašim Google kontaktom z KDE -Comment[sl]=Dostopajte do svojih stikov Google -Comment[sr]=Приступите својим контактима на Гуглу из КДЕ‑а -Comment[sr@ijekavian]=Приступите својим контактима на Гуглу из КДЕ‑а -Comment[sr@ijekavianlatin]=Pristupite svojim kontaktima na Googleu iz KDE‑a -Comment[sr@latin]=Pristupite svojim kontaktima na Googleu iz KDE‑a -Comment[sv]=Kom åt Google kontakter från KDE -Comment[tr]=Google Kişilerinize KDE'den erişin -Comment[uk]=Доступ до ваших записів контактів Google з KDE -Comment[x-test]=xxAccess your Google Contacts from KDExx -Comment[zh_CN]=在 KDE 中访问您的 Google 联系人 -Comment[zh_TW]=用 KDE 存取您的 Google 聯絡人 -Type=AkonadiResource -Exec=akonadi_googlecontacts_resource -X-Akonadi-MimeTypes=text/directory, -X-Akonadi-Capabilities=Resource -X-Akonadi-Identifier=akonadi_googlecontacts_resource -X-Akonadi-Custom-KAccounts=google-contacts,google-calendar -Icon=im-google diff --git a/resources/google/contacts/settings.h b/resources/google/contacts/settings.h deleted file mode 100644 --- a/resources/google/contacts/settings.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (C) 2011-2012 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 GOOGLE_CONTACTS_SETTINGS_H -#define GOOGLE_CONTACTS_SETTINGS_H - -#include "common/googlesettings.h" - -class Settings : public GoogleSettings -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.Akonadi.GoogleContacts.ExtendedSettings") -public: - Settings(); - static Settings *self(); -}; - -#endif // SETTINGS_H diff --git a/resources/google/contacts/settings.cpp b/resources/google/contacts/settings.cpp deleted file mode 100644 --- a/resources/google/contacts/settings.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - 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 . -*/ - -#include "settings.h" -#include "settingsadaptor.h" - -#include - -class SettingsHelper -{ -public: - SettingsHelper() : q(nullptr) - { - } - - ~SettingsHelper() - { - delete q; - q = nullptr; - } - - Settings *q; -}; - -Q_GLOBAL_STATIC(SettingsHelper, s_globalSettings) - -Settings::Settings() - : GoogleSettings() -{ - Q_ASSERT(!s_globalSettings->q); - s_globalSettings->q = this; - - new SettingsAdaptor(this); - QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), this, - QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableContents); -} - -Settings *Settings::self() -{ - if (!s_globalSettings->q) { - new Settings; - s_globalSettings->q->load(); - } - - return s_globalSettings->q; -} diff --git a/resources/google/contacts/settingsbase.kcfg b/resources/google/contacts/settingsbase.kcfg deleted file mode 100644 --- a/resources/google/contacts/settingsbase.kcfg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - 0 - - - - false - - - 60 - - - diff --git a/resources/google/contacts/settingsbase.kcfgc b/resources/google/contacts/settingsbase.kcfgc deleted file mode 100644 --- a/resources/google/contacts/settingsbase.kcfgc +++ /dev/null @@ -1,6 +0,0 @@ -File=settingsbase.kcfg -ClassName=SettingsBase -Mutators=true -ItemAccessors=true -Singleton=false -GlobalEnums=true diff --git a/resources/google/contacts/settingsdialog.h b/resources/google/contacts/settingsdialog.h deleted file mode 100644 --- a/resources/google/contacts/settingsdialog.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - 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 GOOGLE_CONTACTS_SETTINGSDIALOG_H -#define GOOGLE_CONTACTS_SETTINGSDIALOG_H - -#include "common/googlesettingsdialog.h" - -class GoogleAccountManager; - -class SettingsDialog : public GoogleSettingsDialog -{ - Q_OBJECT -public: - explicit SettingsDialog(GoogleAccountManager *accountMgr, WId windowId, GoogleResource *parent); - ~SettingsDialog(); - -private Q_SLOTS: - void saveSettings() override; -}; - -#endif // SETTINGSDIALOG_H diff --git a/resources/google/contacts/settingsdialog.cpp b/resources/google/contacts/settingsdialog.cpp deleted file mode 100644 --- a/resources/google/contacts/settingsdialog.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - 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 . -*/ - -#include "settingsdialog.h" -#include "settings.h" - -#include -#include - -using namespace KGAPI2; - -SettingsDialog::SettingsDialog(GoogleAccountManager *accountMgr, WId windowId, GoogleResource *parent) - : GoogleSettingsDialog(accountMgr, windowId, parent) -{ -} - -SettingsDialog::~SettingsDialog() -{ -} - -void SettingsDialog::saveSettings() -{ - const AccountPtr account = currentAccount(); - if (!account) { - Settings::self()->setAccount(QString()); - Settings::self()->save(); - return; - } - - Settings::self()->setAccount(account->accountName()); - Settings::self()->save(); -}