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