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