From 7731956906ad5ce46c9acc3867086d117c2f6945 Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Wed, 17 Jul 2024 20:15:03 +0100 Subject: [PATCH] Implement emulator data migration functionality + prompt The migration prompt appears when Lime3DS is started while the new user data directory doesn't exist and the old directory *does* exist --- src/common/common_paths.h | 7 ++++- src/common/file_util.cpp | 12 +++++++++ src/common/file_util.h | 7 +++-- src/lime_qt/main.cpp | 55 ++++++++++++++++++++++++++++++++++++++- src/lime_qt/main.h | 3 +++ 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 0e2fe8a98..609fb44da 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -19,17 +19,22 @@ #else #ifdef _WIN32 #define EMU_DATA_DIR "Lime3DS" +#define LEGACY_EMU_DATA_DIR "Citra" #elif defined(__APPLE__) #include -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE // TODO: Kill iOS #define APPLE_EMU_DATA_DIR "Documents" DIR_SEP "Lime3DS" +#define LEGACY_APPLE_EMU_DATA_DIR "Documents" DIR_SEP "Citra" #else #define APPLE_EMU_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Lime3DS" +#define LEGACY_APPLE_EMU_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Citra" #endif // For compatibility with XDG paths. #define EMU_DATA_DIR "lime3ds-emu" +#define LEGACY_EMU_DATA_DIR "citra-emu" #else #define EMU_DATA_DIR "lime3ds-emu" +#define LEGACY_EMU_DATA_DIR "citra-emu" #endif #endif diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 3e052454f..08bafc37c 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -771,8 +771,11 @@ void SetUserPath(const std::string& path) { } else { #ifdef _WIN32 user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; + std::string& legacy_user_path = g_paths[UserPath::LegacyUserDir]; + if (!FileUtil::IsDirectory(user_path)) { user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; + legacy_user_path = AppDataRoamingDirectory() + DIR_SEP LEGACY_EMU_DATA_DIR DIR_SEP; } else { LOG_INFO(Common_Filesystem, "Using the local user directory"); } @@ -784,6 +787,7 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); #else + std::string& legacy_user_path = g_paths[UserPath::LegacyUserDir]; auto current_dir = FileUtil::GetCurrentDir(); if (current_dir.has_value() && FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) { @@ -792,10 +796,16 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); } else { std::string data_dir = GetUserDirectory("XDG_DATA_HOME") + DIR_SEP EMU_DATA_DIR DIR_SEP; + std::string legacy_data_dir = + GetUserDirectory("XDG_DATA_HOME") + DIR_SEP LEGACY_EMU_DATA_DIR DIR_SEP; std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME") + DIR_SEP EMU_DATA_DIR DIR_SEP; std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME") + DIR_SEP EMU_DATA_DIR DIR_SEP; + g_paths.emplace(UserPath::LegacyConfigDir, GetUserDirectory("XDG_CONFIG_HOME") + + DIR_SEP LEGACY_EMU_DATA_DIR DIR_SEP); + g_paths.emplace(UserPath::LegacyCacheDir, GetUserDirectory("XDG_CACHE_HOME") + + DIR_SEP LEGACY_EMU_DATA_DIR DIR_SEP); #if defined(__APPLE__) // If XDG directories don't already exist from a previous setup, use standard macOS @@ -803,12 +813,14 @@ void SetUserPath(const std::string& path) { if (!FileUtil::Exists(data_dir) && !FileUtil::Exists(config_dir) && !FileUtil::Exists(cache_dir)) { data_dir = GetHomeDirectory() + DIR_SEP APPLE_EMU_DATA_DIR DIR_SEP; + legacy_data_dir = GetHomeDirectory() + DIR_SEP LEGACY_APPLE_EMU_DATA_DIR DIR_SEP; config_dir = data_dir + CONFIG_DIR DIR_SEP; cache_dir = data_dir + CACHE_DIR DIR_SEP; } #endif user_path = data_dir; + legacy_user_path = legacy_data_dir; g_paths.emplace(UserPath::ConfigDir, config_dir); g_paths.emplace(UserPath::CacheDir, cache_dir); } diff --git a/src/common/file_util.h b/src/common/file_util.h index c24beeb7a..c0fa49d7d 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -32,17 +32,20 @@ enum class UserPath { ConfigDir, DLLDir, DumpDir, + IconsDir, + LegacyCacheDir, // LegacyCacheDir and LegacyConfigDir are only defined if migrating these + LegacyConfigDir, // directories is necessary (aka not a child of LegacyUserDir) + LegacyUserDir, LoadDir, LogDir, NANDDir, + PlayTimeDir, RootDir, SDMCDir, ShaderDir, StatesDir, SysDataDir, UserDir, - IconsDir, - PlayTimeDir, }; // Replaces install-specific paths with standard placeholders, and back again diff --git a/src/lime_qt/main.cpp b/src/lime_qt/main.cpp index 5023063a4..5d54c00e9 100644 --- a/src/lime_qt/main.cpp +++ b/src/lime_qt/main.cpp @@ -159,12 +159,16 @@ static QString PrettyProductName() { GMainWindow::GMainWindow(Core::System& system_) : ui{std::make_unique()}, system{system_}, movie{system.Movie()}, - config{std::make_unique()}, emu_thread{nullptr} { + emu_thread{nullptr} { Common::Log::Initialize(); Common::Log::Start(); Debugger::ToggleConsole(); + CheckForMigration(); + + this->config = std::make_unique(); + #ifdef __unix__ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); #endif @@ -1099,6 +1103,55 @@ void GMainWindow::ShowUpdaterWidgets() { } #endif +void GMainWindow::CheckForMigration() { + namespace fs = std::filesystem; + if (fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyUserDir)) && + !fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))) { + if (QMessageBox::information( + this, tr("Migration"), + tr("Lime3DS has moved to a new data directory.\n\n" + "Would you like to migrate your Citra data to this new " + "location?\n" + "(This may take a while; The old data will not be deleted)"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + MigrateUserData(); + } else { + QMessageBox::information(this, tr("Migration"), + tr("You can manually re-trigger this prompt by deleting the " + "new user data directory:\n" + "%1") + .arg(QString::fromStdString( + FileUtil::GetUserPath(FileUtil::UserPath::UserDir))), + QMessageBox::Ok); + } + } +} + +void GMainWindow::MigrateUserData() { + namespace fs = std::filesystem; + const auto copyOptions = fs::copy_options::update_existing | fs::copy_options::recursive; + + fs::copy(FileUtil::GetUserPath(FileUtil::UserPath::LegacyUserDir), + FileUtil::GetUserPath(FileUtil::UserPath::UserDir), copyOptions); + if (fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyConfigDir))) { + fs::copy(FileUtil::GetUserPath(FileUtil::UserPath::LegacyConfigDir), + FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir), copyOptions); + } + if (fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyCacheDir))) { + fs::copy(FileUtil::GetUserPath(FileUtil::UserPath::LegacyCacheDir), + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), copyOptions); + } + + QMessageBox::information( + this, tr("Migration"), + tr("Data was migrated successfully. Lime3DS will now start.\n\n" + "If you wish to clean up the files which were left in the old data location, you can do " + "so by deleting the following directory:\n" + "%1") + .arg(QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LegacyUserDir))), + QMessageBox::Ok); +} + #if defined(HAVE_SDL2) && defined(__unix__) && !defined(__APPLE__) static std::optional HoldWakeLockLinux(u32 window_id = 0) { if (!QDBusConnection::sessionBus().isConnected()) { diff --git a/src/lime_qt/main.h b/src/lime_qt/main.h index 03e55013f..774668bfa 100644 --- a/src/lime_qt/main.h +++ b/src/lime_qt/main.h @@ -171,6 +171,9 @@ private: void CheckForUpdates(); #endif + void CheckForMigration(); + void MigrateUserData(); + /** * Stores the filename in the recently loaded files list. * The new filename is stored at the beginning of the recently loaded files list.