etesyncresource.cpp 27.3 KB
Newer Older
1
/*
2
 * SPDX-FileCopyrightText: 2020 Shashwat Jolly <shashwat.jolly@gmail.com>
Laurent Montel's avatar
Laurent Montel committed
3
 *
4
 * SPDX-License-Identifier: GPL-2.0-or-later
5 6 7
 */

#include "etesyncresource.h"
8

9 10
#include <kcontacts/addressee.h>
#include <kcontacts/contactgroup.h>
11 12
#include <kwindowsystem.h>

13
#include <AkonadiCore/AttributeFactory>
14
#include <AkonadiCore/CachePolicy>
15
#include <AkonadiCore/ChangeRecorder>
16
#include <AkonadiCore/CollectionColorAttribute>
17
#include <AkonadiCore/CollectionFetchScope>
18
#include <AkonadiCore/CollectionModifyJob>
19
#include <AkonadiCore/EntityDisplayAttribute>
20
#include <AkonadiCore/ItemFetchScope>
21 22
#include <KCalendarCore/Event>
#include <KCalendarCore/Todo>
23
#include <KMessageBox>
24
#include <QDBusConnection>
25

26
#include "entriesfetchjob.h"
27
#include "etesync_debug.h"
28
#include "journalsfetchjob.h"
29 30
#include "settings.h"
#include "settingsadaptor.h"
31
#include "setupwizard.h"
32

33
using namespace EteSyncAPI;
34 35
using namespace Akonadi;

36 37
#define ROOT_COLLECTION_REMOTEID QStringLiteral("EteSyncRootCollection")

Shashwat Jolly's avatar
Shashwat Jolly committed
38
EteSyncResource::EteSyncResource(const QString &id)
39 40
    : ResourceBase(id)
{
41
    Settings::instance(KSharedConfig::openConfig());
42
    new SettingsAdaptor(Settings::self());
43

44 45 46
    QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"),
                                                 Settings::self(),
                                                 QDBusConnection::ExportAdaptors);
47

Shashwat Jolly's avatar
Shashwat Jolly committed
48
    setName(i18n("EteSync Resource"));
49 50 51

    setNeedsNetwork(true);

52
    changeRecorder()->itemFetchScope().fetchFullPayload(true);
53 54 55
    changeRecorder()->itemFetchScope().setAncestorRetrieval(ItemFetchScope::All);
    changeRecorder()->fetchCollection(true);
    changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All);
56

57
    // Make resource directory
58 59
    initialiseDirectory(baseDirectoryPath());

60
    mClientState = EteSyncClientState::Ptr(new EteSyncClientState(winIdForDialogs()));
61
    connect(mClientState.get(), &EteSyncClientState::clientInitialised, this, &EteSyncResource::initialiseDone);
62
    mClientState->init();
63

64 65
    AttributeFactory::registerAttribute<CollectionColorAttribute>();

Shashwat Jolly's avatar
Shashwat Jolly committed
66
    connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &EteSyncResource::onReloadConfiguration);
67

68
    qCDebug(ETESYNC_LOG) << "Resource started";
69 70
}

71
void EteSyncResource::cleanup()
72
{
73 74
    mClientState->logout();
    QDir dir(cacheDirectoryPath());
75 76
    dir.removeRecursively();
    ResourceBase::cleanup();
77
}
78

79 80
void EteSyncResource::configure(WId windowId)
{
81
    SetupWizard wizard(mClientState.get());
82 83 84 85 86 87 88

    if (windowId) {
        wizard.setAttribute(Qt::WA_NativeWindow, true);
        KWindowSystem::setMainWindow(wizard.windowHandle(), windowId);
    }
    const int result = wizard.exec();
    if (result == QDialog::Accepted) {
89
        mClientState->saveSettings();
90 91 92 93 94 95 96

        // Init cache directories
        initialiseDirectory(cacheDirectoryPath());
        initialiseDirectory(collectionsCacheDirectoryPath());
        initialiseDirectory(itemsCacheDirectoryPath());

        // Save account cache
97
        mClientState->saveAccount();
98

99
        mCredentialsRequired = false;
100
        qCDebug(ETESYNC_LOG) << "Setting online";
101
        setOnline(true);
102
        synchronize();
103
        Q_EMIT configurationDialogAccepted();
104
    } else {
105 106
        qCDebug(ETESYNC_LOG) << "Setting offline";
        setOnline(false);
107 108 109 110
        Q_EMIT configurationDialogRejected();
    }
}

Shashwat Jolly's avatar
Shashwat Jolly committed
111
void EteSyncResource::retrieveCollections()
112
{
113
    qCDebug(ETESYNC_LOG) << "Retrieving collections";
114 115

    if (credentialsRequired()) {
116
        deferTask();
117 118 119
        return;
    }

120 121
    mRootCollection = createRootCollection();

122
    auto job = new JournalsFetchJob(mClientState->account(), mRootCollection, collectionsCacheDirectoryPath(), this);
123 124 125 126 127 128 129 130 131 132 133 134
    connect(job, &JournalsFetchJob::finished, this, &EteSyncResource::slotCollectionsRetrieved);
    job->start();
}

Collection EteSyncResource::createRootCollection()
{
    Collection rootCollection = Collection();
    rootCollection.setContentMimeTypes({Collection::mimeType()});
    rootCollection.setName(mClientState->username());
    rootCollection.setRemoteId(ROOT_COLLECTION_REMOTEID);
    rootCollection.setParentCollection(Collection::root());
    rootCollection.setRights(Collection::CanCreateCollection);
135

136 137 138
    // Keep collection list stoken preserved
    rootCollection.setRemoteRevision(mRootCollection.remoteRevision());

139 140 141 142 143
    Akonadi::CachePolicy cachePolicy;
    cachePolicy.setInheritFromParent(false);
    cachePolicy.setSyncOnDemand(false);
    cachePolicy.setCacheTimeout(-1);
    cachePolicy.setIntervalCheckTime(5);
144
    rootCollection.setCachePolicy(cachePolicy);
145

146
    EntityDisplayAttribute *attr = rootCollection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
147
    attr->setDisplayName(mClientState->username());
148 149
    attr->setIconName(QStringLiteral("akonadi-etesync"));

150
    return rootCollection;
151 152
}

153 154 155 156
void EteSyncResource::slotCollectionsRetrieved(KJob *job)
{
    if (job->error()) {
        qCWarning(ETESYNC_LOG) << "Error in fetching journals";
157
        qCWarning(ETESYNC_LOG) << "EteSync error" << job->error() << job->errorText();
158
        handleError(job->error(), job->errorText());
159 160
        return;
    }
161 162 163 164 165 166 167 168 169 170 171 172

    qCDebug(ETESYNC_LOG) << "slotCollectionsRetrieved()";

    QString sToken = qobject_cast<JournalsFetchJob *>(job)->syncToken();
    mRootCollection.setRemoteRevision(sToken);

    Collection::List collections = {mRootCollection};
    collections.append(qobject_cast<JournalsFetchJob *>(job)->collections());
    Collection::List removedCollections = qobject_cast<JournalsFetchJob *>(job)->removedCollections();

    collectionsRetrievedIncremental(collections, removedCollections);

173 174
    mJournalsCacheUpdateTime = QDateTime::currentDateTime();

175
    qCDebug(ETESYNC_LOG) << "Collections retrieval done";
176 177
}

178
bool EteSyncResource::handleError(const int errorCode, QString errorMessage)
179
{
180
    qCDebug(ETESYNC_LOG) << "handleError" << errorCode << errorMessage;
181
    switch (errorCode) {
182
    case ETEBASE_ERROR_CODE_UNAUTHORIZED:
Laurent Montel's avatar
Laurent Montel committed
183 184 185 186 187
        qCDebug(ETESYNC_LOG) << "Invalid token";
        deferTask();
        connect(mClientState.get(), &EteSyncClientState::tokenRefreshed, this, &EteSyncResource::slotTokenRefreshed);
        scheduleCustomTask(mClientState.get(), "refreshToken", QVariant(), ResourceBase::Prepend);
        return true;
188 189 190 191 192 193 194 195
    case ETEBASE_ERROR_CODE_PERMISSION_DENIED:
        qCDebug(ETESYNC_LOG) << "Permission denied";
        qCDebug(ETESYNC_LOG) << "Etebase error:" << errorMessage;
        showErrorDialog(i18n("You do not have permission to perform this action."), i18n(charArrFromQString(errorMessage)));
        setOnline(false);
        cancelTask(i18n("Permission denied"));
        return true;
    default:
Laurent Montel's avatar
Laurent Montel committed
196 197 198
        qCDebug(ETESYNC_LOG) << "Cancelling task";
        cancelTask();
        return true;
199
    }
Shashwat Jolly's avatar
Shashwat Jolly committed
200
    return false;
201
}
202

203 204
bool EteSyncResource::handleError()
{
205
    return handleError(etebase_error_get_code(), QString::fromUtf8(etebase_error_get_message()));
206 207 208 209 210
}

bool EteSyncResource::credentialsRequired()
{
    if (mCredentialsRequired) {
211
        qCDebug(ETESYNC_LOG) << "Credentials required";
212
        showErrorDialog(i18n("Your EteSync credentials were changed. Please click OK to re-enter your credentials."), i18n(etebase_error_get_message()), i18n("Credentials Changed"));
213 214 215 216 217
        configure(winIdForDialogs());
    }
    return mCredentialsRequired;
}

218 219
void EteSyncResource::slotTokenRefreshed(bool successful)
{
220
    qCDebug(ETESYNC_LOG) << "slotTokenRefreshed" << successful;
221
    if (!successful) {
222 223
        if (etebase_error_get_code() == ETEBASE_ERROR_CODE_UNAUTHORIZED) {
            qCDebug(ETESYNC_LOG) << "Unauthorized for tokenRefresh";
224
            mCredentialsRequired = true;
225 226 227 228 229 230 231 232 233 234 235 236 237 238
        }
    }
    taskDone();
}

void EteSyncResource::showErrorDialog(const QString &errorText, const QString &errorDetails, const QString &title)
{
    QWidget *parent = QWidget::find(winIdForDialogs());
    QDialog *dialog = new QDialog(parent, Qt::Dialog);
    dialog->setAttribute(Qt::WA_NativeWindow, true);
    KWindowSystem::setMainWindow(dialog->windowHandle(), winIdForDialogs());
    KMessageBox::detailedSorry(dialog, errorText, errorDetails, title);
}

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
QString getEtebaseTypeForCollection(const Akonadi::Collection &collection)
{
    QStringList mimeTypes = collection.contentMimeTypes();
    if (mimeTypes.contains(KContacts::Addressee::mimeType())) {
        return ETEBASE_COLLECTION_TYPE_CALENDAR;
    } else if (mimeTypes.contains(KCalendarCore::Event::eventMimeType())) {
        return ETEBASE_COLLECTION_TYPE_CALENDAR;
    } else if (mimeTypes.contains(KCalendarCore::Todo::todoMimeType())) {
        return ETEBASE_COLLECTION_TYPE_TASKS;
    } else {
        qCDebug(ETESYNC_LOG) << "Unable to get Etebase collection type for collection" << collection.remoteId() << mimeTypes;
        return QString();
    }
}

Shashwat Jolly's avatar
Shashwat Jolly committed
254
void EteSyncResource::retrieveItems(const Akonadi::Collection &collection)
255
{
256
    qCDebug(ETESYNC_LOG) << "Retrieving items for collection" << collection.remoteId();
257

258 259 260 261 262 263
    if (!mClientState->account()) {
        qCDebug(ETESYNC_LOG) << "Cannot retrieve items - account is null";
        cancelTask(i18n("Cannot retrieve items - account is null"));
        return;
    }

264 265
    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
    EtebaseCollectionPtr etesyncCollection = getEtebaseCollectionFromCache(collectionManager.get(), collection.remoteId(), collectionsCacheDirectoryPath());
266
    if (!etesyncCollection) {
267
        cancelTask(i18n("Could not get etesyncCollection from cache '%1'", collection.remoteId()));
268 269 270
        return;
    }

Shashwat Jolly's avatar
Shashwat Jolly committed
271
    const int timeSinceLastCacheUpdate = mJournalsCacheUpdateTime.secsTo(QDateTime::currentDateTime());
272
    if (timeSinceLastCacheUpdate <= 30) {
273
        qCDebug(ETESYNC_LOG) << "Retrieve items called immediately after collection fetch";
274 275

        const QString sToken = QString::fromUtf8(etebase_collection_get_stoken(etesyncCollection.get()));
276
        qCDebug(ETESYNC_LOG) << "Comparing" << sToken << "and" << collection.remoteRevision();
277
        if (sToken == collection.remoteRevision()) {
278
            qCDebug(ETESYNC_LOG) << "Already up-to-date: Fetched collection and cached collection have the same stoken";
279 280 281 282 283
            itemsRetrievalDone();
            return;
        }
    }

284
    if (credentialsRequired()) {
285
        deferTask();
286 287 288
        return;
    }

289
    auto job = new EntriesFetchJob(mClientState->account(), collection, std::move(etesyncCollection), itemsCacheDirectoryPath(), this);
Shashwat Jolly's avatar
Shashwat Jolly committed
290

291
    connect(job, &EntriesFetchJob::finished, this, &EteSyncResource::slotItemsRetrieved);
Shashwat Jolly's avatar
Shashwat Jolly committed
292

293 294
    job->start();
}
Shashwat Jolly's avatar
Shashwat Jolly committed
295

296 297 298
void EteSyncResource::slotItemsRetrieved(KJob *job)
{
    if (job->error()) {
Shashwat Jolly's avatar
Shashwat Jolly committed
299
        qCDebug(ETESYNC_LOG) << "Error in fetching entries";
300
        qCWarning(ETESYNC_LOG) << "EteSync error" << job->error() << job->errorText();
301
        handleError(job->error(), job->errorText());
302
    }
303

304 305
    Item::List items = qobject_cast<EntriesFetchJob *>(job)->items();
    Item::List removedItems = qobject_cast<EntriesFetchJob *>(job)->removedItems();
Shashwat Jolly's avatar
Shashwat Jolly committed
306

307 308
    qCDebug(ETESYNC_LOG) << "Updating collection sync token";
    Collection collection = qobject_cast<EntriesFetchJob *>(job)->collection();
309
    qCDebug(ETESYNC_LOG) << "Setting collection" << collection.remoteId() << "'s sync token to" << collection.remoteRevision();
310
    new CollectionModifyJob(collection, this);
311

312 313
    itemsRetrievedIncremental(items, removedItems);

314
    qCDebug(ETESYNC_LOG) << "Items retrieval done";
315 316
}

Shashwat Jolly's avatar
Shashwat Jolly committed
317
void EteSyncResource::aboutToQuit()
318
{
319 320
}

Shashwat Jolly's avatar
Shashwat Jolly committed
321
void EteSyncResource::onReloadConfiguration()
322
{
323
    qCDebug(ETESYNC_LOG) << "Resource config reload";
324 325
    synchronize();
}
326

327 328 329 330 331 332
void EteSyncResource::initialiseDone(bool successful)
{
    qCDebug(ETESYNC_LOG) << "Resource intialised";
    if (successful) {
        synchronize();
    }
333 334
}

335 336
QString EteSyncResource::baseDirectoryPath() const
{
337
    return Settings::self()->basePath();
338 339
}

340 341 342 343 344
QString EteSyncResource::cacheDirectoryPath() const
{
    return Settings::self()->cacheDir();
}

345 346
QString EteSyncResource::collectionsCacheDirectoryPath() const
{
347
    return cacheDirectoryPath() + QStringLiteral("/CollectionCache");
348 349 350 351
}

QString EteSyncResource::itemsCacheDirectoryPath() const
{
352
    return cacheDirectoryPath() + QStringLiteral("/ItemCache");
353 354
}

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
void EteSyncResource::initialiseDirectory(const QString &path) const
{
    QDir dir(path);

    // if folder does not exists, create it
    QDir::root().mkpath(dir.absolutePath());

    // check whether warning file is in place...
    QFile file(dir.absolutePath() + QStringLiteral("/WARNING_README.txt"));
    if (!file.exists()) {
        // ... if not, create it
        file.open(QIODevice::WriteOnly);
        file.write(
            "Important warning!\n\n"
            "Do not create or copy vCards inside this folder manually, they are managed by the Akonadi framework!\n");
        file.close();
    }
}

Laurent Montel's avatar
Laurent Montel committed
374
void EteSyncResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection)
375
{
376
    qCDebug(ETESYNC_LOG) << "Item added" << item.mimeType();
377
    qCDebug(ETESYNC_LOG) << "Journal UID" << collection.remoteId();
378

379
    if (credentialsRequired()) {
380
        deferTask();
381 382 383
        return;
    }

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
    EtebaseCollectionPtr etesyncCollection = getEtebaseCollectionFromCache(collectionManager.get(), collection.remoteId(), collectionsCacheDirectoryPath());
    if (!etesyncCollection) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncCollection from cache" << collection.remoteId();
        cancelTask(i18n("Could not get etesyncCollection from cache '%1'", collection.remoteId()));
        return;
    }
    EtebaseCollectionMetadataPtr collectionMetadata(etebase_collection_get_meta(etesyncCollection.get()));
    const QString type = QString::fromUtf8(etebase_collection_metadata_get_collection_type(collectionMetadata.get()));

    // Create metadata
    int64_t modificationTimeSinceEpoch = item.modificationTime().toMSecsSinceEpoch();
    EtebaseItemMetadataPtr itemMetaData(etebase_item_metadata_new());
    etebase_item_metadata_set_item_type(itemMetaData.get(), "file");
    QString uid;
    if (type == ETEBASE_COLLECTION_TYPE_ADDRESS_BOOK) {
        uid = item.payload<KContacts::Addressee>().uid();
401
    } else {
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
        uid = item.payload<KCalendarCore::Incidence::Ptr>()->uid();
    }
    etebase_item_metadata_set_name(itemMetaData.get(), charArrFromQString(uid));
    etebase_item_metadata_set_mtime(itemMetaData.get(), &modificationTimeSinceEpoch);

    qCDebug(ETESYNC_LOG) << "Created metadata";

    // Get item manager
    EtebaseItemManagerPtr itemManager(etebase_collection_manager_get_item_manager(collectionManager.get(), etesyncCollection.get()));

    // Create Etesync item
    QByteArray payloadData = item.payloadData();
    EtebaseItemPtr etesyncItem(etebase_item_manager_create(itemManager.get(), itemMetaData.get(), payloadData.constData(), payloadData.size()));
    if (!etesyncItem) {
        qCDebug(ETESYNC_LOG) << "Could not create new etesyncItem" << uid;
417
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
418 419
        cancelTask(i18n("Could not create new etesyncItem '%1'", uid));
        return;
420
    }
421 422 423 424 425 426 427

    qCDebug(ETESYNC_LOG) << "Created EteSync item";

    // Upload to server
    const EtebaseItem *items[] = {etesyncItem.get()};

    if (etebase_item_manager_batch(itemManager.get(), items, ETEBASE_UTILS_C_ARRAY_LEN(items), NULL)) {
428
        qCDebug(ETESYNC_LOG) << "Error uploading item addition" << uid;
429
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
430
        handleError();
431
        return;
432 433 434 435
    } else {
        qCDebug(ETESYNC_LOG) << "Uploaded item addition to server";
    }

436 437 438
    // Save to cache
    saveEtebaseItemCache(itemManager.get(), etesyncItem.get(), itemsCacheDirectoryPath());

439 440 441 442
    Item newItem(item);
    newItem.setRemoteId(QString::fromUtf8(etebase_item_get_uid(etesyncItem.get())));
    newItem.setPayloadFromData(payloadData);
    changeCommitted(newItem);
443 444
}

Laurent Montel's avatar
Laurent Montel committed
445
void EteSyncResource::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &parts)
446
{
Shashwat Jolly's avatar
Shashwat Jolly committed
447 448
    Q_UNUSED(parts)

449
    qCDebug(ETESYNC_LOG) << "Item changed" << item.mimeType() << item.remoteId();
450
    qCDebug(ETESYNC_LOG) << "Journal UID" << item.parentCollection().remoteId();
451

452
    if (credentialsRequired()) {
453
        deferTask();
454 455 456
        return;
    }

457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    Collection collection = item.parentCollection();

    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
    EtebaseCollectionPtr etesyncCollection = getEtebaseCollectionFromCache(collectionManager.get(), collection.remoteId(), collectionsCacheDirectoryPath());
    if (!etesyncCollection) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncCollection from cache" << collection.remoteId();
        cancelTask(i18n("Could not get etesyncCollection from cache '%1'", collection.remoteId()));
        return;
    }
    EtebaseItemManagerPtr itemManager(etebase_collection_manager_get_item_manager(collectionManager.get(), etesyncCollection.get()));
    EtebaseItemPtr etesyncItem = getEtebaseItemFromCache(itemManager.get(), item.remoteId(), itemsCacheDirectoryPath());
    if (!etesyncItem) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncItem from cache" << item.remoteId();
        cancelTask(i18n("Could not get etesyncItem from cache '%1'", item.remoteId()));
        return;
    }

    // Update metadata (only mtime in this case)
    int64_t modificationTimeSinceEpoch = item.modificationTime().toMSecsSinceEpoch();

    EtebaseItemMetadataPtr itemMetadata(etebase_item_get_meta(etesyncItem.get()));
    etebase_item_metadata_set_mtime(itemMetadata.get(), &modificationTimeSinceEpoch);
    etebase_item_set_meta(etesyncItem.get(), itemMetadata.get());

    qCDebug(ETESYNC_LOG) << "Updated metadata mtime";

    // Update content
    QByteArray payloadData = item.payloadData();
    etebase_item_set_content(etesyncItem.get(), payloadData.constData(), payloadData.size());

    qCDebug(ETESYNC_LOG) << "Updated item content";

    // Upload to server
    const EtebaseItem *items[] = {etesyncItem.get()};

    if (etebase_item_manager_batch(itemManager.get(), items, ETEBASE_UTILS_C_ARRAY_LEN(items), NULL)) {
493
        qCDebug(ETESYNC_LOG) << "Error uploading item modifications" << item.remoteId();
494
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
495
        handleError();
496
        return;
497
    } else {
498
        qCDebug(ETESYNC_LOG) << "Uploaded item modifications to server";
499
    }
500

501
    // Update cache
502 503 504
    saveEtebaseItemCache(itemManager.get(), etesyncItem.get(), itemsCacheDirectoryPath());

    changeProcessed();
505 506
}

Shashwat Jolly's avatar
Shashwat Jolly committed
507
void EteSyncResource::itemRemoved(const Akonadi::Item &item)
508
{
509
    qCDebug(ETESYNC_LOG) << "Item removed" << item.mimeType();
510

511
    if (credentialsRequired()) {
512
        deferTask();
513 514 515
        return;
    }

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
    Collection collection = item.parentCollection();

    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
    EtebaseCollectionPtr etesyncCollection = getEtebaseCollectionFromCache(collectionManager.get(), collection.remoteId(), collectionsCacheDirectoryPath());
    if (!etesyncCollection) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncCollection from cache" << collection.remoteId();
        cancelTask(i18n("Could not get etesyncCollection from cache '%1'", collection.remoteId()));
        return;
    }
    EtebaseItemManagerPtr itemManager(etebase_collection_manager_get_item_manager(collectionManager.get(), etesyncCollection.get()));
    EtebaseItemPtr etesyncItem = getEtebaseItemFromCache(itemManager.get(), item.remoteId(), itemsCacheDirectoryPath());
    if (!etesyncItem) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncItem from cache" << item.remoteId();
        cancelTask(i18n("Could not get etesyncItem from cache '%1'", item.remoteId()));
        return;
    }

    // Update metadata (only mtime in this case)
    int64_t modificationTimeSinceEpoch = item.modificationTime().toMSecsSinceEpoch();

    EtebaseItemMetadataPtr itemMetadata(etebase_item_get_meta(etesyncItem.get()));
    etebase_item_metadata_set_mtime(itemMetadata.get(), &modificationTimeSinceEpoch);
    etebase_item_set_meta(etesyncItem.get(), itemMetadata.get());

    qCDebug(ETESYNC_LOG) << "Updated metadata mtime";

    // Set item deleted
    etebase_item_delete(etesyncItem.get());

    qCDebug(ETESYNC_LOG) << "Set item deleted";

    // Upload to server
    const EtebaseItem *items[] = {etesyncItem.get()};

    if (etebase_item_manager_batch(itemManager.get(), items, ETEBASE_UTILS_C_ARRAY_LEN(items), NULL)) {
551
        qCDebug(ETESYNC_LOG) << "Error uploading item deletion" << item.remoteId();
552
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
553
        handleError();
554
        return;
555
    } else {
556
        qCDebug(ETESYNC_LOG) << "Uploaded item deletion to server";
557
    }
558 559 560 561 562

    // Delete cache
    deleteCacheFile(item.remoteId(), itemsCacheDirectoryPath());

    changeProcessed();
563 564 565 566
}

void EteSyncResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
{
Shashwat Jolly's avatar
Shashwat Jolly committed
567 568
    Q_UNUSED(parent)

569
    qCDebug(ETESYNC_LOG) << "Collection added" << collection.mimeType();
570

571
    if (credentialsRequired()) {
572
        deferTask();
573 574 575
        return;
    }

576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));

    // Create metadata
    const QString type = getEtebaseTypeForCollection(collection);
    EtebaseCollectionMetadataPtr collectionMetaData(etebase_collection_metadata_new(type, collection.displayName()));
    etebase_collection_metadata_set_color(collectionMetaData.get(), ETESYNC_DEFAULT_COLLECTION_COLOR);

    qCDebug(ETESYNC_LOG) << "Created metadata";

    // Create EteSync collection
    EtebaseCollectionPtr etesyncCollection(etebase_collection_manager_create(collectionManager.get(), collectionMetaData.get(), nullptr, 0));
    if (!etesyncCollection) {
        qCDebug(ETESYNC_LOG) << "Could not create new etesyncCollection";
        qCDebug(ETESYNC_LOG) << "Etebase error;" << etebase_error_get_message();
        cancelTask(i18n("Could not create new etesyncCollection"));
        return;
    }

    qCDebug(ETESYNC_LOG) << "Created EteSync collection";

    // Upload to server
    if (etebase_collection_manager_upload(collectionManager.get(), etesyncCollection.get(), NULL)) {
        qCDebug(ETESYNC_LOG) << "Error uploading collection addition";
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
600
        handleError();
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
        return;
    } else {
        qCDebug(ETESYNC_LOG) << "Uploaded collection addition to server";
    }

    // Save to cache
    saveEtebaseCollectionCache(collectionManager.get(), etesyncCollection.get(), collectionsCacheDirectoryPath());

    // Setup icon, color and name of the new collection
    Collection newCollection(collection);
    newCollection.setRemoteId(QString::fromUtf8(etebase_collection_get_uid(etesyncCollection.get())));

    // Icon and name
    auto attr = newCollection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);

    if (type == ETEBASE_COLLECTION_TYPE_ADDRESS_BOOK) {
        attr->setDisplayName(collection.displayName());
        attr->setIconName(QStringLiteral("view-pim-contacts"));
    } else if (type == ETEBASE_COLLECTION_TYPE_CALENDAR) {
        attr->setDisplayName(collection.displayName());
        attr->setIconName(QStringLiteral("view-calendar"));
    } else if (type == ETEBASE_COLLECTION_TYPE_TASKS) {
        attr->setDisplayName(collection.displayName());
        attr->setIconName(QStringLiteral("view-pim-tasks"));
625
    } else {
626
        qCWarning(ETESYNC_LOG) << "Unknown journal type. Cannot set collection name and icon.";
627
    }
628 629 630 631 632 633

    // Color
    auto colorAttr = newCollection.attribute<Akonadi::CollectionColorAttribute>(Collection::AddIfMissing);
    colorAttr->setColor(ETESYNC_DEFAULT_COLLECTION_COLOR);

    changeCommitted(newCollection);
634 635
}

636 637 638
void EteSyncResource::collectionChanged(const Akonadi::Collection &collection)
{
    qCDebug(ETESYNC_LOG) << "Collection changed" << collection.mimeType();
639

640
    if (credentialsRequired()) {
641
        deferTask();
642 643 644
        return;
    }

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
    // Get EteSync collection from cache
    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
    EtebaseCollectionPtr etesyncCollection = getEtebaseCollectionFromCache(collectionManager.get(), collection.remoteId(), collectionsCacheDirectoryPath());
    if (!etesyncCollection) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncCollection from cache" << collection.remoteId();
        cancelTask(i18n("Could not get etesyncCollection from cache '%1'", collection.remoteId()));
        return;
    }

    // Update metadata
    EtebaseCollectionMetadataPtr collectionMetadata(etebase_collection_get_meta(etesyncCollection.get()));

    // Name
    etebase_collection_metadata_set_name(collectionMetadata.get(), collection.displayName());

    // Color
    auto journalColor = ETESYNC_DEFAULT_COLLECTION_COLOR;
    if (collection.hasAttribute<CollectionColorAttribute>()) {
        const CollectionColorAttribute *colorAttr = collection.attribute<CollectionColorAttribute>();
        if (colorAttr) {
            journalColor = colorAttr->color().name();
        }
    }
    etebase_collection_metadata_set_color(collectionMetadata.get(), journalColor);

    // Set metadata
    etebase_collection_set_meta(etesyncCollection.get(), collectionMetadata.get());

    // Upload to server
    if (etebase_collection_manager_upload(collectionManager.get(), etesyncCollection.get(), NULL)) {
        qCDebug(ETESYNC_LOG) << "Error uploading collection modifications" << collection.remoteId();
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
677
        handleError();
678
        return;
679
    } else {
680
        qCDebug(ETESYNC_LOG) << "Uploaded collection modifications to server";
681
    }
682 683 684 685 686 687

    // Update cache
    saveEtebaseCollectionCache(collectionManager.get(), etesyncCollection.get(), collectionsCacheDirectoryPath());

    Collection newCollection(collection);
    changeCommitted(newCollection);
688 689 690 691 692
}

void EteSyncResource::collectionRemoved(const Akonadi::Collection &collection)
{
    qCDebug(ETESYNC_LOG) << "Collection removed" << collection.mimeType();
693

694
    if (credentialsRequired()) {
695
        deferTask();
696 697 698
        return;
    }

699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
    // Get EteSync collection from cache
    EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
    EtebaseCollectionPtr etesyncCollection = getEtebaseCollectionFromCache(collectionManager.get(), collection.remoteId(), collectionsCacheDirectoryPath());
    if (!etesyncCollection) {
        qCDebug(ETESYNC_LOG) << "Could not get etesyncCollection from cache" << collection.remoteId();
        cancelTask(i18n("Could not get etesyncCollection from cache '%1'", collection.remoteId()));
        return;
    }

    // Set collection deleted
    etebase_collection_delete(etesyncCollection.get());

    // Upload to server
    if (etebase_collection_manager_upload(collectionManager.get(), etesyncCollection.get(), NULL)) {
        qCDebug(ETESYNC_LOG) << "Error uploading collection deletion" << collection.remoteId();
        qCDebug(ETESYNC_LOG) << "Etebase error:" << etebase_error_get_message();
715
        handleError();
716
        return;
717
    } else {
718
        qCDebug(ETESYNC_LOG) << "Uploaded collection deletion to server";
719
    }
720 721 722 723 724

    // Delete cache
    deleteCacheFile(collection.remoteId(), collectionsCacheDirectoryPath());

    changeProcessed();
725 726
}

Shashwat Jolly's avatar
Shashwat Jolly committed
727
AKONADI_RESOURCE_MAIN(EteSyncResource)