Add a migration routine to EDataBookFactory.
authorMatthew Barnes <mbarnes@redhat.com>
Tue, 8 Jun 2010 13:21:05 +0000 (09:21 -0400)
committerMatthew Barnes <mbarnes@redhat.com>
Tue, 27 Jul 2010 13:02:30 +0000 (09:02 -0400)
Migration runs just before the main loop starts.
It's just a sequence of local directory renames.

~/.evolution/cache/addressbook  -->  $XDG_CACHE_HOME/evolution/addressbook
~/.evolution/addressbook/local  -->  $XDG_DATA_HOME/evolution/addressbook

addressbook/libedata-book/Makefile.am
addressbook/libedata-book/e-data-book-factory.c
addressbook/libedata-book/e-data-book-migrate.c [new file with mode: 0644]

index 26351b0..1e27886 100644 (file)
@@ -82,7 +82,10 @@ e_addressbook_factory_CPPFLAGS = \
        -I$(top_srcdir)/addressbook             \
        -I$(top_builddir)/addressbook
 
-e_addressbook_factory_SOURCES = e-data-book-factory.c e-data-book-factory.h
+e_addressbook_factory_SOURCES = \
+       e-data-book-factory.c \
+       e-data-book-factory.h \
+       e-data-book-migrate.c
 
 e_addressbook_factory_LDADD =                                  \
        libedata-book-1.2.la                                    \
index 230d730..fded31a 100644 (file)
@@ -81,6 +81,9 @@ struct _EDataBookFactoryPrivate {
         gint mode;
 };
 
+/* Forward Declarations */
+void e_data_book_migrate (void);
+
 /* Create the EDataBookFactory error quark */
 GQuark
 e_data_book_factory_error_quark (void)
@@ -528,6 +531,9 @@ main (gint argc, gchar **argv)
 
        printf ("Server is up and running...\n");
 
+       /* Migrate user data from ~/.evolution to XDG base directories. */
+       e_data_book_migrate ();
+
        g_main_loop_run (loop);
 
        g_object_unref (eol);
diff --git a/addressbook/libedata-book/e-data-book-migrate.c b/addressbook/libedata-book/e-data-book-migrate.c
new file mode 100644 (file)
index 0000000..3b006a0
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * e-data-book-migrate.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <errno.h>
+#include <glib/gstdio.h>
+#include <libedataserver/e-data-server-util.h>
+
+void e_data_book_migrate (void);
+
+static gboolean
+data_book_migrate_rename (const gchar *old_filename,
+                          const gchar *new_filename)
+{
+       gboolean success = TRUE;
+
+       if (g_file_test (old_filename, G_FILE_TEST_IS_DIR)) {
+               g_print ("  mv %s %s\n", old_filename, new_filename);
+               if (g_rename (old_filename, new_filename) < 0) {
+                       g_printerr ("  FAILED: %s\n", g_strerror (errno));
+                       success = FALSE;
+               }
+       }
+
+       return success;
+}
+
+static gboolean
+data_book_migrate_rmdir (const gchar *dirname)
+{
+       gboolean success = TRUE;
+
+       if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
+               g_print ("  rmdir %s\n", dirname);
+               if (g_rmdir (dirname) < 0) {
+                       g_printerr ("  FAILED: %s\n", g_strerror (errno));
+                       success = FALSE;
+               }
+       }
+
+       return success;
+}
+
+static void
+data_book_migrate_process_corrections (GHashTable *corrections)
+{
+       GHashTableIter iter;
+       gpointer old_filename;
+       gpointer new_filename;
+
+       g_hash_table_iter_init (&iter, corrections);
+
+       while (g_hash_table_iter_next (&iter, &old_filename, &new_filename)) {
+               data_book_migrate_rename (old_filename, new_filename);
+               g_hash_table_iter_remove (&iter);
+       }
+}
+
+static gboolean
+data_book_migrate_move_contents (const gchar *src_directory,
+                                 const gchar *dst_directory)
+{
+       GDir *dir;
+       GHashTable *corrections;
+       const gchar *basename;
+
+       dir = g_dir_open (src_directory, 0, NULL);
+       if (dir == NULL)
+               return FALSE;
+
+       /* This is to avoid renaming files while we're iterating over the
+        * directory.  POSIX says the outcome of that is unspecified. */
+       corrections = g_hash_table_new_full (
+               g_str_hash, g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+
+       g_mkdir_with_parents (dst_directory, 0700);
+
+       while ((basename = g_dir_read_name (dir)) != NULL) {
+               gchar *old_filename;
+               gchar *new_filename;
+
+               old_filename = g_build_filename (src_directory, basename, NULL);
+               new_filename = g_build_filename (dst_directory, basename, NULL);
+
+               g_hash_table_insert (corrections, old_filename, new_filename);
+       }
+
+       g_dir_close (dir);
+
+       data_book_migrate_process_corrections (corrections);
+       g_hash_table_destroy (corrections);
+
+       /* It's tempting to want to remove the source directory here.
+        * Don't.  We might be iterating over the source directory's
+        * parent directory, and removing the source directory would
+        * screw up the iteration. */
+
+       return TRUE;
+}
+
+static void
+data_book_migrate_fix_groupwise_bug (const gchar *old_base_dir)
+{
+       GDir *dir;
+       GHashTable *corrections;
+       const gchar *basename;
+       gchar *old_data_dir;
+       gchar *old_cache_dir;
+
+       /* The groupwise backend mistakenly put its addressbook
+        * cache files in ~/.evolution/addressbook instead of
+        * ~/.evolution/cache/addressbook.  Fix that before
+        * we migrate the cache directory. */
+
+       old_data_dir = g_build_filename (old_base_dir, "addressbook", NULL);
+       old_cache_dir = g_build_filename (old_base_dir, "cache", "addressbook", NULL);
+
+       dir = g_dir_open (old_data_dir, 0, NULL);
+       if (dir == NULL)
+               goto exit;
+
+       /* This is to avoid renaming files while we're iterating over the
+        * directory.  POSIX says the outcome of that is unspecified. */
+       corrections = g_hash_table_new_full (
+               g_str_hash, g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+
+       while ((basename = g_dir_read_name (dir)) != NULL) {
+               gchar *old_filename;
+               gchar *new_filename;
+
+               if (!g_str_has_prefix (basename, "groupwise___"))
+                       continue;
+
+               old_filename = g_build_filename (old_data_dir, basename, NULL);
+               new_filename = g_build_filename (old_cache_dir, basename, NULL);
+
+               g_hash_table_insert (corrections, old_filename, new_filename);
+       }
+
+       g_dir_close (dir);
+
+       data_book_migrate_process_corrections (corrections);
+       g_hash_table_destroy (corrections);
+
+exit:
+       g_free (old_data_dir);
+       g_free (old_cache_dir);
+}
+
+static void
+data_book_migrate_to_user_cache_dir (const gchar *old_base_dir)
+{
+       const gchar *new_cache_dir;
+       gchar *old_cache_dir;
+       gchar *src_directory;
+       gchar *dst_directory;
+
+       old_cache_dir = g_build_filename (old_base_dir, "cache", NULL);
+       new_cache_dir = e_get_user_cache_dir ();
+
+       g_print ("Migrating cached backend data\n");
+
+       /* We don't want to move the source directory directly because the
+        * destination directory may already exist with content.  Instead
+        * we want to merge the content of the source directory into the
+        * destination directory.
+        *
+        * For example, given:
+        *
+        *    $(src_directory)/A   and   $(dst_directory)/B
+        *    $(src_directory)/C
+        *
+        * we want to end up with:
+        *
+        *    $(dst_directory)/A
+        *    $(dst_directory)/B
+        *    $(dst_directory)/C
+        *
+        * Any name collisions will be left in the source directory.
+        */
+
+       src_directory = g_build_filename (old_cache_dir, "addressbook", NULL);
+       dst_directory = g_build_filename (new_cache_dir, "addressbook", NULL);
+
+       data_book_migrate_move_contents (src_directory, dst_directory);
+       data_book_migrate_rmdir (src_directory);
+
+       g_free (src_directory);
+       g_free (dst_directory);
+
+       /* Try to remove the old cache directory.  Good chance this will
+        * fail on the first try, since Evolution puts stuff here too. */
+       data_book_migrate_rmdir (old_cache_dir);
+
+       g_free (old_cache_dir);
+}
+
+static void
+data_book_migrate_to_user_data_dir (const gchar *old_base_dir)
+{
+       const gchar *new_data_dir;
+       gchar *src_directory;
+       gchar *dst_directory;
+
+       new_data_dir = e_get_user_data_dir ();
+
+       g_print ("Migrating local backend data\n");
+
+       /* We don't want to move the source directory directly because the
+        * destination directory may already exist with content.  Instead
+        * we want to merge the content of the source directory into the
+        * destination directory.
+        *
+        * For example, given:
+        *
+        *    $(src_directory)/A   and   $(dst_directory)/B
+        *    $(src_directory)/C
+        *
+        * we want to end up with:
+        *
+        *    $(dst_directory)/A
+        *    $(dst_directory)/B
+        *    $(dst_directory)/C
+        *
+        * Any name collisions will be left in the source directory.
+        */
+
+       src_directory = g_build_filename (old_base_dir, "addressbook", "local", NULL);
+       dst_directory = g_build_filename (new_data_dir, "addressbook", NULL);
+
+       data_book_migrate_move_contents (src_directory, dst_directory);
+       data_book_migrate_rmdir (src_directory);
+
+       g_free (src_directory);
+       g_free (dst_directory);
+}
+
+void
+e_data_book_migrate (void)
+{
+       const gchar *home_dir;
+       gchar *old_base_dir;
+
+       /* XXX This blocks, but it's all just local directory
+        *     renames so it should be nearly instantaneous. */
+
+       home_dir = g_get_home_dir ();
+       old_base_dir = g_build_filename (home_dir, ".evolution", NULL);
+
+       /* Is there even anything to migrate? */
+       if (!g_file_test (old_base_dir, G_FILE_TEST_IS_DIR))
+               goto exit;
+
+       data_book_migrate_fix_groupwise_bug (old_base_dir);
+
+       data_book_migrate_to_user_cache_dir (old_base_dir);
+       data_book_migrate_to_user_data_dir (old_base_dir);
+
+       /* Try to remove the old base directory.  Good chance this will
+        * fail on the first try, since Evolution puts stuff here too. */
+       data_book_migrate_rmdir (old_base_dir);
+
+exit:
+       g_free (old_base_dir);
+}