a-la-karte/src/app.cpp

1067 lines
32 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include "app.h"
#include "bottlesimporter.h"
#include "desktopimporter.h"
#include "flatpakimporter.h"
#include "heroicimporter.h"
#include "itchimporter.h"
#include "legendaryimporter.h"
#include "lutrisimporter.h"
#include "retroarchimporter.h"
#include "steamimporter.h"
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStandardPaths>
#include <QtConcurrent>
App *App::s_instance = nullptr;
App::App(QObject *parent)
: QObject(parent)
, m_gameModel(new GameModel(this))
, m_launcher(new GameLauncher(this))
, m_steamGridDB(new SteamGridDB(this))
, m_mediaManager(new MediaManager(this))
, m_config(new Config(this))
{
loadLibrary();
if (!m_config->importSteam()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Steam")) > 0) {
saveLibrary();
}
}
if (!m_config->importLutris()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Lutris")) > 0) {
saveLibrary();
}
}
if (!m_config->importHeroic()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Heroic")) > 0) {
saveLibrary();
}
}
if (!m_config->importDesktop()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Desktop")) > 0) {
saveLibrary();
}
}
if (!m_config->importBottles()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Bottles")) > 0) {
saveLibrary();
}
}
if (!m_config->importFlatpak()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Flatpak")) > 0) {
saveLibrary();
}
}
if (!m_config->importItch()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("itch.io")) > 0) {
saveLibrary();
}
}
if (!m_config->importLegendary()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Legendary")) > 0) {
saveLibrary();
}
}
if (!m_config->importRetroArch()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("RetroArch")) > 0) {
saveLibrary();
}
}
connect(m_config, &Config::importSteamChanged, this, [this]() {
if (!m_config->importSteam()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Steam")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importLutrisChanged, this, [this]() {
if (!m_config->importLutris()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Lutris")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importHeroicChanged, this, [this]() {
if (!m_config->importHeroic()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Heroic")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importDesktopChanged, this, [this]() {
if (!m_config->importDesktop()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Desktop")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importBottlesChanged, this, [this]() {
if (!m_config->importBottles()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Bottles")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importFlatpakChanged, this, [this]() {
if (!m_config->importFlatpak()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Flatpak")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importItchChanged, this, [this]() {
if (!m_config->importItch()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("itch.io")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importLegendaryChanged, this, [this]() {
if (!m_config->importLegendary()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("Legendary")) > 0) {
saveLibrary();
}
}
});
connect(m_config, &Config::importRetroArchChanged, this, [this]() {
if (!m_config->importRetroArch()) {
if (m_gameModel->removeByPlatformPrefix(QStringLiteral("RetroArch")) > 0) {
saveLibrary();
}
}
});
}
App *App::instance()
{
if (!s_instance) {
s_instance = new App();
}
return s_instance;
}
App *App::create(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return instance();
}
GameModel *App::gameModel() const
{
return m_gameModel;
}
GameLauncher *App::launcher() const
{
return m_launcher;
}
SteamGridDB *App::steamGridDB() const
{
return m_steamGridDB;
}
MediaManager *App::mediaManager() const
{
return m_mediaManager;
}
Config *App::config() const
{
return m_config;
}
bool App::importing() const
{
return m_importing;
}
QString App::importStatus() const
{
return m_importStatus;
}
void App::setImporting(bool importing)
{
if (m_importing != importing) {
m_importing = importing;
Q_EMIT importingChanged();
}
}
void App::setImportStatus(const QString &status)
{
if (m_importStatus != status) {
m_importStatus = status;
Q_EMIT importStatusChanged();
}
}
void App::importAllGames()
{
if (m_importing)
return;
const bool anyEnabled = m_config->importSteam() || m_config->importLutris() || m_config->importHeroic() || m_config->importDesktop()
|| m_config->importBottles() || m_config->importFlatpak() || m_config->importItch() || m_config->importLegendary() || m_config->importRetroArch();
const bool doSteam = m_config->importSteam() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Steam"));
const bool doLutris = m_config->importLutris() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Lutris"));
const bool doHeroic = m_config->importHeroic() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Heroic"));
const bool doDesktop = m_config->importDesktop() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Desktop"));
const bool doBottles = m_config->importBottles() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Bottles"));
const bool doFlatpak = m_config->importFlatpak() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Flatpak"));
const bool doItch = m_config->importItch() && !m_gameModel->hasPlatformPrefix(QStringLiteral("itch.io"));
const bool doLegendary = m_config->importLegendary() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Legendary"));
const bool doRetroArch = m_config->importRetroArch() && !m_gameModel->hasPlatformPrefix(QStringLiteral("RetroArch"));
if (!anyEnabled) {
setImportStatus(tr("No import sources enabled"));
Q_EMIT importCompleted(0);
return;
}
if (!(doSteam || doLutris || doHeroic || doDesktop || doBottles || doFlatpak || doItch || doLegendary || doRetroArch)) {
setImportStatus(tr("All enabled sources already imported"));
Q_EMIT importCompleted(0);
return;
}
setImporting(true);
setImportStatus(tr("Importing games..."));
[[maybe_unused]] auto future = QtConcurrent::run([this, doSteam, doLutris, doHeroic, doDesktop, doBottles, doFlatpak, doItch, doLegendary, doRetroArch]() {
int totalCount = 0;
// Import from Steam
if (doSteam) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning Steam library..."));
},
Qt::QueuedConnection);
SteamImporter steamImporter;
QList<Game *> steamGames = steamImporter.importGames();
for (Game *game : steamGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, steamGames]() {
if (!m_config->importSteam()) {
for (Game *game : steamGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : steamGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += steamGames.count();
}
// Import from Lutris
if (doLutris) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning Lutris library..."));
},
Qt::QueuedConnection);
LutrisImporter lutrisImporter;
QList<Game *> lutrisGames = lutrisImporter.importGames();
for (Game *game : lutrisGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, lutrisGames]() {
if (!m_config->importLutris()) {
for (Game *game : lutrisGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : lutrisGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += lutrisGames.count();
}
// Import from Heroic
if (doHeroic) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning Heroic library..."));
},
Qt::QueuedConnection);
HeroicImporter heroicImporter;
QList<Game *> heroicGames = heroicImporter.importGames();
for (Game *game : heroicGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, heroicGames]() {
if (!m_config->importHeroic()) {
for (Game *game : heroicGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : heroicGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += heroicGames.count();
}
// Import from Desktop entries
if (doDesktop) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning desktop entries..."));
},
Qt::QueuedConnection);
DesktopImporter desktopImporter;
QList<Game *> desktopGames = desktopImporter.importGames();
for (Game *game : desktopGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, desktopGames]() {
if (!m_config->importDesktop()) {
for (Game *game : desktopGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : desktopGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += desktopGames.count();
}
// Import from Bottles
if (doBottles) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning Bottles..."));
},
Qt::QueuedConnection);
BottlesImporter bottlesImporter;
QList<Game *> bottlesGames = bottlesImporter.importGames();
for (Game *game : bottlesGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, bottlesGames]() {
if (!m_config->importBottles()) {
for (Game *game : bottlesGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : bottlesGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += bottlesGames.count();
}
// Import from Flatpak
if (doFlatpak) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning Flatpak games..."));
},
Qt::QueuedConnection);
FlatpakImporter flatpakImporter;
QList<Game *> flatpakGames = flatpakImporter.importGames();
for (Game *game : flatpakGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, flatpakGames]() {
if (!m_config->importFlatpak()) {
for (Game *game : flatpakGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : flatpakGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += flatpakGames.count();
}
// Import from itch.io
if (doItch) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning itch.io library..."));
},
Qt::QueuedConnection);
ItchImporter itchImporter;
QList<Game *> itchGames = itchImporter.importGames();
for (Game *game : itchGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, itchGames]() {
if (!m_config->importItch()) {
for (Game *game : itchGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : itchGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += itchGames.count();
}
// Import from Legendary
if (doLegendary) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning Legendary library..."));
},
Qt::QueuedConnection);
LegendaryImporter legendaryImporter;
QList<Game *> legendaryGames = legendaryImporter.importGames();
for (Game *game : legendaryGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, legendaryGames]() {
if (!m_config->importLegendary()) {
for (Game *game : legendaryGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : legendaryGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += legendaryGames.count();
}
// Import from RetroArch
if (doRetroArch) {
QMetaObject::invokeMethod(
this,
[this]() {
setImportStatus(tr("Scanning RetroArch playlists..."));
},
Qt::QueuedConnection);
RetroArchImporter retroArchImporter;
QList<Game *> retroArchGames = retroArchImporter.importGames();
for (Game *game : retroArchGames) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, retroArchGames]() {
if (!m_config->importRetroArch()) {
for (Game *game : retroArchGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : retroArchGames) {
m_gameModel->addGame(game);
}
},
Qt::QueuedConnection);
totalCount += retroArchGames.count();
}
// Complete
QMetaObject::invokeMethod(
this,
[this, totalCount]() {
setImportStatus(tr("Import complete: %1 games found").arg(totalCount));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(totalCount);
},
Qt::QueuedConnection);
});
}
void App::importFromSteam()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning Steam library..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
SteamImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Steam import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromLutris()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning Lutris library..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
LutrisImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Lutris import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromHeroic()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning Heroic library..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
HeroicImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Heroic import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromDesktop()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning desktop entries..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
DesktopImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Desktop import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromBottles()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning Bottles..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
BottlesImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Bottles import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromFlatpak()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning Flatpak games..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
FlatpakImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Flatpak import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromItch()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning itch.io library..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
ItchImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("itch.io import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromLegendary()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning Legendary library..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
LegendaryImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("Legendary import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::importFromRetroArch()
{
if (m_importing)
return;
setImporting(true);
setImportStatus(tr("Scanning RetroArch playlists..."));
[[maybe_unused]] auto future = QtConcurrent::run([this]() {
RetroArchImporter importer;
QList<Game *> games = importer.importGames();
for (Game *game : games) {
game->moveToThread(this->thread());
game->setParent(nullptr);
}
QMetaObject::invokeMethod(
this,
[this, games]() {
for (Game *game : games) {
m_gameModel->addGame(game);
}
setImportStatus(tr("RetroArch import complete: %1 games found").arg(games.count()));
setImporting(false);
saveLibrary();
Q_EMIT importCompleted(games.count());
},
Qt::QueuedConnection);
});
}
void App::clearLibrary()
{
m_gameModel->clear();
saveLibrary();
}
void App::removeMissingGames()
{
QList<Game *> gamesToRemove;
const QList<Game *> games = m_gameModel->allGames();
for (Game *game : games) {
if (!game)
continue;
QString launchCommand = game->launchCommand();
// Skip URL-based launchers (Steam, Lutris, etc.)
if (launchCommand.startsWith(QLatin1String("steam://")) || launchCommand.startsWith(QLatin1String("lutris:"))
|| launchCommand.startsWith(QLatin1String("xdg-open")) || launchCommand.startsWith(QLatin1String("flatpak run"))
|| launchCommand.startsWith(QLatin1String("legendary launch")) || launchCommand.startsWith(QLatin1String("bottles"))) {
continue;
}
// Extract executable path from command
QString executable = launchCommand;
// Handle quoted paths
if (executable.startsWith(QLatin1Char('"'))) {
int endQuote = executable.indexOf(QLatin1Char('"'), 1);
if (endQuote > 0) {
executable = executable.mid(1, endQuote - 1);
}
} else {
// Take first word as executable
int space = executable.indexOf(QLatin1Char(' '));
if (space > 0) {
executable = executable.left(space);
}
}
// Check if executable exists
if (!executable.isEmpty() && !QFile::exists(executable)) {
gamesToRemove.append(game);
}
}
// Remove missing games
for (Game *game : gamesToRemove) {
m_gameModel->removeGame(game->id());
}
if (!gamesToRemove.isEmpty()) {
saveLibrary();
}
}
void App::saveLibrary()
{
QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(dataPath);
if (!dir.exists()) {
dir.mkpath(dataPath);
}
QFile file(dataPath + QStringLiteral("/library.json"));
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "Failed to save library:" << file.errorString();
return;
}
QJsonArray gamesArray;
const QList<Game *> games = m_gameModel->allGames();
for (Game *game : games) {
if (game) {
gamesArray.append(game->toJson());
}
}
QJsonDocument doc(gamesArray);
file.write(doc.toJson());
}
void App::loadLibrary()
{
QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QFile file(dataPath + QStringLiteral("/library.json"));
if (!file.exists()) {
return;
}
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Failed to load library:" << file.errorString();
return;
}
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
if (!doc.isArray()) {
return;
}
QJsonArray gamesArray = doc.array();
for (const QJsonValue &value : gamesArray) {
if (value.isObject()) {
Game *game = Game::fromJson(value.toObject(), this);
if (game) {
m_gameModel->addGame(game);
}
}
}
}
Game *App::createGame(const QString &name, const QString &launchCommand)
{
if (name.isEmpty() || launchCommand.isEmpty()) {
return nullptr;
}
Game *game = new Game(this);
game->setName(name);
game->setLaunchCommand(launchCommand);
game->setPlatform(QStringLiteral("Manual"));
game->setPlatformId(QStringLiteral("manual"));
m_gameModel->addGame(game);
saveLibrary();
return game;
}
void App::removeGame(Game *game)
{
if (!game) {
return;
}
// Store in removed games map for potential undo
m_removedGames[game->id()] = game->toJson();
m_gameModel->removeGame(game->id());
saveLibrary();
}
void App::restoreGame(const QString &gameId)
{
if (!m_removedGames.contains(gameId)) {
return;
}
Game *game = Game::fromJson(m_removedGames[gameId], this);
if (game) {
m_gameModel->addGame(game);
m_removedGames.remove(gameId);
saveLibrary();
}
}
bool App::setCoverFromFile(Game *game, const QString &filePath)
{
if (!game || filePath.isEmpty()) {
return false;
}
QFile sourceFile(filePath);
if (!sourceFile.exists()) {
return false;
}
// Create covers directory
QString coversPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/covers");
QDir dir(coversPath);
if (!dir.exists()) {
dir.mkpath(coversPath);
}
// Determine file extension
QFileInfo fileInfo(filePath);
QString extension = fileInfo.suffix().toLower();
if (extension.isEmpty()) {
extension = QStringLiteral("jpg");
}
QString destFileName = game->id() + QStringLiteral(".") + extension;
QString destPath = coversPath + QStringLiteral("/") + destFileName;
// Remove existing cover if any
QFile destFile(destPath);
if (destFile.exists()) {
destFile.remove();
}
// Copy file
if (!sourceFile.copy(destPath)) {
return false;
}
// Update game cover URL
game->setCoverUrl(QUrl::fromLocalFile(destPath));
saveLibrary();
return true;
}