# HG changeset patch # User Richard Laager # Date 1177348767 0 # Node ID bb08332c74565aad7c416f6478d22e75ede998fb # Parent 6b4e778ee4b415ecff4b451319a966f63d9a107c# Parent 0fc1b85fd2472936aa68d8a319be51d7be9a49d0 propagate from branch 'im.pidgin.pidgin' (head 05f7badcad9471084218e698a5c011908d17af90) to branch 'im.pidgin.rlaager.gaim_migration' (head 70ed16c24809c1e054fb7ef2132828c029f49c0b) diff -r 6b4e778ee4b4 -r bb08332c7456 libpurple/core.c --- 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; +} diff -r 6b4e778ee4b4 -r bb08332c7456 libpurple/core.h --- 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 diff -r 6b4e778ee4b4 -r bb08332c7456 libpurple/util.c --- 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)); } diff -r 6b4e778ee4b4 -r bb08332c7456 pidgin/gtkmain.c --- 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());