Merge lp:~renatofilho/buteo-sync-plugins-contacts/new-code into lp:buteo-sync-plugins-contacts

Proposed by Renato Araujo Oliveira Filho
Status: Merged
Approved by: Michael Sheldon
Approved revision: 56
Merged at revision: 4
Proposed branch: lp:~renatofilho/buteo-sync-plugins-contacts/new-code
Merge into: lp:buteo-sync-plugins-contacts
Diff against target: 15372 lines (+14750/-0)
121 files modified
CMakeLists.txt (+59/-0)
accounts/CMakeLists.txt (+14/-0)
accounts/address-book-app.application (+11/-0)
accounts/google-contacts.service (+6/-0)
buteo-contact-client/CMakeLists.txt (+37/-0)
buteo-contact-client/UAbstractRemoteSource.cpp (+178/-0)
buteo-contact-client/UAbstractRemoteSource.h (+125/-0)
buteo-contact-client/UAuth.cpp (+181/-0)
buteo-contact-client/UAuth.h (+72/-0)
buteo-contact-client/UContactsBackend.cpp (+572/-0)
buteo-contact-client/UContactsBackend.h (+254/-0)
buteo-contact-client/UContactsClient.cpp (+1028/-0)
buteo-contact-client/UContactsClient.h (+170/-0)
buteo-contact-client/UContactsCustomDetail.cpp (+56/-0)
buteo-contact-client/UContactsCustomDetail.h (+52/-0)
cmake/lcov.cmake (+72/-0)
cmake_uninstall.cmake.in (+21/-0)
config.h.in (+9/-0)
google/CMakeLists.txt (+87/-0)
google/GConfig.cpp (+52/-0)
google/GConfig.h (+71/-0)
google/GContactAtom.cpp (+209/-0)
google/GContactAtom.h (+138/-0)
google/GContactGroupsMap.cpp (+122/-0)
google/GContactGroupsMap.h (+60/-0)
google/GContactImageDownloader.cpp (+117/-0)
google/GContactImageDownloader.h (+68/-0)
google/GContactImageUploader.cpp (+204/-0)
google/GContactImageUploader.h (+78/-0)
google/GContactStream.cpp (+1446/-0)
google/GContactStream.h (+190/-0)
google/GContactsClient.cpp (+83/-0)
google/GContactsClient.h (+58/-0)
google/GRemoteSource.cpp (+720/-0)
google/GRemoteSource.h (+95/-0)
google/GTransport.cpp (+450/-0)
google/GTransport.h (+103/-0)
google/LICENSE (+24/-0)
google/README.md (+4/-0)
google/atom_global.h (+37/-0)
google/buteo-gcontact-plugin_global.h (+37/-0)
google/buteosyncfw_p.h (+40/-0)
google/xmls/CMakeLists.txt (+2/-0)
google/xmls/client/CMakeLists.txt (+3/-0)
google/xmls/client/googlecontacts.xml (+8/-0)
google/xmls/sync/CMakeLists.txt (+3/-0)
google/xmls/sync/google-contacts.xml (+20/-0)
storage-change-notifier/CMakeLists.txt (+1/-0)
storage-change-notifier/contacts/CMakeLists.txt (+31/-0)
storage-change-notifier/contacts/ContactsChangeNotifier.cpp (+95/-0)
storage-change-notifier/contacts/ContactsChangeNotifier.h (+70/-0)
storage-change-notifier/contacts/ContactsChangeNotifierPlugin.cpp (+101/-0)
storage-change-notifier/contacts/ContactsChangeNotifierPlugin.h (+75/-0)
tests/CMakeLists.txt (+4/-0)
tests/plugins/CMakeLists.txt (+1/-0)
tests/plugins/contacts/CMakeLists.txt (+12/-0)
tests/plugins/contacts/memory.json (+3/-0)
tests/plugins/contacts/qcontactmemorybackend.cpp (+1104/-0)
tests/plugins/contacts/qcontactmemorybackend_p.h (+245/-0)
tests/unittest/CMakeLists.txt (+93/-0)
tests/unittest/GContactImageUploader.cpp (+108/-0)
tests/unittest/GTransport.cpp (+249/-0)
tests/unittest/MockAuthenticator.cpp (+49/-0)
tests/unittest/MockAuthenticator.h (+49/-0)
tests/unittest/MockRemoteSource.cpp (+273/-0)
tests/unittest/MockRemoteSource.h (+74/-0)
tests/unittest/TestContactsClient.cpp (+78/-0)
tests/unittest/TestContactsClient.h (+59/-0)
tests/unittest/TestContactsMain.cpp (+483/-0)
tests/unittest/TestGRemoteSource.cpp (+571/-0)
tests/unittest/TestGoogleContactParser.cpp (+1049/-0)
tests/unittest/config-tests.h.in (+10/-0)
tests/unittest/data/fast_sync_changed_both_sides_local.vcf (+47/-0)
tests/unittest/data/fast_sync_changed_both_sides_local_result.vcf (+21/-0)
tests/unittest/data/fast_sync_changed_both_sides_remote.vcf (+39/-0)
tests/unittest/data/fast_sync_changed_both_sides_remote_result.vcf (+18/-0)
tests/unittest/data/fast_sync_delete_same_contact_local.vcf (+47/-0)
tests/unittest/data/fast_sync_delete_same_contact_local_result.vcf (+34/-0)
tests/unittest/data/fast_sync_delete_same_contact_remote.vcf (+39/-0)
tests/unittest/data/fast_sync_delete_same_contact_remote_result.vcf (+28/-0)
tests/unittest/data/fast_sync_with_a_local_change_local.vcf (+46/-0)
tests/unittest/data/fast_sync_with_a_local_change_local_result.vcf (+45/-0)
tests/unittest/data/fast_sync_with_a_local_change_remote.vcf (+38/-0)
tests/unittest/data/fast_sync_with_a_local_change_remote_result.vcf (+38/-0)
tests/unittest/data/fast_sync_with_a_local_removal_local.vcf (+47/-0)
tests/unittest/data/fast_sync_with_a_local_removal_local_result.vcf (+34/-0)
tests/unittest/data/fast_sync_with_a_local_removal_remote.vcf (+38/-0)
tests/unittest/data/fast_sync_with_a_local_removal_remote_result.vcf (+28/-0)
tests/unittest/data/fast_sync_with_a_remote_change_local.vcf (+45/-0)
tests/unittest/data/fast_sync_with_a_remote_change_local_result.vcf (+45/-0)
tests/unittest/data/fast_sync_with_a_remote_change_remote.vcf (+38/-0)
tests/unittest/data/fast_sync_with_a_remote_change_remote_result.vcf (+38/-0)
tests/unittest/data/fast_sync_with_a_remote_removal_local.vcf (+46/-0)
tests/unittest/data/fast_sync_with_a_remote_removal_local_result.vcf (+34/-0)
tests/unittest/data/fast_sync_with_a_remote_removal_remote.vcf (+39/-0)
tests/unittest/data/fast_sync_with_a_remote_removal_remote_result.vcf (+28/-0)
tests/unittest/data/fast_sync_with_new_local_contact_local.vcf (+20/-0)
tests/unittest/data/fast_sync_with_new_local_contact_local_result.vcf (+21/-0)
tests/unittest/data/fast_sync_with_new_local_contact_remote.vcf (+8/-0)
tests/unittest/data/fast_sync_with_new_local_contact_remote_result.vcf (+18/-0)
tests/unittest/data/fast_sync_without_changes_local.vcf (+46/-0)
tests/unittest/data/fast_sync_without_changes_local_result.vcf (+46/-0)
tests/unittest/data/fast_sync_without_changes_remote.vcf (+38/-0)
tests/unittest/data/fast_sync_without_changes_remote_result.vcf (+38/-0)
tests/unittest/data/google_contact_created_page.txt (+25/-0)
tests/unittest/data/google_contact_full_fetch.vcf (+200/-0)
tests/unittest/data/google_contact_full_fetch_page_0.txt (+297/-0)
tests/unittest/data/google_contact_full_fetch_page_1.txt (+158/-0)
tests/unittest/data/google_contact_updated_reply.txt (+28/-0)
tests/unittest/data/google_not_found_contact_response.txt (+31/-0)
tests/unittest/data/google_single_entry.txt (+162/-0)
tests/unittest/data/local_previous_synced_local.vcf (+10/-0)
tests/unittest/data/local_previous_synced_local_result.vcf (+38/-0)
tests/unittest/data/local_previous_synced_remote.vcf (+27/-0)
tests/unittest/data/local_previous_synced_remote_result.vcf (+34/-0)
tests/unittest/data/new_database_local.vcf (+7/-0)
tests/unittest/data/new_database_local_result.vcf (+38/-0)
tests/unittest/data/new_database_remote.vcf (+27/-0)
tests/unittest/data/new_database_remote_result.vcf (+34/-0)
tests/unittest/data/slow_sync_with_pages_remote.vcf (+143/-0)
tests/unittest/profile-test.xml (+23/-0)
To merge this branch: bzr merge lp:~renatofilho/buteo-sync-plugins-contacts/new-code
Reviewer Review Type Date Requested Status
Michael Sheldon (community) Approve
PS Jenkins bot continuous-integration Needs Fixing
Renato Araujo Oliveira Filho Pending
Review via email: mp+264335@code.launchpad.net

Commit message

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

To post a comment you must log in.
6. By Renato Araujo Oliveira Filho

Added support for ringtones sync.
Fixed Notes parse.
Fixed Gender parse.

7. By Renato Araujo Oliveira Filho

Trunk merged.

8. By Renato Araujo Oliveira Filho

Create contact source linked with the online account id.

9. By Renato Araujo Oliveira Filho

Use online account display name as local address book name.

10. By Renato Araujo Oliveira Filho

Update contact avatar with a empty string if its fail to download.

11. By Renato Araujo Oliveira Filho

Added coverage support on cmake. (use with make lcov)
Write more unit tests.

12. By Renato Araujo Oliveira Filho

Increased test coverability.

13. By Renato Araujo Oliveira Filho

Check for "gd:etag" tag on photo link.

if a contact does not have a photo, then the photo link element has no gd:etag attribute.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
14. By Renato Araujo Oliveira Filho

Fixed avatar parse.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
15. By Renato Araujo Oliveira Filho

Changed build to allow mock GContactImageUploader;
Created test for GContactImageUploader;

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
16. By Renato Araujo Oliveira Filho

Fix QContactOrganization parse.

17. By Renato Araujo Oliveira Filho

Set 60s as interval for sync on change.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
18. By Renato Araujo Oliveira Filho

Added 'remote_service_name' key into sync profile template.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
19. By Renato Araujo Oliveira Filho

Changed page result size from 50 to 30.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

I see that you've now marked the buteo-sync-plugins-contacts-google package as conflicting with evolution-data-server-online-accounts, is it not possible to name the buteo plugin differently so that the two can co-exist? It might not be an issue on the phone, but I imagine a lot of desktop users might want to continue using evolution when in desktop mode.

review: Needs Information
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

It is correct to say that these files are part of buteo-gcontact-plugins in the copyright statements, or is this effectively a new project derived from buteo-gcontact-plugins?

I've also added a number of small things in the diff comments (mostly just typos, but also a couple of questions about some TODOs and an unused variable).

(I've only reviewed the first 1000 lines of the diff so far, so there'll be more to come)

review: Needs Fixing
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Highlighted a number of places where you're using qDebug (most of the rest of the code uses LOG_DEBUG), I've only checked on the part of the diff shown on the MR (the first 5000 lines) so you may want to quickly grep the rest of the code as well.

review: Needs Fixing
20. By Renato Araujo Oliveira Filho

Replaced use of qDebug() to LOG_DEBUG

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

More in-line comments (only as far as line 2000 at the moment), mostly small typos but also a couple of questions in there too.

Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

> I see that you've now marked the buteo-sync-plugins-contacts-google package as
> conflicting with evolution-data-server-online-accounts, is it not possible to
> name the buteo plugin differently so that the two can co-exist? It might not
> be an issue on the phone, but I imagine a lot of desktop users might want to
> continue using evolution when in desktop mode.

The problem is that both install an online-account contact service file "google-contacts.service". And since they have the same functionality (Sync google contacts into EDS database). I do not see a use case where the user will want to have both packages installed.

21. By Renato Araujo Oliveira Filho

Fixed typos.
Fixed project name on license header.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) :
22. By Renato Araujo Oliveira Filho

Fixed typos.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Up to line 3000 now, still mostly just small corrections and a few questions.

23. By Renato Araujo Oliveira Filho

Purge contacts removed since last sync only.
Fixed sync time report.

24. By Renato Araujo Oliveira Filho

Handle error in case of contact removed remote during a previous sync.

25. By Renato Araujo Oliveira Filho

Uncomment handleUploadError function call.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
26. By Renato Araujo Oliveira Filho

Delay 2 secs before fire sync finished signal. To wait until galera backed propagate the changes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Added diff comments up to line 5000, noticed a few seemingly unnecessary string conversions, otherwise just more tiny things and questions.

Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Up to line 7000, Launchpad can't seem to handle more than 5000 lines, so I'll include further remarks in comments:

Line 5137
+ QContactExtendedDetail group;
+ group.setName(UContactsCustomDetail::FieldGroupMembershipInfo);
+ group.setData("6");

Can you add a comment explaining the significance of "6"? (Judging from later code I'm guessing this is the 'My Contacts' group?)

Line 5472
+ protocolMap.insert(QContactOnlineAccount::ProtocolJabber, "JABBER");
+ protocolMap.insert(QContactOnlineAccount::ProtocolAim, "AIM");
+ protocolMap.insert(QContactOnlineAccount::ProtocolIcq, "ICQ");
+ protocolMap.insert(QContactOnlineAccount::ProtocolMsn, "MSN");
+ protocolMap.insert(QContactOnlineAccount::ProtocolQq, "QQ");
+ protocolMap.insert(QContactOnlineAccount::ProtocolYahoo, "YAHOO");
+ protocolMap.insert(QContactOnlineAccount::ProtocolSkype, "SKYPE");
+ protocolMap.insert(QContactOnlineAccount::ProtocolIrc, "IRC");

I notice that this list doesn't include GOOGLE_TALK but an earlier one did, should they not match?

Line 6089
+ // keep downloader object live while GRemoreSource exists to avoid remove
+ // the temporary files used to store avatars

GRemoreSource -> GRemoteSource, remove -> removing

Line 6522
+ // FIXME
+ // m_unsupportedXmlElements[accountId].insert(
+ // c.detail<QContactGuid>().guid(),
+ // remoteAddModContacts[i].second);
+ // m_contactEtags[accountId].insert(c.detail<QContactGuid>().guid(), c.detail<QContactOriginMetadata>().id());
+ // c.setId(QContactId::fromString(m_contactIds[accountId].value(c.detail<QContactGuid>().guid())));
+ // m_remoteAddMods[accountId].append(c);

Can you add details about what needs fixing here? Also the same for line 6542

Line 6604
+ // TODO: If interested, check the value of error. But
+ // it is enough to say that it is a SYNC_CONNECTION_ERROR
+ //emit syncFinished (Sync::SYNC_CONNECTION_ERROR);

The code following this looks like it is now checking the type of sync error, so this TODO can probably be removed.

I also just noticed that all the updated copyright notices have a typo in the name, they should be "buteo-sync-plugins-google" not "buteo-sync-plugins-goole" (missing "g" in google).

27. By Renato Araujo Oliveira Filho

Handle contact not found '404', during a contact update.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) :
28. By Renato Araujo Oliveira Filho

Use "ClientId", "ClientSecret" values already present in account information.

29. By Renato Araujo Oliveira Filho

Replaced use of "QLatin1String" in favor or "QStringLiteral"

30. By Renato Araujo Oliveira Filho

Predefine string to keep consistence.

31. By Renato Araujo Oliveira Filho

Does not parse google online account as jabber account.

Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

> Up to line 7000, Launchpad can't seem to handle more than 5000 lines, so I'll
> include further remarks in comments:
>
>
> Line 5137
> + QContactExtendedDetail group;
> + group.setName(UContactsCustomDetail::FieldGroupMembershipInfo);
> + group.setData("6");
>
> Can you add a comment explaining the significance of "6"? (Judging from later
> code I'm guessing this is the 'My Contacts' group?)
Fixed rev. 32

>
>
> Line 5472
> + protocolMap.insert(QContactOnlineAccount::ProtocolJabber, "JABBER");
> + protocolMap.insert(QContactOnlineAccount::ProtocolAim, "AIM");
> + protocolMap.insert(QContactOnlineAccount::ProtocolIcq, "ICQ");
> + protocolMap.insert(QContactOnlineAccount::ProtocolMsn, "MSN");
> + protocolMap.insert(QContactOnlineAccount::ProtocolQq, "QQ");
> + protocolMap.insert(QContactOnlineAccount::ProtocolYahoo, "YAHOO");
> + protocolMap.insert(QContactOnlineAccount::ProtocolSkype, "SKYPE");
> + protocolMap.insert(QContactOnlineAccount::ProtocolIrc, "IRC");
>
> I notice that this list doesn't include GOOGLE_TALK but an earlier one did,
> should they not match?
QContactOnlineAccount does not support "google talk" as a protocol, in this case if the protocol value is not in the protocolMap we fallback to serviceProvider() name.

    QString protocolName = protocolMap.value(onlineAccount.protocol(),
                                             onlineAccount.serviceProvider());
Fixed in Rev. 31

>
>
> Line 6089
> + // keep downloader object live while GRemoreSource exists to avoid remove
> + // the temporary files used to store avatars
>
> GRemoreSource -> GRemoteSource, remove -> removing
Fixed rev 33.

>
>
> Line 6522
> + // FIXME
> + // m_unsupportedXmlElements[accountId].insert(
> + // c.detail<QContactGuid>().guid(),
> + // remoteAddModContacts[i].second);
> + //
> m_contactEtags[accountId].insert(c.detail<QContactGuid>().guid(),
> c.detail<QContactOriginMetadata>().id());
> + // c.setId(QContactId::fromString(m_contactIds[accountId].val
> ue(c.detail<QContactGuid>().guid())));
> + // m_remoteAddMods[accountId].append(c);
>
> Can you add details about what needs fixing here? Also the same for line 6542
Fixed rev. 34

>
>
> Line 6604
> + // TODO: If interested, check the value of error. But
> + // it is enough to say that it is a SYNC_CONNECTION_ERROR
> + //emit syncFinished (Sync::SYNC_CONNECTION_ERROR);
>
> The code following this looks like it is now checking the type of sync error,
> so this TODO can probably be removed.
Fixed rev. 35.

>
>
> I also just noticed that all the updated copyright notices have a typo in the
> name, they should be "buteo-sync-plugins-google" not "buteo-sync-plugins-
> goole" (missing "g" in google).
Fixed rev. 36

32. By Renato Araujo Oliveira Filho

Added "FiXME" message for hardcoded google default group.

33. By Renato Araujo Oliveira Filho

Fixed typos in comments.

34. By Renato Araujo Oliveira Filho

Added more information about the FIXME comment.

35. By Renato Araujo Oliveira Filho

Removed old TODO comment.

36. By Renato Araujo Oliveira Filho

Fixed typo.

37. By Renato Araujo Oliveira Filho

Added missing test data file.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
38. By Renato Araujo Oliveira Filho

Propagate error from remote source to UContactsClient.
Handle error contact not found during a update operation, on UContactsClient class.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
39. By Renato Araujo Oliveira Filho

[google-contact] Make sure that the old contact avatar ulr get copied back to remote contact before return it back to UContactClient.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

This is the last batch of issues going all the way to the end of the diff, so once these have been addressed I'll do a quick check over the previous corrections and then we should be good to approve this from a general code review stand point.

Line 7321
+ // Include "X-HTTP-Method-Override: DELETE" in the delete POST method to avoid blocking of HTTP DELETE message by firewalls
+ //const void DELETE( const QString contactId );

Should this still be here (currently commented out)?

I think you need to add Canonical and yourself to google/LICENSE

Line 8099,
+ /*! \brief constructor
+ */
+ ~ContactsChangeNotifier();

constructor -> destructor

google/Makefile is currently in version control, presumably this can be removed as it's a generated file?

tests/unittest/CMakeLists.txt appears to have DOS line endings (just a quick run through dos2unix will fix this)

Line 11156
+ // remote source will be initialized after sucess
sucess -> success

Line 11187
+ QTest::newRow("page division exaclty") << 5
exaclty -> exactly

Line 11258
+ // trasaction commit will be fired once with empty list
trasaction -> transaction

Line 11751
+ // chek if avatar still the same
chek -> check

Line 11843
+ // check if the repported error is correct
repported -> reported

Line 11923
+ // check each contat details
contat -> contact

Line 12769
+ // Anniversay
Anniversay -> Anniversary

All the test VCF files except for tests/unittest/data/slow_sync_with_pages_remote.vcf have DOS style line endings.

Is there any package that supplies QContactMemoryBackend in a way that could be linked against to remove the need to including the source for it as part of this package?

Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

> Line 7321
> + // Include "X-HTTP-Method-Override: DELETE" in the delete POST method to
> avoid blocking of HTTP DELETE message by firewalls
> + //const void DELETE( const QString contactId );
>
> Should this still be here (currently commented out)?
Fixed rev. 40

>
> I think you need to add Canonical and yourself to google/LICENSE
Fixed rev. 41

>
>
> Line 8099,
> + /*! \brief constructor
> + */
> + ~ContactsChangeNotifier();
>
> constructor -> destructor
Fixed rev. 42
>
>
> google/Makefile is currently in version control, presumably this can be
> removed as it's a generated file?
Fixed rev. 43

>
>
> tests/unittest/CMakeLists.txt appears to have DOS line endings (just a quick
> run through dos2unix will fix this)
Fixed rev. 44

>
>
> Line 11156
> + // remote source will be initialized after sucess
> sucess -> success
>
>
> Line 11187
> + QTest::newRow("page division exaclty") << 5
> exaclty -> exactly
>
>
> Line 11258
> + // trasaction commit will be fired once with empty list
> trasaction -> transaction
>
>
> Line 11751
> + // chek if avatar still the same
> chek -> check
>
>
> Line 11843
> + // check if the repported error is correct
> repported -> reported
>
>
> Line 11923
> + // check each contat details
> contat -> contact
>
>
> Line 12769
> + // Anniversay
> Anniversay -> Anniversary
>
>
> All the test VCF files except for
> tests/unittest/data/slow_sync_with_pages_remote.vcf have DOS style line
> endings.
The default EOL for vcards is the DOS format.

>
> Is there any package that supplies QContactMemoryBackend in a way that could
> be linked against to remove the need to including the source for it as part of
> this package?
Yes QtPim package contains the memory backend. But I copied it to the porjects because I need some small changes necessary to help me to run my tests. And these changes does not make sens upstream they are very specific.

40. By Renato Araujo Oliveira Filho

Removed commented code.

41. By Renato Araujo Oliveira Filho

Updated LICENSE file.

42. By Renato Araujo Oliveira Filho

Fixed typo.

43. By Renato Araujo Oliveira Filho

Removed old file.

44. By Renato Araujo Oliveira Filho

Fixed file EOL syntax.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Changes all look good, but I'm still rather concerned over the file name conflict as it means any existing apps that use evolution-data-server can't make use of online sync functionality if installed alongside address-book-app (e.g. pidgin, evolution, gnome-shell, etc.), which may put a number of desktop users off from trying address-book-app (or any other SDK apps depending on buteo).

It seems an unnecessary restriction when the file could just be renamed to google-contacts-buteo.service?

Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

> Changes all look good, but I'm still rather concerned over the file name
> conflict as it means any existing apps that use evolution-data-server can't
> make use of online sync functionality if installed alongside address-book-app
> (e.g. pidgin, evolution, gnome-shell, etc.), which may put a number of desktop
> users off from trying address-book-app (or any other SDK apps depending on
> buteo).
>
> It seems an unnecessary restriction when the file could just be renamed to
> google-contacts-buteo.service?

It will conflict with "evolution-data-server-online-accounts" because both packages install google contacts service files. And both packages provide the same service, read contacts from google.

It is not buteo which conflicts with this package it is buteo-google-contacts, you can still have "evolution-data-server-online-accounts" installed and buteo main package, but you can not have "buteo-google-contacts" and "evolution-data-server-online-accounts" because of the online account service. This "evolution-data-server-online-accounts" this package contains a EDS extension that automatically configure a "google" EDS source for each account. Having both package will result in duplicated contacts.

The address-book-app does not depend on "buteo-google-contact" you can have the app installed and any other EDS extension.

Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

> (e.g. pidgin, evolution, gnome-shell, etc.), which may put a number of desktop
> users off from trying address-book-app (or any other SDK apps depending on
> buteo).

My pidgin does not depend on "evolution-data-server-online-accounts" which package exactly?
Yeah if the gnome-shell depends on "evolution-data-server-online-accounts" then you can have a problem if you try to install "buteo-google-contacts" but since it is not mandatory for any app, do you still be able to run any Ubuntu-touch app. ("buteo-google-contacts" probably will be a dependency of ubuntu-toutch meta package.

Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Okay, as long as it's possible on a desktop system to have address-book-app and gnome-shell/evolution/etc. installed I'm happy to approve.

review: Approve
45. By Renato Araujo Oliveira Filho

Fixed typos.

46. By Renato Araujo Oliveira Filho

Only fetch contacts from "My Contacts" group.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
47. By Renato Araujo Oliveira Filho

Implemented sync progress notification.

48. By Renato Araujo Oliveira Filho

Updated unit test.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
49. By Renato Araujo Oliveira Filho

Updated unittest.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
50. By Renato Araujo Oliveira Filho

Optimize queries by remote-id using a cache.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
51. By Renato Araujo Oliveira Filho

Keep remoteId map in sync to avoid slow query.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
52. By Renato Araujo Oliveira Filho

Ask for invisible contacts during the sync.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
53. By Renato Araujo Oliveira Filho

Only uses 'invisible' filter if sync target is set.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
54. By Renato Araujo Oliveira Filho

Use "show-invisible" engine parameter to retrieve invisible contacts from server.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
55. By Renato Araujo Oliveira Filho

Initialize contacts backend after authentication.

Avoid to create the contacts source if the authentication failed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
56. By Renato Araujo Oliveira Filho

Implemented abort function for GRemoteSource.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

I'm unclear on the use of stateChanged in UContactsClient now, it seems from the method definition that this has changed from providing one of a few predefined states to providing the progress percentage, however in the code there are places where it's still being called with the old states, e.g.

stateChanged(Sync::SYNC_PROGRESS_SENDING_ITEMS);

vs.

stateChanged(qRound(progress*100));

review: Needs Information
Revision history for this message
Renato Araujo Oliveira Filho (renatofilho) wrote :

> I'm unclear on the use of stateChanged in UContactsClient now, it seems from
> the method definition that this has changed from providing one of a few
> predefined states to providing the progress percentage, however in the code
> there are places where it's still being called with the old states, e.g.
>
> stateChanged(Sync::SYNC_PROGRESS_SENDING_ITEMS);
>
> vs.
>
> stateChanged(qRound(progress*100));

In fact it can be used for both, the enums values like SYNC_PROGRESS_SENDING_ITEMS has values > 200.Because of that we can say that any values 0..100 is a progress status, values > 200 is a error or a real state change.

Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Ah, okay; in that case this all looks good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'CMakeLists.txt'
2--- CMakeLists.txt 1970-01-01 00:00:00 +0000
3+++ CMakeLists.txt 2015-09-24 17:49:24 +0000
4@@ -0,0 +1,59 @@
5+project(buteo-sync-plugins-google)
6+cmake_minimum_required(VERSION 2.8.9)
7+
8+# Find includes in corresponding build directories
9+set(CMAKE_INCLUDE_CURRENT_DIR ON)
10+
11+# Instruct CMake to run moc automatically when needed.
12+set(CMAKE_AUTOMOC ON)
13+
14+# Standard install paths
15+include(GNUInstallDirs)
16+
17+find_package(PkgConfig REQUIRED)
18+find_package(Qt5Core REQUIRED)
19+find_package(Qt5DBus REQUIRED)
20+find_package(Qt5Contacts REQUIRED)
21+find_package(Qt5Network REQUIRED)
22+find_package(Qt5Versit REQUIRED)
23+
24+pkg_check_modules(BUTEOSYNCFW REQUIRED buteosyncfw5)
25+pkg_check_modules(ACCOUNTS REQUIRED accounts-qt5>=1.10)
26+pkg_check_modules(LIBSIGNON REQUIRED libsignon-qt5)
27+
28+set(BUTEOSYNCFW_PLUGIN_PATH "/usr/lib/buteo-plugins-qt5")
29+
30+# config file
31+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
32+ "${CMAKE_CURRENT_BINARY_DIR}/config.h"
33+ IMMEDIATE @ONLY)
34+
35+
36+# Coverage tools
37+OPTION(ENABLE_COVERAGE "Build with coverage analysis support" OFF)
38+if(ENABLE_COVERAGE)
39+ message(STATUS "Using coverage flags")
40+ find_program(COVERAGE_COMMAND gcov)
41+ if(NOT COVERAGE_COMMAND)
42+ message(FATAL_ERROR "gcov command not found")
43+ endif()
44+ SET(CMAKE_C_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
45+ SET(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
46+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -lgcov")
47+ include(${CMAKE_SOURCE_DIR}/cmake/lcov.cmake)
48+endif()
49+
50+enable_testing()
51+
52+add_subdirectory(storage-change-notifier)
53+add_subdirectory(buteo-contact-client)
54+add_subdirectory(google)
55+add_subdirectory(accounts)
56+add_subdirectory(tests)
57+
58+# uninstall target
59+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
60+ "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
61+ IMMEDIATE @ONLY)
62+add_custom_target(uninstall "${CMAKE_COMMAND}"
63+ -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
64
65=== added directory 'accounts'
66=== added file 'accounts/CMakeLists.txt'
67--- accounts/CMakeLists.txt 1970-01-01 00:00:00 +0000
68+++ accounts/CMakeLists.txt 2015-09-24 17:49:24 +0000
69@@ -0,0 +1,14 @@
70+set(SERVICE_FILES
71+ google-contacts.service
72+)
73+set(APPLICATION_FILES
74+ address-book-app.application
75+)
76+
77+install(FILES ${SERVICE_FILES}
78+ DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/accounts/services
79+)
80+
81+install(FILES ${APPLICATION_FILES}
82+ DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/accounts/applications
83+)
84
85=== added file 'accounts/address-book-app.application'
86--- accounts/address-book-app.application 1970-01-01 00:00:00 +0000
87+++ accounts/address-book-app.application 2015-09-24 17:49:24 +0000
88@@ -0,0 +1,11 @@
89+<?xml version="1.0" encoding="UTF-8"?>
90+<application id="address-book-app">
91+ <description>Contacts</description>
92+ <desktop-entry>address-book-app.desktop</desktop-entry>
93+
94+ <service-types>
95+ <service-type id="contacts">
96+ <description>Synchronize your contacts</description>
97+ </service-type>
98+ </service-types>
99+</application>
100
101=== added file 'accounts/google-contacts.service'
102--- accounts/google-contacts.service 1970-01-01 00:00:00 +0000
103+++ accounts/google-contacts.service 2015-09-24 17:49:24 +0000
104@@ -0,0 +1,6 @@
105+<?xml version="1.0" encoding="UTF-8"?>
106+<service id="google-contacts">
107+ <type>contacts</type>
108+ <name>Google Contacts</name>
109+ <provider>google</provider>
110+</service>
111
112=== added directory 'buteo-contact-client'
113=== added file 'buteo-contact-client/CMakeLists.txt'
114--- buteo-contact-client/CMakeLists.txt 1970-01-01 00:00:00 +0000
115+++ buteo-contact-client/CMakeLists.txt 2015-09-24 17:49:24 +0000
116@@ -0,0 +1,37 @@
117+project(buteo-contact-client)
118+
119+set(UBUNTU_CONTACTS_CLIENT ubuntu-contact-client)
120+set(UBUNTU_CONTACTS_CLIENT_SRCS
121+ UAbstractRemoteSource.h
122+ UAbstractRemoteSource.cpp
123+ UAuth.cpp
124+ UAuth.h
125+ UContactsBackend.cpp
126+ UContactsBackend.h
127+ UContactsClient.cpp
128+ UContactsClient.h
129+ UContactsCustomDetail.cpp
130+ UContactsCustomDetail.h
131+)
132+
133+
134+add_library(${UBUNTU_CONTACTS_CLIENT} STATIC
135+ ${UBUNTU_CONTACTS_CLIENT_SRCS}
136+)
137+
138+include_directories(
139+ ${CMAKE_BINARY_DIR}
140+ ${ACCOUNTS_INCLUDE_DIRS}
141+ ${BUTEOSYNCFW_INCLUDE_DIRS}
142+ ${LIBSIGNON_INCLUDE_DIRS}
143+)
144+
145+target_link_libraries(${UBUNTU_CONTACTS_CLIENT}
146+ Qt5::Core
147+ Qt5::Network
148+ Qt5::DBus
149+ Qt5::Contacts
150+ ${ACCOUNTS_LIBRARIES}
151+ ${BUTEOSYNCFW_LIBRARIES}
152+ ${LIBSIGNON_LIBRARIES}
153+)
154
155=== added file 'buteo-contact-client/UAbstractRemoteSource.cpp'
156--- buteo-contact-client/UAbstractRemoteSource.cpp 1970-01-01 00:00:00 +0000
157+++ buteo-contact-client/UAbstractRemoteSource.cpp 2015-09-24 17:49:24 +0000
158@@ -0,0 +1,178 @@
159+/*
160+ * This file is part of buteo-sync-plugins-contacts package
161+ *
162+ * Copyright (C) 2015 Canonical Ltd
163+ *
164+ * Contributors: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
165+ *
166+ * This library is free software; you can redistribute it and/or
167+ * modify it under the terms of the GNU Lesser General Public License
168+ * version 2.1 as published by the Free Software Foundation.
169+ *
170+ * This library is distributed in the hope that it will be useful, but
171+ * WITHOUT ANY WARRANTY; without even the implied warranty of
172+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
173+ * Lesser General Public License for more details.
174+ *
175+ * You should have received a copy of the GNU Lesser General Public
176+ * License along with this library; if not, write to the Free Software
177+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
178+ * 02110-1301 USA
179+ *
180+ */
181+
182+#include "UAbstractRemoteSource.h"
183+#include "UContactsBackend.h"
184+
185+#include <QDebug>
186+
187+QTCONTACTS_USE_NAMESPACE
188+
189+class UContactsBackendBatchOperation
190+{
191+public:
192+ enum BatchOperation {
193+ BATCH_UPDATE = 0,
194+ BATCH_DELETE,
195+ BATCH_CREATE,
196+ BATCH_NONE,
197+ };
198+
199+ UContactsBackendBatchOperation()
200+ : m_op(BATCH_NONE)
201+ {
202+ }
203+
204+ UContactsBackendBatchOperation(BatchOperation op, const QContact &contact)
205+ : m_op(op),
206+ m_contact(contact)
207+ {
208+ }
209+
210+ BatchOperation operation() const
211+ {
212+ return m_op;
213+ }
214+
215+ QContact contact() const
216+ {
217+ return m_contact;
218+ }
219+
220+ bool isValid() const
221+ {
222+ return (m_op != BATCH_NONE);
223+ }
224+
225+private:
226+ BatchOperation m_op;
227+ QContact m_contact;
228+};
229+
230+class UAbstractRemoteSourcePrivate
231+{
232+public:
233+ UAbstractRemoteSourcePrivate()
234+ : m_batchMode(false)
235+ {
236+ }
237+
238+ bool m_batchMode;
239+ QList<UContactsBackendBatchOperation> m_operations;
240+};
241+
242+UAbstractRemoteSource::UAbstractRemoteSource(QObject *parent)
243+ : QObject(parent),
244+ d_ptr(new UAbstractRemoteSourcePrivate)
245+{
246+}
247+
248+UAbstractRemoteSource::~UAbstractRemoteSource()
249+{
250+}
251+
252+void UAbstractRemoteSource::transaction()
253+{
254+ Q_D(UAbstractRemoteSource);
255+
256+ d->m_batchMode = true;
257+}
258+
259+bool UAbstractRemoteSource::commit()
260+{
261+ Q_D(UAbstractRemoteSource);
262+
263+ if (d->m_operations.isEmpty()) {
264+ transactionCommited(QList<QContact>(),
265+ QList<QContact>(),
266+ QStringList(),
267+ QMap<QString, int>(),
268+ Sync::SYNC_DONE);
269+ return true;
270+ }
271+
272+ QList<QContact> create;
273+ QList<QContact> update;
274+ QList<QContact> remove;
275+
276+ foreach(const UContactsBackendBatchOperation &op, d->m_operations) {
277+ switch(op.operation()) {
278+ case UContactsBackendBatchOperation::BATCH_CREATE:
279+ create << op.contact();
280+ break;
281+ case UContactsBackendBatchOperation::BATCH_UPDATE:
282+ update << op.contact();
283+ break;
284+ case UContactsBackendBatchOperation::BATCH_DELETE:
285+ remove << op.contact();
286+ break;
287+ default:
288+ qWarning() << "Invalid operation";
289+ }
290+ }
291+
292+ batch(create, update, remove);
293+ d->m_operations.clear();
294+ d->m_batchMode = false;
295+ return true;
296+}
297+
298+bool UAbstractRemoteSource::rollback()
299+{
300+ Q_D(UAbstractRemoteSource);
301+
302+ d->m_operations.clear();
303+ d->m_batchMode = false;
304+ return true;
305+}
306+
307+void UAbstractRemoteSource::saveContacts(const QList<QContact> &contacts)
308+{
309+ Q_D(UAbstractRemoteSource);
310+
311+ if (d->m_batchMode) {
312+ foreach(const QContact &contact, contacts) {
313+ UContactsBackendBatchOperation::BatchOperation op;
314+ QString remoteId = UContactsBackend::getRemoteId(contact);
315+ op = remoteId.isEmpty() ? UContactsBackendBatchOperation::BATCH_CREATE :
316+ UContactsBackendBatchOperation::BATCH_UPDATE;
317+ d->m_operations << UContactsBackendBatchOperation(op, contact);
318+ }
319+ } else {
320+ saveContactsNonBatch(contacts);
321+ }
322+}
323+
324+void UAbstractRemoteSource::removeContacts(const QList<QContact> &contacts)
325+{
326+ Q_D(UAbstractRemoteSource);
327+
328+ if (d->m_batchMode) {
329+ foreach(const QContact &contact, contacts) {
330+ d->m_operations << UContactsBackendBatchOperation(UContactsBackendBatchOperation::BATCH_DELETE,
331+ contact);
332+ }
333+ } else {
334+ removeContactsNonBatch(contacts);
335+ }
336+}
337
338=== added file 'buteo-contact-client/UAbstractRemoteSource.h'
339--- buteo-contact-client/UAbstractRemoteSource.h 1970-01-01 00:00:00 +0000
340+++ buteo-contact-client/UAbstractRemoteSource.h 2015-09-24 17:49:24 +0000
341@@ -0,0 +1,125 @@
342+/*
343+ * This file is part of buteo-sync-plugins-contacts package
344+ *
345+ * Copyright (C) 2015 Canonical Ltd
346+ *
347+ * Contributors: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
348+ *
349+ * This library is free software; you can redistribute it and/or
350+ * modify it under the terms of the GNU Lesser General Public License
351+ * version 2.1 as published by the Free Software Foundation.
352+ *
353+ * This library is distributed in the hope that it will be useful, but
354+ * WITHOUT ANY WARRANTY; without even the implied warranty of
355+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
356+ * Lesser General Public License for more details.
357+ *
358+ * You should have received a copy of the GNU Lesser General Public
359+ * License along with this library; if not, write to the Free Software
360+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
361+ * 02110-1301 USA
362+ *
363+ */
364+
365+#ifndef UABSTRACTREMOTESOURCE_H
366+#define UABSTRACTREMOTESOURCE_H
367+
368+#include <QObject>
369+#include <QDateTime>
370+#include <QVariantMap>
371+
372+#include <QtContacts/QContact>
373+
374+#include <SyncCommonDefs.h>
375+
376+class UAbstractRemoteSourcePrivate;
377+
378+class UAbstractRemoteSource : public QObject
379+{
380+ Q_OBJECT
381+ Q_DECLARE_PRIVATE(UAbstractRemoteSource)
382+
383+public:
384+
385+ UAbstractRemoteSource(QObject *parent = 0);
386+ ~UAbstractRemoteSource();
387+
388+ virtual void abort() = 0;
389+ virtual bool init(const QVariantMap &properties) = 0;
390+ virtual void fetchContacts(const QDateTime &since, bool includeDeleted, bool fetchAvatar = true) = 0;
391+
392+ /*!
393+ * \brief Begins a transaction on the remote database.
394+ */
395+ void transaction();
396+
397+ /*!
398+ * \brief Commits a transaction to the remote database.
399+ */
400+ bool commit();
401+
402+ /*!
403+ * \brief Rolls back a transaction on the remote database.
404+ */
405+ bool rollback();
406+
407+ virtual void saveContacts(const QList<QtContacts::QContact> &contacts);
408+ virtual void removeContacts(const QList<QtContacts::QContact> &contacts);
409+
410+signals:
411+ void contactsFetched(const QList<QtContacts::QContact> &contacts,
412+ Sync::SyncStatus status,
413+ qreal progress);
414+
415+ /*!
416+ * \brief This signal is emitted, when a remote contact is created
417+ * \param contacts A list of created contacts
418+ * \param status The operation status
419+ */
420+ void contactsCreated(const QList<QtContacts::QContact> &contacts, Sync::SyncStatus status);
421+
422+ /*!
423+ * \brief This signal is emitted, when a remote contact is changed
424+ * \param contacts A list of changed contacts
425+ * \param status The operation status
426+ */
427+ void contactsChanged(const QList<QtContacts::QContact> &contacts, Sync::SyncStatus status);
428+
429+ /*!
430+ * \brief This signal is emitted, when a remote contact is removed
431+ * \param ids A list with remoteId of removed contacts
432+ * \param status The operation status
433+ */
434+ void contactsRemoved(const QStringList &ids, Sync::SyncStatus status);
435+
436+ /*!
437+ * \brief This signal is emitted, when a batch operation finishes
438+ * \param createdContacts A list of created contacts
439+ * \param updatedContacts A list of updated contacts
440+ * \param removedContacts A list with remoteId of removed contacts
441+ * \param errorMap A map with the list of errors found during the batch operation
442+ * the key value contains the contact local id and the value is a int based
443+ * on QContactManager::Error enum
444+ * \param status The operation status
445+ */
446+ void transactionCommited(const QList<QtContacts::QContact> &createdContacts,
447+ const QList<QtContacts::QContact> &updatedContacts,
448+ const QStringList &removedContacts,
449+ const QMap<QString, int> &errorMap,
450+ Sync::SyncStatus status);
451+
452+protected:
453+ virtual void batch(const QList<QtContacts::QContact> &contactsToCreate,
454+ const QList<QtContacts::QContact> &contactsToUpdate,
455+ const QList<QtContacts::QContact> &contactsToRemove) = 0;
456+
457+ virtual void saveContactsNonBatch(const QList<QtContacts::QContact> contacts) = 0;
458+ virtual void removeContactsNonBatch(const QList<QtContacts::QContact> contacts) = 0;
459+
460+private:
461+ QScopedPointer<UAbstractRemoteSourcePrivate> d_ptr;
462+};
463+
464+Q_DECLARE_METATYPE(QList<QtContacts::QContact>)
465+
466+#endif // UABSTRACTREMOTESOURCE_H
467
468=== added file 'buteo-contact-client/UAuth.cpp'
469--- buteo-contact-client/UAuth.cpp 1970-01-01 00:00:00 +0000
470+++ buteo-contact-client/UAuth.cpp 2015-09-24 17:49:24 +0000
471@@ -0,0 +1,181 @@
472+/*
473+ * This file is part of buteo-sync-plugins-contacts package
474+ *
475+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
476+ * 2015 Canonical Ltd
477+ *
478+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
479+ * Mani Chandrasekar <maninc@gmail.com>
480+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
481+ *
482+ * This library is free software; you can redistribute it and/or
483+ * modify it under the terms of the GNU Lesser General Public License
484+ * version 2.1 as published by the Free Software Foundation.
485+ *
486+ * This library is distributed in the hope that it will be useful, but
487+ * WITHOUT ANY WARRANTY; without even the implied warranty of
488+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
489+ * Lesser General Public License for more details.
490+ *
491+ * You should have received a copy of the GNU Lesser General Public
492+ * License along with this library; if not, write to the Free Software
493+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
494+ * 02110-1301 USA
495+ *
496+ */
497+
498+#include "config.h"
499+#include "UAuth.h"
500+
501+#include <QVariantMap>
502+#include <QTextStream>
503+#include <QFile>
504+#include <QStringList>
505+#include <QDebug>
506+
507+#include <Accounts/AccountService>
508+
509+#include <ProfileEngineDefs.h>
510+#include <LogMacros.h>
511+
512+using namespace Accounts;
513+using namespace SignOn;
514+
515+class UAuthPrivate
516+{
517+public:
518+ UAuthPrivate() {}
519+ ~UAuthPrivate() {}
520+
521+ QPointer<Accounts::Manager> mAccountManager;
522+ QPointer<SignOn::Identity> mIdentity;
523+ QPointer<SignOn::AuthSession> mSession;
524+ QPointer<Accounts::Account> mAccount;
525+ QString mServiceName;
526+};
527+
528+UAuth::UAuth(QObject *parent)
529+ : QObject(parent),
530+ d_ptr(new UAuthPrivate)
531+{
532+}
533+
534+UAuth::~UAuth()
535+{
536+}
537+
538+bool
539+UAuth::init(const quint32 accountId, const QString serviceName)
540+{
541+ Q_D(UAuth);
542+
543+ d->mServiceName = serviceName;
544+ if (d->mAccountManager && d_ptr->mAccount) {
545+ LOG_DEBUG("GAuth already initialized");
546+ return false;
547+ }
548+
549+ if (!d->mAccountManager) {
550+ d->mAccountManager = new Accounts::Manager();
551+ if (d->mAccountManager == NULL) {
552+ LOG_DEBUG("Account manager is not created... Cannot authenticate");
553+ return false;
554+ }
555+ }
556+
557+ if (!d->mAccount) {
558+ d->mAccount = Accounts::Account::fromId(d->mAccountManager.data(), accountId, this);
559+ if (d->mAccount == NULL) {
560+ LOG_DEBUG("Account is not created... Cannot authenticate");
561+ return false;
562+ }
563+ mDisplayName = d->mAccount->displayName();
564+ }
565+
566+ return true;
567+}
568+
569+void
570+UAuth::sessionResponse(const SessionData &sessionData)
571+{
572+ SignOn::AuthSession *session = qobject_cast<SignOn::AuthSession*>(sender());
573+ Q_ASSERT(session);
574+ session->disconnect(this);
575+
576+ mToken = sessionData.getProperty(QStringLiteral("AccessToken")).toString();
577+ LOG_DEBUG("Authenticated !!!");
578+
579+ emit success();
580+}
581+
582+bool
583+UAuth::authenticate()
584+{
585+ Q_D(UAuth);
586+ if (d->mSession) {
587+ LOG_WARNING(QString("error: Account %1 Authenticate already requested")
588+ .arg(d->mAccount->displayName()));
589+ return true;
590+ }
591+
592+ Accounts::Service srv(d->mAccountManager->service(d->mServiceName));
593+ if (!srv.isValid()) {
594+ LOG_WARNING(QString("error: Service [%1] not found for account [%2].")
595+ .arg(d->mServiceName)
596+ .arg(d->mAccount->displayName()));
597+ return false;
598+ }
599+ d->mAccount->selectService(srv);
600+
601+ Accounts::AccountService *accSrv = new Accounts::AccountService(d->mAccount, srv);
602+ if (!accSrv) {
603+ LOG_WARNING(QString("error: Account %1 has no valid account service")
604+ .arg(d->mAccount->displayName()));
605+ return false;
606+ }
607+ if (!accSrv->isEnabled()) {
608+ LOG_WARNING(QString("error: Service %1 not enabled for account %2.")
609+ .arg(d->mServiceName)
610+ .arg(d->mAccount->displayName()));
611+ return false;
612+ }
613+
614+ AuthData authData = accSrv->authData();
615+ d->mIdentity = SignOn::Identity::existingIdentity(authData.credentialsId());
616+ if (!d->mIdentity) {
617+ LOG_WARNING(QString("error: Account %1 has no valid credentials")
618+ .arg(d->mAccount->displayName()));
619+ return false;
620+ }
621+
622+ d->mSession = d->mIdentity->createSession(authData.method());
623+ if (!d->mSession) {
624+ LOG_WARNING(QString("error: could not create signon session for Google account %1")
625+ .arg(d->mAccount->displayName()));
626+ accSrv->deleteLater();
627+ return false;
628+ }
629+ connect(d->mSession.data(),SIGNAL(response(SignOn::SessionData)),
630+ SLOT(sessionResponse(SignOn::SessionData)), Qt::QueuedConnection);
631+ connect(d->mSession.data(), SIGNAL(error(SignOn::Error)),
632+ SLOT(error(SignOn::Error)), Qt::QueuedConnection);
633+
634+ QVariantMap signonSessionData = authData.parameters();
635+ signonSessionData.insert("UiPolicy", SignOn::NoUserInteractionPolicy);
636+ d->mSession->process(signonSessionData, authData.mechanism());
637+ accSrv->deleteLater();
638+ return true;
639+}
640+
641+void UAuth::credentialsStored(const quint32 id)
642+{
643+ Q_D(UAuth);
644+ d->mAccount->setCredentialsId(id);
645+ d->mAccount->sync();
646+}
647+
648+void UAuth::error(const SignOn::Error & error)
649+{
650+ LOG_WARNING("LOGIN ERROR:" << error.message());
651+ emit failed();
652+}
653
654=== added file 'buteo-contact-client/UAuth.h'
655--- buteo-contact-client/UAuth.h 1970-01-01 00:00:00 +0000
656+++ buteo-contact-client/UAuth.h 2015-09-24 17:49:24 +0000
657@@ -0,0 +1,72 @@
658+/*
659+ * This file is part of buteo-sync-plugins-contacts package
660+ *
661+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
662+ * 2015 Canonical Ltd
663+ *
664+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
665+ * Mani Chandrasekar <maninc@gmail.com>
666+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
667+ *
668+ * This library is free software; you can redistribute it and/or
669+ * modify it under the terms of the GNU Lesser General Public License
670+ * version 2.1 as published by the Free Software Foundation.
671+ *
672+ * This library is distributed in the hope that it will be useful, but
673+ * WITHOUT ANY WARRANTY; without even the implied warranty of
674+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
675+ * Lesser General Public License for more details.
676+ *
677+ * You should have received a copy of the GNU Lesser General Public
678+ * License along with this library; if not, write to the Free Software
679+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
680+ * 02110-1301 USA
681+ *
682+ */
683+
684+#ifndef UAUTH_H
685+#define UAUTH_H
686+
687+#include <QObject>
688+#include <QScopedPointer>
689+
690+#include <SignOn/AuthService>
691+#include <SignOn/Identity>
692+
693+#include <Accounts/Account>
694+#include <Accounts/Manager>
695+
696+class UAuthPrivate;
697+
698+class UAuth : public QObject
699+{
700+ Q_OBJECT
701+ Q_DECLARE_PRIVATE(UAuth)
702+public:
703+ explicit UAuth(QObject *parent = 0);
704+ ~UAuth();
705+
706+ virtual bool authenticate();
707+ virtual bool init(const quint32 accountId, const QString serviceName);
708+
709+ inline QString accountDisplayName() const { return mDisplayName; }
710+ inline QString token() const { return mToken; }
711+
712+signals:
713+ void success();
714+ void failed();
715+
716+protected:
717+ QString mToken;
718+ QString mDisplayName;
719+
720+private:
721+ QScopedPointer<UAuthPrivate> d_ptr;
722+
723+private slots:
724+ void credentialsStored(const quint32);
725+ void error(const SignOn::Error &);
726+ void sessionResponse(const SignOn::SessionData &);
727+};
728+
729+#endif // UAUTH_H
730
731=== added file 'buteo-contact-client/UContactsBackend.cpp'
732--- buteo-contact-client/UContactsBackend.cpp 1970-01-01 00:00:00 +0000
733+++ buteo-contact-client/UContactsBackend.cpp 2015-09-24 17:49:24 +0000
734@@ -0,0 +1,572 @@
735+/*
736+ * This file is part of buteo-sync-plugins-contacts package
737+ *
738+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
739+ * 2015 Canonical Ltd
740+ *
741+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
742+ * Mani Chandrasekar <maninc@gmail.com>
743+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
744+ *
745+ * This library is free software; you can redistribute it and/or
746+ * modify it under the terms of the GNU Lesser General Public License
747+ * version 2.1 as published by the Free Software Foundation.
748+ *
749+ * This library is distributed in the hope that it will be useful, but
750+ * WITHOUT ANY WARRANTY; without even the implied warranty of
751+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
752+ * Lesser General Public License for more details.
753+ *
754+ * You should have received a copy of the GNU Lesser General Public
755+ * License along with this library; if not, write to the Free Software
756+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
757+ * 02110-1301 USA
758+ *
759+ */
760+
761+#include "config.h"
762+#include "UContactsBackend.h"
763+#include "UContactsCustomDetail.h"
764+
765+#include <LogMacros.h>
766+
767+#include <QContactTimestamp>
768+#include <QContactIdFilter>
769+#include <QContactIntersectionFilter>
770+#include <QContactSyncTarget>
771+#include <QContactDetailFilter>
772+#include <QContactGuid>
773+#include <QContactDisplayLabel>
774+#include <QContactExtendedDetail>
775+#include <QContactSyncTarget>
776+
777+#include <QBuffer>
778+#include <QSet>
779+#include <QHash>
780+
781+#include <QDBusInterface>
782+#include <QDBusReply>
783+
784+static const QString CPIM_SERVICE_NAME ("com.canonical.pim");
785+static const QString CPIM_ADDRESSBOOK_OBJECT_PATH ("/com/canonical/pim/AddressBook");
786+static const QString CPIM_ADDRESSBOOK_IFACE_NAME ("com.canonical.pim.AddressBook");
787+
788+UContactsBackend::UContactsBackend(const QString &managerName, QObject* parent)
789+ : QObject (parent)
790+{
791+ QMap<QString, QString> parameters;
792+ parameters.insert("show-invisible", "true");
793+ iMgr = new QContactManager(managerName, parameters);
794+ FUNCTION_CALL_TRACE;
795+}
796+
797+UContactsBackend::~UContactsBackend()
798+{
799+ FUNCTION_CALL_TRACE;
800+
801+ delete iMgr;
802+ iMgr = NULL;
803+}
804+
805+bool
806+UContactsBackend::init(uint syncAccount, const QString &syncTarget)
807+{
808+ FUNCTION_CALL_TRACE;
809+
810+ // create address book it it does not exists
811+ // check if the source already exists
812+ QContactDetailFilter filter;
813+ filter.setDetailType(QContactDetail::TypeType, QContactType::FieldType);
814+ filter.setValue(QContactType::TypeGroup);
815+
816+ QList<QContact> sources = iMgr->contacts(filter);
817+ Q_FOREACH(const QContact &contact, sources) {
818+ QContactExtendedDetail exd = UContactsCustomDetail::getCustomField(contact,
819+ "ACCOUNT-ID");
820+ if (!exd.isEmpty() && (exd.data().toUInt() == syncAccount)) {
821+ mSyncTargetId = contact.detail<QContactGuid>().guid();
822+ reloadCache();
823+ return true;
824+ }
825+ }
826+
827+ // memory/mock manager does not support syncTarget
828+ if (iMgr->managerName() != "mock") {
829+ // create a new source if necessary
830+ QContact contact;
831+ contact.setType(QContactType::TypeGroup);
832+
833+ QContactDisplayLabel label;
834+ label.setLabel(syncTarget);
835+ contact.saveDetail(&label);
836+
837+ // set the new source as default
838+ QContactExtendedDetail isDefault;
839+ isDefault.setName("IS-PRIMARY");
840+ isDefault.setData(true);
841+ contact.saveDetail(&isDefault);
842+
843+ // Link source with account
844+ QContactExtendedDetail accountId;
845+ accountId.setName("ACCOUNT-ID");
846+ accountId.setData(syncAccount);
847+ contact.saveDetail(&accountId);
848+
849+ if (!iMgr->saveContact(&contact)) {
850+ qWarning() << "Fail to create contact source:" << syncTarget;
851+ return false;
852+ }
853+
854+ mSyncTargetId = contact.detail<QContactGuid>().guid();
855+ }
856+
857+ return true;
858+}
859+
860+bool
861+UContactsBackend::uninit()
862+{
863+ FUNCTION_CALL_TRACE;
864+ mRemoteIdToLocalId.clear();
865+
866+ return true;
867+}
868+
869+QList<QContactId>
870+UContactsBackend::getAllContactIds()
871+{
872+ FUNCTION_CALL_TRACE;
873+ Q_ASSERT (iMgr);
874+ return iMgr->contactIds(getSyncTargetFilter());
875+}
876+
877+RemoteToLocalIdMap
878+UContactsBackend::getAllNewContactIds(const QDateTime &aTimeStamp)
879+{
880+ FUNCTION_CALL_TRACE;
881+ LOG_DEBUG("Retrieve New Contacts Since " << aTimeStamp);
882+
883+ RemoteToLocalIdMap idList;
884+ const QContactChangeLogFilter::EventType eventType =
885+ QContactChangeLogFilter::EventAdded;
886+
887+ getSpecifiedContactIds(eventType, aTimeStamp, &idList);
888+
889+ return idList;
890+}
891+
892+RemoteToLocalIdMap
893+UContactsBackend::getAllModifiedContactIds(const QDateTime &aTimeStamp)
894+{
895+
896+ FUNCTION_CALL_TRACE;
897+
898+ LOG_DEBUG("Retrieve Modified Contacts Since " << aTimeStamp);
899+
900+ RemoteToLocalIdMap idList;
901+ const QContactChangeLogFilter::EventType eventType =
902+ QContactChangeLogFilter::EventChanged;
903+
904+ getSpecifiedContactIds(eventType, aTimeStamp, &idList);
905+
906+ return idList;
907+}
908+
909+RemoteToLocalIdMap
910+UContactsBackend::getAllDeletedContactIds(const QDateTime &aTimeStamp)
911+{
912+ FUNCTION_CALL_TRACE;
913+ LOG_DEBUG("Retrieve Deleted Contacts Since " << aTimeStamp);
914+
915+ RemoteToLocalIdMap idList;
916+ const QContactChangeLogFilter::EventType eventType =
917+ QContactChangeLogFilter::EventRemoved;
918+
919+ getSpecifiedContactIds(eventType, aTimeStamp, &idList);
920+
921+ return idList;
922+}
923+
924+bool
925+UContactsBackend::addContacts(QList<QContact>& aContactList,
926+ QMap<int, UContactsStatus> *aStatusMap)
927+{
928+ FUNCTION_CALL_TRACE;
929+ Q_ASSERT(iMgr);
930+ Q_ASSERT(aStatusMap);
931+
932+ QMap<int, QContactManager::Error> errorMap;
933+
934+ // Check if contact already exists if it exists set the contact id
935+ // to cause an update instead of create a new one
936+ for(int i=0; i < aContactList.size(); i++) {
937+ QContact &c = aContactList[i];
938+ QString remoteId = getRemoteId(c);
939+ QContactId id = entryExists(remoteId);
940+ if (!id.isNull()) {
941+ c.setId(id);
942+ } else {
943+ // make sure that all contacts retrieved are saved on the correct sync target
944+ QContactSyncTarget syncTarget = c.detail<QContactSyncTarget>();
945+ syncTarget.setSyncTarget(syncTargetId());
946+ c.saveDetail(&syncTarget);
947+ }
948+
949+ // remove guid field if it exists
950+ QContactGuid guid = c.detail<QContactGuid>();
951+ if (!guid.isEmpty()) {
952+ c.removeDetail(&guid);
953+ }
954+ }
955+
956+ bool retVal = iMgr->saveContacts(&aContactList, &errorMap);
957+ if (!retVal) {
958+ LOG_WARNING( "Errors reported while saving contacts:" << iMgr->error() );
959+ }
960+
961+ // QContactManager will populate errorMap only for errors, but we use this as a status map,
962+ // so populate NoError if there's no error.
963+ for (int i = 0; i < aContactList.size(); i++)
964+ {
965+ UContactsStatus status;
966+ status.id = i;
967+ if (!errorMap.contains(i)) {
968+ status.errorCode = QContactManager::NoError;
969+
970+ // update remote id map
971+ const QContact &c = aContactList.at(i);
972+ QString remoteId = getRemoteId(c);
973+ mRemoteIdToLocalId.insert(remoteId, c.id());
974+ } else {
975+ LOG_WARNING("Contact with id " << aContactList.at(i).id() << " and index " << i <<" is in error");
976+ status.errorCode = errorMap.value(i);
977+ }
978+ aStatusMap->insert(i, status);
979+ }
980+
981+ return retVal;
982+}
983+
984+QMap<int,UContactsStatus>
985+UContactsBackend::modifyContacts(QList<QContact> *aContactList)
986+{
987+ FUNCTION_CALL_TRACE;
988+
989+ Q_ASSERT (iMgr);
990+ UContactsStatus status;
991+
992+ QMap<int,QContactManager::Error> errors;
993+ QMap<int,UContactsStatus> statusMap;
994+
995+ // WORKAROUND: Our backend uses GUid as contact id due problems with contact id serialization
996+ // we can not use this field
997+ for (int i = 0; i < aContactList->size(); i++) {
998+ QContact &newContact = (*aContactList)[i];
999+ QString remoteId = getRemoteId(newContact);
1000+
1001+ // if the contact was created the remoteId will not exists on local database
1002+ QContactId localId = entryExists(remoteId);
1003+
1004+ // nt this case we should use the guid stored on contact
1005+ QContactGuid guid = newContact.detail<QContactGuid>();
1006+
1007+ if (localId.isNull() && !guid.isEmpty()) {
1008+ // try the guid (should contains the local id) field
1009+ localId = QContactId::fromString(guid.guid());
1010+ }
1011+ newContact.setId(localId);
1012+ newContact.removeDetail(&guid);
1013+ }
1014+
1015+ if(iMgr->saveContacts(aContactList , &errors)) {
1016+ LOG_DEBUG("Batch Modification of Contacts Succeeded");
1017+ } else {
1018+ LOG_DEBUG("Batch Modification of Contacts Failed");
1019+ }
1020+
1021+ // QContactManager will populate errorMap only for errors, but we use this as a status map,
1022+ // so populate NoError if there's no error.
1023+ // TODO QContactManager populates indices from the aContactList, but we populate keys, is this OK?
1024+ for (int i = 0; i < aContactList->size(); i++) {
1025+ const QContact &c = aContactList->at(i);
1026+ QContactId contactId = c.id();
1027+ if( !errors.contains(i) ) {
1028+ LOG_DEBUG("No error for contact with id " << contactId << " and index " << i);
1029+ status.errorCode = QContactManager::NoError;
1030+ statusMap.insert(i, status);
1031+
1032+ // update remote it map
1033+ QString oldRemoteId = mRemoteIdToLocalId.key(c.id());
1034+ mRemoteIdToLocalId.remove(oldRemoteId);
1035+
1036+ QString remoteId = getRemoteId(c);
1037+ mRemoteIdToLocalId.insert(remoteId, c.id());
1038+ } else {
1039+ LOG_DEBUG("contact with id " << contactId << " and index " << i <<" is in error");
1040+ QContactManager::Error errorCode = errors.value(i);
1041+ status.errorCode = errorCode;
1042+ statusMap.insert(i, status);
1043+ }
1044+ }
1045+ return statusMap;
1046+}
1047+
1048+QMap<int, UContactsStatus>
1049+UContactsBackend::deleteContacts(const QStringList &aContactIDList)
1050+{
1051+ FUNCTION_CALL_TRACE;
1052+
1053+ QList<QContactId> qContactIdList;
1054+ foreach (QString id, aContactIDList) {
1055+ qContactIdList.append(QContactId::fromString(id));
1056+ }
1057+
1058+ return deleteContacts(qContactIdList);
1059+}
1060+
1061+QMap<int, UContactsStatus>
1062+UContactsBackend::deleteContacts(const QList<QContactId> &aContactIDList) {
1063+ FUNCTION_CALL_TRACE;
1064+
1065+ Q_ASSERT (iMgr);
1066+ UContactsStatus status;
1067+ QMap<int, QContactManager::Error> errors;
1068+ QMap<int, UContactsStatus> statusMap;
1069+
1070+ if(iMgr->removeContacts(aContactIDList , &errors)) {
1071+ LOG_DEBUG("Successfully Removed all contacts ");
1072+ }
1073+ else {
1074+ LOG_WARNING("Failed Removing Contacts");
1075+ }
1076+
1077+ // QContactManager will populate errorMap only for errors, but we use this as a status map,
1078+ // so populate NoError if there's no error.
1079+ for (int i = 0; i < aContactIDList.size(); i++) {
1080+ const QContactId &contactId = aContactIDList.at(i);
1081+ if( !errors.contains(i) )
1082+ {
1083+ LOG_DEBUG("No error for contact with id " << contactId << " and index " << i);
1084+ status.errorCode = QContactManager::NoError;
1085+ statusMap.insert(i, status);
1086+
1087+ // remove from remote id map
1088+ QString remoteId = mRemoteIdToLocalId.key(contactId);
1089+ mRemoteIdToLocalId.remove(remoteId);
1090+ }
1091+ else
1092+ {
1093+ LOG_DEBUG("contact with id " << contactId << " and index " << i <<" is in error");
1094+ QContactManager::Error errorCode = errors.value(i);
1095+ status.errorCode = errorCode;
1096+ statusMap.insert(i, status);
1097+ }
1098+ }
1099+
1100+ return statusMap;
1101+}
1102+
1103+
1104+void
1105+UContactsBackend::getSpecifiedContactIds(const QContactChangeLogFilter::EventType aEventType,
1106+ const QDateTime& aTimeStamp,
1107+ RemoteToLocalIdMap *aIdList)
1108+{
1109+ FUNCTION_CALL_TRACE;
1110+ Q_ASSERT(aIdList);
1111+
1112+ QList<QContactId> localIdList;
1113+ QContactChangeLogFilter filter(aEventType);
1114+ filter.setSince(aTimeStamp);
1115+
1116+ localIdList = iMgr->contactIds(filter & getSyncTargetFilter());
1117+ LOG_DEBUG("Local ID added = " << localIdList.size() << " Datetime from when this " << aTimeStamp.toString());
1118+ // Filter out ids for items that were added after the specified time.
1119+ if (aEventType != QContactChangeLogFilter::EventAdded)
1120+ {
1121+ filter.setEventType(QContactChangeLogFilter::EventAdded);
1122+ QList<QContactId> addedList = iMgr->contactIds(filter & getSyncTargetFilter());
1123+ foreach (const QContactId &id, addedList)
1124+ {
1125+ localIdList.removeAll(id);
1126+ }
1127+ }
1128+
1129+ // This is a defensive procedure to prevent duplicate items being sent.
1130+ // QSet does not allow duplicates, thus transforming QList to QSet and back
1131+ // again will remove any duplicate items in the original QList.
1132+ int originalIdCount = localIdList.size();
1133+ QSet<QContactId> idSet = localIdList.toSet();
1134+ int idCountAfterDupRemoval = idSet.size();
1135+
1136+ LOG_DEBUG("Item IDs found (returned / incl. duplicates): " << idCountAfterDupRemoval << "/" << originalIdCount);
1137+ if (originalIdCount != idCountAfterDupRemoval) {
1138+ LOG_WARNING("Contacts backend returned duplicate items for requested list");
1139+ LOG_WARNING("Duplicate item IDs have been removed");
1140+ } // no else
1141+
1142+ localIdList = idSet.toList();
1143+
1144+ QContactFetchHint remoteIdHint;
1145+ QList <QContactDetail::DetailType> detailTypes;
1146+ detailTypes << QContactExtendedDetail::Type;
1147+ remoteIdHint.setDetailTypesHint(detailTypes);
1148+
1149+ QList<QContact> contacts = iMgr->contacts(localIdList, remoteIdHint);
1150+ foreach (const QContact &contact, contacts) {
1151+ QString rid = getRemoteId(contact);
1152+ aIdList->insertMulti(rid, contact.id());
1153+ }
1154+}
1155+
1156+/*!
1157+ \fn GContactsBackend::getContact(QContactId aContactId)
1158+ */
1159+QContact
1160+UContactsBackend::getContact(const QContactId& aContactId)
1161+{
1162+ FUNCTION_CALL_TRACE;
1163+ Q_ASSERT (iMgr);
1164+ QList<QContact> returnedContacts;
1165+
1166+ LOG_DEBUG("Contact ID to be retreived = " << aContactId.toString());
1167+ returnedContacts = iMgr->contacts(QList<QContactId>() << aContactId);
1168+
1169+ LOG_DEBUG("Contacts retreived from Contact manager = " << returnedContacts.count());
1170+ return returnedContacts.value(0, QContact());
1171+}
1172+
1173+QContact
1174+UContactsBackend::getContact(const QString& remoteId)
1175+{
1176+ FUNCTION_CALL_TRACE;
1177+ Q_ASSERT (iMgr);
1178+ LOG_DEBUG("Remote id to be searched for = " << remoteId);
1179+
1180+ // use contact id if possible
1181+ QContactId cId = entryExists(remoteId);
1182+ if (!cId.isNull()) {
1183+ return iMgr->contact(cId);
1184+ }
1185+ return QContact();
1186+}
1187+
1188+QContactId
1189+UContactsBackend::entryExists(const QString remoteId)
1190+{
1191+ if (remoteId.isEmpty()) {
1192+ return QContactId();
1193+ }
1194+
1195+ // check cache
1196+ return mRemoteIdToLocalId.value(remoteId);
1197+}
1198+
1199+QString
1200+UContactsBackend::syncTargetId() const
1201+{
1202+ return mSyncTargetId;
1203+}
1204+
1205+const QStringList
1206+UContactsBackend::localIds(const QStringList remoteIds)
1207+{
1208+ QStringList localIdList;
1209+ foreach (QString guid , remoteIds) {
1210+ QString localId = entryExists(guid).toString();
1211+ if (!localId.isEmpty()) {
1212+ localIdList << localId;
1213+ }
1214+ }
1215+ Q_ASSERT(localIdList.count() == remoteIds.count());
1216+ return localIdList;
1217+}
1218+
1219+void UContactsBackend::reloadCache()
1220+{
1221+ FUNCTION_CALL_TRACE;
1222+ QContactFetchHint hint;
1223+ QList<QContactSortOrder> sortOrder;
1224+ QContactFilter sourceFilter;
1225+ if (!mSyncTargetId.isEmpty()) {
1226+ sourceFilter = getSyncTargetFilter();
1227+ }
1228+
1229+ mRemoteIdToLocalId.clear();
1230+ hint.setDetailTypesHint(QList<QContactDetail::DetailType>() << QContactExtendedDetail::Type);
1231+ Q_FOREACH(const QContact &c, iMgr->contacts(sourceFilter, sortOrder, hint)) {
1232+ QString remoteId = getRemoteId(c);
1233+ if (!remoteId.isEmpty()) {
1234+ mRemoteIdToLocalId.insert(remoteId, c.id());
1235+ }
1236+ }
1237+}
1238+
1239+QString
1240+UContactsBackend::getRemoteId(const QContact &contact)
1241+{
1242+ return UContactsCustomDetail::getCustomField(contact, UContactsCustomDetail::FieldRemoteId).data().toString();
1243+}
1244+
1245+void UContactsBackend::setRemoteId(QContact &contact, const QString &remoteId)
1246+{
1247+ UContactsCustomDetail::setCustomField(contact, UContactsCustomDetail::FieldRemoteId, QVariant(remoteId));
1248+}
1249+
1250+QString UContactsBackend::getLocalId(const QContact &contact)
1251+{
1252+ QContactGuid guid = contact.detail<QContactGuid>();
1253+ return guid.guid();
1254+}
1255+
1256+void UContactsBackend::setLocalId(QContact &contact, const QString &localId)
1257+{
1258+ QContactGuid guid = contact.detail<QContactGuid>();
1259+ guid.setGuid(localId);
1260+ contact.saveDetail(&guid);
1261+}
1262+
1263+bool UContactsBackend::deleted(const QContact &contact)
1264+{
1265+ QString deletedAt = UContactsCustomDetail::getCustomField(contact, UContactsCustomDetail::FieldDeletedAt).data().toString();
1266+ return !deletedAt.isEmpty();
1267+}
1268+
1269+void
1270+UContactsBackend::purgecontacts(const QDateTime &date)
1271+{
1272+ QDBusInterface iface(CPIM_SERVICE_NAME,
1273+ CPIM_ADDRESSBOOK_OBJECT_PATH,
1274+ CPIM_ADDRESSBOOK_IFACE_NAME);
1275+ QDBusReply<void> reply = iface.call("purgeContacts", date.toString(Qt::ISODate), mSyncTargetId);
1276+ if (reply.error().isValid()) {
1277+ LOG_WARNING("Fail to purge contacts" << reply.error());
1278+ } else {
1279+ LOG_DEBUG("Purged backend contacts");
1280+ }
1281+}
1282+
1283+QContactFilter
1284+UContactsBackend::getSyncTargetFilter() const
1285+{
1286+ // user entered contacts, i.e. all other contacts that are not sourcing
1287+ // from restricted backends or instant messaging service
1288+ static QContactDetailFilter detailFilterDefaultSyncTarget;
1289+
1290+ if (!mSyncTargetId.isEmpty() &&
1291+ detailFilterDefaultSyncTarget.value().isNull()) {
1292+ detailFilterDefaultSyncTarget.setDetailType(QContactSyncTarget::Type,
1293+ QContactSyncTarget::FieldSyncTarget + 1);
1294+ detailFilterDefaultSyncTarget.setValue(mSyncTargetId);
1295+ } else if (mSyncTargetId.isEmpty()) {
1296+ return QContactFilter();
1297+ }
1298+
1299+ return detailFilterDefaultSyncTarget;
1300+}
1301+
1302+
1303+QtContacts::QContactManager *UContactsBackend::manager() const
1304+{
1305+ return iMgr;
1306+}
1307
1308=== added file 'buteo-contact-client/UContactsBackend.h'
1309--- buteo-contact-client/UContactsBackend.h 1970-01-01 00:00:00 +0000
1310+++ buteo-contact-client/UContactsBackend.h 2015-09-24 17:49:24 +0000
1311@@ -0,0 +1,254 @@
1312+/*
1313+ * This file is part of buteo-sync-plugins-contacts package
1314+ *
1315+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
1316+ * 2015 Canonical Ltd
1317+ *
1318+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
1319+ * Mani Chandrasekar <maninc@gmail.com>
1320+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
1321+ *
1322+ * This library is free software; you can redistribute it and/or
1323+ * modify it under the terms of the GNU Lesser General Public License
1324+ * version 2.1 as published by the Free Software Foundation.
1325+ *
1326+ * This library is distributed in the hope that it will be useful, but
1327+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1328+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1329+ * Lesser General Public License for more details.
1330+ *
1331+ * You should have received a copy of the GNU Lesser General Public
1332+ * License along with this library; if not, write to the Free Software
1333+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
1334+ * 02110-1301 USA
1335+ *
1336+ */
1337+
1338+#ifndef UCONTACTSBACKEND_H_
1339+#define UCONTACTSBACKEND_H_
1340+
1341+#include <QContact>
1342+#include <QContactId>
1343+#include <QContactFetchRequest>
1344+#include <QContactExtendedDetail>
1345+#include <QContactChangeLogFilter>
1346+#include <QContactManager>
1347+
1348+#include <QStringList>
1349+
1350+QTCONTACTS_USE_NAMESPACE
1351+
1352+struct UContactsStatus
1353+{
1354+ int id;
1355+ QContactManager::Error errorCode;
1356+};
1357+
1358+typedef QMultiMap<QString, QContactId> RemoteToLocalIdMap;
1359+
1360+//! \brief Harmattan Contact storage plugin backend interface class
1361+///
1362+/// This class interfaces with the QtContact backend implementation
1363+class UContactsBackend : public QObject
1364+{
1365+
1366+public:
1367+ explicit UContactsBackend(const QString &managerName = "", QObject* parent = 0);
1368+
1369+ /*!
1370+ * \brief Destructor
1371+ */
1372+ ~UContactsBackend();
1373+
1374+ /*!
1375+ * \brief Initialize the backend, must be called before any other function
1376+ * \param syncTarget The name of the collection used to store contacts
1377+ * \return Returns true if initialized with sucess false otherwise
1378+ */
1379+ bool init(uint syncAccount, const QString &syncTarget);
1380+
1381+ /*!
1382+ * \brief releases the resources held.
1383+ * @returnReturns true if sucess false otherwise
1384+ */
1385+ bool uninit();
1386+
1387+ /*!
1388+ * \brief Return ids of all contacts stored locally
1389+ * @return List of contact IDs
1390+ */
1391+ QList<QContactId> getAllContactIds();
1392+
1393+
1394+ /*!
1395+ * \brief Return all new contacts ids in a QList of QStrings
1396+ * @param aTimeStamp Timestamp of the oldest contact ID to be returned
1397+ * @return List of contact IDs
1398+ */
1399+ RemoteToLocalIdMap getAllNewContactIds(const QDateTime& aTimeStamp);
1400+
1401+ /*!
1402+ * \brief Return all modified contact ids in a QList of QStrings
1403+ * @param aTimeStamp Timestamp of the oldest contact ID to be returned
1404+ * @return List of contact IDs
1405+ */
1406+ RemoteToLocalIdMap getAllModifiedContactIds(const QDateTime& aTimeStamp);
1407+
1408+
1409+ /*!
1410+ * \brief Return all deleted contacts ids in a QList of QStrings
1411+ * @param aTimeStamp Timestamp of the oldest contact ID to be returned
1412+ * @return List of contact IDs
1413+ */
1414+ RemoteToLocalIdMap getAllDeletedContactIds(const QDateTime& aTimeStamp);
1415+
1416+ /*!
1417+ * \brief Get contact data for a given contact ID as a QContact object
1418+ * @param aContactId The ID of the contact
1419+ * @return The data of the contact
1420+ */
1421+ QContact getContact(const QContactId& aContactId);
1422+
1423+ /*!
1424+ * \brief Returns a contact for the specified remoteId
1425+ * @param remoteId The remote id of the contact to be returned
1426+ * @return The data of the contact
1427+ */
1428+ QContact getContact(const QString& remoteId);
1429+
1430+ /*!
1431+ * \brief Batch addition of contacts
1432+ * @param aContactDataList Contact data
1433+ * @param aStatusMap Returned status data
1434+ * @return Errors
1435+ */
1436+ bool addContacts(QList<QContact>& aContactList,
1437+ QMap<int, UContactsStatus> *aStatusMap );
1438+
1439+ // Functions for modifying contacts
1440+ /*!
1441+ * \brief Batch modification
1442+ * @param aContactDataList Contact data
1443+ * @param aContactsIdList Contact IDs
1444+ * @return Errors
1445+ */
1446+ QMap<int, UContactsStatus> modifyContacts(QList<QtContacts::QContact> *aContactList);
1447+
1448+ /*!
1449+ * \brief Batch deletion of contacts
1450+ * @param aContactIDList Contact IDs
1451+ * @return Errors
1452+ */
1453+ QMap<int, UContactsStatus> deleteContacts(const QStringList &aContactIDList);
1454+
1455+ /*!
1456+ * \brief Batch deletion of contacts
1457+ * @param aContactIDList Contact IDs
1458+ * @return Errors
1459+ */
1460+ QMap<int, UContactsStatus> deleteContacts(const QList<QContactId> &aContactIDList);
1461+
1462+ /*!
1463+ * \brief Check if a contact exists
1464+ * \param remoteId The remoteId of the contact
1465+ * \return The localId of the contact
1466+ */
1467+ QContactId entryExists(const QString remoteId);
1468+
1469+ /*!
1470+ * \brief Retrieve the current address book used by the backend
1471+ * \return The address book id
1472+ */
1473+ QString syncTargetId() const;
1474+
1475+ /*!
1476+ * \brief Retrieve the local id of a list of remote ids
1477+ * \param remoteIds A list with remote ids
1478+ * \return A list with local ids
1479+ */
1480+ const QStringList localIds(const QStringList remoteIds);
1481+
1482+ /*!
1483+ * \brief Return the value of the remote id field in a contact
1484+ * \param contact The contact object
1485+ * \return A string with the remoteId
1486+ */
1487+ static QString getRemoteId(const QContact &contact);
1488+
1489+ /*!
1490+ * \brief Update the value of the remote id field in a contact
1491+ * \param contact The contact object
1492+ * \param remoteId A string with the remoteId
1493+ */
1494+ static void setRemoteId(QContact &contact, const QString &remoteId);
1495+
1496+ /*!
1497+ * \brief Return the valueof the local id field in a contact
1498+ * \param contact The contact object
1499+ * \return A string with the localId
1500+ */
1501+ static QString getLocalId(const QContact &contact);
1502+
1503+ /*!
1504+ * \brief Update the value of the local id field in a contact
1505+ * \param contact The contact object
1506+ * \param remoteId A string with the localId
1507+ */
1508+ static void setLocalId(QContact &contact, const QString &localId);
1509+
1510+ /*!
1511+ * \brief Check if the contact is marked as deleted
1512+ * \param contact The contact object
1513+ * \return Returns true if the contact is marked as deleted, otherwise returns false
1514+ */
1515+ static bool deleted(const QContact &contact);
1516+
1517+ /*!
1518+ * \brief Purge all deleted contacts from the server
1519+ */
1520+ void purgecontacts(const QDateTime &date);
1521+
1522+ /*!
1523+ * \brief Reload contact id cache
1524+ */
1525+ void reloadCache();
1526+
1527+
1528+ QContactManager *manager() const;
1529+
1530+private: // functions
1531+
1532+ /*!
1533+ * \brief Returns contact IDs specified by event type and timestamp
1534+ * @param aEventType Added/changed/removed contacts
1535+ * @param aTimeStamp Contacts older than aTimeStamp are filtered out
1536+ * @param aIdList Returned contact IDs
1537+ */
1538+ void getSpecifiedContactIds(const QContactChangeLogFilter::EventType aEventType,
1539+ const QDateTime &aTimeStamp,
1540+ RemoteToLocalIdMap *aIdList);
1541+
1542+ /*!
1543+ * \brief Constructs and returns the filter for accessing only contacts allowed to be synchronized
1544+ * Contacts not allowed to be synchronized are Instant messaging contacts and contacts with origin from other sync backends;
1545+ * those contacts have QContactSyncTarget::SyncTarget value different from address book or buteo sync clients.
1546+ * It is designed that buteo sync clients don't restrict access to contacts among themselves
1547+ * - value for QContactSyncTarget::SyncTarget written by this backend is "buteo".
1548+ */
1549+ QContactFilter getSyncTargetFilter() const;
1550+
1551+private: // data
1552+
1553+ // if there is more than one Manager we need to have a list of Managers
1554+ QContactManager *iMgr; ///< A pointer to contact manager
1555+ QString mSyncTargetId;
1556+ QMap<QString, QContactId> mRemoteIdToLocalId;
1557+
1558+
1559+ void createSourceForAccount(uint accountId, const QString &label);
1560+};
1561+
1562+#endif /* CONTACTSBACKEND_H_ */
1563+
1564+
1565+
1566
1567=== added file 'buteo-contact-client/UContactsClient.cpp'
1568--- buteo-contact-client/UContactsClient.cpp 1970-01-01 00:00:00 +0000
1569+++ buteo-contact-client/UContactsClient.cpp 2015-09-24 17:49:24 +0000
1570@@ -0,0 +1,1028 @@
1571+/*
1572+ * This file is part of buteo-sync-plugins-contacts package
1573+ *
1574+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
1575+ * 2015 Canonical Ltd
1576+ *
1577+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
1578+ * Mani Chandrasekar <maninc@gmail.com>
1579+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
1580+ *
1581+ * This library is free software; you can redistribute it and/or
1582+ * modify it under the terms of the GNU Lesser General Public License
1583+ * version 2.1 as published by the Free Software Foundation.
1584+ *
1585+ * This library is distributed in the hope that it will be useful, but
1586+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1587+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1588+ * Lesser General Public License for more details.
1589+ *
1590+ * You should have received a copy of the GNU Lesser General Public
1591+ * License along with this library; if not, write to the Free Software
1592+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
1593+ * 02110-1301 USA
1594+ *
1595+ */
1596+
1597+#include "UContactsClient.h"
1598+#include "UContactsBackend.h"
1599+#include "UAbstractRemoteSource.h"
1600+#include "UAuth.h"
1601+#include "config.h"
1602+
1603+//Buteo
1604+#include <LogMacros.h>
1605+#include <ProfileEngineDefs.h>
1606+#include <ProfileManager.h>
1607+
1608+#include <QLibrary>
1609+#include <QtNetwork>
1610+#include <QDateTime>
1611+#include <QContactGuid>
1612+#include <QContactDetailFilter>
1613+#include <QContactAvatar>
1614+
1615+class UContactsClientPrivate
1616+{
1617+public:
1618+ UContactsClientPrivate(const QString &serviceName)
1619+ : mAuth(0),
1620+ mContactBackend(0),
1621+ mRemoteSource(0),
1622+ mServiceName(serviceName),
1623+ mAborted(false)
1624+ {
1625+ }
1626+
1627+ UAuth* mAuth;
1628+ UContactsBackend* mContactBackend;
1629+ UAbstractRemoteSource* mRemoteSource;
1630+ bool mSlowSync;
1631+ bool mAborted;
1632+ QString mServiceName;
1633+ // local database information
1634+ QSet<QContactId> mAllLocalContactIds;
1635+ RemoteToLocalIdMap mAddedContactIds;
1636+ RemoteToLocalIdMap mModifiedContactIds;
1637+ RemoteToLocalIdMap mDeletedContactIds;
1638+ // sync report
1639+ QMap<QString, Buteo::DatabaseResults> mItemResults;
1640+ Buteo::SyncResults mResults;
1641+ qreal mProgress;
1642+ // sync profile
1643+ QString mSyncTarget;
1644+ qint32 mAccountId;
1645+ Buteo::SyncProfile::SyncDirection mSyncDirection;
1646+ Buteo::SyncProfile::ConflictResolutionPolicy mConflictResPolicy;
1647+};
1648+
1649+UContactsClient::UContactsClient(const QString& aPluginName,
1650+ const Buteo::SyncProfile& aProfile,
1651+ Buteo::PluginCbInterface *aCbInterface, const QString &serviceName)
1652+ : ClientPlugin(aPluginName, aProfile, aCbInterface),
1653+ d_ptr(new UContactsClientPrivate(serviceName))
1654+{
1655+ FUNCTION_CALL_TRACE;
1656+}
1657+
1658+UContactsClient::~UContactsClient()
1659+{
1660+ FUNCTION_CALL_TRACE;
1661+ Q_D(UContactsClient);
1662+
1663+ delete d->mAuth;
1664+ delete d->mRemoteSource;
1665+}
1666+
1667+bool
1668+UContactsClient::init()
1669+{
1670+ FUNCTION_CALL_TRACE;
1671+ Q_D(UContactsClient);
1672+
1673+ d->mProgress = 0.0;
1674+ d->mAborted = false;
1675+
1676+ if (lastSyncTime().isNull()) {
1677+ d->mSlowSync = true;
1678+ } else {
1679+ d->mSlowSync = false;
1680+ }
1681+
1682+ LOG_DEBUG ("Last sync date:" << lastSyncTime() << "Using slow sync?" << d->mSlowSync);
1683+ if (!initConfig()) {
1684+ LOG_CRITICAL("Fail to init configuration");
1685+ return false;
1686+ }
1687+
1688+ d->mAuth = crateAuthenticator(this);
1689+ if (!d->mAuth || !d->mAuth->init(d->mAccountId, d->mServiceName)) {
1690+ LOG_CRITICAL("Fail to create auth object");
1691+ goto init_fail;
1692+ }
1693+
1694+ d->mContactBackend = createContactsBackend(this);
1695+ if (!d->mContactBackend) {
1696+ LOG_CRITICAL("Fail to create contact backend");
1697+ goto init_fail;
1698+ }
1699+
1700+
1701+ // remote source must be initialized after mAuth because its uses the account name property
1702+ d->mRemoteSource = createRemoteSource(this);
1703+ if (!d->mRemoteSource) {
1704+ LOG_CRITICAL("Fail to create remote contact backend");
1705+ goto init_fail;
1706+ }
1707+
1708+ d->mItemResults.insert(syncTargetId(), Buteo::DatabaseResults());
1709+
1710+ // sign in.
1711+ connect(d->mAuth, SIGNAL(success()), SLOT(start()));
1712+ connect(d->mAuth, SIGNAL(failed()), SLOT(onAuthenticationError()));
1713+
1714+ // syncStateChanged to signal changes from CONNECTING, RECEIVING
1715+ // SENDING, DISCONNECTING, CLOSED
1716+ connect(this,
1717+ SIGNAL(stateChanged(int)),
1718+ SLOT(onStateChanged(int)));
1719+
1720+ // Take necessary action when sync is finished
1721+ connect(this,
1722+ SIGNAL(syncFinished(Sync::SyncStatus)),
1723+ SLOT(onSyncFinished(Sync::SyncStatus)));
1724+
1725+ return true;
1726+
1727+init_fail:
1728+
1729+ delete d->mRemoteSource;
1730+ delete d->mContactBackend;
1731+ delete d->mAuth;
1732+ d->mRemoteSource = 0;
1733+ d->mContactBackend = 0;
1734+ d->mAuth = 0;
1735+ return false;
1736+}
1737+
1738+bool
1739+UContactsClient::uninit()
1740+{
1741+ FUNCTION_CALL_TRACE;
1742+ Q_D(UContactsClient);
1743+
1744+ delete d->mRemoteSource;
1745+ delete d->mContactBackend;
1746+ delete d->mAuth;
1747+ d->mRemoteSource = 0;
1748+ d->mContactBackend = 0;
1749+ d->mAuth = 0;
1750+
1751+ return true;
1752+}
1753+
1754+bool
1755+UContactsClient::isReadyToSync() const
1756+{
1757+ const Q_D(UContactsClient);
1758+ return (d->mContactBackend && d->mRemoteSource && d->mAuth);
1759+}
1760+
1761+UContactsBackend *UContactsClient::createContactsBackend(QObject *parent) const
1762+{
1763+ return new UContactsBackend(QCONTACTS_BACKEND_NAME, parent);
1764+}
1765+
1766+UAuth *UContactsClient::crateAuthenticator(QObject *parent) const
1767+{
1768+ return new UAuth(parent);
1769+}
1770+
1771+bool
1772+UContactsClient::startSync()
1773+{
1774+ FUNCTION_CALL_TRACE;
1775+
1776+ if (!isReadyToSync()) {
1777+ LOG_WARNING ("Ubuntu plugin is not ready to sync.");
1778+ return false;
1779+ }
1780+
1781+ Q_D(UContactsClient);
1782+ LOG_DEBUG ("Init done. Continuing with sync");
1783+
1784+ stateChanged(Sync::SYNC_PROGRESS_INITIALISING);
1785+ return d->mAuth->authenticate();
1786+}
1787+
1788+void
1789+UContactsClient::abortSync(Sync::SyncStatus aStatus)
1790+{
1791+ FUNCTION_CALL_TRACE;
1792+ Q_D(UContactsClient);
1793+
1794+ d->mAborted = true;
1795+ d->mRemoteSource->abort();
1796+ emit syncFinished(Sync::SYNC_ABORTED);
1797+}
1798+
1799+bool
1800+UContactsClient::initConfig()
1801+{
1802+ FUNCTION_CALL_TRACE;
1803+ Q_D(UContactsClient);
1804+
1805+ //TODO: support multiple remote databases "scopes"
1806+ QStringList accountList = iProfile.keyValues(Buteo::KEY_ACCOUNT_ID);
1807+ if (!accountList.isEmpty()) {
1808+ QString aId = accountList.first();
1809+ if (aId != NULL) {
1810+ d->mAccountId = aId.toInt();
1811+ }
1812+ } else {
1813+ d->mAccountId = 0;
1814+ LOG_WARNING("Account id not found in config profile");
1815+ return false;
1816+ }
1817+
1818+ QStringList databaseName = iProfile.keyValues(Buteo::KEY_DISPLAY_NAME);
1819+ if (databaseName.isEmpty()) {
1820+ LOG_WARNING("\"displayname\" is missing on configuration file");
1821+ return false;
1822+ }
1823+ d->mSyncTarget = databaseName.first();
1824+ d->mSyncDirection = iProfile.syncDirection();
1825+ d->mConflictResPolicy = iProfile.conflictResolutionPolicy();
1826+
1827+ return true;
1828+}
1829+
1830+void
1831+UContactsClient::onAuthenticationError()
1832+{
1833+ LOG_WARNING("Fail to authenticate with account");
1834+ emit syncFinished (Sync::SYNC_AUTHENTICATION_FAILURE);
1835+}
1836+
1837+bool
1838+UContactsClient::start()
1839+{
1840+ FUNCTION_CALL_TRACE;
1841+ Q_D(UContactsClient);
1842+
1843+ /*
1844+ 1. If no previous sync, go for slow-sync. Fetch all contacts
1845+ from server
1846+ 2. Check if previous sync happened (from SyncLog). If yes,
1847+ fetch the time of last sync
1848+ 3. Using the last sync time, retrieve all contacts from server
1849+ that were added/modified/deleted
1850+ 4. Fetch all added/modified/deleted items from device
1851+ 5. Check for conflicts. Take the default policy as "server-wins"
1852+ 6. Save the list from the server to device
1853+ 7. Push "client changes" - "conflicting items" to the server
1854+ 8. Save the sync log
1855+ */
1856+
1857+ // Remote source will be create after authentication since it needs some information
1858+ // about the authentication (auth-token, etc..)
1859+
1860+ LOG_INFO("Sync Started at:" << QDateTime::currentDateTime().toUTC().toString(Qt::ISODate));
1861+ if (d->mAborted) {
1862+ LOG_WARNING("Sync aborted");
1863+ return false;
1864+ }
1865+
1866+ stateChanged(Sync::SYNC_PROGRESS_RECEIVING_ITEMS);
1867+
1868+ if (!d->mRemoteSource->init(remoteSourceProperties())) {
1869+ LOG_WARNING("Fail to init remote source");
1870+ return false;
1871+ }
1872+
1873+ if (!d->mContactBackend->init(d->mAccountId, d->mAuth->accountDisplayName())) {
1874+ LOG_WARNING("Fail to init contact backend");
1875+ return false;
1876+ }
1877+
1878+ switch (d->mSyncDirection)
1879+ {
1880+ case Buteo::SyncProfile::SYNC_DIRECTION_TWO_WAY:
1881+ {
1882+ QDateTime sinceDate = d->mSlowSync ? QDateTime() : lastSyncTime();
1883+
1884+ LOG_DEBUG("load all contacts since" << sinceDate << sinceDate.isValid());
1885+ // load changed contact since the last sync date or all contacts if no
1886+ // sync was done before
1887+ loadLocalContacts(sinceDate);
1888+
1889+ // load remote contacts
1890+ if (d->mSlowSync) {
1891+ connect(d->mRemoteSource,
1892+ SIGNAL(contactsFetched(QList<QtContacts::QContact>,Sync::SyncStatus,qreal)),
1893+ SLOT(onRemoteContactsFetchedForSlowSync(QList<QtContacts::QContact>,Sync::SyncStatus,qreal)));
1894+ } else {
1895+ connect(d->mRemoteSource,
1896+ SIGNAL(contactsFetched(QList<QtContacts::QContact>,Sync::SyncStatus,qreal)),
1897+ SLOT(onRemoteContactsFetchedForFastSync(QList<QtContacts::QContact>,Sync::SyncStatus, qreal)));
1898+ }
1899+ d->mRemoteSource->fetchContacts(sinceDate, !d->mSlowSync, true);
1900+ break;
1901+ }
1902+ case Buteo::SyncProfile::SYNC_DIRECTION_FROM_REMOTE:
1903+ LOG_WARNING("SYNC_DIRECTION_FROM_REMOTE: not implemented");
1904+ return false;
1905+ case Buteo::SyncProfile::SYNC_DIRECTION_TO_REMOTE:
1906+ LOG_WARNING("SYNC_DIRECTION_TO_REMOTE: not implemented");
1907+ return false;
1908+ case Buteo::SyncProfile::SYNC_DIRECTION_UNDEFINED:
1909+ // Not required
1910+ default:
1911+ // throw configuration error
1912+ return false;
1913+ break;
1914+ };
1915+
1916+ return true;
1917+}
1918+
1919+QList<QContact>
1920+UContactsClient::prepareContactsToUpload(UContactsBackend *backend,
1921+ const QSet<QContactId> &ids)
1922+{
1923+ QList<QContact> toUpdate;
1924+
1925+ foreach(const QContactId &id, ids) {
1926+ QContact contact = backend->getContact(id);
1927+ if (!contact.isEmpty()) {
1928+ toUpdate << contact;
1929+ } else {
1930+ LOG_CRITICAL("Fail to find local contact with id:" << id);
1931+ return QList<QContact>();
1932+ }
1933+ }
1934+
1935+ return toUpdate;
1936+}
1937+
1938+void
1939+UContactsClient::onRemoteContactsFetchedForSlowSync(const QList<QContact> contacts,
1940+ Sync::SyncStatus status,
1941+ qreal progress)
1942+{
1943+ FUNCTION_CALL_TRACE;
1944+ Q_D(UContactsClient);
1945+
1946+ if (d->mAborted) {
1947+ LOG_WARNING("Sync aborted");
1948+ return;
1949+ }
1950+
1951+ if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
1952+ disconnect(d->mRemoteSource);
1953+ }
1954+
1955+ if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
1956+ // save remote contacts locally
1957+ storeToLocalForSlowSync(contacts);
1958+
1959+ if (status == Sync::SYNC_DONE) {
1960+ stateChanged(Sync::SYNC_PROGRESS_SENDING_ITEMS);
1961+ QList<QContact> toUpload = prepareContactsToUpload(d->mContactBackend, d->mAllLocalContactIds);
1962+ connect(d->mRemoteSource,
1963+ SIGNAL(transactionCommited(QList<QtContacts::QContact>,
1964+ QList<QtContacts::QContact>,
1965+ QStringList,
1966+ QMap<QString, int>,
1967+ Sync::SyncStatus)),
1968+ SLOT(onContactsSavedForSlowSync(QList<QtContacts::QContact>,
1969+ QList<QtContacts::QContact>,
1970+ QStringList,
1971+ QMap<QString, int>,
1972+ Sync::SyncStatus)));
1973+
1974+ d->mRemoteSource->transaction();
1975+ d->mRemoteSource->saveContacts(toUpload);
1976+ d->mRemoteSource->commit();
1977+ } else {
1978+ stateChanged(qRound(progress * 100));
1979+ }
1980+ } else {
1981+ emit syncFinished(status);
1982+ }
1983+}
1984+
1985+void
1986+UContactsClient::onContactsSavedForSlowSync(const QList<QtContacts::QContact> &createdContacts,
1987+ const QList<QtContacts::QContact> &updatedContacts,
1988+ const QStringList &removedContacts,
1989+ const QMap<QString, int> errorMap,
1990+ Sync::SyncStatus status)
1991+{
1992+ FUNCTION_CALL_TRACE;
1993+ Q_D(UContactsClient);
1994+
1995+ if (d->mAborted) {
1996+ LOG_WARNING("Sync aborted");
1997+ return;
1998+ }
1999+
2000+ LOG_DEBUG("AFTER UPLOAD(Slow sync):"
2001+ << "\n\tCreated on remote:" << createdContacts.size()
2002+ << "\n\tUpdated on remote:" << updatedContacts.size()
2003+ << "\n\tRemoved from remote:" << removedContacts.size()
2004+ << "\n\tError reported:" << errorMap.size());
2005+
2006+ if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
2007+ disconnect(d->mRemoteSource);
2008+ }
2009+
2010+ if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
2011+ QList<QContact> changedContacts;
2012+
2013+ changedContacts += createdContacts;
2014+ changedContacts += updatedContacts;
2015+ updateIdsToLocal(changedContacts);
2016+ handleError(errorMap);
2017+
2018+ // sync report
2019+ addProcessedItem(Sync::ITEM_ADDED,
2020+ Sync::REMOTE_DATABASE,
2021+ syncTargetId(),
2022+ createdContacts.size());
2023+
2024+ if (status == Sync::SYNC_PROGRESS) {
2025+ // sync still in progress
2026+ return;
2027+ } else {
2028+ stateChanged(Sync::SYNC_PROGRESS_FINALISING);
2029+ // WORKARDOUND: 'galera' contacts service take a while to fire contacts
2030+ // changed singal, this can cause a new sync due the storage change plugin
2031+ // lets wait 2 secs before fire sync finished signal
2032+ QTimer::singleShot(2000, this, SLOT(fireSyncFinishedSucessfully()));
2033+ return;
2034+ }
2035+ }
2036+
2037+ emit syncFinished(status);
2038+}
2039+
2040+void UContactsClient::onRemoteContactsFetchedForFastSync(const QList<QContact> contacts,
2041+ Sync::SyncStatus status,
2042+ qreal progress)
2043+{
2044+ FUNCTION_CALL_TRACE;
2045+ Q_D(UContactsClient);
2046+
2047+ if (d->mAborted) {
2048+ LOG_WARNING("Sync aborted");
2049+ return;
2050+ }
2051+
2052+ if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
2053+ disconnect(d->mRemoteSource);
2054+ }
2055+
2056+ if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
2057+ // save remote contacts locally
2058+ storeToLocalForFastSync(contacts);
2059+
2060+ if (status == Sync::SYNC_DONE) {
2061+ stateChanged(Sync::SYNC_PROGRESS_SENDING_ITEMS);
2062+ QList<QContact> contactsToUpload;
2063+ QList<QContact> contactsToRemove;
2064+
2065+ // Contacts created locally
2066+ LOG_DEBUG("Total number of Contacts ADDED : " << d->mAddedContactIds.count());
2067+ contactsToUpload = prepareContactsToUpload(d->mContactBackend,
2068+ d->mAddedContactIds.values().toSet());
2069+
2070+ // Contacts modified locally
2071+ LOG_DEBUG("Total number of Contacts MODIFIED : " << d->mModifiedContactIds.count());
2072+ contactsToUpload += prepareContactsToUpload(d->mContactBackend,
2073+ d->mModifiedContactIds.values().toSet());
2074+
2075+ // Contacts deleted locally
2076+ LOG_DEBUG("Total number of Contacts DELETED : " << d->mDeletedContactIds.count());
2077+ contactsToRemove = prepareContactsToUpload(d->mContactBackend,
2078+ d->mDeletedContactIds.values().toSet());
2079+
2080+ connect(d->mRemoteSource,
2081+ SIGNAL(transactionCommited(QList<QtContacts::QContact>,
2082+ QList<QtContacts::QContact>,
2083+ QStringList,
2084+ QMap<QString, int>,
2085+ Sync::SyncStatus)),
2086+ SLOT(onContactsSavedForFastSync(QList<QtContacts::QContact>,
2087+ QList<QtContacts::QContact>,
2088+ QStringList,
2089+ QMap<QString, int>,
2090+ Sync::SyncStatus)));
2091+
2092+ d->mRemoteSource->transaction();
2093+ d->mRemoteSource->saveContacts(contactsToUpload);
2094+ d->mRemoteSource->removeContacts(contactsToRemove);
2095+ d->mRemoteSource->commit();
2096+ } else {
2097+ stateChanged(qRound(progress*100));
2098+ }
2099+ } else {
2100+ emit syncFinished(status);
2101+ }
2102+}
2103+
2104+void
2105+UContactsClient::onContactsSavedForFastSync(const QList<QtContacts::QContact> &createdContacts,
2106+ const QList<QtContacts::QContact> &updatedContacts,
2107+ const QStringList &removedContacts,
2108+ const QMap<QString, int> errorMap,
2109+ Sync::SyncStatus status)
2110+{
2111+ Q_UNUSED(updatedContacts)
2112+ Q_UNUSED(removedContacts)
2113+ FUNCTION_CALL_TRACE;
2114+ Q_D(UContactsClient);
2115+
2116+ if (d->mAborted) {
2117+ LOG_WARNING("Sync aborted");
2118+ return;
2119+ }
2120+
2121+ LOG_DEBUG("AFTER UPLOAD(Fast sync):" << status
2122+ << "\n\tCreated on remote:" << createdContacts.size()
2123+ << "\n\tUpdated on remote:" << updatedContacts.size()
2124+ << "\n\tRemoved from remote:" << removedContacts.size()
2125+ << "\n\tError reported:" << errorMap.size());
2126+
2127+ if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
2128+ disconnect(d->mRemoteSource);
2129+ }
2130+
2131+ if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
2132+ QList<QContact> changedContacts;
2133+
2134+ changedContacts += createdContacts;
2135+ changedContacts += updatedContacts;
2136+
2137+ updateIdsToLocal(changedContacts);
2138+ handleError(errorMap);
2139+
2140+ // we need to call delete again because of error handling
2141+ d->mContactBackend->deleteContacts(removedContacts);
2142+
2143+ // sync report
2144+ addProcessedItem(Sync::ITEM_ADDED,
2145+ Sync::REMOTE_DATABASE,
2146+ syncTargetId(),
2147+ createdContacts.size());
2148+ addProcessedItem(Sync::ITEM_MODIFIED,
2149+ Sync::REMOTE_DATABASE,
2150+ syncTargetId(),
2151+ updatedContacts.size());
2152+ addProcessedItem(Sync::ITEM_DELETED,
2153+ Sync::REMOTE_DATABASE,
2154+ syncTargetId(),
2155+ removedContacts.size());
2156+
2157+ if (status == Sync::SYNC_PROGRESS) {
2158+ // sync still in progress
2159+ return;
2160+ } else {
2161+ stateChanged(Sync::SYNC_PROGRESS_FINALISING);
2162+ // WORKARDOUND: 'galera' contacts service take a while to fire contacts
2163+ // changed singal, this can cause a new sync due the storage change plugin
2164+ // lets wait 2 secs before fire sync finished signal
2165+ QTimer::singleShot(2000, this, SLOT(fireSyncFinishedSucessfully()));
2166+ return;
2167+ }
2168+ }
2169+
2170+ emit syncFinished(status);
2171+}
2172+
2173+void
2174+UContactsClient::fireSyncFinishedSucessfully()
2175+{
2176+ emit syncFinished(Sync::SYNC_DONE);
2177+}
2178+
2179+void UContactsClient::handleError(const QMap<QString, int> &errorMap)
2180+{
2181+ Q_D(UContactsClient);
2182+ QStringList contactToRemove;
2183+
2184+ QMap<QString, int>::const_iterator i = errorMap.begin();
2185+ while(i != errorMap.end()) {
2186+ switch(i.value()) {
2187+ case QContactManager::DoesNotExistError:
2188+ // if the contact does not exists on remote side we will remove it locally
2189+ LOG_DEBUG("Romoving contact locally due the remote error:" << i.key());
2190+ contactToRemove << i.key();
2191+ break;
2192+ default:
2193+ LOG_WARNING("Unexpected error:" << i.value());
2194+ break;
2195+ }
2196+ i++;
2197+ }
2198+
2199+ if (!contactToRemove.isEmpty()) {
2200+ d->mContactBackend->deleteContacts(contactToRemove);
2201+ }
2202+}
2203+
2204+bool
2205+UContactsClient::storeToLocalForSlowSync(const QList<QContact> &remoteContacts)
2206+{
2207+ FUNCTION_CALL_TRACE;
2208+
2209+ Q_D(UContactsClient);
2210+ Q_ASSERT(d->mSlowSync);
2211+
2212+ bool syncSuccess = false;
2213+
2214+ LOG_DEBUG ("@@@storeToLocal#SLOW SYNC");
2215+ // Since we request for all the deleted contacts, if
2216+ // slow sync is performed many times, even deleted contacts
2217+ // will appear in *remoteContacts. Filter them out while
2218+ // saving them to device
2219+ LOG_DEBUG ("TOTAL REMOTE CONTACTS:" << remoteContacts.size());
2220+
2221+ if (!remoteContacts.isEmpty()) {
2222+ QMap<int, UContactsStatus> statusMap;
2223+ QList<QContact> cpyContacts(remoteContacts);
2224+ if (d->mContactBackend->addContacts(cpyContacts, &statusMap)) {
2225+ // TODO: Saving succeeded. Update sync results
2226+ syncSuccess = true;
2227+
2228+ // sync report
2229+ addProcessedItem(Sync::ITEM_ADDED,
2230+ Sync::LOCAL_DATABASE,
2231+ syncTargetId(),
2232+ cpyContacts.count());
2233+ } else {
2234+ // TODO: Saving failed. Update sync results and probably stop sync
2235+ syncSuccess = false;
2236+ }
2237+ }
2238+
2239+ return syncSuccess;
2240+}
2241+
2242+bool
2243+UContactsClient::storeToLocalForFastSync(const QList<QContact> &remoteContacts)
2244+{
2245+ FUNCTION_CALL_TRACE;
2246+ Q_D(UContactsClient);
2247+ Q_ASSERT(!d->mSlowSync);
2248+
2249+ bool syncSuccess = false;
2250+ LOG_DEBUG ("@@@storeToLocal#FAST SYNC");
2251+ QList<QContact> remoteAddedContacts, remoteModifiedContacts, remoteDeletedContacts;
2252+ filterRemoteAddedModifiedDeletedContacts(remoteContacts,
2253+ remoteAddedContacts,
2254+ remoteModifiedContacts,
2255+ remoteDeletedContacts);
2256+
2257+ resolveConflicts(remoteModifiedContacts, remoteDeletedContacts);
2258+
2259+ if (!remoteAddedContacts.isEmpty()) {
2260+ LOG_DEBUG ("***Adding " << remoteAddedContacts.size() << " contacts");
2261+ QMap<int, UContactsStatus> addedStatusMap;
2262+ syncSuccess = d->mContactBackend->addContacts(remoteAddedContacts, &addedStatusMap);
2263+
2264+ if (syncSuccess) {
2265+ // sync report
2266+ addProcessedItem(Sync::ITEM_ADDED,
2267+ Sync::LOCAL_DATABASE,
2268+ syncTargetId(),
2269+ remoteAddedContacts.count());
2270+ }
2271+ }
2272+
2273+ if (!remoteModifiedContacts.isEmpty()) {
2274+ LOG_DEBUG ("***Modifying " << remoteModifiedContacts.size() << " contacts");
2275+ QMap<int, UContactsStatus> modifiedStatusMap =
2276+ d->mContactBackend->modifyContacts(&remoteModifiedContacts);
2277+
2278+ syncSuccess = (modifiedStatusMap.size() > 0);
2279+
2280+ if (syncSuccess) {
2281+ // sync report
2282+ addProcessedItem(Sync::ITEM_MODIFIED,
2283+ Sync::LOCAL_DATABASE,
2284+ syncTargetId(),
2285+ modifiedStatusMap.size());
2286+ }
2287+ }
2288+
2289+ if (!remoteDeletedContacts.isEmpty()) {
2290+ LOG_DEBUG ("***Deleting " << remoteDeletedContacts.size() << " contacts");
2291+ QStringList guidList;
2292+ for (int i=0; i<remoteDeletedContacts.size(); i++) {
2293+ guidList << UContactsBackend::getRemoteId(remoteDeletedContacts.at(i));
2294+ }
2295+
2296+ QStringList localIdList = d->mContactBackend->localIds(guidList);
2297+ QMap<int, UContactsStatus> deletedStatusMap =
2298+ d->mContactBackend->deleteContacts(localIdList);
2299+
2300+ syncSuccess = (deletedStatusMap.size() > 0);
2301+ if (syncSuccess) {
2302+ // sync report
2303+ addProcessedItem(Sync::ITEM_DELETED,
2304+ Sync::LOCAL_DATABASE,
2305+ syncTargetId(),
2306+ localIdList.size());
2307+ }
2308+ }
2309+
2310+ return syncSuccess;
2311+}
2312+
2313+bool
2314+UContactsClient::cleanUp()
2315+{
2316+ FUNCTION_CALL_TRACE;
2317+ //TODO
2318+ return true;
2319+}
2320+
2321+void UContactsClient::connectivityStateChanged(Sync::ConnectivityType aType, bool aState)
2322+{
2323+ FUNCTION_CALL_TRACE;
2324+ LOG_DEBUG("Received connectivity change event:" << aType << " changed to " << aState);
2325+}
2326+
2327+void
2328+UContactsClient::loadLocalContacts(const QDateTime &since)
2329+{
2330+ FUNCTION_CALL_TRACE;
2331+ Q_D(UContactsClient);
2332+
2333+ if (!since.isValid()) {
2334+ d->mAllLocalContactIds = d->mContactBackend->getAllContactIds().toSet();
2335+
2336+ LOG_DEBUG ("Number of contacts:" << d->mAllLocalContactIds.size ());
2337+ } else {
2338+ d->mAddedContactIds = d->mContactBackend->getAllNewContactIds(since);
2339+ d->mModifiedContactIds = d->mContactBackend->getAllModifiedContactIds(since);
2340+ d->mDeletedContactIds = d->mContactBackend->getAllDeletedContactIds(since);
2341+
2342+ LOG_DEBUG ("Number of local added contacts:" << d->mAddedContactIds.size());
2343+ LOG_DEBUG ("Number of local modified contacts:" << d->mModifiedContactIds.size());
2344+ LOG_DEBUG ("Number of local removed contacts:" << d->mDeletedContactIds.size());
2345+ }
2346+}
2347+
2348+void
2349+UContactsClient::onStateChanged(int aState)
2350+{
2351+ FUNCTION_CALL_TRACE;
2352+ emit syncProgressDetail(getProfileName(), aState);
2353+}
2354+
2355+void
2356+UContactsClient::onSyncFinished(Sync::SyncStatus aState)
2357+{
2358+ FUNCTION_CALL_TRACE;
2359+ Q_D(UContactsClient);
2360+
2361+ switch(aState)
2362+ {
2363+ case Sync::SYNC_ERROR:
2364+ case Sync::SYNC_AUTHENTICATION_FAILURE:
2365+ case Sync::SYNC_DATABASE_FAILURE:
2366+ case Sync::SYNC_CONNECTION_ERROR:
2367+ case Sync::SYNC_NOTPOSSIBLE:
2368+ {
2369+ generateResults(false);
2370+ emit error(getProfileName(), "", aState);
2371+ break;
2372+ }
2373+ case Sync::SYNC_DONE:
2374+ // purge all deleted contacts
2375+ d->mContactBackend->purgecontacts(lastSyncTime());
2376+ case Sync::SYNC_ABORTED:
2377+ {
2378+ generateResults(true);
2379+ emit success(getProfileName(), QString::number(aState));
2380+ break;
2381+ }
2382+ case Sync::SYNC_QUEUED:
2383+ case Sync::SYNC_STARTED:
2384+ case Sync::SYNC_PROGRESS:
2385+ default:
2386+ {
2387+ generateResults(false);
2388+ emit error(getProfileName(), "", aState);
2389+ break;
2390+ }
2391+ }
2392+}
2393+
2394+Buteo::SyncResults
2395+UContactsClient::getSyncResults() const
2396+{
2397+ return d_ptr->mResults;
2398+}
2399+
2400+QString
2401+UContactsClient::authToken() const
2402+{
2403+ return d_ptr->mAuth->token();
2404+}
2405+
2406+QString
2407+UContactsClient::syncTargetId() const
2408+{
2409+ return d_ptr->mContactBackend->syncTargetId();
2410+}
2411+
2412+QString UContactsClient::accountName() const
2413+{
2414+ if (d_ptr->mAuth) {
2415+ return d_ptr->mAuth->accountDisplayName();
2416+ }
2417+ return QString();
2418+}
2419+
2420+const QDateTime
2421+UContactsClient::lastSyncTime() const
2422+{
2423+ FUNCTION_CALL_TRACE;
2424+
2425+ Buteo::ProfileManager pm;
2426+ Buteo::SyncProfile* sp = pm.syncProfile (iProfile.name ());
2427+ QDateTime lastTime = sp->lastSuccessfulSyncTime();
2428+ if (!lastTime.isNull()) {
2429+ // return UTC time used by google
2430+ return lastTime.addSecs(3).toUTC();
2431+ } else {
2432+ return lastTime;
2433+ }
2434+}
2435+
2436+
2437+void
2438+UContactsClient::filterRemoteAddedModifiedDeletedContacts(const QList<QContact> remoteContacts,
2439+ QList<QContact> &remoteAddedContacts,
2440+ QList<QContact> &remoteModifiedContacts,
2441+ QList<QContact> &remoteDeletedContacts)
2442+{
2443+ FUNCTION_CALL_TRACE;
2444+ Q_D(UContactsClient);
2445+
2446+ foreach (const QContact &contact, remoteContacts) {
2447+ if (UContactsBackend::deleted(contact)) {
2448+ remoteDeletedContacts.append(contact);
2449+ continue;
2450+ }
2451+
2452+ QString remoteId = UContactsBackend::getRemoteId(contact);
2453+ QContactId localId = d->mContactBackend->entryExists(remoteId);
2454+ if (localId.isNull()) {
2455+ remoteAddedContacts.append(contact);
2456+ } else {
2457+ remoteModifiedContacts.append(contact);
2458+ }
2459+ }
2460+}
2461+
2462+void
2463+UContactsClient::resolveConflicts(QList<QContact> &modifiedRemoteContacts,
2464+ QList<QContact> &deletedRemoteContacts)
2465+{
2466+ FUNCTION_CALL_TRACE;
2467+ Q_D(UContactsClient);
2468+
2469+ // TODO: Handle conflicts. The steps:
2470+ // o Compare the list of local modified/deleted contacts with
2471+ // the list of remote modified/deleted contacts
2472+ // o Create a new list (a map maybe) that has the contacts to
2473+ // be modified/deleted using the conflict resolution policy
2474+ // (server-wins, client-wins, add-new)
2475+ // o Return the list
2476+
2477+ //QListIterator<GContactEntry*> iter (modifiedRemoteContacts);
2478+ QList<QContact>::iterator iter;
2479+ for (iter = modifiedRemoteContacts.begin (); iter != modifiedRemoteContacts.end (); ++iter) {
2480+ QContact contact = *iter;
2481+ QString remoteId = UContactsBackend::getRemoteId(contact);
2482+
2483+ if (d->mModifiedContactIds.contains(remoteId)) {
2484+ if (d->mConflictResPolicy == Buteo::SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES) {
2485+ modifiedRemoteContacts.erase(iter);
2486+ } else {
2487+ d->mModifiedContactIds.remove(remoteId);
2488+ }
2489+ }
2490+
2491+ if (d->mDeletedContactIds.contains(remoteId)) {
2492+ if (d->mConflictResPolicy == Buteo::SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES) {
2493+ modifiedRemoteContacts.erase(iter);
2494+ } else {
2495+ d->mDeletedContactIds.remove(remoteId);
2496+ }
2497+ }
2498+ }
2499+
2500+ for (iter = deletedRemoteContacts.begin (); iter != deletedRemoteContacts.end (); ++iter) {
2501+ QContact contact = *iter;
2502+ QString remoteId = UContactsBackend::getRemoteId(contact);
2503+
2504+ if (d->mModifiedContactIds.contains(remoteId)) {
2505+ if (d->mConflictResPolicy == Buteo::SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES) {
2506+ deletedRemoteContacts.erase(iter);
2507+ } else {
2508+ d->mModifiedContactIds.remove(remoteId);
2509+ }
2510+ }
2511+
2512+ if (d->mDeletedContactIds.contains(remoteId)) {
2513+ // If the entry is deleted both at the server and
2514+ // locally, then just remove it from the lists
2515+ // so that no further action need to be taken
2516+ deletedRemoteContacts.erase(iter);
2517+ d->mDeletedContactIds.remove(remoteId);
2518+ }
2519+ }
2520+}
2521+
2522+void
2523+UContactsClient::updateIdsToLocal(const QList<QContact> &contacts)
2524+{
2525+ FUNCTION_CALL_TRACE;
2526+ Q_D(UContactsClient);
2527+ QList<QContact> newList(contacts);
2528+ d->mContactBackend->modifyContacts(&newList);
2529+}
2530+
2531+void
2532+UContactsClient::addProcessedItem(Sync::TransferType modificationType,
2533+ Sync::TransferDatabase database,
2534+ const QString &modifiedDatabase,
2535+ int count)
2536+{
2537+ FUNCTION_CALL_TRACE;
2538+ Q_D(UContactsClient);
2539+
2540+ Buteo::DatabaseResults& results = d->mItemResults[modifiedDatabase];
2541+ if (database == Sync::LOCAL_DATABASE) {
2542+ if (modificationType == Sync::ITEM_ADDED) {
2543+ results.iLocalItemsAdded += count;
2544+ } else if (modificationType == Sync::ITEM_MODIFIED) {
2545+ results.iLocalItemsModified += count;
2546+ } else if (modificationType == Sync::ITEM_DELETED) {
2547+ results.iLocalItemsDeleted += count;
2548+ }
2549+ } else if (database == Sync::REMOTE_DATABASE) {
2550+ if( modificationType == Sync::ITEM_ADDED) {
2551+ results.iRemoteItemsAdded += count;
2552+ } else if (modificationType == Sync::ITEM_MODIFIED) {
2553+ results.iRemoteItemsModified += count;
2554+ } else if (modificationType == Sync::ITEM_DELETED) {
2555+ results.iRemoteItemsDeleted += count;
2556+ }
2557+ }
2558+
2559+ emit transferProgress(getProfileName(), database, modificationType, "text/vcard", count);
2560+}
2561+
2562+void
2563+UContactsClient::generateResults(bool aSuccessful)
2564+{
2565+ FUNCTION_CALL_TRACE;
2566+ Q_D(UContactsClient);
2567+
2568+ d->mResults = Buteo::SyncResults();
2569+ d->mResults.setMajorCode(aSuccessful ? Buteo::SyncResults::SYNC_RESULT_SUCCESS :
2570+ Buteo::SyncResults::SYNC_RESULT_FAILED );
2571+ d->mResults.setTargetId(iProfile.name());
2572+ if (d->mItemResults.isEmpty()) {
2573+ LOG_INFO("No items transferred");
2574+ } else {
2575+ QMapIterator<QString, Buteo::DatabaseResults> i(d->mItemResults);
2576+ while (i.hasNext())
2577+ {
2578+ i.next();
2579+ const Buteo::DatabaseResults &r = i.value();
2580+ Buteo::TargetResults targetResults(i.key(), // Target name
2581+ Buteo::ItemCounts(r.iLocalItemsAdded,
2582+ r.iLocalItemsDeleted,
2583+ r.iLocalItemsModified),
2584+ Buteo::ItemCounts(r.iRemoteItemsAdded,
2585+ r.iRemoteItemsDeleted,
2586+ r.iRemoteItemsModified));
2587+ d->mResults.addTargetResults(targetResults);
2588+ LOG_INFO("Sync finished at:" << d->mResults.syncTime().toUTC().toString(Qt::ISODate));
2589+ LOG_INFO("Items for" << targetResults.targetName() << ":");
2590+ LOG_INFO("LA:" << targetResults.localItems().added <<
2591+ "LD:" << targetResults.localItems().deleted <<
2592+ "LM:" << targetResults.localItems().modified <<
2593+ "RA:" << targetResults.remoteItems().added <<
2594+ "RD:" << targetResults.remoteItems().deleted <<
2595+ "RM:" << targetResults.remoteItems().modified);
2596+ }
2597+ }
2598+}
2599
2600=== added file 'buteo-contact-client/UContactsClient.h'
2601--- buteo-contact-client/UContactsClient.h 1970-01-01 00:00:00 +0000
2602+++ buteo-contact-client/UContactsClient.h 2015-09-24 17:49:24 +0000
2603@@ -0,0 +1,170 @@
2604+/*
2605+ * This file is part of buteo-sync-plugins-contacts package
2606+ *
2607+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
2608+ * 2015 Canonical Ltd
2609+ *
2610+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
2611+ * Mani Chandrasekar <maninc@gmail.com>
2612+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
2613+ *
2614+ * This library is free software; you can redistribute it and/or
2615+ * modify it under the terms of the GNU Lesser General Public License
2616+ * version 2.1 as published by the Free Software Foundation.
2617+ *
2618+ * This library is distributed in the hope that it will be useful, but
2619+ * WITHOUT ANY WARRANTY; without even the implied warranty of
2620+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2621+ * Lesser General Public License for more details.
2622+ *
2623+ * You should have received a copy of the GNU Lesser General Public
2624+ * License along with this library; if not, write to the Free Software
2625+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
2626+ * 02110-1301 USA
2627+ *
2628+ */
2629+
2630+#ifndef UCONTACSTCLIENT_H
2631+#define UCONTACSTCLIENT_H
2632+
2633+#include <QNetworkReply>
2634+#include <QContact>
2635+#include <QList>
2636+#include <QPair>
2637+
2638+#include <ClientPlugin.h>
2639+
2640+class UContactsClientPrivate;
2641+class UAuth;
2642+class UAbstractRemoteSource;
2643+class UContactsBackend;
2644+
2645+class UContactsClient : public Buteo::ClientPlugin
2646+{
2647+ Q_OBJECT
2648+ Q_DECLARE_PRIVATE(UContactsClient)
2649+
2650+public:
2651+
2652+ /*! \brief Constructor
2653+ *
2654+ * @param aPluginName Name of this client plugin
2655+ * @param aProfile Sync profile
2656+ * @param aCbInterface Pointer to the callback interface
2657+ * @param authenticator a instance of UAuth class to be used during the authentication
2658+ */
2659+ UContactsClient(const QString& aPluginName,
2660+ const Buteo::SyncProfile &aProfile,
2661+ Buteo::PluginCbInterface *aCbInterface,
2662+ const QString& serviceName);
2663+
2664+ /*! \brief Destructor
2665+ *
2666+ * Call uninit before destroying the object.
2667+ */
2668+ virtual ~UContactsClient();
2669+
2670+ //! @see SyncPluginBase::init
2671+ virtual bool init();
2672+
2673+ //! @see SyncPluginBase::uninit
2674+ virtual bool uninit();
2675+
2676+ //! @see ClientPlugin::startSync
2677+ virtual bool startSync();
2678+
2679+ //! @see SyncPluginBase::abortSync
2680+ virtual void abortSync(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED);
2681+
2682+ //! @see SyncPluginBase::getSyncResults
2683+ virtual Buteo::SyncResults getSyncResults() const;
2684+
2685+ //! @see SyncPluginBase::cleanUp
2686+ virtual bool cleanUp();
2687+
2688+public slots:
2689+ //! @see SyncPluginBase::connectivityStateChanged
2690+ virtual void connectivityStateChanged( Sync::ConnectivityType aType,
2691+ bool aState );
2692+
2693+protected:
2694+ QString authToken() const;
2695+ QString syncTargetId() const;
2696+ QString accountName() const;
2697+
2698+ // Must be implemented by the plugins
2699+ virtual UAbstractRemoteSource* createRemoteSource(QObject *parent) const = 0;
2700+ virtual QVariantMap remoteSourceProperties() const = 0;
2701+
2702+ virtual UContactsBackend* createContactsBackend(QObject *parent) const;
2703+ virtual UAuth* crateAuthenticator(QObject *parent) const;
2704+
2705+ virtual bool isReadyToSync() const;
2706+ virtual const QDateTime lastSyncTime() const;
2707+
2708+signals:
2709+ void stateChanged(int progress);
2710+ void itemProcessed(Sync::TransferType type,
2711+ Sync::TransferDatabase db,
2712+ int committedItems);
2713+ void syncFinished(Sync::SyncStatus);
2714+
2715+
2716+private:
2717+ QScopedPointer<UContactsClientPrivate> d_ptr;
2718+
2719+ void loadLocalContacts(const QDateTime &since);
2720+
2721+ bool initConfig();
2722+ void generateResults(bool aSuccessful);
2723+ void updateIdsToLocal(const QList<QtContacts::QContact> &contacts);
2724+ void filterRemoteAddedModifiedDeletedContacts(const QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> remoteContacts,
2725+ QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &remoteAddedContacts,
2726+ QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &remoteModifiedContacts,
2727+ QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &remoteDeletedContacts);
2728+ void resolveConflicts(QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &modifiedRemoteContacts,
2729+ QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &deletedRemoteContacts);
2730+ void addProcessedItem(Sync::TransferType modificationType,
2731+ Sync::TransferDatabase database,
2732+ const QString &modifiedDatabase,
2733+ int count = 1);
2734+ QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> prepareContactsToUpload(UContactsBackend *backend,
2735+ const QSet<QTCONTACTS_PREPEND_NAMESPACE(QContactId)> &ids);
2736+
2737+ /* slow sync */
2738+ bool storeToLocalForSlowSync(const QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &remoteContacts);
2739+
2740+ /* fast sync */
2741+ bool storeToLocalForFastSync(const QList<QTCONTACTS_PREPEND_NAMESPACE(QContact)> &remoteContacts);
2742+
2743+private slots:
2744+ bool start();
2745+ void onAuthenticationError();
2746+ void onStateChanged(int progress);
2747+ void onSyncFinished(Sync::SyncStatus status);
2748+ void fireSyncFinishedSucessfully();
2749+ void handleError(const QMap<QString, int> &errorMap);
2750+
2751+
2752+ /* slow sync */
2753+ void onRemoteContactsFetchedForSlowSync(const QList<QtContacts::QContact> contacts,
2754+ Sync::SyncStatus status,
2755+ qreal progress);
2756+ void onContactsSavedForSlowSync(const QList<QtContacts::QContact> &createdContacts,
2757+ const QList<QtContacts::QContact> &updatedContacts,
2758+ const QStringList &removedContacts,
2759+ const QMap<QString, int> errorList,
2760+ Sync::SyncStatus status);
2761+ /* fast sync */
2762+ void onRemoteContactsFetchedForFastSync(const QList<QtContacts::QContact> contacts,
2763+ Sync::SyncStatus status,
2764+ qreal progress);
2765+ void onContactsSavedForFastSync(const QList<QtContacts::QContact> &createdContacts,
2766+ const QList<QtContacts::QContact> &updatedContacts,
2767+ const QStringList &removedContacts,
2768+ const QMap<QString, int> errorMap,
2769+ Sync::SyncStatus status);
2770+
2771+};
2772+
2773+#endif // UCONTACTCLIENT_H
2774
2775=== added file 'buteo-contact-client/UContactsCustomDetail.cpp'
2776--- buteo-contact-client/UContactsCustomDetail.cpp 1970-01-01 00:00:00 +0000
2777+++ buteo-contact-client/UContactsCustomDetail.cpp 2015-09-24 17:49:24 +0000
2778@@ -0,0 +1,56 @@
2779+/*
2780+ * This file is part of buteo-sync-plugins-contacts package
2781+ *
2782+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
2783+ * 2015 Canonical Ltd
2784+ *
2785+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
2786+ * Mani Chandrasekar <maninc@gmail.com>
2787+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
2788+ *
2789+ * This library is free software; you can redistribute it and/or
2790+ * modify it under the terms of the GNU Lesser General Public License
2791+ * version 2.1 as published by the Free Software Foundation.
2792+ *
2793+ * This library is distributed in the hope that it will be useful, but
2794+ * WITHOUT ANY WARRANTY; without even the implied warranty of
2795+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2796+ * Lesser General Public License for more details.
2797+ *
2798+ * You should have received a copy of the GNU Lesser General Public
2799+ * License along with this library; if not, write to the Free Software
2800+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
2801+ * 02110-1301 USA
2802+ *
2803+ */
2804+
2805+#include "UContactsCustomDetail.h"
2806+
2807+// Ubuntu fields
2808+const QString UContactsCustomDetail::FieldContactETag = "X-GOOGLE-ETAG";
2809+const QString UContactsCustomDetail::FieldGroupMembershipInfo = "X-GROUP-ID";
2810+const QString UContactsCustomDetail::FieldRemoteId = "X-REMOTE-ID";
2811+const QString UContactsCustomDetail::FieldDeletedAt = "X-DELETED-AT";
2812+const QString UContactsCustomDetail::FieldCreatedAt = "X-CREATED-AT";
2813+const QString UContactsCustomDetail::FieldContactAvatarETag = "X-AVATAR-REV";
2814+
2815+QContactExtendedDetail
2816+UContactsCustomDetail::getCustomField(const QContact &contact, const QString &name)
2817+{
2818+ foreach (QContactExtendedDetail xd, contact.details<QContactExtendedDetail>()) {
2819+ if (xd.name() == name) {
2820+ return xd;
2821+ }
2822+ }
2823+ QContactExtendedDetail xd;
2824+ xd.setName(name);
2825+ return xd;
2826+}
2827+
2828+
2829+void UContactsCustomDetail::setCustomField(QtContacts::QContact &contact, const QString &name, const QVariant &value)
2830+{
2831+ QContactExtendedDetail xd = getCustomField(contact, name);
2832+ xd.setData(value);
2833+ contact.saveDetail(&xd);
2834+}
2835
2836=== added file 'buteo-contact-client/UContactsCustomDetail.h'
2837--- buteo-contact-client/UContactsCustomDetail.h 1970-01-01 00:00:00 +0000
2838+++ buteo-contact-client/UContactsCustomDetail.h 2015-09-24 17:49:24 +0000
2839@@ -0,0 +1,52 @@
2840+/*
2841+ * This file is part of buteo-sync-plugins-contacts package
2842+ *
2843+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
2844+ * 2015 Canonical Ltd
2845+ *
2846+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
2847+ * Mani Chandrasekar <maninc@gmail.com>
2848+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
2849+ *
2850+ * This library is free software; you can redistribute it and/or
2851+ * modify it under the terms of the GNU Lesser General Public License
2852+ * version 2.1 as published by the Free Software Foundation.
2853+ *
2854+ * This library is distributed in the hope that it will be useful, but
2855+ * WITHOUT ANY WARRANTY; without even the implied warranty of
2856+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2857+ * Lesser General Public License for more details.
2858+ *
2859+ * You should have received a copy of the GNU Lesser General Public
2860+ * License along with this library; if not, write to the Free Software
2861+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
2862+ * 02110-1301 USA
2863+ *
2864+ */
2865+
2866+#ifndef UCONTACTSCUSTOMDETAIL_H
2867+#define UCONTACTSCUSTOMDETAIL_H
2868+
2869+#include <QString>
2870+
2871+#include <QContact>
2872+#include <QContactExtendedDetail>
2873+
2874+QTCONTACTS_USE_NAMESPACE
2875+
2876+class UContactsCustomDetail
2877+{
2878+public:
2879+ // Ubuntu fields
2880+ static const QString FieldContactETag;
2881+ static const QString FieldRemoteId;
2882+ static const QString FieldGroupMembershipInfo;
2883+ static const QString FieldDeletedAt;
2884+ static const QString FieldCreatedAt;
2885+ static const QString FieldContactAvatarETag;
2886+
2887+ static QContactExtendedDetail getCustomField(const QContact &contact, const QString &name);
2888+ static void setCustomField(QContact &contact, const QString &name, const QVariant &value);
2889+};
2890+
2891+#endif // GCONTACTCUSTOMDETAIL_H
2892
2893=== added directory 'cmake'
2894=== added file 'cmake/lcov.cmake'
2895--- cmake/lcov.cmake 1970-01-01 00:00:00 +0000
2896+++ cmake/lcov.cmake 2015-09-24 17:49:24 +0000
2897@@ -0,0 +1,72 @@
2898+# - This module creates a new 'lcov' target which generates
2899+# a coverage analysis html output.
2900+# LCOV is a graphical front-end for GCC's coverage testing tool gcov. Please see
2901+# http://ltp.sourceforge.net/coverage/lcov.php
2902+#
2903+# Usage: you must add an option to your CMakeLists.txt to build your application
2904+# with coverage support. Then you need to include this file to the lcov target.
2905+#
2906+# Example:
2907+# IF(BUILD_WITH_COVERAGE)
2908+# SET(CMAKE_C_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
2909+# SET(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage")
2910+# SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -lgcov")
2911+# include(${CMAKE_SOURCE_DIR}/cmake/lcov.cmake)
2912+# ENDIF(BUILD_WITH_COVERAGE)
2913+#=============================================================================
2914+# Copyright 2010 ascolab GmbH
2915+#
2916+# Distributed under the OSI-approved BSD License (the "License");
2917+# see accompanying file Copyright.txt for details.
2918+#
2919+# This software is distributed WITHOUT ANY WARRANTY; without even the
2920+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2921+# See the License for more information.
2922+#=============================================================================
2923+# (To distributed this file outside of CMake, substitute the full
2924+# License text for the above reference.)
2925+
2926+set(REMOVE_PATTERN
2927+ q*.h
2928+ *.moc
2929+ moc_*.cpp
2930+ locale_facets.h
2931+ new
2932+ move.h
2933+ qcontactmemorybackend.cpp
2934+ limits
2935+ /usr/include/c++/4.9/bits/*
2936+ /usr/include/buteosyncfw5/*
2937+ )
2938+
2939+## lcov target
2940+ADD_CUSTOM_TARGET(lcov)
2941+ADD_CUSTOM_COMMAND(TARGET lcov
2942+ COMMAND mkdir -p coverage
2943+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2944+ )
2945+ADD_CUSTOM_COMMAND(TARGET lcov
2946+ COMMAND lcov --directory . --zerocounters
2947+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2948+ )
2949+ADD_CUSTOM_COMMAND(TARGET lcov
2950+ COMMAND make test
2951+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2952+ )
2953+ADD_CUSTOM_COMMAND(TARGET lcov
2954+ COMMAND lcov --directory . --capture --output-file ./coverage/stap_all.info --no-checksum --compat-libtool
2955+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2956+ )
2957+ADD_CUSTOM_COMMAND(TARGET lcov
2958+ COMMAND lcov --directory . -r ./coverage/stap_all.info ${REMOVE_PATTERN} --output-file ./coverage/stap.info
2959+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2960+ )
2961+ADD_CUSTOM_COMMAND(TARGET lcov
2962+ COMMAND genhtml -o ./coverage --title "Code Coverage" --legend --show-details --demangle-cpp ./coverage/stap.info
2963+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2964+ )
2965+ADD_CUSTOM_COMMAND(TARGET lcov
2966+ COMMAND echo "Open ${CMAKE_BINARY_DIR}/coverage/index.html to view the coverage analysis results."
2967+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
2968+ )
2969+
2970
2971=== added file 'cmake_uninstall.cmake.in'
2972--- cmake_uninstall.cmake.in 1970-01-01 00:00:00 +0000
2973+++ cmake_uninstall.cmake.in 2015-09-24 17:49:24 +0000
2974@@ -0,0 +1,21 @@
2975+IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
2976+ MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
2977+ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
2978+
2979+FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
2980+STRING(REGEX REPLACE "\n" ";" files "${files}")
2981+FOREACH(file ${files})
2982+ MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
2983+ IF(EXISTS "$ENV{DESTDIR}${file}")
2984+ EXEC_PROGRAM(
2985+ "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
2986+ OUTPUT_VARIABLE rm_out
2987+ RETURN_VALUE rm_retval
2988+ )
2989+ IF(NOT "${rm_retval}" STREQUAL 0)
2990+ MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
2991+ ENDIF(NOT "${rm_retval}" STREQUAL 0)
2992+ ELSE(EXISTS "$ENV{DESTDIR}${file}")
2993+ MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
2994+ ENDIF(EXISTS "$ENV{DESTDIR}${file}")
2995+ENDFOREACH(file)
2996
2997=== added file 'config.h.in'
2998--- config.h.in 1970-01-01 00:00:00 +0000
2999+++ config.h.in 2015-09-24 17:49:24 +0000
3000@@ -0,0 +1,9 @@
3001+#ifndef __CONFIG_H__
3002+#define __CONFIG_H__
3003+
3004+#include <QString>
3005+
3006+const QString GOOGLE_ETAG_DETAIL ("X-GOOGLE-ETAG");
3007+const QString QCONTACTS_BACKEND_NAME ("galera");
3008+
3009+#endif
3010
3011=== added directory 'google'
3012=== added file 'google/CMakeLists.txt'
3013--- google/CMakeLists.txt 1970-01-01 00:00:00 +0000
3014+++ google/CMakeLists.txt 2015-09-24 17:49:24 +0000
3015@@ -0,0 +1,87 @@
3016+project(buteo-contact-google)
3017+set(GOOGLE_CONTACTS_CLIENT googlecontacts-client)
3018+set(GOOGLE_CONTACTS_LIB googlecontacts-lib)
3019+
3020+include_directories(
3021+ ${CMAKE_BINARY_DIR}
3022+ ${buteo-contact-client_SOURCE_DIR}
3023+ ${ACCOUNTS_INCLUDE_DIRS}
3024+ ${BUTEOSYNCFW_INCLUDE_DIRS}
3025+ ${LIBSIGNON_INCLUDE_DIRS}
3026+)
3027+
3028+set(GOOGLE_CONTACTS_LIB_SRCS
3029+ atom_global.h
3030+ buteo-gcontact-plugin_global.h
3031+ buteosyncfw_p.h
3032+ GConfig.h
3033+ GConfig.cpp
3034+ GContactAtom.h
3035+ GContactAtom.cpp
3036+ GContactImageDownloader.h
3037+ GContactImageDownloader.cpp
3038+ GContactStream.h
3039+ GContactStream.cpp
3040+ GRemoteSource.h
3041+ GRemoteSource.cpp
3042+)
3043+
3044+add_library(${GOOGLE_CONTACTS_LIB} STATIC
3045+ ${GOOGLE_CONTACTS_LIB_SRCS}
3046+)
3047+
3048+target_link_libraries(${GOOGLE_CONTACTS_LIB}
3049+ ${ACCOUNTS_LIBRARIES}
3050+ ${BUTEOSYNCFW_LIBRARIES}
3051+ ${LIBSIGNON_LIBRARIES}
3052+ ubuntu-contact-client
3053+)
3054+
3055+qt5_use_modules(${GOOGLE_CONTACTS_LIB} Core Network Contacts)
3056+
3057+# Buteo oop plugin
3058+add_definitions(-DCLASSNAME=GContactsClient)
3059+add_definitions(-DCLASSNAME_H=\"GContactsClient.h\")
3060+add_definitions(-DCLIENT_PLUGIN)
3061+set(GOOGLE_CONTACT_BUTEO_SRCS
3062+ ${BUTEOSYNCFW_INCLUDEDIR}/ButeoPluginIfaceAdaptor.h
3063+ ${BUTEOSYNCFW_INCLUDEDIR}/PluginCbImpl.h
3064+ ${BUTEOSYNCFW_INCLUDEDIR}/PluginServiceObj.h
3065+ ${BUTEOSYNCFW_INCLUDEDIR}/ButeoPluginIfaceAdaptor.cpp
3066+ ${BUTEOSYNCFW_INCLUDEDIR}/PluginCbImpl.cpp
3067+ ${BUTEOSYNCFW_INCLUDEDIR}/PluginServiceObj.cpp
3068+ ${BUTEOSYNCFW_INCLUDEDIR}/plugin_main.cpp
3069+)
3070+
3071+set(GOOGLE_CONTACTS_CLIENT_SRCS
3072+ GContactsClient.h
3073+ GContactsClient.cpp
3074+ # real implementation
3075+ GTransport.h
3076+ GTransport.cpp
3077+ GContactImageUploader.cpp
3078+ GContactImageUploader.h
3079+ ${GOOGLE_CONTACT_BUTEO_SRCS}
3080+)
3081+
3082+add_executable(${GOOGLE_CONTACTS_CLIENT}
3083+ ${GOOGLE_CONTACTS_CLIENT_SRCS}
3084+)
3085+
3086+target_link_libraries(${GOOGLE_CONTACTS_CLIENT}
3087+ ${ACCOUNTS_LIBRARIES}
3088+ ${BUTEOSYNCFW_LIBRARIES}
3089+ ${LIBSIGNON_LIBRARIES}
3090+ ${GOOGLE_CONTACTS_LIB}
3091+ ubuntu-contact-client
3092+)
3093+
3094+qt5_use_modules(${GOOGLE_CONTACTS_CLIENT} Core DBus Network Contacts)
3095+
3096+install(TARGETS ${GOOGLE_CONTACTS_CLIENT}
3097+ RUNTIME DESTINATION "${BUTEOSYNCFW_PLUGIN_PATH}/oopp"
3098+)
3099+
3100+add_subdirectory(xmls)
3101+
3102+
3103
3104=== added file 'google/GConfig.cpp'
3105--- google/GConfig.cpp 1970-01-01 00:00:00 +0000
3106+++ google/GConfig.cpp 2015-09-24 17:49:24 +0000
3107@@ -0,0 +1,52 @@
3108+/*
3109+ * This file is part of buteo-sync-plugins-contacts package
3110+ *
3111+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
3112+ * 2015 Canonical Ltd
3113+ *
3114+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
3115+ * Mani Chandrasekar <maninc@gmail.com>
3116+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3117+ *
3118+ * This library is free software; you can redistribute it and/or
3119+ * modify it under the terms of the GNU Lesser General Public License
3120+ * version 2.1 as published by the Free Software Foundation.
3121+ *
3122+ * This library is distributed in the hope that it will be useful, but
3123+ * WITHOUT ANY WARRANTY; without even the implied warranty of
3124+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3125+ * Lesser General Public License for more details.
3126+ *
3127+ * You should have received a copy of the GNU Lesser General Public
3128+ * License along with this library; if not, write to the Free Software
3129+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3130+ * 02110-1301 USA
3131+ *
3132+ */
3133+
3134+#include "GConfig.h"
3135+
3136+const int GConfig::MAX_RESULTS = 30;
3137+const QString GConfig::SCOPE_URL = "https://www.google.com/m8/feeds/";
3138+const QString GConfig::GCONTACT_URL = SCOPE_URL + "/contacts/default/";
3139+
3140+const QString GConfig::GDATA_VERSION_TAG = "GData-Version";
3141+const QString GConfig::GDATA_VERSION = "3.0";
3142+const QString GConfig::G_DELETE_OVERRIDE_HEADER = "X-HTTP-Method-Override: DELETE";
3143+const QString GConfig::G_ETAG_HEADER = "If-Match";
3144+const QString GConfig::G_AUTH_HEADER = "Authorization";
3145+
3146+/* Query parameters */
3147+const QString GConfig::QUERY_TAG = "q";
3148+const QString GConfig::MAX_RESULTS_TAG = "max-results";
3149+const QString GConfig::START_INDEX_TAG = "start-index";
3150+const QString GConfig::UPDATED_MIN_TAG = "updated-min";
3151+const QString GConfig::ORDERBY_TAG = "orderby";
3152+const QString GConfig::SHOW_DELETED_TAG = "showdeleted";
3153+const QString GConfig::REQUIRE_ALL_DELETED = "requirealldeleted";
3154+const QString GConfig::SORTORDER_TAG = "sortorder";
3155+const QString GConfig::GROUP_MY_CONTACTS_ID = "6";
3156+
3157+const QString GConfig::PHOTO_TAG = "photos";
3158+const QString GConfig::MEDIA_TAG = "media";
3159+const QString GConfig::BATCH_TAG = "batch";
3160
3161=== added file 'google/GConfig.h'
3162--- google/GConfig.h 1970-01-01 00:00:00 +0000
3163+++ google/GConfig.h 2015-09-24 17:49:24 +0000
3164@@ -0,0 +1,71 @@
3165+/*
3166+ * This file is part of buteo-sync-plugins-contacts package
3167+ *
3168+ * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
3169+ * 2015 Canonical Ltd
3170+ *
3171+ * Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
3172+ * Mani Chandrasekar <maninc@gmail.com>
3173+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3174+ *
3175+ * This library is free software; you can redistribute it and/or
3176+ * modify it under the terms of the GNU Lesser General Public License
3177+ * version 2.1 as published by the Free Software Foundation.
3178+ *
3179+ * This library is distributed in the hope that it will be useful, but
3180+ * WITHOUT ANY WARRANTY; without even the implied warranty of
3181+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3182+ * Lesser General Public License for more details.
3183+ *
3184+ * You should have received a copy of the GNU Lesser General Public
3185+ * License along with this library; if not, write to the Free Software
3186+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3187+ * 02110-1301 USA
3188+ *
3189+ */
3190+
3191+#ifndef GCONFIG_H
3192+#define GCONFIG_H
3193+
3194+#include <QString>
3195+
3196+class GConfig
3197+{
3198+public:
3199+ static const int MAX_RESULTS;
3200+ static const QString SCOPE_URL;
3201+ static const QString GCONTACT_URL;
3202+
3203+ static const QString GDATA_VERSION_TAG;
3204+ static const QString GDATA_VERSION;
3205+ static const QString G_DELETE_OVERRIDE_HEADER;
3206+ static const QString G_ETAG_HEADER;
3207+ static const QString G_AUTH_HEADER;
3208+
3209+ /* My Contacts Group */
3210+ static const QString GROUP_MY_CONTACTS_ID;
3211+
3212+ /* Query parameters */
3213+ static const QString QUERY_TAG;
3214+ static const QString MAX_RESULTS_TAG;
3215+ static const QString START_INDEX_TAG;
3216+ static const QString UPDATED_MIN_TAG;
3217+ static const QString ORDERBY_TAG;
3218+ static const QString SHOW_DELETED_TAG;
3219+ static const QString REQUIRE_ALL_DELETED;
3220+ static const QString SORTORDER_TAG;
3221+
3222+ static const QString PHOTO_TAG;
3223+ static const QString MEDIA_TAG;
3224+ static const QString BATCH_TAG;
3225+
3226+ typedef enum
3227+ {
3228+ NONE = 0,
3229+ ADD,
3230+ UPDATE,
3231+ DELETE
3232+ } TRANSACTION_TYPE;
3233+};
3234+#endif // GCONFIG_H
3235+
3236
3237=== added file 'google/GContactAtom.cpp'
3238--- google/GContactAtom.cpp 1970-01-01 00:00:00 +0000
3239+++ google/GContactAtom.cpp 2015-09-24 17:49:24 +0000
3240@@ -0,0 +1,209 @@
3241+/****************************************************************************
3242+ **
3243+ ** Copyright (C) 2013-2014 Jolla Ltd. and/or its subsidiary(-ies).
3244+ ** Contact: Chris Adams <chris.adams@jolla.com>
3245+ **
3246+ ** Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
3247+ ** Mani Chandrasekar <maninc@gmail.com>
3248+ ** Chris Adams <chris.adams@jolla.com>
3249+ **
3250+ ** This program/library is free software; you can redistribute it and/or
3251+ ** modify it under the terms of the GNU Lesser General Public License
3252+ ** version 2.1 as published by the Free Software Foundation.
3253+ **
3254+ ** This program/library is distributed in the hope that it will be useful,
3255+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3256+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3257+ ** Lesser General Public License for more details.
3258+ **
3259+ ** You should have received a copy of the GNU Lesser General Public
3260+ ** License along with this program/library; if not, write to the Free
3261+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3262+ ** 02110-1301 USA
3263+ **
3264+ ****************************************************************************/
3265+
3266+#include "GContactAtom.h"
3267+
3268+GoogleContactAtom::BatchOperationResponse::BatchOperationResponse()
3269+ : isError(false)
3270+{
3271+}
3272+
3273+GoogleContactAtom::GoogleContactAtom()
3274+{
3275+}
3276+
3277+void GoogleContactAtom::setAuthorEmail(const QString &authorEmail)
3278+{
3279+ mAuthorEmail = authorEmail;
3280+}
3281+
3282+QString GoogleContactAtom::authorEmail() const
3283+{
3284+ return mAuthorEmail;
3285+}
3286+
3287+void GoogleContactAtom::setAuthorName(const QString &authorName)
3288+{
3289+ mAuthorName = authorName;
3290+}
3291+
3292+QString GoogleContactAtom::authorName() const
3293+{
3294+ return mAuthorName;
3295+}
3296+
3297+void GoogleContactAtom::setId(const QString &id)
3298+{
3299+ mId = id;
3300+}
3301+
3302+QString GoogleContactAtom::id() const
3303+{
3304+ return mId;
3305+}
3306+
3307+void GoogleContactAtom::setUpdated(const QString &updated)
3308+{
3309+ mUpdated = updated;
3310+}
3311+
3312+QString GoogleContactAtom::updated() const
3313+{
3314+ return mUpdated;
3315+}
3316+
3317+void GoogleContactAtom::setCategory(const QString &schema, const QString &term)
3318+{
3319+ mCategorySchema = schema;
3320+ mCategoryTerm = term;
3321+}
3322+
3323+QString GoogleContactAtom::categorySchema() const
3324+{
3325+ return mCategorySchema;
3326+}
3327+
3328+QString GoogleContactAtom::categoryTerm() const
3329+{
3330+ return mCategoryTerm;
3331+}
3332+
3333+void GoogleContactAtom::setTitle(const QString &title)
3334+{
3335+ mTitle = title;
3336+}
3337+
3338+QString GoogleContactAtom::title() const
3339+{
3340+ return mTitle;
3341+}
3342+
3343+void GoogleContactAtom::setGenerator(const QString &name, const QString &version, const QString &uri)
3344+{
3345+ mGeneratorName = name;
3346+ mGeneratorVersion = version;
3347+ mGeneratorUri = uri;
3348+}
3349+
3350+void GoogleContactAtom::setContent(const QString &note, const QString &type)
3351+{
3352+ Q_UNUSED(note)
3353+ Q_UNUSED(type)
3354+}
3355+
3356+QString GoogleContactAtom::generatorName() const
3357+{
3358+ return mGeneratorName;
3359+}
3360+
3361+QString GoogleContactAtom::generatorVersion() const
3362+{
3363+ return mGeneratorVersion;
3364+}
3365+
3366+QString GoogleContactAtom::generatorUri() const
3367+{
3368+ return mGeneratorUri;
3369+}
3370+
3371+void GoogleContactAtom::setTotalResults(int totalResults)
3372+{
3373+ mTotalResults = totalResults;
3374+}
3375+
3376+int GoogleContactAtom::totalResults() const
3377+{
3378+ return mTotalResults;
3379+}
3380+
3381+void GoogleContactAtom::setStartIndex(int startIndex)
3382+{
3383+ mStartIndex = startIndex;
3384+}
3385+
3386+int GoogleContactAtom::startIndex() const
3387+{
3388+ return mStartIndex;
3389+}
3390+
3391+void GoogleContactAtom::setItemsPerPage(int itemsPerPage)
3392+{
3393+ mItemsPerPage = itemsPerPage;
3394+}
3395+
3396+int GoogleContactAtom::itemsPerPage() const
3397+{
3398+ return mItemsPerPage;
3399+}
3400+
3401+void GoogleContactAtom::addBatchOperationResponse(const QString &operationId, GoogleContactAtom::BatchOperationResponse response)
3402+{
3403+ mBatchOperationResponses.insert(operationId, response);
3404+}
3405+
3406+QMap<QString, GoogleContactAtom::BatchOperationResponse> GoogleContactAtom::batchOperationResponses() const
3407+{
3408+ return mBatchOperationResponses;
3409+}
3410+
3411+void GoogleContactAtom::addEntryContact(const QContact &entryContact, const QStringList &unsupportedElements)
3412+{
3413+ mContactList.append(qMakePair(entryContact, unsupportedElements));
3414+}
3415+
3416+QList<QPair<QContact, QStringList> > GoogleContactAtom::entryContacts() const
3417+{
3418+ return mContactList;
3419+}
3420+
3421+void GoogleContactAtom::addDeletedEntryContact(const QContact &deletedContact)
3422+{
3423+ mDeletedContactList.append(deletedContact);
3424+}
3425+
3426+QList<QContact> GoogleContactAtom::deletedEntryContacts() const
3427+{
3428+ return mDeletedContactList;
3429+}
3430+
3431+void GoogleContactAtom::addEntrySystemGroup(const QString &systemGroupId, const QString &systemGroupAtomId)
3432+{
3433+ mSystemGroupAtomIds.insert(systemGroupId, systemGroupAtomId);
3434+}
3435+
3436+QMap<QString, QString> GoogleContactAtom::entrySystemGroups() const
3437+{
3438+ return mSystemGroupAtomIds;
3439+}
3440+
3441+void GoogleContactAtom::setNextEntriesUrl(const QString &nextUrl)
3442+{
3443+ mNextEntriesUrl = nextUrl;
3444+}
3445+
3446+QString GoogleContactAtom::nextEntriesUrl() const
3447+{
3448+ return mNextEntriesUrl;
3449+}
3450
3451=== added file 'google/GContactAtom.h'
3452--- google/GContactAtom.h 1970-01-01 00:00:00 +0000
3453+++ google/GContactAtom.h 2015-09-24 17:49:24 +0000
3454@@ -0,0 +1,138 @@
3455+/****************************************************************************
3456+ **
3457+ ** Copyright (C) 2013-2014 Jolla Ltd. and/or its subsidiary(-ies).
3458+ ** Contact: Chris Adams <chris.adams@jolla.com>
3459+ **
3460+ ** Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
3461+ ** Chris Adams <chris.adams@jolla.com>
3462+ **
3463+ ** This program/library is free software; you can redistribute it and/or
3464+ ** modify it under the terms of the GNU Lesser General Public License
3465+ ** version 2.1 as published by the Free Software Foundation.
3466+ **
3467+ ** This program/library is distributed in the hope that it will be useful,
3468+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3469+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3470+ ** Lesser General Public License for more details.
3471+ **
3472+ ** You should have received a copy of the GNU Lesser General Public
3473+ ** License along with this program/library; if not, write to the Free
3474+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3475+ ** 02110-1301 USA
3476+ **
3477+ ****************************************************************************/
3478+
3479+#ifndef GOOGLECONTACTATOM_H
3480+#define GOOGLECONTACTATOM_H
3481+
3482+#include <QMetaEnum>
3483+#include <QMap>
3484+#include <QList>
3485+#include <QXmlStreamWriter>
3486+
3487+#include <QContact>
3488+
3489+QTCONTACTS_USE_NAMESPACE
3490+
3491+class GoogleContactAtom {
3492+public:
3493+ GoogleContactAtom();
3494+
3495+ void setAuthorName(const QString &authorName);
3496+ QString authorName() const;
3497+
3498+ void setAuthorEmail(const QString &authorEmail);
3499+ QString authorEmail() const;
3500+
3501+ void setId(const QString &id);
3502+ QString id() const;
3503+
3504+ void setUpdated(const QString &updated);
3505+ QString updated() const;
3506+
3507+ void setCategory(const QString &schema = QStringLiteral("http://schemas.google.com/g/2005#kind"),
3508+ const QString &term = QStringLiteral("http://schemas.google.com/contact/2008#contact"));
3509+ QString categorySchema() const;
3510+ QString categoryTerm() const;
3511+
3512+ void setTitle(const QString &title);
3513+ QString title() const;
3514+
3515+ void setContent(const QString &note, const QString &type = QStringLiteral("text"));
3516+
3517+ void setGenerator(const QString &name = QStringLiteral("Contacts"),
3518+ const QString &version = QStringLiteral("1.0"),
3519+ const QString &uri = QStringLiteral("https://sailfishos.org"));
3520+ QString generatorName() const;
3521+ QString generatorVersion() const;
3522+ QString generatorUri() const;
3523+
3524+ void setTotalResults(int totalResults);
3525+ int totalResults() const;
3526+
3527+ void setStartIndex(int startIndex);
3528+ int startIndex() const;
3529+
3530+ void setItemsPerPage(int itemsPerPage);
3531+ int itemsPerPage() const;
3532+
3533+ void addEntryContact(const QContact &contact, const QStringList &unsupportedElements);
3534+ QList<QPair<QContact, QStringList> > entryContacts() const;
3535+ void addDeletedEntryContact(const QContact &contact);
3536+ QList<QContact> deletedEntryContacts() const;
3537+
3538+ void addEntrySystemGroup(const QString &systemGroupId, const QString &systemGroupAtomId);
3539+ QMap<QString, QString> entrySystemGroups() const;
3540+
3541+ void setNextEntriesUrl(const QString &nextUrl);
3542+ QString nextEntriesUrl() const;
3543+
3544+ class BatchOperationResponse {
3545+ public:
3546+ BatchOperationResponse();
3547+ QString operationId;
3548+ QString type;
3549+ QString code;
3550+ QString reason;
3551+ QString reasonDescription;
3552+ QString contactGuid;
3553+ QString eTag;
3554+ bool isError;
3555+ };
3556+ void addBatchOperationResponse(const QString &operationId, BatchOperationResponse response);
3557+ QMap<QString, BatchOperationResponse> batchOperationResponses() const;
3558+
3559+private:
3560+ QString mAuthorEmail;
3561+ QString mAuthorName;
3562+ QString mCategory;
3563+ QString mCategorySchema;
3564+ QString mCategoryTerm;
3565+ QString mContributor;
3566+ QString mGeneratorName;
3567+ QString mGeneratorVersion;
3568+ QString mGeneratorUri;
3569+ QString mIcon;
3570+ QString mId;
3571+ QString mLink;
3572+ QString mLogo;
3573+ QString mRights;
3574+ QString mSubtitle;
3575+ QString mTitle;
3576+ QString mUpdated;
3577+
3578+ int mTotalResults;
3579+ int mStartIndex;
3580+ int mItemsPerPage;
3581+
3582+ QMap<QString, BatchOperationResponse> mBatchOperationResponses;
3583+
3584+ QList<QContact> mDeletedContactList;
3585+ QList<QPair<QContact, QStringList> > mContactList;
3586+
3587+ QMap<QString, QString> mSystemGroupAtomIds;
3588+
3589+ QString mNextEntriesUrl;
3590+};
3591+
3592+#endif // GOOGLECONTACTATOM_H
3593
3594=== added file 'google/GContactGroupsMap.cpp'
3595--- google/GContactGroupsMap.cpp 1970-01-01 00:00:00 +0000
3596+++ google/GContactGroupsMap.cpp 2015-09-24 17:49:24 +0000
3597@@ -0,0 +1,122 @@
3598+/****************************************************************************
3599+ **
3600+ ** Copyright (C) 2015 Canonical Ltd.
3601+ **
3602+ ** Contact: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3603+ **
3604+ ** This program/library is free software; you can redistribute it and/or
3605+ ** modify it under the terms of the GNU Lesser General Public License
3606+ ** version 2.1 as published by the Free Software Foundation.
3607+ **
3608+ ** This program/library is distributed in the hope that it will be useful,
3609+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3610+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3611+ ** Lesser General Public License for more details.
3612+ **
3613+ ** You should have received a copy of the GNU Lesser General Public
3614+ ** License along with this program/library; if not, write to the Free
3615+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3616+ ** 02110-1301 USA
3617+ **
3618+ ****************************************************************************/
3619+
3620+#include "GContactGroupsMap.h"
3621+#include <LogMacros.h>
3622+
3623+#include <QNetworkRequest>
3624+#include <QNetworkReply>
3625+#include <QNetworkAccessManager>
3626+#include <QTemporaryFile>
3627+
3628+GContactGroupMap::GContactGroupMap(const QString &authToken, QObject *parent)
3629+ : QObject(parent),
3630+ mNetworkAccessManager(new QNetworkAccessManager),
3631+ mAuthToken(authToken)
3632+{
3633+}
3634+
3635+GContactGroupMap::~GContactGroupMap()
3636+{
3637+}
3638+
3639+void GContactGroupMap::reload()
3640+{
3641+ connect(networkAccessManager,
3642+ SIGNAL(finished(QNetworkReply*)),
3643+ SLOT(onRequestFinished(QNetworkReply*)),
3644+ Qt::QueuedConnection);
3645+
3646+ QNetworkRequest request(mQueue.takeFirst());
3647+ request.setRawHeader("GData-Version", "3.0");
3648+ request.setRawHeader(QString(QLatin1String("Authorization")).toUtf8(),
3649+ QString(QLatin1String("Bearer ") + mAuthToken).toUtf8());
3650+ QNetworkReply *reply = networkAccessManager->get(request);
3651+}
3652+
3653+void GContactGroupMap::push(const QUrl &imgUrl)
3654+{
3655+ mQueue.push_back(imgUrl);
3656+}
3657+
3658+QMap<QUrl, QUrl> GContactImageDownloader::donwloaded()
3659+{
3660+ return mResults;
3661+}
3662+
3663+void GContactImageDownloader::exec()
3664+{
3665+ QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager;
3666+
3667+
3668+ QEventLoop eventLoop;
3669+ mEventLoop = &eventLoop;
3670+
3671+ while(!mQueue.isEmpty()) {
3672+ QNetworkRequest request(mQueue.takeFirst());
3673+ request.setRawHeader("GData-Version", "3.0");
3674+ request.setRawHeader(QString(QLatin1String("Authorization")).toUtf8(),
3675+ QString(QLatin1String("Bearer ") + mAuthToken).toUtf8());
3676+ QNetworkReply *reply = networkAccessManager->get(request);
3677+
3678+ // wait for the download to finish
3679+ eventLoop.exec();
3680+
3681+ // should we abort?
3682+ if (mAbort) {
3683+ break;
3684+ }
3685+ }
3686+ delete networkAccessManager;
3687+}
3688+
3689+void GContactImageDownloader::onRequestFinished(QNetworkReply *reply)
3690+{
3691+ if (reply->error() != QNetworkReply::NoError) {
3692+ LOG_WARNING("Fail to download avatar:" << reply->errorString());
3693+ emit donwloadError(reply->url(), reply->errorString());
3694+ } else {
3695+ QUrl localFile(saveImage(reply->url(), reply->readAll()));
3696+ mResults.insert(reply->url(), localFile);
3697+ emit downloadFinished(reply->url(), localFile);
3698+ }
3699+
3700+ if (mEventLoop) {
3701+ mEventLoop->quit();
3702+ }
3703+}
3704+
3705+QUrl GContactImageDownloader::saveImage(const QUrl &remoteFile, const QByteArray &imgData)
3706+{
3707+ Q_UNUSED(remoteFile);
3708+
3709+ QTemporaryFile tmp;
3710+ if (tmp.open()) {
3711+ tmp.write(imgData);
3712+ tmp.setAutoRemove(false);
3713+ tmp.close();
3714+ mTempFiles << tmp.fileName();
3715+ return QUrl::fromLocalFile(tmp.fileName());
3716+ }
3717+ return QUrl();
3718+}
3719+
3720
3721=== added file 'google/GContactGroupsMap.h'
3722--- google/GContactGroupsMap.h 1970-01-01 00:00:00 +0000
3723+++ google/GContactGroupsMap.h 2015-09-24 17:49:24 +0000
3724@@ -0,0 +1,60 @@
3725+/****************************************************************************
3726+ **
3727+ ** Copyright (C) 2015 Canonical Ltd.
3728+ **
3729+ ** Contact: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3730+ **
3731+ ** This program/library is free software; you can redistribute it and/or
3732+ ** modify it under the terms of the GNU Lesser General Public License
3733+ ** version 2.1 as published by the Free Software Foundation.
3734+ **
3735+ ** This program/library is distributed in the hope that it will be useful,
3736+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3737+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3738+ ** Lesser General Public License for more details.
3739+ **
3740+ ** You should have received a copy of the GNU Lesser General Public
3741+ ** License along with this program/library; if not, write to the Free
3742+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3743+ ** 02110-1301 USA
3744+ **
3745+ ****************************************************************************/
3746+
3747+#ifndef GOOGLECONTACTGRUOPMAP_H
3748+#define GOOGLECONTACTGRUOPMAP_H
3749+
3750+#include <QObject>
3751+#include <QString>
3752+#include <QUrl>
3753+#include <QQueue>
3754+#include <QThread>
3755+#include <QMap>
3756+#include <QMutex>
3757+#include <QEventLoop>
3758+#include <QtNetwork/QNetworkAccessManager>
3759+
3760+class GTransport;
3761+
3762+class GContactGroupMap: public QObject
3763+{
3764+ Q_OBJECT
3765+
3766+public:
3767+ explicit GContactGroupMap(const QString &authToken, QObject *parent = 0);
3768+ ~GContactGroupMap();
3769+
3770+ void reload();
3771+
3772+signals:
3773+ void updated();
3774+
3775+private slots:
3776+ void onRequestFinished(QNetworkReply *reply);
3777+
3778+private:
3779+ QScopedPointer<QNetworkAccessManager> mNetworkAccessManager;
3780+ QString mAuthToken;
3781+ QMap<QString, QString> mGroups;
3782+};
3783+
3784+#endif // GOOGLECONTACTGRUOPMAP_H
3785
3786=== added file 'google/GContactImageDownloader.cpp'
3787--- google/GContactImageDownloader.cpp 1970-01-01 00:00:00 +0000
3788+++ google/GContactImageDownloader.cpp 2015-09-24 17:49:24 +0000
3789@@ -0,0 +1,117 @@
3790+/****************************************************************************
3791+ **
3792+ ** Copyright (C) 2015 Canonical Ltd.
3793+ **
3794+ ** Contact: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3795+ **
3796+ ** This program/library is free software; you can redistribute it and/or
3797+ ** modify it under the terms of the GNU Lesser General Public License
3798+ ** version 2.1 as published by the Free Software Foundation.
3799+ **
3800+ ** This program/library is distributed in the hope that it will be useful,
3801+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3802+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3803+ ** Lesser General Public License for more details.
3804+ **
3805+ ** You should have received a copy of the GNU Lesser General Public
3806+ ** License along with this program/library; if not, write to the Free
3807+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3808+ ** 02110-1301 USA
3809+ **
3810+ ****************************************************************************/
3811+
3812+#include "GContactImageDownloader.h"
3813+#include "GTransport.h"
3814+
3815+#include <LogMacros.h>
3816+
3817+#include <QNetworkRequest>
3818+#include <QNetworkReply>
3819+#include <QNetworkAccessManager>
3820+#include <QTemporaryFile>
3821+
3822+GContactImageDownloader::GContactImageDownloader(const QString &authToken, QObject *parent)
3823+ : QObject(parent),
3824+ mEventLoop(0),
3825+ mAuthToken(authToken),
3826+ mAbort(false)
3827+{
3828+}
3829+
3830+GContactImageDownloader::~GContactImageDownloader()
3831+{
3832+ foreach(const QString &file, mTempFiles) {
3833+ QFile::remove(file);
3834+ }
3835+}
3836+
3837+void GContactImageDownloader::push(const QUrl &imgUrl)
3838+{
3839+ mQueue.push_back(imgUrl);
3840+}
3841+
3842+QMap<QUrl, QUrl> GContactImageDownloader::donwloaded()
3843+{
3844+ return mResults;
3845+}
3846+
3847+void GContactImageDownloader::exec()
3848+{
3849+ QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager;
3850+ connect(networkAccessManager,
3851+ SIGNAL(finished(QNetworkReply*)),
3852+ SLOT(onRequestFinished(QNetworkReply*)),
3853+ Qt::QueuedConnection);
3854+
3855+ QEventLoop eventLoop;
3856+ mEventLoop = &eventLoop;
3857+
3858+ while(!mQueue.isEmpty()) {
3859+ QNetworkRequest request(mQueue.takeFirst());
3860+ request.setRawHeader(QStringLiteral("GData-Version").toUtf8(), QStringLiteral("3.0").toUtf8());
3861+ request.setRawHeader(QStringLiteral("Authorization").toUtf8(),
3862+ QStringLiteral("Bearer %1").arg(mAuthToken).toUtf8());
3863+ networkAccessManager->get(request);
3864+
3865+ // wait for the download to finish
3866+ eventLoop.exec();
3867+
3868+ // should we abort?
3869+ if (mAbort) {
3870+ break;
3871+ }
3872+ }
3873+ delete networkAccessManager;
3874+}
3875+
3876+void GContactImageDownloader::onRequestFinished(QNetworkReply *reply)
3877+{
3878+ if (reply->error() != QNetworkReply::NoError) {
3879+ LOG_WARNING("Fail to download avatar:" << reply->errorString());
3880+ emit donwloadError(reply->url(), reply->errorString());
3881+ } else {
3882+ QUrl localFile(saveImage(reply->url(), reply->readAll()));
3883+ mResults.insert(reply->url(), localFile);
3884+ emit downloadFinished(reply->url(), localFile);
3885+ }
3886+
3887+ if (mEventLoop) {
3888+ mEventLoop->quit();
3889+ }
3890+}
3891+
3892+QUrl GContactImageDownloader::saveImage(const QUrl &remoteFile, const QByteArray &imgData)
3893+{
3894+ Q_UNUSED(remoteFile);
3895+
3896+ QTemporaryFile tmp;
3897+ if (tmp.open()) {
3898+ tmp.write(imgData);
3899+ tmp.setAutoRemove(false);
3900+ tmp.close();
3901+ mTempFiles << tmp.fileName();
3902+ return QUrl::fromLocalFile(tmp.fileName());
3903+ }
3904+ return QUrl();
3905+}
3906+
3907
3908=== added file 'google/GContactImageDownloader.h'
3909--- google/GContactImageDownloader.h 1970-01-01 00:00:00 +0000
3910+++ google/GContactImageDownloader.h 2015-09-24 17:49:24 +0000
3911@@ -0,0 +1,68 @@
3912+/****************************************************************************
3913+ **
3914+ ** Copyright (C) 2015 Canonical Ltd.
3915+ **
3916+ ** Contact: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3917+ **
3918+ ** This program/library is free software; you can redistribute it and/or
3919+ ** modify it under the terms of the GNU Lesser General Public License
3920+ ** version 2.1 as published by the Free Software Foundation.
3921+ **
3922+ ** This program/library is distributed in the hope that it will be useful,
3923+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3924+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3925+ ** Lesser General Public License for more details.
3926+ **
3927+ ** You should have received a copy of the GNU Lesser General Public
3928+ ** License along with this program/library; if not, write to the Free
3929+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
3930+ ** 02110-1301 USA
3931+ **
3932+ ****************************************************************************/
3933+
3934+#ifndef GOOGLECONTACTIMAGEDOWNLOADER_H
3935+#define GOOGLECONTACTIMAGEDOWNLOADER_H
3936+
3937+#include <QObject>
3938+#include <QString>
3939+#include <QUrl>
3940+#include <QQueue>
3941+#include <QThread>
3942+#include <QMap>
3943+#include <QMutex>
3944+#include <QEventLoop>
3945+#include <QtNetwork/QNetworkAccessManager>
3946+
3947+class GTransport;
3948+
3949+class GContactImageDownloader: public QObject
3950+{
3951+ Q_OBJECT
3952+
3953+public:
3954+ explicit GContactImageDownloader(const QString &authToken, QObject *parent = 0);
3955+ ~GContactImageDownloader();
3956+
3957+ void push(const QUrl &imgUrl);
3958+ QMap<QUrl, QUrl> donwloaded();
3959+ void exec();
3960+
3961+signals:
3962+ void downloadFinished(const QUrl &imgUrl, const QUrl &localFile);
3963+ void donwloadError(const QUrl &imgUrl, const QString &error);
3964+
3965+private slots:
3966+ void onRequestFinished(QNetworkReply *reply);
3967+
3968+private:
3969+ QEventLoop *mEventLoop;
3970+ QQueue<QUrl> mQueue;
3971+ QString mAuthToken;
3972+ QMap<QUrl, QUrl> mResults;
3973+ bool mAbort;
3974+ QStringList mTempFiles;
3975+
3976+ QUrl saveImage(const QUrl &remoteFile, const QByteArray &imgData);
3977+};
3978+
3979+#endif // GOOGLECONTACTIMAGEDOWNLOADER_H
3980
3981=== added file 'google/GContactImageUploader.cpp'
3982--- google/GContactImageUploader.cpp 1970-01-01 00:00:00 +0000
3983+++ google/GContactImageUploader.cpp 2015-09-24 17:49:24 +0000
3984@@ -0,0 +1,204 @@
3985+/****************************************************************************
3986+ **
3987+ ** Copyright (C) 2015 Canonical Ltd.
3988+ **
3989+ ** Contact: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
3990+ **
3991+ ** This program/library is free software; you can redistribute it and/or
3992+ ** modify it under the terms of the GNU Lesser General Public License
3993+ ** version 2.1 as published by the Free Software Foundation.
3994+ **
3995+ ** This program/library is distributed in the hope that it will be useful,
3996+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
3997+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3998+ ** Lesser General Public License for more details.
3999+ **
4000+ ** You should have received a copy of the GNU Lesser General Public
4001+ ** License along with this program/library; if not, write to the Free
4002+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
4003+ ** 02110-1301 USA
4004+ **
4005+ ****************************************************************************/
4006+
4007+#include "GContactImageUploader.h"
4008+#include "GContactStream.h"
4009+#include "GContactAtom.h"
4010+#include "UContactsCustomDetail.h"
4011+
4012+#include <LogMacros.h>
4013+
4014+#include <QNetworkRequest>
4015+#include <QNetworkReply>
4016+#include <QNetworkAccessManager>
4017+#include <QScopedPointer>
4018+#include <QDomDocument>
4019+
4020+#define GOOGLE_URL "https://www.google.com/m8/feeds/contacts/default/full"
4021+#define GOOGLE_PHOTO_URL "https://www.google.com/m8/feeds/photos/media/%1/%2"
4022+
4023+GContactImageUploader::GContactImageUploader(const QString &authToken,
4024+ const QString &accountId,
4025+ QObject *parent)
4026+ : QObject(parent),
4027+ mEventLoop(0),
4028+ mAuthToken(authToken),
4029+ mAccountId(accountId),
4030+ mAbort(false)
4031+{
4032+}
4033+
4034+void GContactImageUploader::push(const QString &remoteId, const QUrl &imgUrl)
4035+{
4036+ mQueue.push_back(qMakePair(remoteId, imgUrl));
4037+}
4038+
4039+QMap<QString, GContactImageUploader::UploaderReply> GContactImageUploader::result()
4040+{
4041+ return mResults;
4042+}
4043+
4044+/*
4045+ * This is a sync function since the contact sync process happen in a different
4046+ * thread we do not need this function to be async
4047+ */
4048+void GContactImageUploader::exec()
4049+{
4050+ if (mQueue.isEmpty()) {
4051+ return;
4052+ }
4053+
4054+ QDateTime startTime = QDateTime::currentDateTime().toUTC();
4055+ QScopedPointer<QNetworkAccessManager> networkAccessManager(new QNetworkAccessManager);
4056+ connect(networkAccessManager.data(),
4057+ SIGNAL(finished(QNetworkReply*)),
4058+ SLOT(onRequestFinished(QNetworkReply*)),
4059+ Qt::QueuedConnection);
4060+
4061+ QEventLoop eventLoop;
4062+ mEventLoop = &eventLoop;
4063+ mUploadCompleted = false;
4064+
4065+ while(!mQueue.isEmpty()) {
4066+ QPair<QString, QUrl> data = mQueue.takeFirst();
4067+
4068+ QByteArray imgData;
4069+ QFile imgFile(data.second.toLocalFile());
4070+ if (!imgFile.open(QIODevice::ReadOnly)) {
4071+ LOG_WARNING("Fail to open file:" << data.second.toLocalFile());
4072+ continue;
4073+ }
4074+ imgData = imgFile.readAll();
4075+ imgFile.close();;
4076+ mCurrentRemoteId = data.first;
4077+
4078+ // upload photo
4079+ QString requestUrl = QString(GOOGLE_PHOTO_URL)
4080+ .arg(mAccountId)
4081+ .arg(mCurrentRemoteId);
4082+ QNetworkRequest request(requestUrl);
4083+ request.setRawHeader(QStringLiteral("GData-Version").toUtf8(), QStringLiteral("3.0").toUtf8());
4084+ request.setRawHeader(QStringLiteral("Authorization").toUtf8(),
4085+ QStringLiteral("Bearer %1").arg(mAuthToken).toUtf8());
4086+ request.setRawHeader(QStringLiteral("Content-Type").toUtf8(), QStringLiteral("image/*").toUtf8());
4087+ request.setRawHeader(QStringLiteral("If-Match").toUtf8(), QStringLiteral("*").toUtf8());
4088+ networkAccessManager->put(request, imgData);
4089+
4090+ // wait for the upload to finish
4091+ eventLoop.exec();
4092+
4093+ // should we abort?
4094+ if (mAbort) {
4095+ break;
4096+ }
4097+ }
4098+
4099+ mUploadCompleted = true;
4100+
4101+ if (mAbort) {
4102+ return;
4103+ }
4104+
4105+ // After upload the pictures the contact etag get updated we need to retrieve
4106+ // the new ones
4107+ QString requestUrl =
4108+ QString("%1?updated-min=%2&fields=entry(@gd:etag, id, link(@rel, @gd:etag))")
4109+ .arg(GOOGLE_URL)
4110+ .arg(startTime.toString(Qt::ISODate));
4111+ QNetworkRequest request(requestUrl);
4112+ request.setRawHeader(QStringLiteral("GData-Version").toUtf8(), QStringLiteral("3.0").toUtf8());
4113+ request.setRawHeader(QStringLiteral("Authorization").toUtf8(),
4114+ QStringLiteral("Bearer %1").arg(mAuthToken).toUtf8());
4115+ networkAccessManager->get(request);
4116+
4117+ // wait for the reply to finish
4118+ eventLoop.exec();
4119+}
4120+
4121+void GContactImageUploader::onRequestFinished(QNetworkReply *reply)
4122+{
4123+ if (mUploadCompleted) {
4124+ if (reply->error() != QNetworkReply::NoError) {
4125+ LOG_WARNING("Fail to retrieve new etags:" << reply->errorString());
4126+ } else {
4127+ QByteArray data = reply->readAll();
4128+ LOG_TRACE("After avatar upload query result:" << data);
4129+ QMap<QString, GContactImageUploader::UploaderReply> entries = parseEntryList(data);
4130+ foreach(const QString &remoteId, entries.keys()) {
4131+ if (mResults.contains(remoteId)) {
4132+ mResults[remoteId] = entries[remoteId];
4133+ emit uploadFinished(remoteId, entries[remoteId]);
4134+ }
4135+ }
4136+ }
4137+ } else {
4138+ mResults.insert(mCurrentRemoteId, UploaderReply());
4139+ if (reply->error() != QNetworkReply::NoError) {
4140+ LOG_WARNING("Fail to upload avatar:" << reply->errorString());
4141+ emit uploadError(mCurrentRemoteId, reply->errorString());
4142+ } else {
4143+ LOG_TRACE("Avatar upload result" << reply->readAll());
4144+ }
4145+ mCurrentRemoteId.clear();
4146+ }
4147+
4148+ if (mEventLoop) {
4149+ mEventLoop->quit();
4150+ }
4151+}
4152+
4153+QMap<QString, GContactImageUploader::UploaderReply> GContactImageUploader::parseEntryList(const QByteArray &data) const
4154+{
4155+ QMap<QString, UploaderReply> result;
4156+
4157+ QDomDocument doc;
4158+ QString errorMsg;
4159+ if (!doc.setContent(data, &errorMsg)) {
4160+ LOG_WARNING("Fail to parse etag list" << errorMsg);
4161+ return result;
4162+ }
4163+
4164+ QDomNodeList entries = doc.elementsByTagName("entry");
4165+ for (int i = 0; i < entries.size(); i++) {
4166+ UploaderReply reply;
4167+ QDomElement entry = entries.item(i).toElement();
4168+ QDomElement id = entry.firstChildElement("id");
4169+
4170+ reply.newEtag = entry.attribute("gd:etag");
4171+ if (!id.isNull()) {
4172+ reply.remoteId = id.text().split('/').last();
4173+ }
4174+
4175+ QDomNodeList links = entry.elementsByTagName("link");
4176+ for (int l = 0; l < links.size(); l++) {
4177+ QDomElement link = links.item(l).toElement();
4178+
4179+ if (link.attribute("rel") == "http://schemas.google.com/contacts/2008/rel#photo") {
4180+ reply.newAvatarEtag = link.attribute("gd:etag");
4181+ break;
4182+ }
4183+ }
4184+ result.insert(reply.remoteId, reply);
4185+ }
4186+
4187+ return result;
4188+}
4189
4190=== added file 'google/GContactImageUploader.h'
4191--- google/GContactImageUploader.h 1970-01-01 00:00:00 +0000
4192+++ google/GContactImageUploader.h 2015-09-24 17:49:24 +0000
4193@@ -0,0 +1,78 @@
4194+/****************************************************************************
4195+ **
4196+ ** Copyright (C) 2015 Canonical Ltd.
4197+ **
4198+ ** Contact: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
4199+ **
4200+ ** This program/library is free software; you can redistribute it and/or
4201+ ** modify it under the terms of the GNU Lesser General Public License
4202+ ** version 2.1 as published by the Free Software Foundation.
4203+ **
4204+ ** This program/library is distributed in the hope that it will be useful,
4205+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
4206+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4207+ ** Lesser General Public License for more details.
4208+ **
4209+ ** You should have received a copy of the GNU Lesser General Public
4210+ ** License along with this program/library; if not, write to the Free
4211+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
4212+ ** 02110-1301 USA
4213+ **
4214+ ****************************************************************************/
4215+
4216+#ifndef GOOGLECONTACTIMAGEUPLOADER_H
4217+#define GOOGLECONTACTIMAGEUPLOADER_H
4218+
4219+#include <QObject>
4220+#include <QString>
4221+#include <QUrl>
4222+#include <QQueue>
4223+#include <QMap>
4224+#include <QMutex>
4225+#include <QEventLoop>
4226+#include <QNetworkReply>
4227+
4228+
4229+
4230+class GContactImageUploader: public QObject
4231+{
4232+ Q_OBJECT
4233+
4234+public:
4235+ class UploaderReply
4236+ {
4237+ public:
4238+ QString remoteId;
4239+ QString newEtag;
4240+ QString newAvatarEtag;
4241+ };
4242+
4243+ explicit GContactImageUploader(const QString &authToken,
4244+ const QString &accountId,
4245+ QObject *parent = 0);
4246+
4247+ void push(const QString &remoteId, const QUrl &imgUrl);
4248+ QMap<QString, UploaderReply> result();
4249+ void exec();
4250+
4251+signals:
4252+ void uploadFinished(const QUrl &remoteId, const GContactImageUploader::UploaderReply &eTag);
4253+ void uploadError(const QUrl &remoteId, const QString &error);
4254+
4255+private slots:
4256+ void onRequestFinished(QNetworkReply *reply);
4257+
4258+private:
4259+ QEventLoop *mEventLoop;
4260+ QQueue<QPair<QString, QUrl> > mQueue;
4261+ QString mAuthToken;
4262+ QString mAccountId;
4263+ QString mCurrentRemoteId;
4264+ QMap<QString, UploaderReply> mResults;
4265+ bool mAbort;
4266+ bool mUploadCompleted;
4267+
4268+ QMap<QString, UploaderReply> parseEntryList(const QByteArray &data) const;
4269+};
4270+
4271+#endif // GOOGLECONTACTIMAGEUPLOADER_H
4272
4273=== added file 'google/GContactStream.cpp'
4274--- google/GContactStream.cpp 1970-01-01 00:00:00 +0000
4275+++ google/GContactStream.cpp 2015-09-24 17:49:24 +0000
4276@@ -0,0 +1,1446 @@
4277+/****************************************************************************
4278+ **
4279+ ** Copyright (C) 2013-2014 Jolla Ltd. and/or its subsidiary(-ies).
4280+ ** 2015 Canonical Ltd.
4281+ **
4282+ ** Contributors: Sateesh Kavuri <sateesh.kavuri@gmail.com>
4283+ ** Mani Chandrasekar <maninc@gmail.com>
4284+ ** Chris Adams <chris.adams@jolla.com>
4285+ ** Renato Araujo Oliveira Filho <renato.filho@canonical.com>
4286+ **
4287+ ** This program/library is free software; you can redistribute it and/or
4288+ ** modify it under the terms of the GNU Lesser General Public License
4289+ ** version 2.1 as published by the Free Software Foundation.
4290+ **
4291+ ** This program/library is distributed in the hope that it will be useful,
4292+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
4293+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4294+ ** Lesser General Public License for more details.
4295+ **
4296+ ** You should have received a copy of the GNU Lesser General Public
4297+ ** License along with this program/library; if not, write to the Free
4298+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
4299+ ** 02110-1301 USA
4300+ **
4301+ ****************************************************************************/
4302+
4303+#include "GConfig.h"
4304+#include "GContactStream.h"
4305+#include "GContactAtom.h"
4306+#include "UContactsCustomDetail.h"
4307+#include "buteosyncfw_p.h"
4308+
4309+#include <QDateTime>
4310+#include <QtContacts/QContactId>
4311+
4312+GoogleContactStream::GoogleContactStream(bool response, const QString &accountEmail, QObject* parent)
4313+ : QObject(parent)
4314+ , mXmlReader(0)
4315+ , mAtom(0)
4316+ , mXmlWriter(0)
4317+ , mAccountEmail(accountEmail)
4318+{
4319+ if (response == true) {
4320+ initResponseFunctionMap();
4321+ } else {
4322+ initFunctionMap();
4323+ }
4324+}
4325+
4326+GoogleContactStream::~GoogleContactStream()
4327+{
4328+}
4329+
4330+GoogleContactAtom *GoogleContactStream::parse(const QByteArray &xmlBuffer)
4331+{
4332+ mXmlReader = new QXmlStreamReader(xmlBuffer);
4333+ mAtom = new GoogleContactAtom;
4334+
4335+ Q_CHECK_PTR(mXmlReader);
4336+ Q_CHECK_PTR(mAtom);
4337+
4338+ while (!mXmlReader->atEnd() && !mXmlReader->hasError()) {
4339+ if (mXmlReader->readNextStartElement()) {
4340+ Handler handler = mAtomFunctionMap.value(mXmlReader->name().toString());
4341+ if (handler) {
4342+ (*this.*handler)();
4343+ }
4344+ }
4345+ }
4346+
4347+ delete mXmlReader;
4348+ return mAtom;
4349+}
4350+
4351+QByteArray GoogleContactStream::encode(const QMultiMap<GoogleContactStream::UpdateType, QPair<QContact, QStringList> > &updates)
4352+{
4353+ QByteArray xmlBuffer;
4354+ mXmlWriter = new QXmlStreamWriter(&xmlBuffer);
4355+ startBatchFeed();
4356+
4357+ QList<QPair<QContact, QStringList> > removedContacts = updates.values(GoogleContactStream::Remove);
4358+ for (int i = 0; i < removedContacts.size(); ++i) {
4359+ encodeContactUpdate(removedContacts[i].first, removedContacts[i].second, GoogleContactStream::Remove, true); // batchmode = true
4360+ }
4361+
4362+ QList<QPair<QContact, QStringList> > addedContacts = updates.values(GoogleContactStream::Add);
4363+ for (int i = 0; i < addedContacts.size(); ++i) {
4364+ encodeContactUpdate(addedContacts[i].first, addedContacts[i].second, GoogleContactStream::Add, true); // batchmode = true
4365+ }
4366+
4367+ QList<QPair<QContact, QStringList> > modifiedContacts = updates.values(GoogleContactStream::Modify);
4368+ for (int i = 0; i < modifiedContacts.size(); ++i) {
4369+ encodeContactUpdate(modifiedContacts[i].first, modifiedContacts[i].second, GoogleContactStream::Modify, true); // batchmode = true
4370+ }
4371+
4372+ endBatchFeed();
4373+ mXmlWriter->writeEndDocument();
4374+ delete mXmlWriter;
4375+ return xmlBuffer;
4376+}
4377+
4378+// ----------------------------------------
4379+
4380+void GoogleContactStream::initAtomFunctionMap()
4381+{
4382+ mAtomFunctionMap.insert("updated", &GoogleContactStream::handleAtomUpdated);
4383+ mAtomFunctionMap.insert("category", &GoogleContactStream::handleAtomCategory);
4384+ mAtomFunctionMap.insert("author", &GoogleContactStream::handleAtomAuthor);
4385+ mAtomFunctionMap.insert("id", &GoogleContactStream::handleAtomId);
4386+ mAtomFunctionMap.insert("totalResults", &GoogleContactStream::handleAtomOpenSearch);
4387+ mAtomFunctionMap.insert("startIndex", &GoogleContactStream::handleAtomOpenSearch);
4388+ mAtomFunctionMap.insert("itemsPerPage", &GoogleContactStream::handleAtomOpenSearch);
4389+ mAtomFunctionMap.insert("link", &GoogleContactStream::handleAtomLink);
4390+ mAtomFunctionMap.insert("entry", &GoogleContactStream::handleAtomEntry);
4391+ mAtomFunctionMap.insert("generator", &GoogleContactStream::handleAtomGenerator);
4392+ mAtomFunctionMap.insert("title", &GoogleContactStream::handleAtomTitle);
4393+}
4394+
4395+void GoogleContactStream::initResponseFunctionMap()
4396+{
4397+ initAtomFunctionMap();
4398+ // TODO: move the batch request response handling stuff here.
4399+}
4400+
4401+void GoogleContactStream::initFunctionMap()
4402+{
4403+ initAtomFunctionMap();
4404+ mContactFunctionMap.insert("updated", &GoogleContactStream::handleEntryUpdated);
4405+ //mContactFunctionMap.insert("app:edited", &GoogleContactStream::handleEntryUpdated);
4406+ mContactFunctionMap.insert("gContact:birthday", &GoogleContactStream::handleEntryBirthday);
4407+ mContactFunctionMap.insert("gContact:gender", &GoogleContactStream::handleEntryGender);
4408+ mContactFunctionMap.insert("gContact:hobby", &GoogleContactStream::handleEntryHobby);
4409+ mContactFunctionMap.insert("gContact:nickname", &GoogleContactStream::handleEntryNickname);
4410+ mContactFunctionMap.insert("gContact:occupation", &GoogleContactStream::handleEntryOccupation);
4411+ mContactFunctionMap.insert("gContact:website", &GoogleContactStream::handleEntryWebsite);
4412+ mContactFunctionMap.insert("gContact:groupMembershipInfo", &GoogleContactStream::handleEntryGroup);
4413+ mContactFunctionMap.insert("gContact:event", &GoogleContactStream::handleEntryEvent);
4414+ mContactFunctionMap.insert("gContact:jot", &GoogleContactStream::handleEntryJot);
4415+ mContactFunctionMap.insert("gContact:relation", &GoogleContactStream::handleRelation);
4416+ mContactFunctionMap.insert("gd:email", &GoogleContactStream::handleEntryEmail);
4417+ mContactFunctionMap.insert("gd:im", &GoogleContactStream::handleEntryIm);
4418+ mContactFunctionMap.insert("gd:name", &GoogleContactStream::handleEntryName);
4419+ mContactFunctionMap.insert("gd:organization", &GoogleContactStream::handleEntryOrganization);
4420+ mContactFunctionMap.insert("gd:phoneNumber", &GoogleContactStream::handleEntryPhoneNumber);
4421+ mContactFunctionMap.insert("gd:structuredPostalAddress", &GoogleContactStream::handleEntryStructuredPostalAddress);
4422+ mContactFunctionMap.insert("gd:extendedProperty", &GoogleContactStream::handleEntryExtendedProperty);
4423+
4424+}
4425+
4426+// ----------------------------------------
4427+
4428+void GoogleContactStream::handleAtomUpdated()
4429+{
4430+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "updated");
4431+ mAtom->setUpdated(mXmlReader->readElementText());
4432+}
4433+
4434+void GoogleContactStream::handleAtomCategory()
4435+{
4436+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "category");
4437+
4438+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4439+ QString scheme, term;
4440+ if (attributes.hasAttribute("scheme")) {
4441+ scheme = attributes.value("scheme").toString();
4442+ }
4443+ if (attributes.hasAttribute("term")) {
4444+ term = attributes.value("term").toString();
4445+ }
4446+
4447+ mAtom->setCategory(scheme, term);
4448+}
4449+
4450+void GoogleContactStream::handleAtomAuthor()
4451+{
4452+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "author");
4453+
4454+ while (!(mXmlReader->tokenType() == QXmlStreamReader::EndElement && mXmlReader->name() == "author")) {
4455+ if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
4456+ if (mXmlReader->name() == "name") {
4457+ mAtom->setAuthorName(mXmlReader->readElementText());
4458+ } else if (mXmlReader->name() == "email") {
4459+ mAtom->setAuthorEmail(mXmlReader->readElementText());
4460+ }
4461+ }
4462+ mXmlReader->readNextStartElement();
4463+ }
4464+}
4465+
4466+void GoogleContactStream::handleAtomOpenSearch()
4467+{
4468+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->prefix() == "openSearch");
4469+
4470+ if (mXmlReader->name() == "totalResults") {
4471+ mAtom->setTotalResults(mXmlReader->readElementText().toInt());
4472+ } else if (mXmlReader->name() == "startIndex") {
4473+ mAtom->setStartIndex(mXmlReader->readElementText().toInt());
4474+ } else if (mXmlReader->name() == "itemsPerPage") {
4475+ mAtom->setItemsPerPage(mXmlReader->readElementText().toInt());
4476+ }
4477+}
4478+
4479+void GoogleContactStream::handleAtomLink()
4480+{
4481+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "link");
4482+
4483+ if (mXmlReader->attributes().hasAttribute("rel") && (mXmlReader->attributes().value("rel") == "next")) {
4484+ mAtom->setNextEntriesUrl(mXmlReader->attributes().value("href").toString());
4485+ }
4486+}
4487+
4488+void GoogleContactStream::handleAtomId()
4489+{
4490+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "id");
4491+ mAtom->setId(mXmlReader->readElementText());
4492+}
4493+
4494+void GoogleContactStream::handleAtomGenerator()
4495+{
4496+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "generator");
4497+
4498+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4499+ QString name, version, uri;
4500+ if (attributes.hasAttribute("version")) {
4501+ version = attributes.value("version").toString();
4502+ }
4503+ if (attributes.hasAttribute("uri")) {
4504+ uri = attributes.value("uri").toString();
4505+ }
4506+ name = mXmlReader->readElementText();
4507+
4508+ mAtom->setGenerator(name, version, uri);
4509+}
4510+
4511+void GoogleContactStream::handleAtomTitle()
4512+{
4513+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "title");
4514+ mAtom->setTitle(mXmlReader->readElementText());
4515+}
4516+
4517+void GoogleContactStream::handleAtomEntry()
4518+{
4519+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "entry");
4520+
4521+ // the entry will either be a contact, a group, or a response to a batch update request.
4522+ // if it's a group, we need to store some information about it.
4523+ QString systemGroupId;
4524+ QString systemGroupAtomId;
4525+
4526+ // the entry will be a contact if this is a response to a "read" request
4527+ QContact entryContact;
4528+ QStringList unsupportedElements;
4529+ bool isInGroup = false;
4530+ bool isDeleted = false;
4531+
4532+ // or it will be a series of batch operation success/fail info
4533+ // if this xml is the response to a batch update/delete request.
4534+ bool isBatchOperationResponse = false;
4535+ GoogleContactAtom::BatchOperationResponse response;
4536+
4537+ while (!((mXmlReader->tokenType() == QXmlStreamReader::EndElement) && (mXmlReader->name() == "entry"))) {
4538+ if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
4539+ isInGroup |= (mXmlReader->qualifiedName().toString() == QStringLiteral("gContact:groupMembershipInfo"));
4540+ DetailHandler handler = mContactFunctionMap.value(mXmlReader->qualifiedName().toString());
4541+ if (handler) {
4542+ QContactDetail convertedDetail = (*this.*handler)();
4543+ if (convertedDetail != QContactDetail()) {
4544+ entryContact.saveDetail(&convertedDetail);
4545+ } else {
4546+ LOG_WARNING("Handle not found for " << mXmlReader->qualifiedName().toString());
4547+ }
4548+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("gd:deleted")) {
4549+ isDeleted = true;
4550+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("batch:id")) {
4551+ isBatchOperationResponse = true;
4552+ handleEntryBatchId(&response);
4553+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("batch:operation")) {
4554+ isBatchOperationResponse = true;
4555+ handleEntryBatchOperation(&response);
4556+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("batch:status")) {
4557+ isBatchOperationResponse = true;
4558+ handleEntryBatchStatus(&response);
4559+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("link")) {
4560+ // There are several possible links:
4561+ // Avatar Photo link
4562+ // Self query link
4563+ // Edit link
4564+ // Batch link etc.
4565+
4566+ // If it's an avatar, we grab it as a QContactAvatar detail
4567+ bool isAvatar = false;
4568+ QContactAvatar googleAvatar;
4569+ QString etag;
4570+ QString unsupportedElement = handleEntryLink(&googleAvatar, &isAvatar, &etag);
4571+ if (isAvatar) {
4572+ // check if we have already a google avatar
4573+ entryContact.saveDetail(&googleAvatar);
4574+ UContactsCustomDetail::setCustomField(entryContact,
4575+ UContactsCustomDetail::FieldContactAvatarETag,
4576+ etag);
4577+ }
4578+
4579+ // Whether it's an avatar or not, we also store the element text.
4580+ if (!unsupportedElement.isEmpty()) {
4581+ unsupportedElements.append(unsupportedElement);
4582+ }
4583+ } else if (mXmlReader->name().toString() == QStringLiteral("entry")) {
4584+ // read the etag out of the entry.
4585+ response.eTag = mXmlReader->attributes().value("gd:etag").toString();
4586+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("gContact:systemGroup")) {
4587+ systemGroupId = mXmlReader->attributes().value("id").toString();
4588+ } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("id")) {
4589+ // either a contact id or a group id.
4590+ QContactDetail guidDetail = handleEntryId(&systemGroupAtomId);
4591+ entryContact.saveDetail(&guidDetail);
4592+ } else {
4593+ // This is some XML element which we don't handle.
4594+ // We should store it, so that we can send it back when we upload changes.
4595+ QString unsupportedElement = handleEntryUnknownElement();
4596+ if (!unsupportedElement.isEmpty()) {
4597+ unsupportedElements.append(unsupportedElement);
4598+ }
4599+ }
4600+ }
4601+ mXmlReader->readNextStartElement();
4602+ }
4603+
4604+ if (!systemGroupId.isEmpty()) {
4605+ // this entry was a group
4606+ mAtom->addEntrySystemGroup(systemGroupId, systemGroupAtomId);
4607+ } else {
4608+ // this entry was a contact.
4609+ // the etag is the "version identifier". Save it into the QCOM detail.
4610+ if (!response.eTag.isEmpty()) {
4611+ QtContacts::QContactExtendedDetail etagDetail =
4612+ UContactsCustomDetail::getCustomField(entryContact, UContactsCustomDetail::FieldContactETag);
4613+ etagDetail.setData(response.eTag);
4614+ entryContact.saveDetail(&etagDetail);
4615+ }
4616+
4617+ if (isInGroup) {
4618+ // Only sync the contact if it is in a "real" group
4619+ // as otherwise we get hundreds of "Other Contacts"
4620+ // (random email addresses etc).
4621+ if (isDeleted) {
4622+ // if contact is deleted set the deletedAt value
4623+ QContactExtendedDetail deletedAt =
4624+ UContactsCustomDetail::getCustomField(entryContact, UContactsCustomDetail::FieldDeletedAt);
4625+ deletedAt.setData(QDateTime::currentDateTime());
4626+ entryContact.saveDetail(&deletedAt);
4627+
4628+ mAtom->addDeletedEntryContact(entryContact);
4629+ } else {
4630+ mAtom->addEntryContact(entryContact, unsupportedElements);
4631+ }
4632+ }
4633+ }
4634+
4635+ if (isBatchOperationResponse) {
4636+ if (!entryContact.detail<QContactGuid>().guid().isEmpty()) {
4637+ response.contactGuid = entryContact.detail<QContactGuid>().guid();
4638+ }
4639+ mAtom->addBatchOperationResponse(response.operationId, response);
4640+ }
4641+}
4642+
4643+QString GoogleContactStream::handleEntryLink(QContactAvatar *avatar,
4644+ bool *isAvatar,
4645+ QString *etag)
4646+{
4647+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "link");
4648+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4649+
4650+ // if a contact does not have a photo, then the photo link element has no gd:etag attribute.
4651+ *isAvatar = ((attributes.value("rel") == "http://schemas.google.com/contacts/2008/rel#photo") &&
4652+ attributes.hasAttribute("gd:etag"));
4653+
4654+ if (*isAvatar) {
4655+ // this is an avatar photo for the contact entry
4656+ avatar->setImageUrl(attributes.value("href").toString());
4657+ *etag = attributes.value("gd:etag").toString();
4658+ }
4659+
4660+ return handleEntryUnknownElement();
4661+}
4662+
4663+QContactDetail GoogleContactStream::handleEntryExtendedProperty()
4664+{
4665+ Q_ASSERT(mXmlReader->isStartElement() &&
4666+ mXmlReader->name() == "gd:extendedProperty");
4667+
4668+ QContactExtendedDetail xd;
4669+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4670+ QString propName = attributes.value("name").toString();
4671+
4672+ // FIXME:
4673+ // We use extendedProperty to store favorite property until we implment support
4674+ // for google groups sync
4675+ if (propName == "X-FAVORITE") {
4676+ QContactFavorite fav;
4677+ fav.setFavorite(attributes.value("value").toString() == "true");
4678+ return fav;
4679+ } else if (propName == "SOUND") {
4680+ QContactRingtone ring;
4681+ ring.setAudioRingtoneUrl(QUrl(attributes.value("value").toString()));
4682+ return ring;
4683+ }
4684+
4685+ // handle as generic type
4686+ xd.setName(attributes.value("name").toString());
4687+ xd.setData(attributes.value("value").toString());
4688+ return xd;
4689+}
4690+
4691+QContactDetail GoogleContactStream::handleEntryEvent()
4692+{
4693+ Q_ASSERT(mXmlReader->isStartElement() &&
4694+ mXmlReader->name() == "gContact:event");
4695+
4696+// <gContact:event rel="anniversary" label="memorial">
4697+// <gd:when startTime="2005-06-06" endTime="2005-06-08" valueString="This month"/>
4698+// </gContact:event>
4699+ static QMap<QString, QContactAnniversary::SubType> anniversaryTypes;
4700+ if (anniversaryTypes.isEmpty()) {
4701+ anniversaryTypes.insert(QString::fromLatin1("engagement"),
4702+ QContactAnniversary::SubTypeEngagement);
4703+ anniversaryTypes.insert(QString::fromLatin1("employment"),
4704+ QContactAnniversary::SubTypeEmployment);
4705+ anniversaryTypes.insert(QString::fromLatin1("memorial"),
4706+ QContactAnniversary::SubTypeMemorial);
4707+ anniversaryTypes.insert(QString::fromLatin1("house"),
4708+ QContactAnniversary::SubTypeHouse);
4709+ anniversaryTypes.insert(QString::fromLatin1("wedding"),
4710+ QContactAnniversary::SubTypeWedding);
4711+ }
4712+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4713+ if (attributes.value("rel") == "anniversary") {
4714+ QContactAnniversary anniversary;
4715+ anniversary.setSubType(anniversaryTypes.value(attributes.value("label").toString(),
4716+ QContactAnniversary::SubTypeWedding));
4717+ mXmlReader->readNextStartElement();
4718+ if (mXmlReader->qualifiedName() == "gd:when") {
4719+ attributes = mXmlReader->attributes();
4720+ anniversary.setOriginalDateTime(QDateTime::fromString(attributes.value("startTime").toString(),
4721+ Qt::ISODate));
4722+ anniversary.setEvent(attributes.value("valueString").toString());
4723+ // FIXME: missing endTime
4724+ // QContactAnniversary API does not support endTime
4725+ }
4726+ return anniversary;
4727+ }
4728+
4729+ return QContactDetail();
4730+}
4731+
4732+QContactDetail GoogleContactStream::handleRelation()
4733+{
4734+ Q_ASSERT(mXmlReader->isStartElement() &&
4735+ mXmlReader->name() == "gContact:relation");
4736+
4737+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4738+ QString rel = attributes.hasAttribute("rel")
4739+ ? attributes.value("rel").toString()
4740+ : QString();
4741+
4742+ QContactFamily family;
4743+ if (rel == "spouse") {
4744+ family.setSpouse(mXmlReader->readElementText());
4745+ } else if (rel == "child") {
4746+ family.setChildren(QStringList() << mXmlReader->readElementText());
4747+ } else {
4748+ LOG_WARNING("Family relation type not supported" << rel);
4749+ return QContactDetail();
4750+ }
4751+
4752+ return family;
4753+}
4754+
4755+QString GoogleContactStream::handleEntryUnknownElement()
4756+{
4757+ Q_ASSERT(mXmlReader->isStartElement());
4758+
4759+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4760+ QString attributesString;
4761+ for (int i = 0; i < attributes.size(); ++i) {
4762+ QString extra = QStringLiteral(" %1=\"%2\"")
4763+ .arg(attributes[i].qualifiedName().toString())
4764+ .arg(attributes[i].value().toString().toHtmlEscaped());
4765+ attributesString.append(extra);
4766+ }
4767+
4768+ QString unknownElement = QStringLiteral("<%1%2>%3</%1>")
4769+ .arg(mXmlReader->qualifiedName().toString())
4770+ .arg(attributesString)
4771+ .arg(mXmlReader->text().toString());
4772+
4773+ return unknownElement;
4774+}
4775+
4776+QList<int> GoogleContactStream::handleContext(const QString &rel) const
4777+{
4778+ QList<int> contexts;
4779+ QString name = rel.split("#").last();
4780+ if (name == QStringLiteral("home")) {
4781+ contexts << QContactDetail::ContextHome;
4782+ } else if (name == QStringLiteral("work")) {
4783+ contexts << QContactDetail::ContextWork;
4784+ } else if (!name.isEmpty()) {
4785+ contexts << QContactDetail::ContextOther;
4786+ }
4787+ return contexts;
4788+}
4789+
4790+void GoogleContactStream::handleEntryBatchStatus(GoogleContactAtom::BatchOperationResponse *response)
4791+{
4792+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "status");
4793+
4794+ response->code = mXmlReader->attributes().value("code").toString();
4795+ response->reason = mXmlReader->attributes().value("reason").toString();
4796+ response->reasonDescription = mXmlReader->readElementText();
4797+ response->isError = true;
4798+ if (response->code == QStringLiteral("200") // No error.
4799+ || response->code == QStringLiteral("201") // Created without error.
4800+ || response->code == QStringLiteral("304")) { // Not modified (no change since time specified)
4801+ // according to Google Data API these response codes signify success cases.
4802+ response->isError = false;
4803+ }
4804+}
4805+
4806+void GoogleContactStream::handleEntryBatchOperation(GoogleContactAtom::BatchOperationResponse *response)
4807+{
4808+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "operation");
4809+ response->type = mXmlReader->attributes().value("type").toString();
4810+}
4811+
4812+void GoogleContactStream::handleEntryBatchId(GoogleContactAtom::BatchOperationResponse *response)
4813+{
4814+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "id");
4815+ response->operationId = mXmlReader->readElementText();
4816+}
4817+
4818+QContactDetail GoogleContactStream::handleEntryId(QString *rawId)
4819+{
4820+ *rawId = mXmlReader->readElementText();
4821+ QString idUrl = *rawId;
4822+ QContactGuid guid;
4823+ guid.setGuid(idUrl.split('/').last());
4824+ return guid;
4825+}
4826+
4827+QContactDetail GoogleContactStream::handleEntryBirthday()
4828+{
4829+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:birthday");
4830+
4831+ QContactBirthday birthday;
4832+ birthday.setDate(QDate::fromString(mXmlReader->attributes().value("when").toString(), Qt::ISODate));
4833+
4834+ if (birthday.dateTime().isValid()) {
4835+ return birthday;
4836+ } else {
4837+ LOG_WARNING("Birthday date not supported:" << mXmlReader->attributes().value("when").toString());
4838+ return QContactDetail();
4839+ }
4840+}
4841+
4842+QContactDetail GoogleContactStream::handleEntryGender()
4843+{
4844+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:gender");
4845+
4846+ QString genderStr = mXmlReader->attributes().value("value").toString().toLower();
4847+ QContactGender gender;
4848+ if (genderStr.startsWith('m')) {
4849+ gender.setGender(QContactGender::GenderMale);
4850+ } else if (genderStr.startsWith('f')) {
4851+ gender.setGender(QContactGender::GenderFemale);
4852+ } else {
4853+ gender.setGender(QContactGender::GenderUnspecified);
4854+ }
4855+
4856+ return gender;
4857+}
4858+
4859+QContactDetail GoogleContactStream::handleEntryHobby()
4860+{
4861+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:hobby");
4862+
4863+ QContactHobby hobby;
4864+ hobby.setHobby(mXmlReader->readElementText());
4865+ return hobby;
4866+}
4867+
4868+QContactDetail GoogleContactStream::handleEntryNickname()
4869+{
4870+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:nickname");
4871+
4872+ QContactNickname nickname;
4873+ nickname.setNickname(mXmlReader->readElementText());
4874+ return nickname;
4875+}
4876+
4877+QContactDetail GoogleContactStream::handleEntryOccupation()
4878+{
4879+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:occupation");
4880+
4881+ QContactOrganization org;
4882+ org.setRole(mXmlReader->readElementText());
4883+ return org;
4884+}
4885+
4886+QContactDetail GoogleContactStream::handleEntryWebsite()
4887+{
4888+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:website");
4889+
4890+ QContactUrl url;
4891+ QXmlStreamAttributes attributes = mXmlReader->attributes();
4892+ QString rel = attributes.hasAttribute("rel")
4893+ ? attributes.value("rel").toString()
4894+ : QString();
4895+
4896+ if (rel == "home-page") {
4897+ url.setSubType(QContactUrl::SubTypeHomePage);
4898+ } else if (rel == "blog") {
4899+ url.setSubType(QContactUrl::SubTypeBlog);
4900+ } else {
4901+ url.setSubType(QContactUrl::SubTypeFavourite);
4902+ }
4903+ url.setContexts(handleContext(rel));
4904+ url.setUrl(attributes.value("href").toString());
4905+ return url;
4906+}
4907+
4908+QContactDetail GoogleContactStream::handleEntryJot()
4909+{
4910+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:jot");
4911+
4912+ QString rel = mXmlReader->attributes().hasAttribute("rel")
4913+ ? mXmlReader->attributes().value("rel").toString()
4914+ : QString();
4915+
4916+ QContactNote note;
4917+ note.setContexts(handleContext(rel));
4918+ note.setNote(mXmlReader->readElementText());
4919+ return note;
4920+}
4921+
4922+QContactDetail GoogleContactStream::handleEntryEmail()
4923+{
4924+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:email");
4925+
4926+ QContactEmailAddress email;
4927+ email.setEmailAddress(mXmlReader->attributes().value("address").toString());
4928+
4929+ QString rel = mXmlReader->attributes().hasAttribute("rel")
4930+ ? mXmlReader->attributes().value("rel").toString()
4931+ : QString();
4932+ email.setContexts(handleContext(rel));
4933+ return email;
4934+}
4935+
4936+QContactDetail GoogleContactStream::handleEntryIm()
4937+{
4938+ Q_ASSERT(mXmlReader->isStartElement () && mXmlReader->qualifiedName () == "gd:im");
4939+
4940+ static QMap<QString, QContactOnlineAccount::Protocol> protocolMap;
4941+ if (protocolMap.isEmpty()) {
4942+ protocolMap.insert("AIM", QContactOnlineAccount::ProtocolAim);
4943+ protocolMap.insert("MSN", QContactOnlineAccount::ProtocolMsn);
4944+ protocolMap.insert("YAHOO", QContactOnlineAccount::ProtocolYahoo);
4945+ protocolMap.insert("SKYPE", QContactOnlineAccount::ProtocolSkype);
4946+ protocolMap.insert("ICQ", QContactOnlineAccount::ProtocolIcq);
4947+ protocolMap.insert("JABBER", QContactOnlineAccount::ProtocolJabber);
4948+ protocolMap.insert("QQ", QContactOnlineAccount::ProtocolQq);
4949+ protocolMap.insert("IRC", QContactOnlineAccount::ProtocolIrc);
4950+ }
4951+ QString rel, protocol;
4952+ if (mXmlReader->attributes().hasAttribute("rel")) {
4953+ rel = mXmlReader->attributes().value("rel").toString();
4954+ }
4955+
4956+ if (mXmlReader->attributes().hasAttribute ("protocol")) {
4957+ QString protocolUrl = mXmlReader->attributes().value("protocol").toString();
4958+ protocol = protocolUrl.split("#").last();
4959+ }
4960+
4961+ QContactOnlineAccount imAccount;
4962+ imAccount.setAccountUri(mXmlReader->attributes().value("address").toString());
4963+ imAccount.setProtocol(protocolMap.value(protocol, QContactOnlineAccount::ProtocolUnknown));
4964+ if (!protocolMap.contains(protocol)) {
4965+ imAccount.setServiceProvider(protocol);
4966+ }
4967+ imAccount.setContexts(handleContext(rel));
4968+ return imAccount;
4969+}
4970+
4971+QContactDetail GoogleContactStream::handleEntryName()
4972+{
4973+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:name");
4974+
4975+ QContactName name;
4976+ while (!(mXmlReader->tokenType() == QXmlStreamReader::EndElement && mXmlReader->qualifiedName() == "gd:name")) {
4977+ if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
4978+ if (mXmlReader->qualifiedName() == "gd:givenName") {
4979+ name.setFirstName(mXmlReader->readElementText());
4980+ } else if (mXmlReader->qualifiedName() == "gd:additionalName") {
4981+ name.setMiddleName(mXmlReader->readElementText());
4982+ } else if (mXmlReader->qualifiedName() == "gd:familyName") {
4983+ name.setLastName(mXmlReader->readElementText());
4984+ } else if (mXmlReader->qualifiedName() == "gd:namePrefix") {
4985+ name.setPrefix(mXmlReader->readElementText());
4986+ } else if (mXmlReader->qualifiedName() == "gd:nameSuffix") {
4987+ name.setSuffix(mXmlReader->readElementText());
4988+ }
4989+ }
4990+ mXmlReader->readNextStartElement();
4991+ }
4992+
4993+ return name;
4994+}
4995+
4996+QContactDetail GoogleContactStream::handleEntryOrganization()
4997+{
4998+ Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:organization");
4999+
5000+ QContactOrganization org;
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches