Merge branch 'master' of ssh+git://git.claws-mail.org/home/git/claws
[claws.git] / src / plugins / gdata / cm_gdata_contacts.c
1 /* GData plugin for Claws-Mail
2  * Copyright (C) 2011 Holger Berndt
3  * Copyright (C) 2011-2015 the Claws Mail team
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, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #  include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26
27 #include <gtk/gtk.h>
28
29 #include "cm_gdata_contacts.h"
30 #include "cm_gdata_prefs.h"
31
32 #include "addr_compl.h"
33 #include "main.h"
34 #include "prefs_common.h"
35 #include "mainwindow.h"
36 #include "common/log.h"
37 #include "common/xml.h"
38 #include "common/utils.h"
39 #include "common/passcrypt.h"
40 #include "gtk/gtkutils.h"
41
42 #include <gdata/gdata.h>
43
44 #define GDATA_CONTACTS_FILENAME "gdata_cache.xml"
45
46 #define GDATA_C1 "EOnuQt4fxED3WrO//iub3KLQMScIxXiT0VBD8RZUeKjkcm1zEBVMboeWDLYyqjJKZaL4oaZ24umWygbj19T2oJR6ZpjbCw=="
47 #define GDATA_C2 "QYjIgZblg/4RMCnEqNQypcHZba9ePqAN"
48 #define GDATA_C3 "XHEZEgO06YbWfQWOyYhE/ny5Q10aNOZlkQ=="
49
50
51 typedef struct
52 {
53   const gchar *family_name;
54   const gchar *given_name;
55   const gchar *full_name;
56   const gchar *address;
57 } Contact;
58
59 typedef struct
60 {
61   GSList *contacts;
62 } CmGDataContactsCache;
63
64
65 CmGDataContactsCache contacts_cache;
66 gboolean cm_gdata_contacts_query_running = FALSE;
67 gchar *contacts_group_id = NULL;
68 GDataOAuth2Authorizer *authorizer = NULL;
69 GDataContactsService *service = NULL;
70
71
72 static void protect_fields_against_NULL(Contact *contact)
73 {
74   g_return_if_fail(contact != NULL);
75
76   /* protect fields against being NULL */
77   if(contact->full_name == NULL)
78     contact->full_name = g_strdup("");
79   if(contact->given_name == NULL)
80     contact->given_name = g_strdup("");
81   if(contact->family_name == NULL)
82     contact->family_name = g_strdup("");
83 }
84
85 typedef struct
86 {
87   const gchar *auth_uri;
88   GtkWidget *entry;
89 } AuthCodeQueryButtonData;
90
91
92 static void auth_uri_link_button_clicked_cb(GtkButton *button, gpointer data)
93 {
94   AuthCodeQueryButtonData *auth_code_query_data = data;
95   open_uri(auth_code_query_data->auth_uri, prefs_common_get_uri_cmd());
96   gtk_widget_grab_focus(auth_code_query_data->entry);
97 }
98
99 static void auth_code_entry_changed_cb(GtkEditable *entry, gpointer data)
100 {
101   gtk_widget_set_sensitive(GTK_WIDGET(data), gtk_entry_get_text_length(GTK_ENTRY(entry)) > 0);
102 }
103
104
105 /* Returns the authorization code as newly allocated string, or NULL */
106 gchar* ask_user_for_auth_code(const gchar *auth_uri)
107 {
108   GtkWidget *dialog;
109   GtkWidget *vbox;
110   GtkWidget *table;
111   GtkWidget *link_button;
112   GtkWidget *label;
113   GtkWidget *entry;
114   gchar *str;
115   gchar *retval = NULL;
116   MainWindow *mainwin;
117   gint dlg_res;
118   GtkWidget *btn_ok;
119   AuthCodeQueryButtonData *auth_code_query_data;
120
121   mainwin = mainwindow_get_mainwindow();
122   dialog = gtk_message_dialog_new_with_markup(mainwin ? GTK_WINDOW(mainwin->window) : NULL,
123       GTK_DIALOG_DESTROY_WITH_PARENT,
124       GTK_MESSAGE_INFO,
125       GTK_BUTTONS_NONE,
126       "<span weight=\"bold\" size=\"larger\">%s</span>", _("GData plugin: Authorization required"));
127   gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
128       _("You need to authorize Claws Mail to access your Google contact list to use the GData plugin."
129       "\n\nVisit Google's authorization page by pressing the button below. After you "
130       "confirmed the authorization, you will get an authorization code. Enter that code "
131       "in the field below to grant Claws Mail access to your Google contact list."));
132   gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
133   btn_ok = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
134   gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
135   gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
136
137   gtk_widget_set_sensitive(btn_ok, FALSE);
138
139   table = gtk_table_new(2, 3, FALSE);
140   gtk_table_set_row_spacings(GTK_TABLE(table), 8);
141   gtk_table_set_col_spacings(GTK_TABLE(table), 8);
142
143   str = g_strconcat("<b>", _("Step 1:"), "</b>", NULL);
144   label = gtk_label_new(str);
145   g_free(str);
146   gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
147   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, 0, 0, 0, 0);
148
149   link_button = gtk_button_new_with_label(_("Click here to open the Google authorization page in a browser"));
150   auth_code_query_data = g_new0(AuthCodeQueryButtonData,1);
151   gtk_table_attach(GTK_TABLE(table), link_button, 1, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
152
153   str = g_strconcat("<b>", _("Step 2:"), "</b>", NULL);
154   label = gtk_label_new(str);
155   g_free(str);
156   gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
157   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, 0, 0, 0, 0);
158
159   gtk_table_attach(GTK_TABLE(table), gtk_label_new(_("Enter code:")), 1, 2, 1, 2, 0, 0, 0, 0);
160
161   entry = gtk_entry_new();
162   g_signal_connect(G_OBJECT(entry), "changed", (GCallback)auth_code_entry_changed_cb, (gpointer)btn_ok);
163   gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
164
165   auth_code_query_data->auth_uri = auth_uri;
166   auth_code_query_data->entry = entry;
167   g_signal_connect(G_OBJECT(link_button), "clicked", (GCallback)auth_uri_link_button_clicked_cb, (gpointer)auth_code_query_data);
168
169   vbox = gtk_vbox_new(FALSE, 4);
170   gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
171
172   gtk_box_pack_start(GTK_BOX(gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog))), vbox, FALSE, FALSE, 0);
173
174   gtk_widget_show_all(dialog);
175
176   dlg_res = gtk_dialog_run(GTK_DIALOG(dialog));
177   switch(dlg_res)
178   {
179   case GTK_RESPONSE_DELETE_EVENT:
180   case GTK_RESPONSE_CANCEL:
181     break;
182   case GTK_RESPONSE_OK:
183     retval = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
184     break;
185   }
186
187   g_free(auth_code_query_data);
188   gtk_widget_destroy(dialog);
189
190   return retval;
191 }
192
193
194 static void write_cache_to_file(void)
195 {
196   gchar *path;
197   PrefFile *pfile;
198   XMLTag *tag;
199   XMLNode *xmlnode;
200   GNode *rootnode;
201   GNode *contactsnode;
202   GSList *walk;
203
204   path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
205   pfile = prefs_write_open(path);
206   g_free(path);
207   if(pfile == NULL) {
208     debug_print("GData plugin error: Cannot open file " GDATA_CONTACTS_FILENAME " for writing\n");
209     return;
210   }
211
212   /* XML declarations */
213   xml_file_put_xml_decl(pfile->fp);
214
215   /* Build up XML tree */
216
217   /* root node */
218   tag = xml_tag_new("gdata");
219   xmlnode = xml_node_new(tag, NULL);
220   rootnode = g_node_new(xmlnode);
221
222   /* contacts node */
223   tag = xml_tag_new("contacts");
224   xmlnode = xml_node_new(tag, NULL);
225   contactsnode = g_node_new(xmlnode);
226   g_node_append(rootnode, contactsnode);
227
228   /* walk contacts cache */
229   for(walk = contacts_cache.contacts; walk; walk = walk->next)
230   {
231     GNode *contactnode;
232     Contact *contact = walk->data;
233     tag = xml_tag_new("contact");
234     xml_tag_add_attr(tag, xml_attr_new("family_name",contact->family_name));
235     xml_tag_add_attr(tag, xml_attr_new("given_name",contact->given_name));
236     xml_tag_add_attr(tag, xml_attr_new("full_name",contact->full_name));
237     xml_tag_add_attr(tag, xml_attr_new("address",contact->address));
238     xmlnode = xml_node_new(tag, NULL);
239     contactnode = g_node_new(xmlnode);
240     g_node_append(contactsnode, contactnode);
241   }
242
243         /* Actual writing and cleanup */
244         xml_write_tree(rootnode, pfile->fp);
245         if (prefs_file_close(pfile) < 0)
246                 debug_print("GData plugin error: Failed to write file " GDATA_CONTACTS_FILENAME "\n");
247         else
248                 debug_print("GData plugin: Wrote cache to file " GDATA_CONTACTS_FILENAME "\n");
249
250   /* Free XML tree */
251   xml_free_tree(rootnode);
252 }
253
254 static int add_gdata_contact_to_cache(GDataContactsContact *contact)
255 {
256   GList *walk;
257   int retval;
258
259   retval = 0;
260   for(walk = gdata_contacts_contact_get_email_addresses(contact); walk; walk = walk->next) {
261     const gchar *email_address;
262     GDataGDEmailAddress *address = GDATA_GD_EMAIL_ADDRESS(walk->data);
263
264     email_address = gdata_gd_email_address_get_address(address);
265     if(email_address && (*email_address != '\0')) {
266       GDataGDName *name;
267       Contact *cached_contact;
268
269       name = gdata_contacts_contact_get_name(contact);
270
271       cached_contact = g_new0(Contact, 1);
272       cached_contact->full_name = g_strdup(gdata_gd_name_get_full_name(name));
273       cached_contact->given_name = g_strdup(gdata_gd_name_get_given_name(name));
274       cached_contact->family_name = g_strdup(gdata_gd_name_get_family_name(name));
275       cached_contact->address = g_strdup(email_address);
276
277       protect_fields_against_NULL(cached_contact);
278
279       contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
280
281       debug_print("GData plugin: Added %s <%s>\n", cached_contact->full_name, cached_contact->address);
282       retval = 1;
283     }
284   }
285   if(retval == 0)
286   {
287     debug_print("GData plugin: Skipped received contact \"%s\" because it doesn't have an email address\n",
288         gdata_gd_name_get_full_name(gdata_contacts_contact_get_name(contact)));
289   }
290   return retval;
291 }
292
293 static void free_contact(Contact *contact)
294 {
295   g_free((gpointer)contact->full_name);
296   g_free((gpointer)contact->family_name);
297   g_free((gpointer)contact->given_name);
298   g_free((gpointer)contact->address);
299   g_free(contact);
300 }
301
302 static void clear_contacts_cache(void)
303 {
304   GSList *walk;
305   for(walk = contacts_cache.contacts; walk; walk = walk->next)
306     free_contact(walk->data);
307   g_slist_free(contacts_cache.contacts);
308   contacts_cache.contacts = NULL;
309 }
310
311 static void cm_gdata_query_contacts_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
312 {
313   GDataFeed *feed;
314   GList *walk;
315   GError *error = NULL;
316   guint num_contacts = 0;
317   guint num_contacts_added = 0;
318         gchar *tmpstr1, *tmpstr2;
319
320   feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
321   cm_gdata_contacts_query_running = FALSE;
322   if(error)
323   {
324     g_object_unref(feed);
325     log_error(LOG_PROTOCOL, _("GData plugin: Error querying for contacts: %s\n"), error->message);
326     g_error_free(error);
327     return;
328   }
329
330   /* clear cache */
331   clear_contacts_cache();
332
333   /* Iterate through the returned contacts and fill the cache */
334   for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
335     num_contacts_added += add_gdata_contact_to_cache(GDATA_CONTACTS_CONTACT(walk->data));
336     num_contacts++;
337   }
338   g_object_unref(feed);
339   contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
340         /* TRANSLATORS: First part of "Added X of Y contacts to cache" */
341   tmpstr1 = g_strdup_printf(ngettext("Added %d of", "Added %d of", num_contacts_added), num_contacts_added);
342         /* TRANSLATORS: Second part of "Added X of Y contacts to cache" */
343   tmpstr2 = g_strdup_printf(ngettext("1 contact to the cache", "%d contacts to the cache", num_contacts), num_contacts);
344   log_message(LOG_PROTOCOL, "%s %s\n", tmpstr1, tmpstr2);
345         g_free(tmpstr1);
346         g_free(tmpstr2);
347 }
348
349 static void query_contacts(GDataContactsService *service)
350 {
351   GDataContactsQuery *query;
352
353   log_message(LOG_PROTOCOL, _("GData plugin: Starting async contacts query\n"));
354
355   query = gdata_contacts_query_new(NULL);
356   gdata_contacts_query_set_group(query, contacts_group_id);
357   gdata_query_set_max_results(GDATA_QUERY(query), cm_gdata_config.max_num_results);
358   gdata_contacts_service_query_contacts_async(service, GDATA_QUERY(query), NULL, NULL, NULL,
359   NULL, (GAsyncReadyCallback)cm_gdata_query_contacts_ready, NULL);
360
361   g_object_unref(query);
362 }
363
364 static void cm_gdata_query_groups_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
365 {
366   GDataFeed *feed;
367   GList *walk;
368   GError *error = NULL;
369
370   feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
371   if(error)
372   {
373     g_object_unref(feed);
374     log_error(LOG_PROTOCOL, _("GData plugin: Error querying for groups: %s\n"), error->message);
375     g_error_free(error);
376     return;
377   }
378
379   /* Iterate through the returned groups and search for Contacts group id */
380   for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
381     const gchar *system_group_id;
382     GDataContactsGroup *group = GDATA_CONTACTS_GROUP(walk->data);
383
384     system_group_id = gdata_contacts_group_get_system_group_id(group);
385     if(system_group_id && !strcmp(system_group_id, GDATA_CONTACTS_GROUP_CONTACTS)) {
386       gchar *pos;
387       const gchar *id;
388
389       id = gdata_entry_get_id(GDATA_ENTRY(group));
390
391       /* possibly replace projection "full" by "base" */
392       pos = g_strrstr(id, "/full/");
393       if(pos) {
394         GString *str = g_string_new("\0");
395         int off = pos-id;
396
397         g_string_append_len(str, id, off);
398         g_string_append(str, "/base/");
399         g_string_append(str, id+off+strlen("/full/"));
400         g_string_append_c(str, '\0');
401         contacts_group_id = str->str;
402         g_string_free(str, FALSE);
403       }
404       else
405         contacts_group_id = g_strdup(id);
406       break;
407     }
408   }
409   g_object_unref(feed);
410
411   log_message(LOG_PROTOCOL, _("GData plugin: Groups received\n"));
412
413   query_contacts(service);
414 }
415
416 static void query_for_contacts_group_id(GDataAuthorizer *authorizer)
417 {
418   log_message(LOG_PROTOCOL, _("GData plugin: Starting async groups query\n"));
419
420   gdata_contacts_service_query_groups_async(service, NULL, NULL, NULL, NULL, NULL,
421       (GAsyncReadyCallback)cm_gdata_query_groups_ready, NULL);
422 }
423
424
425 static void query_after_auth()
426 {
427   if(!contacts_group_id)
428     query_for_contacts_group_id(GDATA_AUTHORIZER(authorizer));
429   else
430     query_contacts(service);
431 }
432
433
434 static void cm_gdata_auth_ready(GDataOAuth2Authorizer *auth, GAsyncResult *res, gpointer data)
435 {
436   GError *error = NULL;
437
438   if(gdata_oauth2_authorizer_request_authorization_finish(auth, res, &error) == FALSE)
439   {
440     /* Notify the user of all errors except cancellation errors */
441     if(!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
442     {
443       log_error(LOG_PROTOCOL, _("GData plugin: Authorization error: %s\n"), error->message);
444     }
445     g_error_free(error);
446     cm_gdata_contacts_query_running = FALSE;
447     return;
448   }
449
450   log_message(LOG_PROTOCOL, _("GData plugin: Authorization successful\n"));
451
452   query_after_auth();
453 }
454
455 static void cm_gdata_interactive_auth()
456 {
457   gchar *auth_uri;
458   gchar *auth_code;
459
460   log_message(LOG_PROTOCOL, _("GData plugin: Starting interactive authorization\n"));
461
462   auth_uri = gdata_oauth2_authorizer_build_authentication_uri(authorizer, cm_gdata_config.username, FALSE);
463   g_return_if_fail(auth_uri);
464
465   auth_code = ask_user_for_auth_code(auth_uri);
466
467   if(auth_code)
468   {
469     cm_gdata_contacts_query_running = TRUE;
470     log_message(LOG_PROTOCOL, _("GData plugin: Got authorization code, requesting authorization\n"));
471     gdata_oauth2_authorizer_request_authorization_async(authorizer, auth_code, NULL, (GAsyncReadyCallback)cm_gdata_auth_ready, NULL);
472     memset(auth_code, 0, strlen(auth_code));
473     g_free(auth_code);
474   }
475   else
476   {
477     log_warning(LOG_PROTOCOL, _("GData plugin: No authorization code received, authorization request cancelled\n"));
478   }
479
480   g_free(auth_uri);
481 }
482
483
484 #if GDATA_CHECK_VERSION(0,17,2)
485 static void cm_gdata_refresh_ready(GDataOAuth2Authorizer *auth, GAsyncResult *res, gpointer data)
486 {
487   GError *error = NULL;
488
489   if(gdata_authorizer_refresh_authorization_finish(GDATA_AUTHORIZER(auth), res, &error) == FALSE)
490   {
491     /* Notify the user of all errors except cancellation errors */
492     if(!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
493     {
494       log_error(LOG_PROTOCOL, _("GData plugin: Authorization refresh error: %s\n"), error->message);
495     }
496     g_error_free(error);
497
498     cm_gdata_interactive_auth();
499
500     return;
501   }
502
503   log_message(LOG_PROTOCOL, _("GData plugin: Authorization refresh successful\n"));
504
505   query_after_auth();
506 }
507 #endif
508
509
510 /* returns allocated string which must be freed */
511 static guchar* decode(const gchar *in)
512 {
513   guchar *tmp;
514   gsize len;
515
516   tmp = g_base64_decode(in, &len);
517   passcrypt_decrypt(tmp, len);
518   return tmp;
519 }
520
521
522 static void query()
523 {
524   if(cm_gdata_contacts_query_running)
525   {
526     debug_print("GData plugin: Network query already in progress");
527     return;
528   }
529
530   if(!authorizer)
531   {
532     gchar *c1 = decode(GDATA_C1);
533     gchar *c2 = decode(GDATA_C2);
534     gchar *c3 = decode(GDATA_C3);
535
536     authorizer = gdata_oauth2_authorizer_new(c1, c2, c3, GDATA_TYPE_CONTACTS_SERVICE);
537
538     g_free(c1);
539     g_free(c2);
540     g_free(c3);
541   }
542   g_return_if_fail(authorizer);
543
544   if(!service)
545   {
546     service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
547   }
548   g_return_if_fail(service);
549
550   if(!gdata_service_is_authorized(GDATA_SERVICE(service)))
551   {
552 #if GDATA_CHECK_VERSION(0,17,2)
553     /* Try to restore from saved refresh token.*/
554     if(cm_gdata_config.oauth2_refresh_token)
555     {
556       log_message(LOG_PROTOCOL, _("GData plugin: Trying to refresh authorization\n"));
557       gdata_oauth2_authorizer_set_refresh_token(authorizer, cm_gdata_config.oauth2_refresh_token);
558       gdata_authorizer_refresh_authorization_async(GDATA_AUTHORIZER(authorizer), NULL, (GAsyncReadyCallback)cm_gdata_refresh_ready, NULL);
559     }
560     else
561     {
562       cm_gdata_interactive_auth();
563     }
564 #else
565     cm_gdata_interactive_auth();
566 #endif
567   }
568   else
569   {
570     query_after_auth();
571   }
572 }
573
574
575 static void add_contacts_to_list(GList **address_list, GSList *contacts)
576 {
577   GSList *walk;
578
579   for(walk = contacts; walk; walk = walk->next)
580   {
581     address_entry *ae;
582     Contact *contact = walk->data;
583
584     ae = g_new0(address_entry, 1);
585     ae->name = g_strdup(contact->full_name);
586     ae->address = g_strdup(contact->address);
587     ae->grp_emails = NULL;
588
589     *address_list = g_list_prepend(*address_list, ae);
590     addr_compl_add_address1(ae->address, ae);
591
592     if(contact->given_name && *(contact->given_name) != '\0')
593       addr_compl_add_address1(contact->given_name, ae);
594
595     if(contact->family_name && *(contact->family_name) != '\0')
596       addr_compl_add_address1(contact->family_name, ae);
597   }
598 }
599
600 void cm_gdata_add_contacts(GList **address_list)
601 {
602   add_contacts_to_list(address_list, contacts_cache.contacts);
603 }
604
605 gboolean cm_gdata_update_contacts_cache(void)
606 {
607   if(prefs_common_get_prefs()->work_offline)
608   {
609     debug_print("GData plugin: Offline mode\n");
610   }
611   else
612   {
613     debug_print("GData plugin: Querying contacts\n");
614     query();
615   }
616   return TRUE;
617 }
618
619 void cm_gdata_contacts_done(void)
620 {
621   g_free(contacts_group_id);
622   contacts_group_id = NULL;
623
624   write_cache_to_file();
625   if(contacts_cache.contacts && !claws_is_exiting())
626     clear_contacts_cache();
627
628   if(authorizer)
629   {
630 #if GDATA_CHECK_VERSION(0,17,2)
631     /* store refresh token */
632     cm_gdata_config.oauth2_refresh_token = gdata_oauth2_authorizer_dup_refresh_token(authorizer);
633 #endif
634
635     g_object_unref(G_OBJECT(authorizer));
636     authorizer = NULL;
637   }
638
639   if(service)
640   {
641     g_object_unref(G_OBJECT(service));
642     service = NULL;
643   }
644 }
645
646 void cm_gdata_load_contacts_cache_from_file(void)
647 {
648   gchar *path;
649   GNode *rootnode, *childnode, *contactnode;
650   XMLNode *xmlnode;
651
652   path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
653   if(!is_file_exist(path)) {
654     g_free(path);
655     return;
656   }
657
658   /* no merging; make sure the cache is empty (this should be a noop, but just to be safe...) */
659   clear_contacts_cache();
660
661   rootnode = xml_parse_file(path);
662   g_free(path);
663   if(!rootnode)
664     return;
665   xmlnode = rootnode->data;
666
667   /* Check that root entry is "gdata" */
668   if(strcmp2(xmlnode->tag->tag, "gdata") != 0) {
669     g_warning("wrong gdata cache file");
670     xml_free_tree(rootnode);
671     return;
672   }
673
674   for(childnode = rootnode->children; childnode; childnode = childnode->next) {
675     GList *attributes;
676     xmlnode = childnode->data;
677
678     if(strcmp2(xmlnode->tag->tag, "contacts") != 0)
679       continue;
680
681     for(contactnode = childnode->children; contactnode; contactnode = contactnode->next)
682     {
683       Contact *cached_contact;
684
685       xmlnode = contactnode->data;
686
687       cached_contact = g_new0(Contact, 1);
688       /* Attributes of the branch nodes */
689       for(attributes = xmlnode->tag->attr; attributes; attributes = attributes->next) {
690         XMLAttr *attr = attributes->data;
691
692         if(attr && attr->name && attr->value) {
693           if(!strcmp2(attr->name, "full_name"))
694             cached_contact->full_name = g_strdup(attr->value);
695           else if(!strcmp2(attr->name, "given_name"))
696             cached_contact->given_name = g_strdup(attr->value);
697           else if(!strcmp2(attr->name, "family_name"))
698             cached_contact->family_name = g_strdup(attr->value);
699           else if(!strcmp2(attr->name, "address"))
700             cached_contact->address = g_strdup(attr->value);
701         }
702       }
703
704       if(cached_contact->address)
705       {
706         protect_fields_against_NULL(cached_contact);
707
708         contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
709         debug_print("Read contact from cache: %s\n", cached_contact->full_name);
710       }
711       else {
712         debug_print("Ignored contact without email address: %s\n", cached_contact->full_name ? cached_contact->full_name : "(null)");
713       }
714     }
715   }
716
717   /* Free XML tree */
718   xml_free_tree(rootnode);
719
720   contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
721 }