1 /*
2 * e-mail-migrate.c
3 *
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.
7 *
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.
12 *
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/>.
15 *
16 *
17 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
18 *
19 */
20
21 #include "evolution-config.h"
22
23 #include "e-mail-migrate.h"
24
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>
36
37 #include <glib/gi18n.h>
38 #include <glib/gstdio.h>
39
40 #include <gtk/gtk.h>
41
42 #include <libxml/tree.h>
43 #include <libxml/parser.h>
44 #include <libxml/xmlmemory.h>
45
46 #include <shell/e-shell.h>
47 #include <shell/e-shell-migrate.h>
48
49 #include <libemail-engine/libemail-engine.h>
50
51 #include "e-mail-backend.h"
52 #include "em-utils.h"
53
54 #define d(x) x
55
56 /* 1.4 upgrade functions */
57
58 static GtkProgressBar *progress;
59
60 static void
em_migrate_set_progress(double percent)61 em_migrate_set_progress (double percent)
62 {
63 gchar text[5];
64
65 snprintf (text, sizeof (text), "%d%%", (gint) (percent * 100.0f));
66
67 gtk_progress_bar_set_fraction (progress, percent);
68 gtk_progress_bar_set_text (progress, text);
69
70 while (gtk_events_pending ())
71 gtk_main_iteration ();
72 }
73
74 enum {
75 CP_UNIQUE = 0,
76 CP_OVERWRITE,
77 CP_APPEND
78 };
79
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,
84 };
85
86 static gboolean
cp(const gchar * src,const gchar * dest,gboolean show_progress,gint mode)87 cp (const gchar *src,
88 const gchar *dest,
89 gboolean show_progress,
90 gint mode)
91 {
92 const gint nreadbuf = 65535;
93 guchar *readbuf = NULL;
94 gssize nread, nwritten;
95 gint errnosav, readfd, writefd;
96 gsize total = 0;
97 struct stat st;
98 struct utimbuf ut;
99
100 /* if the dest file exists and has content, abort - we don't
101 * want to corrupt their existing data */
102 if (g_stat (dest, &st) == 0 && st.st_size > 0 && mode == CP_UNIQUE) {
103 errno = EEXIST;
104 return FALSE;
105 }
106
107 if (g_stat (src, &st) == -1
108 || (readfd = g_open (src, O_RDONLY | O_BINARY, 0)) == -1)
109 return FALSE;
110
111 if ((writefd = g_open (dest, open_flags[mode] | O_BINARY, 0666)) == -1) {
112 errnosav = errno;
113 close (readfd);
114 errno = errnosav;
115 return FALSE;
116 }
117
118 readbuf = g_new0 (guchar, nreadbuf);
119 do {
120 do {
121 nread = read (readfd, readbuf, nreadbuf);
122 } while (nread == -1 && errno == EINTR);
123
124 if (nread == 0)
125 break;
126 else if (nread < 0)
127 goto exception;
128
129 do {
130 nwritten = write (writefd, readbuf, nread);
131 } while (nwritten == -1 && errno == EINTR);
132
133 if (nwritten < nread)
134 goto exception;
135
136 total += nwritten;
137 if (show_progress)
138 em_migrate_set_progress (((gdouble) total) / ((gdouble) st.st_size));
139 } while (total < st.st_size);
140
141 #ifndef G_OS_WIN32
142 if (fsync (writefd) == -1)
143 goto exception;
144 #endif
145
146 close (readfd);
147 if (close (writefd) == -1)
148 goto failclose;
149
150 ut.actime = st.st_atime;
151 ut.modtime = st.st_mtime;
152 utime (dest, &ut);
153 if (chmod (dest, st.st_mode) == -1) {
154 g_warning ("%s: Failed to chmod '%s': %s", G_STRFUNC, dest, g_strerror (errno));
155 }
156
157 g_free (readbuf);
158
159 return TRUE;
160
161 exception:
162
163 errnosav = errno;
164 close (readfd);
165 close (writefd);
166 errno = errnosav;
167
168 failclose:
169
170 errnosav = errno;
171 unlink (dest);
172 errno = errnosav;
173
174 g_free (readbuf);
175
176 return FALSE;
177 }
178
179 static gboolean
emm_setup_initial(const gchar * data_dir)180 emm_setup_initial (const gchar *data_dir)
181 {
182 GDir *dir;
183 const gchar *d;
184 gchar *local = NULL, *base;
185 const gchar * const *language_names;
186
187 /* special-case - this means brand new install of evolution */
188 /* FIXME: create default folders and stuff... */
189
190 d (printf ("Setting up initial mail tree\n"));
191
192 base = g_build_filename (data_dir, "local", NULL);
193 if (g_mkdir_with_parents (base, 0700) == -1 && errno != EEXIST) {
194 g_free (base);
195 return FALSE;
196 }
197
198 /* e.g. try en-AU then en, etc */
199 language_names = g_get_language_names ();
200 while (*language_names != NULL) {
201 local = g_build_filename (
202 EVOLUTION_PRIVDATADIR, "default",
203 *language_names, "mail", "local", NULL);
204 if (g_file_test (local, G_FILE_TEST_EXISTS))
205 break;
206 g_free (local);
207 local = NULL;
208 language_names++;
209 }
210
211 /* Make sure we found one. */
212 g_return_val_if_fail (local != NULL, FALSE);
213
214 dir = g_dir_open (local, 0, NULL);
215 if (dir) {
216 while ((d = g_dir_read_name (dir))) {
217 gchar *src, *dest;
218
219 src = g_build_filename (local, d, NULL);
220 dest = g_build_filename (base, d, NULL);
221
222 cp (src, dest, FALSE, CP_UNIQUE);
223 g_free (dest);
224 g_free (src);
225 }
226 g_dir_close (dir);
227 }
228
229 g_free (local);
230 g_free (base);
231
232 return TRUE;
233 }
234
235 static void
em_rename_view_in_folder(gpointer data,gpointer user_data)236 em_rename_view_in_folder (gpointer data,
237 gpointer user_data)
238 {
239 const gchar *filename = data;
240 const gchar *views_dir = user_data;
241 gchar *folderpos, *dotpos;
242
243 g_return_if_fail (filename != NULL);
244 g_return_if_fail (views_dir != NULL);
245
246 folderpos = strstr (filename, "-folder:__");
247 if (!folderpos)
248 folderpos = strstr (filename, "-folder___");
249 if (!folderpos)
250 return;
251
252 /* points on 'f' from the "folder" word */
253 folderpos++;
254 dotpos = strrchr (filename, '.');
255 if (folderpos < dotpos && g_str_equal (dotpos, ".xml")) {
256 GChecksum *checksum;
257 gchar *oldname, *newname, *newfile;
258 const gchar *md5_string;
259
260 *dotpos = 0;
261
262 /* use MD5 checksum of the folder URI, to not depend on its length */
263 checksum = g_checksum_new (G_CHECKSUM_MD5);
264 g_checksum_update (checksum, (const guchar *) folderpos, -1);
265
266 *folderpos = 0;
267 md5_string = g_checksum_get_string (checksum);
268 newfile = g_strconcat (filename, md5_string, ".xml", NULL);
269 *folderpos = 'f';
270 *dotpos = '.';
271
272 oldname = g_build_filename (views_dir, filename, NULL);
273 newname = g_build_filename (views_dir, newfile, NULL);
274
275 if (g_rename (oldname, newname) == -1) {
276 g_warning (
277 "%s: Failed to rename '%s' to '%s': %s", G_STRFUNC,
278 oldname, newname, g_strerror (errno));
279 }
280
281 g_checksum_free (checksum);
282 g_free (oldname);
283 g_free (newname);
284 g_free (newfile);
285 }
286 }
287
288 static void
em_rename_folder_views(EShellBackend * shell_backend)289 em_rename_folder_views (EShellBackend *shell_backend)
290 {
291 const gchar *config_dir;
292 gchar *views_dir;
293 GDir *dir;
294
295 g_return_if_fail (shell_backend != NULL);
296
297 config_dir = e_shell_backend_get_config_dir (shell_backend);
298 views_dir = g_build_filename (config_dir, "views", NULL);
299
300 dir = g_dir_open (views_dir, 0, NULL);
301 if (dir) {
302 GSList *to_rename = NULL;
303 const gchar *filename;
304
305 while (filename = g_dir_read_name (dir), filename) {
306 if (strstr (filename, "-folder:__") ||
307 strstr (filename, "-folder___"))
308 to_rename = g_slist_prepend (to_rename, g_strdup (filename));
309 }
310
311 g_dir_close (dir);
312
313 g_slist_foreach (to_rename, em_rename_view_in_folder, views_dir);
314 g_slist_free_full (to_rename, g_free);
315 }
316
317 g_free (views_dir);
318 }
319
320 static gboolean
em_maybe_update_filter_rule_part(xmlNodePtr part)321 em_maybe_update_filter_rule_part (xmlNodePtr part)
322 {
323 xmlNodePtr values;
324 xmlChar *name, *value;
325
326 name = xmlGetProp (part, (xmlChar *) "name");
327 if (name) {
328 if (g_strcmp0 ((const gchar *) name, "completed-on") != 0) {
329 xmlFree (name);
330 return FALSE;
331 }
332
333 xmlFree (name);
334 } else {
335 return FALSE;
336 }
337
338 xmlSetProp (part, (xmlChar *) "name", (xmlChar *) "follow-up");
339
340 values = part->children;
341 while (values) {
342 if (g_strcmp0 ((const gchar *) values->name, "value") == 0) {
343 name = xmlGetProp (values, (xmlChar *) "name");
344 if (name) {
345 if (g_strcmp0 ((const gchar *) name, "date-spec-type") == 0) {
346 xmlSetProp (values, (xmlChar *) "name", (xmlChar *) "match-type");
347
348 value = xmlGetProp (values, (xmlChar *) "value");
349 if (value) {
350 if (g_strcmp0 ((const gchar *) value, "is set") == 0)
351 xmlSetProp (values, (xmlChar *) "value", (xmlChar *) "is completed");
352 else if (g_strcmp0 ((const gchar *) value, "is not set") == 0)
353 xmlSetProp (values, (xmlChar *) "value", (xmlChar *) "is not completed");
354
355 xmlFree (value);
356 }
357 }
358
359 xmlFree (name);
360 }
361 }
362
363 values = values->next;
364 }
365
366 return TRUE;
367 }
368
369 static void
em_update_filter_rules_file(const gchar * filename)370 em_update_filter_rules_file (const gchar *filename)
371 {
372 xmlNodePtr set, rule, root;
373 xmlDocPtr doc;
374 gboolean changed = FALSE;
375
376 if (!filename || !*filename || !g_file_test (filename, G_FILE_TEST_IS_REGULAR))
377 return;
378
379 doc = e_xml_parse_file (filename);
380 if (!doc)
381 return;
382
383 root = xmlDocGetRootElement (doc);
384 set = root && g_strcmp0 ((const gchar *) root->name, "filteroptions") == 0 ? root->children : NULL;
385 while (set) {
386 if (g_strcmp0 ((const gchar *) set->name, "ruleset") == 0) {
387 rule = set->children;
388 while (rule) {
389 if (g_strcmp0 ((const gchar *) rule->name, "rule") == 0) {
390 xmlNodePtr partset;
391
392 partset = rule->children;
393 while (partset) {
394 if (g_strcmp0 ((const gchar *) partset->name, "partset") == 0) {
395 xmlNodePtr part;
396
397 part = partset->children;
398 while (part) {
399 if (g_strcmp0 ((const gchar *) part->name, "part") == 0) {
400 changed = em_maybe_update_filter_rule_part (part) || changed;
401 }
402
403 part = part->next;
404 }
405 }
406
407 partset = partset->next;
408 }
409 }
410
411 rule = rule->next;
412 }
413 }
414
415 set = set->next;
416 }
417
418 if (changed)
419 e_xml_save_file (filename, doc);
420
421 xmlFreeDoc (doc);
422 }
423
424 static void
em_update_filter_rules(EShellBackend * shell_backend)425 em_update_filter_rules (EShellBackend *shell_backend)
426 {
427 const gchar *config_dir;
428 gchar *filename;
429
430 g_return_if_fail (shell_backend != NULL);
431
432 config_dir = e_shell_backend_get_config_dir (shell_backend);
433
434 filename = g_build_filename (config_dir, "filters.xml", NULL);
435 em_update_filter_rules_file (filename);
436 g_free (filename);
437
438 filename = g_build_filename (config_dir, "searches.xml", NULL);
439 em_update_filter_rules_file (filename);
440 g_free (filename);
441
442 filename = g_build_filename (config_dir, "vfolders.xml", NULL);
443 em_update_filter_rules_file (filename);
444 g_free (filename);
445 }
446
447 static void
unset_initial_setup_write_finished_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)448 unset_initial_setup_write_finished_cb (GObject *source_object,
449 GAsyncResult *result,
450 gpointer user_data)
451 {
452 ESource *source;
453 GError *local_error = NULL;
454
455 g_return_if_fail (E_IS_SOURCE (source_object));
456 g_return_if_fail (result != NULL);
457
458 source = E_SOURCE (source_object);
459
460 if (!e_source_write_finish (source, result, &local_error)) {
461 g_warning ("%s: Failed to save source '%s' (%s): %s", G_STRFUNC, e_source_get_uid (source),
462 e_source_get_display_name (source), local_error ? local_error->message : "Unknown error");
463 }
464
465 g_clear_error (&local_error);
466 }
467
468 static void
em_unset_initial_setup_for_accounts(EShellBackend * shell_backend)469 em_unset_initial_setup_for_accounts (EShellBackend *shell_backend)
470 {
471 ESourceRegistry *registry;
472 GList *sources, *link;
473
474 g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
475
476 registry = e_shell_get_registry (e_shell_backend_get_shell (shell_backend));
477 sources = e_source_registry_list_sources (registry, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
478
479 for (link = sources; link; link = g_list_next (link)) {
480 ESource *source = link->data;
481 ESourceMailAccount *mail_account;
482
483 mail_account = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
484 if (e_source_mail_account_get_needs_initial_setup (mail_account)) {
485 e_source_mail_account_set_needs_initial_setup (mail_account, FALSE);
486
487 e_source_write (source, NULL, unset_initial_setup_write_finished_cb, NULL);
488 }
489 }
490
491 g_list_free_full (sources, g_object_unref);
492 }
493
494 /* The default value for this key changed from 'false' to 'true' in 3.27.90,
495 but existing users can be affected by the change when they never changed
496 the option, thus make sure their value will remain 'false' here. */
497 static void
em_ensure_global_view_setting_key(EShellBackend * shell_backend)498 em_ensure_global_view_setting_key (EShellBackend *shell_backend)
499 {
500 GSettings *settings;
501 GVariant *value;
502
503 settings = e_util_ref_settings ("org.gnome.evolution.mail");
504
505 value = g_settings_get_user_value (settings, "global-view-setting");
506 if (value)
507 g_variant_unref (value);
508 else
509 g_settings_set_boolean (settings, "global-view-setting", FALSE);
510
511 g_clear_object (&settings);
512 }
513
514 gboolean
e_mail_migrate(EShellBackend * shell_backend,gint major,gint minor,gint micro,GError ** error)515 e_mail_migrate (EShellBackend *shell_backend,
516 gint major,
517 gint minor,
518 gint micro,
519 GError **error)
520 {
521 const gchar *data_dir;
522
523 data_dir = e_shell_backend_get_data_dir (shell_backend);
524
525 if (major == 0)
526 return emm_setup_initial (data_dir);
527
528 if (major <= 2 || (major == 3 && minor < 4))
529 em_rename_folder_views (shell_backend);
530
531 if (major <= 2 || (major == 3 && minor < 17))
532 em_update_filter_rules (shell_backend);
533
534 if (major <= 2 || (major == 3 && minor < 19) || (major == 3 && minor == 19 && micro < 90))
535 em_unset_initial_setup_for_accounts (shell_backend);
536
537 if (major <= 2 || (major == 3 && minor < 27) || (major == 3 && minor == 27 && micro < 90))
538 em_ensure_global_view_setting_key (shell_backend);
539
540 return TRUE;
541 }
542