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