1 /* GData plugin for Claws-Mail
2 * Copyright (C) 2011 Holger Berndt
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.
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.
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/>.
21 # include "claws-features.h"
25 #include <glib/gi18n.h>
27 #include "cm_gdata_contacts.h"
28 #include "cm_gdata_prefs.h"
31 #include "addr_compl.h"
33 #include "prefs_common.h"
34 #include "common/log.h"
35 #include "common/xml.h"
37 #include <gdata/gdata.h>
39 #define GDATA_CONTACTS_FILENAME "gdata_cache.xml"
43 const gchar *family_name;
44 const gchar *given_name;
45 const gchar *full_name;
52 } CmGDataContactsCache;
55 CmGDataContactsCache contacts_cache;
56 gboolean cm_gdata_contacts_query_running = FALSE;
57 gchar *contacts_group_id = NULL;
59 static void write_cache_to_file(void)
69 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
70 pfile = prefs_write_open(path);
73 debug_print("GData plugin error: Cannot open file " GDATA_CONTACTS_FILENAME " for writing\n");
77 /* XML declarations */
78 xml_file_put_xml_decl(pfile->fp);
80 /* Build up XML tree */
83 tag = xml_tag_new("gdata");
84 xmlnode = xml_node_new(tag, NULL);
85 rootnode = g_node_new(xmlnode);
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);
93 /* walk contacts cache */
94 for(walk = contacts_cache.contacts; walk; walk = walk->next)
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);
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");
113 debug_print("GData plugin error: Wrote cache to file " GDATA_CONTACTS_FILENAME "\n");
116 xml_free_tree(rootnode);
119 static int add_gdata_contact_to_cache(GDataContactsContact *contact)
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);
129 email_address = gdata_gd_email_address_get_address(address);
130 if(email_address && (*email_address != '\0')) {
132 Contact *cached_contact;
134 name = gdata_contacts_contact_get_name(contact);
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);
142 contacts_cache.contacts = g_slist_prepend(contacts_cache.contacts, cached_contact);
144 debug_print("GData plugin: Added %s <%s>\n", cached_contact->full_name, cached_contact->address);
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)));
156 static void free_contact(Contact *contact)
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);
165 static void clear_contacts_cache(void)
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;
174 static void cm_gdata_query_contacts_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
178 GError *error = NULL;
179 guint num_contacts = 0;
180 guint num_contacts_added = 0;
181 gchar *tmpstr1, *tmpstr2;
183 feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
184 cm_gdata_contacts_query_running = FALSE;
187 g_object_unref(feed);
188 log_error(LOG_PROTOCOL, _("GData plugin: Error querying for contacts: %s\n"), error->message);
194 clear_contacts_cache();
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));
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);
212 static void query_after_auth(GDataContactsService *service)
214 GDataContactsQuery *query;
216 log_message(LOG_PROTOCOL, _("GData plugin: Starting async contacts query\n"));
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
225 (GAsyncReadyCallback)cm_gdata_query_contacts_ready, NULL);
227 g_object_unref(query);
230 #ifdef HAVE_GDATA_VERSION_0_9_1
231 static void cm_gdata_query_groups_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
235 GError *error = NULL;
237 feed = gdata_service_query_finish(GDATA_SERVICE(service), res, &error);
240 g_object_unref(feed);
241 log_error(LOG_PROTOCOL, _("GData plugin: Error querying for groups: %s\n"), error->message);
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);
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)) {
256 id = gdata_entry_get_id(GDATA_ENTRY(group));
258 /* possibly replace projection "full" by "base" */
259 pos = g_strrstr(id, "/full/");
261 GString *str = g_string_new("\0");
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);
272 contacts_group_id = g_strdup(id);
276 g_object_unref(feed);
278 log_message(LOG_PROTOCOL, _("GData plugin: Groups received\n"));
280 query_after_auth(service);
284 #ifdef HAVE_GDATA_VERSION_0_9
285 static void query_for_contacts_group_id(GDataClientLoginAuthorizer *authorizer)
287 GDataContactsService *service;
288 #ifdef HAVE_GDATA_VERSION_0_9_1
290 log_message(LOG_PROTOCOL, _("GData plugin: Starting async groups query\n"));
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);
296 service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
297 query_after_auth(service);
299 g_object_unref(service);
302 static void cm_gdata_auth_ready(GDataClientLoginAuthorizer *authorizer, GAsyncResult *res, gpointer data)
304 GError *error = NULL;
306 if(gdata_client_login_authorizer_authenticate_finish(authorizer, res, &error) == FALSE)
308 log_error(LOG_PROTOCOL, _("GData plugin: Authentication error: %s\n"), error->message);
310 cm_gdata_contacts_query_running = FALSE;
314 log_message(LOG_PROTOCOL, _("GData plugin: Authenticated\n"));
316 if(!contacts_group_id)
318 query_for_contacts_group_id(authorizer);
321 GDataContactsService *service;
322 service = gdata_contacts_service_new(GDATA_AUTHORIZER(authorizer));
323 query_after_auth(service);
324 g_object_unref(service);
328 static void cm_gdata_auth_ready(GDataContactsService *service, GAsyncResult *res, gpointer data)
330 GError *error = NULL;
332 if(!gdata_service_authenticate_finish(GDATA_SERVICE(service), res, &error))
334 log_error(LOG_PROTOCOL, _("GData plugin: Authentication error: %s\n"), error->message);
336 cm_gdata_contacts_query_running = FALSE;
340 log_message(LOG_PROTOCOL, _("GData plugin: Authenticated\n"));
342 query_after_auth(service);
348 #ifdef HAVE_GDATA_VERSION_0_9
349 GDataClientLoginAuthorizer *authorizer;
351 GDataContactsService *service;
354 if(cm_gdata_contacts_query_running)
356 debug_print("GData plugin: Network query already in progress");
360 log_message(LOG_PROTOCOL, _("GData plugin: Starting async authentication\n"));
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;
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);
374 #ifdef HAVE_GDATA_VERSION_0_9
375 g_object_unref(authorizer);
377 g_object_unref(service);
383 static void add_contacts_to_list(GList **address_list, GSList *contacts)
387 for(walk = contacts; walk; walk = walk->next)
390 Contact *contact = walk->data;
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;
397 *address_list = g_list_prepend(*address_list, ae);
398 addr_compl_add_address1(ae->address, ae);
400 if(contact->given_name && *(contact->given_name) != '\0')
401 addr_compl_add_address1(contact->given_name, ae);
403 if(contact->family_name && *(contact->family_name) != '\0')
404 addr_compl_add_address1(contact->family_name, ae);
408 void cm_gdata_add_contacts(GList **address_list)
410 add_contacts_to_list(address_list, contacts_cache.contacts);
413 gboolean cm_gdata_update_contacts_cache(void)
415 if(prefs_common.work_offline)
417 debug_print("GData plugin: Offline mode\n");
419 else if(!cm_gdata_config.username || *(cm_gdata_config.username) == '\0' || !cm_gdata_config.password)
421 /* noop if no credentials are given */
422 debug_print("GData plugin: Empty username or password\n");
426 debug_print("GData plugin: Querying contacts");
432 void cm_gdata_contacts_done(void)
434 g_free(contacts_group_id);
435 contacts_group_id = NULL;
437 write_cache_to_file();
438 if(contacts_cache.contacts && !claws_is_exiting())
439 clear_contacts_cache();
442 void cm_gdata_load_contacts_cache_from_file(void)
445 GNode *rootnode, *childnode, *contactnode;
448 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, GDATA_CONTACTS_FILENAME, NULL);
449 if(!is_file_exist(path)) {
454 /* no merging; make sure the cache is empty (this should be a noop, but just to be safe...) */
455 clear_contacts_cache();
457 rootnode = xml_parse_file(path);
461 xmlnode = rootnode->data;
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);
470 for(childnode = rootnode->children; childnode; childnode = childnode->next) {
472 xmlnode = childnode->data;
474 if(strcmp2(xmlnode->tag->tag, "contacts") != 0)
477 for(contactnode = childnode->children; contactnode; contactnode = contactnode->next)
479 Contact *cached_contact;
481 xmlnode = contactnode->data;
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;
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);
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);
505 xml_free_tree(rootnode);
507 contacts_cache.contacts = g_slist_reverse(contacts_cache.contacts);