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