blob: c099aed2748bfb952bd83e36906cff0e3dbf802d [file] [log] [blame]
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001/*
Stepan Salenikovichbe87d2c2016-01-25 14:14:34 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Stepan Salenikovich6f687072015-03-26 10:43:37 -04003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovich6f687072015-03-26 10:43:37 -040018 */
19
20#include "edscontactbackend.h"
21
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -040022#include <glib/gi18n.h>
Stepan Salenikovich6f687072015-03-26 10:43:37 -040023#include <person.h>
24#include <personmodel.h>
25#include <contactmethod.h>
26#include <collectioneditor.h>
27#include <memory>
28
29static void
30client_cb(G_GNUC_UNUSED ESource *source, GAsyncResult *result, G_GNUC_UNUSED gpointer user_data)
31{
32 GError *error = NULL;
33 EClient *client = e_book_client_connect_finish(result, &error);
34 if (!client) {
35 g_warning("%s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -040036 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -040037 } else {
38 /* got a client for this addressbook, add as backend */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -040039 PersonModel::instance().addCollection<EdsContactBackend, EClient *>(
Stepan Salenikovich6f687072015-03-26 10:43:37 -040040 client, LoadOptions::FORCE_ENABLED);
41 }
42}
43
44static void
45registry_cb(G_GNUC_UNUSED GObject *source, GAsyncResult *result, GCancellable *cancellable)
46{
47 GError *error = NULL;
48 ESourceRegistry *registry = e_source_registry_new_finish(result, &error);
49 if(!registry) {
50 g_critical("Unable to create EDS registry: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -040051 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -040052 return;
53 } else {
54 GList *list = e_source_registry_list_enabled(registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
55
56 for (GList *l = list ; l; l = l->next) {
57 ESource *source = E_SOURCE(l->data);
58 /* try to connect to each source ansynch */
Stepan Salenikovichb53e9c02015-04-14 17:41:10 -040059#if EDS_CHECK_VERSION(3,16,0)
Edric Milaret66c50a32015-05-06 10:38:42 -040060 e_book_client_connect(source,
61 EdsContactBackend::WAIT_FOR_CONNECTED_SECONDS,
62 cancellable,
63 (GAsyncReadyCallback)client_cb,
64 NULL);
65#else
66 e_book_client_connect(source, cancellable, (GAsyncReadyCallback)client_cb, NULL);
Stepan Salenikovichb53e9c02015-04-14 17:41:10 -040067#endif
Stepan Salenikovich6f687072015-03-26 10:43:37 -040068 }
69
70 g_list_free_full(list, g_object_unref);
71 }
72}
73
74void load_eds_sources(GCancellable *cancellable)
75{
76 /* load the registery asynchronously, and then each source asynch as well
77 * pass the cancellable as a param so that the loading of each source can
78 * also be cancelled
79 */
80 e_source_registry_new(cancellable, (GAsyncReadyCallback)registry_cb, cancellable);
81}
82
83class EdsContactEditor : public CollectionEditor<Person>
84{
85public:
86 EdsContactEditor(CollectionMediator<Person>* m, EdsContactBackend* parent);
87 ~EdsContactEditor();
88 virtual bool save ( const Person* item ) override;
89 virtual bool remove ( const Person* item ) override;
90 virtual bool edit ( Person* item ) override;
Stepan Salenikovich58dd9e12015-08-05 10:39:36 -040091 virtual bool addNew ( Person* item ) override;
Stepan Salenikovich6f687072015-03-26 10:43:37 -040092 virtual bool addExisting( const Person* item ) override;
93
94private:
95 virtual QVector<Person*> items() const override;
96
97 QVector<Person*> items_;
98 EdsContactBackend* collection_;
99};
100
101EdsContactEditor::EdsContactEditor(CollectionMediator<Person>* m, EdsContactBackend* parent) :
102CollectionEditor<Person>(m),collection_(parent)
103{
104}
105
106EdsContactEditor::~EdsContactEditor()
107{
108}
109
110bool EdsContactEditor::save(const Person* item)
111{
Stepan Salenikovich0cf247d2015-07-24 17:36:32 -0400112 return collection_->savePerson(item);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400113}
114
115bool EdsContactEditor::remove(const Person* item)
116{
Stepan Salenikovichbb358592015-07-10 14:45:50 -0400117 mediator()->removeItem(item);
118 return true;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400119}
120
121bool EdsContactEditor::edit( Person* item)
122{
123 Q_UNUSED(item)
124 return false;
125}
126
Stepan Salenikovich58dd9e12015-08-05 10:39:36 -0400127bool EdsContactEditor::addNew(Person* item)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400128{
Stepan Salenikovich58dd9e12015-08-05 10:39:36 -0400129 bool ret = collection_->addNewPerson(item);
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400130 if (ret) {
Stepan Salenikovich58dd9e12015-08-05 10:39:36 -0400131 items_ << item;
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400132 mediator()->addItem(item);
133 }
134 return ret;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400135}
136
137bool EdsContactEditor::addExisting(const Person* item)
138{
139 items_ << const_cast<Person*>(item);
140 mediator()->addItem(item);
141 return true;
142}
143
144QVector<Person*> EdsContactEditor::items() const
145{
146 return items_;
147}
148
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400149static void free_client_view(EBookClientView *client_view) {
150 g_return_if_fail(client_view);
151 GError *error = NULL;
152 e_book_client_view_stop(client_view, &error);
153 if (error) {
154 g_warning("error stopping EBookClientView: %s", error->message);
155 g_clear_error(&error);
156 }
157 g_object_unref(client_view);
158}
159
160static void
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400161free_object_list(GSList *list)
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400162{
163 g_slist_free_full(list, g_object_unref);
164};
165
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400166static void
167free_string_list(GSList *list)
168{
169 g_slist_free_full(list, g_free);
170};
171
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400172EdsContactBackend::EdsContactBackend(CollectionMediator<Person>* mediator, EClient *client, CollectionInterface* parent)
173 : CollectionInterface(new EdsContactEditor(mediator,this), parent)
174 , mediator_(mediator)
175 , client_(client, g_object_unref)
176 , cancellable_(g_cancellable_new(), g_object_unref)
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400177 , client_view_(nullptr, &free_client_view)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400178{
Stepan Salenikovichf4149272015-07-16 16:12:19 -0400179 // cache the name
180 if (client_) {
181 auto source = e_client_get_source(client_.get());
182 auto extension = (ESourceAddressBook *)e_source_get_extension(source, E_SOURCE_EXTENSION_ADDRESS_BOOK);
183 auto backend = e_source_backend_get_backend_name(E_SOURCE_BACKEND(extension));
184 auto addressbook = e_source_get_display_name(source);
185
186 gchar *name = g_strdup_printf("%s (%s)", addressbook, backend);
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400187 name_ = name;
Stepan Salenikovichf4149272015-07-16 16:12:19 -0400188 g_free(name);
189 } else
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400190 name_ = _("Unknown EDS addressbook");
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400191}
192
193EdsContactBackend::~EdsContactBackend()
194{
195 /* cancel any cancellable operations */
196 g_cancellable_cancel(cancellable_.get());
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400197
198 /* cancel contact loading timeout source, if its not finished */
199 if (add_contacts_source_id != 0)
200 g_source_remove(add_contacts_source_id);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400201}
202
203QString EdsContactBackend::name() const
204{
Stepan Salenikovichf4149272015-07-16 16:12:19 -0400205 return name_;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400206}
207
208QString EdsContactBackend::category() const
209{
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400210 return C_("Backend type", "Contacts");
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400211}
212
213bool EdsContactBackend::isEnabled() const
214{
215 return true;
216}
217
218static void
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400219contacts_added(G_GNUC_UNUSED EBookClientView *client_view, const GSList *objects, EdsContactBackend *self)
220{
221 std::unique_ptr<GSList,void(*)(GSList *)> contacts(
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400222 g_slist_copy_deep((GSList *)objects, (GCopyFunc)g_object_ref, NULL ), &free_object_list);
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400223 self->addContacts(std::move(contacts));
224}
225
226static void
Stepan Salenikovicheccd3102015-06-26 15:49:34 -0400227contacts_modified(EBookClientView *client_view, const GSList *objects, EdsContactBackend *self)
228{
229 /* The parseContact function will check if the contacts we're "adding" have
230 * the same URI as any existing ones and if so will update those instead */
231 contacts_added(client_view, objects, self);
232}
233
234static void
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400235contacts_removed(G_GNUC_UNUSED EBookClientView *client_view, const GSList *uids, EdsContactBackend *self)
236{
237 std::unique_ptr<GSList,void(*)(GSList *)> contact_uids(
238 g_slist_copy_deep((GSList *)uids, (GCopyFunc)g_strdup, NULL ), &free_string_list);
239 self->removeContacts(std::move(contact_uids));
240}
241
242static void
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400243client_view_cb(EBookClient *client, GAsyncResult *result, EdsContactBackend *self)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400244{
245 g_return_if_fail(E_IS_BOOK_CLIENT(client));
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400246 EBookClientView *client_view = NULL;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400247 GError *error = NULL;
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400248 if(!e_book_client_get_view_finish(client, result, &client_view, &error)) {
249 g_critical("Unable to get client view: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400250 g_clear_error(&error);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400251 return;
252 } else {
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400253 /* we want the EBookClientView to have the same life cycle as the backend */
254 std::unique_ptr<EBookClientView, void(*)(EBookClientView *)> client_view_ptr(
255 client_view, &free_client_view);
256 self->addClientView(std::move(client_view_ptr));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400257 }
258}
259
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400260void EdsContactBackend::parseContact(EContact *contact)
261{
262 /* Check if the photo is in-line or a URI, in the case that it is a URI,
263 * try to make it inline so that the lrc vcard parser is able to get the
264 * photo. Note that this will only work on local URIs
265 */
266 EContactPhoto *photo = (EContactPhoto *)e_contact_get(contact, E_CONTACT_PHOTO);
267 if (photo) {
268 if (photo->type == E_CONTACT_PHOTO_TYPE_URI) {
269 GError *error = NULL;
270 if (!e_contact_inline_local_photos(contact, &error)) {
271 g_warning("could not inline photo from vcard URI: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400272 g_clear_error(&error);
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400273 }
274 }
275 }
276 e_contact_photo_free(photo);
277
278 EVCard *vcard = E_VCARD(contact);
279 gchar *vcard_str = e_vcard_to_string(vcard, EVC_FORMAT_VCARD_30);
Stepan Salenikovicheccd3102015-06-26 15:49:34 -0400280
281 /* check if this person already exists */
Stepan Salenikoviche86ffba2015-07-13 11:32:40 -0400282 Person *existing = nullptr;
283
284 gchar *uid = (gchar *)e_contact_get(contact, E_CONTACT_UID);
285 if (uid) {
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400286 // g_warning("got uid: %s", uid);
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400287 existing = PersonModel::instance().getPersonByUid(uid);
Stepan Salenikoviche86ffba2015-07-13 11:32:40 -0400288 g_free(uid);
289 }
290
Stepan Salenikovicheccd3102015-06-26 15:49:34 -0400291 if (existing) {
292 /* update existing person */
Stepan Salenikoviche86ffba2015-07-13 11:32:40 -0400293 existing->updateFromVCard(vcard_str);
Stepan Salenikovicheccd3102015-06-26 15:49:34 -0400294 } else {
Stepan Salenikoviche86ffba2015-07-13 11:32:40 -0400295 Person *p = new Person(vcard_str, Person::Encoding::vCard, this);
Stepan Salenikovicheccd3102015-06-26 15:49:34 -0400296 editor<Person>()->addExisting(p);
297 }
Stepan Salenikoviche86ffba2015-07-13 11:32:40 -0400298
299 g_free(vcard_str);
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400300}
301
302typedef struct AddContactsData_
303{
304 EdsContactBackend* backend;
305 GSList *next;
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400306 std::unique_ptr<GSList, void(*)(GSList *)> contacts;
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400307} AddContactsData;
308
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400309void
310free_add_contacts_data(AddContactsData *data)
311{
312 g_return_if_fail(data && data->contacts);
313 data->contacts.reset();
314 g_free(data);
315}
316
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400317static gboolean
318add_contacts(AddContactsData *data)
319{
320 for (int i = 0; i < data->backend->CONTACT_ADD_LIMIT && data->next; i++) {
321 data->backend->parseContact(E_CONTACT(data->next->data));
322 data->next = data->next->next;
323 }
324
325 if (!data->next) {
326 data->backend->lastContactAdded();
327 return G_SOURCE_REMOVE;
328 }
329
330 return G_SOURCE_CONTINUE;
331}
332
333void EdsContactBackend::lastContactAdded()
334{
335 /* Sets the source id to 0 to make sure we don't try to remove this source
336 * after it has already finished */
337 add_contacts_source_id = 0;
338}
339
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400340void EdsContactBackend::addClientView(std::unique_ptr<EBookClientView, void(*)(EBookClientView *)> client_view)
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400341{
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400342 client_view_ = std::move(client_view);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400343
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400344 /* connect signals for adding, removing, and modifying contacts */
345 g_signal_connect(client_view_.get(), "objects-added", G_CALLBACK(contacts_added), this);
Stepan Salenikovicheccd3102015-06-26 15:49:34 -0400346 g_signal_connect(client_view_.get(), "objects-modified", G_CALLBACK(contacts_modified), this);
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400347 g_signal_connect(client_view_.get(), "objects-removed", G_CALLBACK(contacts_removed), this);
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400348
349 /* start processing the signals */
350 GError *error = NULL;
351 e_book_client_view_start(client_view_.get(), &error);
352 if (error) {
353 g_critical("Unable to get start client view: %s", error->message);
354 g_clear_error(&error);
355 }
356}
357
358void EdsContactBackend::addContacts(std::unique_ptr<GSList, void(*)(GSList *)> contacts)
359{
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400360 /* add CONTACT_ADD_LIMIT # of contacts every CONTACT_ADD_INTERVAL miliseconds */
361 AddContactsData *data = g_new0(AddContactsData, 1);
362 data->backend = this;
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400363 data->contacts = std::move(contacts);
364 data->next = data->contacts.get();
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400365
Stepan Salenikovichdc7178b2015-04-13 17:38:46 -0400366 g_timeout_add_full(G_PRIORITY_DEFAULT,
367 CONTACT_ADD_INTERVAL,
368 (GSourceFunc)add_contacts,
369 data,
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400370 (GDestroyNotify)free_add_contacts_data);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400371}
372
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400373void EdsContactBackend::removeContacts(std::unique_ptr<GSList, void(*)(GSList *)> contact_uids)
374{
375 GSList *next = contact_uids.get();
376 while(next) {
377 gchar *uid = (gchar *)next->data;
378 if (uid) {
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400379 Person *p = PersonModel::instance().getPersonByUid(uid);
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400380 if (p) {
381 g_debug("removing: %s", p->formattedName().toUtf8().constData());
382 deactivate(p);
Stepan Salenikovichbb358592015-07-10 14:45:50 -0400383 editor<Person>()->remove(p);
Stepan Salenikovich21366ed2015-06-26 17:11:45 -0400384 } else {
385 g_warning("person with given UID doesn't exist: %s", uid);
386 }
387 } else {
388 g_warning("null UID in list");
389 }
390 next = g_slist_next(next);
391 }
392}
393
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400394bool EdsContactBackend::load()
395{
396 /**
397 * load the contacts by querying for them,
398 * we want the contact to have some kind of name
399 */
400 EBookQuery *queries[4];
401 int idx = 0;
402 queries[idx++] = e_book_query_field_exists(E_CONTACT_NAME_OR_ORG);
403 queries[idx++] = e_book_query_field_exists(E_CONTACT_GIVEN_NAME);
404 queries[idx++] = e_book_query_field_exists(E_CONTACT_FAMILY_NAME);
405 queries[idx++] = e_book_query_field_exists(E_CONTACT_NICKNAME);
406
407 EBookQuery *name_query = e_book_query_or(idx, queries, TRUE);
408 gchar *query_str = e_book_query_to_string(name_query);
409 e_book_query_unref(name_query);
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400410
411 /* test */
412 e_book_client_get_view(E_BOOK_CLIENT(client_.get()),
413 query_str,
414 cancellable_.get(),
415 (GAsyncReadyCallback)client_view_cb,
416 this);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400417 g_free(query_str);
418
419 return true;
420}
421
422bool EdsContactBackend::reload()
423{
424 return false;
425}
426
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400427FlagPack<CollectionInterface::SupportedFeatures> EdsContactBackend::supportedFeatures() const
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400428{
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400429 return (CollectionInterface::SupportedFeatures::NONE |
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400430 CollectionInterface::SupportedFeatures::LOAD |
Stepan Salenikovich0cf247d2015-07-24 17:36:32 -0400431 CollectionInterface::SupportedFeatures::ADD |
432 CollectionInterface::SupportedFeatures::SAVE );
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400433}
434
435bool EdsContactBackend::clear()
436{
437 return false;
438}
439
440QByteArray EdsContactBackend::id() const
441{
Stepan Salenikovichf4149272015-07-16 16:12:19 -0400442 if (client_)
443 return e_source_get_uid(e_client_get_source(client_.get()));
444 return "edscb";
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400445}
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400446
447bool EdsContactBackend::addNewPerson(Person *item)
448{
Stepan Salenikovich0cf247d2015-07-24 17:36:32 -0400449 g_return_val_if_fail(client_.get(), false);
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400450
451 auto contact = e_contact_new_from_vcard(item->toVCard().constData());
452 gchar *uid = NULL;
453 GError *error = NULL;
454
Stepan Salenikovich0cf247d2015-07-24 17:36:32 -0400455 /* FIXME: this methods returns True for a google addressbook, but it never
456 * actually adds the new contact... not clear if this is possible */
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400457 bool ret = e_book_client_add_contact_sync(
458 E_BOOK_CLIENT(client_.get()),
459 contact,
460 &uid,
461 cancellable_.get(),
462 &error
463 );
464
465 if (!ret) {
466 if (error) {
467 g_warning("could not add contact to collection: %s", error->message);
468 g_clear_error(&error);
469 } else {
470 g_warning("could not add contact to collection");
471 }
472 } else {
473 item->setUid(uid);
474 }
475
476 g_free(uid);
477 g_object_unref(contact);
478
479 return ret;
480}
Stepan Salenikovich0cf247d2015-07-24 17:36:32 -0400481
482bool EdsContactBackend::savePerson(const Person *item)
483{
484 g_return_val_if_fail(client_.get(), false);
485
486 g_debug("saving person");
487
488 auto contact = e_contact_new_from_vcard(item->toVCard().constData());
489 GError *error = NULL;
490
491 /* FIXME: this methods fails for a google addressbook, not clear if it is
492 * possible to edit a google address book... gnome contacts simply creates
493 * a local contact with the same uid */
494 bool ret = e_book_client_modify_contact_sync(
495 E_BOOK_CLIENT(client_.get()),
496 contact,
497 cancellable_.get(),
498 &error
499 );
500
501 if (!ret) {
502 if (error) {
503 g_warning("could not modify contact: %s", error->message);
504 g_clear_error(&error);
505 } else {
506 g_warning("could not modify contact");
507 }
508 }
509
510 g_object_unref(contact);
511
512 return ret;
513}