diff options
| author | crueter <crueter@eden-emu.dev> | 2025-10-15 04:54:41 +0200 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2025-10-15 04:54:41 +0200 |
| commit | 5f9dba40a0fe71ccd7014ae37a2fbfd8f225396e (patch) | |
| tree | 4a0258d989bb795dd68fc2067fb01a7d7703868b /src/yuzu/data_dialog.cpp | |
| parent | 0a54ac63f00782605b74044e1661569d931354fe (diff) | |
[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 <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2700
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Diffstat (limited to 'src/yuzu/data_dialog.cpp')
| -rw-r--r-- | src/yuzu/data_dialog.cpp | 167 |
1 files changed, 167 insertions, 0 deletions
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 <QDesktopServices> +#include <QFileDialog> +#include <QFutureWatcher> +#include <QProgressDialog> +#include <QtConcurrentRun> + +#include <core/frontend/applets/profile_select.h> + +#include <applets/qt_profile_select.h> + +DataDialog::DataDialog(QWidget *parent) + : QDialog(parent) + , ui(std::make_unique<Ui::DataDialog>()) +{ + 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<Ui::DataWidget>()) + , 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<u64> *watcher = new QFutureWatcher<u64>(this); + + connect(watcher, &QFutureWatcher<u64>::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<std::size_t>(index)); + ASSERT(uuid); + + const auto user_id = uuid->AsU128(); + + return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]); +} |
