]> git.0d.be Git - empathy.git/blob - src/empathy-import-dialog.c
Be more careful in import_dialog_account_data_free
[empathy.git] / src / empathy-import-dialog.c
1 /*
2  * Copyright (C) 2008 Collabora Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (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 GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>
20  * */
21
22 #include <config.h>
23
24 #include <string.h>
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <glade/glade.h>
29 #include <glib/gi18n.h>
30 #include <glib/gstdio.h>
31
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34
35 #include <libmissioncontrol/mc-account.h>
36 #include <telepathy-glib/util.h>
37
38 #include "empathy-import-dialog.h"
39
40 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
41 #include <libempathy/empathy-debug.h>
42 #include <libempathy/empathy-utils.h>
43
44 #include <libempathy-gtk/empathy-ui-utils.h>
45
46 /* Pidgin to MC map */
47 typedef struct
48 {
49   gchar *protocol;
50   gchar *pidgin_name;
51   gchar *mc_name;
52 } PidginMcMapItem;
53
54 static PidginMcMapItem pidgin_mc_map[] =
55 {
56   { "msn", "server", "server" },
57   { "msn", "port", "port" },
58
59   { "jabber", "connect_server", "server" },
60   { "jabber", "port", "port" },
61   { "jabber", "require_tls", "require-encryption" },
62   { "jabber", "old_ssl", "old-ssl" },
63
64   { "aim", "server", "server" },
65   { "aim", "port", "port" },
66
67   { "salut", "first", "first-name" },
68   { "salut", "last", "last-name" },
69   { "salut", "jid", "jid" },
70   { "salut", "email", "email" },
71
72   { "groupwise", "server", "server" },
73   { "groupwise", "port", "port" },
74
75   { "icq", "server", "server" },
76   { "icq", "port", "port" },
77
78   { "irc", "realname", "fullname" },
79   { "irc", "ssl", "use-ssl" },
80   { "irc", "port", "port" },
81
82   { "yahoo", "server", "server" },
83   { "yahoo", "port", "port" },
84   { "yahoo", "xfer_port", "xfer-port" },
85   { "yahoo", "ignore_invites", "ignore-invites" },
86   { "yahoo", "yahoojp", "yahoojp" },
87   { "yahoo", "xferjp_host", "xferjp-host" },
88   { "yahoo", "serverjp", "serverjp" },
89   { "yahoo", "xfer_host", "xfer-host" },
90 };
91
92 typedef struct
93 {
94   GHashTable *settings;
95   McProfile *profile;
96 } AccountData;
97
98 typedef struct
99 {
100   GtkWidget *window;
101   GtkWidget *treeview;
102   GtkWidget *button_ok;
103   GtkWidget *button_cancel;
104   gboolean not_imported;
105   GList *accounts;
106 } EmpathyImportDialog;
107
108 #define PIDGIN_ACCOUNT_TAG_NAME "name"
109 #define PIDGIN_ACCOUNT_TAG_ACCOUNT "account"
110 #define PIDGIN_ACCOUNT_TAG_PROTOCOL "protocol"
111 #define PIDGIN_ACCOUNT_TAG_PASSWORD "password"
112 #define PIDGIN_ACCOUNT_TAG_SETTINGS "settings"
113 #define PIDGIN_SETTING_PROP_TYPE "type"
114 #define PIDGIN_PROTOCOL_BONJOUR "bonjour"
115 #define PIDGIN_PROTOCOL_NOVELL "novell"
116
117 enum
118 {
119   COL_IMPORT = 0,
120   COL_PROTOCOL,
121   COL_NAME,
122   COL_SOURCE,
123   COL_ACCOUNT_DATA,
124   COL_COUNT
125 };
126
127
128 static void import_dialog_add_setting (GHashTable *settings,
129     gchar *key, gpointer value, EmpathyImportSettingType  type);
130 static gboolean import_dialog_add_account (gchar *protocol_name,
131     GHashTable *settings);
132 static void import_dialog_pidgin_parse_setting (gchar *protocol,
133     xmlNodePtr setting, GHashTable *settings);
134 static void import_dialog_pidgin_import_accounts ();
135 static void import_dialog_button_ok_clicked_cb (GtkButton *button,
136     EmpathyImportDialog *dialog);
137 static void import_dialog_button_cancel_clicked_cb (GtkButton *button,
138     EmpathyImportDialog *dialog);
139
140 static void
141 import_dialog_account_data_free (AccountData *data)
142 {
143   if (data == NULL)
144     return;
145   if (data->profile != NULL)
146     g_object_unref (data->profile);
147   if (data->settings != NULL)
148     g_hash_table_destroy (data->settings);
149 }
150
151 static gboolean
152 import_dialog_add_account (AccountData *data)
153 {
154   McAccount *account;
155   GHashTableIter iter;
156   gpointer key, value;
157   gchar *display_name;
158   GValue *username;
159
160   account = mc_account_create (data->profile);
161
162   g_hash_table_iter_init (&iter, data->settings);
163   while (g_hash_table_iter_next (&iter, &key, &value))
164     {
165       const gchar *param = key;
166       GValue *gvalue = value;
167
168       switch (G_VALUE_TYPE (gvalue))
169         {
170           case G_TYPE_STRING:
171             DEBUG ("Set param '%s' to '%s' (string)",
172                 param, g_value_get_string (gvalue));
173             mc_account_set_param_string (account,
174                 param, g_value_get_string (gvalue));
175             break;
176
177           case G_TYPE_BOOLEAN:
178             DEBUG ("Set param '%s' to %s (boolean)",
179                 param, g_value_get_boolean (gvalue) ? "TRUE" : "FALSE");
180             mc_account_set_param_boolean (account,
181                 param, g_value_get_boolean (gvalue));
182             break;
183
184           case G_TYPE_INT:
185             DEBUG ("Set param '%s' to '%i' (integer)",
186                 param, g_value_get_int (gvalue));
187             mc_account_set_param_int (account,
188                 param, g_value_get_int (gvalue));
189             break;
190         }
191     }
192
193   /* Set the display name of the account */
194   username = g_hash_table_lookup (data->settings, "account");
195   display_name = g_strdup_printf ("%s (%s)",
196       mc_profile_get_display_name (data->profile),
197       g_value_get_string (username));
198   mc_account_set_display_name (account, display_name);
199
200   g_free (display_name);
201   g_object_unref (account);
202
203   return TRUE;
204 }
205
206 static void
207 import_dialog_pidgin_parse_setting (AccountData *data,
208                                     xmlNodePtr setting)
209 {
210   PidginMcMapItem *item = NULL;
211   gchar *tag_name;
212   gchar *type = NULL;
213   gchar *content;
214   gint i;
215   GValue *value = NULL;
216
217   /* We can't do anything if the setting don't have a name */
218   tag_name = (gchar *) xmlGetProp (setting, PIDGIN_ACCOUNT_TAG_NAME);
219   if (!tag_name)
220     return;
221
222   /* Search for the map corresponding to setting we are parsing */
223   for (i = 0; i < G_N_ELEMENTS (pidgin_mc_map); i++)
224     {
225       if (!tp_strdiff (mc_profile_get_protocol_name (data->profile),
226             pidgin_mc_map[i].protocol) &&
227           !tp_strdiff (tag_name, pidgin_mc_map[i].pidgin_name))
228         {
229           item = pidgin_mc_map + i;
230           break;
231         }
232     }
233   g_free (tag_name);
234
235   /* If we didn't find the item, there is nothing we can do */
236   if (!item)
237     return;
238
239   type = (gchar *) xmlGetProp (setting, PIDGIN_SETTING_PROP_TYPE);
240   content = (gchar *) xmlNodeGetContent (setting);
241
242   if (!tp_strdiff (type, "bool"))
243     {
244       i = (gint) g_ascii_strtod (content, NULL);
245       value = tp_g_value_slice_new (G_TYPE_BOOLEAN);
246       g_value_set_boolean (value, i != 0);
247     }
248   else if (!tp_strdiff (type, "int"))
249     {
250       i = (gint) g_ascii_strtod (content, NULL);
251       value = tp_g_value_slice_new (G_TYPE_INT);
252       g_value_set_int (value, i);
253     }
254   else if (!tp_strdiff (type, "string"))
255     {
256       value = tp_g_value_slice_new (G_TYPE_STRING);
257       g_value_set_string (value, content);
258     }
259
260   if (value)
261     g_hash_table_insert (data->settings, item->mc_name, value);
262
263   g_free (type);
264   g_free (content);
265 }
266
267 static GList *
268 import_dialog_pidgin_load (void)
269 {
270   xmlNodePtr rootnode, node, child, setting;
271   xmlParserCtxtPtr ctxt;
272   xmlDocPtr doc;
273   gchar *filename;
274   GList *accounts = NULL;
275
276   /* Load pidgin accounts xml */
277   ctxt = xmlNewParserCtxt ();
278   filename = g_build_filename (g_get_home_dir (), ".purple", "accounts.xml",
279       NULL);
280
281   if (g_access (filename, R_OK) != 0)
282     goto FILENAME;
283
284   doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
285
286   rootnode = xmlDocGetRootElement (doc);
287   if (rootnode == NULL)
288     goto OUT;
289
290   for (node = rootnode->children; node; node = node->next)
291     {
292       AccountData *data;
293
294       /* If it is not an account node, skip. */
295       if (tp_strdiff ((gchar *) node->name, PIDGIN_ACCOUNT_TAG_ACCOUNT))
296         continue;
297
298       /* Create account data struct */
299       data = g_slice_new0 (AccountData);
300       data->settings = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
301         (GDestroyNotify) tp_g_value_slice_free);
302
303       /* Parse account's child nodes to fill the account data struct */
304       for (child = node->children; child; child = child->next)
305         {
306           GValue *value;
307
308           /* Protocol */
309           if (!tp_strdiff ((gchar *) child->name,
310               PIDGIN_ACCOUNT_TAG_PROTOCOL))
311             {
312               const gchar *protocol;
313
314               protocol = (gchar *) xmlNodeGetContent (child);
315
316               if (g_str_has_prefix (protocol, "prpl-"))
317                 protocol += 5;
318
319               if (!tp_strdiff (protocol, PIDGIN_PROTOCOL_BONJOUR))
320                 protocol = "salut";
321               else if (!tp_strdiff (protocol, PIDGIN_PROTOCOL_NOVELL))
322                 protocol = "groupwise";
323
324               data->profile = mc_profile_lookup (protocol);
325             }
326
327           /* Username and IRC server. */
328           else if (!tp_strdiff ((gchar *) child->name,
329               PIDGIN_ACCOUNT_TAG_NAME))
330             {
331               gchar *name;
332               GStrv name_resource = NULL;
333               GStrv nick_server = NULL;
334               const gchar *username;
335
336               name = (gchar *) xmlNodeGetContent (child);
337
338               /* Split "username/resource" */
339               if (g_strrstr (name, "/") != NULL)
340                 {
341                   name_resource = g_strsplit (name, "/", 2);
342                   username = name_resource[0];
343                 }
344               else
345                 username = name;
346
347              /* Split "username@server" if it is an IRC account */
348              if (strstr (name, "@") && !tp_strdiff (
349                    mc_profile_get_protocol_name (data->profile), "irc"))
350               {
351                 nick_server = g_strsplit (name, "@", 2);
352                 username = nick_server[0];
353
354                 /* Add the server setting */
355                 value = tp_g_value_slice_new (G_TYPE_STRING);
356                 g_value_set_string (value, nick_server[1]);
357                 g_hash_table_insert (data->settings, "server", value);
358               }
359
360               /* Add the account setting */
361               value = tp_g_value_slice_new (G_TYPE_STRING);
362               g_value_set_string (value, username);
363               g_hash_table_insert (data->settings, "account", value);
364
365               g_strfreev (name_resource);
366               g_strfreev (nick_server);
367               g_free (name);
368             }
369
370           /* Password */
371           else if (!tp_strdiff ((gchar *) child->name,
372               PIDGIN_ACCOUNT_TAG_PASSWORD))
373             {
374               gchar *password;
375
376               password = (gchar *) xmlNodeGetContent (child);
377
378               /* Add the password setting */
379               value = tp_g_value_slice_new (G_TYPE_STRING);
380               g_value_set_string (value, password);
381               g_hash_table_insert (data->settings, "password", value);
382
383               g_free (password);
384             }
385
386           /* Other settings */
387           else if (!tp_strdiff ((gchar *) child->name,
388               PIDGIN_ACCOUNT_TAG_SETTINGS))
389               for (setting = child->children; setting; setting = setting->next)
390                 import_dialog_pidgin_parse_setting (data, setting);
391         }
392
393       /* If we have the needed settings, add the account data to the list,
394        * otherwise free the data */
395       if (g_hash_table_size (data->settings) > 0)
396         accounts = g_list_prepend (accounts, data);
397       else
398         import_dialog_account_data_free (data);
399     }
400
401 OUT:
402   xmlFreeDoc(doc);
403   xmlFreeParserCtxt (ctxt);
404
405 FILENAME:
406   g_free (filename);
407
408   return accounts;
409 }
410
411 static gboolean
412 import_dialog_tree_model_foreach (GtkTreeModel *model,
413                                   GtkTreePath *path,
414                                   GtkTreeIter *iter,
415                                   gpointer user_data)
416 {
417   EmpathyImportDialog *dialog = (EmpathyImportDialog *) user_data;
418   gboolean to_import;
419   AccountData *data;
420
421   gtk_tree_model_get (model, iter,
422       COL_IMPORT, &to_import,
423       COL_ACCOUNT_DATA, &data,
424       -1);
425
426   if (to_import)
427     if (!import_dialog_add_account (data))
428       dialog->not_imported = TRUE;
429
430   import_dialog_account_data_free (data);
431   return FALSE;
432 }
433
434 static void
435 import_dialog_free (EmpathyImportDialog *dialog)
436 {
437   if (dialog->window)
438     gtk_widget_destroy (dialog->window);
439   g_list_free (dialog->accounts);
440   g_slice_free (EmpathyImportDialog, dialog);
441 }
442
443 static void
444 import_dialog_button_ok_clicked_cb (GtkButton *button,
445                                     EmpathyImportDialog *dialog)
446 {
447   GtkTreeModel *model;
448   GtkWidget *message;
449   GtkWindow *window;
450
451   model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview));
452
453   gtk_tree_model_foreach (model, import_dialog_tree_model_foreach, dialog);
454
455   window = gtk_window_get_transient_for (GTK_WINDOW (dialog->window));
456
457   import_dialog_free (dialog);
458
459   if (!dialog->not_imported)
460     return;
461
462   message = gtk_message_dialog_new (window,
463       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
464       _("One or more accounts failed to import."));
465
466   gtk_dialog_run (GTK_DIALOG (message));
467   gtk_widget_destroy (message);
468 }
469
470 static void
471 import_dialog_button_cancel_clicked_cb (GtkButton *button,
472                                         EmpathyImportDialog *dialog)
473 {
474   import_dialog_free (dialog);
475 }
476
477 static gboolean
478 import_dialog_account_id_in_list (GList *accounts,
479                                   const gchar *account_id)
480 {
481   GList *l;
482
483   for (l = accounts; l; l = l->next)
484     {
485       McAccount *account = l->data;
486       gchar *value;
487       gboolean result;
488
489       if (mc_account_get_param_string (account, "account", &value)
490           == MC_ACCOUNT_SETTING_ABSENT)
491         continue;
492
493       result = tp_strdiff (value, account_id);
494
495       g_free (value);
496
497       if (!result)
498         return TRUE;
499     }
500
501   return FALSE;
502 }
503
504 static void
505 import_dialog_add_accounts_to_model (EmpathyImportDialog *dialog)
506 {
507   GtkTreeModel *model;
508   GtkTreeIter iter;
509   GList *account;
510
511   model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview));
512
513   for (account = dialog->accounts; account; account = account->next)
514     {
515       GValue *value;
516       AccountData *data = (AccountData *) account->data;
517       gboolean import;
518       GList *accounts;
519
520       value = g_hash_table_lookup (data->settings, "account");
521
522       accounts = mc_accounts_list_by_profile (data->profile);
523
524       /* Only set the "Import" cell to be active if there isn't already an
525        * account set up with the same account id. */
526       import = !import_dialog_account_id_in_list (accounts,
527           g_value_get_string (value));
528
529       mc_accounts_list_free (accounts);
530
531       gtk_list_store_append (GTK_LIST_STORE (model), &iter);
532
533       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
534           COL_IMPORT, import,
535           COL_PROTOCOL, mc_profile_get_display_name (data->profile),
536           COL_NAME, g_value_get_string (value),
537           COL_SOURCE, "Pidgin",
538           COL_ACCOUNT_DATA, data,
539           -1);
540     }
541 }
542
543 static void
544 import_dialog_cell_toggled_cb (GtkCellRendererToggle *cell_renderer,
545                                const gchar *path_str,
546                                EmpathyImportDialog *dialog)
547 {
548   GtkTreeModel *model;
549   GtkTreeIter iter;
550   GtkTreePath *path;
551
552   path = gtk_tree_path_new_from_string (path_str);
553   model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview));
554
555   gtk_tree_model_get_iter (model, &iter, path);
556
557   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
558       COL_IMPORT, !gtk_cell_renderer_toggle_get_active (cell_renderer),
559       -1);
560
561   gtk_tree_path_free (path);
562 }
563
564 static void
565 import_dialog_set_up_account_list (EmpathyImportDialog *dialog)
566 {
567   GtkListStore *store;
568   GtkTreeView *view;
569   GtkTreeViewColumn *column;
570   GtkCellRenderer *cell;
571
572   store = gtk_list_store_new (COL_COUNT, G_TYPE_BOOLEAN, G_TYPE_STRING,
573       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
574
575   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
576       GTK_TREE_MODEL (store));
577
578   g_object_unref (store);
579
580   view = GTK_TREE_VIEW (dialog->treeview);
581   gtk_tree_view_set_headers_visible (view, TRUE);
582
583   /* Import column */
584   cell = gtk_cell_renderer_toggle_new ();
585   gtk_tree_view_insert_column_with_attributes (view, -1,
586       _("Import"), cell,
587       "active", COL_IMPORT,
588       NULL);
589
590   g_signal_connect (cell, "toggled",
591       G_CALLBACK (import_dialog_cell_toggled_cb), dialog);
592
593   /* Protocol column */
594   column = gtk_tree_view_column_new ();
595   gtk_tree_view_column_set_title (column, _("Protocol"));
596   gtk_tree_view_column_set_expand (column, TRUE);
597   gtk_tree_view_append_column (view, column);
598
599   cell = gtk_cell_renderer_text_new ();
600   g_object_set (cell,
601       "editable", FALSE,
602       NULL);
603   gtk_tree_view_column_pack_start (column, cell, TRUE);
604   gtk_tree_view_column_add_attribute (column, cell, "text", COL_PROTOCOL);
605
606   /* Account column */
607   column = gtk_tree_view_column_new ();
608   gtk_tree_view_column_set_title (column, _("Account"));
609   gtk_tree_view_column_set_expand (column, TRUE);
610   gtk_tree_view_append_column (view, column);
611
612   cell = gtk_cell_renderer_text_new ();
613   g_object_set (cell,
614       "editable", FALSE,
615       NULL);
616   gtk_tree_view_column_pack_start (column, cell, TRUE);
617   gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME);
618
619   /* Source column */
620   column = gtk_tree_view_column_new ();
621   gtk_tree_view_column_set_title (column, _("Source"));
622   gtk_tree_view_column_set_expand (column, TRUE);
623   gtk_tree_view_append_column (view, column);
624
625   cell = gtk_cell_renderer_text_new ();
626   g_object_set (cell,
627       "editable", FALSE,
628       NULL);
629   gtk_tree_view_column_pack_start (column, cell, TRUE);
630   gtk_tree_view_column_add_attribute (column, cell, "text", COL_SOURCE);
631
632   import_dialog_add_accounts_to_model (dialog);
633 }
634
635 void
636 empathy_import_dialog_show (GtkWindow *parent,
637                             gboolean warning)
638 {
639   static EmpathyImportDialog *dialog = NULL;
640   GladeXML *glade;
641   gchar *filename;
642
643   if (dialog)
644     {
645       gtk_window_present (GTK_WINDOW (dialog->window));
646       return;
647     }
648
649   dialog = g_slice_new0 (EmpathyImportDialog);
650
651   dialog->accounts = import_dialog_pidgin_load ();
652
653   if (!dialog->accounts)
654     {
655       GtkWidget *message;
656
657       if (warning)
658         {
659           message = gtk_message_dialog_new (parent,
660               GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
661               _("No accounts to import could be found. Empathy currently only "
662                 "supports importing accounts from Pidgin."));
663
664           gtk_dialog_run (GTK_DIALOG (message));
665           gtk_widget_destroy (message);
666         }
667       else
668         DEBUG ("No accounts to import; closing dialog silently.");
669
670       import_dialog_free (dialog);
671       dialog = NULL;
672       return;
673     }
674
675   filename = empathy_file_lookup ("empathy-import-dialog.glade", "src");
676   glade = empathy_glade_get_file (filename,
677       "import_dialog",
678       NULL,
679       "import_dialog", &dialog->window,
680       "treeview", &dialog->treeview,
681       NULL);
682
683   empathy_glade_connect (glade,
684       dialog,
685       "button_ok", "clicked", import_dialog_button_ok_clicked_cb,
686       "button_cancel", "clicked", import_dialog_button_cancel_clicked_cb,
687       NULL);
688
689   g_object_add_weak_pointer (G_OBJECT (dialog->window), (gpointer) &dialog);
690
691   g_free (filename);
692   g_object_unref (glade);
693
694   if (parent)
695     gtk_window_set_transient_for (GTK_WINDOW (dialog->window), parent);
696
697   import_dialog_set_up_account_list (dialog);
698
699   gtk_widget_show (dialog->window);
700 }