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.
This commit is contained in:
Marco Allegretti 2026-01-20 00:13:11 +01:00
parent e468f53c91
commit 58f69e6717
3 changed files with 162 additions and 9 deletions

View file

@ -179,6 +179,156 @@ void App::importAllGames()
Qt::QueuedConnection); Qt::QueuedConnection);
totalCount += heroicGames.count(); totalCount += heroicGames.count();
// Import from Desktop entries
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]() {
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<Game *> 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<Game *> 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<Game *> 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<Game *> 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<Game *> 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 // Complete
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
@ -490,8 +640,8 @@ void App::removeMissingGames()
{ {
QList<Game *> gamesToRemove; QList<Game *> gamesToRemove;
for (int i = 0; i < m_gameModel->rowCount(); ++i) { const QList<Game *> games = m_gameModel->allGames();
Game *game = m_gameModel->gameAt(i); for (Game *game : games) {
if (!game) if (!game)
continue; continue;
@ -552,8 +702,8 @@ void App::saveLibrary()
} }
QJsonArray gamesArray; QJsonArray gamesArray;
for (int i = 0; i < m_gameModel->rowCount(); ++i) { const QList<Game *> games = m_gameModel->allGames();
Game *game = m_gameModel->gameAt(i); for (Game *game : games) {
if (game) { if (game) {
gamesArray.append(game->toJson()); gamesArray.append(game->toJson());
} }

View file

@ -201,7 +201,7 @@ void GameModel::addGame(Game *game)
// Check for duplicates // Check for duplicates
for (Game *existing : m_games) { for (Game *existing : m_games) {
if (existing->platform() == game->platform() && existing->platformId() == game->platformId()) { if (existing->id() == game->id()) {
delete game; delete game;
return; return;
} }
@ -209,12 +209,8 @@ void GameModel::addGame(Game *game)
game->setParent(this); game->setParent(this);
beginInsertRows(QModelIndex(), m_games.count(), m_games.count());
m_games.append(game); m_games.append(game);
endInsertRows();
applyFilter(); applyFilter();
Q_EMIT countChanged();
} }
void GameModel::removeGame(const QString &id) void GameModel::removeGame(const QString &id)
@ -275,6 +271,11 @@ QStringList GameModel::platforms() const
return result; return result;
} }
QList<Game *> GameModel::allGames() const
{
return m_games;
}
bool GameModel::matchesFilter(Game *game) const bool GameModel::matchesFilter(Game *game) const
{ {
if (!m_showHidden && game->hidden()) { if (!m_showHidden && game->hidden()) {

View file

@ -84,6 +84,8 @@ public:
Q_INVOKABLE void clear(); Q_INVOKABLE void clear();
Q_INVOKABLE QStringList platforms() const; Q_INVOKABLE QStringList platforms() const;
QList<Game *> allGames() const;
Q_SIGNALS: Q_SIGNALS:
void countChanged(); void countChanged();
void filterTextChanged(); void filterTextChanged();