contacthandler.cpp 14.3 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 "contacthandler.h"

#include <kcontacts/vcardconverter.h>

#include <AkonadiCore/CollectionModifyJob>
23
#include <AkonadiCore/ItemModifyJob>
24
#include <KLocalizedString>
25 26 27 28 29 30 31 32
#include <QFile>

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

using namespace Akonadi;

33
ContactHandler::ContactHandler(EteSyncResource *resource) : BaseHandler(resource)
34
{
35
    initialiseBaseDirectory();
36 37
}

38 39 40 41 42
const QString ContactHandler::mimeType()
{
    return KContacts::Addressee::mimeType();
}

43
const QString ContactHandler::etesyncCollectionType()
44 45 46 47
{
    return QStringLiteral(ETESYNC_COLLECTION_TYPE_ADDRESS_BOOK);
}

48
void ContactHandler::getItemListFromEntries(std::vector<EteSyncEntryPtr> &entries, Item::List &changedItems, Item::List &removedItems, Collection &collection, const QString &journalUid, QString &prevUid)
49
{
50
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
51 52 53 54 55
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "SetupItems: Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }
56
    EteSyncCryptoManagerPtr cryptoManager = etesync_journal_get_crypto_manager(journal.get(), mClientState->derived(), mClientState->keypair());
57 58 59

    QMap<QString, KContacts::Addressee> contacts;

60
    for (auto &entry : entries) {
Shashwat Jolly's avatar
Shashwat Jolly committed
61 62 63 64 65
        if (!entry) {
            qCDebug(ETESYNC_LOG) << "SetupItems: Entry is null";
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
            continue;
        }
66
        EteSyncSyncEntryPtr syncEntry = etesync_entry_get_sync_entry(entry.get(), cryptoManager.get(), prevUid);
67

Shashwat Jolly's avatar
Shashwat Jolly committed
68 69
        if (!syncEntry) {
            qCDebug(ETESYNC_LOG) << "SetupItems: syncEntry is null for entry" << etesync_entry_get_uid(entry.get());
70
            qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
71 72 73 74
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
            continue;
        }

75 76 77 78 79
        KContacts::VCardConverter converter;
        CharPtr contentStr(etesync_sync_entry_get_content(syncEntry.get()));
        QByteArray content(contentStr.get());
        const KContacts::Addressee contact = converter.parseVCard(content);

80
        if ((contact.uid()).isEmpty()) {
Shashwat Jolly's avatar
Shashwat Jolly committed
81 82
            qCDebug(ETESYNC_LOG) << "Couldn't parse entry with uid" << etesync_entry_get_uid(entry.get());
            prevUid = QStringFromCharPtr(CharPtr(etesync_entry_get_uid(entry.get())));
83 84 85
            continue;
        }

Shashwat Jolly's avatar
Shashwat Jolly committed
86 87
        qCDebug(ETESYNC_LOG) << "Entry parsed into contact - UID" << contact.uid();

88 89 90 91 92 93 94 95
        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)) {
            contacts[contact.uid()] = contact;
        } else if (action == QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_DELETE)) {
            if (contacts.contains(contact.uid())) {
                contacts.remove(contact.uid());
            } else {
                Item item;
96
                item.setMimeType(mimeType());
97 98
                item.setParentCollection(collection);
                item.setRemoteId(contact.uid());
99
                removedItems.push_back(item);
100

101
                deleteLocalContact(contact.uid());
102 103 104 105 106 107 108 109
            }
        }

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

    for (auto it = contacts.constBegin(); it != contacts.constEnd(); it++) {
        Item item;
110
        item.setMimeType(mimeType());
111 112 113
        item.setParentCollection(collection);
        item.setRemoteId(it.key());
        item.setPayload<KContacts::Addressee>(it.value());
114
        changedItems.push_back(item);
115 116 117 118 119 120 121 122 123 124

        updateLocalContact(it.value());
    }
}

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

Shashwat Jolly's avatar
Shashwat Jolly committed
125
QString ContactHandler::getLocalContact(const QString &contactUid) const
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
{
    const QString path = baseDirectoryPath() + QLatin1Char('/') + contactUid + QLatin1String(".vcf");

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

void ContactHandler::updateLocalContact(const KContacts::Addressee &contact)
{
    const QString path = baseDirectoryPath() + QLatin1Char('/') + contact.uid() + QLatin1String(".vcf");

    QFile file(path);
    if (!file.open(QIODevice::WriteOnly)) {
        qCDebug(ETESYNC_LOG) << "Unable to open " << path << file.errorString();
        return;
    }
    KContacts::VCardConverter converter;
    file.write(converter.createVCard(contact));
}

151
void ContactHandler::deleteLocalContact(const QString &contactUid)
152
{
153
    const QString path = baseDirectoryPath() + QLatin1Char('/') + contactUid + QLatin1String(".vcf");
154 155 156 157 158 159 160 161 162 163
    QFile file(path);
    if (!file.remove()) {
        qCDebug(ETESYNC_LOG) << "Unable to remove " << path << file.errorString();
        return;
    }
}

void ContactHandler::itemAdded(const Akonadi::Item &item,
                               const Akonadi::Collection &collection)
{
164 165 166 167 168
    if (!item.hasPayload<KContacts::Addressee>()) {
        qCDebug(ETESYNC_LOG) << "Received item with unknown payload";
        mResource->cancelTask(i18n("Received item with unknown payload %1", item.mimeType()));
        return;
    }
Shashwat Jolly's avatar
Shashwat Jolly committed
169
    const QString journalUid = collection.remoteId();
170
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
171

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

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

    KContacts::VCardConverter converter;
181 182
    const auto contact = item.payload<KContacts::Addressee>();
    QByteArray content = converter.createVCard(contact);
183

Shashwat Jolly's avatar
Shashwat Jolly committed
184 185 186 187 188 189
    if (content.isEmpty()) {
        qCDebug(ETESYNC_LOG) << "Could not create vcard from payload";
        mResource->cancelTask(i18n("Could not create vcard from payload"));
        return;
    }

190
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_ADD), QString::fromUtf8(content));
191 192

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

198 199 200 201
    Item newItem(item);
    newItem.setRemoteId(contact.uid());
    newItem.setPayload<KContacts::Addressee>(contact);
    mResource->changeCommitted(newItem);
202 203 204 205 206 207 208

    updateLocalContact(item.payload<KContacts::Addressee>());
}

void ContactHandler::itemChanged(const Akonadi::Item &item,
                                 const QSet<QByteArray> &parts)
{
209 210 211 212 213
    if (!item.hasPayload<KContacts::Addressee>()) {
        qCDebug(ETESYNC_LOG) << "Received item with unknown payload";
        mResource->cancelTask(i18n("Received item with unknown payload %1", item.mimeType()));
        return;
    }
214 215
    Collection collection = item.parentCollection();

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

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

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

    KContacts::VCardConverter converter;
228 229
    const auto contact = item.payload<KContacts::Addressee>();
    QByteArray content = converter.createVCard(contact);
230

Shashwat Jolly's avatar
Shashwat Jolly committed
231 232 233 234 235 236
    if (content.isEmpty()) {
        qCDebug(ETESYNC_LOG) << "Could not create vcard from content";
        mResource->cancelTask(i18n("Could not create vcard from content"));
        return;
    }

237
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_CHANGE), QString::fromUtf8(content));
238 239

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
240 241
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
242 243
        return;
    }
244

245
    // Using ItemModifyJob + changeProcessed() instead of changeCommitted to handle conflict error - ItemSync modifies local item payload
246 247
    Item newItem(item);
    newItem.setPayload<KContacts::Addressee>(contact);
248 249 250
    Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(newItem);
    modifyJob->disableRevisionCheck();
    mResource->changeProcessed();
251 252 253 254 255 256 257 258

    updateLocalContact(item.payload<KContacts::Addressee>());
}

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

259 260 261 262 263 264 265 266 267 268 269
    const QString contact = getLocalContact(item.remoteId());

    if (contact.isEmpty()) {
        qCDebug(ETESYNC_LOG) << "Could not get local contact";
        mResource->cancelTask(i18n("Could not get local contact"));
        return;
    }

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

Shashwat Jolly's avatar
Shashwat Jolly committed
270
    const QString journalUid = collection.remoteId();
271
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
Shashwat Jolly's avatar
Shashwat Jolly committed
272

273 274 275 276 277
    if (!journal) {
        mResource->cancelTask();
        return;
    }

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

280
    EteSyncSyncEntryPtr syncEntry = etesync_sync_entry_new(QStringLiteral(ETESYNC_SYNC_ENTRY_ACTION_DELETE), contact);
281 282

    if (!createEteSyncEntry(syncEntry.get(), cryptoManager.get(), collection)) {
283 284
        qCDebug(ETESYNC_LOG) << "Could not create EteSync entry";
        mResource->cancelTask(i18n("Could not create EteSync entry"));
285 286
        return;
    }
287

288
    mResource->changeProcessed();
289 290 291 292
}

void ContactHandler::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
{
Shashwat Jolly's avatar
Shashwat Jolly committed
293
    const QString journalUid = QStringFromCharPtr(CharPtr(etesync_gen_uid()));
294
    EteSyncJournalPtr journal = etesync_journal_new(journalUid, ETESYNC_CURRENT_VERSION);
295

296
    EteSyncCollectionInfoPtr info = etesync_collection_info_new(etesyncCollectionType(), collection.displayName(), QString(), ETESYNC_COLLECTION_DEFAULT_COLOR);
297

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

Shashwat Jolly's avatar
Shashwat Jolly committed
300 301
    if (etesync_journal_set_info(journal.get(), cryptoManager.get(), info.get())) {
        qCDebug(ETESYNC_LOG) << "Could not set journal info";
302
        qCDebug(ETESYNC_LOG) << "EteSync error" << QStringFromCharPtr(CharPtr(etesync_get_error_message()));
Shashwat Jolly's avatar
Shashwat Jolly committed
303 304 305
        mResource->cancelTask(i18n("Could not set journal info"));
        return;
    };
306

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

    Collection newCollection(collection);
    mResource->setupCollection(newCollection, journal.get());
316
    mResource->mJournalsCache[newCollection.remoteId()] = std::move(journal);
317 318 319 320 321
    mResource->changeCommitted(newCollection);
}

void ContactHandler::collectionChanged(const Akonadi::Collection &collection)
{
Shashwat Jolly's avatar
Shashwat Jolly committed
322
    const QString journalUid = collection.remoteId();
323
    const EteSyncJournalPtr &journal = mResource->getJournal(journalUid);
324

325 326 327 328 329 330
    if (!journal) {
        qCDebug(ETESYNC_LOG) << "Could not get journal";
        mResource->cancelTask(i18n("Could not get journal"));
        return;
    }

331
    EteSyncCollectionInfoPtr info = etesync_collection_info_new(etesyncCollectionType(), collection.displayName(), QString(), EteSyncDEFAULT_COLOR);
332

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

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

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

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

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

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

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