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