calendartaskbasehandler.cpp 15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * Copyright (C) 2020 by Shashwat Jolly <shashwat.jolly@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 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, see <https://www.gnu.org/licenses/>.
 */

#include "calendartaskbasehandler.h"

#include <AkonadiCore/AttributeFactory>
#include <AkonadiCore/CollectionColorAttribute>
#include <AkonadiCore/CollectionModifyJob>
23
#include <AkonadiCore/ItemModifyJob>
24 25
#include <KCalendarCore/ICalFormat>
#include <KCalendarCore/MemoryCalendar>
26
#include <KLocalizedString>
27 28 29 30 31 32 33 34 35
#include <QFile>

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

using namespace Akonadi;
using namespace KCalendarCore;

36
CalendarTaskBaseHandler::CalendarTaskBaseHandler(EteSyncResource *resource) : BaseHandler(resource)
37
{
38
    initialiseBaseDirectory();
39
    AttributeFactory::registerAttribute<CollectionColorAttribute>();
40 41
}

42
void CalendarTaskBaseHandler::getItemListFromEntries(std::vector<EteSyncEntryPtr> &entries, Item::List &changedItems, Item::List &removedItems, Collection &collection, const QString &journalUid, QString &prevUid)
43
{
44
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
45 46 47 48 49
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "SetupItems: Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }
50
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
51 52 53

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

54
    for (auto &entry : entries) {
55 56
        if (!entry) {
            qCDebug(ETESYNC_LOG) << "SetupItems: Entry is null";
57
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
58 59
            continue;
        }
60
        EteSyncSyncEntryPtr syncEntry = etesync_entry_get_sync_entry(entry.get(), cryptoManager.get(), prevUid);
61

62 63
        if (!syncEntry) {
            qCDebug(ETESYNC_LOG) << "SetupItems: syncEntry is null for entry" << etesync_entry_get_uid(entry.get());
64
            qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
65
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
66 67 68
            continue;
        }

69 70 71
        CharPtr contentStr(etesync_sync_entry_get_content(syncEntry.get()));

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

74
        if (!incidence || (incidence->uid()).isEmpty()) {
75
            qCDebug(ETESYNC_LOG) << "Couldn't parse entry with uid" << etesync_entry_get_uid(entry.get());
76
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
77 78 79
            continue;
        }

80 81
        qCDebug(ETESYNC_LOG) << "Entry parsed into incidence - UID" << incidence->uid();

82 83 84 85 86 87 88 89 90 91 92
        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());
93
                removedItems.push_back(item);
94

95
                deleteLocalCalendar(incidence->uid());
96 97 98 99 100 101 102 103 104 105 106 107
            }
        }

        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());
108
        changedItems.push_back(item);
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

        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");

    QFile file(path);
    if (!file.open(QFile::ReadOnly | QFile::Text)) {
        qCDebug(ETESYNC_LOG) << "Unable to read " << path << file.errorString();
        return QString();
    }
    QTextStream in(&file);
    return in.readAll();
}

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

    QFile file(path);
    if (!file.open(QIODevice::WriteOnly)) {
        qCDebug(ETESYNC_LOG) << "Unable to open " << path << file.errorString();
        return false;
    }
    KCalendarCore::Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
    calendar->addIncidence(incidence);
    KCalendarCore::ICalFormat format;
144
    file.write(format.toString(calendar).toUtf8());
145 146 147
    return true;
}

148
void CalendarTaskBaseHandler::deleteLocalCalendar(const QString &incidenceUid)
149
{
150
    const QString path = baseDirectoryPath() + QLatin1Char('/') + incidenceUid + QLatin1String(".ical");
151 152 153 154 155 156 157 158 159 160
    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)
{
161
    if (!item.hasPayload<Incidence::Ptr>()) {
162
        qCDebug(ETESYNC_LOG) << "Received item with unknown payload - Remote ID: " << item.remoteId();
163 164 165
        mResource->cancelTask(i18n("Received item with unknown payload %1", item.mimeType()));
        return;
    }
166
    KCalendarCore::Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
167
    const auto incidence = item.payload<Incidence::Ptr>();
168 169 170 171 172 173 174 175 176

    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;
    }

177
    calendar->addIncidence(incidence);
178 179
    KCalendarCore::ICalFormat format;

Shashwat Jolly's avatar
Shashwat Jolly committed
180
    const QString journalUid = collection.remoteId();
181
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
182

183 184 185 186 187 188
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

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

191
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_ADD), format.toString(calendar));
192 193

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
194 195
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
196 197
        return;
    }
198

199 200 201 202
    Item newItem(item);
    newItem.setRemoteId(incidence->uid());
    newItem.setPayload<Incidence::Ptr>(incidence);
    mResource->changeCommitted(newItem);
203 204 205 206 207 208 209

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

void CalendarTaskBaseHandler::itemChanged(const Akonadi::Item &item,
                                          const QSet<QByteArray> &parts)
{
210 211 212 213 214
    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;
    }
215 216 217
    Collection collection = item.parentCollection();

    KCalendarCore::Calendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
218 219
    const auto incidence = item.payload<Incidence::Ptr>();
    calendar->addIncidence(incidence);
220 221
    KCalendarCore::ICalFormat format;

Shashwat Jolly's avatar
Shashwat Jolly committed
222
    const QString journalUid = collection.remoteId();
223
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
224

225 226 227 228 229 230
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

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

233
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_CHANGE), format.toString(calendar));
234 235

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
236 237
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
238 239
        return;
    }
240

241
    // Using ItemModifyJob + changeProcessed() instead of changeCommitted to handle conflict error - ItemSync modifies local item payload
242 243
    Item newItem(item);
    newItem.setPayload<Incidence::Ptr>(incidence);
244 245 246
    Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(newItem);
    modifyJob->disableRevisionCheck();
    mResource->changeProcessed();
247 248 249 250 251 252 253 254

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

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

255 256 257 258 259 260 261 262 263 264
    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
265
    const QString journalUid = collection.remoteId();
266
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
267

268 269 270 271 272
    if (!journal) {
        mResource->cancelTask();
        return;
    }

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

275
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_DELETE), calendar);
276 277

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
278 279
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
280 281
        return;
    }
282

283
    mResource->changeProcessed();
284 285 286 287
}

void CalendarTaskBaseHandler::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
{
Shashwat Jolly's avatar
Shashwat Jolly committed
288
    const QString journalUid = QStringFromCharPtr(CharPtr(etesync_gen_uid()));
289
    EteSyncJournalPtr journal = etesync_journal_new(journalUid, ETESYNC_CURRENT_VERSION);
290

291
    EteSyncCollectionInfoPtr info = etesync_collection_info_new(etesyncCollectionType(), collection.displayName(), QString(), ETESYNC_COLLECTION_DEFAULT_COLOR);
292

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

Shashwat Jolly's avatar
Shashwat Jolly committed
295 296
    if (etesync_journal_set_info(journal.get(), cryptoManager.get(), info.get())) {
        qCDebug(ETESYNC_LOG) << "Could not set journal info";
297
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
298 299 300
        mResource->cancelTask(i18n("Could not set journal info"));
        return;
    };
301

Shashwat Jolly's avatar
Shashwat Jolly committed
302 303
    if (etesync_journal_manager_create(mClientState->journalManager(), journal.get())) {
        qCDebug(ETESYNC_LOG) << "Could not create journal";
304
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
305 306 307
        mResource->handleTokenError();
        return;
    }
308 309 310

    Collection newCollection(collection);
    mResource->setupCollection(newCollection, journal.get());
311
    mResource->mJournalsCache[newCollection.remoteId()] = std::move(journal);
312 313 314 315 316
    mResource->changeCommitted(newCollection);
}

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

320 321 322 323 324 325
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

326
    auto journalColor = ETESYNC_COLLECTION_DEFAULT_COLOR;
327 328 329 330 331 332 333
    if (collection.hasAttribute<CollectionColorAttribute>()) {
        const CollectionColorAttribute *colorAttr = collection.attribute<CollectionColorAttribute>();
        if (colorAttr) {
            journalColor = colorAttr->color().rgb();
        }
    }

334
    EteSyncCollectionInfoPtr info = etesync_collection_info_new(etesyncCollectionType(), collection.displayName(), QString(), journalColor);
335

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

Shashwat Jolly's avatar
Shashwat Jolly committed
338 339
    if (etesync_journal_set_info(journal.get(), cryptoManager.get(), info.get())) {
        qCDebug(ETESYNC_LOG) << "Could not set journal info";
340
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
341 342 343
        mResource->cancelTask(i18n("Could not set journal info"));
        return;
    };
344

Shashwat Jolly's avatar
Shashwat Jolly committed
345 346
    if (etesync_journal_manager_update(mClientState->journalManager(), journal.get())) {
        qCDebug(ETESYNC_LOG) << "Could not update journal";
347
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
348 349 350
        mResource->handleTokenError();
        return;
    }
351

352 353 354
    Collection newCollection(collection);
    mResource->setupCollection(newCollection, journal.get());
    mResource->changeCommitted(newCollection);
355 356 357 358
}

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

362 363 364 365 366 367
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

Shashwat Jolly's avatar
Shashwat Jolly committed
368 369
    if (etesync_journal_manager_delete(mClientState->journalManager(), journal.get())) {
        qCDebug(ETESYNC_LOG) << "Could not delete journal";
370
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
371 372 373
        mResource->handleTokenError();
        return;
    }
374
    mResource->changeProcessed();
375
}