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