afd91ebfa9f618d29057c182d48166757102109d
[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 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;
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_message ("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