1 /*
2 * evolution-backup-tool.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
18 #include "evolution-config.h"
19
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <glib/gi18n.h>
26 #include <glib/gstdio.h>
27 #include <gtk/gtk.h>
28
29 #include <libedataserver/libedataserver.h>
30
31 #ifdef G_OS_WIN32
32 #ifdef DATADIR
33 #undef DATADIR
34 #endif
35 #endif
36
37 #include "e-util/e-util-private.h"
38 #include "e-util/e-util.h"
39
40 #define EVOUSERDATADIR_MAGIC "#EVO_USERDATADIR#"
41
42 #define EVOLUTION "evolution"
43 #define EVOLUTION_DIR "$DATADIR/"
44 #define EVOLUTION_DIR_FILE EVOLUTION ".dir"
45
46 #define ANCIENT_GCONF_DUMP_FILE "backup-restore-gconf.xml"
47
48 #define ANCIENT_DCONF_DUMP_FILE_EDS "backup-restore-dconf-eds.ini"
49 #define ANCIENT_DCONF_DUMP_FILE_EVO "backup-restore-dconf-evo.ini"
50
51 #define ANCIENT_DCONF_PATH_EDS "/org/gnome/evolution-data-server/"
52 #define ANCIENT_DCONF_PATH_EVO "/org/gnome/evolution/"
53
54 #define GSETTINGS_DUMP_FILE "backup-restore-gsettings.ini"
55
56 #define KEY_FILE_GROUP "Evolution Backup"
57
58 static gboolean backup_op = FALSE;
59 static gchar *bk_file = NULL;
60 static gboolean restore_op = FALSE;
61 static gchar *res_file = NULL;
62 static gboolean check_op = FALSE;
63 static gchar *chk_file = NULL;
64 static gboolean restart_arg = FALSE;
65 static gboolean gui_arg = FALSE;
66 static gchar **opt_remaining = NULL;
67 static gint result = 0;
68 static GtkWidget *progress_dialog;
69 static GtkWidget *pbar;
70 static gchar *txt = NULL;
71
72 static GOptionEntry options[] = {
73 { "backup", '\0', 0, G_OPTION_ARG_NONE, &backup_op,
74 N_("Back up Evolution directory"), NULL },
75 { "restore", '\0', 0, G_OPTION_ARG_NONE, &restore_op,
76 N_("Restore Evolution directory"), NULL },
77 { "check", '\0', 0, G_OPTION_ARG_NONE, &check_op,
78 N_("Check Evolution Back up"), NULL },
79 { "restart", '\0', 0, G_OPTION_ARG_NONE, &restart_arg,
80 N_("Restart Evolution"), NULL },
81 { "gui", '\0', 0, G_OPTION_ARG_NONE, &gui_arg,
82 N_("With Graphical User Interface"), NULL },
83 { G_OPTION_REMAINING, '\0', 0,
84 G_OPTION_ARG_STRING_ARRAY, &opt_remaining },
85 { NULL }
86 };
87
88 #define d(x)
89
90 #define print_and_run(x) \
91 G_STMT_START { g_message ("%s", x); if (system (x) == -1) g_warning ("%s: Failed to execute '%s'", G_STRFUNC, (x)); } G_STMT_END
92
93 static gboolean check (const gchar *filename, gboolean *is_new_format);
94
95 static const gchar *
strip_home_dir(const gchar * dir)96 strip_home_dir (const gchar *dir)
97 {
98 const gchar *home_dir, *res;
99
100 g_return_val_if_fail (dir != NULL, NULL);
101
102 home_dir = g_get_home_dir ();
103 g_return_val_if_fail (home_dir != NULL, dir);
104 g_return_val_if_fail (*home_dir != '\0', dir);
105
106 res = dir;
107 if (g_str_has_prefix (res, home_dir))
108 res += strlen (home_dir);
109
110 if (*res == G_DIR_SEPARATOR)
111 res++;
112
113 return res;
114 }
115
116 static GString *
replace_variables(const gchar * str,gboolean remove_dir_sep)117 replace_variables (const gchar *str,
118 gboolean remove_dir_sep)
119 {
120 GString *res = NULL, *use;
121 const gchar *strip_datadir, *strip_configdir;
122
123 g_return_val_if_fail (str != NULL, NULL);
124
125 strip_datadir = strip_home_dir (e_get_user_data_dir ());
126 strip_configdir = strip_home_dir (e_get_user_config_dir ());
127
128 #define repl(_find, _replace) \
129 use = e_str_replace_string (res ? res->str : str, _find, _replace); \
130 g_return_val_if_fail (use != NULL, NULL); \
131 if (res) \
132 g_string_free (res, TRUE); \
133 res = use;
134
135 repl ("$HOME", g_get_home_dir ());
136 repl ("$TMP", g_get_tmp_dir ());
137 repl ("$DATADIR", e_get_user_data_dir ());
138 repl ("$CONFIGDIR", e_get_user_config_dir ());
139 repl ("$STRIPDATADIR", strip_datadir);
140 repl ("$STRIPCONFIGDIR", strip_configdir);
141 repl ("$DBUSDATADIR", DBUS_SERVICES_DIR);
142
143 #undef repl
144
145 g_return_val_if_fail (res != NULL, NULL);
146
147 if (remove_dir_sep) {
148 /* remove trailing dir separator */
149 while (res->len > 0 && res->str[res->len - 1] == G_DIR_SEPARATOR) {
150 g_string_truncate (res, res->len - 1);
151 }
152 }
153
154 return res;
155 }
156
157 static void
replace_in_file(const gchar * filename,const gchar * find,const gchar * replace)158 replace_in_file (const gchar *filename,
159 const gchar *find,
160 const gchar *replace)
161 {
162 gchar *content = NULL;
163 GError *error = NULL;
164 GString *filenamestr = NULL;
165
166 g_return_if_fail (filename != NULL);
167 g_return_if_fail (find != NULL);
168 g_return_if_fail (*find);
169 g_return_if_fail (replace != NULL);
170
171 if (strstr (filename, "$")) {
172 filenamestr = replace_variables (filename, TRUE);
173
174 if (!filenamestr) {
175 g_warning (
176 "%s: Replace variables in '%s' failed!",
177 G_STRFUNC, filename);
178 return;
179 }
180
181 filename = filenamestr->str;
182 }
183
184 if (g_file_get_contents (filename, &content, NULL, &error)) {
185 GString *str = e_str_replace_string (content, find, replace);
186
187 if (str) {
188 if (!g_file_set_contents (filename, str->str, -1, &error) && error) {
189 g_warning (
190 "%s: cannot write file content, "
191 "error: %s", G_STRFUNC, error->message);
192 g_error_free (error);
193 }
194
195 g_string_free (str, TRUE);
196 } else {
197 g_warning (
198 "%s: Replace of '%s' to '%s' failed!",
199 G_STRFUNC, find, replace);
200 }
201
202 g_free (content);
203
204 } else if (error != NULL) {
205 g_warning (
206 "%s: Cannot read file content, error: %s",
207 G_STRFUNC, error->message);
208 g_error_free (error);
209 }
210
211 if (filenamestr)
212 g_string_free (filenamestr, TRUE);
213 }
214
215 static void
run_cmd(const gchar * cmd)216 run_cmd (const gchar *cmd)
217 {
218 if (!cmd)
219 return;
220
221 if (strstr (cmd, "$") != NULL) {
222 /* read the doc for g_get_home_dir to know why replacing it here */
223 GString *str = replace_variables (cmd, FALSE);
224
225 if (str) {
226 print_and_run (str->str);
227 g_string_free (str, TRUE);
228 }
229 } else
230 print_and_run (cmd);
231 }
232
233 static void
run_evolution_no_wait(void)234 run_evolution_no_wait (void)
235 {
236 g_spawn_command_line_async (EVOLUTION, NULL);
237 }
238
239 static void
write_dir_file(void)240 write_dir_file (void)
241 {
242 GString *content, *filename;
243 GError *error = NULL;
244
245 filename = replace_variables ("$HOME/" EVOLUTION_DIR_FILE, TRUE);
246 g_return_if_fail (filename != NULL);
247
248 content = replace_variables (
249 "[" KEY_FILE_GROUP "]\n"
250 "Version=" VERSION "\n"
251 "UserDataDir=$STRIPDATADIR\n"
252 "UserConfigDir=$STRIPCONFIGDIR\n"
253 , TRUE);
254 g_return_if_fail (content != NULL);
255
256 g_file_set_contents (filename->str, content->str, content->len, &error);
257
258 if (error != NULL) {
259 g_warning ("Failed to write file '%s': %s\n", filename->str, error->message);
260 g_error_free (error);
261 }
262
263 g_string_free (filename, TRUE);
264 g_string_free (content, TRUE);
265 }
266
267 static gboolean
get_filename_is_xz(const gchar * filename)268 get_filename_is_xz (const gchar *filename)
269 {
270 gint len;
271
272 if (!filename)
273 return FALSE;
274
275 len = strlen (filename);
276 if (len < 3)
277 return FALSE;
278
279 return g_ascii_strcasecmp (filename + len - 3, ".xz") == 0;
280 }
281
282 typedef gboolean (* SettingsFunc) (GSettings *settings,
283 GSettingsSchema *schema,
284 const gchar *group,
285 const gchar *key,
286 GKeyFile *keyfile);
287
288 static gboolean
settings_foreach_schema_traverse(GSettings * settings,SettingsFunc func,GKeyFile * keyfile)289 settings_foreach_schema_traverse (GSettings *settings,
290 SettingsFunc func,
291 GKeyFile *keyfile)
292 {
293 GSettingsSchema *schema = NULL;
294 const gchar *group;
295 gchar *schema_id = NULL, *path = NULL, *group_tmp = NULL;
296 gchar **strv;
297 gint ii;
298 gboolean need_sync = FALSE;
299
300 g_object_get (G_OBJECT (settings),
301 "settings-schema", &schema,
302 "schema-id", &schema_id,
303 "path", &path,
304 NULL);
305
306 if (!g_settings_schema_get_path (schema)) {
307 group_tmp = g_strconcat (schema_id, ":", path, NULL);
308 group = group_tmp;
309 } else {
310 group = schema_id;
311 }
312
313 strv = g_settings_schema_list_keys (schema);
314
315 for (ii = 0; strv && strv[ii]; ii++) {
316 need_sync = func (settings, schema, group, strv[ii], keyfile) || need_sync;
317 }
318
319 g_strfreev (strv);
320
321 strv = g_settings_schema_list_children (schema);
322 for (ii = 0; strv && strv[ii]; ii++) {
323 GSettings *child;
324
325 child = g_settings_get_child (settings, strv[ii]);
326 if (child) {
327 if (settings_foreach_schema_traverse (child, func, keyfile))
328 g_settings_sync ();
329 g_object_unref (child);
330 }
331 }
332
333 g_strfreev (strv);
334
335 g_settings_schema_unref (schema);
336 g_free (group_tmp);
337 g_free (schema_id);
338 g_free (path);
339
340 return need_sync;
341 }
342
343 static gboolean
settings_foreach_schema(SettingsFunc func,GKeyFile * keyfile,GError ** error)344 settings_foreach_schema (SettingsFunc func,
345 GKeyFile *keyfile,
346 GError **error)
347 {
348 GSettingsSchemaSource *schema_source;
349 gchar **non_relocatable = NULL, **relocatable = NULL;
350 gint ii;
351
352 schema_source = g_settings_schema_source_get_default ();
353 if (!schema_source) {
354 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No GSettings schema found");
355 return FALSE;
356 }
357
358 g_settings_schema_source_list_schemas (schema_source, TRUE, &non_relocatable, &relocatable);
359
360 for (ii = 0; non_relocatable && non_relocatable[ii]; ii++) {
361 if (g_str_has_prefix (non_relocatable[ii], "org.gnome.evolution")) {
362 GSettings *settings;
363
364 settings = g_settings_new (non_relocatable[ii]);
365 if (settings_foreach_schema_traverse (settings, func, keyfile))
366 g_settings_sync ();
367 g_object_unref (settings);
368 }
369 }
370
371 g_strfreev (non_relocatable);
372 g_strfreev (relocatable);
373
374 return TRUE;
375 }
376
377 static gboolean
backup_settings_foreach_cb(GSettings * settings,GSettingsSchema * schema,const gchar * group,const gchar * key,GKeyFile * keyfile)378 backup_settings_foreach_cb (GSettings *settings,
379 GSettingsSchema *schema,
380 const gchar *group,
381 const gchar *key,
382 GKeyFile *keyfile)
383 {
384 GVariant *variant;
385
386 variant = g_settings_get_user_value (settings, key);
387
388 if (variant) {
389 gchar *tmp;
390
391 tmp = g_variant_print (variant, TRUE);
392 g_key_file_set_string (keyfile, group, key, tmp);
393 g_free (tmp);
394
395 g_variant_unref (variant);
396 }
397
398 return FALSE;
399 }
400
401 static gboolean
backup_settings(const gchar * to_filename,GError ** error)402 backup_settings (const gchar *to_filename,
403 GError **error)
404 {
405 GKeyFile *keyfile;
406 GString *filename;
407 gboolean success;
408
409 filename = replace_variables (to_filename, TRUE);
410
411 if (!filename) {
412 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to construct file name");
413 return FALSE;
414 }
415
416 keyfile = g_key_file_new ();
417
418 if (!settings_foreach_schema (backup_settings_foreach_cb, keyfile, error)) {
419 g_string_free (filename, TRUE);
420 g_key_file_free (keyfile);
421 return FALSE;
422 }
423
424 success = g_key_file_save_to_file (keyfile, filename->str, error);
425
426 g_string_free (filename, TRUE);
427 g_key_file_free (keyfile);
428
429 return success;
430 }
431
432 static gboolean
restore_settings_foreach_cb(GSettings * settings,GSettingsSchema * schema,const gchar * group,const gchar * key,GKeyFile * keyfile)433 restore_settings_foreach_cb (GSettings *settings,
434 GSettingsSchema *schema,
435 const gchar *group,
436 const gchar *key,
437 GKeyFile *keyfile)
438 {
439 gchar *value;
440
441 if (!g_settings_is_writable (settings, key))
442 return FALSE;
443
444 value = g_key_file_get_string (keyfile, group, key, NULL);
445
446 if (value) {
447 GVariant *variant;
448
449 variant = g_variant_parse (NULL, value, NULL, NULL, NULL);
450
451 if (variant) {
452 GSettingsSchemaKey *schema_key;
453
454 schema_key = g_settings_schema_get_key (schema, key);
455
456 if (g_settings_schema_key_range_check (schema_key, variant))
457 g_settings_set_value (settings, key, variant);
458
459 g_settings_schema_key_unref (schema_key);
460 g_variant_unref (variant);
461 }
462
463 g_free (value);
464 } else {
465 g_settings_reset (settings, key);
466 }
467
468 return TRUE;
469 }
470
471 static gboolean
restore_settings(const gchar * from_filename,GError ** error)472 restore_settings (const gchar *from_filename,
473 GError **error)
474 {
475 GKeyFile *keyfile;
476 GString *filename;
477 gboolean success;
478
479 filename = replace_variables (from_filename, TRUE);
480
481 if (!filename) {
482 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to construct file name");
483 return FALSE;
484 }
485
486 keyfile = g_key_file_new ();
487
488 if (!g_key_file_load_from_file (keyfile, filename->str, G_KEY_FILE_NONE, error)) {
489 g_string_free (filename, TRUE);
490 g_key_file_free (keyfile);
491 return FALSE;
492 }
493
494 success = settings_foreach_schema (restore_settings_foreach_cb, keyfile, error);
495
496 g_string_free (filename, TRUE);
497 g_key_file_free (keyfile);
498
499 return success;
500 }
501
502 static void
backup(const gchar * filename,GCancellable * cancellable)503 backup (const gchar *filename,
504 GCancellable *cancellable)
505 {
506 gchar *command;
507 gchar *quotedfname;
508 gboolean use_xz;
509 GError *error = NULL;
510
511 g_return_if_fail (filename && *filename);
512
513 if (g_cancellable_is_cancelled (cancellable))
514 return;
515
516 txt = _("Shutting down Evolution");
517 /* FIXME Will the versioned setting always work? */
518 run_cmd (EVOLUTION " --quit");
519
520 run_cmd ("rm $CONFIGDIR/.running");
521
522 if (g_cancellable_is_cancelled (cancellable))
523 return;
524
525 txt = _("Backing Evolution accounts and settings");
526 if (!backup_settings (EVOLUTION_DIR GSETTINGS_DUMP_FILE, &error)) {
527 g_warning ("Failed to backup settings: %s", error ? error->message : "Unknown error");
528 g_clear_error (&error);
529 }
530
531 replace_in_file (
532 EVOLUTION_DIR GSETTINGS_DUMP_FILE,
533 e_get_user_data_dir (), EVOUSERDATADIR_MAGIC);
534
535 write_dir_file ();
536
537 if (g_cancellable_is_cancelled (cancellable))
538 return;
539
540 txt = _("Backing Evolution data (Mails, Contacts, Calendar, Tasks, Memos)");
541
542 quotedfname = g_shell_quote (filename);
543 use_xz = get_filename_is_xz (filename);
544
545 command = g_strdup_printf (
546 "cd $HOME && tar chf - $STRIPDATADIR "
547 "$STRIPCONFIGDIR " EVOLUTION_DIR_FILE " | "
548 "%s > %s", use_xz ? "xz -z" : "gzip", quotedfname);
549 run_cmd (command);
550
551 g_free (command);
552 g_free (quotedfname);
553
554 run_cmd ("rm $HOME/" EVOLUTION_DIR_FILE);
555
556 txt = _("Back up complete");
557
558 if (restart_arg) {
559
560 if (g_cancellable_is_cancelled (cancellable))
561 return;
562
563 txt = _("Restarting Evolution");
564 run_evolution_no_wait ();
565 }
566
567 }
568
569 static void
extract_backup_data(const gchar * filename,gchar ** restored_version,gchar ** data_dir,gchar ** config_dir)570 extract_backup_data (const gchar *filename,
571 gchar **restored_version,
572 gchar **data_dir,
573 gchar **config_dir)
574 {
575 GKeyFile *key_file;
576 GError *error = NULL;
577
578 g_return_if_fail (filename != NULL);
579 g_return_if_fail (data_dir != NULL);
580 g_return_if_fail (config_dir != NULL);
581
582 key_file = g_key_file_new ();
583 g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &error);
584
585 if (error != NULL) {
586 g_warning ("Failed to read '%s': %s", filename, error->message);
587 g_error_free (error);
588
589 /* This is the current format as of Evolution 3.6. */
590 } else if (g_key_file_has_group (key_file, KEY_FILE_GROUP)) {
591 gchar *tmp;
592
593 tmp = g_key_file_get_value (
594 key_file, KEY_FILE_GROUP, "Version", NULL);
595 if (tmp != NULL)
596 *restored_version = g_strstrip (g_strdup (tmp));
597 g_free (tmp);
598
599 tmp = g_key_file_get_value (
600 key_file, KEY_FILE_GROUP, "UserDataDir", NULL);
601 if (tmp != NULL)
602 *data_dir = g_shell_quote (tmp);
603 g_free (tmp);
604
605 tmp = g_key_file_get_value (
606 key_file, KEY_FILE_GROUP, "UserConfigDir", NULL);
607 if (tmp != NULL)
608 *config_dir = g_shell_quote (tmp);
609 g_free (tmp);
610
611 /* This is the legacy format with no version information. */
612 } else if (g_key_file_has_group (key_file, "dirs")) {
613 gchar *tmp;
614
615 tmp = g_key_file_get_value (key_file, "dirs", "data", NULL);
616 if (tmp)
617 *data_dir = g_shell_quote (tmp);
618 g_free (tmp);
619
620 tmp = g_key_file_get_value (key_file, "dirs", "config", NULL);
621 if (tmp)
622 *config_dir = g_shell_quote (tmp);
623 g_free (tmp);
624 }
625
626 g_key_file_free (key_file);
627 }
628
629 static gint
get_dir_level(const gchar * dir)630 get_dir_level (const gchar *dir)
631 {
632 gint res = 0, i;
633
634 g_return_val_if_fail (dir != NULL, -1);
635
636 for (i = 0; dir[i]; i++) {
637 if (dir[i] == '/' || dir[i] == '\\')
638 res++;
639 }
640
641 if (i > 0)
642 res++;
643
644 return res;
645 }
646
647 #define DEFAULT_SOURCES_DBUS_NAME "org.gnome.evolution.dataserver.Sources0"
648
649 static gchar *
get_source_manager_reload_command(void)650 get_source_manager_reload_command (void)
651 {
652 GString *tmp;
653 gchar *command;
654
655 tmp = replace_variables ("$DBUSDATADIR", TRUE);
656
657 if (tmp) {
658 /* The service file is named based on the service name, thus search for it in the D-Bus directory. */
659 GDir *dir;
660
661 dir = g_dir_open (tmp->str, 0, NULL);
662
663 if (dir) {
664 gchar *base_filename;
665 gint base_filename_len;
666
667 g_string_free (tmp, TRUE);
668 tmp = NULL;
669
670 base_filename = g_strdup (EDS_SOURCES_DBUS_SERVICE_NAME);
671
672 if (!base_filename || !*base_filename) {
673 g_free (base_filename);
674 base_filename = g_strdup (DEFAULT_SOURCES_DBUS_NAME);
675 }
676
677 base_filename_len = strlen (base_filename);
678
679 while (base_filename_len > 0 && base_filename[base_filename_len - 1] >= '0' && base_filename[base_filename_len - 1] <= '9') {
680 base_filename_len--;
681 base_filename[base_filename_len] = '\0';
682 }
683
684 while (!tmp) {
685 const gchar *name;
686
687 name = g_dir_read_name (dir);
688
689 if (!name)
690 break;
691
692 if (g_ascii_strncasecmp (name, base_filename, base_filename_len) == 0 &&
693 g_ascii_strncasecmp (name + strlen (name) - 8, ".service", 8) == 0) {
694 gchar *filename;
695
696 filename = g_strconcat ("$DBUSDATADIR", G_DIR_SEPARATOR_S, name, NULL);
697 tmp = replace_variables (filename, TRUE);
698 g_free (filename);
699
700 if (tmp) {
701 GKeyFile *key_file;
702 gchar *str = NULL;
703
704 key_file = g_key_file_new ();
705 if (g_key_file_load_from_file (key_file, tmp->str, G_KEY_FILE_NONE, NULL)) {
706 str = g_key_file_get_string (key_file, "D-BUS Service", "Name", NULL);
707 }
708 g_key_file_free (key_file);
709
710 if (str && *str) {
711 g_string_assign (tmp, str);
712 } else {
713 g_string_free (tmp, TRUE);
714 tmp = NULL;
715 }
716
717 g_free (str);
718 }
719 }
720 }
721
722 g_free (base_filename);
723 g_dir_close (dir);
724 } else {
725 g_string_free (tmp, TRUE);
726 tmp = NULL;
727 }
728 }
729
730 command = g_strdup_printf ("gdbus call --session --dest %s "
731 "--object-path /org/gnome/evolution/dataserver/SourceManager "
732 "--method org.gnome.evolution.dataserver.SourceManager.Reload",
733 tmp ? tmp->str : DEFAULT_SOURCES_DBUS_NAME);
734
735 if (tmp)
736 g_string_free (tmp, TRUE);
737
738 return command;
739 }
740
741 static void
unset_eds_migrated_flag(void)742 unset_eds_migrated_flag (void)
743 {
744 GSettings *settings;
745
746 settings = g_settings_new ("org.gnome.evolution-data-server");
747 g_settings_set_boolean (settings, "migrated", FALSE);
748 g_object_unref (settings);
749 }
750
751 static void
restore(const gchar * filename,GCancellable * cancellable)752 restore (const gchar *filename,
753 GCancellable *cancellable)
754 {
755 gchar *command;
756 gchar *quotedfname;
757 gboolean is_new_format = FALSE;
758
759 g_return_if_fail (filename && *filename);
760
761 if (!check (filename, &is_new_format)) {
762 g_message ("Cannot restore from an incorrect archive '%s'.", filename);
763 goto end;
764 }
765
766 quotedfname = g_shell_quote (filename);
767
768 if (g_cancellable_is_cancelled (cancellable))
769 return;
770
771 /* FIXME Will the versioned setting always work? */
772 txt = _("Shutting down Evolution");
773 run_cmd (EVOLUTION " --quit");
774
775 if (g_cancellable_is_cancelled (cancellable))
776 return;
777
778 txt = _("Back up current Evolution data");
779 run_cmd ("mv $DATADIR $DATADIR_old");
780 run_cmd ("mv $CONFIGDIR $CONFIGDIR_old");
781
782 if (g_cancellable_is_cancelled (cancellable))
783 return;
784
785 txt = _("Extracting files from back up");
786
787 if (is_new_format) {
788 GString *dir_fn;
789 gchar *data_dir = NULL;
790 gchar *config_dir = NULL;
791 gchar *restored_version = NULL;
792 const gchar *tar_opts;
793
794 if (get_filename_is_xz (filename))
795 tar_opts = "-xJf";
796 else
797 tar_opts = "-xzf";
798
799 command = g_strdup_printf (
800 "cd $TMP && tar %s %s " EVOLUTION_DIR_FILE,
801 tar_opts, quotedfname);
802 run_cmd (command);
803 g_free (command);
804
805 dir_fn = replace_variables ("$TMP" G_DIR_SEPARATOR_S EVOLUTION_DIR_FILE, TRUE);
806 if (!dir_fn) {
807 g_warning ("Failed to create evolution's dir filename");
808 goto end;
809 }
810
811 /* data_dir and config_dir are quoted inside extract_backup_data */
812 extract_backup_data (
813 dir_fn->str,
814 &restored_version,
815 &data_dir,
816 &config_dir);
817
818 g_unlink (dir_fn->str);
819 g_string_free (dir_fn, TRUE);
820
821 if (!data_dir || !config_dir) {
822 g_warning (
823 "Failed to get old data_dir (%p)/"
824 "config_dir (%p)", data_dir, config_dir);
825 g_free (data_dir);
826 g_free (config_dir);
827 goto end;
828 }
829
830 g_mkdir_with_parents (e_get_user_data_dir (), 0700);
831 g_mkdir_with_parents (e_get_user_config_dir (), 0700);
832
833 command = g_strdup_printf (
834 "cd $DATADIR && tar --strip-components %d %s %s %s",
835 get_dir_level (data_dir), tar_opts, quotedfname, data_dir);
836 run_cmd (command);
837 g_free (command);
838
839 command = g_strdup_printf (
840 "cd $CONFIGDIR && tar --strip-components %d %s %s %s",
841 get_dir_level (config_dir), tar_opts, quotedfname, config_dir);
842 run_cmd (command);
843 g_free (command);
844
845 /* If the back file had version information, set the last
846 * used version in GSettings before restarting Evolution. */
847 if (restored_version != NULL && *restored_version != '\0') {
848 GSettings *settings;
849
850 settings = e_util_ref_settings ("org.gnome.evolution");
851 g_settings_set_string (
852 settings, "version", restored_version);
853 g_object_unref (settings);
854 }
855
856 g_free (data_dir);
857 g_free (config_dir);
858 g_free (restored_version);
859 } else {
860 const gchar *decr_opts;
861
862 if (get_filename_is_xz (filename))
863 decr_opts = "xz -cd";
864 else
865 decr_opts = "gzip -cd";
866
867 run_cmd ("mv $HOME/.evolution $HOME/.evolution_old");
868
869 command = g_strdup_printf (
870 "cd $HOME && %s %s | tar xf -", decr_opts, quotedfname);
871 run_cmd (command);
872 g_free (command);
873 }
874
875 g_free (quotedfname);
876
877 if (g_cancellable_is_cancelled (cancellable))
878 return;
879
880 txt = _("Loading Evolution settings");
881
882 if (is_new_format) {
883 /* new format has it in DATADIR... */
884 GString *file = replace_variables (EVOLUTION_DIR ANCIENT_GCONF_DUMP_FILE, TRUE);
885 if (file && g_file_test (file->str, G_FILE_TEST_EXISTS)) {
886 unset_eds_migrated_flag ();
887
888 /* ancient backup */
889 replace_in_file (
890 EVOLUTION_DIR ANCIENT_GCONF_DUMP_FILE,
891 EVOUSERDATADIR_MAGIC, e_get_user_data_dir ());
892 run_cmd ("gconftool-2 --load " EVOLUTION_DIR ANCIENT_GCONF_DUMP_FILE);
893
894 /* give a chance to GConf to save what was loaded into a disk */
895 g_usleep (G_USEC_PER_SEC * 5);
896
897 /* do not forget to convert GConf keys into GSettings */
898 run_cmd ("gsettings-data-convert");
899 run_cmd ("rm " EVOLUTION_DIR ANCIENT_GCONF_DUMP_FILE);
900 } else {
901 if (file)
902 g_string_free (file, TRUE);
903
904 file = replace_variables (EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EDS, TRUE);
905
906 if (file && g_file_test (file->str, G_FILE_TEST_EXISTS)) {
907 replace_in_file (
908 EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EDS,
909 EVOUSERDATADIR_MAGIC, e_get_user_data_dir ());
910 run_cmd ("cat " EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EDS " | dconf load " ANCIENT_DCONF_PATH_EDS);
911 run_cmd ("rm " EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EDS);
912
913 replace_in_file (
914 EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EVO,
915 EVOUSERDATADIR_MAGIC, e_get_user_data_dir ());
916 run_cmd ("cat " EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EVO " | dconf load " ANCIENT_DCONF_PATH_EVO);
917 run_cmd ("rm " EVOLUTION_DIR ANCIENT_DCONF_DUMP_FILE_EVO);
918 } else {
919 GError *error = NULL;
920
921 if (!restore_settings (EVOLUTION_DIR GSETTINGS_DUMP_FILE, &error)) {
922 g_warning ("Failed to restore settings: %s", error ? error->message : "Unknown error");
923 g_clear_error (&error);
924 }
925 run_cmd ("rm " EVOLUTION_DIR GSETTINGS_DUMP_FILE);
926 }
927 }
928
929 if (file)
930 g_string_free (file, TRUE);
931 } else {
932 gchar *gconf_dump_file;
933
934 unset_eds_migrated_flag ();
935
936 /* ... old format in ~/.evolution */
937 gconf_dump_file = g_build_filename (
938 "$HOME", ".evolution", ANCIENT_GCONF_DUMP_FILE, NULL);
939
940 replace_in_file (
941 gconf_dump_file,
942 EVOUSERDATADIR_MAGIC,
943 e_get_user_data_dir ());
944
945 command = g_strconcat (
946 "gconftool-2 --load ", gconf_dump_file, NULL);
947 run_cmd (command);
948 g_free (command);
949
950 /* give a chance to GConf to save what was loaded into a disk */
951 g_usleep (G_USEC_PER_SEC * 5);
952
953 /* do not forget to convert GConf keys into GSettings */
954 run_cmd ("gsettings-data-convert");
955
956 command = g_strconcat ("rm ", gconf_dump_file, NULL);
957 run_cmd (command);
958 g_free (command);
959
960 g_free (gconf_dump_file);
961 }
962
963 if (g_cancellable_is_cancelled (cancellable))
964 return;
965
966 txt = _("Removing temporary back up files");
967 run_cmd ("rm -rf $DATADIR_old");
968 run_cmd ("rm -rf $CONFIGDIR_old");
969 run_cmd ("rm $CONFIGDIR/.running");
970
971 if (!is_new_format)
972 run_cmd ("rm -rf $HOME/.evolution_old");
973
974 if (g_cancellable_is_cancelled (cancellable))
975 return;
976
977 /* Make full-restart background processes after restore */
978 run_cmd (EVOLUTION " --force-shutdown");
979
980 txt = _("Reloading registry service");
981
982 /* wait few seconds, till changes settle */
983 g_usleep (G_USEC_PER_SEC * 5);
984
985 command = get_source_manager_reload_command ();
986 /* This runs migration routines on the newly-restored data. */
987 run_cmd (command);
988 g_free (command);
989
990 end:
991 if (restart_arg) {
992 if (g_cancellable_is_cancelled (cancellable))
993 return;
994
995 txt = _("Restarting Evolution");
996
997 /* wait 5 seconds before restarting evolution, thus any
998 * changes being done are updated in source registry too */
999 g_usleep (G_USEC_PER_SEC * 5);
1000
1001 run_evolution_no_wait ();
1002 }
1003 }
1004
1005 static gboolean
check(const gchar * filename,gboolean * is_new_format)1006 check (const gchar *filename,
1007 gboolean *is_new_format)
1008 {
1009 gchar *command;
1010 gchar *quotedfname;
1011 const gchar *tar_opts;
1012 gboolean is_new = TRUE;
1013
1014 g_return_val_if_fail (filename && *filename, FALSE);
1015
1016 if (get_filename_is_xz (filename))
1017 tar_opts = "-tJf";
1018 else
1019 tar_opts = "-tzf";
1020
1021 quotedfname = g_shell_quote (filename);
1022
1023 if (is_new_format)
1024 *is_new_format = FALSE;
1025
1026 command = g_strdup_printf ("tar %s %s 1>/dev/null", tar_opts, quotedfname);
1027 result = system (command);
1028 g_free (command);
1029
1030 g_message ("First result %d", result);
1031 if (result) {
1032 g_free (quotedfname);
1033 return FALSE;
1034 }
1035
1036 command = g_strdup_printf (
1037 "tar %s %s | grep -e \"%s$\"",
1038 tar_opts, quotedfname, EVOLUTION_DIR_FILE);
1039 result = system (command);
1040 g_free (command);
1041
1042 if (result) {
1043 command = g_strdup_printf (
1044 "tar %s %s | grep -e \"^\\.evolution/$\"",
1045 tar_opts, quotedfname);
1046 result = system (command);
1047 g_free (command);
1048 is_new = FALSE;
1049 }
1050
1051 g_message ("Second result %d", result);
1052 if (result) {
1053 g_free (quotedfname);
1054 return FALSE;
1055 }
1056
1057 if (is_new) {
1058 if (is_new_format)
1059 *is_new_format = TRUE;
1060 g_free (quotedfname);
1061 return TRUE;
1062 }
1063
1064 command = g_strdup_printf (
1065 "tar %s %s | grep -e \"^\\.evolution/%s$\"",
1066 tar_opts, quotedfname, ANCIENT_GCONF_DUMP_FILE);
1067 result = system (command);
1068 g_free (command);
1069
1070 if (result != 0) {
1071 /* maybe it's an ancient backup */
1072 command = g_strdup_printf (
1073 "tar %s %s | grep -e \"^\\.evolution/%s$\"",
1074 tar_opts, quotedfname, ANCIENT_GCONF_DUMP_FILE);
1075 result = system (command);
1076 g_free (command);
1077 }
1078
1079 g_free (quotedfname);
1080
1081 g_message ("Third result %d", result);
1082
1083 return result == 0;
1084 }
1085
1086 static gboolean
pbar_update(gpointer user_data)1087 pbar_update (gpointer user_data)
1088 {
1089 GCancellable *cancellable = G_CANCELLABLE (user_data);
1090
1091 gtk_progress_bar_pulse ((GtkProgressBar *) pbar);
1092 gtk_progress_bar_set_text ((GtkProgressBar *) pbar, txt);
1093
1094 /* Return TRUE to reschedule the timeout. */
1095 return !g_cancellable_is_cancelled (cancellable);
1096 }
1097
1098 static gboolean
finish_job(gpointer user_data)1099 finish_job (gpointer user_data)
1100 {
1101 gtk_main_quit ();
1102
1103 return FALSE;
1104 }
1105
1106 static void
start_job(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1107 start_job (GTask *task,
1108 gpointer source_object,
1109 gpointer task_data,
1110 GCancellable *cancellable)
1111 {
1112 if (backup_op)
1113 backup (bk_file, cancellable);
1114 else if (restore_op)
1115 restore (res_file, cancellable);
1116 else if (check_op)
1117 check (chk_file, NULL); /* not cancellable */
1118
1119 g_main_context_invoke (NULL, finish_job, NULL);
1120 }
1121
1122 static void
dlg_response(GtkWidget * dlg,gint response,GCancellable * cancellable)1123 dlg_response (GtkWidget *dlg,
1124 gint response,
1125 GCancellable *cancellable)
1126 {
1127 /* We will cancel only backup/restore
1128 * operations and not the check operation. */
1129 g_cancellable_cancel (cancellable);
1130
1131 /* If the response is not of delete_event then destroy the event. */
1132 if (response != GTK_RESPONSE_NONE)
1133 gtk_widget_destroy (dlg);
1134
1135 /* We will kill just the tar operation. Rest of
1136 * them will be just a second of microseconds.*/
1137 run_cmd ("pkill tar");
1138
1139 if (bk_file && backup_op && response == GTK_RESPONSE_REJECT) {
1140 /* Backup was cancelled, delete the
1141 * backup file as it is not needed now. */
1142 gchar *cmd, *filename;
1143
1144 g_message ("Back up cancelled, removing partial back up file.");
1145
1146 filename = g_shell_quote (bk_file);
1147 cmd = g_strconcat ("rm ", filename, NULL);
1148
1149 run_cmd (cmd);
1150
1151 g_free (cmd);
1152 g_free (filename);
1153 }
1154
1155 gtk_main_quit ();
1156 }
1157
1158 gint
main(gint argc,gchar ** argv)1159 main (gint argc,
1160 gchar **argv)
1161 {
1162 GTask *task;
1163 GCancellable *cancellable;
1164 gchar *file = NULL, *oper = NULL;
1165 const gchar *title = NULL;
1166 gint ii;
1167 GError *error = NULL;
1168
1169 #ifdef G_OS_WIN32
1170 e_util_win32_initialize ();
1171 #endif
1172
1173 bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR);
1174 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1175 textdomain (GETTEXT_PACKAGE);
1176
1177 gtk_init_with_args (
1178 &argc, &argv, NULL, options, GETTEXT_PACKAGE, &error);
1179
1180 if (error != NULL) {
1181 if (gui_arg)
1182 g_printerr ("Failed to initialize gtk+. Do not use --gui, to run without it. Reported error: %s\n", error->message);
1183
1184 g_clear_error (&error);
1185
1186 if (gui_arg)
1187 exit (EXIT_FAILURE);
1188 }
1189
1190 if (opt_remaining != NULL) {
1191 for (ii = 0; ii < g_strv_length (opt_remaining); ii++) {
1192 if (backup_op) {
1193 title = _("Evolution Back Up");
1194 oper = _("Backing up to the file %s");
1195 bk_file = g_strdup ((gchar *) opt_remaining[ii]);
1196 file = bk_file;
1197 } else if (restore_op) {
1198 title = _("Evolution Restore");
1199 oper = _("Restoring from the file %s");
1200 res_file = g_strdup ((gchar *) opt_remaining[ii]);
1201 file = res_file;
1202 } else if (check_op) {
1203 d (g_message ("Checking %s", (gchar *) opt_remaining[ii]));
1204 chk_file = g_strdup ((gchar *) opt_remaining[ii]);
1205 }
1206 }
1207 }
1208
1209 cancellable = g_cancellable_new ();
1210
1211 if (gui_arg && !check_op) {
1212 GtkWidget *widget, *container;
1213 GtkWidget *action_area;
1214 GtkWidget *content_area;
1215 const gchar *txt, *txt2;
1216 gchar *str = NULL;
1217 gchar *markup;
1218
1219 gtk_window_set_default_icon_name ("evolution");
1220
1221 /* Backup / Restore only can have GUI.
1222 * We should restrict the rest. */
1223 progress_dialog = gtk_dialog_new_with_buttons (
1224 title, NULL,
1225 GTK_DIALOG_MODAL,
1226 _("_Cancel"), GTK_RESPONSE_REJECT,
1227 NULL);
1228
1229 gtk_container_set_border_width (
1230 GTK_CONTAINER (progress_dialog), 12);
1231
1232 action_area = gtk_dialog_get_action_area (
1233 GTK_DIALOG (progress_dialog));
1234 content_area = gtk_dialog_get_content_area (
1235 GTK_DIALOG (progress_dialog));
1236
1237 /* Override GtkDialog defaults */
1238 gtk_box_set_spacing (GTK_BOX (content_area), 12);
1239 gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
1240 gtk_box_set_spacing (GTK_BOX (action_area), 12);
1241 gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
1242
1243 if (oper && file)
1244 str = g_strdup_printf (oper, file);
1245
1246 container = gtk_grid_new ();
1247 gtk_grid_set_column_spacing (GTK_GRID (container), 6);
1248 gtk_grid_set_row_spacing (GTK_GRID (container), 0);
1249 gtk_widget_show (container);
1250
1251 gtk_box_pack_start (
1252 GTK_BOX (content_area), container, FALSE, TRUE, 0);
1253
1254 widget = gtk_image_new_from_icon_name (
1255 "edit-copy", GTK_ICON_SIZE_DIALOG);
1256 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
1257 gtk_widget_show (widget);
1258
1259 gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 3);
1260 g_object_set (
1261 G_OBJECT (widget),
1262 "halign", GTK_ALIGN_FILL,
1263 "valign", GTK_ALIGN_FILL,
1264 "vexpand", TRUE,
1265 NULL);
1266
1267 if (backup_op) {
1268 txt = _("Backing up Evolution Data");
1269 txt2 = _("Please wait while Evolution is backing up your data.");
1270 } else if (restore_op) {
1271 txt = _("Restoring Evolution Data");
1272 txt2 = _("Please wait while Evolution is restoring your data.");
1273 } else {
1274 g_return_val_if_reached (EXIT_FAILURE);
1275 }
1276
1277 markup = g_markup_printf_escaped ("<b><big>%s</big></b>", txt);
1278 widget = gtk_label_new (markup);
1279 gtk_label_set_line_wrap (GTK_LABEL (widget), FALSE);
1280 gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
1281 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
1282 gtk_widget_show (widget);
1283 g_free (markup);
1284
1285 gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
1286 g_object_set (
1287 G_OBJECT (widget),
1288 "halign", GTK_ALIGN_FILL,
1289 "hexpand", TRUE,
1290 "valign", GTK_ALIGN_FILL,
1291 NULL);
1292
1293 markup = g_strconcat (
1294 txt2, " ", _("This may take a while depending "
1295 "on the amount of data in your account."), NULL);
1296 widget = gtk_label_new (markup);
1297 gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
1298 gtk_label_set_width_chars (GTK_LABEL (widget), 20);
1299 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
1300 gtk_widget_show (widget);
1301 g_free (markup);
1302
1303 gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
1304 g_object_set (
1305 G_OBJECT (widget),
1306 "halign", GTK_ALIGN_FILL,
1307 "hexpand", TRUE,
1308 "valign", GTK_ALIGN_FILL,
1309 NULL);
1310
1311 pbar = gtk_progress_bar_new ();
1312
1313 if (str != NULL) {
1314 markup = g_markup_printf_escaped ("<i>%s</i>", str);
1315 widget = gtk_label_new (markup);
1316 gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
1317 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
1318 g_free (markup);
1319 g_free (str);
1320
1321 gtk_grid_attach (GTK_GRID (container), widget, 1, 2, 1, 1);
1322 g_object_set (
1323 G_OBJECT (widget),
1324 "halign", GTK_ALIGN_FILL,
1325 "hexpand", TRUE,
1326 "valign", GTK_ALIGN_FILL,
1327 NULL);
1328
1329 gtk_grid_attach (GTK_GRID (container), pbar, 1, 3, 1, 1);
1330 } else
1331 gtk_grid_attach (GTK_GRID (container), pbar, 1, 2, 1, 1);
1332
1333 g_object_set (
1334 G_OBJECT (pbar),
1335 "halign", GTK_ALIGN_FILL,
1336 "hexpand", TRUE,
1337 "valign", GTK_ALIGN_FILL,
1338 NULL);
1339
1340 g_signal_connect (
1341 progress_dialog, "response",
1342 G_CALLBACK (dlg_response), cancellable);
1343 gtk_widget_show_all (progress_dialog);
1344
1345 } else if (check_op) {
1346 /* For sanity we don't need gui */
1347 check (chk_file, NULL);
1348 exit (result == 0 ? 0 : 1);
1349 }
1350
1351 if (gui_arg) {
1352 e_named_timeout_add_full (
1353 G_PRIORITY_DEFAULT,
1354 50, pbar_update,
1355 g_object_ref (cancellable),
1356 (GDestroyNotify) g_object_unref);
1357 }
1358
1359 task = g_task_new (cancellable, cancellable, NULL, NULL);
1360 g_task_run_in_thread (task, start_job);
1361 g_object_unref (task);
1362
1363 gtk_main ();
1364
1365 g_object_unref (cancellable);
1366 e_misc_util_free_global_memory ();
1367
1368 return result;
1369 }
1370