Fixes NB#115566, do not show "invalid username or password" on connection errors
[modest] / src / modest-utils.c
1 /* Copyright (c) 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <glib.h>
31 #include <glib/gstdio.h>
32 #include <errno.h>
33 #include <string.h> /* for strlen */
34 #include <modest-runtime.h>
35 #include <libgnomevfs/gnome-vfs.h>
36 #include <tny-fs-stream.h>
37 #include <tny-camel-account.h>
38 #include <tny-status.h>
39 #include <tny-camel-transport-account.h>
40 #include <tny-camel-imap-store-account.h>
41 #include <tny-camel-pop-store-account.h>
42
43 #include <modest-defs.h>
44 #include "modest-utils.h"
45 #include "modest-platform.h"
46 #include <modest-account-protocol.h>
47 #include "modest-account-mgr-helpers.h"
48 #include "modest-text-utils.h"
49 #include <modest-local-folder-info.h>
50 #include "widgets/modest-header-view.h"
51 #include "widgets/modest-main-window.h"
52 #include "modest-widget-memory.h"
53 #include "widgets/modest-sort-criterium-view.h"
54 #ifdef MODEST_TOOLKIT_HILDON2
55 #include "modest-header-window.h"
56 #endif
57 #include <langinfo.h>
58
59 GQuark
60 modest_utils_get_supported_secure_authentication_error_quark (void)
61 {
62         return g_quark_from_static_string("modest-utils-get-supported-secure-authentication-error-quark");
63 }
64
65 gboolean
66 modest_utils_folder_writable (const gchar *filename)
67 {
68         g_return_val_if_fail (filename, FALSE);
69
70         if (!filename)
71                 return FALSE;
72
73         if (g_strncasecmp (filename, "obex", 4) != 0) {
74                 GnomeVFSFileInfo *folder_info = NULL;
75                 GnomeVFSResult result = GNOME_VFS_OK;
76                 GnomeVFSURI *uri = NULL;
77                 GnomeVFSURI *folder_uri = NULL;
78
79                 uri = gnome_vfs_uri_new (filename);
80                 folder_uri = gnome_vfs_uri_get_parent (uri);
81
82                 if (folder_uri != NULL) {
83                         folder_info = gnome_vfs_file_info_new ();
84                         result = gnome_vfs_get_file_info_uri (folder_uri, folder_info,
85                                                               GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS);
86                         gnome_vfs_uri_unref (folder_uri);
87                 }
88                 gnome_vfs_uri_unref (uri);
89
90                 if (folder_uri == NULL)
91                         return FALSE;
92
93                 if ((result != GNOME_VFS_OK) ||
94                     (!((folder_info->permissions & GNOME_VFS_PERM_ACCESS_WRITABLE) ||
95                        (folder_info->permissions & GNOME_VFS_PERM_USER_WRITE)))) {
96
97                         gnome_vfs_file_info_unref (folder_info);
98                         return FALSE;
99                 }
100                 gnome_vfs_file_info_unref (folder_info);
101         }
102         return TRUE;
103 }
104
105 gboolean
106 modest_utils_file_exists (const gchar *filename)
107 {
108         GnomeVFSURI *uri = NULL;
109         gboolean result = FALSE;
110
111         g_return_val_if_fail (filename, FALSE);
112
113         uri = gnome_vfs_uri_new (filename);
114         if (uri) {
115                 result = gnome_vfs_uri_exists (uri);
116                 gnome_vfs_uri_unref (uri);
117         }
118         return result;
119 }
120
121 TnyFsStream *
122 modest_utils_create_temp_stream (const gchar *orig_name, const gchar *hash_base, gchar **path)
123 {
124         gint fd;
125         gchar *filepath = NULL;
126         gchar *tmpdir;
127         guint hash_number;
128
129         /* hmmm... maybe we need a modest_text_utils_validate_file_name? */
130         g_return_val_if_fail (orig_name && strlen(orig_name) != 0, NULL);
131
132         if (strlen(orig_name) > 200) {
133                 g_warning ("%s: filename too long ('%s')",
134                            __FUNCTION__, orig_name);
135                 return NULL;
136         }
137
138         if (g_strstr_len (orig_name, strlen(orig_name), "/") != NULL) {
139                 g_warning ("%s: filename contains '/' character(s) (%s)",
140                            __FUNCTION__, orig_name);
141                 return NULL;
142         }
143
144         /* make a random subdir under /tmp or /var/tmp */
145         if (hash_base != NULL) {
146                 hash_number = g_str_hash (hash_base);
147         } else {
148                 hash_number = (guint) random ();
149         }
150         tmpdir = g_strdup_printf ("%s/%u", g_get_tmp_dir (), hash_number);
151         if ((g_access (tmpdir, R_OK) == -1) && (g_mkdir (tmpdir, 0755) == -1)) {
152                 g_warning ("%s: failed to create dir '%s': %s",
153                            __FUNCTION__, tmpdir, g_strerror(errno));
154                 g_free (tmpdir);
155                 return NULL;
156         }
157
158         filepath = g_strconcat (tmpdir, "/", orig_name, NULL);
159
160         /* if file exists, first we try to remove it */
161         if (modest_utils_file_exists (filepath)) {
162                 g_unlink (filepath);
163         }
164
165         /* don't overwrite if it already exists, even if it is writable */
166         if (modest_utils_file_exists (filepath)) {
167                 if (path!=NULL) {
168                         *path = filepath;
169                 } else {
170                         g_free (filepath);
171                 }
172                 g_free (tmpdir);
173                 return NULL;
174         } else {
175                 /* try to write the file there */
176                 fd = g_open (filepath, O_CREAT|O_WRONLY|O_TRUNC, 0644);
177                 if (fd == -1) {
178                         g_warning ("%s: failed to create '%s': %s",
179                                         __FUNCTION__, filepath, g_strerror(errno));
180                         g_free (filepath);
181                         g_free (tmpdir);
182                         return NULL;
183                 }
184         }
185
186         g_free (tmpdir);
187
188         if (path)
189                 *path = filepath;
190         else
191                 g_free (filepath);
192
193         return TNY_FS_STREAM (tny_fs_stream_new (fd));
194 }
195
196 typedef struct 
197 {
198         GList **result;
199         GtkWidget* dialog;
200         GtkWidget* progress;
201 } ModestGetSupportedAuthInfo;
202
203 static gboolean
204 on_idle_secure_auth_finished (gpointer user_data)
205 {
206         /* Operation has finished, close the dialog. Control continues after
207          * gtk_dialog_run in modest_utils_get_supported_secure_authentication_methods() */
208         gdk_threads_enter(); /* CHECKED */
209         gtk_dialog_response (GTK_DIALOG (user_data), GTK_RESPONSE_ACCEPT);
210         gdk_threads_leave(); /* CHECKED */
211
212         return FALSE;
213 }
214
215 static void
216 on_camel_account_get_supported_secure_authentication (TnyCamelAccount *self,
217                                                       gboolean cancelled,
218                                                       TnyList *auth_types,
219                                                       GError *err,
220                                                       gpointer user_data)
221 {
222         ModestPairList *pairs;
223         GList *result;
224         ModestProtocolRegistry *protocol_registry;
225         ModestGetSupportedAuthInfo *info = (ModestGetSupportedAuthInfo*)user_data;
226         TnyIterator* iter;
227
228         g_return_if_fail (user_data);
229         g_return_if_fail (TNY_IS_CAMEL_ACCOUNT(self));
230         g_return_if_fail (TNY_IS_LIST(auth_types));
231
232         info = (ModestGetSupportedAuthInfo *) user_data;
233
234         /* Free everything if the actual action was canceled */
235         if (cancelled) {
236                 g_debug ("%s: operation canceled\n", __FUNCTION__);
237                 goto close_dialog;
238         }
239
240         if (err) {
241                 g_debug ("%s: error getting the supported auth methods\n", __FUNCTION__);
242                 goto close_dialog;
243         }
244
245         if (!auth_types) {
246                 g_debug ("%s: auth_types is NULL.\n", __FUNCTION__);
247                 goto close_dialog;
248         }
249
250         if (tny_list_get_length(auth_types) == 0) {
251                 g_debug ("%s: auth_types is an empty TnyList.\n", __FUNCTION__);
252                 goto close_dialog;
253         }
254
255         protocol_registry = modest_runtime_get_protocol_registry ();
256         pairs = modest_protocol_registry_get_pair_list_by_tag (protocol_registry, MODEST_PROTOCOL_REGISTRY_AUTH_PROTOCOLS);
257
258         /* Get the enum value for the strings: */
259         result = NULL;
260         iter = tny_list_create_iterator(auth_types);
261         while (!tny_iterator_is_done(iter)) {
262                 TnyPair *pair;
263                 const gchar *auth_name;
264                 ModestProtocolType protocol_type;
265
266                 pair = TNY_PAIR(tny_iterator_get_current(iter));
267                 auth_name = NULL;
268                 if (pair) {
269                         auth_name = tny_pair_get_name(pair);
270                         g_object_unref (pair);
271                         pair = NULL;
272                 }
273
274                 g_debug ("%s: auth_name=%s\n", __FUNCTION__, auth_name);
275
276                 protocol_type = modest_protocol_get_type_id (modest_protocol_registry_get_protocol_by_name (protocol_registry,
277                                                                                                             MODEST_PROTOCOL_REGISTRY_AUTH_PROTOCOLS,
278                                                                                                             auth_name));
279
280                 if (modest_protocol_registry_protocol_type_is_secure (protocol_registry, protocol_type))
281                         result = g_list_prepend(result, GINT_TO_POINTER(protocol_type));
282
283                 tny_iterator_next(iter);
284         }
285         g_object_unref (iter);
286
287         modest_pair_list_free (pairs);
288         *(info->result) = result;
289
290  close_dialog:
291         /* Close the dialog in a main thread */
292         g_idle_add(on_idle_secure_auth_finished, info->dialog);
293
294         /* Free the info */
295         g_slice_free (ModestGetSupportedAuthInfo, info);
296 }
297
298 typedef struct {
299         GtkWidget *progress;
300         gboolean not_finished;
301 } KeepPulsing;
302
303 static gboolean
304 keep_pulsing (gpointer user_data)
305 {
306         KeepPulsing *info = (KeepPulsing *) user_data;
307
308         if (!info->not_finished) {
309                 g_slice_free (KeepPulsing, info);
310                 return FALSE;
311         }
312
313         gtk_progress_bar_pulse (GTK_PROGRESS_BAR (info->progress));
314         return TRUE;
315 }
316
317 GList*
318 modest_utils_get_supported_secure_authentication_methods (ModestProtocolType protocol_type,
319                                                           const gchar* hostname,
320                                                           gint port,
321                                                           const gchar* username,
322                                                           GtkWindow *parent_window,
323                                                           GError** error)
324 {
325         TnyAccount * tny_account = NULL;
326         ModestProtocolRegistry *protocol_registry;
327         GtkWidget *dialog;
328         gint retval;
329         ModestTnyAccountStore *account_store;
330         TnySessionCamel *session = NULL;
331         ModestProtocol *protocol = NULL;
332         GList *result = NULL;
333         GtkWidget *progress;
334
335         g_return_val_if_fail (protocol_type != MODEST_PROTOCOL_REGISTRY_TYPE_INVALID, NULL);
336
337         protocol_registry = modest_runtime_get_protocol_registry ();
338
339         /* We need a connection to get the capabilities; */
340         if (!modest_platform_connect_and_wait (GTK_WINDOW (parent_window), NULL))
341                 return NULL;
342
343         /* Create a TnyCamelAccount so we can use 
344          * tny_camel_account_get_supported_secure_authentication(): */
345         protocol = modest_protocol_registry_get_protocol_by_type (protocol_registry, protocol_type);
346         tny_account = NULL;
347         if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
348                 tny_account = modest_account_protocol_create_account (MODEST_ACCOUNT_PROTOCOL (protocol));
349         }
350
351         if (!tny_account) {
352                 g_printerr ("%s could not create tny account.", __FUNCTION__);
353                 return NULL;
354         }
355
356         /* Set proto, so that the prepare_func() vfunc will work when
357          * we call set_session(): */
358         protocol = modest_protocol_registry_get_protocol_by_type (protocol_registry, protocol_type);
359         tny_account_set_id (tny_account, "temp_account");
360         tny_account_set_proto (tny_account, modest_protocol_get_name (protocol));
361         tny_account_set_hostname (tny_account, hostname);
362         tny_account_set_user (tny_account, username);
363
364         if(port > 0)
365                 tny_account_set_port (tny_account, port);
366
367         /* Set the session for the account, so we can use it: */
368         account_store = modest_runtime_get_account_store ();
369         session = modest_tny_account_store_get_session (TNY_ACCOUNT_STORE (account_store));
370         g_return_val_if_fail (session, NULL);
371         tny_camel_account_set_session (TNY_CAMEL_ACCOUNT(tny_account), session);
372
373         dialog = gtk_dialog_new_with_buttons(" ",
374                                              parent_window,
375                                              GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
376                                              _("mcen_bd_dialog_cancel"),
377                                              GTK_RESPONSE_REJECT,
378                                              NULL);
379
380         /* Ask camel to ask the server, asynchronously: */
381         ModestGetSupportedAuthInfo *info = g_slice_new (ModestGetSupportedAuthInfo);
382         info->result = &result;
383         info->dialog = dialog;
384
385         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(info->dialog)->vbox),
386                           gtk_label_new(_("emev_ni_checking_supported_auth_methods")));
387         progress = gtk_progress_bar_new();
388         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(info->dialog)->vbox), progress);
389         gtk_widget_show_all(info->dialog);
390
391         KeepPulsing *pi = g_slice_new (KeepPulsing);
392         pi->progress = progress;
393         pi->not_finished = TRUE;
394
395         /* Starts the pulsing of the progressbar */
396         g_timeout_add (500, keep_pulsing, pi);
397
398         tny_camel_account_get_supported_secure_authentication (TNY_CAMEL_ACCOUNT (tny_account),
399                                                                on_camel_account_get_supported_secure_authentication,
400                                                                NULL,
401                                                                info);
402
403         retval = gtk_dialog_run (GTK_DIALOG (info->dialog));
404
405         pi->not_finished = FALSE;
406         /* pi is freed in the timeout itself to avoid a GCond here */
407
408         gtk_widget_destroy(dialog);
409
410         /* Frees */
411         tny_account_cancel (tny_account);
412         g_object_unref (tny_account);
413
414         return result;
415 }
416
417 void
418 modest_utils_show_dialog_and_forget (GtkWindow *parent_window,
419                                      GtkDialog *dialog)
420 {
421         g_return_if_fail (GTK_IS_WINDOW(parent_window));
422         g_return_if_fail (GTK_IS_DIALOG(dialog));
423
424         gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
425
426         /* Destroy the dialog when it is closed: */
427         g_signal_connect_swapped (dialog,
428                                   "response",
429                                   G_CALLBACK (gtk_widget_destroy),
430                                   dialog);
431
432         gtk_widget_show (GTK_WIDGET (dialog));
433 }
434
435 void
436 modest_utils_toggle_action_set_active_block_notify (GtkToggleAction *action, gboolean value)
437 {
438         GSList *proxies = NULL;
439
440         g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
441
442         for (proxies = gtk_action_get_proxies (GTK_ACTION (action));
443              proxies != NULL; proxies = g_slist_next (proxies)) {
444                 GtkWidget *widget = (GtkWidget *) proxies->data;
445                 gtk_action_block_activate_from (GTK_ACTION (action), widget);
446         }
447
448         gtk_toggle_action_set_active (action, value);
449
450         for (proxies = gtk_action_get_proxies (GTK_ACTION (action));
451              proxies != NULL; proxies = g_slist_next (proxies)) {
452                 GtkWidget *widget = (GtkWidget *) proxies->data;
453                 gtk_action_unblock_activate_from (GTK_ACTION (action), widget);
454         }
455
456 }
457
458
459 gint 
460 modest_list_index (TnyList *list, GObject *object)
461 {
462         TnyIterator *iter;
463         gint index = 0;
464
465         g_return_val_if_fail (TNY_IS_LIST(list), -1);
466         g_return_val_if_fail (G_IS_OBJECT(object), -1);
467         
468         iter = tny_list_create_iterator (list);
469         while (!tny_iterator_is_done (iter)) {
470                 GObject *current = tny_iterator_get_current (iter);
471
472                 g_object_unref (current);
473                 if (current == object)
474                         break;
475
476                 tny_iterator_next (iter);
477                 index++;
478         }
479
480         if (tny_iterator_is_done (iter))
481                 index = -1;
482         g_object_unref (iter);
483         return index;
484 }
485
486 guint64 
487 modest_utils_get_available_space (const gchar *maildir_path)
488 {
489         gchar *folder;
490         gchar *uri_string;
491         GnomeVFSURI *uri;
492         GnomeVFSFileSize size;
493
494         folder = modest_local_folder_info_get_maildir_path (maildir_path);
495         uri_string = gnome_vfs_get_uri_from_local_path (folder);
496         uri = gnome_vfs_uri_new (uri_string);
497         g_free (folder);
498         g_free (uri_string);
499
500         if (uri) {
501                 if (gnome_vfs_get_volume_free_space (uri, &size) != GNOME_VFS_OK)
502                         size = 0;
503                 gnome_vfs_uri_unref (uri);
504         } else {
505                 size = 0;
506         }
507
508         return (guint64) size;
509 }
510 static void
511 on_destroy_dialog (GtkDialog *dialog)
512 {
513         gtk_widget_destroy (GTK_WIDGET(dialog));
514         if (gtk_events_pending ())
515                 gtk_main_iteration ();
516 }
517
518 static guint
519 checked_modest_sort_criterium_view_add_sort_key (ModestSortCriteriumView *view, const gchar* key, guint max)
520 {
521         gint sort_key;
522         
523         g_return_val_if_fail (view && MODEST_IS_SORT_CRITERIUM_VIEW(view), 0);
524         g_return_val_if_fail (key, 0);
525         
526         sort_key = modest_sort_criterium_view_add_sort_key (view, key);
527         if (sort_key < 0 || sort_key >= max) {
528                 g_warning ("%s: out of range (%d) for %s", __FUNCTION__, sort_key, key);
529                 return 0;
530         } else
531                 return (guint)sort_key; 
532 }
533
534 static void
535 launch_sort_headers_dialog (GtkWindow *parent_window,
536                             GtkDialog *dialog)
537 {
538         ModestHeaderView *header_view = NULL;
539         GList *cols = NULL;
540         GtkSortType sort_type;
541         gint sort_key;
542         gint default_key = 0;
543         gint result;
544         gboolean outgoing = FALSE;
545         gint current_sort_colid = -1;
546         GtkSortType current_sort_type;
547         gint attachments_sort_id;
548         gint priority_sort_id;
549         GtkTreeSortable *sortable;
550         
551         /* Get header window */
552         if (MODEST_IS_MAIN_WINDOW (parent_window)) {
553                 header_view = MODEST_HEADER_VIEW(modest_main_window_get_child_widget (MODEST_MAIN_WINDOW(parent_window),
554                                                                                       MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
555 #ifdef MODEST_TOOLKIT_HILDON2
556         } else if (MODEST_IS_HEADER_WINDOW (parent_window)) {
557                 header_view = MODEST_HEADER_VIEW (modest_header_window_get_header_view (MODEST_HEADER_WINDOW (parent_window)));
558 #endif
559
560         }
561         if (!header_view)
562                 return;
563         
564         /* Add sorting keys */
565         cols = modest_header_view_get_columns (header_view);
566         if (cols == NULL) 
567                 return;
568 #define SORT_ID_NUM 6
569         int sort_model_ids[SORT_ID_NUM];
570         int sort_ids[SORT_ID_NUM];
571
572         outgoing = (GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cols->data), MODEST_HEADER_VIEW_COLUMN))==
573                     MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT);
574
575         sort_key = checked_modest_sort_criterium_view_add_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), _("mcen_li_sort_sender_recipient"),
576                                                                     SORT_ID_NUM);
577         if (outgoing) {
578                 sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN;
579                 sort_ids[sort_key] = MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT;
580         } else {
581                 sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN;
582                 sort_ids[sort_key] = MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN;
583         }
584
585         sort_key = checked_modest_sort_criterium_view_add_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), _("mcen_li_sort_date"),
586                                                             SORT_ID_NUM);
587         if (outgoing) {
588                 sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN;
589                 sort_ids[sort_key] = MODEST_HEADER_VIEW_COLUMN_COMPACT_SENT_DATE;
590         } else {
591                 sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN;
592                 sort_ids[sort_key] = MODEST_HEADER_VIEW_COLUMN_COMPACT_RECEIVED_DATE;
593         }
594         default_key = sort_key;
595
596         sort_key = checked_modest_sort_criterium_view_add_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), _("mcen_li_sort_subject"),
597                                                                     SORT_ID_NUM);
598         sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN;
599         if (outgoing)
600                 sort_ids[sort_key] = MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT;
601         else
602                 sort_ids[sort_key] = MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN;
603
604         sort_key = checked_modest_sort_criterium_view_add_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), _("mcen_li_sort_attachment"),
605                                                                     SORT_ID_NUM);
606         sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN;
607         sort_ids[sort_key] = TNY_HEADER_FLAG_ATTACHMENTS;
608         attachments_sort_id = sort_key;
609
610         sort_key = checked_modest_sort_criterium_view_add_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), _("mcen_li_sort_size"),
611                                                                     SORT_ID_NUM);
612         sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN;
613         sort_ids[sort_key] = 0;
614
615         sort_key = checked_modest_sort_criterium_view_add_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), _("mcen_li_sort_priority"),
616                                                                     SORT_ID_NUM);
617         sort_model_ids[sort_key] = TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN;
618         sort_ids[sort_key] = TNY_HEADER_FLAG_PRIORITY_MASK;
619         priority_sort_id = sort_key;
620         
621         sortable = GTK_TREE_SORTABLE (gtk_tree_model_filter_get_model
622                                       (GTK_TREE_MODEL_FILTER (gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)))));
623         /* Launch dialogs */
624         if (!gtk_tree_sortable_get_sort_column_id (sortable,
625                                                    &current_sort_colid, &current_sort_type)) {
626                 modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), default_key);
627                 modest_sort_criterium_view_set_sort_order (MODEST_SORT_CRITERIUM_VIEW (dialog), GTK_SORT_DESCENDING);
628         } else {
629                 modest_sort_criterium_view_set_sort_order (MODEST_SORT_CRITERIUM_VIEW (dialog), current_sort_type);
630                 if (current_sort_colid == TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN) {
631                         gpointer flags_sort_type_pointer;
632                         flags_sort_type_pointer = g_object_get_data (G_OBJECT (cols->data), MODEST_HEADER_VIEW_FLAG_SORT);
633                         if (GPOINTER_TO_INT (flags_sort_type_pointer) == TNY_HEADER_FLAG_PRIORITY_MASK)
634                                 modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), priority_sort_id);
635                         else
636                                 modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), attachments_sort_id);
637                 } else {
638                         gint current_sort_keyid = 0;
639                         while (current_sort_keyid < SORT_ID_NUM) {
640                                 if (sort_model_ids[current_sort_keyid] == current_sort_colid)
641                                         break;
642                                 else 
643                                         current_sort_keyid++;
644                         }
645                         modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), current_sort_keyid);
646                 }
647         }
648
649         result = gtk_dialog_run (GTK_DIALOG (dialog));
650         if (result == GTK_RESPONSE_OK) {
651                 sort_key = modest_sort_criterium_view_get_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog));
652                 if (sort_key < 0 || sort_key > SORT_ID_NUM -1) {
653                         g_warning ("%s: out of range (%d)", __FUNCTION__, sort_key);
654                         sort_key = 0;
655                 }
656
657                 sort_type = modest_sort_criterium_view_get_sort_order (MODEST_SORT_CRITERIUM_VIEW (dialog));
658                 if (sort_model_ids[sort_key] == TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN) {
659                         g_object_set_data (G_OBJECT(cols->data), MODEST_HEADER_VIEW_FLAG_SORT,
660                                            GINT_TO_POINTER (sort_ids[sort_key]));
661                         /* This is a hack to make it resort rows always when flag fields are
662                          * selected. If we do not do this, changing sort field from priority to
663                          * attachments does not work */
664                         modest_header_view_sort_by_column_id (header_view, 0, sort_type);
665                 } else {
666                         gtk_tree_view_column_set_sort_column_id (GTK_TREE_VIEW_COLUMN (cols->data), 
667                                                                  sort_model_ids[sort_key]);
668                 }
669
670                 modest_header_view_sort_by_column_id (header_view, sort_model_ids[sort_key], sort_type);
671                 gtk_tree_sortable_sort_column_changed (sortable);
672         }
673
674         modest_widget_memory_save (modest_runtime_get_conf (),
675                                    G_OBJECT (header_view), MODEST_CONF_HEADER_VIEW_KEY);
676         
677         /* free */
678         g_list_free(cols);      
679 }
680
681 void
682 modest_utils_run_sort_dialog (GtkWindow *parent_window,
683                               ModestSortDialogType type)
684 {
685         GtkWidget *dialog = NULL;
686
687         /* Build dialog */
688         dialog = modest_platform_create_sort_dialog (parent_window);
689         if (dialog == NULL)
690                 return;
691         modest_window_mgr_set_modal (modest_runtime_get_window_mgr (),
692                                      GTK_WINDOW (dialog), parent_window);
693
694         /* Fill sort keys */
695         switch (type) {
696         case MODEST_SORT_HEADERS:
697                 launch_sort_headers_dialog (parent_window, 
698                                             GTK_DIALOG(dialog));
699                 break;
700         }
701         
702         /* Free */
703         on_destroy_dialog (GTK_DIALOG(dialog));
704 }
705
706
707 gchar *
708 modest_images_cache_get_id (const gchar *account, const gchar *uri)
709 {
710         GnomeVFSURI *vfs_uri;
711         gchar *result;
712  
713         vfs_uri = gnome_vfs_uri_new (uri);
714         if (vfs_uri == NULL)
715                 return NULL;
716  
717         result = g_strdup_printf ("%s__%x", account, gnome_vfs_uri_hash (vfs_uri));
718         gnome_vfs_uri_unref (vfs_uri);
719  
720         return result;
721 }
722
723 gchar *
724 modest_utils_get_account_name_from_recipient (const gchar *from_header, gchar **mailbox)
725 {
726         gchar *account_name = NULL;
727         ModestAccountMgr *mgr = NULL;
728         GSList *accounts = NULL, *node = NULL;
729
730         if (mailbox)
731                 *mailbox = NULL;
732         g_return_val_if_fail (from_header, NULL);
733
734         mgr = modest_runtime_get_account_mgr ();
735         accounts = modest_account_mgr_account_names (mgr, TRUE);
736                 
737         for (node = accounts; node != NULL; node = g_slist_next (node)) {
738                 gchar *from;
739                 gchar *transport_account;
740
741                 if (!strcmp (from_header, node->data)) {
742                         account_name = g_strdup (node->data);
743                         break;
744                 }
745
746                 from = 
747                         modest_account_mgr_get_from_string (mgr, node->data, NULL);
748                         
749                 if (from) {
750                         gchar *from_email = 
751                                 modest_text_utils_get_email_address (from);
752                         gchar *from_header_email =
753                                 modest_text_utils_get_email_address (from_header);
754                                 
755                         if (from_email && from_header_email) {
756                                 if (!modest_text_utils_utf8_strcmp (from_header_email, from_email, TRUE)) {
757                                         account_name = g_strdup (node->data);
758                                         g_free (from);
759                                         g_free (from_email);
760                                         break;
761                                 }
762                         }
763                         g_free (from_email);
764                         g_free (from_header_email);
765                         g_free (from);
766                 }
767
768                 transport_account = modest_account_mgr_get_server_account_name (modest_runtime_get_account_mgr (),
769                                                                                 (const gchar *) node->data,
770                                                                                 TNY_ACCOUNT_TYPE_TRANSPORT);
771                 if (transport_account) {
772                         gchar *proto;
773                         proto = modest_account_mgr_get_string (mgr, transport_account, MODEST_ACCOUNT_PROTO, TRUE);
774
775                         if (proto != NULL) {
776                                 ModestProtocol *protocol = 
777                                         modest_protocol_registry_get_protocol_by_name (modest_runtime_get_protocol_registry (),
778                                                                                        MODEST_PROTOCOL_REGISTRY_TRANSPORT_PROTOCOLS,
779                                                                                        proto);
780                                 if (protocol && MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
781                                         ModestPairList *pair_list;
782                                         ModestPair *pair;
783                                         gchar *from_header_email =
784                                                 modest_text_utils_get_email_address (from_header);
785                                         pair_list = modest_account_protocol_get_from_list (MODEST_ACCOUNT_PROTOCOL (protocol),
786                                                                                            node->data);
787                                         
788                                         pair = modest_pair_list_find_by_first_as_string (pair_list, from_header_email);
789                                         if (pair != NULL) {
790                                                 account_name = g_strdup (node->data);
791                                                 if (mailbox)
792                                                         *mailbox = g_strdup (from_header_email);
793                                         }
794                                         
795                                         modest_pair_list_free (pair_list);
796                                         
797                                 }
798                                 g_free (proto);
799                         }
800                         g_free (transport_account);
801                 }
802                 if (mailbox && *mailbox)
803                         break;
804                         
805         }
806         g_slist_foreach (accounts, (GFunc) g_free, NULL);
807         g_slist_free (accounts);
808
809         return account_name;
810 }
811
812 void 
813 modest_utils_on_entry_invalid_character (ModestValidatingEntry *self, 
814                                          const gchar* character,
815                                          gpointer user_data)
816 {
817         gchar *message = NULL;
818         const gchar *show_char = NULL;
819
820         if (character)
821                 show_char = character;
822         else {
823                 show_char = "' '";
824         }
825         
826         message = g_strdup_printf (_CS("ckdg_ib_illegal_characters_entered"), show_char);
827         modest_platform_information_banner (GTK_WIDGET (self), NULL, message);
828         g_free (message);
829 }
830
831 FILE*
832 modest_utils_open_mcc_mapping_file (gboolean *translated)
833 {
834         FILE* result = NULL;
835         const gchar* path;
836         gchar *path1 = g_strdup_printf ("%s.%s", MODEST_OPERATOR_WIZARD_MCC_MAPPING, getenv("LANG"));
837         const gchar* path2 = MODEST_MCC_MAPPING;
838
839         if (translated)
840                 *translated = TRUE;
841
842         if (access (path1, R_OK) == 0) {
843                 path = path1;
844         } else if (access (MODEST_OPERATOR_WIZARD_MCC_MAPPING, R_OK) == 0) {
845                 path = MODEST_OPERATOR_WIZARD_MCC_MAPPING;
846                 if (translated)
847                         *translated = FALSE;
848         } else if (access (path2, R_OK) == 0) {
849                 path = path2;
850         } else {
851                 g_warning ("%s: neither '%s' nor '%s' is a readable mapping file",
852                            __FUNCTION__, path1, path2);
853                 goto end;
854         }
855
856         result = fopen (path, "r");
857         if (!result) {
858                 g_warning ("%s: error opening mapping file '%s': %s",
859                            __FUNCTION__, path, strerror(errno));
860                 goto end;
861         }
862  end:
863         g_free (path1);
864         return result;
865 }
866
867 /* cluster mcc's, based on the list
868  * http://en.wikipedia.org/wiki/Mobile_country_code
869  */
870 static int
871 effective_mcc (gint mcc)
872 {
873         switch (mcc) {
874         case 405: return 404; /* india */
875         case 441: return 440; /* japan */       
876         case 235: return 234; /* united kingdom */
877         case 311:
878         case 312:
879         case 313:
880         case 314:
881         case 315:
882         case 316: return 310; /* united states */
883         default:  return mcc;
884         }
885 }
886
887 /* each line is of the form:
888    xxx    logical_id
889
890   NOTE: this function is NOT re-entrant, the out-param country
891   are static strings that should NOT be freed. and will change when
892   calling this function again
893
894   also note, this function will return the "effective mcc", which
895   is the normalized mcc for a country - ie. even if the there
896   are multiple entries for the United States with various mccs,
897   this function will always return 310, even if the real mcc parsed
898   would be 314. see the 'effective_mcc' function above.
899 */
900 static int
901 parse_mcc_mapping_line (const char* line,  char** country)
902 {
903         char mcc[4];  /* the mcc code, always 3 bytes*/
904         gchar *iter, *tab, *final;
905
906         if (!line) {
907                 *country = NULL;
908                 return 0;
909         }
910
911         /* Go to the first tab (Country separator) */
912         tab = g_utf8_strrchr (line, -1, '\t');
913         if (!tab)
914                 return 0;
915
916         *country = g_utf8_find_next_char (tab, NULL);
917
918         /* Replace by end of string. We need to use strlen, because
919            g_utf8_strrchr expects bytes and not UTF8 characters  */
920         final = g_utf8_strrchr (tab, strlen (tab) + 1, '\n');
921         if (G_LIKELY (final))
922                 *final = '\0';
923         else
924                 tab[strlen(tab) - 1] = '\0';
925
926         /* Get MCC code */
927         mcc[0] = g_utf8_get_char (line);
928         iter = g_utf8_find_next_char (line, NULL);
929         mcc[1] = g_utf8_get_char (iter);
930         iter = g_utf8_find_next_char (iter, NULL);
931         mcc[2] = g_utf8_get_char (iter);
932         mcc[3] = '\0';
933
934         return effective_mcc ((int) strtol ((const char*)mcc, NULL, 10));
935 }
936
937 #define MCC_FILE_MAX_LINE_LEN 128 /* max length of a line in MCC file */
938
939 /** Note that the mcc_mapping file is installed 
940  * by the operator-wizard-settings package.
941  */
942 GtkTreeModel *
943 modest_utils_create_country_model (void)
944 {
945         GtkTreeModel *model;
946
947         model = GTK_TREE_MODEL (gtk_list_store_new (2,  G_TYPE_STRING, G_TYPE_INT));
948
949         return model;
950 }
951
952 void
953 modest_utils_fill_country_model (GtkTreeModel *model, gint *locale_mcc)
954 {
955         gboolean translated;
956         char line[MCC_FILE_MAX_LINE_LEN];
957         guint previous_mcc = 0;
958         gchar *territory;
959
960         FILE *file;
961
962         file = modest_utils_open_mcc_mapping_file (&translated);
963         if (!file) {
964                 g_warning ("Could not open mcc_mapping file");
965                 return;
966         }
967
968         /* Get the territory specified for the current locale */
969         territory = nl_langinfo (_NL_ADDRESS_COUNTRY_NAME);
970
971         while (fgets (line, MCC_FILE_MAX_LINE_LEN, file) != NULL) {
972
973                 int mcc;
974                 char *country = NULL;
975                 const gchar *name_translated;
976
977                 mcc = parse_mcc_mapping_line (line, &country);
978                 if (!country || mcc == 0) {
979                         g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line);
980                         continue;
981                 }
982
983                 if (mcc == previous_mcc) {
984                         /* g_warning ("already seen: %s", line); */
985                         continue;
986                 }
987                 previous_mcc = mcc;
988
989                 if (!(*locale_mcc)) {
990                         if (translated) {
991                                 if (!g_utf8_collate (country, territory))
992                                         *locale_mcc = mcc;
993                         } else {
994                                 gchar *translation = dgettext ("osso-countries", country);
995                                 if (!g_utf8_collate (translation, territory))
996                                         *locale_mcc = mcc;
997                         }
998                 }
999                 name_translated = dgettext ("osso-countries", country);
1000
1001                 /* Add the row to the model: */
1002                 GtkTreeIter iter;
1003                 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1004                 gtk_list_store_set(GTK_LIST_STORE (model), &iter, 
1005                                    MODEST_UTILS_COUNTRY_MODEL_COLUMN_MCC, mcc, 
1006                                    MODEST_UTILS_COUNTRY_MODEL_COLUMN_NAME, name_translated, 
1007                                    -1);
1008         }
1009         fclose (file);
1010
1011         /* Fallback to Finland */
1012         if (!(*locale_mcc))
1013                 *locale_mcc = 244;
1014
1015         /* Sort the items: */
1016         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), 
1017                                               MODEST_UTILS_COUNTRY_MODEL_COLUMN_NAME, GTK_SORT_ASCENDING);
1018 }
1019