Distribute .in files too
[claws.git] / src / plugins / gdata / cm_gdata_contacts.c
1 /* GData plugin for Claws-Mail
2  * Copyright (C) 2011 Holger Berndt
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
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 "cm_gdata_contacts.h"
28 #include "cm_gdata_prefs.h"
29
30 #include <gtk/gtk.h>
31 #include "addr_compl.h"
32 #include "main.h"
33 #include "prefs_common.h"
34 #include "common/log.h"
35 #include "common/xml.h"
36
37 #include <gdata/gdata.h>
38
39 #define GDATA_CONTACTS_FILENAME "gdata_cache.xml"
40
41 typedef struct
42 {
43   const gchar *family_name;
44   const gchar *given_name;
45   const gchar *full_name;
46   const gchar *address;
47 } Contact;
48
49 typedef struct
50 {
51   GSList *contacts;
52 } CmGDataContactsCache;
53
54
55 CmGDataContactsCache contacts_cache;
56 gboolean cm_gdata_contacts_query_running = FALSE;
57 gchar *contacts_group_id = NULL;
58
59 static void write_cache_to_file(void)
60 {
61   gchar *path;
62   PrefFile *pfile;
63   XMLTag *tag;
64   XMLNode *xmlnode;
65   GNode *rootnode;
66   GNode *contactsnode;
67   GSList *walk;
68
69   path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
70   pfile = prefs_write_open(path);
71   g_free(path);
72   if(pfile == NULL) {
73     debug_print("GData plugin error: Cannot open file " GDATA_CONTACTS_FILENAME " for writing\n");
74     return;
75   }
76
77   /* XML declarations */
78   xml_file_put_xml_decl(pfile->fp);
79
80   /* Build up XML tree */
81
82   /* root node */
83   tag = xml_tag_new("gdata");
84   xmlnode = xml_node_new(tag, NULL);
85   rootnode = g_node_new(xmlnode);
86
87   /* contacts node */
88   tag = xml_tag_new("contacts");
89   xmlnode = xml_node_new(tag, NULL);
90   contactsnode = g_node_new(xmlnode);
91   g_node_append(rootnode, contactsnode);
92
93   /* walk contacts cache */
94   for(walk = contacts_cache.contacts; walk; walk = walk->next)
95   {
96     GNode *contactnode;
97     Contact *contact = walk->data;
98     tag = xml_tag_new("contact");
99     xml_tag_add_attr(tag, xml_attr_new("family_name",contact->family_name));
100     xml_tag_add_attr(tag, xml_attr_new("given_name",contact->given_name));
101     xml_tag_add_attr(tag, xml_attr_new("full_name",contact->full_name));
102     xml_tag_add_attr(tag, xml_attr_new("address",contact->address));
103     xmlnode = xml_node_new(tag, NULL);
104     contactnode = g_node_new(xmlnode);
105     g_node_append(contactsnode, contactnode);
106   }
107
108   /* Actual writing and cleanup */
109   xml_write_tree(rootnode, pfile->fp);
110   if(prefs_file_close(pfile) < 0)
111     debug_print("GData plugin error: Failed to write file " GDATA_CONTACTS_FILENAME "\n");
112
113   debug_print("GData plugin error: Wrote cache to file " GDATA_CONTACTS_FILENAME "\n");
114
115   /* Free XML tree */
116   xml_free_tree(rootnode);
117 }
118
119 static int add_gdata_contact_to_cache(GDataContactsContact *contact)
120 {
121   GList *walk;
122   int retval;
123
124   retval = 0;
125   for(walk = gdata_contacts_contact_get_email_addresses(contact); walk; walk = walk->next) {
126     const gchar *email_address;
127     GDataGDEmailAddress *address = GDATA_GD_EMAIL_ADDRESS(walk->data);
128
129     email_address = gdata_gd_email_address_get_address(address);
130     if(email_address && (*email_address != '\0')) {
131       GDataGDName *name;
132       Contact *cached_contact;
133
134       name = gdata_contacts_contact_get_name(contact);
135
136       cached_contact = g_new0(Contact, 1);
137       cached_contact->full_name = g_strdup(gdata_gd_name_get_full_name(name));
138       cached_contact->given_name = g_strdup(gdata_gd_name_get_given_name(name));
139       cached_contact->family_name = g_strdup(gdata_gd_name_get_family_name(name));
140       cached_contact->address = g_strdup(email_address);
141
142       contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
143
144       debug_print("GData plugin: Added %s <%s>\n", cached_contact->full_name, cached_contact->address);
145       retval = 1;
146     }
147   }
148   if(retval == 0)
149   {
150     debug_print("GData plugin: Skipped received contact \"%s\" because it doesn't have an email address\n",
151         gdata_gd_name_get_full_name(gdata_contacts_contact_get_name(contact)));
152   }
153   return retval;
154 }
155
156 static void free_contact(Contact *contact)
157 {
158   g_free((gpointer)contact->full_name);
159   g_free((gpointer)contact->family_name);
160   g_free((gpointer)contact->given_name);
161   g_free((gpointer)contact->address);
162   g_free(contact);
163 }
164
165 static void clear_contacts_cache(void)
166 {
167   GSList *walk;
168   for(walk = contacts_cache.contacts; walk; walk = walk->next)
169     free_contact(walk->data);
170   g_slist_free(contacts_cache.contacts);
171   contacts_cache.contacts = NULL;
172 }
173
174 static void cm_gdata_query_contacts_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
175 {
176   GDataFeed *feed;
177   GList *walk;
178   GError *error = NULL;
179   guint num_contacts = 0;
180   guint num_contacts_added = 0;
181         gchar *tmpstr1, *tmpstr2;
182
183   feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
184   cm_gdata_contacts_query_running = FALSE;
185   if(error)
186   {
187     g_object_unref(feed);
188     log_error(LOG_PROTOCOL, _("GData plugin: Error querying for contacts: %s\n"), error->message);
189     g_error_free(error);
190     return;
191   }
192
193   /* clear cache */
194   clear_contacts_cache();
195
196   /* Iterate through the returned contacts and fill the cache */
197   for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
198     num_contacts_added += add_gdata_contact_to_cache(GDATA_CONTACTS_CONTACT(walk->data));
199     num_contacts++;
200   }
201   g_object_unref(feed);
202   contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
203         /* i18n: First part of "Added X of Y contacts to cache" */
204   tmpstr1 = g_strdup_printf(ngettext("Added %d of", "Added %d of", num_contacts_added), num_contacts_added);
205         /* i18n: Second part of "Added X of Y contacts to cache" */
206   tmpstr2 = g_strdup_printf(ngettext("1 contact to the cache", "%d contacts to the cache", num_contacts), num_contacts);
207   log_message(LOG_PROTOCOL, "%s %s\n", tmpstr1, tmpstr2);
208         g_free(tmpstr1);
209         g_free(tmpstr2);
210 }
211
212 static void query_after_auth(GDataContactsService *service)
213 {
214   GDataContactsQuery *query;
215
216   log_message(LOG_PROTOCOL, _("GData plugin: Starting async contacts query\n"));
217
218   query = gdata_contacts_query_new(NULL);
219   gdata_contacts_query_set_group(query, contacts_group_id);
220   gdata_query_set_max_results(GDATA_QUERY(query), cm_gdata_config.max_num_results);
221   gdata_contacts_service_query_contacts_async(service, GDATA_QUERY(query), NULL, NULL, NULL,
222 #ifdef HAVE_GDATA_VERSION_0_9_1
223   NULL,
224 #endif
225   (GAsyncReadyCallback)cm_gdata_query_contacts_ready, NULL);
226
227   g_object_unref(query);
228 }
229
230 #ifdef HAVE_GDATA_VERSION_0_9_1
231 static void cm_gdata_query_groups_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
232 {
233   GDataFeed *feed;
234   GList *walk;
235   GError *error = NULL;
236
237   feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
238   if(error)
239   {
240     g_object_unref(feed);
241     log_error(LOG_PROTOCOL, _("GData plugin: Error querying for groups: %s\n"), error->message);
242     g_error_free(error);
243     return;
244   }
245
246   /* Iterate through the returned groups and search for Contacts group id */
247   for(walk = gdata_feed_get_entries(feed); walk; walk = walk->next) {
248     const gchar *system_group_id;
249     GDataContactsGroup *group = GDATA_CONTACTS_GROUP(walk->data);
250
251     system_group_id = gdata_contacts_group_get_system_group_id(group);
252     if(system_group_id && !strcmp(system_group_id, GDATA_CONTACTS_GROUP_CONTACTS)) {
253       gchar *pos;
254       const gchar *id;
255
256       id = gdata_entry_get_id(GDATA_ENTRY(group));
257
258       /* possibly replace projection "full" by "base" */
259       pos = g_strrstr(id, "/full/");
260       if(pos) {
261         GString *str = g_string_new("\0");
262         int off = pos-id;
263
264         g_string_append_len(str, id, off);
265         g_string_append(str, "/base/");
266         g_string_append(str, id+off+strlen("/full/"));
267         g_string_append_c(str, '\0');
268         contacts_group_id = str->str;
269         g_string_free(str, FALSE);
270       }
271       else
272         contacts_group_id = g_strdup(id);
273       break;
274     }
275   }
276   g_object_unref(feed);
277
278   log_message(LOG_PROTOCOL, _("GData plugin: Groups received\n"));
279
280   query_after_auth(service);
281 }
282 #endif
283
284 #ifdef HAVE_GDATA_VERSION_0_9
285 static void query_for_contacts_group_id(GDataClientLoginAuthorizer *authorizer)
286 {
287   GDataContactsService *service;
288 #ifdef HAVE_GDATA_VERSION_0_9_1
289
290   log_message(LOG_PROTOCOL, _("GData plugin: Starting async groups query\n"));
291
292   service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
293   gdata_contacts_service_query_groups_async(service, NULL, NULL, NULL, NULL, NULL,
294       (GAsyncReadyCallback)cm_gdata_query_groups_ready, NULL);
295 #else
296   service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
297   query_after_auth(service);
298 #endif
299   g_object_unref(service);
300 }
301
302 static void cm_gdata_auth_ready(GDataClientLoginAuthorizer *authorizer, GAsyncResult *res, gpointer data)
303 {
304   GError *error = NULL;
305
306   if(gdata_client_login_authorizer_authenticate_finish(authorizer, res, &error) == FALSE)
307   {
308     log_error(LOG_PROTOCOL, _("GData plugin: Authentication error: %s\n"), error->message);
309     g_error_free(error);
310     cm_gdata_contacts_query_running = FALSE;
311     return;
312   }
313
314   log_message(LOG_PROTOCOL, _("GData plugin: Authenticated\n"));
315
316   if(!contacts_group_id)
317   {
318     query_for_contacts_group_id(authorizer);
319   }
320   else {
321     GDataContactsService *service;
322     service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
323     query_after_auth(service);
324     g_object_unref(service);
325   }
326 }
327 #else
328 static void cm_gdata_auth_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
329 {
330   GError *error = NULL;
331
332   if(!gdata_service_authenticate_finish(GDATA_SERVICE(service), res, &error))
333   {
334     log_error(LOG_PROTOCOL, _("GData plugin: Authentication error: %s\n"), error->message);
335     g_error_free(error);
336     cm_gdata_contacts_query_running = FALSE;
337     return;
338   }
339
340   log_message(LOG_PROTOCOL, _("GData plugin: Authenticated\n"));
341
342   query_after_auth(service);
343 }
344 #endif
345 static void query()
346 {
347
348 #ifdef HAVE_GDATA_VERSION_0_9
349   GDataClientLoginAuthorizer *authorizer;
350 #else
351   GDataContactsService *service;
352 #endif
353
354   if(cm_gdata_contacts_query_running)
355   {
356     debug_print("GData plugin: Network query already in progress");
357     return;
358   }
359
360   log_message(LOG_PROTOCOL, _("GData plugin: Starting async authentication\n"));
361
362 #ifdef HAVE_GDATA_VERSION_0_9
363   authorizer = gdata_client_login_authorizer_new(CM_GDATA_CLIENT_ID, GDATA_TYPE_CONTACTS_SERVICE);
364   gdata_client_login_authorizer_authenticate_async(authorizer, cm_gdata_config.username, cm_gdata_config.password, NULL, (GAsyncReadyCallback)cm_gdata_auth_ready, NULL);
365   cm_gdata_contacts_query_running = TRUE;
366 #else
367   service = gdata_contacts_service_new(CM_GDATA_CLIENT_ID);
368   cm_gdata_contacts_query_running = TRUE;
369   gdata_service_authenticate_async(GDATA_SERVICE(service), cm_gdata_config.username, cm_gdata_config.password, NULL,
370       (GAsyncReadyCallback)cm_gdata_auth_ready, NULL);
371 #endif
372
373
374 #ifdef HAVE_GDATA_VERSION_0_9
375   g_object_unref(authorizer);
376 #else
377   g_object_unref(service);
378 #endif
379
380 }
381
382
383 static void add_contacts_to_list(GList **address_list, GSList *contacts)
384 {
385   GSList *walk;
386
387   for(walk = contacts; walk; walk = walk->next)
388   {
389     address_entry *ae;
390     Contact *contact = walk->data;
391
392     ae = g_new0(address_entry, 1);
393     ae->name = g_strdup(contact->full_name);
394     ae->address = g_strdup(contact->address);
395     ae->grp_emails = NULL;
396
397     *address_list = g_list_prepend(*address_list, ae);
398     addr_compl_add_address1(ae->address, ae);
399
400     if(contact->given_name && *(contact->given_name) != '\0')
401       addr_compl_add_address1(contact->given_name, ae);
402
403     if(contact->family_name && *(contact->family_name) != '\0')
404       addr_compl_add_address1(contact->family_name, ae);
405   }
406 }
407
408 void cm_gdata_add_contacts(GList **address_list)
409 {
410   add_contacts_to_list(address_list, contacts_cache.contacts);
411 }
412
413 gboolean cm_gdata_update_contacts_cache(void)
414 {
415   if(prefs_common.work_offline)
416   {
417     debug_print("GData plugin: Offline mode\n");
418   }
419   else if(!cm_gdata_config.username || *(cm_gdata_config.username) == '\0' || !cm_gdata_config.password)
420   {
421     /* noop if no credentials are given */
422     debug_print("GData plugin: Empty username or password\n");
423   }
424   else
425   {
426     debug_print("GData plugin: Querying contacts");
427     query();
428   }
429   return TRUE;
430 }
431
432 void cm_gdata_contacts_done(void)
433 {
434   g_free(contacts_group_id);
435   contacts_group_id = NULL;
436
437   write_cache_to_file();
438   if(contacts_cache.contacts && !claws_is_exiting())
439     clear_contacts_cache();
440 }
441
442 void cm_gdata_load_contacts_cache_from_file(void)
443 {
444   gchar *path;
445   GNode *rootnode, *childnode, *contactnode;
446   XMLNode *xmlnode;
447
448   path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
449   if(!is_file_exist(path)) {
450     g_free(path);
451     return;
452   }
453
454   /* no merging; make sure the cache is empty (this should be a noop, but just to be safe...) */
455   clear_contacts_cache();
456
457   rootnode = xml_parse_file(path);
458   g_free(path);
459   if(!rootnode)
460     return;
461   xmlnode = rootnode->data;
462
463   /* Check that root entry is "gdata" */
464   if(strcmp2(xmlnode->tag->tag, "gdata") != 0) {
465     g_warning("wrong gdata cache file\n");
466     xml_free_tree(rootnode);
467     return;
468   }
469
470   for(childnode = rootnode->children; childnode; childnode = childnode->next) {
471     GList *attributes;
472     xmlnode = childnode->data;
473
474     if(strcmp2(xmlnode->tag->tag, "contacts") != 0)
475       continue;
476
477     for(contactnode = childnode->children; contactnode; contactnode = contactnode->next)
478     {
479       Contact *cached_contact;
480
481       xmlnode = contactnode->data;
482
483       cached_contact = g_new0(Contact, 1);
484       /* Attributes of the branch nodes */
485       for(attributes = xmlnode->tag->attr; attributes; attributes = attributes->next) {
486         XMLAttr *attr = attributes->data;
487
488         if(attr && attr->name && attr->value) {
489           if(!strcmp2(attr->name, "full_name"))
490             cached_contact->full_name = g_strdup(attr->value);
491           else if(!strcmp2(attr->name, "given_name"))
492             cached_contact->given_name = g_strdup(attr->value);
493           else if(!strcmp2(attr->name, "family_name"))
494             cached_contact->family_name = g_strdup(attr->value);
495           else if(!strcmp2(attr->name, "address"))
496             cached_contact->address = g_strdup(attr->value);
497         }
498       }
499       contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
500       debug_print("Read contact from cache: %s\n", cached_contact->full_name);
501     }
502   }
503
504   /* Free XML tree */
505   xml_free_tree(rootnode);
506
507   contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);
508 }