mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-17 17:18:58 +02:00
[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>
This commit is contained in:
parent
0a54ac63f0
commit
5f9dba40a0
51 changed files with 1534 additions and 99 deletions
|
|
@ -4,9 +4,6 @@
|
|||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
|
||||
add_library(qt_common STATIC
|
||||
qt_common.h
|
||||
qt_common.cpp
|
||||
|
|
@ -27,6 +24,8 @@ add_library(qt_common STATIC
|
|||
qt_rom_util.h qt_rom_util.cpp
|
||||
qt_applet_util.h qt_applet_util.cpp
|
||||
qt_progress_dialog.h qt_progress_dialog.cpp
|
||||
qt_string_lookup.h
|
||||
qt_compress.h qt_compress.cpp
|
||||
|
||||
)
|
||||
|
||||
|
|
@ -37,9 +36,29 @@ if (ENABLE_QT)
|
|||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(qt_common PUBLIC
|
||||
# Use QStringBuilder for string concatenation to reduce
|
||||
# the overall number of temporary strings created.
|
||||
QT_USE_QSTRINGBUILDER
|
||||
|
||||
# Disable implicit conversions from/to C strings
|
||||
QT_NO_CAST_FROM_ASCII
|
||||
QT_NO_CAST_TO_ASCII
|
||||
|
||||
# Disable implicit type narrowing in signal/slot connect() calls.
|
||||
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
||||
|
||||
# Disable unsafe overloads of QProcess' start() function.
|
||||
QT_NO_PROCESS_COMBINED_ARGUMENT_START
|
||||
|
||||
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
|
||||
QT_NO_URL_CAST_FROM_STRING
|
||||
)
|
||||
|
||||
add_subdirectory(externals)
|
||||
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||
target_link_libraries(qt_common PUBLIC frozen::frozen)
|
||||
|
||||
if (NOT APPLE AND ENABLE_OPENGL)
|
||||
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)
|
||||
|
|
|
|||
3
src/qt_common/externals/CMakeLists.txt
vendored
3
src/qt_common/externals/CMakeLists.txt
vendored
|
|
@ -16,5 +16,4 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
|
|||
AddJsonPackage(quazip)
|
||||
|
||||
# frozen
|
||||
# TODO(crueter): Qt String Lookup
|
||||
# AddJsonPackage(frozen)
|
||||
AddJsonPackage(frozen)
|
||||
|
|
|
|||
354
src/qt_common/qt_compress.cpp
Normal file
354
src/qt_common/qt_compress.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_compress.h"
|
||||
#include "quazipfileinfo.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
|
||||
/** This is a modified version of JlCompress **/
|
||||
namespace QtCommon::Compress {
|
||||
|
||||
bool compressDir(QString fileCompressed,
|
||||
QString dir,
|
||||
const JlCompress::Options &options,
|
||||
QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
// Create zip
|
||||
QuaZip zip(fileCompressed);
|
||||
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
||||
if (!zip.open(QuaZip::mdCreate)) {
|
||||
QFile::remove(fileCompressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
// See how big the overall fs structure is
|
||||
// good approx. of total progress
|
||||
// TODO(crueter): QDirListing impl
|
||||
QDirIterator iter(dir,
|
||||
QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files,
|
||||
QDirIterator::Subdirectories);
|
||||
|
||||
std::size_t total = 0;
|
||||
while (iter.hasNext()) {
|
||||
total += iter.nextFileInfo().size();
|
||||
}
|
||||
|
||||
std::size_t progress = 0;
|
||||
callback(total, progress);
|
||||
|
||||
// Add the files and subdirectories
|
||||
if (!compressSubDir(&zip, dir, dir, options, total, progress, callback)) {
|
||||
QFile::remove(fileCompressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close zip
|
||||
zip.close();
|
||||
if (zip.getZipError() != 0) {
|
||||
QFile::remove(fileCompressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compressSubDir(QuaZip *zip,
|
||||
QString dir,
|
||||
QString origDir,
|
||||
const JlCompress::Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtProgressCallback callback)
|
||||
{
|
||||
// zip: object where to add the file
|
||||
// dir: current real directory
|
||||
// origDir: original real directory
|
||||
// (path(dir)-path(origDir)) = path inside the zip object
|
||||
|
||||
if (!zip)
|
||||
return false;
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend
|
||||
&& zip->getMode() != QuaZip::mdAdd)
|
||||
return false;
|
||||
|
||||
QDir directory(dir);
|
||||
if (!directory.exists())
|
||||
return false;
|
||||
|
||||
|
||||
QDir origDirectory(origDir);
|
||||
if (dir != origDir) {
|
||||
QuaZipFile dirZipFile(zip);
|
||||
std::unique_ptr<QuaZipNewInfo> qzni;
|
||||
if (options.getDateTime().isNull()) {
|
||||
qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir)
|
||||
+ QLatin1String("/"),
|
||||
dir);
|
||||
} else {
|
||||
qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir)
|
||||
+ QLatin1String("/"),
|
||||
dir,
|
||||
options.getDateTime());
|
||||
}
|
||||
if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) {
|
||||
return false;
|
||||
}
|
||||
dirZipFile.close();
|
||||
}
|
||||
|
||||
// For each subfolder
|
||||
QFileInfoList subfiles = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot
|
||||
| QDir::Hidden | QDir::Dirs);
|
||||
for (const auto &file : std::as_const(subfiles)) {
|
||||
if (!compressSubDir(
|
||||
zip, file.absoluteFilePath(), origDir, options, total, progress, callback)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For each file in directory
|
||||
QFileInfoList files = directory.entryInfoList(QDir::Hidden | QDir::Files);
|
||||
for (const auto &file : std::as_const(files)) {
|
||||
// If it's not a file or it's the compressed file being created
|
||||
if (!file.isFile() || file.absoluteFilePath() == zip->getZipName())
|
||||
continue;
|
||||
|
||||
// Create relative name for the compressed file
|
||||
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
|
||||
|
||||
// Compress the file
|
||||
if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, callback)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compressFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
const JlCompress::Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
// zip: object where to add the file
|
||||
// fileName: real file name
|
||||
// fileDest: file name inside the zip object
|
||||
|
||||
if (!zip)
|
||||
return false;
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend
|
||||
&& zip->getMode() != QuaZip::mdAdd)
|
||||
return false;
|
||||
|
||||
QuaZipFile outFile(zip);
|
||||
if (options.getDateTime().isNull()) {
|
||||
if (!outFile.open(QIODevice::WriteOnly,
|
||||
QuaZipNewInfo(fileDest, fileName),
|
||||
nullptr,
|
||||
0,
|
||||
options.getCompressionMethod(),
|
||||
options.getCompressionLevel()))
|
||||
return false;
|
||||
} else {
|
||||
if (!outFile.open(QIODevice::WriteOnly,
|
||||
QuaZipNewInfo(fileDest, fileName, options.getDateTime()),
|
||||
nullptr,
|
||||
0,
|
||||
options.getCompressionMethod(),
|
||||
options.getCompressionLevel()))
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo input(fileName);
|
||||
if (quazip_is_symlink(input)) {
|
||||
// Not sure if we should use any specialized codecs here.
|
||||
// After all, a symlink IS just a byte array. And
|
||||
// this is mostly for Linux, where UTF-8 is ubiquitous these days.
|
||||
QString path = quazip_symlink_target(input);
|
||||
QString relativePath = input.dir().relativeFilePath(path);
|
||||
outFile.write(QFile::encodeName(relativePath));
|
||||
} else {
|
||||
QFile inFile;
|
||||
inFile.setFileName(fileName);
|
||||
if (!inFile.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
if (!copyData(inFile, outFile, total, progress, callback) || outFile.getZipError() != UNZ_OK) {
|
||||
return false;
|
||||
}
|
||||
inFile.close();
|
||||
}
|
||||
|
||||
outFile.close();
|
||||
return outFile.getZipError() == UNZ_OK;
|
||||
}
|
||||
|
||||
bool copyData(QIODevice &inFile,
|
||||
QIODevice &outFile,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtProgressCallback callback)
|
||||
{
|
||||
while (!inFile.atEnd()) {
|
||||
char buf[4096];
|
||||
qint64 readLen = inFile.read(buf, 4096);
|
||||
if (readLen <= 0)
|
||||
return false;
|
||||
if (outFile.write(buf, readLen) != readLen)
|
||||
return false;
|
||||
|
||||
progress += readLen;
|
||||
if (!callback(total, progress)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
// Open zip
|
||||
QuaZip zip(fileCompressed);
|
||||
return extractDir(zip, dir, callback);
|
||||
}
|
||||
|
||||
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
return QStringList();
|
||||
}
|
||||
QString cleanDir = QDir::cleanPath(dir);
|
||||
QDir directory(cleanDir);
|
||||
QString absCleanDir = directory.absolutePath();
|
||||
if (!absCleanDir.endsWith(QLatin1Char('/'))) // It only ends with / if it's the FS root.
|
||||
absCleanDir += QLatin1Char('/');
|
||||
QStringList extracted;
|
||||
if (!zip.goToFirstFile()) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
std::size_t total = 0;
|
||||
for (const QuaZipFileInfo64 &info : zip.getFileInfoList64()) {
|
||||
total += info.uncompressedSize;
|
||||
}
|
||||
|
||||
std::size_t progress = 0;
|
||||
callback(total, progress);
|
||||
|
||||
do {
|
||||
QString name = zip.getCurrentFileName();
|
||||
QString absFilePath = directory.absoluteFilePath(name);
|
||||
QString absCleanPath = QDir::cleanPath(absFilePath);
|
||||
if (!absCleanPath.startsWith(absCleanDir))
|
||||
continue;
|
||||
if (!extractFile(&zip, QLatin1String(""), absFilePath, total, progress, callback)) {
|
||||
removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
extracted.append(absFilePath);
|
||||
} while (zip.goToNextFile());
|
||||
|
||||
// Close zip
|
||||
zip.close();
|
||||
if (zip.getZipError() != 0) {
|
||||
removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
bool extractFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
// zip: object where to add the file
|
||||
// filename: real file name
|
||||
// fileincompress: file name of the compressed file
|
||||
|
||||
if (!zip)
|
||||
return false;
|
||||
if (zip->getMode() != QuaZip::mdUnzip)
|
||||
return false;
|
||||
|
||||
if (!fileName.isEmpty())
|
||||
zip->setCurrentFile(fileName);
|
||||
QuaZipFile inFile(zip);
|
||||
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
|
||||
return false;
|
||||
|
||||
// Check existence of resulting file
|
||||
QDir curDir;
|
||||
if (fileDest.endsWith(QLatin1String("/"))) {
|
||||
if (!curDir.mkpath(fileDest)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QuaZipFileInfo64 info;
|
||||
if (!zip->getCurrentFileInfo(&info))
|
||||
return false;
|
||||
|
||||
QFile::Permissions srcPerm = info.getPermissions();
|
||||
if (fileDest.endsWith(QLatin1String("/")) && QFileInfo(fileDest).isDir()) {
|
||||
if (srcPerm != 0) {
|
||||
QFile(fileDest).setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info.isSymbolicLink()) {
|
||||
QString target = QFile::decodeName(inFile.readAll());
|
||||
return QFile::link(target, fileDest);
|
||||
}
|
||||
|
||||
// Open resulting file
|
||||
QFile outFile;
|
||||
outFile.setFileName(fileDest);
|
||||
if (!outFile.open(QIODevice::WriteOnly))
|
||||
return false;
|
||||
|
||||
// Copy data
|
||||
if (!copyData(inFile, outFile, total, progress, callback) || inFile.getZipError() != UNZ_OK) {
|
||||
outFile.close();
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
outFile.close();
|
||||
|
||||
// Close file
|
||||
inFile.close();
|
||||
if (inFile.getZipError() != UNZ_OK) {
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (srcPerm != 0) {
|
||||
outFile.setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool removeFile(QStringList listFile)
|
||||
{
|
||||
bool ret = true;
|
||||
// For each file
|
||||
for (int i = 0; i < listFile.count(); i++) {
|
||||
// Remove
|
||||
ret = ret && QFile::remove(listFile.at(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Compress
|
||||
71
src/qt_common/qt_compress.h
Normal file
71
src/qt_common/qt_compress.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <JlCompress.h>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
/** This is a modified version of JlCompress **/
|
||||
namespace QtCommon::Compress {
|
||||
|
||||
/**
|
||||
* @brief Compress an entire directory and report its progress.
|
||||
* @param fileCompressed Destination file
|
||||
* @param dir The directory to compress
|
||||
* @param options Compression level, etc
|
||||
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled.
|
||||
*/
|
||||
bool compressDir(QString fileCompressed,
|
||||
QString dir,
|
||||
const JlCompress::Options& options = JlCompress::Options(),
|
||||
QtCommon::QtProgressCallback callback = {});
|
||||
|
||||
// Internal //
|
||||
bool compressSubDir(QuaZip *zip,
|
||||
QString dir,
|
||||
QString origDir,
|
||||
const JlCompress::Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool compressFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
const JlCompress::Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool copyData(QIODevice &inFile,
|
||||
QIODevice &outFile,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
|
||||
// Extract //
|
||||
|
||||
/**
|
||||
* @brief Extract a zip file and report its progress.
|
||||
* @param fileCompressed Compressed file
|
||||
* @param dir The directory to push the results to
|
||||
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled.
|
||||
*/
|
||||
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback = {});
|
||||
|
||||
// Internal //
|
||||
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool extractFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool removeFile(QStringList listFile);
|
||||
|
||||
}
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_common/qt_game_util.h"
|
||||
#include "qt_content_util.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "frontend_common/data_manager.h"
|
||||
#include "frontend_common/firmware_manager.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "qt_common/qt_compress.h"
|
||||
#include "qt_common/qt_game_util.h"
|
||||
#include "qt_common/qt_progress_dialog.h"
|
||||
#include "qt_frontend_util.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QtConcurrentRun>
|
||||
#include <JlCompress.h>
|
||||
#include <qfuturewatcher.h>
|
||||
|
||||
namespace QtCommon::Content {
|
||||
|
||||
|
|
@ -21,10 +26,10 @@ bool CheckGameFirmware(u64 program_id, QObject* parent)
|
|||
&& !FirmwareManager::CheckFirmwarePresence(*system)) {
|
||||
auto result = QtCommon::Frontend::ShowMessage(
|
||||
QMessageBox::Warning,
|
||||
"Game Requires Firmware",
|
||||
"The game you are trying to launch requires firmware to boot or to get past the "
|
||||
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
|
||||
"dump and install firmware</a>, or press \"OK\" to launch anyways.",
|
||||
tr("Game Requires Firmware"),
|
||||
tr("The game you are trying to launch requires firmware to boot or to get past the "
|
||||
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
|
||||
"dump and install firmware</a>, or press \"OK\" to launch anyways."),
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
parent);
|
||||
|
||||
|
|
@ -60,8 +65,8 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
|
||||
const auto ShowMessage = [&]() {
|
||||
QtCommon::Frontend::ShowMessage(icon,
|
||||
failedTitle,
|
||||
GetFirmwareInstallResultString(result));
|
||||
tr(failedTitle),
|
||||
tr(GetFirmwareInstallResultString(result)));
|
||||
};
|
||||
|
||||
LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
|
||||
|
|
@ -125,8 +130,8 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
i++;
|
||||
auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(),
|
||||
FileSys::OpenMode::Read);
|
||||
auto firmware_dst_vfile = firmware_vdir
|
||||
->CreateFileRelative(firmware_src_path.filename().string());
|
||||
auto firmware_dst_vfile = firmware_vdir->CreateFileRelative(
|
||||
firmware_src_path.filename().string());
|
||||
|
||||
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
|
||||
LOG_ERROR(Frontend,
|
||||
|
|
@ -168,9 +173,9 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
const auto failed_names = QString::fromStdString(
|
||||
fmt::format("{}", fmt::join(results, "\n")));
|
||||
progress.close();
|
||||
QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1")
|
||||
.arg(failed_names));
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Firmware integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -181,10 +186,10 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
const std::string display_version(firmware_data.display_version.data());
|
||||
|
||||
result = FirmwareInstallResult::Success;
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr(successTitle),
|
||||
tr(GetFirmwareInstallResultString(result))
|
||||
.arg(QString::fromStdString(display_version)));
|
||||
QtCommon::Frontend::Information(
|
||||
rootObject,
|
||||
tr(successTitle),
|
||||
tr(GetFirmwareInstallResultString(result)).arg(QString::fromStdString(display_version)));
|
||||
}
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString& location)
|
||||
|
|
@ -193,7 +198,7 @@ QString UnzipFirmwareToTmp(const QString& location)
|
|||
fs::path tmp{fs::temp_directory_path()};
|
||||
|
||||
if (!fs::create_directories(tmp / "eden" / "firmware")) {
|
||||
return "";
|
||||
return QString();
|
||||
}
|
||||
|
||||
tmp /= "eden";
|
||||
|
|
@ -205,7 +210,7 @@ QString UnzipFirmwareToTmp(const QString& location)
|
|||
|
||||
QStringList result = JlCompress::extractDir(&zip, qCacheDir);
|
||||
if (result.isEmpty()) {
|
||||
return "";
|
||||
return QString();
|
||||
}
|
||||
|
||||
return qCacheDir;
|
||||
|
|
@ -264,9 +269,8 @@ void InstallKeys()
|
|||
return;
|
||||
}
|
||||
|
||||
FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location
|
||||
.toStdString(),
|
||||
"keys");
|
||||
FirmwareManager::KeyInstallResult result
|
||||
= FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys");
|
||||
|
||||
system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
|
||||
|
|
@ -282,9 +286,14 @@ void InstallKeys()
|
|||
}
|
||||
}
|
||||
|
||||
void VerifyInstalledContents() {
|
||||
void VerifyInstalledContents()
|
||||
{
|
||||
// Initialize a progress dialog.
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject);
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
|
||||
tr("Cancel"),
|
||||
0,
|
||||
100,
|
||||
QtCommon::rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
|
|
@ -296,16 +305,17 @@ void VerifyInstalledContents() {
|
|||
return progress.wasCanceled();
|
||||
};
|
||||
|
||||
const std::vector<std::string> result =
|
||||
ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback);
|
||||
const std::vector<std::string> result
|
||||
= ContentManager::VerifyInstalledContents(*QtCommon::system,
|
||||
*QtCommon::provider,
|
||||
QtProgressCallback);
|
||||
progress.close();
|
||||
|
||||
if (result.empty()) {
|
||||
QtCommon::Frontend::Information(tr("Integrity verification succeeded!"),
|
||||
tr("The operation completed successfully."));
|
||||
} else {
|
||||
const auto failed_names =
|
||||
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
const auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
|
|
@ -332,7 +342,7 @@ void FixProfiles()
|
|||
qorphaned.reserve(8 * 33);
|
||||
|
||||
for (const std::string& s : orphaned) {
|
||||
qorphaned += "\n" + QString::fromStdString(s);
|
||||
qorphaned = qorphaned % QStringLiteral("\n") % QString::fromStdString(s);
|
||||
}
|
||||
|
||||
QtCommon::Frontend::Critical(
|
||||
|
|
@ -348,4 +358,184 @@ void FixProfiles()
|
|||
QtCommon::Game::OpenSaveFolder();
|
||||
}
|
||||
|
||||
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id)
|
||||
{
|
||||
auto result = QtCommon::Frontend::Warning(tr("Really clear data?"),
|
||||
tr("Important data may be lost!"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (result != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
result = QtCommon::Frontend::Warning(
|
||||
tr("Are you REALLY sure?"),
|
||||
tr("Once deleted, your data will NOT come back!\n"
|
||||
"Only do this if you're 100% sure you want to delete this data."),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (result != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0);
|
||||
dialog.show();
|
||||
|
||||
FrontendCommon::DataManager::ClearDir(dir, user_id);
|
||||
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
||||
const std::string& user_id,
|
||||
const QString& name,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
using namespace QtCommon::Frontend;
|
||||
const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir, user_id);
|
||||
|
||||
const QString zip_dump_location = GetSaveFileName(tr("Select Export Location"),
|
||||
tr("%1.zip").arg(name),
|
||||
tr("Zipped Archives (*.zip)"));
|
||||
|
||||
if (zip_dump_location.isEmpty())
|
||||
return;
|
||||
|
||||
QtProgressDialog* progress = new QtProgressDialog(
|
||||
tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100, rootObject);
|
||||
|
||||
progress->setWindowTitle(tr("Exporting"));
|
||||
progress->setWindowModality(Qt::WindowModal);
|
||||
progress->setMinimumDuration(100);
|
||||
progress->setAutoClose(false);
|
||||
progress->setAutoReset(false);
|
||||
progress->show();
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
auto progress_callback = [=](size_t total_size, size_t processed_size) {
|
||||
QMetaObject::invokeMethod(progress,
|
||||
&QtProgressDialog::setValue,
|
||||
static_cast<int>((processed_size * 100) / total_size));
|
||||
|
||||
return !progress->wasCanceled();
|
||||
};
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run([=]() {
|
||||
return QtCommon::Compress::compressDir(zip_dump_location,
|
||||
QString::fromStdString(dir),
|
||||
JlCompress::Options(),
|
||||
progress_callback);
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
|
||||
|
||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
|
||||
progress->close();
|
||||
|
||||
if (watcher->result()) {
|
||||
Information(tr("Exported Successfully"), tr("Data was exported successfully."));
|
||||
} else if (progress->wasCanceled()) {
|
||||
Information(tr("Export Cancelled"), tr("Export was cancelled by the user."));
|
||||
} else {
|
||||
Critical(
|
||||
tr("Export Failed"),
|
||||
tr("Ensure you have write permissions on the targeted directory and try again."));
|
||||
}
|
||||
|
||||
progress->deleteLater();
|
||||
watcher->deleteLater();
|
||||
if (callback)
|
||||
callback();
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
||||
const std::string& user_id,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir, user_id);
|
||||
|
||||
using namespace QtCommon::Frontend;
|
||||
|
||||
const QString zip_dump_location = GetOpenFileName(tr("Select Import Location"),
|
||||
{},
|
||||
tr("Zipped Archives (*.zip)"));
|
||||
|
||||
if (zip_dump_location.isEmpty())
|
||||
return;
|
||||
|
||||
StandardButton button = Warning(
|
||||
tr("Import Warning"),
|
||||
tr("All previous data in this directory will be deleted. Are you sure you wish to "
|
||||
"proceed?"),
|
||||
StandardButton::Yes | StandardButton::No);
|
||||
|
||||
if (button != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
QtProgressDialog* progress = new QtProgressDialog(
|
||||
tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100, rootObject);
|
||||
|
||||
progress->setWindowTitle(tr("Importing"));
|
||||
progress->setWindowModality(Qt::WindowModal);
|
||||
progress->setMinimumDuration(100);
|
||||
progress->setAutoClose(false);
|
||||
progress->setAutoReset(false);
|
||||
progress->show();
|
||||
progress->setValue(0);
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
// to prevent GUI mangling we have to run this in a thread as well
|
||||
QFuture<bool> delete_future = QtConcurrent::run([=]() {
|
||||
FrontendCommon::DataManager::ClearDir(data_dir, user_id);
|
||||
return !progress->wasCanceled();
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* delete_watcher = new QFutureWatcher<bool>(rootObject);
|
||||
delete_watcher->setFuture(delete_future);
|
||||
|
||||
QObject::connect(delete_watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
|
||||
auto progress_callback = [=](size_t total_size, size_t processed_size) {
|
||||
QMetaObject::invokeMethod(progress,
|
||||
&QtProgressDialog::setValue,
|
||||
static_cast<int>((processed_size * 100) / total_size));
|
||||
|
||||
return !progress->wasCanceled();
|
||||
};
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run([=]() {
|
||||
return !QtCommon::Compress::extractDir(zip_dump_location,
|
||||
QString::fromStdString(dir),
|
||||
progress_callback)
|
||||
.empty();
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
|
||||
|
||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
|
||||
progress->close();
|
||||
|
||||
// this sucks
|
||||
if (watcher->result()) {
|
||||
Information(tr("Imported Successfully"), tr("Data was imported successfully."));
|
||||
} else if (progress->wasCanceled()) {
|
||||
Information(tr("Import Cancelled"), tr("Import was cancelled by the user."));
|
||||
} else {
|
||||
Critical(tr("Import Failed"),
|
||||
tr("Ensure you have read permissions on the targeted directory and try "
|
||||
"again."));
|
||||
}
|
||||
|
||||
progress->deleteLater();
|
||||
watcher->deleteLater();
|
||||
if (callback)
|
||||
callback();
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Content
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#include "common/common_types.h"
|
||||
#include "frontend_common/data_manager.h"
|
||||
|
||||
namespace QtCommon::Content {
|
||||
|
||||
|
|
@ -46,6 +47,13 @@ void InstallKeys();
|
|||
void VerifyGameContents(const std::string &game_path);
|
||||
void VerifyInstalledContents();
|
||||
|
||||
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "");
|
||||
void ExportDataDir(FrontendCommon::DataManager::DataDir dir,
|
||||
const std::string &user_id = "",
|
||||
const QString &name = QStringLiteral("export"),
|
||||
std::function<void()> callback = {});
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "", std::function<void()> callback = {});
|
||||
|
||||
// Profiles //
|
||||
void FixProfiles();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,4 +32,15 @@ const QString GetOpenFileName(const QString &title,
|
|||
#endif
|
||||
}
|
||||
|
||||
const QString GetSaveFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter,
|
||||
Options options)
|
||||
{
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getSaveFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
|
|
|
|||
|
|
@ -97,10 +97,10 @@ Q_ENUM_NS(Icon)
|
|||
|
||||
// TODO(crueter) widgets-less impl, choices et al.
|
||||
StandardButton ShowMessage(Icon icon,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
StandardButtons buttons = StandardButton::NoButton,
|
||||
QObject *parent = nullptr);
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
StandardButtons buttons = StandardButton::NoButton,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
#define UTIL_OVERRIDES(level) \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
|
|
@ -110,21 +110,6 @@ StandardButton ShowMessage(Icon icon,
|
|||
{ \
|
||||
return ShowMessage(Icon::level, title, text, buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
const char *title, \
|
||||
const char *text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(const char *title, \
|
||||
const char *text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \
|
||||
} \
|
||||
inline StandardButton level(const QString title, \
|
||||
const QString &text, \
|
||||
StandardButtons buttons \
|
||||
|
|
@ -144,5 +129,11 @@ const QString GetOpenFileName(const QString &title,
|
|||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
|
||||
const QString GetSaveFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
#endif // QT_FRONTEND_UTIL_H
|
||||
|
|
|
|||
|
|
@ -220,8 +220,8 @@ void RemoveBaseContent(u64 program_id, InstalledEntryType type)
|
|||
program_id);
|
||||
if (res) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
"Successfully Removed",
|
||||
"Successfully removed the installed base game.");
|
||||
tr("Successfully Removed"),
|
||||
tr("Successfully removed the installed base game."));
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(
|
||||
rootObject,
|
||||
|
|
@ -235,8 +235,8 @@ void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
|
|||
const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
|
||||
if (res) {
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
"Successfully Removed",
|
||||
"Successfully removed the installed update.");
|
||||
tr("Successfully Removed"),
|
||||
tr("Successfully removed the installed update."));
|
||||
} else {
|
||||
QtCommon::Frontend::Warning(rootObject,
|
||||
GetGameListErrorRemoving(type),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,12 @@ bool OpenShaderCache(u64 program_id, QObject *parent)
|
|||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)};
|
||||
if (!Common::FS::CreateDirs(shader_cache_folder_path)) {
|
||||
QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent);
|
||||
QtCommon::Frontend::ShowMessage(QMessageBox::Warning,
|
||||
tr("Error Opening Shader Cache"),
|
||||
tr("Failed to create or open shader cache for this title, "
|
||||
"ensure your app data directory has write permissions."),
|
||||
QMessageBox::Ok,
|
||||
parent);
|
||||
}
|
||||
|
||||
const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};
|
||||
|
|
|
|||
38
src/qt_common/qt_string_lookup.h
Normal file
38
src/qt_common/qt_string_lookup.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <frozen/string.h>
|
||||
#include <frozen/unordered_map.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
namespace QtCommon::StringLookup {
|
||||
|
||||
Q_NAMESPACE
|
||||
|
||||
// TODO(crueter): QML interface
|
||||
enum StringKey {
|
||||
SavesTooltip,
|
||||
ShadersTooltip,
|
||||
UserNandTooltip,
|
||||
SysNandTooltip,
|
||||
ModsTooltip,
|
||||
};
|
||||
|
||||
static constexpr const frozen::unordered_map<StringKey, frozen::string, 5> strings = {
|
||||
{SavesTooltip, "Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"},
|
||||
{ShadersTooltip, "Contains Vulkan and OpenGL pipeline caches. Generally safe to remove."},
|
||||
{UserNandTooltip, "Contains updates and DLC for games."},
|
||||
{SysNandTooltip, "Contains firmware and applet data."},
|
||||
{ModsTooltip, "Contains game mods, patches, and cheats."},
|
||||
};
|
||||
|
||||
static inline const QString Lookup(StringKey key)
|
||||
{
|
||||
return QString::fromStdString(strings.at(key).data());
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue