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 }