From 5f9dba40a0fe71ccd7014ae37a2fbfd8f225396e Mon Sep 17 00:00:00 2001 From: crueter Date: Wed, 15 Oct 2025 04:54:41 +0200 Subject: [desktop] Data Manager, data import/export (#2700) This adds a "Data Manager" dialog to the Tools menu. The Data Manager allows for the following operations: - Open w/ system file manager - Clear - Export - Import On any of the following directories: - Save (w/ profile selector) - UserNAND - SysNAND - Mods - Shaders TODO for the future: - "Cleanup" for each directory - TitleID -> Game name--let users clean data for a specific game if applicable Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2700 Reviewed-by: MaranBr Reviewed-by: Lizzie --- src/yuzu/data_dialog.cpp | 167 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/yuzu/data_dialog.cpp (limited to 'src/yuzu/data_dialog.cpp') diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp new file mode 100644 index 0000000000..bae49ff351 --- /dev/null +++ b/src/yuzu/data_dialog.cpp @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "data_dialog.h" +#include "core/hle/service/acc/profile_manager.h" +#include "frontend_common/data_manager.h" +#include "qt_common/qt_common.h" +#include "qt_common/qt_content_util.h" +#include "qt_common/qt_string_lookup.h" +#include "ui_data_dialog.h" + +#include +#include +#include +#include +#include + +#include + +#include + +DataDialog::DataDialog(QWidget *parent) + : QDialog(parent) + , ui(std::make_unique()) +{ + ui->setupUi(this); + + // TODO: Should we make this a single widget that pulls data from a model? +#define WIDGET(name) \ + ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \ + QtCommon::StringLookup::name##Tooltip, \ + QStringLiteral(#name), \ + this)); + + WIDGET(Saves) + WIDGET(Shaders) + WIDGET(UserNand) + WIDGET(SysNand) + WIDGET(Mods) + +#undef WIDGET + + connect(ui->labels, &QListWidget::itemSelectionChanged, this, [this]() { + const auto items = ui->labels->selectedItems(); + if (items.isEmpty()) { + return; + } + + ui->page->setCurrentIndex(ui->labels->row(items[0])); + }); +} + +DataDialog::~DataDialog() = default; + +DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, + QtCommon::StringLookup::StringKey tooltip, + const QString &exportName, + QWidget *parent) + : QWidget(parent) + , ui(std::make_unique()) + , m_dir(data_dir) + , m_exportName(exportName) +{ + ui->setupUi(this); + + ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip)); + + ui->clear->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); + ui->open->setIcon(QIcon::fromTheme(QStringLiteral("folder"))); + ui->upload->setIcon(QIcon::fromTheme(QStringLiteral("upload"))); + ui->download->setIcon(QIcon::fromTheme(QStringLiteral("download"))); + + connect(ui->clear, &QPushButton::clicked, this, &DataWidget::clear); + connect(ui->open, &QPushButton::clicked, this, &DataWidget::open); + connect(ui->upload, &QPushButton::clicked, this, &DataWidget::upload); + connect(ui->download, &QPushButton::clicked, this, &DataWidget::download); + + scan(); +} + +void DataWidget::clear() +{ + std::string user_id{}; + if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { + user_id = selectProfile(); + } + QtCommon::Content::ClearDataDir(m_dir, user_id); + scan(); +} + +void DataWidget::open() +{ + std::string user_id{}; + if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { + user_id = selectProfile(); + } + QDesktopServices::openUrl(QUrl::fromLocalFile( + QString::fromStdString(FrontendCommon::DataManager::GetDataDir(m_dir, user_id)))); +} + +void DataWidget::upload() +{ + std::string user_id{}; + if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { + user_id = selectProfile(); + } + QtCommon::Content::ExportDataDir(m_dir, user_id, m_exportName); +} + +void DataWidget::download() +{ + std::string user_id{}; + if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { + user_id = selectProfile(); + } + QtCommon::Content::ImportDataDir(m_dir, user_id, std::bind(&DataWidget::scan, this)); +} + +void DataWidget::scan() { + ui->size->setText(tr("Calculating...")); + + QFutureWatcher *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcher::finished, this, [=, this]() { + u64 size = watcher->result(); + ui->size->setText( + QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size))); + watcher->deleteLater(); + }); + + watcher->setFuture( + QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); })); +} + +std::string DataWidget::selectProfile() +{ + const auto select_profile = [this] { + const Core::Frontend::ProfileSelectParameters parameters{ + .mode = Service::AM::Frontend::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Frontend::UserSelectionPurpose::General, + }; + QtProfileSelectionDialog dialog(*QtCommon::system, this, parameters); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint + | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::WindowModal); + + if (dialog.exec() == QDialog::Rejected) { + return -1; + } + + return dialog.GetIndex(); + }; + + const auto index = select_profile(); + if (index == -1) { + return ""; + } + + const auto uuid = QtCommon::system->GetProfileManager().GetUser(static_cast(index)); + ASSERT(uuid); + + const auto user_id = uuid->AsU128(); + + return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]); +} -- cgit v1.2.3