~renatofilho/buteo-sync-plugins-contacts/solve-eds-conflict

« back to all changes in this revision

Viewing changes to google/GRemoteSource.cpp

  • Committer: CI Train Bot
  • Author(s): Renato Araujo Oliveira Filho
  • Date: 2015-09-24 17:50:44 UTC
  • mfrom: (1.3.53 initial-vr)
  • Revision ID: ci-train-bot@canonical.com-20150924175044-rp0akjs0h1xwxmpj
Implemented buteo contacts sync plugin for google, heavily based on:
  - https://github.com/nemomobile-graveyard/buteo-sync-plugins-google
  - https://github.com/nemomobile/buteo-sync-plugins-social
Approved by: Michael Sheldon

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * This file is part of buteo-sync-plugins-contacts package
 
3
 *
 
4
 * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
 
5
 *               2015 Canonical Ltd
 
6
 *
 
7
 * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
 
8
 *               Mani Chandrasekar <maninc@gmail.com>
 
9
 *               Renato Araujo Oliveira Filho <renato.filho@canonical.com>
 
10
 *
 
11
 * This library is free software; you can redistribute it and/or
 
12
 * modify it under the terms of the GNU Lesser General Public License
 
13
 * version 2.1 as published by the Free Software Foundation.
 
14
 *
 
15
 * This library is distributed in the hope that it will be useful, but
 
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 
18
 * Lesser General Public License for more details.
 
19
 *
 
20
 * You should have received a copy of the GNU Lesser General Public
 
21
 * License along with this library; if not, write to the Free Software
 
22
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 
23
 * 02110-1301 USA
 
24
 *
 
25
 */
 
26
 
 
27
#include "GRemoteSource.h"
 
28
#include "GTransport.h"
 
29
#include "GConfig.h"
 
30
#include "GContactStream.h"
 
31
#include "GContactImageDownloader.h"
 
32
#include "GContactImageUploader.h"
 
33
#include "buteosyncfw_p.h"
 
34
 
 
35
#include <UContactsBackend.h>
 
36
#include <UContactsCustomDetail.h>
 
37
 
 
38
#include <ProfileEngineDefs.h>
 
39
#include <ProfileManager.h>
 
40
 
 
41
#include <QtContacts/QContact>
 
42
#include <QtContacts/QContactGuid>
 
43
#include <QtContacts/QContact>
 
44
 
 
45
QTCONTACTS_USE_NAMESPACE
 
46
 
 
47
GRemoteSource::GRemoteSource(QObject *parent)
 
48
    : UAbstractRemoteSource(parent),
 
49
      mTransport(new GTransport),
 
50
      mState(GRemoteSource::STATE_IDLE),
 
51
      mStartIndex(0),
 
52
      mFetchAvatars(true)
 
53
{
 
54
    connect(mTransport.data(),
 
55
            SIGNAL(finishedRequest()),
 
56
            SLOT(networkRequestFinished()));
 
57
 
 
58
    connect(mTransport.data(),
 
59
            SIGNAL(error(int)),
 
60
            SLOT(networkError(int)));
 
61
}
 
62
 
 
63
GRemoteSource::~GRemoteSource()
 
64
{
 
65
}
 
66
 
 
67
bool GRemoteSource::init(const QVariantMap &properties)
 
68
{
 
69
    FUNCTION_CALL_TRACE;
 
70
    LOG_DEBUG("Creating HTTP transport");
 
71
 
 
72
    if (mState != GRemoteSource::STATE_IDLE) {
 
73
        LOG_WARNING("Init called with wrong state" << mState);
 
74
        return false;
 
75
    }
 
76
 
 
77
    mAccountName = properties.value("ACCOUNT-NAME").toString();
 
78
    mAuthToken = properties.value("AUTH-TOKEN").toString();
 
79
    mSyncTarget = properties.value("SYNC-TARGET").toString();
 
80
    mRemoteUri = properties.value(Buteo::KEY_REMOTE_DATABASE).toString();
 
81
    mStartIndex = 1;
 
82
    mPendingBatchOps.clear();
 
83
    mState = GRemoteSource::STATE_IDLE;
 
84
 
 
85
    if (mRemoteUri.isEmpty()) {
 
86
        // Set to the default value
 
87
        mRemoteUri = QStringLiteral("https://www.google.com/m8/feeds/contacts/default/full/");
 
88
    }
 
89
 
 
90
    LOG_DEBUG("Setting remote URI to" << mRemoteUri);
 
91
    mTransport->setUrl(mRemoteUri);
 
92
 
 
93
    QString proxyHost = properties.value(Buteo::KEY_HTTP_PROXY_HOST).toString();
 
94
    // Set proxy, if available
 
95
    if (!proxyHost.isEmpty()) {
 
96
        QString proxyPort = properties.value(Buteo::KEY_HTTP_PROXY_PORT).toString();
 
97
        mTransport->setProxy(proxyHost, proxyPort);
 
98
        LOG_DEBUG("Proxy host:" << proxyHost);
 
99
        LOG_DEBUG("Proxy port:" << proxyPort);
 
100
    } else {
 
101
        LOG_DEBUG("Not using proxy");
 
102
    }
 
103
 
 
104
    return true;
 
105
}
 
106
 
 
107
void GRemoteSource::abort()
 
108
{
 
109
    disconnect(mTransport.data());
 
110
    mState = STATE_ABORTED;
 
111
}
 
112
 
 
113
void GRemoteSource::fetchContacts(const QDateTime &since, bool includeDeleted, bool fetchAvatar)
 
114
{
 
115
    FUNCTION_CALL_TRACE;
 
116
    if (mState != GRemoteSource::STATE_IDLE) {
 
117
        LOG_WARNING("GRemote source is not in idle state, current state is" << mState);
 
118
        return;
 
119
    }
 
120
 
 
121
    mFetchAvatars = fetchAvatar;
 
122
    mState = GRemoteSource::STATE_FETCHING_CONTACTS;
 
123
    fetchRemoteContacts(since, includeDeleted, 1);
 
124
}
 
125
 
 
126
void GRemoteSource::fetchAvatars(QList<QContact> *contacts)
 
127
{
 
128
    // keep downloader object live while GRemoteSource exists to avoid removing
 
129
    // the temporary files used to store avatars.
 
130
    // The files will be removed when the object get destroyed
 
131
    GContactImageDownloader *downloader = new GContactImageDownloader(mAuthToken, this);
 
132
    QMap<QUrl, QPair<QContactAvatar, QContact*> > avatars;
 
133
 
 
134
    for(int i=0; i < contacts->size(); i++) {
 
135
        QContact &c = (*contacts)[i];
 
136
        foreach (const QContactAvatar &avatar, c.details<QContactAvatar>()) {
 
137
            if (!avatar.imageUrl().isLocalFile()) {
 
138
                LOG_DEBUG("Download avatar:" << avatar.imageUrl());
 
139
                avatars.insert(avatar.imageUrl(), qMakePair(avatar, &c));
 
140
                downloader->push(avatar.imageUrl());
 
141
            }
 
142
        }
 
143
    }
 
144
 
 
145
    downloader->exec();
 
146
 
 
147
    QMap<QUrl, QUrl> downloaded = downloader->donwloaded();
 
148
    foreach (const QUrl &avatarUrl, avatars.keys()) {
 
149
        QPair<QContactAvatar, QContact*> &p = avatars[avatarUrl];
 
150
        LOG_DEBUG("Replace avatar image:" <<  p.first.imageUrl() << downloaded.value(avatarUrl));
 
151
        p.first.setImageUrl(downloaded.value(avatarUrl));
 
152
        p.second->saveDetail(&p.first);
 
153
    }
 
154
}
 
155
 
 
156
void GRemoteSource::uploadAvatars(QList<QContact> *contacts)
 
157
{
 
158
    GContactImageUploader uploader(mAuthToken, mAccountName);
 
159
 
 
160
    foreach(const QContact &c, *contacts) {
 
161
        QString localId = UContactsBackend::getLocalId(c);
 
162
        LOG_DEBUG("Will upload avatar for:" << localId);
 
163
        if (mLocalIdToAvatar.contains(localId)) {
 
164
            QContactExtendedDetail rEtag =
 
165
                    UContactsCustomDetail::getCustomField(c,
 
166
                                                          UContactsCustomDetail::FieldContactAvatarETag);
 
167
 
 
168
            QPair<QString, QUrl> avatar = mLocalIdToAvatar.value(localId);
 
169
            LOG_DEBUG("Current avatar:"
 
170
                      << "\n\tlocal-etag:" << avatar.first
 
171
                      << "\n\tremote-etag:" << rEtag.data().toString()
 
172
                      << "\n\tlocal-url:" << avatar.second);
 
173
 
 
174
            // check if the remote etag has changed
 
175
            if (avatar.second.isLocalFile() &&
 
176
                (avatar.first.isEmpty() || (avatar.first != rEtag.data().toString()))) {
 
177
                QString remoteId = UContactsBackend::getRemoteId(c);
 
178
                LOG_DEBUG("Avatar revision changed:"
 
179
                          << "\n\tRemote version:" << rEtag.data().toString()
 
180
                          << "\n\tLocal version:" << avatar.first);
 
181
                LOG_DEBUG("Uploade avatar:" << remoteId << avatar.second);
 
182
                uploader.push(remoteId, avatar.second);
 
183
            } else if (!avatar.second.isLocalFile()) {
 
184
                LOG_DEBUG("Contact avatar is not local" << avatar.second);
 
185
            } else {
 
186
                LOG_DEBUG("Avatar did not change");
 
187
            }
 
188
        } else {
 
189
            LOG_WARNING("Local id not found found on avatar map:" << localId);
 
190
        }
 
191
    }
 
192
 
 
193
    uploader.exec();
 
194
 
 
195
    QMap<QString, GContactImageUploader::UploaderReply> result = uploader.result();
 
196
    for(int i =0; i < contacts->size(); i++) {
 
197
        QContact &c = (*contacts)[i];
 
198
 
 
199
        GContactImageUploader::UploaderReply reply = result.value(UContactsBackend::getRemoteId(c));
 
200
 
 
201
        // update contact e-tag if necessary
 
202
        if (!reply.newEtag.isEmpty()) {
 
203
            UContactsCustomDetail::setCustomField(c,
 
204
                                                  UContactsCustomDetail::FieldContactETag,
 
205
                                                  reply.newEtag);
 
206
        }
 
207
 
 
208
        // update avatar e-tag if necessary
 
209
        if (!reply.newAvatarEtag.isEmpty()) {
 
210
            UContactsCustomDetail::setCustomField(c,
 
211
                                                  UContactsCustomDetail::FieldContactAvatarETag,
 
212
                                                  reply.newAvatarEtag);
 
213
        }
 
214
 
 
215
        QString localId = UContactsBackend::getLocalId(c);
 
216
        // copy local url to new remote avatar
 
217
        QContactAvatar avatar = c.detail<QContactAvatar>();
 
218
        avatar.setImageUrl(mLocalIdToAvatar.value(localId).second);
 
219
        c.saveDetail(&avatar);
 
220
    }
 
221
}
 
222
 
 
223
void GRemoteSource::saveContactsNonBatch(const QList<QContact> contacts)
 
224
{
 
225
    FUNCTION_CALL_TRACE;
 
226
    if (mState != GRemoteSource::STATE_IDLE) {
 
227
        LOG_WARNING("GRemote source is not in idle state, current state is" << mState);
 
228
        return;
 
229
    }
 
230
 
 
231
    mState = GRemoteSource::STATE_BATCH_RUNNING;
 
232
    foreach (const QContact &contact, contacts) {
 
233
        QString remoteId = UContactsBackend::getRemoteId(contact);
 
234
        if (remoteId.isEmpty()) {
 
235
            mPendingBatchOps.insertMulti(GoogleContactStream::Add,
 
236
                                         qMakePair(contact, QStringList()));
 
237
        } else {
 
238
            mPendingBatchOps.insertMulti(GoogleContactStream::Modify,
 
239
                                         qMakePair(contact, QStringList()));
 
240
        }
 
241
    }
 
242
 
 
243
    batchOperationContinue();
 
244
}
 
245
 
 
246
void GRemoteSource::removeContactsNonBatch(const QList<QContact> contacts)
 
247
{
 
248
    FUNCTION_CALL_TRACE;
 
249
    if (mState != GRemoteSource::STATE_IDLE) {
 
250
        LOG_WARNING("GRemote source is not in idle state, current state is" << mState);
 
251
        return;
 
252
    }
 
253
 
 
254
    mState = GRemoteSource::STATE_BATCH_RUNNING;
 
255
    foreach (const QContact &contact, contacts) {
 
256
        mPendingBatchOps.insertMulti(GoogleContactStream::Remove,
 
257
                                     qMakePair(contact, QStringList()));
 
258
    }
 
259
 
 
260
    batchOperationContinue();
 
261
}
 
262
 
 
263
void GRemoteSource::batch(const QList<QContact> &contactsToCreate,
 
264
                          const QList<QContact> &contactsToUpdate,
 
265
                          const QList<QContact> &contactsToRemove)
 
266
{
 
267
    FUNCTION_CALL_TRACE;
 
268
    if (mState != GRemoteSource::STATE_IDLE) {
 
269
        LOG_WARNING("GRemote source is not in idle state, current state is" << mState);
 
270
        emit transactionCommited(QList<QContact>(),
 
271
                                 QList<QContact>(),
 
272
                                 QStringList(),
 
273
                                 QMap<QString, int>(),
 
274
                                 Sync::SYNC_ERROR);
 
275
        return;
 
276
    }
 
277
 
 
278
    mLocalIdToAvatar.clear();
 
279
    mState = GRemoteSource::STATE_BATCH_RUNNING;
 
280
 
 
281
    foreach (const QContact &contact, contactsToCreate) {
 
282
        QString localID = UContactsBackend::getLocalId(contact);
 
283
        QString avatarEtag =
 
284
                UContactsCustomDetail::getCustomField(contact,
 
285
                                                      UContactsCustomDetail::FieldContactAvatarETag).data().toString();
 
286
 
 
287
        mLocalIdToAvatar.insert(QString("qtcontacts:galera::%1").arg(localID),
 
288
                                qMakePair(avatarEtag, contact.detail<QContactAvatar>().imageUrl()));
 
289
        mPendingBatchOps.insertMulti(GoogleContactStream::Add,
 
290
                                     qMakePair(contact, QStringList()));
 
291
    }
 
292
 
 
293
    foreach (const QContact &contact, contactsToUpdate) {
 
294
        QString localID = UContactsBackend::getLocalId(contact);
 
295
        QString avatarEtag =
 
296
                UContactsCustomDetail::getCustomField(contact,
 
297
                                                      UContactsCustomDetail::FieldContactAvatarETag).data().toString();
 
298
 
 
299
        mLocalIdToAvatar.insert(QString("qtcontacts:galera::%1").arg(localID),
 
300
                                qMakePair(avatarEtag, contact.detail<QContactAvatar>().imageUrl()));
 
301
        mPendingBatchOps.insertMulti(GoogleContactStream::Modify,
 
302
                                     qMakePair(contact, QStringList()));
 
303
    }
 
304
 
 
305
    foreach (const QContact &contact, contactsToRemove) {
 
306
        mPendingBatchOps.insertMulti(GoogleContactStream::Remove,
 
307
                                     qMakePair(contact, QStringList()));
 
308
    }
 
309
 
 
310
    batchOperationContinue();
 
311
}
 
312
 
 
313
void
 
314
GRemoteSource::batchOperationContinue()
 
315
{
 
316
    FUNCTION_CALL_TRACE;
 
317
 
 
318
    if (mState == GRemoteSource::STATE_ABORTED) {
 
319
        LOG_WARNING("Operation aborted");
 
320
        return;
 
321
    }
 
322
 
 
323
    int limit = qMin(mPendingBatchOps.size(), GConfig::MAX_RESULTS);
 
324
    // no pending batch ops
 
325
    if (limit < 1)  {
 
326
        LOG_DEBUG ("No pending operations");
 
327
        emit transactionCommited(QList<QContact>(),
 
328
                                 QList<QContact>(),
 
329
                                 QStringList(),
 
330
                                 QMap<QString, int>(),
 
331
                                 Sync::SYNC_DONE);
 
332
        return;
 
333
    }
 
334
    QMultiMap<GoogleContactStream::UpdateType, QPair<QContact, QStringList> > batchPage;
 
335
    QPair<QContact, QStringList> value;
 
336
 
 
337
    while (batchPage.size() < limit) {
 
338
        GoogleContactStream::UpdateType type;
 
339
 
 
340
        if (!mPendingBatchOps.values(GoogleContactStream::Add).isEmpty()) {
 
341
            type = GoogleContactStream::Add;
 
342
        } else if (!mPendingBatchOps.values(GoogleContactStream::Modify).isEmpty()) {
 
343
            type = GoogleContactStream::Modify;
 
344
        } else if (!mPendingBatchOps.values(GoogleContactStream::Remove).isEmpty()) {
 
345
            type = GoogleContactStream::Remove;
 
346
        }
 
347
 
 
348
        value = mPendingBatchOps.values(type).first();
 
349
        mPendingBatchOps.remove(type, value);
 
350
        batchPage.insertMulti(type, value);
 
351
    }
 
352
 
 
353
    GoogleContactStream encoder(false, mAccountName);
 
354
    QByteArray encodedContacts = encoder.encode(batchPage);
 
355
 
 
356
    mTransport->reset();
 
357
    mTransport->setUrl(mRemoteUri + "batch");
 
358
    mTransport->setGDataVersionHeader();
 
359
    mTransport->setAuthToken(mAuthToken);
 
360
    mTransport->setData(encodedContacts);
 
361
    mTransport->addHeader("Content-Type", "application/atom+xml; charset=UTF-8; type=feed");
 
362
    LOG_TRACE("POST DATA:" << encodedContacts);
 
363
    mTransport->request(GTransport::POST);
 
364
}
 
365
 
 
366
int GRemoteSource::parseErrorReponse(const GoogleContactAtom::BatchOperationResponse &response)
 
367
{
 
368
    if ((response.code == "404") && (response.type == "update")) {
 
369
        // contact not found on remote side:
 
370
        return QContactManager::DoesNotExistError;
 
371
    }
 
372
 
 
373
    return QContactManager::UnspecifiedError;
 
374
}
 
375
 
 
376
void GRemoteSource::emitTransactionCommited(const QList<QContact> &created,
 
377
                                            const QList<QContact> &changed,
 
378
                                            const QList<QContact> &removed,
 
379
                                            const QMap<QString, int> &errorMap,
 
380
                                            Sync::SyncStatus status)
 
381
{
 
382
    FUNCTION_CALL_TRACE;
 
383
    LOG_INFO("ADDED:" << created.size() <<
 
384
             "CHANGED" << changed.size() <<
 
385
             "REMOVED" << removed.size());
 
386
 
 
387
    if (!created.isEmpty()) {
 
388
        emit contactsCreated(created, status);
 
389
    }
 
390
 
 
391
    if (!changed.isEmpty()) {
 
392
        emit contactsChanged(changed, status);
 
393
    }
 
394
 
 
395
    QStringList removedIds;
 
396
    foreach(const QContact &c, removed) {
 
397
        removedIds << UContactsCustomDetail::getCustomField(c, UContactsCustomDetail::FieldRemoteId).data().toString();
 
398
    }
 
399
 
 
400
    if (!removedIds.isEmpty()) {
 
401
        emit contactsRemoved(removedIds, status);
 
402
    }
 
403
 
 
404
    emit transactionCommited(created, changed, removedIds, errorMap, status);
 
405
}
 
406
 
 
407
void
 
408
GRemoteSource::fetchRemoteContacts(const QDateTime &since, bool includeDeleted, int startIndex)
 
409
{
 
410
    FUNCTION_CALL_TRACE;
 
411
    if (mState == GRemoteSource::STATE_ABORTED) {
 
412
        LOG_WARNING("Operation aborted");
 
413
        return;
 
414
    }
 
415
    /**
 
416
     o Get last sync time
 
417
     o Get etag value from local file system (this is a soft etag)
 
418
     o Connect finishedRequest to parseResults & network error slots
 
419
     o Use mTransport to perform network fetch
 
420
    */
 
421
    if (since.isValid()) {
 
422
        mTransport->setUpdatedMin(since);
 
423
    }
 
424
 
 
425
    if (startIndex > 1) {
 
426
        mTransport->setStartIndex(startIndex);
 
427
    }
 
428
 
 
429
    mTransport->setMaxResults(GConfig::MAX_RESULTS);
 
430
    if (includeDeleted) {
 
431
        mTransport->setShowDeleted();
 
432
    }
 
433
 
 
434
    // TODO: only fetch contacts from "My Contacts" group for now
 
435
    // we should implement support for all groups
 
436
    mTransport->setGroupFilter(mAccountName, GConfig::GROUP_MY_CONTACTS_ID);
 
437
 
 
438
    mTransport->setGDataVersionHeader();
 
439
    mTransport->addHeader(QByteArray("Authorization"),
 
440
                          QString("Bearer " + mAuthToken).toUtf8());
 
441
    mTransport->request(GTransport::GET);
 
442
}
 
443
 
 
444
/**
 
445
  * The state machine is pretty much maintained in this method.
 
446
  * Maybe it is better to create a separate class that can handle
 
447
  * the sync state. It can act of Qt signals that will be emitted
 
448
  * by this method
 
449
  */
 
450
void
 
451
GRemoteSource::networkRequestFinished()
 
452
{
 
453
    FUNCTION_CALL_TRACE;
 
454
 
 
455
    if (mState == GRemoteSource::STATE_ABORTED) {
 
456
        LOG_WARNING("Operation aborted");
 
457
        return;
 
458
    }
 
459
 
 
460
    // o Error - if network error, set the sync results with the code
 
461
    // o Call uninit
 
462
    // o Stop sync
 
463
    // o If success, invoke the mParser->parse ()
 
464
    Sync::SyncStatus syncStatus = Sync::SYNC_ERROR;
 
465
    GTransport::HTTP_REQUEST_TYPE requestType = mTransport->requestType();
 
466
    if (mTransport->hasReply()) {
 
467
        QByteArray data = mTransport->replyBody();
 
468
        LOG_TRACE(data);
 
469
        if (data.isNull () || data.isEmpty()) {
 
470
            LOG_INFO("Nothing returned from server");
 
471
            syncStatus = Sync::SYNC_CONNECTION_ERROR;
 
472
            goto operationFailed;
 
473
        }
 
474
 
 
475
        GoogleContactStream parser(false);
 
476
        GoogleContactAtom *atom = parser.parse(data);
 
477
        if (!atom) {
 
478
            LOG_CRITICAL("NULL atom object. Something wrong with parsing");
 
479
            goto operationFailed;
 
480
        }
 
481
 
 
482
        if ((requestType == GTransport::POST) ||
 
483
            (requestType == GTransport::PUT)) {
 
484
            QList<QContact> addedContacts;
 
485
            QList<QContact> modContacts;
 
486
            QList<QContact> delContacts;
 
487
            QMap<QString, int> errorMap;
 
488
 
 
489
            LOG_DEBUG("@@@PREVIOUS REQUEST TYPE=POST");
 
490
            QMap<QString, GoogleContactAtom::BatchOperationResponse> operationResponses = atom->batchOperationResponses();
 
491
            QMap<QString, QString> batchOperationRemoteIdToType;
 
492
            QMap<QString, QString> batchOperationRemoteToLocalId;
 
493
 
 
494
            LOG_DEBUG("RESPONSE SIZE:" << operationResponses.size());
 
495
            foreach (const GoogleContactAtom::BatchOperationResponse &response, operationResponses) {
 
496
                if (response.isError) {
 
497
                    LOG_CRITICAL("batch operation error:\n"
 
498
                              "    id:     " << response.operationId << "\n"
 
499
                              "    type:   " << response.type << "\n"
 
500
                              "    code:   " << response.code << "\n"
 
501
                              "    reason: " << response.reason << "\n"
 
502
                              "    descr:  " << response.reasonDescription << "\n");
 
503
                    errorMap.insert(response.operationId, parseErrorReponse(response));
 
504
                } else {
 
505
                    LOG_DEBUG("RESPONSE" << response.contactGuid << response.type);
 
506
                    batchOperationRemoteToLocalId.insert(response.contactGuid, response.operationId);
 
507
                    batchOperationRemoteIdToType.insert(response.contactGuid, response.type);
 
508
                }
 
509
            }
 
510
 
 
511
            QList<QPair<QContact, QStringList> > remoteAddModContacts = atom->entryContacts();
 
512
            LOG_DEBUG("Number of changed contacts:" << remoteAddModContacts.size());
 
513
            for (int i = 0; i < remoteAddModContacts.size(); i++) {
 
514
                QContact &c = remoteAddModContacts[i].first;
 
515
                QContactGuid guid = c.detail<QContactGuid>();
 
516
                QString cRemoteId = guid.guid();
 
517
                if (cRemoteId.isEmpty()) {
 
518
                    LOG_WARNING("Remote contact without remote id");
 
519
                    continue;
 
520
                }
 
521
                UContactsBackend::setRemoteId(c, cRemoteId);
 
522
                UContactsBackend::setLocalId(c, batchOperationRemoteToLocalId.value(cRemoteId, ""));
 
523
 
 
524
                QString opType = batchOperationRemoteIdToType.value(cRemoteId, "");
 
525
                c.removeDetail(&guid);
 
526
                if (opType == QStringLiteral("insert")) {
 
527
                    addedContacts << c;
 
528
                } else {
 
529
                    modContacts << c;
 
530
                }
 
531
            }
 
532
            delContacts += atom->deletedEntryContacts();
 
533
            LOG_DEBUG("Number of deleted contacts:" << delContacts.size());
 
534
 
 
535
            if (!atom->nextEntriesUrl().isEmpty()) {
 
536
                syncStatus = Sync::SYNC_PROGRESS;
 
537
            } else {
 
538
                //TODO: avatars
 
539
                syncStatus = Sync::SYNC_DONE;
 
540
                mState = GRemoteSource::STATE_IDLE;
 
541
            }
 
542
 
 
543
            uploadAvatars(&addedContacts);
 
544
            uploadAvatars(&modContacts);
 
545
 
 
546
            emitTransactionCommited(addedContacts, modContacts, delContacts, errorMap, syncStatus);
 
547
 
 
548
            if (syncStatus == Sync::SYNC_PROGRESS) {
 
549
                batchOperationContinue();
 
550
            } else {
 
551
                mLocalIdToAvatar.clear();
 
552
            }
 
553
        } else if (requestType == GTransport::GET) {
 
554
            LOG_DEBUG ("@@@PREVIOUS REQUEST TYPE=GET");
 
555
            if (mState != GRemoteSource::STATE_FETCHING_CONTACTS) {
 
556
                LOG_WARNING("Received a network request finish but the state is not fetching contacts" << mState);
 
557
                return;
 
558
            }
 
559
 
 
560
            LOG_INFO("received information about" <<
 
561
                     atom->entryContacts().size() << "add/mod contacts and " <<
 
562
                     atom->deletedEntryContacts().size() << "del contacts");
 
563
 
 
564
            QList<QContact> remoteContacts;
 
565
 
 
566
            // for each remote contact, there are some associated XML elements which
 
567
            // could not be stored in QContactDetail form (eg, link URIs etc).
 
568
            // build up some datastructures to help us retrieve that information
 
569
            // when we need it.
 
570
            // we also store the etag data out-of-band to avoid spurious contact saves
 
571
            // when the etag changes are reported by the remote server.
 
572
            // finally, we can set the id of the contact.
 
573
            QList<QPair<QContact, QStringList> > remoteAddModContacts = atom->entryContacts();
 
574
            for (int i = 0; i < remoteAddModContacts.size(); ++i) {
 
575
                QContact c = remoteAddModContacts[i].first;
 
576
                QContactGuid guid =  c.detail<QContactGuid>();
 
577
                UContactsBackend::setRemoteId(c, guid.guid());
 
578
                c.removeDetail(&guid);
 
579
                // FIXME: This code came from the meego implementation, until now we did not face
 
580
                // any unsupported xml element. Keep the code here in case some unsupported element
 
581
                // apper. Then we should store it some how on our backend.
 
582
                //  m_unsupportedXmlElements[accountId].insert(
 
583
                //          c.detail<QContactGuid>().guid(),
 
584
                //          remoteAddModContacts[i].second);
 
585
                //  m_contactEtags[accountId].insert(c.detail<QContactGuid>().guid(), c.detail<QContactOriginMetadata>().id());
 
586
                // c.setId(QContactId::fromString(m_contactIds[accountId].value(c.detail<QContactGuid>().guid())));
 
587
                // m_remoteAddMods[accountId].append(c);
 
588
                remoteContacts << c;
 
589
            }
 
590
 
 
591
            if (mFetchAvatars) {
 
592
                fetchAvatars(&remoteContacts);
 
593
            }
 
594
 
 
595
            QList<QContact> remoteDelContacts = atom->deletedEntryContacts();
 
596
            for (int i = 0; i < remoteDelContacts.size(); ++i) {
 
597
                QContact c = remoteDelContacts[i];
 
598
                QContactGuid guid =  c.detail<QContactGuid>();
 
599
                UContactsBackend::setRemoteId(c, guid.guid());
 
600
                c.removeDetail(&guid);
 
601
                // FIXME
 
602
                // c.setId(QContactId::fromString(m_contactIds[accountId].value(c.detail<QContactGuid>().guid())));
 
603
                // m_contactAvatars[accountId].remove(c.detail<QContactGuid>().guid()); // just in case the avatar was outstanding.
 
604
                // m_remoteDels[accountId].append(c);
 
605
                remoteContacts << c;
 
606
            }
 
607
 
 
608
            bool hasMore = (!atom->nextEntriesUrl().isNull() ||
 
609
                            !atom->nextEntriesUrl().isEmpty());
 
610
            if (hasMore) {
 
611
                // Request for the next batch
 
612
                // This condition will make this slot to be
 
613
                // called again and again until there are no more
 
614
                // entries left to be fetched from the server
 
615
                mStartIndex += GConfig::MAX_RESULTS;
 
616
                mTransport->setUrl(atom->nextEntriesUrl());
 
617
                syncStatus = Sync::SYNC_PROGRESS;
 
618
                LOG_DEBUG("Has more contacts to retrieve");
 
619
            } else {
 
620
                LOG_DEBUG("NO contacts to retrieve");
 
621
                syncStatus = Sync::SYNC_DONE;
 
622
                mState = GRemoteSource::STATE_IDLE;
 
623
            }
 
624
 
 
625
            // progress
 
626
            qreal progress = -1.0;
 
627
            if (hasMore && (atom->totalResults() > 0)) {
 
628
                progress = (mStartIndex / (qreal) atom->totalResults());
 
629
            } else if (!hasMore) {
 
630
                progress = 1.0;
 
631
            }
 
632
 
 
633
            LOG_TRACE("NOTIFY CONTACTS FETCHED:" << remoteContacts.size() << "Progress" << progress);
 
634
            emit contactsFetched(remoteContacts, syncStatus, progress);
 
635
 
 
636
            if (hasMore) {
 
637
                LOG_TRACE("FETCH MORE CONTACTS FROM INDEX:" << mStartIndex);
 
638
                fetchRemoteContacts(QDateTime(),
 
639
                                    mTransport->showDeleted(),
 
640
                                    mStartIndex);
 
641
            }
 
642
        }
 
643
        delete atom;
 
644
    }
 
645
    return;
 
646
 
 
647
operationFailed:
 
648
    switch(mState) {
 
649
    case GRemoteSource::STATE_FETCHING_CONTACTS:
 
650
        contactsFetched(QList<QContact>(), syncStatus, -1.0);
 
651
        break;
 
652
    case GRemoteSource::STATE_BATCH_RUNNING:
 
653
        emitTransactionCommited(QList<QContact>(),
 
654
                                QList<QContact>(),
 
655
                                QList<QContact>(),
 
656
                                QMap<QString, int>(),
 
657
                                syncStatus);
 
658
        break;
 
659
    default:
 
660
        break;
 
661
    }
 
662
    mState = GRemoteSource::STATE_IDLE;
 
663
}
 
664
 
 
665
void
 
666
GRemoteSource::networkError(int errorCode)
 
667
{
 
668
    FUNCTION_CALL_TRACE;
 
669
 
 
670
    Sync::SyncStatus syncStatus = Sync::SYNC_ERROR;
 
671
    switch (errorCode)
 
672
    {
 
673
    case 400:
 
674
        // Bad request. Better to bail out, since it could be a problem with the
 
675
        // data format of the request/response
 
676
        syncStatus = Sync::SYNC_BAD_REQUEST;
 
677
        break;
 
678
    case 401:
 
679
        syncStatus = Sync::SYNC_AUTHENTICATION_FAILURE;
 
680
        break;
 
681
    case 403:
 
682
    case 408:
 
683
        syncStatus = Sync::SYNC_ERROR;
 
684
        break;
 
685
    case 500:
 
686
    case 503:
 
687
    case 504:
 
688
        // Server failures
 
689
        syncStatus = Sync::SYNC_SERVER_FAILURE;
 
690
        break;
 
691
    default:
 
692
        break;
 
693
    };
 
694
 
 
695
    switch(mState) {
 
696
    case GRemoteSource::STATE_FETCHING_CONTACTS:
 
697
        contactsFetched(QList<QContact>(), syncStatus, -1.0);
 
698
        break;
 
699
    case GRemoteSource::STATE_BATCH_RUNNING:
 
700
        emitTransactionCommited(QList<QContact>(),
 
701
                                QList<QContact>(),
 
702
                                QList<QContact>(),
 
703
                                QMap<QString, int>(),
 
704
                                syncStatus);
 
705
        break;
 
706
    default:
 
707
        break;
 
708
    }
 
709
    mState = GRemoteSource::STATE_IDLE;
 
710
}
 
711
 
 
712
const GTransport *GRemoteSource::transport() const
 
713
{
 
714
    return mTransport.data();
 
715
}
 
716
 
 
717
int GRemoteSource::state() const
 
718
{
 
719
    return mState;
 
720
}