Bug 792781 - 'Go to Folder' incorrectly unrefs CamelFolder twice
[evolution.git] / src / mail / e-mail-migrate.c
blob50fdf2b07c75a20b09faabf2b7a3c8ae0ca1dc38
1 /*
2 * e-mail-migrate.c
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 #include "evolution-config.h"
23 #include "e-mail-migrate.h"
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <utime.h>
31 #include <unistd.h>
32 #include <dirent.h>
33 #include <regex.h>
34 #include <errno.h>
35 #include <ctype.h>
37 #include <glib/gi18n.h>
38 #include <glib/gstdio.h>
40 #include <gtk/gtk.h>
42 #include <libxml/tree.h>
43 #include <libxml/parser.h>
44 #include <libxml/xmlmemory.h>
46 #include <shell/e-shell.h>
47 #include <shell/e-shell-migrate.h>
49 #include <libemail-engine/libemail-engine.h>
51 #include "e-mail-backend.h"
52 #include "em-utils.h"
54 #define d(x) x
56 /* 1.4 upgrade functions */
58 static GtkProgressBar *progress;
60 static void
61 em_migrate_set_progress (double percent)
63 gchar text[5];
65 snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f));
67 gtk_progress_bar_set_fraction (progress, percent);
68 gtk_progress_bar_set_text (progress, text);
70 while (gtk_events_pending ())
71 gtk_main_iteration ();
74 enum {
75 CP_UNIQUE = 0,
76 CP_OVERWRITE,
77 CP_APPEND
80 static gint open_flags[3] = {
81 O_WRONLY | O_CREAT | O_TRUNC,
82 O_WRONLY | O_CREAT | O_TRUNC,
83 O_WRONLY | O_CREAT | O_APPEND,
86 static gboolean
87 cp (const gchar *src,
88 const gchar *dest,
89 gboolean show_progress,
90 gint mode)
92 guchar readbuf[65536];
93 gssize nread, nwritten;
94 gint errnosav, readfd, writefd;
95 gsize total = 0;
96 struct stat st;
97 struct utimbuf ut;
99 /* if the dest file exists and has content, abort - we don't
100 * want to corrupt their existing data */
101 if (g_stat (dest, &st) == 0 && st.st_size > 0 && mode == CP_UNIQUE) {
102 errno = EEXIST;
103 return FALSE;
106 if (g_stat (src, &st) == -1
107 || (readfd = g_open (src, O_RDONLY | O_BINARY, 0)) == -1)
108 return FALSE;
110 if ((writefd = g_open (dest, open_flags[mode] | O_BINARY, 0666)) == -1) {
111 errnosav = errno;
112 close (readfd);
113 errno = errnosav;
114 return FALSE;
117 do {
118 do {
119 nread = read (readfd, readbuf, sizeof (readbuf));
120 } while (nread == -1 && errno == EINTR);
122 if (nread == 0)
123 break;
124 else if (nread < 0)
125 goto exception;
127 do {
128 nwritten = write (writefd, readbuf, nread);
129 } while (nwritten == -1 && errno == EINTR);
131 if (nwritten < nread)
132 goto exception;
134 total += nwritten;
135 if (show_progress)
136 em_migrate_set_progress (((gdouble) total) / ((gdouble) st.st_size));
137 } while (total < st.st_size);
139 #ifndef G_OS_WIN32
140 if (fsync (writefd) == -1)
141 goto exception;
142 #endif
144 close (readfd);
145 if (close (writefd) == -1)
146 goto failclose;
148 ut.actime = st.st_atime;
149 ut.modtime = st.st_mtime;
150 utime (dest, &ut);
151 if (chmod (dest, st.st_mode) == -1) {
152 g_warning ("%s: Failed to chmod '%s': %s", G_STRFUNC, dest, g_strerror (errno));
155 return TRUE;
157 exception:
159 errnosav = errno;
160 close (readfd);
161 close (writefd);
162 errno = errnosav;
164 failclose:
166 errnosav = errno;
167 unlink (dest);
168 errno = errnosav;
170 return FALSE;
173 static gboolean
174 emm_setup_initial (const gchar *data_dir)
176 GDir *dir;
177 const gchar *d;
178 gchar *local = NULL, *base;
179 const gchar * const *language_names;
181 /* special-case - this means brand new install of evolution */
182 /* FIXME: create default folders and stuff... */
184 d (printf ("Setting up initial mail tree\n"));
186 base = g_build_filename (data_dir, "local", NULL);
187 if (g_mkdir_with_parents (base, 0700) == -1 && errno != EEXIST) {
188 g_free (base);
189 return FALSE;
192 /* e.g. try en-AU then en, etc */
193 language_names = g_get_language_names ();
194 while (*language_names != NULL) {
195 local = g_build_filename (
196 EVOLUTION_PRIVDATADIR, "default",
197 *language_names, "mail", "local", NULL);
198 if (g_file_test (local, G_FILE_TEST_EXISTS))
199 break;
200 g_free (local);
201 language_names++;
204 /* Make sure we found one. */
205 g_return_val_if_fail (*language_names != NULL, FALSE);
207 dir = g_dir_open (local, 0, NULL);
208 if (dir) {
209 while ((d = g_dir_read_name (dir))) {
210 gchar *src, *dest;
212 src = g_build_filename (local, d, NULL);
213 dest = g_build_filename (base, d, NULL);
215 cp (src, dest, FALSE, CP_UNIQUE);
216 g_free (dest);
217 g_free (src);
219 g_dir_close (dir);
222 g_free (local);
223 g_free (base);
225 return TRUE;
228 static void
229 em_rename_view_in_folder (gpointer data,
230 gpointer user_data)
232 const gchar *filename = data;
233 const gchar *views_dir = user_data;
234 gchar *folderpos, *dotpos;
236 g_return_if_fail (filename != NULL);
237 g_return_if_fail (views_dir != NULL);
239 folderpos = strstr (filename, "-folder:__");
240 if (!folderpos)
241 folderpos = strstr (filename, "-folder___");
242 if (!folderpos)
243 return;
245 /* points on 'f' from the "folder" word */
246 folderpos++;
247 dotpos = strrchr (filename, '.');
248 if (folderpos < dotpos && g_str_equal (dotpos, ".xml")) {
249 GChecksum *checksum;
250 gchar *oldname, *newname, *newfile;
251 const gchar *md5_string;
253 *dotpos = 0;
255 /* use MD5 checksum of the folder URI, to not depend on its length */
256 checksum = g_checksum_new (G_CHECKSUM_MD5);
257 g_checksum_update (checksum, (const guchar *) folderpos, -1);
259 *folderpos = 0;
260 md5_string = g_checksum_get_string (checksum);
261 newfile = g_strconcat (filename, md5_string, ".xml", NULL);
262 *folderpos = 'f';
263 *dotpos = '.';
265 oldname = g_build_filename (views_dir, filename, NULL);
266 newname = g_build_filename (views_dir, newfile, NULL);
268 if (g_rename (oldname, newname) == -1) {
269 g_warning (
270 "%s: Failed to rename '%s' to '%s': %s", G_STRFUNC,
271 oldname, newname, g_strerror (errno));
274 g_checksum_free (checksum);
275 g_free (oldname);
276 g_free (newname);
277 g_free (newfile);
281 static void
282 em_rename_folder_views (EShellBackend *shell_backend)
284 const gchar *config_dir;
285 gchar *views_dir;
286 GDir *dir;
288 g_return_if_fail (shell_backend != NULL);
290 config_dir = e_shell_backend_get_config_dir (shell_backend);
291 views_dir = g_build_filename (config_dir, "views", NULL);
293 dir = g_dir_open (views_dir, 0, NULL);
294 if (dir) {
295 GSList *to_rename = NULL;
296 const gchar *filename;
298 while (filename = g_dir_read_name (dir), filename) {
299 if (strstr (filename, "-folder:__") ||
300 strstr (filename, "-folder___"))
301 to_rename = g_slist_prepend (to_rename, g_strdup (filename));
304 g_dir_close (dir);
306 g_slist_foreach (to_rename, em_rename_view_in_folder, views_dir);
307 g_slist_free_full (to_rename, g_free);
310 g_free (views_dir);
313 static gboolean
314 em_maybe_update_filter_rule_part (xmlNodePtr part)
316 xmlNodePtr values;
317 xmlChar *name, *value;
319 name = xmlGetProp (part, (xmlChar *) "name");
320 if (name) {
321 if (g_strcmp0 ((const gchar *) name, "completed-on") != 0) {
322 xmlFree (name);
323 return FALSE;
326 xmlFree (name);
327 } else {
328 return FALSE;
331 xmlSetProp (part, (xmlChar *) "name", (xmlChar *) "follow-up");
333 values = part->children;
334 while (values) {
335 if (g_strcmp0 ((const gchar *) values->name, "value") == 0) {
336 name = xmlGetProp (values, (xmlChar *) "name");
337 if (name) {
338 if (g_strcmp0 ((const gchar *) name, "date-spec-type") == 0) {
339 xmlSetProp (values, (xmlChar *) "name", (xmlChar *) "match-type");
341 value = xmlGetProp (values, (xmlChar *) "value");
342 if (value) {
343 if (g_strcmp0 ((const gchar *) value, "is set") == 0)
344 xmlSetProp (values, (xmlChar *) "value", (xmlChar *) "is completed");
345 else if (g_strcmp0 ((const gchar *) value, "is not set") == 0)
346 xmlSetProp (values, (xmlChar *) "value", (xmlChar *) "is not completed");
348 xmlFree (value);
352 xmlFree (name);
356 values = values->next;
359 return TRUE;
362 static void
363 em_update_filter_rules_file (const gchar *filename)
365 xmlNodePtr set, rule, root;
366 xmlDocPtr doc;
367 gboolean changed = FALSE;
369 if (!filename || !*filename || !g_file_test (filename, G_FILE_TEST_IS_REGULAR))
370 return;
372 doc = e_xml_parse_file (filename);
373 if (!doc)
374 return;
376 root = xmlDocGetRootElement (doc);
377 set = root && g_strcmp0 ((const gchar *) root->name, "filteroptions") == 0 ? root->children : NULL;
378 while (set) {
379 if (g_strcmp0 ((const gchar *) set->name, "ruleset") == 0) {
380 rule = set->children;
381 while (rule) {
382 if (g_strcmp0 ((const gchar *) rule->name, "rule") == 0) {
383 xmlNodePtr partset;
385 partset = rule->children;
386 while (partset) {
387 if (g_strcmp0 ((const gchar *) partset->name, "partset") == 0) {
388 xmlNodePtr part;
390 part = partset->children;
391 while (part) {
392 if (g_strcmp0 ((const gchar *) part->name, "part") == 0) {
393 changed = em_maybe_update_filter_rule_part (part) || changed;
396 part = part->next;
400 partset = partset->next;
404 rule = rule->next;
408 set = set->next;
411 if (changed)
412 e_xml_save_file (filename, doc);
414 xmlFreeDoc (doc);
417 static void
418 em_update_filter_rules (EShellBackend *shell_backend)
420 const gchar *config_dir;
421 gchar *filename;
423 g_return_if_fail (shell_backend != NULL);
425 config_dir = e_shell_backend_get_config_dir (shell_backend);
427 filename = g_build_filename (config_dir, "filters.xml", NULL);
428 em_update_filter_rules_file (filename);
429 g_free (filename);
431 filename = g_build_filename (config_dir, "searches.xml", NULL);
432 em_update_filter_rules_file (filename);
433 g_free (filename);
435 filename = g_build_filename (config_dir, "vfolders.xml", NULL);
436 em_update_filter_rules_file (filename);
437 g_free (filename);
440 static void
441 unset_initial_setup_write_finished_cb (GObject *source_object,
442 GAsyncResult *result,
443 gpointer user_data)
445 ESource *source;
446 GError *local_error = NULL;
448 g_return_if_fail (E_IS_SOURCE (source_object));
449 g_return_if_fail (result != NULL);
451 source = E_SOURCE (source_object);
453 if (!e_source_write_finish (source, result, &local_error)) {
454 g_warning ("%s: Failed to save source '%s' (%s): %s", G_STRFUNC, e_source_get_uid (source),
455 e_source_get_display_name (source), local_error ? local_error->message : "Unknown error");
458 g_clear_error (&local_error);
461 static void
462 em_unset_initial_setup_for_accounts (EShellBackend *shell_backend)
464 ESourceRegistry *registry;
465 GList *sources, *link;
467 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
469 registry = e_shell_get_registry (e_shell_backend_get_shell (shell_backend));
470 sources = e_source_registry_list_sources (registry, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
472 for (link = sources; link; link = g_list_next (link)) {
473 ESource *source = link->data;
474 ESourceMailAccount *mail_account;
476 mail_account = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
477 if (e_source_mail_account_get_needs_initial_setup (mail_account)) {
478 e_source_mail_account_set_needs_initial_setup (mail_account, FALSE);
480 e_source_write (source, NULL, unset_initial_setup_write_finished_cb, NULL);
484 g_list_free_full (sources, g_object_unref);
487 gboolean
488 e_mail_migrate (EShellBackend *shell_backend,
489 gint major,
490 gint minor,
491 gint micro,
492 GError **error)
494 const gchar *data_dir;
496 data_dir = e_shell_backend_get_data_dir (shell_backend);
498 if (major == 0)
499 return emm_setup_initial (data_dir);
501 if (major <= 2 || (major == 3 && minor < 4))
502 em_rename_folder_views (shell_backend);
504 if (major <= 2 || (major == 3 && minor < 17))
505 em_update_filter_rules (shell_backend);
507 if (major <= 2 || (major == 3 && minor < 19) || (major == 3 && minor == 19 && micro < 90))
508 em_unset_initial_setup_for_accounts (shell_backend);
510 return TRUE;