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