calendartaskbasehandler.cpp 14.6 KB
Newer Older
1
/*
2 3 4
 * SPDX-FileCopyrightText: 2020 Shashwat Jolly <shashwat.jolly@gmail.com>
 * 
 * SPDX-License-Identifier: GPL-2.0-or-later
5 6 7 8 9 10 11
 */

#include "calendartaskbasehandler.h"

#include <AkonadiCore/AttributeFactory>
#include <AkonadiCore/CollectionColorAttribute>
#include <AkonadiCore/CollectionModifyJob>
12
#include <AkonadiCore/ItemModifyJob>
13
#include <KCalendarCore/FileStorage>
14 15
#include <KCalendarCore/ICalFormat>
#include <KCalendarCore/MemoryCalendar>
16
#include <KLocalizedString>
17 18 19 20 21 22 23 24 25
#include <QFile>

#include "entriesfetchjob.h"
#include "etesync_debug.h"
#include "etesyncresource.h"

using namespace Akonadi;
using namespace KCalendarCore;

26
CalendarTaskBaseHandler::CalendarTaskBaseHandler(EteSyncResource *resource) : BaseHandler(resource)
27
{
28
    initialiseBaseDirectory();
29
    AttributeFactory::registerAttribute<CollectionColorAttribute>();
30 31
}

32
void CalendarTaskBaseHandler::getItemListFromEntries(std::vector<EteSyncEntryPtr> &entries, Item::List &changedItems, Item::List &removedItems, Collection &collection, const QString &journalUid, QString &prevUid)
33
{
34
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
35
    if (!journal) {
36
        qCDebug(ETESYNC_LOG) << "SetupItems: Could not get journal" << journalUid;
37 38 39
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }
40
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
41 42 43

    QMap<QString, KCalendarCore::Incidence::Ptr> incidences;

44
    for (auto &entry : entries) {
45 46
        if (!entry) {
            qCDebug(ETESYNC_LOG) << "SetupItems: Entry is null";
47
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
48 49
            continue;
        }
50
        EteSyncSyncEntryPtr syncEntry = etesync_entry_get_sync_entry(entry.get(), cryptoManager.get(), prevUid);
51

52 53
        if (!syncEntry) {
            qCDebug(ETESYNC_LOG) << "SetupItems: syncEntry is null for entry" << etesync_entry_get_uid(entry.get());
54
            qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
55
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
56 57 58
            continue;
        }

59 60 61
        CharPtr contentStr(etesync_sync_entry_get_content(syncEntry.get()));

        KCalendarCore::ICalFormat format;
Shashwat Jolly's avatar
Shashwat Jolly committed
62
        const KCalendarCore::Incidence::Ptr incidence = format.fromString(QStringFromCharPtr(contentStr));
63

64
        if (!incidence || (incidence->uid()).isEmpty()) {
65
            qCDebug(ETESYNC_LOG) << "Couldn't parse entry with uid" << etesync_entry_get_uid(entry.get());
66
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
67 68 69
            continue;
        }

70 71
        qCDebug(ETESYNC_LOG) << "Entry parsed into incidence - UID" << incidence->uid();

72 73 74 75 76 77 78 79 80 81 82
        const QString action = QStringFromCharPtr(CharPtr(etesync_sync_entry_get_action(syncEntry.get())));
        if (action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_ADD) || action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_CHANGE)) {
            incidences[incidence->uid()] = incidence;
        } else if (action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_DELETE)) {
            if (incidences.contains(incidence->uid())) {
                incidences.remove(incidence->uid());
            } else {
                Item item;
                item.setMimeType(mimeType());
                item.setParentCollection(collection);
                item.setRemoteId(incidence->uid());
83
                removedItems.push_back(item);
84

85
                deleteLocalCalendar(incidence->uid());
86 87 88 89 90 91 92 93 94 95 96 97
            }
        }

        prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
    }

    for (auto it = incidences.constBegin(); it != incidences.constEnd(); it++) {
        Item item;
        item.setMimeType(mimeType());
        item.setParentCollection(collection);
        item.setRemoteId(it.key());
        item.setPayload<KCalendarCore::Incidence::Ptr>(it.value());
98
        changedItems.push_back(item);
99 100 101 102 103 104 105 106 107 108 109 110 111 112

        updateLocalCalendar(it.value());
    }
}

QString CalendarTaskBaseHandler::baseDirectoryPath() const
{
    return mResource->baseDirectoryPath() + QStringLiteral("/Calendar");
}

QString CalendarTaskBaseHandler::getLocalCalendar(const QString &incidenceUid) const
{
    const QString path = baseDirectoryPath() + QLatin1Char('/') + incidenceUid + QLatin1String(".ical");

113 114 115 116
    const auto calendar = Calendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
    const auto fileStorage = FileStorage::Ptr(new FileStorage(calendar, path, new ICalFormat()));

    if (!fileStorage->load()) {
Shashwat Jolly's avatar
Shashwat Jolly committed
117
        qCDebug(ETESYNC_LOG) << "Unable to read" << path;
118 119
        return QString();
    }
120 121
    KCalendarCore::ICalFormat format;
    return format.toString(calendar);
122 123 124 125 126 127
}

bool CalendarTaskBaseHandler::updateLocalCalendar(const KCalendarCore::Incidence::Ptr &incidence)
{
    const QString path = baseDirectoryPath() + QLatin1Char('/') + incidence->uid() + QLatin1String(".ical");

128 129 130 131 132
    const auto calendar = Calendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
    calendar->addIncidence(incidence);
    const auto fileStorage = FileStorage::Ptr(new FileStorage(calendar, path, new ICalFormat()));

    if (!fileStorage->save()) {
Shashwat Jolly's avatar
Shashwat Jolly committed
133
        qCDebug(ETESYNC_LOG) << "Unable to write" << path;
134 135 136 137 138
        return false;
    }
    return true;
}

139
void CalendarTaskBaseHandler::deleteLocalCalendar(const QString &incidenceUid)
140
{
141
    const QString path = baseDirectoryPath() + QLatin1Char('/') + incidenceUid + QLatin1String(".ical");
142 143 144 145 146 147 148 149 150 151
    QFile file(path);
    if (!file.remove()) {
        qCDebug(ETESYNC_LOG) << "Unable to remove " << path << file.errorString();
        return;
    }
}

void CalendarTaskBaseHandler::itemAdded(const Akonadi::Item &item,
                                        const Akonadi::Collection &collection)
{
152
    if (!item.hasPayload<Incidence::Ptr>()) {
153
        qCDebug(ETESYNC_LOG) << "Received item with unknown payload - Remote ID: " << item.remoteId();
154 155 156
        mResource->cancelTask(i18n("Received item with unknown payload %1", item.mimeType()));
        return;
    }
157
    KCalendarCore::Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
158
    const auto incidence = item.payload<Incidence::Ptr>();
159 160 161 162 163 164 165 166 167

    qCDebug(ETESYNC_LOG) << "Incidence mime type" << incidence->mimeType();

    if (!collection.contentMimeTypes().contains(incidence->mimeType())) {
        qCDebug(ETESYNC_LOG) << "Received item of different type";
        mResource->cancelTask(i18n("Received item of different type"));
        return;
    }

168
    calendar->addIncidence(incidence);
169 170
    KCalendarCore::ICalFormat format;

Shashwat Jolly's avatar
Shashwat Jolly committed
171
    const QString journalUid = collection.remoteId();
172
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
173

174
    if (!journal) {
175
        qCDebug(ETESYNC_LOG) << "Could not get journal" << journalUid;
176 177 178 179
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

180
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
181

182
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_ADD), format.toString(calendar));
183 184

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
185 186
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
187 188
        return;
    }
189

190 191 192 193
    Item newItem(item);
    newItem.setRemoteId(incidence->uid());
    newItem.setPayload<Incidence::Ptr>(incidence);
    mResource->changeCommitted(newItem);
194 195 196 197 198 199 200

    updateLocalCalendar(item.payload<Incidence::Ptr>());
}

void CalendarTaskBaseHandler::itemChanged(const Akonadi::Item &item,
                                          const QSet<QByteArray> &parts)
{
201 202
    Q_UNUSED(parts);

203 204 205 206 207
    if (!item.hasPayload<Incidence::Ptr>()) {
        qCDebug(ETESYNC_LOG) << "Received item with unknown payload";
        mResource->cancelTask(i18n("Received item with unknown payload %1", item.mimeType()));
        return;
    }
208 209 210
    Collection collection = item.parentCollection();

    KCalendarCore::Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
211 212
    const auto incidence = item.payload<Incidence::Ptr>();
    calendar->addIncidence(incidence);
213 214
    KCalendarCore::ICalFormat format;

Shashwat Jolly's avatar
Shashwat Jolly committed
215
    const QString journalUid = collection.remoteId();
216
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
217

218
    if (!journal) {
219
        qCDebug(ETESYNC_LOG) << "Could not get journal" << journalUid;
220 221 222 223
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

224
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
225

226
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_CHANGE), format.toString(calendar));
227 228

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
229 230
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
231 232
        return;
    }
233

234
    // Using ItemModifyJob + changeProcessed() instead of changeCommitted to handle conflict error - ItemSync modifies local item payload
235 236
    Item newItem(item);
    newItem.setPayload<Incidence::Ptr>(incidence);
237 238 239
    Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(newItem);
    modifyJob->disableRevisionCheck();
    mResource->changeProcessed();
240 241 242 243 244 245 246 247

    updateLocalCalendar(item.payload<Incidence::Ptr>());
}

void CalendarTaskBaseHandler::itemRemoved(const Akonadi::Item &item)
{
    Collection collection = item.parentCollection();

248 249 250 251 252 253 254 255 256 257
    const QString calendar = getLocalCalendar(item.remoteId());
    if (calendar.isEmpty()) {
        qCDebug(ETESYNC_LOG) << "Could not get local calendar";
        mResource->cancelTask(i18n("Could not get local calendar"));
        return;
    }

    // Delete now, because itemRemoved() may be called when collection is removed
    deleteLocalCalendar(item.remoteId());

Shashwat Jolly's avatar
Shashwat Jolly committed
258
    const QString journalUid = collection.remoteId();
259
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
260

261 262 263 264 265
    if (!journal) {
        mResource->cancelTask();
        return;
    }

266
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
267

268
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_DELETE), calendar);
269 270

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
271 272
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
273 274
        return;
    }
275

276
    mResource->changeProcessed();
277 278 279 280
}

void CalendarTaskBaseHandler::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
{
281 282
    Q_UNUSED(parent);

Shashwat Jolly's avatar
Shashwat Jolly committed
283
    const QString journalUid = QStringFromCharPtr(CharPtr(etesync_gen_uid()));
284
    EteSyncJournalPtr journal = etesync_journal_new(journalUid, ETESYNC_CURRENT_VERSION);
285

286
    EteSyncCollectionInfoPtr info = etesync_collection_info_new(etesyncCollectionType(), collection.displayName(), QString(), ETESYNC_COLLECTION_DEFAULT_COLOR);
287

288
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
289

Shashwat Jolly's avatar
Shashwat Jolly committed
290 291
    if (etesync_journal_set_info(journal.get(), cryptoManager.get(), info.get())) {
        qCDebug(ETESYNC_LOG) << "Could not set journal info";
292
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
293 294 295
        mResource->cancelTask(i18n("Could not set journal info"));
        return;
    };
296

Shashwat Jolly's avatar
Shashwat Jolly committed
297 298
    if (etesync_journal_manager_create(mClientState->journalManager(), journal.get())) {
        qCDebug(ETESYNC_LOG) << "Could not create journal";
299
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
300
        mResource->handleError();
301 302
        return;
    }
303 304 305

    Collection newCollection(collection);
    mResource->setupCollection(newCollection, journal.get());
306
    mResource->mJournalsCache[newCollection.remoteId()] = std::move(journal);
307 308 309 310 311
    mResource->changeCommitted(newCollection);
}

void CalendarTaskBaseHandler::collectionChanged(const Akonadi::Collection &collection)
{
Shashwat Jolly's avatar
Shashwat Jolly committed
312
    const QString journalUid = collection.remoteId();
313
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
314

315
    if (!journal) {
316
        qCDebug(ETESYNC_LOG) << "Could not get journal" << journalUid;
317 318 319 320
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

321
    auto journalColor = ETESYNC_COLLECTION_DEFAULT_COLOR;
322 323 324 325 326 327 328
    if (collection.hasAttribute<CollectionColorAttribute>()) {
        const CollectionColorAttribute *colorAttr = collection.attribute<CollectionColorAttribute>();
        if (colorAttr) {
            journalColor = colorAttr->color().rgb();
        }
    }

329
    EteSyncCollectionInfoPtr info = etesync_collection_info_new(etesyncCollectionType(), collection.displayName(), QString(), journalColor);
330

331
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
332

Shashwat Jolly's avatar
Shashwat Jolly committed
333 334
    if (etesync_journal_set_info(journal.get(), cryptoManager.get(), info.get())) {
        qCDebug(ETESYNC_LOG) << "Could not set journal info";
335
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
336 337 338
        mResource->cancelTask(i18n("Could not set journal info"));
        return;
    };
339

Shashwat Jolly's avatar
Shashwat Jolly committed
340 341
    if (etesync_journal_manager_update(mClientState->journalManager(), journal.get())) {
        qCDebug(ETESYNC_LOG) << "Could not update journal";
342
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
343
        mResource->handleError();
344 345
        return;
    }
346

347 348 349
    Collection newCollection(collection);
    mResource->setupCollection(newCollection, journal.get());
    mResource->changeCommitted(newCollection);
350 351 352 353
}

void CalendarTaskBaseHandler::collectionRemoved(const Akonadi::Collection &collection)
{
Shashwat Jolly's avatar
Shashwat Jolly committed
354
    const QString journalUid = collection.remoteId();
355
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
356

357
    if (!journal) {
358
        qCDebug(ETESYNC_LOG) << "Could not get journal" << journalUid;
359 360 361 362
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

Shashwat Jolly's avatar
Shashwat Jolly committed
363 364
    if (etesync_journal_manager_delete(mClientState->journalManager(), journal.get())) {
        qCDebug(ETESYNC_LOG) << "Could not delete journal";
365
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
366
        mResource->handleError();
367 368
        return;
    }
369
    mResource->changeProcessed();
370
}