evolution-3.6.4/plugins/bbdb/gaimbuddies.c

No issues found

  1 /*
  2  *  Routines to copy information from a Gaim buddy list into an
  3  *  Evolution addressbook.
  4  *
  5  *  I currently copy IM account names and buddy icons, provided you
  6  *  don't already have a buddy icon defined for a person.
  7  *
  8  *  This works today (25 October 2004), but is pretty sure to break
  9  *  later on as the Gaim buddylist file format shifts.
 10  *
 11  * This program is free software; you can redistribute it and/or
 12  * modify it under the terms of the GNU Lesser General Public
 13  * License as published by the Free Software Foundation; either
 14  * version 2 of the License, or (at your option) version 3.
 15  *
 16  * This program is distributed in the hope that it will be useful,
 17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19  * Lesser General Public License for more details.
 20  *
 21  * You should have received a copy of the GNU Lesser General Public
 22  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 23  *
 24  *
 25  * Authors:
 26  *		Nat Friedman <nat@novell.com>
 27  *
 28  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 29  *
 30  */
 31 
 32 #ifdef HAVE_CONFIG_H
 33 #include <config.h>
 34 #endif
 35 
 36 #include <libxml/tree.h>
 37 #include <libxml/parser.h>
 38 #include <libxml/xmlmemory.h>
 39 #include <libevolution-utils/e-xml-utils.h>
 40 
 41 #include <gtk/gtk.h>
 42 #include <glib/gi18n.h>
 43 #include <string.h>
 44 
 45 #include <libedataserverui/libedataserverui.h>
 46 
 47 #include <sys/time.h>
 48 #include <sys/stat.h>
 49 
 50 #include <e-util/e-config.h>
 51 
 52 #include "bbdb.h"
 53 
 54 typedef struct {
 55 	gchar *account_name;
 56 	gchar *proto;
 57 	gchar *alias;
 58 	gchar *icon;
 59 } GaimBuddy;
 60 
 61 /* Forward declarations for this file. */
 62 static gboolean	bbdb_merge_buddy_to_contact	(EBookClient *client,
 63 						 GaimBuddy *buddy,
 64 						 EContact *contact);
 65 static GList *	bbdb_get_gaim_buddy_list	(void);
 66 static gchar *	get_node_text			(xmlNodePtr node);
 67 static gchar *	get_buddy_icon_from_setting	(xmlNodePtr setting);
 68 static void	free_buddy_list			(GList *blist);
 69 static void	parse_buddy_group		(xmlNodePtr group,
 70 						 GList **buddies,
 71 						 GSList *blocked);
 72 static EContactField
 73 		proto_to_contact_field		(const gchar *proto);
 74 
 75 static gchar *
 76 get_buddy_filename (void)
 77 {
 78 	return g_build_filename (
 79 		g_get_home_dir (), ".purple", "blist.xml", NULL);
 80 }
 81 
 82 static gchar *
 83 get_md5_as_string (const gchar *filename)
 84 {
 85 	GMappedFile *mapped_file;
 86 	const gchar *contents;
 87 	gchar *digest;
 88 	gsize length;
 89 	GError *error = NULL;
 90 
 91 	g_return_val_if_fail (filename != NULL, NULL);
 92 
 93 	mapped_file = g_mapped_file_new (filename, FALSE, &error);
 94 	if (mapped_file == NULL) {
 95 		g_warning ("%s", error->message);
 96 		return NULL;
 97 	}
 98 
 99 	contents = g_mapped_file_get_contents (mapped_file);
100 	length = g_mapped_file_get_length (mapped_file);
101 
102 	digest = g_compute_checksum_for_data (
103 		G_CHECKSUM_MD5, (guchar *) contents, length);
104 
105 	g_mapped_file_unref (mapped_file);
106 
107 	return digest;
108 }
109 
110 void
111 bbdb_sync_buddy_list_check (void)
112 {
113 	struct stat statbuf;
114 	time_t last_sync_time;
115 	gchar *md5;
116 	gchar *blist_path;
117 	gchar *last_sync_str;
118 	GSettings *settings = g_settings_new (CONF_SCHEMA);
119 
120 	blist_path = get_buddy_filename ();
121 	if (stat (blist_path, &statbuf) < 0) {
122 		g_free (blist_path);
123 		return;
124 	}
125 
126 	/* Reprocess the buddy list if it's been updated. */
127 	last_sync_str = g_settings_get_string (settings, CONF_KEY_GAIM_LAST_SYNC_TIME);
128 	if (last_sync_str == NULL || !strcmp ((const gchar *) last_sync_str, ""))
129 		last_sync_time = (time_t) 0;
130 	else
131 		last_sync_time = (time_t) g_ascii_strtoull (last_sync_str, NULL, 10);
132 
133 	g_free (last_sync_str);
134 
135 	if (statbuf.st_mtime <= last_sync_time) {
136 		g_object_unref (G_OBJECT (settings));
137 		g_free (blist_path);
138 		return;
139 	}
140 
141 	last_sync_str = g_settings_get_string (
142 		settings, CONF_KEY_GAIM_LAST_SYNC_MD5);
143 
144 	g_object_unref (settings);
145 
146 	md5 = get_md5_as_string (blist_path);
147 
148 	if (!last_sync_str || !*last_sync_str || !g_str_equal (md5, last_sync_str)) {
149 		fprintf (stderr, "bbdb: Buddy list has changed since last sync.\n");
150 
151 		bbdb_sync_buddy_list ();
152 	}
153 
154 	g_free (last_sync_str);
155 	g_free (blist_path);
156 	g_free (md5);
157 }
158 
159 static gboolean
160 store_last_sync_idle_cb (gpointer data)
161 {
162 	GSettings *settings;
163 	gchar *md5;
164 	gchar *blist_path = get_buddy_filename ();
165 	time_t last_sync;
166 	gchar *last_sync_time;
167 
168 	time (&last_sync);
169 	last_sync_time = g_strdup_printf ("%ld", (glong) last_sync);
170 
171 	md5 = get_md5_as_string (blist_path);
172 
173 	settings = g_settings_new (CONF_SCHEMA);
174 	g_settings_set_string (
175 		settings, CONF_KEY_GAIM_LAST_SYNC_TIME, last_sync_time);
176 	g_settings_set_string (
177 		settings, CONF_KEY_GAIM_LAST_SYNC_MD5, md5);
178 
179 	g_object_unref (G_OBJECT (settings));
180 
181 	g_free (last_sync_time);
182 	g_free (blist_path);
183 	g_free (md5);
184 
185 	return FALSE;
186 }
187 
188 static gboolean syncing = FALSE;
189 G_LOCK_DEFINE_STATIC (syncing);
190 
191 struct sync_thread_data
192 {
193 	GList *blist;
194 	EBookClient *client;
195 };
196 
197 static gpointer
198 bbdb_sync_buddy_list_in_thread (gpointer data)
199 {
200 	GList *l;
201 	struct sync_thread_data *std = data;
202 
203 	g_return_val_if_fail (std != NULL, NULL);
204 
205 	if (!bbdb_open_book_client (std->client)) {
206 		/* client got freed in bbdb_open_book_client on a failure */
207 		free_buddy_list (std->blist);
208 		g_free (std);
209 
210 		G_LOCK (syncing);
211 		syncing = FALSE;
212 		G_UNLOCK (syncing);
213 
214 		return NULL;
215 	}
216 
217 	printf ("bbdb: Synchronizing buddy list to contacts...\n");
218 	/* Walk the buddy list */
219 	for (l = std->blist; l != NULL; l = l->next) {
220 		GaimBuddy *b = l->data;
221 		EBookQuery *query;
222 		gchar *query_string, *uid;
223 		GSList *contacts = NULL;
224 		GError *error = NULL;
225 		EContact *c;
226 
227 		if (b->alias == NULL || strlen (b->alias) == 0) {
228 			g_free (b->alias);
229 			b->alias = g_strdup (b->account_name);
230 		}
231 
232 		/* Look for an exact match full name == buddy alias */
233 		query = e_book_query_field_test (
234 			E_CONTACT_FULL_NAME, E_BOOK_QUERY_IS, b->alias);
235 		query_string = e_book_query_to_string (query);
236 		e_book_query_unref (query);
237 		if (!e_book_client_get_contacts_sync (
238 			std->client, query_string, &contacts, NULL, NULL)) {
239 			g_free (query_string);
240 			continue;
241 		}
242 
243 		g_free (query_string);
244 
245 		if (contacts != NULL) {
246 
247 			/* FIXME: If there's more than one contact with this
248 			 * name, just give up; we're not smart enough for
249 			 * this. */
250 			if (contacts->next != NULL) {
251 				e_client_util_free_object_slist (contacts);
252 				continue;
253 			}
254 
255 			c = E_CONTACT (contacts->data);
256 
257 			if (!bbdb_merge_buddy_to_contact (std->client, b, c)) {
258 				e_client_util_free_object_slist (contacts);
259 				continue;
260 			}
261 
262 			/* Write it out to the addressbook */
263 			if (!e_book_client_modify_contact_sync (std->client, c, NULL, &error)) {
264 				g_warning ("bbdb: Could not modify contact: %s", error->message);
265 				g_error_free (error);
266 			}
267 			e_client_util_free_object_slist (contacts);
268 			continue;
269 		}
270 
271 		/* Otherwise, create a new contact. */
272 		c = e_contact_new ();
273 		e_contact_set (c, E_CONTACT_FULL_NAME, (gpointer) b->alias);
274 		if (!bbdb_merge_buddy_to_contact (std->client, b, c)) {
275 			g_object_unref (c);
276 			continue;
277 		}
278 
279 		uid = NULL;
280 		if (!e_book_client_add_contact_sync (std->client, c, &uid, NULL, &error)) {
281 			g_warning ("bbdb: Failed to add new contact: %s", error->message);
282 			g_error_free (error);
283 			goto finish;
284 		}
285 
286 		g_object_unref (c);
287 		g_free (uid);
288 	}
289 
290 	g_idle_add (store_last_sync_idle_cb, NULL);
291 
292  finish:
293 	printf ("bbdb: Done syncing buddy list to contacts.\n");
294 
295 	g_object_unref (std->client);
296 	free_buddy_list (std->blist);
297 	g_free (std);
298 
299 	G_LOCK (syncing);
300 	syncing = FALSE;
301 	G_UNLOCK (syncing);
302 
303 	return NULL;
304 }
305 
306 void
307 bbdb_sync_buddy_list (void)
308 {
309 	GList *blist;
310 	GError *error = NULL;
311 	EBookClient *client = NULL;
312 	struct sync_thread_data *std;
313 
314 	G_LOCK (syncing);
315 	if (syncing) {
316 		G_UNLOCK (syncing);
317 		printf ("bbdb: Already syncing buddy list, skipping this call\n");
318 		return;
319 	}
320 
321 	/* Get the Gaim buddy list */
322 	blist = bbdb_get_gaim_buddy_list ();
323 	if (blist == NULL) {
324 		G_UNLOCK (syncing);
325 		return;
326 	}
327 
328 	/* Open the addressbook */
329 	client = bbdb_create_book_client (GAIM_ADDRESSBOOK);
330 	if (client == NULL) {
331 		free_buddy_list (blist);
332 		G_UNLOCK (syncing);
333 		return;
334 	}
335 
336 	std = g_new0 (struct sync_thread_data, 1);
337 	std->blist = blist;
338 	std->client = client;
339 
340 	syncing = TRUE;
341 
342 	g_thread_create (bbdb_sync_buddy_list_in_thread, std, FALSE, &error);
343 	if (error) {
344 		g_warning (
345 			"%s: Creation of the thread failed with error: %s",
346 			G_STRFUNC, error->message);
347 		g_error_free (error);
348 
349 		G_UNLOCK (syncing);
350 		bbdb_sync_buddy_list_in_thread (std);
351 		G_LOCK (syncing);
352 	}
353 
354 	G_UNLOCK (syncing);
355 }
356 
357 static gboolean
358 im_list_contains_buddy (GList *ims,
359                         GaimBuddy *b)
360 {
361 	GList *l;
362 
363 	for (l = ims; l != NULL; l = l->next) {
364 		gchar *im = (gchar *) l->data;
365 
366 		if (!strcmp (im, b->account_name))
367 			return TRUE;
368 	}
369 
370 	return FALSE;
371 }
372 
373 static gboolean
374 bbdb_merge_buddy_to_contact (EBookClient *client,
375                              GaimBuddy *b,
376                              EContact *c)
377 {
378 	EContactField field;
379 	GList *ims;
380 	gboolean dirty = FALSE;
381 
382 	EContactPhoto *photo = NULL;
383 
384 	GError *error = NULL;
385 
386 	/* Set the IM account */
387 	field = proto_to_contact_field (b->proto);
388 	ims = e_contact_get (c, field);
389 	if (!im_list_contains_buddy (ims, b)) {
390 		ims = g_list_append (ims, g_strdup (b->account_name));
391 		e_contact_set (c, field, (gpointer) ims);
392 		dirty = TRUE;
393 	}
394 
395 	g_list_foreach (ims, (GFunc) g_free, NULL);
396 	g_list_free (ims);
397 	ims = NULL;
398 
399         /* Set the photo if it's not set */
400 	if (b->icon != NULL) {
401 		photo = e_contact_get (c, E_CONTACT_PHOTO);
402 		if (photo == NULL) {
403 			gchar *contents = NULL;
404 
405 			photo = g_new0 (EContactPhoto, 1);
406 			photo->type = E_CONTACT_PHOTO_TYPE_INLINED;
407 
408 			if (!g_file_get_contents (
409 				b->icon, &contents,
410 				&photo->data.inlined.length, &error)) {
411 				g_warning (
412 					"bbdb: Could not read buddy icon: "
413 					"%s\n", error->message);
414 				g_error_free (error);
415 				return dirty;
416 			}
417 
418 			photo->data.inlined.data = (guchar *) contents;
419 			e_contact_set (c, E_CONTACT_PHOTO, (gpointer) photo);
420 			dirty = TRUE;
421 		}
422 	}
423 
424 	/* Clean up */
425 	if (photo != NULL)
426 		e_contact_photo_free (photo);
427 
428 	return dirty;
429 }
430 
431 static EContactField
432 proto_to_contact_field (const gchar *proto)
433 {
434 	if (!strcmp (proto,  "prpl-oscar"))
435 		return E_CONTACT_IM_AIM;
436 	if (!strcmp (proto, "prpl-novell"))
437 		return E_CONTACT_IM_GROUPWISE;
438 	if (!strcmp (proto, "prpl-msn"))
439 		return E_CONTACT_IM_MSN;
440 	if (!strcmp (proto, "prpl-icq"))
441 		return E_CONTACT_IM_ICQ;
442 	if (!strcmp (proto, "prpl-yahoo"))
443 		return E_CONTACT_IM_YAHOO;
444 	if (!strcmp (proto, "prpl-jabber"))
445 		return E_CONTACT_IM_JABBER;
446 	if (!strcmp (proto, "prpl-gg"))
447 		return E_CONTACT_IM_GADUGADU;
448 
449 	return E_CONTACT_IM_AIM;
450 }
451 
452 static void
453 get_all_blocked (xmlNodePtr node,
454                  GSList **blocked)
455 {
456 	xmlNodePtr child;
457 
458 	if (!node || !blocked)
459 		return;
460 
461 	for (child = node->children; child; child = child->next) {
462 		if (child->children)
463 			get_all_blocked (child, blocked);
464 
465 		if (!strcmp ((const gchar *) child->name, "block")) {
466 			gchar *name = get_node_text (child);
467 
468 			if (name)
469 				*blocked = g_slist_prepend (*blocked, name);
470 		}
471 	}
472 }
473 
474 static GList *
475 bbdb_get_gaim_buddy_list (void)
476 {
477 	gchar *blist_path;
478 	xmlDocPtr buddy_xml;
479 	xmlNodePtr root, child, blist;
480 	GList *buddies = NULL;
481 	GSList *blocked = NULL;
482 
483 	blist_path = get_buddy_filename ();
484 
485 	buddy_xml = xmlParseFile (blist_path);
486 	g_free (blist_path);
487 	if (!buddy_xml) {
488 		fprintf (stderr, "bbdb: Could not open Pidgin buddy list.\n");
489 		return NULL;
490 	}
491 
492 	root = xmlDocGetRootElement (buddy_xml);
493 	if (strcmp ((const gchar *) root->name, "purple")) {
494 		fprintf (stderr, "bbdb: Could not parse Pidgin buddy list.\n");
495 		xmlFreeDoc (buddy_xml);
496 		return NULL;
497 	}
498 
499 	for (child = root->children; child != NULL; child = child->next) {
500 		if (!strcmp ((const gchar *) child->name, "privacy")) {
501 			get_all_blocked (child, &blocked);
502 			break;
503 		}
504 	}
505 
506 	blist = NULL;
507 	for (child = root->children; child != NULL; child = child->next) {
508 		if (!strcmp ((const gchar *) child->name, "blist")) {
509 			blist = child;
510 			break;
511 		}
512 	}
513 	if (blist == NULL) {
514 		fprintf (
515 			stderr, "bbdb: Could not find 'blist' "
516 			"element in Pidgin buddy list.\n");
517 		xmlFreeDoc (buddy_xml);
518 		return NULL;
519 	}
520 
521 	for (child = blist->children; child != NULL; child = child->next) {
522 		if (!strcmp ((const gchar *) child->name, "group"))
523 			parse_buddy_group (child, &buddies, blocked);
524 	}
525 
526 	xmlFreeDoc (buddy_xml);
527 
528 	g_slist_foreach (blocked, (GFunc) g_free, NULL);
529 	g_slist_free (blocked);
530 
531 	return buddies;
532 }
533 
534 static void
535 free_gaim_body (GaimBuddy *gb)
536 {
537 	if (!gb)
538 		return;
539 
540 	g_free (gb->icon);
541 	g_free (gb->alias);
542 	g_free (gb->account_name);
543 	g_free (gb->proto);
544 	g_free (gb);
545 }
546 
547 static void
548 free_buddy_list (GList *blist)
549 {
550 	g_list_foreach (blist, (GFunc) free_gaim_body, NULL);
551 	g_list_free (blist);
552 }
553 
554 static gchar *
555 get_node_text (xmlNodePtr node)
556 {
557 	if (node->children == NULL || node->children->content == NULL ||
558 	    strcmp ((gchar *) node->children->name, "text"))
559 		return NULL;
560 
561 	return g_strdup ((gchar *) node->children->content);
562 }
563 
564 static gchar *
565 get_buddy_icon_from_setting (xmlNodePtr setting)
566 {
567 	gchar *icon = NULL;
568 
569 	icon = get_node_text (setting);
570 	if (icon[0] != '/') {
571 		gchar *path;
572 
573 		path = g_build_path ("/", g_get_home_dir (), ".purple/icons", icon, NULL);
574 		g_free (icon);
575 		icon = path;
576 	}
577 
578 	return icon;
579 }
580 
581 static void
582 parse_contact (xmlNodePtr contact,
583                GList **buddies,
584                GSList *blocked)
585 {
586 	xmlNodePtr  child;
587 	xmlNodePtr  buddy = NULL;
588 	GaimBuddy  *gb;
589 	gboolean    is_blocked = FALSE;
590 
591 	for (child = contact->children; child != NULL; child = child->next) {
592 		if (!strcmp ((const gchar *) child->name, "buddy")) {
593 			buddy = child;
594 			break;
595 		}
596 	}
597 
598 	if (buddy == NULL) {
599 		fprintf (
600 			stderr, "bbdb: Could not find buddy in contact. "
601 			"Malformed Pidgin buddy list file.\n");
602 		return;
603 	}
604 
605 	gb = g_new0 (GaimBuddy, 1);
606 
607 	gb->proto = e_xml_get_string_prop_by_name (buddy, (const guchar *)"proto");
608 
609 	for (child = buddy->children; child != NULL && !is_blocked; child = child->next) {
610 		if (!strcmp ((const gchar *) child->name, "setting")) {
611 			gchar *setting_type;
612 
613 			setting_type = e_xml_get_string_prop_by_name (
614 				child, (const guchar *)"name");
615 
616 			if (!strcmp ((const gchar *) setting_type, "buddy_icon"))
617 				gb->icon = get_buddy_icon_from_setting (child);
618 
619 			g_free (setting_type);
620 		} else if (!strcmp ((const gchar *) child->name, "name")) {
621 			gb->account_name = get_node_text (child);
622 			is_blocked = g_slist_find_custom (
623 				blocked, gb->account_name,
624 				(GCompareFunc) strcmp) != NULL;
625 		} else if (!strcmp ((const gchar *) child->name, "alias"))
626 			gb->alias = get_node_text (child);
627 
628 	}
629 
630 	if (is_blocked)
631 		free_gaim_body (gb);
632 	else
633 		*buddies = g_list_prepend (*buddies, gb);
634 }
635 
636 static void
637 parse_buddy_group (xmlNodePtr group,
638                    GList **buddies,
639                    GSList *blocked)
640 {
641 	xmlNodePtr child;
642 
643 	for (child = group->children; child != NULL; child = child->next) {
644 		if (strcmp ((const gchar *) child->name, "contact"))
645 			continue;
646 
647 		parse_contact (child, buddies, blocked);
648 	}
649 }