From 58f69e67178bc7aa24929942f5547d672a9d1db0 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Tue, 20 Jan 2026 00:13:11 +0100 Subject: [PATCH] Fix library persistence and duplicate handling Ensure persistence operates on the full game list, not filtered rows. Deduplicate imported entries by stable game ID to prevent missing games and regressions when importing from multiple sources. --- src/app.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++-- src/gamemodel.cpp | 11 ++-- src/gamemodel.h | 2 + 3 files changed, 162 insertions(+), 9 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index c2f7da6..9ef762f 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -179,6 +179,156 @@ void App::importAllGames() Qt::QueuedConnection); totalCount += heroicGames.count(); + // Import from Desktop entries + QMetaObject::invokeMethod( + this, + [this]() { + setImportStatus(tr("Scanning desktop entries...")); + }, + Qt::QueuedConnection); + + DesktopImporter desktopImporter; + QList desktopGames = desktopImporter.importGames(); + for (Game *game : desktopGames) { + game->moveToThread(this->thread()); + game->setParent(nullptr); + } + + QMetaObject::invokeMethod( + this, + [this, desktopGames]() { + for (Game *game : desktopGames) { + m_gameModel->addGame(game); + } + }, + Qt::QueuedConnection); + totalCount += desktopGames.count(); + + // Import from Bottles + QMetaObject::invokeMethod( + this, + [this]() { + setImportStatus(tr("Scanning Bottles...")); + }, + Qt::QueuedConnection); + + BottlesImporter bottlesImporter; + QList bottlesGames = bottlesImporter.importGames(); + for (Game *game : bottlesGames) { + game->moveToThread(this->thread()); + game->setParent(nullptr); + } + + QMetaObject::invokeMethod( + this, + [this, bottlesGames]() { + for (Game *game : bottlesGames) { + m_gameModel->addGame(game); + } + }, + Qt::QueuedConnection); + totalCount += bottlesGames.count(); + + // Import from Flatpak + QMetaObject::invokeMethod( + this, + [this]() { + setImportStatus(tr("Scanning Flatpak games...")); + }, + Qt::QueuedConnection); + + FlatpakImporter flatpakImporter; + QList flatpakGames = flatpakImporter.importGames(); + for (Game *game : flatpakGames) { + game->moveToThread(this->thread()); + game->setParent(nullptr); + } + + QMetaObject::invokeMethod( + this, + [this, flatpakGames]() { + for (Game *game : flatpakGames) { + m_gameModel->addGame(game); + } + }, + Qt::QueuedConnection); + totalCount += flatpakGames.count(); + + // Import from itch.io + QMetaObject::invokeMethod( + this, + [this]() { + setImportStatus(tr("Scanning itch.io library...")); + }, + Qt::QueuedConnection); + + ItchImporter itchImporter; + QList itchGames = itchImporter.importGames(); + for (Game *game : itchGames) { + game->moveToThread(this->thread()); + game->setParent(nullptr); + } + + QMetaObject::invokeMethod( + this, + [this, itchGames]() { + for (Game *game : itchGames) { + m_gameModel->addGame(game); + } + }, + Qt::QueuedConnection); + totalCount += itchGames.count(); + + // Import from Legendary + QMetaObject::invokeMethod( + this, + [this]() { + setImportStatus(tr("Scanning Legendary library...")); + }, + Qt::QueuedConnection); + + LegendaryImporter legendaryImporter; + QList legendaryGames = legendaryImporter.importGames(); + for (Game *game : legendaryGames) { + game->moveToThread(this->thread()); + game->setParent(nullptr); + } + + QMetaObject::invokeMethod( + this, + [this, legendaryGames]() { + for (Game *game : legendaryGames) { + m_gameModel->addGame(game); + } + }, + Qt::QueuedConnection); + totalCount += legendaryGames.count(); + + // Import from RetroArch + QMetaObject::invokeMethod( + this, + [this]() { + setImportStatus(tr("Scanning RetroArch playlists...")); + }, + Qt::QueuedConnection); + + RetroArchImporter retroArchImporter; + QList retroArchGames = retroArchImporter.importGames(); + for (Game *game : retroArchGames) { + game->moveToThread(this->thread()); + game->setParent(nullptr); + } + + QMetaObject::invokeMethod( + this, + [this, retroArchGames]() { + for (Game *game : retroArchGames) { + m_gameModel->addGame(game); + } + }, + Qt::QueuedConnection); + totalCount += retroArchGames.count(); + // Complete QMetaObject::invokeMethod( this, @@ -490,8 +640,8 @@ void App::removeMissingGames() { QList gamesToRemove; - for (int i = 0; i < m_gameModel->rowCount(); ++i) { - Game *game = m_gameModel->gameAt(i); + const QList games = m_gameModel->allGames(); + for (Game *game : games) { if (!game) continue; @@ -552,8 +702,8 @@ void App::saveLibrary() } QJsonArray gamesArray; - for (int i = 0; i < m_gameModel->rowCount(); ++i) { - Game *game = m_gameModel->gameAt(i); + const QList games = m_gameModel->allGames(); + for (Game *game : games) { if (game) { gamesArray.append(game->toJson()); } diff --git a/src/gamemodel.cpp b/src/gamemodel.cpp index 0d6d6ea..1cb509e 100644 --- a/src/gamemodel.cpp +++ b/src/gamemodel.cpp @@ -201,7 +201,7 @@ void GameModel::addGame(Game *game) // Check for duplicates for (Game *existing : m_games) { - if (existing->platform() == game->platform() && existing->platformId() == game->platformId()) { + if (existing->id() == game->id()) { delete game; return; } @@ -209,12 +209,8 @@ void GameModel::addGame(Game *game) game->setParent(this); - beginInsertRows(QModelIndex(), m_games.count(), m_games.count()); m_games.append(game); - endInsertRows(); - applyFilter(); - Q_EMIT countChanged(); } void GameModel::removeGame(const QString &id) @@ -275,6 +271,11 @@ QStringList GameModel::platforms() const return result; } +QList GameModel::allGames() const +{ + return m_games; +} + bool GameModel::matchesFilter(Game *game) const { if (!m_showHidden && game->hidden()) { diff --git a/src/gamemodel.h b/src/gamemodel.h index 88b09fc..6493350 100644 --- a/src/gamemodel.h +++ b/src/gamemodel.h @@ -84,6 +84,8 @@ public: Q_INVOKABLE void clear(); Q_INVOKABLE QStringList platforms() const; + QList allGames() const; + Q_SIGNALS: void countChanged(); void filterTextChanged();