Address comments

This commit is contained in:
Lukas Brübach 2026-07-23 04:17:45 +02:00
parent a508d767bc
commit ec80acb7ab
8 changed files with 41 additions and 17 deletions

View file

@ -541,6 +541,10 @@ MainWindow::MainWindow(QWidget *parent)
void MainWindow::startupConfigCheck() void MainWindow::startupConfigCheck()
{ {
// Signals for unknown/failed DB were emitted during the front-loaded parse
// before MainWindow existed. Drive the UX now that receivers are connected.
CardDatabaseManager::getInstance()->checkUnknownSets();
if (SettingsCache::instance().debug().getLocalGameOnStartup()) { if (SettingsCache::instance().debug().getLocalGameOnStartup()) {
LocalGameOptions options; LocalGameOptions options;
options.numberPlayers = SettingsCache::instance().debug().getLocalGamePlayerCount(); options.numberPlayers = SettingsCache::instance().debug().getLocalGamePlayerCount();

View file

@ -264,6 +264,8 @@ int main(int argc, char *argv[])
// contention that happens when the load runs alongside window construction. // contention that happens when the load runs alongside window construction.
// The CardDatabaseModel populates from the already-loaded data in its // The CardDatabaseModel populates from the already-loaded data in its
// constructor, so the window appears fully populated with no startup lag. // constructor, so the window appears fully populated with no startup lag.
// Note: unknown-set / failure signals are deferred; MainWindow triggers
// checkUnknownSets() in startupConfigCheck() once receivers are connected.
CardDatabaseManager::getInstance()->loadCardDatabases(); CardDatabaseManager::getInstance()->loadCardDatabases();
MainWindow ui; MainWindow ui;

View file

@ -248,9 +248,6 @@ void CardDatabase::swapInDatabaseData(CardDatabaseData data)
loadStatus = cards.isEmpty() ? NotLoaded : Ok; loadStatus = cards.isEmpty() ? NotLoaded : Ok;
// Detect newly-encountered sets now that the live data is populated.
checkUnknownSets();
// inform listeners that the whole database was replaced; they should // inform listeners that the whole database was replaced; they should
// rebuild from the live containers in a single batch instead of reacting // rebuild from the live containers in a single batch instead of reacting
// to individual card additions. // to individual card additions.

View file

@ -54,12 +54,13 @@ protected:
/** @brief Querier for higher-level card lookups. */ /** @brief Querier for higher-level card lookups. */
CardDatabaseQuerier *querier; CardDatabaseQuerier *querier;
private: public:
/** /**
* @brief Check for sets that are unknown and emit signals if needed. * @brief Check for sets that are unknown and emit signals if needed.
*/ */
void checkUnknownSets(); void checkUnknownSets();
private:
/** /**
* @brief Refreshes the cached reverse-related cards for all cards. * @brief Refreshes the cached reverse-related cards for all cards.
*/ */

View file

@ -6,11 +6,13 @@
#include "../relation/card_relation.h" #include "../relation/card_relation.h"
#include "../relation/card_relation_type.h" #include "../relation/card_relation_type.h"
#include "../set/card_set.h" #include "../set/card_set.h"
#include "card_database_loader.h"
#include <QBuffer> #include <QBuffer>
#include <QDataStream> #include <QDataStream>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QFile> #include <QFile>
#include <QSaveFile>
#include <QVariantHash> #include <QVariantHash>
namespace namespace
@ -50,6 +52,9 @@ QByteArray readHashBlob(QDataStream &in)
{ {
quint32 len = 0; quint32 len = 0;
in >> len; in >> len;
if (in.status() != QDataStream::Ok || static_cast<qint64>(len) > in.device()->bytesAvailable()) {
return {};
}
QByteArray blob(len, Qt::Uninitialized); QByteArray blob(len, Qt::Uninitialized);
in.readRawData(blob.data(), static_cast<int>(len)); in.readRawData(blob.data(), static_cast<int>(len));
return blob; return blob;
@ -72,7 +77,7 @@ QDate readDate(QDataStream &in)
void writeRelation(QDataStream &out, const CardRelation *rel) void writeRelation(QDataStream &out, const CardRelation *rel)
{ {
writeString(out, rel->getName()); writeString(out, rel->getName());
out << static_cast<quint32>(static_cast<quint32>(rel->getAttachType())); out << static_cast<quint32>(rel->getAttachType());
out << rel->getIsCreateAllExclusion(); out << rel->getIsCreateAllExclusion();
out << rel->getIsVariable(); out << rel->getIsVariable();
out << rel->getDefaultCount(); out << rel->getDefaultCount();
@ -323,7 +328,7 @@ FormatRulesPtr readFormat(QDataStream &in)
bool CardDatabaseCache::write(const QString &cachePath, const CardDatabaseData &data, const QByteArray &sourceHash) bool CardDatabaseCache::write(const QString &cachePath, const CardDatabaseData &data, const QByteArray &sourceHash)
{ {
QFile file(cachePath); QSaveFile file(cachePath);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
return false; return false;
} }
@ -355,8 +360,7 @@ bool CardDatabaseCache::write(const QString &cachePath, const CardDatabaseData &
writeFormat(out, format); writeFormat(out, format);
} }
file.close(); return file.commit();
return file.error() == QFile::NoError;
} }
bool CardDatabaseCache::read(const QString &cachePath, bool CardDatabaseCache::read(const QString &cachePath,
@ -371,12 +375,12 @@ bool CardDatabaseCache::read(const QString &cachePath,
// Read the whole cache into memory up front; deserialization then works on an // Read the whole cache into memory up front; deserialization then works on an
// in-memory buffer with no further disk I/O. // in-memory buffer with no further disk I/O.
const QByteArray raw = file.readAll(); QByteArray raw = file.readAll();
if (raw.isEmpty()) { if (raw.isEmpty()) {
return false; return false;
} }
QBuffer buffer(const_cast<QByteArray *>(&raw)); QBuffer buffer(&raw);
buffer.open(QIODevice::ReadOnly); buffer.open(QIODevice::ReadOnly);
QDataStream in(&buffer); QDataStream in(&buffer);
in.setVersion(QDataStream::Qt_6_4); in.setVersion(QDataStream::Qt_6_4);
@ -416,6 +420,7 @@ bool CardDatabaseCache::read(const QString &cachePath,
for (const PrintingInfo &printing : printings) { for (const PrintingInfo &printing : printings) {
if (auto set = printing.getSet()) { if (auto set = printing.getSet()) {
set->append(card); set->append(card);
break;
} }
} }
} }
@ -430,7 +435,12 @@ bool CardDatabaseCache::read(const QString &cachePath,
data.formats.insert(format->formatName.toLower(), format); data.formats.insert(format->formatName.toLower(), format);
} }
qInfo() << "[cache] read + deserialize" << deserializeTimer.elapsed() << "ms for" << cardCount << "cards"; qCInfo(CardDatabaseLoadingLog) << "[cache] read + deserialize" << deserializeTimer.elapsed() << "ms for"
<< cardCount << "cards";
if (in.status() != QDataStream::Ok) {
return false;
}
return file.error() == QFile::NoError; return file.error() == QFile::NoError;
} }

View file

@ -6,6 +6,7 @@
#include "parser/cockatrice_xml_4.h" #include "parser/cockatrice_xml_4.h"
#include <QByteArray> #include <QByteArray>
#include <QCoreApplication>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QDebug> #include <QDebug>
#include <QDirIterator> #include <QDirIterator>
@ -118,7 +119,7 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases()
} }
// AFTER all the cards have been loaded: resolve the reverse-related tags // AFTER all the cards have been loaded: resolve the reverse-related tags
// against the fully-built snapshot (off the GUI thread). // against the fully-built snapshot.
database->refreshCachedReverseRelatedCards(data.cards); database->refreshCachedReverseRelatedCards(data.cards);
if (loadStatus == Ok) { if (loadStatus == Ok) {
@ -143,6 +144,12 @@ QByteArray CardDatabaseLoader::computeSourceHash() const
// Hash over the paths, sizes and modification times of every input file so // Hash over the paths, sizes and modification times of every input file so
// the cache invalidates when any source changes. Cheap (no content read). // the cache invalidates when any source changes. Cheap (no content read).
QCryptographicHash hash(QCryptographicHash::Sha256); QCryptographicHash hash(QCryptographicHash::Sha256);
// Include the application version so parser changes automatically invalidate
// old caches even when the XML files are byte-identical.
hash.addData(QCoreApplication::applicationVersion().toUtf8());
hash.addData(QByteArray(1, '\0'));
const QStringList inputs = QStringList() const QStringList inputs = QStringList()
<< pathProvider->getCardDatabasePath() << pathProvider->getTokenDatabasePath() << pathProvider->getCardDatabasePath() << pathProvider->getTokenDatabasePath()
<< pathProvider->getSpoilerCardDatabasePath() << collectCustomDatabasePaths(); << pathProvider->getSpoilerCardDatabasePath() << collectCustomDatabasePaths();
@ -150,8 +157,11 @@ QByteArray CardDatabaseLoader::computeSourceHash() const
QFileInfo info(path); QFileInfo info(path);
if (info.exists()) { if (info.exists()) {
hash.addData(path.toUtf8()); hash.addData(path.toUtf8());
hash.addData(QByteArray(1, '\0'));
hash.addData(QByteArray::number(info.size())); hash.addData(QByteArray::number(info.size()));
hash.addData(QByteArray(1, '\0'));
hash.addData(QByteArray::number(info.lastModified().toSecsSinceEpoch())); hash.addData(QByteArray::number(info.lastModified().toSecsSinceEpoch()));
hash.addData(QByteArray(1, '\0'));
} }
} }
return hash.result(); return hash.result();

View file

@ -64,8 +64,8 @@ public slots:
/** /**
* @brief Loads all configured card databases. * @brief Loads all configured card databases.
* *
* The heavy work runs on a dedicated high-priority thread so it is not * Runs synchronously on the calling thread. The caller should ensure
* starved by the GUI thread's startup work on low-core machines. * that any signal receivers are already connected before invoking this.
* @return Status of the main database load. * @return Status of the main database load.
*/ */
LoadStatus loadCardDatabases(); LoadStatus loadCardDatabases();
@ -120,7 +120,7 @@ private:
LoadStatus loadFromFile(const QString &fileName, CardDatabaseData &data); LoadStatus loadFromFile(const QString &fileName, CardDatabaseData &data);
/** /**
* @brief Performs the actual load work on the dedicated loader thread. * @brief Performs the actual load work synchronously on the calling thread.
* @return Status of the main database load. * @return Status of the main database load.
*/ */
LoadStatus doLoadCardDatabases(); LoadStatus doLoadCardDatabases();

View file

@ -25,8 +25,8 @@ public:
struct SetOptions struct SetOptions
{ {
unsigned int sortKey = 0; unsigned int sortKey = 0;
bool enabled = true; bool enabled = false;
bool isKnown = true; bool isKnown = false;
}; };
virtual ~ICardSetPriorityController() = default; virtual ~ICardSetPriorityController() = default;