c6e50e7b8dd6dbd9ea99af7bdeced43e5a4ecddb
[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_view_get_model (GTK_TREE_VIEW (header_view)));
622         /* Launch dialogs */
623         if (!gtk_tree_sortable_get_sort_column_id (sortable,
624                                                    &current_sort_colid, &current_sort_type)) {
625                 modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), default_key);
626                 modest_sort_criterium_view_set_sort_order (MODEST_SORT_CRITERIUM_VIEW (dialog), GTK_SORT_DESCENDING);
627         } else {
628                 modest_sort_criterium_view_set_sort_order (MODEST_SORT_CRITERIUM_VIEW (dialog), current_sort_type);
629                 if (current_sort_colid == TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN) {
630                         gpointer flags_sort_type_pointer;
631                         flags_sort_type_pointer = g_object_get_data (G_OBJECT (cols->data), MODEST_HEADER_VIEW_FLAG_SORT);
632                         if (GPOINTER_TO_INT (flags_sort_type_pointer) == TNY_HEADER_FLAG_PRIORITY_MASK)
633                                 modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), priority_sort_id);
634                         else
635                                 modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), attachments_sort_id);
636                 } else {
637                         gint current_sort_keyid = 0;
638                         while (current_sort_keyid < SORT_ID_NUM) {
639                                 if (sort_model_ids[current_sort_keyid] == current_sort_colid)
640                                         break;
641                                 else 
642                                         current_sort_keyid++;
643                         }
644                         modest_sort_criterium_view_set_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog), current_sort_keyid);
645                 }
646         }
647
648         result = gtk_dialog_run (GTK_DIALOG (dialog));
649         if (result == GTK_RESPONSE_OK) {
650                 sort_key = modest_sort_criterium_view_get_sort_key (MODEST_SORT_CRITERIUM_VIEW (dialog));
651                 if (sort_key < 0 || sort_key > SORT_ID_NUM -1) {
652                         g_warning ("%s: out of range (%d)", __FUNCTION__, sort_key);
653                         sort_key = 0;
654                 }
655
656                 sort_type = modest_sort_criterium_view_get_sort_order (MODEST_SORT_CRITERIUM_VIEW (dialog));
657                 if (sort_model_ids[sort_key] == TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN) {
658                         g_object_set_data (G_OBJECT(cols->data), MODEST_HEADER_VIEW_FLAG_SORT,
659                                            GINT_TO_POINTER (sort_ids[sort_key]));
660                         /* This is a hack to make it resort rows always when flag fields are
661                          * selected. If we do not do this, changing sort field from priority to
662                          * attachments does not work */
663                         modest_header_view_sort_by_column_id (header_view, 0, sort_type);
664                 } else {
665                         gtk_tree_view_column_set_sort_column_id (GTK_TREE_VIEW_COLUMN (cols->data), 
666                                                                  sort_model_ids[sort_key]);
667                 }
668
669                 modest_header_view_sort_by_column_id (header_view, sort_model_ids[sort_key], sort_type);
670                 gtk_tree_sortable_sort_column_changed (sortable);
671         }
672
673         modest_widget_memory_save (modest_runtime_get_conf (),
674                                    G_OBJECT (header_view), MODEST_CONF_HEADER_VIEW_KEY);
675         
676         /* free */
677         g_list_free(cols);      
678 }
679
680 void
681 modest_utils_run_sort_dialog (GtkWindow *parent_window,
682                               ModestSortDialogType type)
683 {
684         GtkWidget *dialog = NULL;
685
686         /* Build dialog */
687         dialog = modest_platform_create_sort_dialog (parent_window);
688         if (dialog == NULL)
689                 return;
690         modest_window_mgr_set_modal (modest_runtime_get_window_mgr (),
691                                      GTK_WINDOW (dialog), parent_window);
692
693         /* Fill sort keys */
694         switch (type) {
695         case MODEST_SORT_HEADERS:
696                 launch_sort_headers_dialog (parent_window, 
697                                             GTK_DIALOG(dialog));
698                 break;
699         }
700         
701         /* Free */
702         on_destroy_dialog (GTK_DIALOG(dialog));
703 }
704
705
706 gchar *
707 modest_images_cache_get_id (const gchar *account, const gchar *uri)
708 {
709         GnomeVFSURI *vfs_uri;
710         gchar *result;
711  
712         vfs_uri = gnome_vfs_uri_new (uri);
713         if (vfs_uri == NULL)
714                 return NULL;
715  
716         result = g_strdup_printf ("%s__%x", account, gnome_vfs_uri_hash (vfs_uri));
717         gnome_vfs_uri_unref (vfs_uri);
718  
719         return result;
720 }
721
722 gchar *
723 modest_utils_get_account_name_from_recipient (const gchar *from_header, gchar **mailbox)
724 {
725         gchar *account_name = NULL;
726         ModestAccountMgr *mgr = NULL;
727         GSList *accounts = NULL, *node = NULL;
728
729         if (mailbox)
730                 *mailbox = NULL;
731         g_return_val_if_fail (from_header, NULL);
732
733         mgr = modest_runtime_get_account_mgr ();
734         accounts = modest_account_mgr_account_names (mgr, TRUE);
735                 
736         for (node = accounts; node != NULL; node = g_slist_next (node)) {
737                 gchar *from;
738                 gchar *transport_account;
739
740                 if (!strcmp (from_header, node->data)) {
741                         account_name = g_strdup (node->data);
742                         break;
743                 }
744
745                 transport_account = modest_account_mgr_get_server_account_name (modest_runtime_get_account_mgr (),
746                                                                                 (const gchar *) node->data,
747                                                                                 TNY_ACCOUNT_TYPE_TRANSPORT);
748                 if (transport_account) {
749                         gchar *proto;
750                         proto = modest_account_mgr_get_string (mgr, transport_account, MODEST_ACCOUNT_PROTO, TRUE);
751
752                         if (proto != NULL) {
753                                 ModestProtocol *protocol = 
754                                         modest_protocol_registry_get_protocol_by_name (modest_runtime_get_protocol_registry (),
755                                                                                        MODEST_PROTOCOL_REGISTRY_TRANSPORT_PROTOCOLS,
756                                                                                        proto);
757                                 if (protocol && MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
758                                         ModestPairList *pair_list;
759                                         ModestPair *pair;
760                                         gchar *from_header_email =
761                                                 modest_text_utils_get_email_address (from_header);
762                                         pair_list = modest_account_protocol_get_from_list (MODEST_ACCOUNT_PROTOCOL (protocol),
763                                                                                            node->data);
764                                         
765                                         pair = modest_pair_list_find_by_first_as_string (pair_list, from_header_email);
766                                         if (pair != NULL) {
767                                                 account_name = g_strdup (node->data);
768                                                 if (mailbox)
769                                                         *mailbox = g_strdup (from_header_email);
770                                         }
771                                         
772                                         modest_pair_list_free (pair_list);
773                                         
774                                 }
775                                 g_free (proto);
776                         }
777                         g_free (transport_account);
778                 }
779                 if (mailbox && *mailbox)
780                         break;
781
782                 from = 
783                         modest_account_mgr_get_from_string (mgr, node->data, NULL);
784                         
785                 if (from) {
786                         gchar *from_email = 
787                                 modest_text_utils_get_email_address (from);
788                         gchar *from_header_email =
789                                 modest_text_utils_get_email_address (from_header);
790                                 
791                         if (from_email && from_header_email) {
792                                 if (!modest_text_utils_utf8_strcmp (from_header_email, from_email, TRUE)) {
793                                         account_name = g_strdup (node->data);
794                                         g_free (from);
795                                         g_free (from_email);
796                                         break;
797                                 }
798                         }
799                         g_free (from_email);
800                         g_free (from_header_email);
801                         g_free (from);
802                 }
803
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 from_lc_messages, gboolean *translated)
833 {
834         FILE* result = NULL;
835         const gchar* path;
836         const gchar *env_list;
837         gchar **parts, **node;
838
839         if (from_lc_messages) {
840                 env_list = getenv ("LC_MESSAGES");
841         } else {
842                 env_list = getenv ("LANG");
843         }
844         parts = g_strsplit (env_list, ":", 0);
845         gchar *path1 = NULL;
846         const gchar* path2 = MODEST_MCC_MAPPING;
847
848         if (translated)
849                 *translated = TRUE;
850
851         path = NULL;
852         for (node = parts; path == NULL && node != NULL && *node != NULL && **node != '\0'; node++) {
853                 path1 = g_strdup_printf ("%s.%s", MODEST_OPERATOR_WIZARD_MCC_MAPPING, *node);
854                 if (access (path1, R_OK) == 0) {
855                         path = path1;
856                         break;
857                 } else {
858                         g_free (path1);
859                         path1 = NULL;
860                 }
861         }
862         g_strfreev (parts);
863
864         if (path == NULL) {
865                 if (access (MODEST_OPERATOR_WIZARD_MCC_MAPPING, R_OK) == 0) {
866                         path = MODEST_OPERATOR_WIZARD_MCC_MAPPING;
867                         if (translated)
868                                 *translated = FALSE;
869                 } else if (access (path2, R_OK) == 0) {
870                         path = path2;
871                 } else {
872                         g_warning ("%s: neither '%s' nor '%s' is a readable mapping file",
873                                    __FUNCTION__, path1, path2);
874                         goto end;
875                 }
876         }
877
878         result = fopen (path, "r");
879         if (!result) {
880                 g_warning ("%s: error opening mapping file '%s': %s",
881                            __FUNCTION__, path, strerror(errno));
882                 goto end;
883         }
884  end:
885         g_free (path1);
886         return result;
887 }
888
889 /* cluster mcc's, based on the list
890  * http://en.wikipedia.org/wiki/Mobile_country_code
891  */
892 static int
893 effective_mcc (gint mcc)
894 {
895         switch (mcc) {
896         case 405: return 404; /* india */
897         case 441: return 440; /* japan */       
898         case 235: return 234; /* united kingdom */
899         case 311:
900         case 312:
901         case 313:
902         case 314:
903         case 315:
904         case 316: return 310; /* united states */
905         default:  return mcc;
906         }
907 }
908
909 /* each line is of the form:
910    xxx    logical_id
911
912   NOTE: this function is NOT re-entrant, the out-param country
913   are static strings that should NOT be freed. and will change when
914   calling this function again
915
916   also note, this function will return the "effective mcc", which
917   is the normalized mcc for a country - ie. even if the there
918   are multiple entries for the United States with various mccs,
919   this function will always return 310, even if the real mcc parsed
920   would be 314. see the 'effective_mcc' function above.
921 */
922 static int
923 parse_mcc_mapping_line (const char* line,  char** country)
924 {
925         char mcc[4];  /* the mcc code, always 3 bytes*/
926         gchar *iter, *tab, *final;
927
928         if (!line) {
929                 *country = NULL;
930                 return 0;
931         }
932
933         /* Go to the first tab (Country separator) */
934         tab = g_utf8_strrchr (line, -1, '\t');
935         if (!tab)
936                 return 0;
937
938         *country = g_utf8_find_next_char (tab, NULL);
939
940         /* Replace by end of string. We need to use strlen, because
941            g_utf8_strrchr expects bytes and not UTF8 characters  */
942         final = g_utf8_strrchr (tab, strlen (tab) + 1, '\n');
943         if (G_LIKELY (final))
944                 *final = '\0';
945         else
946                 tab[strlen(tab) - 1] = '\0';
947
948         /* Get MCC code */
949         mcc[0] = g_utf8_get_char (line);
950         iter = g_utf8_find_next_char (line, NULL);
951         mcc[1] = g_utf8_get_char (iter);
952         iter = g_utf8_find_next_char (iter, NULL);
953         mcc[2] = g_utf8_get_char (iter);
954         mcc[3] = '\0';
955
956         return effective_mcc ((int) strtol ((const char*)mcc, NULL, 10));
957 }
958
959 #define MCC_FILE_MAX_LINE_LEN 128 /* max length of a line in MCC file */
960
961 /** Note that the mcc_mapping file is installed 
962  * by the operator-wizard-settings package.
963  */
964 GtkTreeModel *
965 modest_utils_create_country_model (void)
966 {
967         GtkTreeModel *model;
968
969         model = GTK_TREE_MODEL (gtk_list_store_new (2,  G_TYPE_STRING, G_TYPE_INT));
970
971         return model;
972 }
973
974 void
975 modest_utils_fill_country_model (GtkTreeModel *model, gint *locale_mcc)
976 {
977         gboolean translated;
978         char line[MCC_FILE_MAX_LINE_LEN];
979         guint previous_mcc = 0;
980         gchar *territory;
981         GHashTable *country_hash;
982         FILE *file;
983
984         /* First we need to know our current region */
985         file = modest_utils_open_mcc_mapping_file (FALSE, &translated);
986         if (!file) {
987                 g_warning ("Could not open mcc_mapping file");
988                 return;
989         }
990
991         /* Get the territory specified for the current locale */
992         territory = nl_langinfo (_NL_ADDRESS_COUNTRY_NAME);
993
994         while (fgets (line, MCC_FILE_MAX_LINE_LEN, file) != NULL) {
995                 int mcc;
996                 char *country = NULL;
997
998                 mcc = parse_mcc_mapping_line (line, &country);
999                 if (!country || mcc == 0) {
1000                         g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line);
1001                         continue;
1002                 }
1003
1004                 if (mcc == previous_mcc) {
1005                         /* g_warning ("already seen: %s", line); */
1006                         continue;
1007                 }
1008                 previous_mcc = mcc;
1009
1010                 if (!(*locale_mcc)) {
1011                         if (translated) {
1012                                 if (!g_utf8_collate (country, territory))
1013                                         *locale_mcc = mcc;
1014                         } else {
1015                                 gchar *translation = dgettext ("osso-countries", country);
1016                                 if (!g_utf8_collate (translation, territory))
1017                                         *locale_mcc = mcc;
1018                         }
1019                 }
1020         }
1021         fclose (file);
1022
1023         /* Now we fill the model */
1024         file = modest_utils_open_mcc_mapping_file (TRUE, &translated);
1025         if (!file) {
1026                 g_warning ("Could not open mcc_mapping file");
1027                 return;
1028         }
1029
1030         country_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1031         while (fgets (line, MCC_FILE_MAX_LINE_LEN, file) != NULL) {
1032
1033                 int mcc;
1034                 char *country = NULL;
1035                 GtkTreeIter iter;
1036                 const gchar *name_translated;
1037
1038                 mcc = parse_mcc_mapping_line (line, &country);
1039                 if (!country || mcc == 0) {
1040                         g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line);
1041                         continue;
1042                 }
1043
1044                 if (mcc == previous_mcc ||
1045                     g_hash_table_lookup (country_hash, country)) {
1046                         g_debug ("already seen: '%s' %d", country, mcc);
1047                         continue;
1048                 }
1049                 previous_mcc = mcc;
1050
1051                 g_hash_table_insert (country_hash, g_strdup (country), GINT_TO_POINTER (mcc));
1052
1053                 name_translated = dgettext ("osso-countries", country);
1054
1055                 /* Add the row to the model: */
1056                 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1057                 gtk_list_store_set(GTK_LIST_STORE (model), &iter, 
1058                                    MODEST_UTILS_COUNTRY_MODEL_COLUMN_MCC, mcc, 
1059                                    MODEST_UTILS_COUNTRY_MODEL_COLUMN_NAME, name_translated, 
1060                                    -1);
1061         }
1062
1063
1064         g_hash_table_unref (country_hash);
1065         fclose (file);
1066
1067         /* Fallback to Finland */
1068         if (!(*locale_mcc))
1069                 *locale_mcc = 244;
1070
1071         /* Sort the items: */
1072         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), 
1073                                               MODEST_UTILS_COUNTRY_MODEL_COLUMN_NAME, GTK_SORT_ASCENDING);
1074 }
1075
1076 GList *
1077 modest_utils_create_notification_list_from_header_list (TnyList *header_list)
1078 {
1079         GList *new_headers_list;
1080         TnyIterator *iter;
1081
1082         g_return_val_if_fail (TNY_IS_LIST (header_list), NULL);
1083         g_return_val_if_fail (tny_list_get_length (header_list) > 0, NULL);
1084
1085         new_headers_list = NULL;
1086         iter = tny_list_create_iterator (header_list);
1087         while (!tny_iterator_is_done (iter)) {
1088                 ModestMsgNotificationData *data;
1089                 TnyHeader *header;
1090                 TnyFolder *folder;
1091
1092                 header = (TnyHeader *) tny_iterator_get_current (iter);
1093                 if (header) {
1094                         folder = tny_header_get_folder (header);
1095
1096                         if (folder) {
1097                                 gchar *uri, *uid;
1098
1099                                 uid = tny_header_dup_uid (header);
1100                                 uri = g_strdup_printf ("%s/%s",
1101                                                        tny_folder_get_url_string (folder),
1102                                                        uid);
1103                                 g_free (uid);
1104
1105                                 /* Create data & add to list */
1106                                 data = g_slice_new0 (ModestMsgNotificationData);
1107                                 data->subject = tny_header_dup_subject (header);
1108                                 data->from = tny_header_dup_from (header);
1109                                 data->uri = uri;
1110
1111                                 new_headers_list = g_list_append (new_headers_list, data);
1112
1113                                 g_object_unref (folder);
1114                         }
1115                         g_object_unref (header);
1116                 }
1117                 tny_iterator_next (iter);
1118         }
1119         g_object_unref (iter);
1120
1121         return new_headers_list;
1122 }
1123
1124 static void
1125 free_notification_data (gpointer data,
1126                         gpointer user_data)
1127 {
1128         ModestMsgNotificationData *notification_data  = (ModestMsgNotificationData *) data;
1129
1130         g_free (notification_data->from);
1131         g_free (notification_data->subject);
1132         g_free (notification_data->uri);
1133 }
1134
1135 void
1136 modest_utils_free_notification_list (GList *notification_list)
1137 {
1138         g_return_if_fail (g_list_length (notification_list) > 0);
1139
1140         g_list_foreach (notification_list, free_notification_data, NULL);
1141         g_list_free (notification_list);
1142 }