]> git.0d.be Git - empathy.git/blob - src/empathy-import-dialog.c
755d056a599ca939a52d889826afc597a9dbfb95
[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   g_object_unref (data->profile);
144   g_hash_table_destroy (data->settings);
145 }
146
147 static gboolean
148 import_dialog_add_account (AccountData *data)
149 {
150   McAccount *account;
151   GHashTableIter iter;
152   gpointer key, value;
153   gchar *display_name;
154   GValue *username;
155
156   account = mc_account_create (data->profile);
157
158   g_hash_table_iter_init (&iter, data->settings);
159   while (g_hash_table_iter_next (&iter, &key, &value))
160     {
161       const gchar *param = key;
162       GValue *gvalue = value;
163
164       switch (G_VALUE_TYPE (gvalue))
165         {
166           case G_TYPE_STRING:
167             DEBUG ("Set param '%s' to '%s' (string)",
168                 param, g_value_get_string (gvalue));
169             mc_account_set_param_string (account,
170                 param, g_value_get_string (gvalue));
171             break;
172
173           case G_TYPE_BOOLEAN:
174             DEBUG ("Set param '%s' to %s (boolean)",
175                 param, g_value_get_boolean (gvalue) ? "TRUE" : "FALSE");
176             mc_account_set_param_boolean (account,
177                 param, g_value_get_boolean (gvalue));
178             break;
179
180           case G_TYPE_INT:
181             DEBUG ("Set param '%s' to '%i' (integer)",
182                 param, g_value_get_int (gvalue));
183             mc_account_set_param_int (account,
184                 param, g_value_get_int (gvalue));
185             break;
186         }
187     }
188
189   /* Set the display name of the account */
190   username = g_hash_table_lookup (data->settings, "account");
191   display_name = g_strdup_printf ("%s (%s)",
192       mc_profile_get_display_name (data->profile),
193       g_value_get_string (username));
194   mc_account_set_display_name (account, display_name);
195
196   g_free (display_name);
197   g_object_unref (account);
198
199   return TRUE;
200 }
201
202 static void
203 import_dialog_pidgin_parse_setting (AccountData *data,
204                                     xmlNodePtr setting)
205 {
206   PidginMcMapItem *item = NULL;
207   gchar *tag_name;
208   gchar *type = NULL;
209   gchar *content;
210   gint i;
211   GValue *value = NULL;
212
213   /* We can't do anything if the setting don't have a name */
214   tag_name = (gchar *) xmlGetProp (setting, PIDGIN_ACCOUNT_TAG_NAME);
215   if (!tag_name)
216     return;
217
218   /* Search for the map corresponding to setting we are parsing */
219   for (i = 0; i < G_N_ELEMENTS (pidgin_mc_map); i++)
220     {
221       if (!tp_strdiff (mc_profile_get_protocol_name (data->profile),
222             pidgin_mc_map[i].protocol) &&
223           !tp_strdiff (tag_name, pidgin_mc_map[i].pidgin_name))
224         {
225           item = pidgin_mc_map + i;
226           break;
227         }
228     }
229   g_free (tag_name);
230
231   /* If we didn't find the item, there is nothing we can do */
232   if (!item)
233     return;
234
235   type = (gchar *) xmlGetProp (setting, PIDGIN_SETTING_PROP_TYPE);
236   content = (gchar *) xmlNodeGetContent (setting);
237
238   if (!tp_strdiff (type, "bool"))
239     {
240       i = (gint) g_ascii_strtod (content, NULL);
241       value = tp_g_value_slice_new (G_TYPE_BOOLEAN);
242       g_value_set_boolean (value, i != 0);
243     }
244   else if (!tp_strdiff (type, "int"))
245     {
246       i = (gint) g_ascii_strtod (content, NULL);
247       value = tp_g_value_slice_new (G_TYPE_INT);
248       g_value_set_int (value, i);
249     }
250   else if (!tp_strdiff (type, "string"))
251     {
252       value = tp_g_value_slice_new (G_TYPE_STRING);
253       g_value_set_string (value, content);
254     }
255
256   if (value)
257     g_hash_table_insert (data->settings, item->mc_name, value);
258
259   g_free (type);
260   g_free (content);
261 }
262
263 static GList *
264 import_dialog_pidgin_load (void)
265 {
266   xmlNodePtr rootnode, node, child, setting;
267   xmlParserCtxtPtr ctxt;
268   xmlDocPtr doc;
269   gchar *filename;
270   GList *accounts = NULL;
271
272   /* Load pidgin accounts xml */
273   ctxt = xmlNewParserCtxt ();
274   filename = g_build_filename (g_get_home_dir (), ".purple", "accounts.xml",
275       NULL);
276
277   if (g_access (filename, R_OK) != 0)
278     goto FILENAME;
279
280   doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
281
282   rootnode = xmlDocGetRootElement (doc);
283   if (rootnode == NULL)
284     goto OUT;
285
286   for (node = rootnode->children; node; node = node->next)
287     {
288       AccountData *data;
289
290       /* If it is not an account node, skip. */
291       if (tp_strdiff ((gchar *) node->name, PIDGIN_ACCOUNT_TAG_ACCOUNT))
292         continue;
293
294       /* Create account data struct */
295       data = g_slice_new0 (AccountData);
296       data->settings = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
297         (GDestroyNotify) tp_g_value_slice_free);
298
299       /* Parse account's child nodes to fill the account data struct */
300       for (child = node->children; child; child = child->next)
301         {
302           GValue *value;
303
304           /* Protocol */
305           if (!tp_strdiff ((gchar *) child->name,
306               PIDGIN_ACCOUNT_TAG_PROTOCOL))
307             {
308               const gchar *protocol;
309
310               protocol = (gchar *) xmlNodeGetContent (child);
311
312               if (g_str_has_prefix (protocol, "prpl-"))
313                 protocol += 5;
314
315               if (!tp_strdiff (protocol, PIDGIN_PROTOCOL_BONJOUR))
316                 protocol = "salut";
317               else if (!tp_strdiff (protocol, PIDGIN_PROTOCOL_NOVELL))
318                 protocol = "groupwise";
319
320               data->profile = mc_profile_lookup (protocol);
321             }
322
323           /* Username and IRC server. */
324           else if (!tp_strdiff ((gchar *) child->name,
325               PIDGIN_ACCOUNT_TAG_NAME))
326             {
327               gchar *name;
328               GStrv name_resource = NULL;
329               GStrv nick_server = NULL;
330               const gchar *username;
331
332               name = (gchar *) xmlNodeGetContent (child);
333
334               /* Split "username/resource" */
335               if (g_strrstr (name, "/") != NULL)
336                 {
337                   name_resource = g_strsplit (name, "/", 2);
338                   username = name_resource[0];
339                 }
340               else
341                 username = name;
342
343              /* Split "username@server" if it is an IRC account */
344              if (strstr (name, "@") && !tp_strdiff (
345                    mc_profile_get_protocol_name (data->profile), "irc"))
346               {
347                 nick_server = g_strsplit (name, "@", 2);
348                 username = nick_server[0];
349
350                 /* Add the server setting */
351                 value = tp_g_value_slice_new (G_TYPE_STRING);
352                 g_value_set_string (value, nick_server[1]);
353                 g_hash_table_insert (data->settings, "server", value);
354               }
355
356               /* Add the account setting */
357               value = tp_g_value_slice_new (G_TYPE_STRING);
358               g_value_set_string (value, username);
359               g_hash_table_insert (data->settings, "account", value);
360
361               g_strfreev (name_resource);
362               g_strfreev (nick_server);
363               g_free (name);
364             }
365
366           /* Password */
367           else if (!tp_strdiff ((gchar *) child->name,
368               PIDGIN_ACCOUNT_TAG_PASSWORD))
369             {
370               gchar *password;
371
372               password = (gchar *) xmlNodeGetContent (child);
373
374               /* Add the password setting */
375               value = tp_g_value_slice_new (G_TYPE_STRING);
376               g_value_set_string (value, password);
377               g_hash_table_insert (data->settings, "password", value);
378
379               g_free (password);
380             }
381
382           /* Other settings */
383           else if (!tp_strdiff ((gchar *) child->name,
384               PIDGIN_ACCOUNT_TAG_SETTINGS))
385               for (setting = child->children; setting; setting = setting->next)
386                 import_dialog_pidgin_parse_setting (data, setting);
387         }
388
389       /* If we have the needed settings, add the account data to the list,
390        * otherwise free the data */
391       if (g_hash_table_size (data->settings) > 0)
392         accounts = g_list_prepend (accounts, data);
393       else
394         import_dialog_account_data_free (data);
395     }
396
397 OUT:
398   xmlFreeDoc(doc);
399   xmlFreeParserCtxt (ctxt);
400
401 FILENAME:
402   g_free (filename);
403
404   return accounts;
405 }
406
407 static gboolean
408 import_dialog_tree_model_foreach (GtkTreeModel *model,
409                                   GtkTreePath *path,
410                                   GtkTreeIter *iter,
411                                   gpointer user_data)
412 {
413   EmpathyImportDialog *dialog = (EmpathyImportDialog *) user_data;
414   gboolean to_import;
415   AccountData *data;
416
417   gtk_tree_model_get (model, iter,
418       COL_IMPORT, &to_import,
419       COL_ACCOUNT_DATA, &data,
420       -1);
421
422   if (to_import)
423     if (!import_dialog_add_account (data))
424       dialog->not_imported = TRUE;
425
426   import_dialog_account_data_free (data);
427   return FALSE;
428 }
429
430 static void
431 import_dialog_free (EmpathyImportDialog *dialog)
432 {
433   if (dialog->window)
434     gtk_widget_destroy (dialog->window);
435   g_list_free (dialog->accounts);
436   g_slice_free (EmpathyImportDialog, dialog);
437 }
438
439 static void
440 import_dialog_button_ok_clicked_cb (GtkButton *button,
441                                     EmpathyImportDialog *dialog)
442 {
443   GtkTreeModel *model;
444   GtkWidget *message;
445   GtkWindow *window;
446
447   model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview));
448
449   gtk_tree_model_foreach (model, import_dialog_tree_model_foreach, dialog);
450
451   window = gtk_window_get_transient_for (GTK_WINDOW (dialog->window));
452
453   import_dialog_free (dialog);
454
455   if (!dialog->not_imported)
456     return;
457
458   message = gtk_message_dialog_new (window,
459       GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
460       _("One or more accounts failed to import."));
461
462   gtk_dialog_run (GTK_DIALOG (message));
463   gtk_widget_destroy (message);
464 }
465
466 static void
467 import_dialog_button_cancel_clicked_cb (GtkButton *button,
468                                         EmpathyImportDialog *dialog)
469 {
470   import_dialog_free (dialog);
471 }
472
473 static gboolean
474 import_dialog_account_id_in_list (GList *accounts,
475                                   const gchar *account_id)
476 {
477   GList *l;
478
479   for (l = accounts; l; l = l->next)
480     {
481       McAccount *account = l->data;
482       gchar *value;
483       gboolean result;
484
485       if (mc_account_get_param_string (account, "account", &value)
486           == MC_ACCOUNT_SETTING_ABSENT)
487         continue;
488
489       result = tp_strdiff (value, account_id);
490
491       g_free (value);
492
493       if (!result)
494         return TRUE;
495     }
496
497   return FALSE;
498 }
499
500 static void
501 import_dialog_add_accounts_to_model (EmpathyImportDialog *dialog)
502 {
503   GtkTreeModel *model;
504   GtkTreeIter iter;
505   GList *account;
506
507   model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview));
508
509   for (account = dialog->accounts; account; account = account->next)
510     {
511       GValue *value;
512       AccountData *data = (AccountData *) account->data;
513       gboolean import;
514       GList *accounts;
515
516       value = g_hash_table_lookup (data->settings, "account");
517
518       accounts = mc_accounts_list_by_profile (data->profile);
519
520       /* Only set the "Import" cell to be active if there isn't already an
521        * account set up with the same account id. */
522       import = !import_dialog_account_id_in_list (accounts,
523           g_value_get_string (value));
524
525       mc_accounts_list_free (accounts);
526
527       gtk_list_store_append (GTK_LIST_STORE (model), &iter);
528
529       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
530           COL_IMPORT, import,
531           COL_PROTOCOL, mc_profile_get_display_name (data->profile),
532           COL_NAME, g_value_get_string (value),
533           COL_SOURCE, "Pidgin",
534           COL_ACCOUNT_DATA, data,
535           -1);
536     }
537 }
538
539 static void
540 import_dialog_cell_toggled_cb (GtkCellRendererToggle *cell_renderer,
541                                const gchar *path_str,
542                                EmpathyImportDialog *dialog)
543 {
544   GtkTreeModel *model;
545   GtkTreeIter iter;
546   GtkTreePath *path;
547
548   path = gtk_tree_path_new_from_string (path_str);
549   model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->treeview));
550
551   gtk_tree_model_get_iter (model, &iter, path);
552
553   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
554       COL_IMPORT, !gtk_cell_renderer_toggle_get_active (cell_renderer),
555       -1);
556
557   gtk_tree_path_free (path);
558 }
559
560 static void
561 import_dialog_set_up_account_list (EmpathyImportDialog *dialog)
562 {
563   GtkListStore *store;
564   GtkTreeView *view;
565   GtkTreeViewColumn *column;
566   GtkCellRenderer *cell;
567
568   store = gtk_list_store_new (COL_COUNT, G_TYPE_BOOLEAN, G_TYPE_STRING,
569       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
570
571   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
572       GTK_TREE_MODEL (store));
573
574   g_object_unref (store);
575
576   view = GTK_TREE_VIEW (dialog->treeview);
577   gtk_tree_view_set_headers_visible (view, TRUE);
578
579   /* Import column */
580   cell = gtk_cell_renderer_toggle_new ();
581   gtk_tree_view_insert_column_with_attributes (view, -1,
582       _("Import"), cell,
583       "active", COL_IMPORT,
584       NULL);
585
586   g_signal_connect (cell, "toggled",
587       G_CALLBACK (import_dialog_cell_toggled_cb), dialog);
588
589   /* Protocol column */
590   column = gtk_tree_view_column_new ();
591   gtk_tree_view_column_set_title (column, _("Protocol"));
592   gtk_tree_view_column_set_expand (column, TRUE);
593   gtk_tree_view_append_column (view, column);
594
595   cell = gtk_cell_renderer_text_new ();
596   g_object_set (cell,
597       "editable", FALSE,
598       NULL);
599   gtk_tree_view_column_pack_start (column, cell, TRUE);
600   gtk_tree_view_column_add_attribute (column, cell, "text", COL_PROTOCOL);
601
602   /* Account column */
603   column = gtk_tree_view_column_new ();
604   gtk_tree_view_column_set_title (column, _("Account"));
605   gtk_tree_view_column_set_expand (column, TRUE);
606   gtk_tree_view_append_column (view, column);
607
608   cell = gtk_cell_renderer_text_new ();
609   g_object_set (cell,
610       "editable", FALSE,
611       NULL);
612   gtk_tree_view_column_pack_start (column, cell, TRUE);
613   gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME);
614
615   /* Source column */
616   column = gtk_tree_view_column_new ();
617   gtk_tree_view_column_set_title (column, _("Source"));
618   gtk_tree_view_column_set_expand (column, TRUE);
619   gtk_tree_view_append_column (view, column);
620
621   cell = gtk_cell_renderer_text_new ();
622   g_object_set (cell,
623       "editable", FALSE,
624       NULL);
625   gtk_tree_view_column_pack_start (column, cell, TRUE);
626   gtk_tree_view_column_add_attribute (column, cell, "text", COL_SOURCE);
627
628   import_dialog_add_accounts_to_model (dialog);
629 }
630
631 void
632 empathy_import_dialog_show (GtkWindow *parent,
633                             gboolean warning)
634 {
635   static EmpathyImportDialog *dialog = NULL;
636   GladeXML *glade;
637   gchar *filename;
638
639   if (dialog)
640     {
641       gtk_window_present (GTK_WINDOW (dialog->window));
642       return;
643     }
644
645   dialog = g_slice_new0 (EmpathyImportDialog);
646
647   dialog->accounts = import_dialog_pidgin_load ();
648
649   if (!dialog->accounts)
650     {
651       GtkWidget *message;
652
653       if (warning)
654         {
655           message = gtk_message_dialog_new (parent,
656               GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
657               _("No accounts to import could be found. Empathy currently only "
658                 "supports importing accounts from Pidgin."));
659
660           gtk_dialog_run (GTK_DIALOG (message));
661           gtk_widget_destroy (message);
662         }
663       else
664         DEBUG ("No accounts to import; closing dialog silently.");
665
666       import_dialog_free (dialog);
667       dialog = NULL;
668       return;
669     }
670
671   filename = empathy_file_lookup ("empathy-import-dialog.glade", "src");
672   glade = empathy_glade_get_file (filename,
673       "import_dialog",
674       NULL,
675       "import_dialog", &dialog->window,
676       "treeview", &dialog->treeview,
677       NULL);
678
679   empathy_glade_connect (glade,
680       dialog,
681       "button_ok", "clicked", import_dialog_button_ok_clicked_cb,
682       "button_cancel", "clicked", import_dialog_button_cancel_clicked_cb,
683       NULL);
684
685   g_object_add_weak_pointer (G_OBJECT (dialog->window), (gpointer) &dialog);
686
687   g_free (filename);
688   g_object_unref (glade);
689
690   if (parent)
691     gtk_window_set_transient_for (GTK_WINDOW (dialog->window), parent);
692
693   import_dialog_set_up_account_list (dialog);
694
695   gtk_widget_show (dialog->window);
696 }