Commit 52113e2f authored by Igor Poboiko's avatar Igor Poboiko
Browse files

[resources] Add a unified Google Groupware Resource

Summary:
This is an attempt to unify existing Calendar&Tasks and Contacts resources into
a single Groupware resource. At some point, hopefully, GMail support could be also
added here (see task {T646} and {T9422}).

Various "subresources" (Calendar, Tasks and Contacts) are implemented as subclasses of `GenericHandler`,
which is a basic `Akonadi::ResourceBase` interface. The resource decides which `Handler` it should call
by looking at mimetypes. `Handlers` are `friends` of `GoogleResource`, so they can call its callbacks
(like `itemsRetrieved()`) as needed. This was done primarily to separate logic of different subresources;
this might be not the best solution, I'm open to suggestions.

This patch also reworks the settings dialog & relevant code.
The dialog is now using `.ui` file. The "account picker" is gone, as it's no longer needed;
instead, a single "Configure..." button is added which invokes the auth process.

It also implements "last sync token" API ({T647}) for calendar incremental updates. Without this API,
event moving between calendars were not handled properly (i.e. event was not removed from the "source" calendar).

Work to be done:
 # KAccounts integration. Need to be able to `disable` various `subresources` on demand, and determine auth `scopes` based on that.
 # GMail integration. Need to somehow adopt `ImapResourceBase` / `ResourceState` scheme, and merge it with current `Handlers` scheme.
 # Add `Akonadi::Tag` support for Contacts. Tags seem to be more appropriate than having bunch of virtual collections, but this might require some changes inside KAddressBook.

Test Plan:
Here's a comprehensive list of what was tested and what issues were discovered.
 # Adding event locally
 # Changing event locally
 # Removing event locally
 # Moving events between calendars locally
 # **Adding calendar locally**
   - new calendar is added as a virtual collection, cannot add events there afterwards; probably a KOrganizer issue
   - color of newly added calendar is not known, google just don't return it to us
 # Removing calendar locally
 # Changing calendar locally

 # Adding event remotely
 # Changing event remotely
 # Removing event remotely
 # Moving events between calendars remotely
 # Adding calendar remotely
 # Removing calendar remotely

 # Adding task&subtask remotely
 # Changing task locally
 # Removing task locally
 # Removing a task with subtasks locally (subtasks go to the upper level)
 # **Adding/removing tasklist locally**
   - wasn't tested, but should work. KOrganizer just don't know how to add a tasklist, it adds a calendar by default (probably it sees no difference between them...)
 # Changing tasklist locally

 # Adding task&subtask remotely
 # Changing task remotely
 # Removing task remotely
 # Adding tasklist remotely (it is not subscribed automatically, however, so user have to go to account settings and enable it)
 # Changing tasklist remotely
 # Removing tasklist remotely

 # Adding contact (including photo) locally, both inside "My Contacts" and "Other Contacts" groups
 # Moving contact between "My Contacts" and "Other Contacts" groups
 # Chaging contact (including photo) locally
 # Removing contat locally
 # Adding contact to contact group locally
   - {D28432} required, otherwise "Link Item" gets silently ignored
 # **Removing contact from contact group locally**
   - wasn't tested, since I've found to UI to "Unlink Item" :(

 # Adding contact (including photo) remotely
 # Changing contact (including photo) remotely
 # Removing contact remotely
 # Adding contact to contact group remotely
 # **Removing contact from contact group remotely**
   - doesn't work: need an easy way to fetch all collections we're linked to (so we know which UnlinkJobs we should start)

Reviewers: dvratil, mlaurent

Reviewed By: dvratil

Subscribers: mlaurent, kde-pim

Tags: #kde_pim

Differential Revision: https://phabricator.kde.org/D28560
parent fae81064
......@@ -52,7 +52,7 @@ add_subdirectory( maildir )
add_subdirectory( openxchange )
add_subdirectory( pop3 )
add_subdirectory( google )
add_subdirectory( google-groupware )
add_subdirectory( shared )
add_subdirectory( birthdays )
......
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"
)
/*
Copyright (C) 2011-2013 Daniel Vrátil <dvratil@redhat.com>
2020 Igor Poboiko <igor.poboiko@gmail.com>
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 <https://www.gnu.org/licenses/>.
*/
#include "calendarhandler.h"
#include "defaultreminderattribute.h"
#include "googleresource.h"
#include "googlesettings.h"
#include "googlecalendar_debug.h"
#include <AkonadiCore/CollectionColorAttribute>
#include <AkonadiCore/CollectionModifyJob>
#include <AkonadiCore/EntityDisplayAttribute>
#include <AkonadiCore/ItemModifyJob>
#include <Akonadi/Calendar/BlockAlarmsAttribute>
#include <KGAPI/Account>
#include <KGAPI/Calendar/Calendar>
#include <KGAPI/Calendar/CalendarCreateJob>
#include <KGAPI/Calendar/CalendarDeleteJob>
#include <KGAPI/Calendar/CalendarFetchJob>
#include <KGAPI/Calendar/CalendarModifyJob>
#include <KGAPI/Calendar/Event>
#include <KGAPI/Calendar/EventCreateJob>
#include <KGAPI/Calendar/EventDeleteJob>
#include <KGAPI/Calendar/EventFetchJob>
#include <KGAPI/Calendar/EventModifyJob>
#include <KGAPI/Calendar/EventMoveJob>
#include <KGAPI/Calendar/FreeBusyQueryJob>
#include <KCalendarCore/Calendar>
#include <KCalendarCore/FreeBusy>
#include <KCalendarCore/ICalFormat>
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<KCalendarCore::Event::Ptr>(item);
}
bool CalendarHandler::canPerformTask(const Item::List &items)
{
return GenericHandler::canPerformTask<KCalendarCore::Event::Ptr>(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<EntityDisplayAttribute>(Collection::AddIfMissing);
attr->setDisplayName(calendar->title());
attr->setIconName(QStringLiteral("view-calendar"));
// Setting color
if (calendar->backgroundColor().isValid()) {
auto colorAttr = collection.attribute<CollectionColorAttribute>(Collection::AddIfMissing);
colorAttr->setColor(calendar->backgroundColor());
}
// Setting default reminders
auto reminderAttr = collection.attribute<DefaultReminderAttribute>(Collection::AddIfMissing);
reminderAttr->setReminders(calendar->defaultReminders());
// Block email reminders, since Google sends them for us
auto blockAlarms = collection.attribute<BlockAlarmsAttribute>(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<CalendarFetchJob *>(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<Calendar>();
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<Collection>();
DefaultReminderAttribute *attr = collection.attribute<DefaultReminderAttribute>();
auto fetchJob = qobject_cast<EventFetchJob *>(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<Event>();
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<KCalendarCore::Event::Ptr>(event.dynamicCast<KCalendarCore::Event>());
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<KCalendarCore::Event::Ptr>()));
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<EventCreateJob *>(job)->items().first().dynamicCast<Event>();
qCDebug(GOOGLE_CALENDAR_LOG) << "Event added";
newItem.setRemoteId(event->id());
newItem.setRemoteRevision(event->etag());
newItem.setGid(event->uid());
m_iface->itemChangeCommitted(newItem);
newItem.setPayload<KCalendarCore::Event::Ptr>(event.dynamicCast<KCalendarCore::Event>());
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<KCalendarCore::Event::Ptr>()));
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<CalendarCreateJob *>(job)->items().first().dynamicCast<Calendar>();
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<FreeBusyQueryJob *>(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<FreeBusyQueryJob *>(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());
});
}
/*
Copyright (C) 2011-2013 Daniel Vrátil <dvratil@redhat.com>
2020 Igor Pobiko <igor.poboiko@gmail.com>
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
......@@ -15,68 +16,54 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GOOGLE_CALENDAR_CALENDARRESOURCE_H
#define GOOGLE_CALENDAR_CALENDARRESOURCE_H
#ifndef CALENDARHANDLER_H
#define CALENDARHANDLER_H
#include "common/googleresource.h"
#include "generichandler.h"
#include <QObject>
#include <QSharedPointer>
#include <AkonadiCore/Item>
#include <AkonadiCore/Collection>
#include <Akonadi/Calendar/FreeBusyProviderBase>
class CalendarResource : public GoogleResource, public Akonadi::FreeBusyProviderBase
class CalendarHandler : public GenericHandler
{
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;
using GenericHandler::GenericHandler;
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;
QString mimeType() override;
bool canPerformTask(const Akonadi::Item &item) override;
bool canPerformTask(const Akonadi::Item::List &items) override;
protected:
using ResourceBase::retrieveItems; // Suppress -Woverload-virtual
protected Q_SLOTS:
void retrieveCollections() 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 itemRemoved(const Akonadi::Item &item) override;
void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) override;
void itemsRemoved(const Akonadi::Item::List &items) override;
void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination) override;
void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override;
void collectionChanged(const Akonadi::Collection &collection) override;
void collectionRemoved(const Akonadi::Collection &collection) override;
private Q_SLOTS:
void slotItemsRetrieved(KGAPI2::Job *job);
void slotCollectionsRetrieved(KGAPI2::Job *job);
void slotCalendarsRetrieved(KGAPI2::Job *job);