Options for Data Migration (#95)

Copy, move, or link

Co-authored-by: KeatonTheBot <onikeaton@gmail.com>
Signed-off-by: swurl <swurl@swurl.xyz>

Reviewed-on: eden-emu/eden#95
Co-authored-by: swurl <swurl@swurl.xyz>
Co-committed-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-10 15:40:15 +00:00 committed by crueter
parent 09c72e9f98
commit 28d2b06380
6 changed files with 270 additions and 57 deletions

View file

@ -22,9 +22,10 @@ if ($debug -eq "yes") {
Move-Item $BUILD_PDB $ARTIFACTS_DIR/ -ErrorAction SilentlyContinue Move-Item $BUILD_PDB $ARTIFACTS_DIR/ -ErrorAction SilentlyContinue
} }
} else { } else {
Get-ChildItem "build/$target/bin/" -Recurse -Filter "*.pdb" | Remove-Item -Force -ErrorAction SilentlyContinue Remove-Item -Force "$RELEASE_DIST\*.pdb"
} }
Copy-Item "build/$target/bin/Release/*" -Destination "$RELEASE_DIST" -Recurse -ErrorAction SilentlyContinue Copy-Item "build/$target/bin/Release/*" -Destination "$RELEASE_DIST" -Recurse -ErrorAction SilentlyContinue
if (-not $?) { if (-not $?) {
# Try without Release subfolder if that doesn't exist # Try without Release subfolder if that doesn't exist

View file

@ -237,6 +237,7 @@ add_executable(yuzu
yuzu.qrc yuzu.qrc
yuzu.rc yuzu.rc
migration_dialog.h migration_dialog.cpp
) )
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")

View file

@ -0,0 +1,63 @@
#include "migration_dialog.h"
#include <QApplication>
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QStyle>
MigrationDialog::MigrationDialog(
QWidget *parent)
: QDialog(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
m_text = new QLabel(this);
m_boxes = new QVBoxLayout;
m_buttons = new QHBoxLayout;
layout->addWidget(m_text, 1);
layout->addLayout(m_boxes, 1);
layout->addLayout(m_buttons, 1);
}
MigrationDialog::~MigrationDialog() {
m_boxes->deleteLater();
m_buttons->deleteLater();
}
void MigrationDialog::setText(
const QString &text)
{
m_text->setText(text);
}
void MigrationDialog::addBox(
QWidget *box)
{
m_boxes->addWidget(box);
}
QAbstractButton *MigrationDialog::addButton(
const QString &text, const bool reject)
{
QAbstractButton *button = new QPushButton(this);
button->setText(text);
m_buttons->addWidget(button, 1);
connect(button, &QAbstractButton::clicked, this, [this, button, reject]() {
if (reject) {
this->reject();
} else {
m_clickedButton = button;
this->accept();
}
});
return button;
}
QAbstractButton *MigrationDialog::clickedButton() const
{
return m_clickedButton;
}

View file

@ -0,0 +1,30 @@
#ifndef MIGRATION_DIALOG_H
#define MIGRATION_DIALOG_H
#include <QMessageBox>
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
class MigrationDialog : public QDialog {
Q_OBJECT
public:
MigrationDialog(QWidget *parent = nullptr);
virtual ~MigrationDialog();
void setText(const QString &text);
void addBox(QWidget *box);
QAbstractButton *addButton(const QString &text, const bool reject = false);
QAbstractButton *clickedButton() const;
private:
QLabel *m_text;
QVBoxLayout *m_boxes;
QHBoxLayout *m_buttons;
QAbstractButton *m_clickedButton;
};
#endif // MIGRATION_DIALOG_H

View file

@ -5,20 +5,25 @@
// SPDX-FileCopyrightText: Copyright 2025 eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "user_data_migration.h"
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QString> #include <QString>
#include <QTranslator> #include <QTranslator>
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "user_data_migration.h" #include "migration_dialog.h"
// Needs to be included at the end due to https://bugreports.qt.io/browse/QTBUG-73263 // Needs to be included at the end due to https://bugreports.qt.io/browse/QTBUG-73263
#include <QButtonGroup>
#include <QCheckBox> #include <QCheckBox>
#include <QRadioButton>
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
UserDataMigrator::UserDataMigrator(QMainWindow* main_window) { UserDataMigrator::UserDataMigrator(
QMainWindow *main_window)
{
// NOTE: Logging is not initialized yet, do not produce logs here. // NOTE: Logging is not initialized yet, do not produce logs here.
// Check migration if config directory does not exist // Check migration if config directory does not exist
@ -29,25 +34,58 @@ UserDataMigrator::UserDataMigrator(QMainWindow* main_window) {
} }
} }
void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) { void UserDataMigrator::ShowMigrationPrompt(
QMainWindow *main_window)
{
namespace fs = std::filesystem; namespace fs = std::filesystem;
const QString migration_prompt_message = const QString migration_prompt_message = main_window->tr(
main_window->tr("Would you like to migrate your data for use in Eden?\n" "Would you like to migrate your data for use in Eden?\n"
"Select the corresponding button to migrate data from that emulator.\n" "Select the corresponding button to migrate data from that emulator.\n"
"(This may take a while; The old data will not be deleted)\n\n" "This may take a while.");
"Clearing shader cache is recommended for all users.\nDo not uncheck unless you know what you're doing.");
bool any_found = false; bool any_found = false;
QMessageBox migration_prompt; MigrationDialog migration_prompt;
migration_prompt.setWindowTitle(main_window->tr("Migration")); migration_prompt.setWindowTitle(main_window->tr("Migration"));
migration_prompt.setIcon(QMessageBox::Information);
QCheckBox *clear_shaders = new QCheckBox(&migration_prompt); QCheckBox *clear_shaders = new QCheckBox(&migration_prompt);
clear_shaders->setText(main_window->tr("Clear Shader Cache")); clear_shaders->setText(main_window->tr("Clear Shader Cache"));
clear_shaders->setToolTip(main_window->tr(
"Clearing shader cache is recommended for all users.\nDo not uncheck unless you know what "
"you're doing."));
clear_shaders->setChecked(true); clear_shaders->setChecked(true);
migration_prompt.setCheckBox(clear_shaders);
QRadioButton *keep_old = new QRadioButton(&migration_prompt);
keep_old->setText(main_window->tr("Keep Old Data"));
keep_old->setToolTip(
main_window->tr("Keeps the old data directory. This is recommended if you aren't\n"
"space-constrained and want to keep separate data for the old emulator."));
keep_old->setChecked(true);
QRadioButton *clear_old = new QRadioButton(&migration_prompt);
clear_old->setText(main_window->tr("Clear Old Data"));
clear_old->setToolTip(main_window->tr("Deletes the old data directory.\nThis is recommended on "
"devices with space constraints."));
clear_old->setChecked(false);
QRadioButton *link = new QRadioButton(&migration_prompt);
link->setText(main_window->tr("Link Old Directory"));
link->setToolTip(
main_window->tr("Creates a filesystem link between the old directory and Eden directory.\n"
"This is recommended if you want to share data between emulators.."));
link->setChecked(false);
// Link and Clear Old are mutually exclusive
QButtonGroup *group = new QButtonGroup(&migration_prompt);
group->addButton(keep_old);
group->addButton(clear_old);
group->addButton(link);
migration_prompt.addBox(clear_shaders);
migration_prompt.addBox(keep_old);
migration_prompt.addBox(clear_old);
migration_prompt.addBox(link);
// Reflection would make this code 10x better // Reflection would make this code 10x better
// but for now... MACRO MADNESS!!!! // but for now... MACRO MADNESS!!!!
@ -55,10 +93,13 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) {
QMap<QString, LegacyEmu> legacyMap; QMap<QString, LegacyEmu> legacyMap;
QMap<QString, QAbstractButton *> buttonMap; QMap<QString, QAbstractButton *> buttonMap;
#define EMU_MAP(name) const bool name##_found = fs::is_directory(Common::FS::GetLegacyPath(Common::FS::LegacyPath::name##Dir)); \ #define EMU_MAP(name) \
legacyMap[main_window->tr(#name)] = LegacyEmu::name; \ const bool name##_found = fs::is_directory( \
found[main_window->tr(#name)] = name##_found; \ Common::FS::GetLegacyPath(Common::FS::LegacyPath::name##Dir)); \
if (name##_found) any_found = true; legacyMap[main_window->tr(#name)] = LegacyEmu::name; \
found[main_window->tr(#name)] = name##_found; \
if (name##_found) \
any_found = true;
EMU_MAP(Citron) EMU_MAP(Citron)
EMU_MAP(Sudachi) EMU_MAP(Sudachi)
@ -68,30 +109,44 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) {
#undef EMU_MAP #undef EMU_MAP
if (any_found) { if (any_found) {
QString promptText = main_window->tr("Eden has detected user data for the following emulators:"); QString promptText = main_window->tr(
"Eden has detected user data for the following emulators:");
QMapIterator iter(found); QMapIterator iter(found);
while (iter.hasNext()) { while (iter.hasNext()) {
iter.next(); iter.next();
if (!iter.value()) continue; if (!iter.value())
continue;
buttonMap[iter.key()] = migration_prompt.addButton(iter.key(), QMessageBox::YesRole); buttonMap[iter.key()] = migration_prompt.addButton(iter.key());
promptText.append(main_window->tr("\n- %1").arg(iter.key())); promptText.append(main_window->tr("\n- %1").arg(iter.key()));
} }
promptText.append(main_window->tr("\n\n")); promptText.append(main_window->tr("\n\n"));
migration_prompt.setText(promptText + migration_prompt_message); migration_prompt.setText(promptText + migration_prompt_message);
migration_prompt.addButton(QMessageBox::No); migration_prompt.addButton(main_window->tr("No"), true);
migration_prompt.exec(); migration_prompt.exec();
MigrationStrategy strategy;
if (link->isChecked()) {
strategy = MigrationStrategy::Link;
} else if (clear_old->isChecked()) {
strategy = MigrationStrategy::Move;
} else {
strategy = MigrationStrategy::Copy;
}
QMapIterator buttonIter(buttonMap); QMapIterator buttonIter(buttonMap);
while (buttonIter.hasNext()) { while (buttonIter.hasNext()) {
buttonIter.next(); buttonIter.next();
if (buttonIter.value() == migration_prompt.clickedButton()) { if (buttonIter.value() == migration_prompt.clickedButton()) {
MigrateUserData(main_window, legacyMap[iter.key()], clear_shaders->isChecked()); MigrateUserData(main_window,
legacyMap[buttonIter.key()],
clear_shaders->isChecked(),
strategy);
return; return;
} }
} }
@ -104,31 +159,43 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) {
return; return;
} }
void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow* main_window) { void UserDataMigrator::ShowMigrationCancelledMessage(
QMessageBox::information( QMainWindow *main_window)
main_window, main_window->tr("Migration"), {
main_window QMessageBox::information(main_window,
->tr("You can manually re-trigger this prompt by deleting the " main_window->tr("Migration"),
"new config directory:\n" main_window
"%1") ->tr("You can manually re-trigger this prompt by deleting the "
.arg(QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::ConfigDir))), "new config directory:\n"
QMessageBox::Ok); "%1")
.arg(QString::fromStdString(Common::FS::GetEdenPathString(
Common::FS::EdenPath::ConfigDir))),
QMessageBox::Ok);
} }
void UserDataMigrator::MigrateUserData(QMainWindow* main_window, void UserDataMigrator::MigrateUserData(
const LegacyEmu selected_legacy_emu, const bool clear_shader_cache) { QMainWindow *main_window,
const LegacyEmu selected_legacy_emu,
const bool clear_shader_cache,
const MigrationStrategy strategy)
{
namespace fs = std::filesystem; namespace fs = std::filesystem;
const auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive; const auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive;
QString success_text = main_window->tr("Data was migrated successfully.");
std::string legacy_user_dir; std::string legacy_user_dir;
std::string legacy_config_dir; std::string legacy_config_dir;
std::string legacy_cache_dir; std::string legacy_cache_dir;
#define LEGACY_EMU(emu) case LegacyEmu::emu: \ #define LEGACY_EMU(emu) \
legacy_user_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##Dir).string(); \ case LegacyEmu::emu: \
legacy_config_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##ConfigDir).string(); \ legacy_user_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##Dir).string(); \
legacy_cache_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##CacheDir).string(); \ legacy_config_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##ConfigDir) \
break; .string(); \
legacy_cache_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##CacheDir) \
.string(); \
break;
switch (selected_legacy_emu) { switch (selected_legacy_emu) {
LEGACY_EMU(Citron) LEGACY_EMU(Citron)
@ -139,30 +206,74 @@ void UserDataMigrator::MigrateUserData(QMainWindow* main_window,
#undef LEGACY_EMU #undef LEGACY_EMU
fs::copy(legacy_user_dir, Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir), copy_options); fs::path eden_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir);
fs::path config_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir);
fs::path cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
fs::path shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
if (fs::is_directory(legacy_config_dir)) { fs::remove_all(eden_dir);
fs::copy(legacy_config_dir, Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir),
copy_options); switch (strategy) {
} case MigrationStrategy::Link:
if (fs::is_directory(legacy_cache_dir)) { // Create symlinks/directory junctions if requested
fs::copy(legacy_cache_dir, Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir), fs::create_directory_symlink(legacy_user_dir, eden_dir);
copy_options);
// Windows doesn't need any more links, because cache and config
// are already children of the root directory
#ifndef WIN32
if (fs::is_directory(legacy_config_dir)) {
fs::create_directory_symlink(legacy_config_dir, config_dir);
}
if (fs::is_directory(legacy_cache_dir)) {
fs::create_directory_symlink(legacy_cache_dir, cache_dir);
}
#endif
break;
case MigrationStrategy::Move:
// Rename directories if deletion is requested (achieves the same result)
fs::rename(legacy_user_dir, eden_dir);
// Windows doesn't need any more renames, because cache and config
// are already children of the root directory
#ifndef WIN32
if (fs::is_directory(legacy_config_dir)) {
fs::rename(legacy_config_dir, config_dir);
}
if (fs::is_directory(legacy_cache_dir)) {
fs::rename(legacy_cache_dir, cache_dir);
}
#endif
break;
case MigrationStrategy::Copy:
default:
// Default behavior: copy
fs::copy(legacy_user_dir, eden_dir, copy_options);
if (fs::is_directory(legacy_config_dir)) {
fs::copy(legacy_config_dir, config_dir, copy_options);
}
if (fs::is_directory(legacy_cache_dir)) {
fs::copy(legacy_cache_dir, cache_dir, copy_options);
}
success_text.append(
main_window->tr("\n\nIf 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(legacy_user_dir)));
break;
} }
// Delete and re-create shader dir // Delete and re-create shader dir
if (clear_shader_cache) { if (clear_shader_cache) {
fs::remove_all(Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)); fs::remove_all(shader_dir);
fs::create_directory(Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)); fs::create_directory(shader_dir);
} }
QMessageBox::information( QMessageBox::information(main_window,
main_window, main_window->tr("Migration"), main_window->tr("Migration"),
main_window success_text,
->tr("Data was migrated successfully.\n\n" QMessageBox::Ok);
"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(legacy_user_dir)),
QMessageBox::Ok);
} }

View file

@ -21,7 +21,14 @@ private:
Suyu, Suyu,
}; };
enum class MigrationStrategy {
Copy,
Move,
Link,
};
void ShowMigrationPrompt(QMainWindow* main_window); void ShowMigrationPrompt(QMainWindow* main_window);
void ShowMigrationCancelledMessage(QMainWindow* main_window); void ShowMigrationCancelledMessage(QMainWindow* main_window);
void MigrateUserData(QMainWindow* main_window, const LegacyEmu selected_legacy_emu, const bool clear_shader_cache); void MigrateUserData(QMainWindow* main_window, const LegacyEmu selected_legacy_emu,
const bool clear_shader_cache, const MigrationStrategy strategy);
}; };