App: guard importAllGames task during shutdown

This commit is contained in:
Marco Allegretti 2026-02-13 18:30:00 +01:00
parent 058fe8c8e0
commit 4b7655bc6b
2 changed files with 420 additions and 272 deletions

View file

@ -12,14 +12,17 @@
#include "retroarchimporter.h" #include "retroarchimporter.h"
#include "steamimporter.h" #include "steamimporter.h"
#include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonParseError> #include <QJsonParseError>
#include <QPointer>
#include <QSaveFile> #include <QSaveFile>
#include <QStandardPaths> #include <QStandardPaths>
#include <QThread>
#include <QtConcurrent> #include <QtConcurrent>
App *App::s_instance = nullptr; App *App::s_instance = nullptr;
@ -34,6 +37,12 @@ App::App(QObject *parent)
, m_mediaManager(new MediaManager(this)) , m_mediaManager(new MediaManager(this))
, m_config(new Config(this)) , m_config(new Config(this))
{ {
if (QCoreApplication::instance()) {
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
m_shuttingDown.store(true);
});
}
loadLibrary(); loadLibrary();
if (!m_config->importSteam()) { if (!m_config->importSteam()) {
@ -147,6 +156,11 @@ App::App(QObject *parent)
}); });
} }
App::~App()
{
m_shuttingDown.store(true);
}
App *App::instance() App *App::instance()
{ {
if (!s_instance) { if (!s_instance) {
@ -225,7 +239,7 @@ void App::setImportStatus(const QString &status)
void App::importAllGames() void App::importAllGames()
{ {
if (m_importing) if (m_importing || m_shuttingDown.load())
return; return;
const bool anyEnabled = m_config->importSteam() || m_config->importLutris() || m_config->importHeroic() || m_config->importDesktop() const bool anyEnabled = m_config->importSteam() || m_config->importLutris() || m_config->importHeroic() || m_config->importDesktop()
@ -256,335 +270,463 @@ void App::importAllGames()
setImporting(true); setImporting(true);
setImportStatus(tr("Importing games...")); setImportStatus(tr("Importing games..."));
[[maybe_unused]] auto future = QtConcurrent::run([this, doSteam, doLutris, doHeroic, doDesktop, doBottles, doFlatpak, doItch, doLegendary, doRetroArch]() { [[maybe_unused]] auto future =
int totalCount = 0; QtConcurrent::run([self = QPointer<App>(this), doSteam, doLutris, doHeroic, doDesktop, doBottles, doFlatpak, doItch, doLegendary, doRetroArch]() {
if (!self || self->m_shuttingDown.load()) {
// Import from Steam return;
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( QThread *appThread = self->thread();
this, auto shouldAbort = [&self]() {
[this, steamGames]() { return !self || self->m_shuttingDown.load();
if (!m_config->importSteam()) { };
for (Game *game : steamGames) {
if (game) { int totalCount = 0;
game->deleteLater();
} // Import from Steam
if (doSteam) {
QMetaObject::invokeMethod(
self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning Steam library..."));
} }
return; },
} Qt::QueuedConnection);
SteamImporter steamImporter;
QList<Game *> steamGames = steamImporter.importGames();
if (shouldAbort()) {
for (Game *game : steamGames) { for (Game *game : steamGames) {
m_gameModel->addGame(game); delete game;
} }
}, return;
Qt::QueuedConnection); }
totalCount += steamGames.count(); for (Game *game : steamGames) {
} if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
// Import from Lutris QMetaObject::invokeMethod(
if (doLutris) { self.data(),
QMetaObject::invokeMethod( [self, steamGames]() {
this, if (!self || self->m_shuttingDown.load() || !self->m_config->importSteam()) {
[this]() { for (Game *game : steamGames) {
setImportStatus(tr("Scanning Lutris library...")); if (game) {
}, game->deleteLater();
Qt::QueuedConnection); }
}
LutrisImporter lutrisImporter; return;
QList<Game *> lutrisGames = lutrisImporter.importGames(); }
for (Game *game : lutrisGames) { for (Game *game : steamGames) {
game->moveToThread(this->thread()); self->m_gameModel->addGame(game);
game->setParent(nullptr); }
},
Qt::QueuedConnection);
totalCount += steamGames.count();
} }
QMetaObject::invokeMethod( // Import from Lutris
this, if (doLutris) {
[this, lutrisGames]() { QMetaObject::invokeMethod(
if (!m_config->importLutris()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning Lutris library..."));
}
},
Qt::QueuedConnection);
LutrisImporter lutrisImporter;
QList<Game *> lutrisGames = lutrisImporter.importGames();
if (shouldAbort()) {
for (Game *game : lutrisGames) {
delete game;
}
return;
}
for (Game *game : lutrisGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, lutrisGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importLutris()) {
for (Game *game : lutrisGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : lutrisGames) { for (Game *game : lutrisGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : lutrisGames) { totalCount += lutrisGames.count();
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( // Import from Heroic
this, if (doHeroic) {
[this, heroicGames]() { QMetaObject::invokeMethod(
if (!m_config->importHeroic()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning Heroic library..."));
}
},
Qt::QueuedConnection);
HeroicImporter heroicImporter;
QList<Game *> heroicGames = heroicImporter.importGames();
if (shouldAbort()) {
for (Game *game : heroicGames) {
delete game;
}
return;
}
for (Game *game : heroicGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, heroicGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importHeroic()) {
for (Game *game : heroicGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : heroicGames) { for (Game *game : heroicGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : heroicGames) { totalCount += heroicGames.count();
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( // Import from Desktop entries
this, if (doDesktop) {
[this, desktopGames]() { QMetaObject::invokeMethod(
if (!m_config->importDesktop()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning desktop entries..."));
}
},
Qt::QueuedConnection);
DesktopImporter desktopImporter;
QList<Game *> desktopGames = desktopImporter.importGames();
if (shouldAbort()) {
for (Game *game : desktopGames) {
delete game;
}
return;
}
for (Game *game : desktopGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, desktopGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importDesktop()) {
for (Game *game : desktopGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : desktopGames) { for (Game *game : desktopGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : desktopGames) { totalCount += desktopGames.count();
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( // Import from Bottles
this, if (doBottles) {
[this, bottlesGames]() { QMetaObject::invokeMethod(
if (!m_config->importBottles()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning Bottles..."));
}
},
Qt::QueuedConnection);
BottlesImporter bottlesImporter;
QList<Game *> bottlesGames = bottlesImporter.importGames();
if (shouldAbort()) {
for (Game *game : bottlesGames) {
delete game;
}
return;
}
for (Game *game : bottlesGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, bottlesGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importBottles()) {
for (Game *game : bottlesGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : bottlesGames) { for (Game *game : bottlesGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : bottlesGames) { totalCount += bottlesGames.count();
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( // Import from Flatpak
this, if (doFlatpak) {
[this, flatpakGames]() { QMetaObject::invokeMethod(
if (!m_config->importFlatpak()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning Flatpak games..."));
}
},
Qt::QueuedConnection);
FlatpakImporter flatpakImporter;
QList<Game *> flatpakGames = flatpakImporter.importGames();
if (shouldAbort()) {
for (Game *game : flatpakGames) {
delete game;
}
return;
}
for (Game *game : flatpakGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, flatpakGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importFlatpak()) {
for (Game *game : flatpakGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : flatpakGames) { for (Game *game : flatpakGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : flatpakGames) { totalCount += flatpakGames.count();
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( // Import from itch.io
this, if (doItch) {
[this, itchGames]() { QMetaObject::invokeMethod(
if (!m_config->importItch()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning itch.io library..."));
}
},
Qt::QueuedConnection);
ItchImporter itchImporter;
QList<Game *> itchGames = itchImporter.importGames();
if (shouldAbort()) {
for (Game *game : itchGames) {
delete game;
}
return;
}
for (Game *game : itchGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, itchGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importItch()) {
for (Game *game : itchGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : itchGames) { for (Game *game : itchGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : itchGames) { totalCount += itchGames.count();
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( // Import from Legendary
this, if (doLegendary) {
[this, legendaryGames]() { QMetaObject::invokeMethod(
if (!m_config->importLegendary()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning Legendary library..."));
}
},
Qt::QueuedConnection);
LegendaryImporter legendaryImporter;
QList<Game *> legendaryGames = legendaryImporter.importGames();
if (shouldAbort()) {
for (Game *game : legendaryGames) {
delete game;
}
return;
}
for (Game *game : legendaryGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, legendaryGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importLegendary()) {
for (Game *game : legendaryGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : legendaryGames) { for (Game *game : legendaryGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
return; },
} Qt::QueuedConnection);
for (Game *game : legendaryGames) { totalCount += legendaryGames.count();
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( // Import from RetroArch
this, if (doRetroArch) {
[this, retroArchGames]() { QMetaObject::invokeMethod(
if (!m_config->importRetroArch()) { self.data(),
[self]() {
if (self && !self->m_shuttingDown.load()) {
self->setImportStatus(self->tr("Scanning RetroArch playlists..."));
}
},
Qt::QueuedConnection);
RetroArchImporter retroArchImporter;
QList<Game *> retroArchGames = retroArchImporter.importGames();
if (shouldAbort()) {
for (Game *game : retroArchGames) {
delete game;
}
return;
}
for (Game *game : retroArchGames) {
if (game) {
game->moveToThread(appThread);
game->setParent(nullptr);
}
}
QMetaObject::invokeMethod(
self.data(),
[self, retroArchGames]() {
if (!self || self->m_shuttingDown.load() || !self->m_config->importRetroArch()) {
for (Game *game : retroArchGames) {
if (game) {
game->deleteLater();
}
}
return;
}
for (Game *game : retroArchGames) { for (Game *game : retroArchGames) {
if (game) { if (game) {
game->deleteLater(); self->m_gameModel->addGame(game);
} }
} }
},
Qt::QueuedConnection);
totalCount += retroArchGames.count();
}
// Complete
QMetaObject::invokeMethod(
self.data(),
[self, totalCount]() {
if (!self || self->m_shuttingDown.load()) {
return; return;
} }
for (Game *game : retroArchGames) { self->setImportStatus(self->tr("Import complete: %1 games found").arg(totalCount));
m_gameModel->addGame(game); self->setImporting(false);
} self->saveLibrary();
Q_EMIT self->importCompleted(totalCount);
}, },
Qt::QueuedConnection); 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() void App::importFromSteam()

View file

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <atomic>
#include <QHash> #include <QHash>
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
@ -36,6 +38,8 @@ public:
static App *instance(); static App *instance();
static App *create(QQmlEngine *engine, QJSEngine *scriptEngine); static App *create(QQmlEngine *engine, QJSEngine *scriptEngine);
~App() override;
GameModel *gameModel() const; GameModel *gameModel() const;
GameLauncher *launcher() const; GameLauncher *launcher() const;
RunnerManagerClient *runnerManager() const; RunnerManagerClient *runnerManager() const;
@ -90,6 +94,8 @@ private:
QString m_importStatus; QString m_importStatus;
QHash<QString, QJsonObject> m_removedGames; QHash<QString, QJsonObject> m_removedGames;
std::atomic_bool m_shuttingDown{false};
void setImporting(bool importing); void setImporting(bool importing);
void setImportStatus(const QString &status); void setImportStatus(const QString &status);
}; };