changeset 16372:bb08332c7456

propagate from branch 'im.pidgin.pidgin' (head 05f7badcad9471084218e698a5c011908d17af90) to branch 'im.pidgin.rlaager.gaim_migration' (head 70ed16c24809c1e054fb7ef2132828c029f49c0b)
author Richard Laager <rlaager@wiktel.com>
date Mon, 23 Apr 2007 17:19:27 +0000
parents 6b4e778ee4b4 (current diff) 0fc1b85fd247 (diff)
children c9b4ff420140
files
diffstat 4 files changed, 429 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/core.c	Mon Apr 23 17:19:11 2007 +0000
+++ b/libpurple/core.c	Mon Apr 23 17:19:27 2007 +0000
@@ -44,6 +44,7 @@
 #include "sslconn.h"
 #include "status.h"
 #include "stun.h"
+#include "util.h"
 
 #ifdef HAVE_DBUS
 #  include "dbus-server.h"
@@ -268,3 +269,376 @@
 {
 	return _ops;
 }
+
+static gboolean
+move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative)
+{
+	char *new_name = g_build_filename(new_base, basename, NULL);
+#ifndef _WIN32
+	char *old_name;
+#endif
+	if (g_rename(path, new_name))
+	{
+		purple_debug_error("core", "Error renaming %s to %s: %s\n",
+		                   path, new_name, strerror(errno));
+		g_free(new_name);
+		return FALSE;
+	}
+	g_free(new_name);
+
+#ifndef _WIN32
+	/* NOTE: This new_name is relative. */
+	new_name = g_build_filename(relative, basename, NULL);
+	old_name = g_build_filename(old_base, basename, NULL);
+	if (symlink(new_name, old_name))
+	{
+		purple_debug_warning("core", "Error symlinking %s to %s: %s\n",
+		                     old_name, new_name, strerror(errno));
+	}
+	g_free(old_name);
+	g_free(new_name);
+#endif
+
+	return TRUE;
+}
+
+gboolean
+purple_core_migrate(void)
+{
+	const char *user_dir = purple_user_dir();
+	char *old_user_dir = g_strconcat(purple_home_dir(),
+	                                 G_DIR_SEPARATOR_S ".gaim", NULL);
+	char *status_file;
+	FILE *fp;
+	GDir *dir;
+	GError *err;
+	const char *entry;
+#ifndef _WIN32
+	char *logs_dir;
+#endif
+	char *old_icons_dir;
+
+	if (!g_file_test(old_user_dir, G_FILE_TEST_EXISTS))
+	{
+		/* ~/.gaim doesn't exist, so there's nothing to migrate. */
+		g_free(old_user_dir);
+		return TRUE;
+	}
+
+	status_file = g_strconcat(user_dir, G_DIR_SEPARATOR_S "migrating", NULL);
+
+	if (g_file_test(user_dir, G_FILE_TEST_EXISTS))
+	{
+		/* If we're here, we have both ~/.gaim and .purple. */
+
+		if (!g_file_test(status_file, G_FILE_TEST_EXISTS))
+		{
+			/* There's no "migrating" status file,
+			 * so ~/.purple is all up to date. */
+			g_free(status_file);
+			g_free(old_user_dir);
+			return TRUE;
+		}
+	}
+
+	/* If we're here, it's time to migrate from ~/.gaim to ~/.purple. */
+
+        /* Ensure the user directory exists */
+	if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
+	{
+		if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+		{
+			purple_debug_error("core", "Error creating directory %s: %s\n",
+			                   user_dir, strerror(errno));
+			g_free(status_file);
+			g_free(old_user_dir);
+			return FALSE;
+		}
+	}
+
+	/* This writes ~/.purple/migrating, which allows us to detect
+	 * incomplete migrations and properly retry. */
+	if (!(fp = g_fopen(status_file, "w")))
+	{
+		purple_debug_error("core", "Error opening file %s for writing: %s\n",
+		                   status_file, strerror(errno));
+		g_free(status_file);
+		g_free(old_user_dir);
+		return FALSE;
+	}
+	fclose(fp);
+
+	/* Open ~/.gaim so we can loop over its contents. */
+	err = NULL;
+	if (!(dir = g_dir_open(old_user_dir, 0, &err)))
+	{
+		purple_debug_error("core", "Error opening directory %s: %s\n",
+		                   status_file,
+		                   (err ? err->message : "Unknown error"));
+		if (err)
+			g_error_free(err);
+		g_free(status_file);
+		g_free(old_user_dir);
+		return FALSE;
+	}
+
+	/* Loop over the contents of ~/.gaim */
+	while ((entry = g_dir_read_name(dir)))
+	{
+		char *name = g_build_filename(old_user_dir, entry, NULL);
+
+#ifndef _WIN32
+		/* Deal with symlinks... */
+		if (g_file_test(name, G_FILE_TEST_IS_SYMLINK))
+		{
+			/* We're only going to duplicate a logs symlink. */
+			if (!strcmp(entry, "logs"))
+			{
+				char buf[MAXPATHLEN];
+
+				if (readlink(name, buf, sizeof(buf) - 1) == -1)
+				{
+					purple_debug_error("core", "Error reading symlink %s: %s\n",
+					                   name, strerror(errno));
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+				buf[sizeof(buf) - 1] = '\0';
+
+				logs_dir = g_strconcat(user_dir, G_DIR_SEPARATOR_S "logs", NULL);
+
+				if (!strcmp(buf, "../.purple/logs") || !strcmp(buf, logs_dir))
+				{
+					/* If the symlink points to the new directory, we're
+					 * likely just trying again after a failed migration,
+					 * so there's no need to fail here. */
+					g_free(logs_dir);
+					continue;
+				}
+
+				/* In case we are trying again after a failed migration, we need
+				 * to unlink any existing symlink.  If it's a directory, this
+				 * will fail, and so will the symlink below, which is good
+				 * because the user should sort things out. */
+				g_unlink(logs_dir);
+
+				/* Relative links will most likely still be
+				 * valid from ~/.purple, though not it's not
+				 * guaranteed.  Oh well. */
+				if (symlink(buf, logs_dir))
+				{
+					purple_debug_error("core", "Error symlinking %s to %s: %s\n",
+					                   logs_dir, buf, strerror(errno));
+					g_free(name);
+					g_free(logs_dir);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+
+				g_free(logs_dir);
+				continue;
+			}
+
+			/* Ignore all other symlinks. */
+			continue;
+		}
+#endif
+
+		/* Deal with directories... */
+		if (g_file_test(name, G_FILE_TEST_IS_DIR))
+		{
+			if (!strcmp(entry, "icons"))
+			{
+				/* This is a special case for the Album plugin, which
+				 * stores data in the icons folder.  We're not copying
+				 * the icons directory over because previous bugs
+				 * meant that it filled up with junk for many users.
+				 * This is a great time to purge it. */
+
+				GDir *icons_dir;
+				char *new_icons_dir;
+				const char *icons_entry;
+
+				err = NULL;
+				if (!(icons_dir = g_dir_open(name, 0, &err)))
+				{
+					purple_debug_error("core", "Error opening directory %s: %s\n",
+					                   name,
+					                   (err ? err->message : "Unknown error"));
+					if (err)
+						g_error_free(err);
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+
+				new_icons_dir = g_build_filename(user_dir, "icons", NULL);
+			        /* Ensure the new icon directory exists */
+				if (!g_file_test(new_icons_dir, G_FILE_TEST_IS_DIR))
+				{
+					if (g_mkdir(new_icons_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+					{
+						purple_debug_error("core", "Error creating directory %s: %s\n",
+						                   new_icons_dir, strerror(errno));
+						g_free(new_icons_dir);
+						g_dir_close(icons_dir);
+						g_free(name);
+						g_dir_close(dir);
+						g_free(status_file);
+						g_free(old_user_dir);
+						return FALSE;
+					}
+				}
+
+				while ((icons_entry = g_dir_read_name(icons_dir)))
+				{
+					char *icons_name = g_build_filename(name, icons_entry, NULL);
+
+					if (g_file_test(icons_name, G_FILE_TEST_IS_DIR))
+					{
+						if (!move_and_symlink_dir(icons_name, icons_entry,
+						                          name, new_icons_dir, "../../.purple/icons"))
+						{
+							g_free(icons_name);
+							g_free(new_icons_dir);
+							g_dir_close(icons_dir);
+							g_free(name);
+							g_dir_close(dir);
+							g_free(status_file);
+							g_free(old_user_dir);
+							return FALSE;
+						}
+					}
+					g_free(icons_name);
+				}
+
+				g_dir_close(icons_dir);
+			}
+			else if (!strcmp(entry, "plugins"))
+			{
+				/* Do nothing, because we broke plugin compatibility.
+				 * This means that the plugins directory gets left behind. */
+			}
+			else
+			{
+				/* All other directories are moved and symlinked. */
+				if (!move_and_symlink_dir(name, entry, old_user_dir, user_dir, "../.purple"))
+				{
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+			}
+		}
+		else if (g_file_test(name, G_FILE_TEST_IS_REGULAR))
+		{
+			/* Regular files are copied. */
+
+			char *new_name;
+			FILE *new_file;
+
+			if (!(fp = g_fopen(name, "rb")))
+			{
+				purple_debug_error("core", "Error opening file %s for reading: %s\n",
+				                   name, strerror(errno));
+				g_free(name);
+				g_dir_close(dir);
+				g_free(status_file);
+				g_free(old_user_dir);
+				return FALSE;
+			}
+
+			new_name = g_build_filename(user_dir, entry, NULL);
+			if (!(new_file = g_fopen(new_name, "wb")))
+			{
+				purple_debug_error("core", "Error opening file %s for writing: %s\n",
+				                   new_name, strerror(errno));
+				fclose(fp);
+				g_free(new_name);
+				g_free(name);
+				g_dir_close(dir);
+				g_free(status_file);
+				g_free(old_user_dir);
+				return FALSE;
+			}
+
+			while (!feof(fp))
+			{
+				unsigned char buf[256];
+				size_t size;
+
+				size = fread(buf, 1, sizeof(buf), fp);
+				if (size != sizeof(buf) && !feof(fp))
+				{
+					purple_debug_error("core", "Error reading %s: %s\n",
+					                   name, strerror(errno));
+					fclose(new_file);
+					fclose(fp);
+					g_free(new_name);
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+
+				if (!fwrite(buf, size, 1, new_file))
+				{
+					purple_debug_error("core", "Error writing %s: %s\n",
+					                   new_name, strerror(errno));
+					fclose(new_file);
+					fclose(fp);
+					g_free(new_name);
+					g_free(name);
+					g_dir_close(dir);
+					g_free(status_file);
+					g_free(old_user_dir);
+					return FALSE;
+				}
+			}
+
+			if (fclose(new_file))
+			{
+				purple_debug_error("core", "Error writing: %s: %s\n",
+				                   new_name, strerror(errno));
+			}
+			if (fclose(fp))
+			{
+				purple_debug_warning("core", "Error closing %s: %s\n",
+				                     name, strerror(errno));
+			}
+			g_free(new_name);
+		}
+		else
+			purple_debug_warning("core", "Not a regular file or directory: %s\n", name);
+
+		g_free(name);
+	}
+
+	/* The migration was successful, so delete the status file. */
+	if (g_unlink(status_file))
+	{
+		purple_debug_error("core", "Error unlinking file %s: %s\n",
+		                   status_file, strerror(errno));
+		g_free(status_file);
+		return FALSE;
+	}
+
+	old_icons_dir = g_build_filename(old_user_dir, "icons", NULL);
+	purple_buddy_icon_set_old_icons_dir(old_icons_dir);
+	g_free(old_icons_dir);
+
+	g_free(old_user_dir);
+
+	g_free(status_file);
+	return TRUE;
+}
--- a/libpurple/core.h	Mon Apr 23 17:19:11 2007 +0000
+++ b/libpurple/core.h	Mon Apr 23 17:19:27 2007 +0000
@@ -106,6 +106,17 @@
  */
 PurpleCoreUiOps *purple_core_get_ui_ops(void);
 
+/**
+ * Migrates from .gaim to .purple.
+ *
+ * UIs MUST NOT call this if they have been told to use a custom
+ * user directory.
+ *
+ * @return A boolean indicating success or migration failure. On failure,
+ *         the application must display an error to the user and then exit.
+ */
+gboolean purple_core_migrate(void);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/util.c	Mon Apr 23 17:19:11 2007 +0000
+++ b/libpurple/util.c	Mon Apr 23 17:19:27 2007 +0000
@@ -65,7 +65,7 @@
 };
 
 static char custom_home_dir[MAXPATHLEN];
-static char home_dir[MAXPATHLEN];
+static char home_dir[MAXPATHLEN] = "";
 
 PurpleMenuAction *
 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
@@ -2245,25 +2245,17 @@
 #endif
 }
 
-/* Returns the argument passed to -c IFF it was present, or ~/.gaim IFF it
- * exists, else ~/.purple. */
+/* Returns the argument passed to -c IFF it was present, or ~/.purple. */
 const char *
 purple_user_dir(void)
 {
-	if (custom_home_dir != NULL && strlen(custom_home_dir) > 0) {
+	if (custom_home_dir != NULL && *custom_home_dir) {
 		strcpy ((char*) &home_dir, (char*) &custom_home_dir);
-	} else {
+	} else if (!*home_dir) {
 		const gchar *hd = purple_home_dir();
 
 		if (hd) {
 			g_strlcpy((char*) &home_dir, hd, sizeof(home_dir));
-			g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".gaim",
-					sizeof(home_dir));
-
-			if (g_file_test(home_dir, G_FILE_TEST_EXISTS))
-				return home_dir;
-
-			g_strlcpy((char*) &home_dir, hd, sizeof(home_dir));
 			g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".purple",
 					sizeof(home_dir));
 		}
--- a/pidgin/gtkmain.c	Mon Apr 23 17:19:11 2007 +0000
+++ b/pidgin/gtkmain.c	Mon Apr 23 17:19:27 2007 +0000
@@ -446,6 +446,7 @@
 	int opt;
 	gboolean gui_check;
 	gboolean debug_enabled;
+	gboolean migration_failed = FALSE;
 
 	struct option long_options[] = {
 		{"config",   required_argument, NULL, 'c'},
@@ -639,6 +640,15 @@
 
 	purple_debug_set_enabled(debug_enabled);
 
+	/* If we're using a custom configuration directory, we
+	 * do NOT want to migrate, or weird things will happen. */
+	if (opt_config_dir_arg == NULL)
+	{
+		if (!purple_core_migrate())
+		{
+			migration_failed = TRUE;
+		}
+	}
 
 	search_path = g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL);
 	gtk_rc_add_default_file(search_path);
@@ -663,6 +673,36 @@
 	winpidgin_init(hint);
 #endif
 
+	if (migration_failed)
+	{
+		char *old = g_strconcat(purple_home_dir(),
+		                        G_DIR_SEPARATOR_S ".gaim", NULL);
+		char *text = _(
+			"Pidgin encountered errors migrating your settings "
+			"from %s to %s. Please investigate and complete the "
+			"migration by hand.");
+		GtkWidget *dialog;
+
+		dialog = gtk_message_dialog_new(NULL,
+		                                0,
+		                                GTK_MESSAGE_ERROR,
+		                                GTK_BUTTONS_CLOSE,
+		                                text, old, purple_user_dir());
+		g_free(old);
+
+		g_signal_connect_swapped(dialog, "response",
+		                         G_CALLBACK(gtk_main_quit), NULL);
+
+		gtk_widget_show_all(dialog);
+
+		gtk_main();
+
+#ifdef HAVE_SIGNAL_H
+		g_free(segfault_message);
+#endif
+		return 0;
+	}
+
 	purple_core_set_ui_ops(pidgin_core_get_ui_ops());
 	purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops());