calendarresource.cpp 30.7 KB
Newer Older
1
/*
2
    Copyright (C) 2011-2013  Daniel Vrátil <dvratil@redhat.com>
3

4
5
6
7
    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.
8

9
10
11
12
    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.
13

14
15
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17
18
19
20
21
22
*/

#include "calendarresource.h"
#include "defaultreminderattribute.h"
#include "settings.h"
#include "settingsdialog.h"

Laurent Montel's avatar
Laurent Montel committed
23
#include <AkonadiCore/Attribute>
Laurent Montel's avatar
Laurent Montel committed
24
25
26
27
28
29
#include <AkonadiCore/AttributeFactory>
#include <AkonadiCore/CollectionModifyJob>
#include <AkonadiCore/EntityDisplayAttribute>
#include <AkonadiCore/ItemFetchJob>
#include <AkonadiCore/ItemFetchScope>
#include <AkonadiCore/ItemModifyJob>
30
#include <Akonadi/Calendar/BlockAlarmsAttribute>
Laurent Montel's avatar
Laurent Montel committed
31
#include <AkonadiCore/CachePolicy>
32
#include <AkonadiCore/VectorHelper>
33

34
#include <KCalCore/Calendar>
35
36
37
#include <KCalCore/Attendee>
#include <KCalCore/ICalFormat>
#include <KCalCore/FreeBusy>
38

Laurent Montel's avatar
Laurent Montel committed
39
#include <KLocalizedString>
Laurent Montel's avatar
Laurent Montel committed
40
#include <QDialog>
Laurent Montel's avatar
Laurent Montel committed
41
#include <QIcon>
42

Laurent Montel's avatar
Laurent Montel committed
43
44
45
46
47
48
49
50
51
52
53
#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>
54
#include <KGAPI/Calendar/FreeBusyQueryJob>
Laurent Montel's avatar
Laurent Montel committed
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <KGAPI/Tasks/Task>
#include <KGAPI/Tasks/TaskCreateJob>
#include <KGAPI/Tasks/TaskDeleteJob>
#include <KGAPI/Tasks/TaskFetchJob>
#include <KGAPI/Tasks/TaskModifyJob>
#include <KGAPI/Tasks/TaskMoveJob>
#include <KGAPI/Tasks/TaskList>
#include <KGAPI/Tasks/TaskListCreateJob>
#include <KGAPI/Tasks/TaskListDeleteJob>
#include <KGAPI/Tasks/TaskListFetchJob>
#include <KGAPI/Tasks/TaskListModifyJob>

#include <KGAPI/Account>
68

69
#define ROOT_COLLECTION_REMOTEID QStringLiteral("RootCollection")
70
71
72
#define CALENDARS_PROPERTY "_KGAPI2CalendarPtr"
#define TASK_PROPERTY "_KGAPI2::TaskPtr"

Laurent Montel's avatar
Laurent Montel committed
73
74
Q_DECLARE_METATYPE(KGAPI2::ObjectsList)
Q_DECLARE_METATYPE(KGAPI2::TaskPtr)
75

76
using namespace Akonadi;
77
using namespace KGAPI2;
78

Laurent Montel's avatar
Laurent Montel committed
79
80
CalendarResource::CalendarResource(const QString &id):
    GoogleResource(id)
81
{
82
83
    AttributeFactory::registerAttribute< DefaultReminderAttribute >();
    updateResourceName();
84
85
86
87
88
89
}

CalendarResource::~CalendarResource()
{
}

90
GoogleSettings *CalendarResource::settings() const
91
{
92
    return Settings::self();
93
94
}

Laurent Montel's avatar
Laurent Montel committed
95
int CalendarResource::runConfigurationDialog(WId windowId)
96
{
Laurent Montel's avatar
Laurent Montel committed
97
    QScopedPointer<SettingsDialog> settingsDialog(new SettingsDialog(accountManager(), windowId, this));
Laurent Montel's avatar
Laurent Montel committed
98
    settingsDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("im-google")));
99

Laurent Montel's avatar
Laurent Montel committed
100
    return settingsDialog->exec();
101
102
}

103
void CalendarResource::updateResourceName()
104
{
105
    const QString accountName = Settings::self()->account();
Laurent Montel's avatar
Laurent Montel committed
106
    setName(i18nc("%1 is account name (user@gmail.com)", "Google Calendars and Tasks (%1)", accountName.isEmpty() ? i18n("not configured") : accountName));
107
108
}

109
QList< QUrl > CalendarResource::scopes() const
110
{
111
    QList<QUrl> scopes;
112
113
    scopes << Account::calendarScopeUrl()
           << Account::tasksScopeUrl();
114

115
    return scopes;
116
117
}

Laurent Montel's avatar
Laurent Montel committed
118
void CalendarResource::retrieveItems(const Akonadi::Collection &collection)
119
{
Laurent Montel's avatar
Laurent Montel committed
120
    if (!canPerformTask()) {
121
122
        return;
    }
123

124
125
    // https://bugs.kde.org/show_bug.cgi?id=308122: we can only request changes in
    // max. last 25 days, otherwise we get an error.
Daniel Vrátil's avatar
Daniel Vrátil committed
126
    int lastSyncDelta = -1;
Laurent Montel's avatar
Laurent Montel committed
127
    if (!collection.remoteRevision().isEmpty()) {
Daniel Vrátil's avatar
Daniel Vrátil committed
128
        lastSyncDelta = QDateTime::currentDateTimeUtc().toTime_t() - collection.remoteRevision().toUInt();
129
    }
130

Laurent Montel's avatar
Laurent Montel committed
131
    KGAPI2::Job *job = Q_NULLPTR;
Laurent Montel's avatar
Laurent Montel committed
132
133
134
135
    if (collection.contentMimeTypes().contains(KCalCore::Event::eventMimeType())) {
        EventFetchJob *fetchJob = new EventFetchJob(collection.remoteId(), account(), this);
        if (lastSyncDelta > -1 && lastSyncDelta < 25 * 24 * 3600) {
            fetchJob->setFetchOnlyUpdated(collection.remoteRevision().toULongLong());
136
        }
Laurent Montel's avatar
Laurent Montel committed
137
138
139
        if (!Settings::self()->eventsSince().isEmpty()) {
            const QDate date = QDate::fromString(Settings::self()->eventsSince(), Qt::ISODate);
            fetchJob->setTimeMin(QDateTime(date).toTime_t());
140
        }
141
        job = fetchJob;
Laurent Montel's avatar
Laurent Montel committed
142
143
144
145
    } else if (collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType())) {
        TaskFetchJob *fetchJob = new TaskFetchJob(collection.remoteId(), account(), this);
        if (lastSyncDelta > -1 && lastSyncDelta < 25 * 25 * 3600) {
            fetchJob->setFetchOnlyUpdated(collection.remoteRevision().toULongLong());
146
147
148
        }
        job = fetchJob;
    } else {
Laurent Montel's avatar
Laurent Montel committed
149
        itemsRetrieved(Item::List());
150
151
        return;
    }
152

Laurent Montel's avatar
Laurent Montel committed
153
    job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection));
Laurent Montel's avatar
Laurent Montel committed
154
155
    connect(job, &KGAPI2::Job::progress, this, &CalendarResource::emitPercent);
    connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotItemsRetrieved);
156
157
158
159
}

void CalendarResource::retrieveCollections()
{
Laurent Montel's avatar
Laurent Montel committed
160
    if (!canPerformTask()) {
161
162
163
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
164
    CalendarFetchJob *fetchJob = new CalendarFetchJob(account(), this);
Laurent Montel's avatar
Laurent Montel committed
165
    connect(fetchJob, &EventFetchJob::finished, this, &CalendarResource::slotCalendarsRetrieved);
166
167
}

Laurent Montel's avatar
Laurent Montel committed
168
void CalendarResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection)
169
{
Laurent Montel's avatar
Laurent Montel committed
170
171
172
173
    if ((!collection.contentMimeTypes().contains(KCalCore::Event::eventMimeType()) &&
            !collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType())) ||
            (!canPerformTask<KCalCore::Event::Ptr>(item, KCalCore::Event::eventMimeType()) &&
             !canPerformTask<KCalCore::Todo::Ptr>(item, KCalCore::Todo::todoMimeType()))) {
174
        return;
Allen Winter's avatar
Allen Winter committed
175
    }
176

Laurent Montel's avatar
Laurent Montel committed
177
178
    if (collection.parentCollection() == Akonadi::Collection::root()) {
        cancelTask(i18n("The top-level collection cannot contain any tasks or events"));
179
180
        return;
    }
181

Laurent Montel's avatar
Laurent Montel committed
182
    KGAPI2::Job *job = Q_NULLPTR;
Laurent Montel's avatar
Laurent Montel committed
183
    if (item.hasPayload<KCalCore::Event::Ptr>()) {
184
        KCalCore::Event::Ptr event = item.payload<KCalCore::Event::Ptr>();
Laurent Montel's avatar
Laurent Montel committed
185
186
        EventPtr kevent(new Event(*event));
        kevent->setUid(QLatin1String(""));
187

Laurent Montel's avatar
Laurent Montel committed
188
        job = new EventCreateJob(kevent, collection.remoteId(), account(),  this);
189

Laurent Montel's avatar
Laurent Montel committed
190
    } else if (item.hasPayload<KCalCore::Todo::Ptr>()) {
191
        KCalCore::Todo::Ptr todo = item.payload<KCalCore::Todo::Ptr>();
Laurent Montel's avatar
Laurent Montel committed
192
193
        TaskPtr ktodo(new Task(*todo));
        ktodo->setUid(QLatin1String(""));
194

Laurent Montel's avatar
Laurent Montel committed
195
        if (!ktodo->relatedTo(KCalCore::Incidence::RelTypeParent).isEmpty()) {
196
            Akonadi::Item parentItem;
Laurent Montel's avatar
Laurent Montel committed
197
            parentItem.setGid(ktodo->relatedTo(KCalCore::Incidence::RelTypeParent));
198

Laurent Montel's avatar
Laurent Montel committed
199
200
201
            ItemFetchJob *fetchJob = new ItemFetchJob(parentItem, this);
            fetchJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
            fetchJob->setProperty(TASK_PROPERTY, QVariant::fromValue(ktodo));
202

Laurent Montel's avatar
Laurent Montel committed
203
            connect(fetchJob, &ItemFetchJob::finished, this, &CalendarResource::slotTaskAddedSearchFinished);
204
205
            return;
        } else {
Laurent Montel's avatar
Laurent Montel committed
206
            job = new TaskCreateJob(ktodo, collection.remoteId(), account(), this);
207
208
        }
    } else {
Laurent Montel's avatar
Laurent Montel committed
209
        cancelTask(i18n("Invalid payload type"));
210
211
        return;
    }
212

Laurent Montel's avatar
Laurent Montel committed
213
    job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
214
    connect(job, &EventCreateJob::finished, this, &CalendarResource::slotCreateJobFinished);
215
216
}

Laurent Montel's avatar
Laurent Montel committed
217
218
void CalendarResource::itemChanged(const Akonadi::Item &item,
                                   const QSet< QByteArray > &partIdentifiers)
219
{
Laurent Montel's avatar
Laurent Montel committed
220
    Q_UNUSED(partIdentifiers);
221

Laurent Montel's avatar
Laurent Montel committed
222
223
    if (!canPerformTask<KCalCore::Event::Ptr>(item, KCalCore::Event::eventMimeType()) &&
            !canPerformTask<KCalCore::Todo::Ptr>(item, KCalCore::Todo::todoMimeType())) {
224
225
        return;
    }
226

Laurent Montel's avatar
Laurent Montel committed
227
    KGAPI2::Job *job = Q_NULLPTR;
Laurent Montel's avatar
Laurent Montel committed
228
    if (item.hasPayload<KCalCore::Event::Ptr>()) {
229
        KCalCore::Event::Ptr event = item.payload<KCalCore::Event::Ptr>();
Laurent Montel's avatar
Laurent Montel committed
230
231
        EventPtr kevent(new Event(*event));
        kevent->setUid(item.remoteId());
232

Laurent Montel's avatar
Laurent Montel committed
233
        job = new EventModifyJob(kevent, item.parentCollection().remoteId(), account(), this);
Laurent Montel's avatar
Laurent Montel committed
234
        connect(job, &EventCreateJob::finished, this, &CalendarResource::slotGenericJobFinished);
235

Laurent Montel's avatar
Laurent Montel committed
236
    } else if (item.hasPayload<KCalCore::Todo::Ptr>()) {
237
        KCalCore::Todo::Ptr todo = item.payload<KCalCore::Todo::Ptr>();
Laurent Montel's avatar
Laurent Montel committed
238
239
240
241
        TaskPtr ktodo(new Task(*todo));
        QString parentUid = todo->relatedTo(KCalCore::Incidence::RelTypeParent);
        job = new TaskMoveJob(item.remoteId(), item.parentCollection().remoteId(), parentUid, account(), this);
        job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
242
        connect(job, &EventCreateJob::finished, this, &CalendarResource::slotModifyTaskReparentFinished);
243
    } else {
Laurent Montel's avatar
Laurent Montel committed
244
        cancelTask(i18n("Invalid payload type"));
245
246
        return;
    }
247

Laurent Montel's avatar
Laurent Montel committed
248
    job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
249
250
}

Laurent Montel's avatar
Laurent Montel committed
251
void CalendarResource::itemRemoved(const Akonadi::Item &item)
252
{
Laurent Montel's avatar
Laurent Montel committed
253
    if (!canPerformTask()) {
254
255
        return;
    }
256

Laurent Montel's avatar
Laurent Montel committed
257
258
259
    if (item.mimeType() == KCalCore::Event::eventMimeType()) {
        KGAPI2::Job *job = new EventDeleteJob(item.remoteId(), item.parentCollection().remoteId(), account(), this);
        job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
260
        connect(job, &EventCreateJob::finished, this, &CalendarResource::slotGenericJobFinished);
261

Laurent Montel's avatar
Laurent Montel committed
262
    } else if (item.mimeType() == KCalCore::Todo::todoMimeType()) {
263
264
265
266
        /* 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. */
Laurent Montel's avatar
Laurent Montel committed
267
268
269
270
        ItemFetchJob *fetchJob = new ItemFetchJob(item.parentCollection());
        fetchJob->setAutoDelete(true);
        fetchJob->fetchScope().fetchFullPayload(true);
        fetchJob->setProperty(ITEM_PROPERTY, qVariantFromValue(item));
Laurent Montel's avatar
Laurent Montel committed
271
        connect(fetchJob, &ItemFetchJob::finished, this, &CalendarResource::slotRemoveTaskFetchJobFinished);
272
273
274
        fetchJob->start();

    } else {
Laurent Montel's avatar
Laurent Montel committed
275
        cancelTask(i18n("Invalid payload type. Expected event or todo, got %1", item.mimeType()));
276
    }
277
278
}

Laurent Montel's avatar
Laurent Montel committed
279
280
281
void CalendarResource::itemMoved(const Item &item,
                                 const Collection &collectionSource,
                                 const Collection &collectionDestination)
282
{
Laurent Montel's avatar
Laurent Montel committed
283
    if (!canPerformTask()) {
284
285
        return;
    }
286

Laurent Montel's avatar
Laurent Montel committed
287
288
    if (collectionDestination.parentCollection() == Akonadi::Collection::root()) {
        cancelTask(i18n("The top-level collection cannot contain any tasks or events"));
289
290
        return;
    }
291

Laurent Montel's avatar
Laurent Montel committed
292
293
    if (item.mimeType() != KCalCore::Event::eventMimeType()) {
        cancelTask(i18n("Moving tasks between task lists is not supported"));
294
295
        return;
    }
296

Laurent Montel's avatar
Laurent Montel committed
297
298
299
300
    KGAPI2::Job *job = new EventMoveJob(item.remoteId(), collectionSource.remoteId(),
                                        collectionDestination.remoteId(), account(),
                                        this);
    job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
301
    connect(job, &EventCreateJob::finished, this, &CalendarResource::slotGenericJobFinished);
302
}
303

Laurent Montel's avatar
Laurent Montel committed
304
void CalendarResource::collectionAdded(const Collection &collection, const Collection &parent)
305
{
Laurent Montel's avatar
Laurent Montel committed
306
    Q_UNUSED(parent)
307

Laurent Montel's avatar
Laurent Montel committed
308
    if (!canPerformTask()) {
309
310
        return;
    }
311

312
    KGAPI2::Job *job;
Laurent Montel's avatar
Laurent Montel committed
313
314
315
316
317
    if (collection.contentMimeTypes().contains(KCalCore::Event::eventMimeType())) {
        CalendarPtr calendar(new Calendar());
        calendar->setTitle(collection.displayName());
        calendar->setEditable(true);
        job = new CalendarCreateJob(calendar, account(), this);
318

Laurent Montel's avatar
Laurent Montel committed
319
320
321
    } if (collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType())) {
        TaskListPtr taskList(new TaskList());
        taskList->setTitle(collection.displayName());
322

Laurent Montel's avatar
Laurent Montel committed
323
        job = new TaskListCreateJob(taskList, account(), this);
324
    } else {
Laurent Montel's avatar
Laurent Montel committed
325
        cancelTask(i18n("Unknown collection mimetype"));
326
327
        return;
    }
328

Laurent Montel's avatar
Laurent Montel committed
329
    job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection));
Laurent Montel's avatar
Laurent Montel committed
330
    connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished);
331
332
}

Laurent Montel's avatar
Laurent Montel committed
333
void CalendarResource::collectionChanged(const Collection &collection)
334
{
Laurent Montel's avatar
Laurent Montel committed
335
    if (!canPerformTask()) {
336
337
        return;
    }
338

339
    KGAPI2::Job *job;
Laurent Montel's avatar
Laurent Montel committed
340
341
342
343
344
345
346
347
348
349
350
351
    if (collection.contentMimeTypes().contains(KCalCore::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(KCalCore::Todo::todoMimeType())) {
        TaskListPtr taskList(new TaskList());
        taskList->setUid(collection.remoteId());
        taskList->setTitle(collection.displayName());
        job = new TaskListModifyJob(taskList, account(), this);
352
    } else {
Laurent Montel's avatar
Laurent Montel committed
353
        cancelTask(i18n("Unknown collection mimetype"));
354
355
        return;
    }
356

Laurent Montel's avatar
Laurent Montel committed
357
    job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection));
Laurent Montel's avatar
Laurent Montel committed
358
    connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished);
359
}
360

Laurent Montel's avatar
Laurent Montel committed
361
void CalendarResource::collectionRemoved(const Collection &collection)
362
{
Laurent Montel's avatar
Laurent Montel committed
363
    if (!canPerformTask()) {
364
365
        return;
    }
366

367
    KGAPI2::Job *job;
Laurent Montel's avatar
Laurent Montel committed
368
369
    if (collection.contentMimeTypes().contains(KCalCore::Event::eventMimeType())) {
        job = new CalendarDeleteJob(collection.remoteId(), account(), this);
370

Laurent Montel's avatar
Laurent Montel committed
371
372
    } if (collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType())) {
        job = new TaskListDeleteJob(collection.remoteId(), account(), this);
373

374
    } else {
Laurent Montel's avatar
Laurent Montel committed
375
        cancelTask(i18n("Unknown collection mimetype"));
376
377
        return;
    }
378

Laurent Montel's avatar
Laurent Montel committed
379
    job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection));
Laurent Montel's avatar
Laurent Montel committed
380
    connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished);
381
382
}

Laurent Montel's avatar
Laurent Montel committed
383
void CalendarResource::slotCalendarsRetrieved(KGAPI2::Job *job)
384
{
Laurent Montel's avatar
Laurent Montel committed
385
    if (!handleError(job)) {
386
387
        return;
    }
388

Laurent Montel's avatar
Laurent Montel committed
389
    CalendarFetchJob *calendarJob = qobject_cast<CalendarFetchJob *>(job);
390
391
    ObjectsList objects = calendarJob->items();
    calendarJob->deleteLater();
392

Laurent Montel's avatar
Laurent Montel committed
393
394
    TaskListFetchJob *fetchJob = new TaskListFetchJob(job->account(), this);
    fetchJob->setProperty(CALENDARS_PROPERTY, QVariant::fromValue(objects));
Laurent Montel's avatar
Laurent Montel committed
395
    connect(fetchJob, &EventFetchJob::finished, this, &CalendarResource::slotCollectionsRetrieved);
396
}
397

Laurent Montel's avatar
Laurent Montel committed
398
void CalendarResource::slotCollectionsRetrieved(KGAPI2::Job *job)
399
{
Laurent Montel's avatar
Laurent Montel committed
400
    if (!handleError(job)) {
401
402
        return;
    }
403

Laurent Montel's avatar
Laurent Montel committed
404
405
    TaskListFetchJob *fetchJob = qobject_cast<TaskListFetchJob *>(job);
    ObjectsList calendars = fetchJob->property(CALENDARS_PROPERTY).value<ObjectsList>();
406
407
    ObjectsList taskLists = fetchJob->items();

408
    CachePolicy cachePolicy;
Laurent Montel's avatar
Laurent Montel committed
409
410
411
    if (Settings::self()->enableIntervalCheck()) {
        cachePolicy.setInheritFromParent(false);
        cachePolicy.setIntervalCheckTime(Settings::self()->intervalCheckTime());
412
413
    }

414
    m_rootCollection = Collection();
Laurent Montel's avatar
Laurent Montel committed
415
416
417
418
419
420
    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);
421

422
    EntityDisplayAttribute *attr = m_rootCollection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
Laurent Montel's avatar
Laurent Montel committed
423
    attr->setDisplayName(fetchJob->account()->accountName());
Laurent Montel's avatar
Laurent Montel committed
424
    attr->setIconName(QStringLiteral("im-google"));
425
426
427
428

    m_collections[ ROOT_COLLECTION_REMOTEID ] = m_rootCollection;

    const QStringList activeCalendars = Settings::self()->calendars();
Laurent Montel's avatar
Laurent Montel committed
429
    Q_FOREACH (const ObjectPtr &object, calendars) {
430
431
        const CalendarPtr &calendar = object.dynamicCast<Calendar>();

Laurent Montel's avatar
Laurent Montel committed
432
        if (!activeCalendars.contains(calendar->uid())) {
433
434
435
436
            continue;
        }

        Collection collection;
Laurent Montel's avatar
Laurent Montel committed
437
438
439
440
441
442
443
444
445
        collection.setContentMimeTypes(QStringList() << KCalCore::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);
446
        } else {
Laurent Montel's avatar
Laurent Montel committed
447
            collection.setRights(Q_NULLPTR);
448
449
        }

450
        EntityDisplayAttribute *attr = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
Laurent Montel's avatar
Laurent Montel committed
451
        attr->setDisplayName(calendar->title());
452
        attr->setIconName(QStringLiteral("view-calendar"));
453

454
        DefaultReminderAttribute *reminderAttr = collection.attribute<DefaultReminderAttribute>(Collection::AddIfMissing);
Laurent Montel's avatar
Laurent Montel committed
455
        reminderAttr->setReminders(calendar->defaultReminders());
456

457
        // Block email reminders, since Google sends them for us
458
        BlockAlarmsAttribute *blockAlarms = collection.attribute<BlockAlarmsAttribute>(Collection::AddIfMissing);
Laurent Montel's avatar
Laurent Montel committed
459
460
461
        blockAlarms->blockAlarmType(KCalCore::Alarm::Audio, false);
        blockAlarms->blockAlarmType(KCalCore::Alarm::Display, false);
        blockAlarms->blockAlarmType(KCalCore::Alarm::Procedure, false);
462

463
464
        m_collections[ collection.remoteId() ] = collection;
    }
465

466
    const QStringList activeTaskLists = Settings::self()->taskLists();
Laurent Montel's avatar
Laurent Montel committed
467
    Q_FOREACH (const ObjectPtr &object, taskLists) {
468
469
        const TaskListPtr &taskList = object.dynamicCast<TaskList>();

Laurent Montel's avatar
Laurent Montel committed
470
        if (!activeTaskLists.contains(taskList->uid())) {
471
472
473
474
            continue;
        }

        Collection collection;
Laurent Montel's avatar
Laurent Montel committed
475
476
477
478
479
480
481
482
483
        collection.setContentMimeTypes(QStringList() << KCalCore::Todo::todoMimeType());
        collection.setName(taskList->uid());
        collection.setParentCollection(m_rootCollection);
        collection.setRemoteId(taskList->uid());
        collection.setRights(Collection::CanChangeCollection |
                             Collection::CanCreateItem |
                             Collection::CanChangeItem |
                             Collection::CanDeleteItem);

484
        EntityDisplayAttribute *attr = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
Laurent Montel's avatar
Laurent Montel committed
485
        attr->setDisplayName(taskList->title());
Laurent Montel's avatar
Laurent Montel committed
486
        attr->setIconName(QStringLiteral("view-pim-tasks"));
487
488
489

        m_collections[ collection.remoteId() ] = collection;
    }
490

491
    collectionsRetrieved(Akonadi::valuesToVector(m_collections));
492

493
    job->deleteLater();
494
495
}

Laurent Montel's avatar
Laurent Montel committed
496
void CalendarResource::slotItemsRetrieved(KGAPI2::Job *job)
497
{
Laurent Montel's avatar
Laurent Montel committed
498
    if (!handleError(job)) {
499
500
        return;
    }
501

502
    Item::List changedItems, removedItems;
Laurent Montel's avatar
Laurent Montel committed
503
    Collection collection = job->property(COLLECTION_PROPERTY).value<Collection>();
504
    DefaultReminderAttribute *attr = collection.attribute<DefaultReminderAttribute>();
Daniel Vrátil's avatar
Daniel Vrátil committed
505
    bool isIncremental = false;
506

Laurent Montel's avatar
Laurent Montel committed
507
508
    ObjectsList objects = qobject_cast<FetchJob *>(job)->items();
    if (collection.contentMimeTypes().contains(KCalCore::Event::eventMimeType())) {
509
510
        QMap< QString, EventPtr > recurrentEvents;

Laurent Montel's avatar
Laurent Montel committed
511
        isIncremental = (qobject_cast<EventFetchJob *>(job)->fetchOnlyUpdated() > 0);
Daniel Vrátil's avatar
Daniel Vrátil committed
512

513
514
515
516
        /* Step 1: Find all recurrent events and move them to a separate map */
        int i = 0;
        while (i < objects.length()) {
            EventPtr event = objects.at(i).dynamicCast<Event>();
Laurent Montel's avatar
Laurent Montel committed
517
518
            if (event->recurs() && !event->deleted()) {
                recurrentEvents.insert(event->uid(), event);
519
520
521
522
523
524
525
                objects.removeAt(i);
            } else {
                i++;
            }
        }

        /* Step 2: Process all remaining events */
Laurent Montel's avatar
Laurent Montel committed
526
        Q_FOREACH (const ObjectPtr &object, objects) {
527
528
529
530
531
            EventPtr event = object.dynamicCast<Event>();

            /* If current event is related to a recurrent event stored in the map then
             * take the original recurrent event, set date of the current event as an
             * exception and continue. We will process content of the map later. */
Laurent Montel's avatar
Laurent Montel committed
532
533
534
            if (recurrentEvents.contains(event->uid())) {
                EventPtr rEvent = recurrentEvents.value(event->uid());
                rEvent->recurrence()->addExDate(event->dtStart().date());
Daniel Vrátil's avatar
Daniel Vrátil committed
535
536
537
                continue;
            }

Laurent Montel's avatar
Laurent Montel committed
538
539
            if (event->useDefaultReminders() && attr) {
                KCalCore::Alarm::List alarms = attr->alarms(event.data());
540
                Q_FOREACH (const KCalCore::Alarm::Ptr &alarm, alarms) {
Laurent Montel's avatar
Laurent Montel committed
541
                    event->addAlarm(alarm);
Daniel Vrátil's avatar
Daniel Vrátil committed
542
                }
543
544
545
            }

            Item item;
Laurent Montel's avatar
Laurent Montel committed
546
547
548
549
550
            item.setMimeType(KCalCore::Event::eventMimeType());
            item.setParentCollection(collection);
            item.setRemoteId(event->uid());
            item.setRemoteRevision(event->etag());
            item.setPayload<KCalCore::Event::Ptr>(event.dynamicCast<KCalCore::Event>());
551

Laurent Montel's avatar
Laurent Montel committed
552
            if (event->deleted()) {
553
554
555
556
557
558
559
                removedItems << item;
            } else {
                changedItems << item;
            }
        }

        /* Step 3: Now process the recurrent events */
560
        Q_FOREACH (const EventPtr &event, recurrentEvents) {
561
562

            Item item;
Laurent Montel's avatar
Laurent Montel committed
563
564
565
566
567
            item.setRemoteId(event->uid());
            item.setRemoteRevision(event->etag());
            item.setPayload< KCalCore::Event::Ptr >(event.dynamicCast<KCalCore::Event>());
            item.setMimeType(KCalCore::Event::eventMimeType());
            item.setParentCollection(collection);
568
569
570
571

            changedItems << item;
        }

Laurent Montel's avatar
Laurent Montel committed
572
    } else if (collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType())) {
Daniel Vrátil's avatar
Daniel Vrátil committed
573

Laurent Montel's avatar
Laurent Montel committed
574
        isIncremental = (qobject_cast<TaskFetchJob *>(job)->fetchOnlyUpdated() > 0);
Daniel Vrátil's avatar
Daniel Vrátil committed
575

Laurent Montel's avatar
Laurent Montel committed
576
        Q_FOREACH (const ObjectPtr &object, objects) {
577
578
579
            TaskPtr task = object.dynamicCast<Task>();

            Item item;
Laurent Montel's avatar
Laurent Montel committed
580
581
582
583
584
            item.setMimeType(KCalCore::Todo::todoMimeType());
            item.setParentCollection(collection);
            item.setRemoteId(task->uid());
            item.setRemoteRevision(task->etag());
            item.setPayload<KCalCore::Todo::Ptr>(task.dynamicCast<KCalCore::Todo>());
585

Laurent Montel's avatar
Laurent Montel committed
586
            if (task->deleted()) {
587
588
589
590
591
592
                removedItems << item;
            } else {
                changedItems << item;
            }
        }
    }
593

Laurent Montel's avatar
Laurent Montel committed
594
595
    if (isIncremental) {
        itemsRetrievedIncremental(changedItems, removedItems);
Daniel Vrátil's avatar
Daniel Vrátil committed
596
    } else {
Laurent Montel's avatar
Laurent Montel committed
597
        itemsRetrieved(changedItems);
Daniel Vrátil's avatar
Daniel Vrátil committed
598
    }
Laurent Montel's avatar
Laurent Montel committed
599
600
    const QDateTime local(QDateTime::currentDateTime());
    const QDateTime UTC(local.toUTC());
601

Laurent Montel's avatar
Laurent Montel committed
602
    collection.setRemoteRevision(QString::number(UTC.toTime_t()));
Laurent Montel's avatar
Laurent Montel committed
603
    new CollectionModifyJob(collection, this);
604

605
606
    job->deleteLater();
}
607

Laurent Montel's avatar
Laurent Montel committed
608
void CalendarResource::slotModifyTaskReparentFinished(KGAPI2::Job *job)
609
{
Laurent Montel's avatar
Laurent Montel committed
610
    if (!handleError(job)) {
611
612
        return;
    }
613

Laurent Montel's avatar
Laurent Montel committed
614
    Item item = job->property(ITEM_PROPERTY).value<Item>();
615
    KCalCore::Todo::Ptr todo = item.payload<KCalCore::Todo::Ptr>();
Laurent Montel's avatar
Laurent Montel committed
616
    TaskPtr ktodo(new Task(*todo.data()));
617

Laurent Montel's avatar
Laurent Montel committed
618
619
    job = new TaskModifyJob(ktodo, item.parentCollection().remoteId(), job->account(), this);
    job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
620
    connect(job, &KGAPI2::Job::finished, this, &CalendarResource::slotGenericJobFinished);
621
622
}

Laurent Montel's avatar
Laurent Montel committed
623
void CalendarResource::slotRemoveTaskFetchJobFinished(KJob *job)
624
{
Laurent Montel's avatar
Laurent Montel committed
625
626
    if (job->error()) {
        cancelTask(i18n("Failed to delete task: %1", job->errorString()));
627
628
        return;
    }
629

Laurent Montel's avatar
Laurent Montel committed
630
631
    ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
    Item removedItem = fetchJob->property(ITEM_PROPERTY).value<Item>();
632
633
634
635

    Item::List detachItems;

    Item::List items = fetchJob->items();
Laurent Montel's avatar
Laurent Montel committed
636
637
    Q_FOREACH (Item item, items) {   //krazy:exclude=foreach
        if (!item.hasPayload<KCalCore::Todo::Ptr>()) {
Laurent Montel's avatar
Laurent Montel committed
638
            qDebug() << "Item " << item.remoteId() << " does not have Todo payload";
639
640
641
642
643
            continue;
        }

        KCalCore::Todo::Ptr todo = item.payload<KCalCore::Todo::Ptr>();
        /* If this item is child of the item we want to remove then add it to detach list */
Laurent Montel's avatar
Laurent Montel committed
644
645
646
        if (todo->relatedTo(KCalCore::Incidence::RelTypeParent) == removedItem.remoteId()) {
            todo->setRelatedTo(QString(), KCalCore::Incidence::RelTypeParent);
            item.setPayload(todo);
647
648
649
            detachItems << item;
        }
    }
650

651
    /* If there are no items do detach, then delete the task right now */
Laurent Montel's avatar
Laurent Montel committed
652
653
    if (detachItems.isEmpty()) {
        slotDoRemoveTask(job);
654
655
        return;
    }
656

657
658
    /* Send modify request to detach all the sub-tasks from the task that is about to be
     * removed. */
Laurent Montel's avatar
Laurent Montel committed
659
660
661
    ItemModifyJob *modifyJob = new ItemModifyJob(detachItems);
    modifyJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(removedItem));
    modifyJob->setAutoDelete(true);
Laurent Montel's avatar
Laurent Montel committed
662
    connect(modifyJob, &ItemModifyJob::finished, this, &CalendarResource::slotDoRemoveTask);
663
}
664

Laurent Montel's avatar
Laurent Montel committed
665
void CalendarResource::slotDoRemoveTask(KJob *job)
666
{
Laurent Montel's avatar
Laurent Montel committed
667
668
    if (job->error()) {
        cancelTask(i18n("Failed to delete task: %1", job->errorString()));
669
670
        return;
    }
671

672
    // Make sure account is still valid
Laurent Montel's avatar
Laurent Montel committed
673
    if (!canPerformTask()) {
674
675
676
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
677
    Item item = job->property(ITEM_PROPERTY).value< Item >();
678

679
    /* Now finally we can safely remove the task we wanted to */
Laurent Montel's avatar
Laurent Montel committed
680
681
    TaskDeleteJob *deleteJob = new TaskDeleteJob(item.remoteId(), item.parentCollection().remoteId(), account(), this);
    deleteJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
682
    connect(deleteJob, &TaskDeleteJob::finished, this, &CalendarResource::slotGenericJobFinished);
683
684
}

Laurent Montel's avatar
Laurent Montel committed
685
void CalendarResource::slotTaskAddedSearchFinished(KJob *job)
686
{
Laurent Montel's avatar
Laurent Montel committed
687
688
689
    ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
    Item item = job->property(ITEM_PROPERTY).value<Item>();
    TaskPtr task = job->property(TASK_PROPERTY).value<TaskPtr>();
690

691
    Item::List items = fetchJob->items();
Laurent Montel's avatar
Laurent Montel committed
692
    qDebug() << "Parent query returned" << items.count() << "results";
693
694
695

    const QString tasksListId = item.parentCollection().remoteId();

696
    // Make sure account is still valid
Laurent Montel's avatar
Laurent Montel committed
697
    if (!canPerformTask()) {
698
699
700
        return;
    }

701
    KGAPI2::Job *newJob;
702
    // The parent is not known, so give up and just store the item in Google
703
    // without the information about parent.
Laurent Montel's avatar
Laurent Montel committed
704
    if (items.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
705
706
        task->setRelatedTo(QString(), KCalCore::Incidence::RelTypeParent);
        newJob = new TaskCreateJob(task, tasksListId, account(), this);
707
    } else {
708
        Item matchedItem = items.first();
709

Laurent Montel's avatar
Laurent Montel committed
710
711
712
        task->setRelatedTo(matchedItem.remoteId(), KCalCore::Incidence::RelTypeParent);
        TaskCreateJob *createJob = new TaskCreateJob(task, tasksListId, account(), this);
        createJob->setParentItem(matchedItem.remoteId());
713
714
        newJob = createJob;
    }
715

Laurent Montel's avatar
Laurent Montel committed
716
    newJob->setProperty(ITEM_PROPERTY, QVariant::fromValue(item));
Laurent Montel's avatar
Laurent Montel committed
717
    connect(newJob, &KGAPI2::Job::finished, this, &CalendarResource::slotCreateJobFinished);
718
}
719

Laurent Montel's avatar
Laurent Montel committed
720
void CalendarResource::slotCreateJobFinished(KGAPI2::Job *job)
721
{
Laurent Montel's avatar
Laurent Montel committed
722
    if (!handleError(job)) {
723
724
        return;
    }
725

Laurent Montel's avatar
Laurent Montel committed
726
    Item item = job->property(ITEM_PROPERTY).value<Item>();
727

Laurent Montel's avatar
Laurent Montel committed
728
    CreateJob *createJob = qobject_cast<CreateJob *>(job);
729
    ObjectsList objects = createJob->items();
Laurent Montel's avatar
Laurent Montel committed
730
    Q_ASSERT(objects.count() > 0);
731

Laurent Montel's avatar
Laurent Montel committed
732
    if (item.mimeType() == KCalCore::Event::eventMimeType()) {
733
        EventPtr event = objects.first().dynamicCast<Event>();
Laurent Montel's avatar
Laurent Montel committed
734
735
736
737
738
739
740
        item.setRemoteId(event->uid());
        item.setRemoteRevision(event->etag());
        item.setGid(event->uid());
        changeCommitted(item);
        item.setPayload<KCalCore::Event::Ptr>(event.dynamicCast<KCalCore::Event>());
        new ItemModifyJob(item, this);
    } else if (item.mimeType() == KCalCore::Todo::todoMimeType()) {
741
        TaskPtr task = objects.first().dynamicCast<Task>();
Laurent Montel's avatar
Laurent Montel committed
742
743
744
745
746
747
        item.setRemoteId(task->uid());
        item.setRemoteRevision(task->etag());
        item.setGid(task->uid());
        changeCommitted(item);
        item.setPayload<KCalCore::Todo::Ptr>(task.dynamicCast<KCalCore::Todo>());
        new ItemModifyJob(item, this);
748
    }
749
750
}

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
KDateTime CalendarResource::lastCacheUpdate() const
{
    return KDateTime();
}

void CalendarResource::canHandleFreeBusy(const QString &email) const
{
    if (!const_cast<CalendarResource *>(this)->canPerformTask()) {
        handlesFreeBusy(email, false);
        return;
    }

    auto job = new KGAPI2::FreeBusyQueryJob(email,
                                            QDateTime::currentDateTimeUtc(),
                                            QDateTime::currentDateTimeUtc().addSecs(3600),
Laurent Montel's avatar
Laurent Montel committed
766
767
                                            const_cast<CalendarResource *>(this)->account(),
                                            const_cast<CalendarResource *>(this));
768
769
770
771
772
773
    connect(job, &KGAPI2::Job::finished,
            this, &CalendarResource::slotCanHandleFreeBusyJobFinished);
}

void CalendarResource::slotCanHandleFreeBusyJobFinished(KGAPI2::Job *job)
{
Laurent Montel's avatar
Laurent Montel committed
774
    auto queryJob = qobject_cast<KGAPI2::FreeBusyQueryJob *>(job);
775
776
777
778
779
780
781
782
783
784
785
786

    if (!handleError(job, false)) {
        handlesFreeBusy(queryJob->id(), false);
        return;
    }

    handlesFreeBusy(queryJob->id(), true);
}

void CalendarResource::retrieveFreeBusy(const QString &email, const KDateTime &start,
                                        const KDateTime &end)
{
Laurent Montel's avatar
Laurent Montel committed
787
    if (!const_cast<CalendarResource *>(this)->canPerformTask()) {
788
789
790
791
792
        freeBusyRetrieved(email, QString(), false, QString());
        return;
    }

    auto job = new KGAPI2::FreeBusyQueryJob(email, start.dateTime(), end.dateTime(),
Laurent Montel's avatar
Laurent Montel committed
793
                                            account(), this);
794
795
796
797
798
799
    connect(job, &KGAPI2::Job::finished,
            this, &CalendarResource::slotRetrieveFreeBusyJobFinished);
}

void CalendarResource::slotRetrieveFreeBusyJobFinished(KGAPI2::Job *job)
{
Laurent Montel's avatar
Laurent Montel committed
800
    auto queryJob = qobject_cast<KGAPI2::FreeBusyQueryJob *>(job);
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823

    if (!handleError(job, false)) {
        freeBusyRetrieved(queryJob->id(), QString(), false, QString());
        return;
    }

    KCalCore::FreeBusy::Ptr fb(new KCalCore::FreeBusy);
    fb->setUid(QStringLiteral("%1%2@google.com").arg(QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMddTHHmmssZ"))));
    fb->setOrganizer(account()->accountName());
    fb->addAttendee(KCalCore::Attendee::Ptr(new KCalCore::Attendee(QString(), queryJob->id())));
    // FIXME: is it really sort?
    fb->setDateTime(KDateTime::currentUtcDateTime(), KCalCore::IncidenceBase::RoleSort);

    Q_FOREACH (const KGAPI2::FreeBusyQueryJob::BusyRange &range, queryJob->busy()) {
        fb->addPeriod(KDateTime(range.busyStart), KDateTime(range.busyEnd));
    }

    KCalCore::ICalFormat format;
    const QString fbStr = format.createScheduleMessage(fb, KCalCore::iTIPRequest);

    freeBusyRetrieved(queryJob->id(), fbStr, true, QString());
}

Laurent Montel's avatar
Laurent Montel committed
824
AKONADI_RESOURCE_MAIN(CalendarResource)