More fixes.

Took 6 minutes

Took 26 seconds

Took 8 minutes
This commit is contained in:
Lukas Brübach 2026-07-27 21:04:31 +02:00
parent aebb7f599b
commit a6a164e33e
11 changed files with 80 additions and 34 deletions

View file

@ -264,9 +264,11 @@ 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: checkUnknownSets() is deferred until MainWindow::startupConfigCheck() // Note: checkUnknownSets() is deferred from the initial load to
// so that cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live // MainWindow::startupConfigCheck() so that
// receivers when emitted. // cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled have live
// receivers when emitted. Subsequent reloads (e.g. path changes) call
// checkUnknownSets() directly from the loader after the first load.
CardDatabaseManager::getInstance()->loadCardDatabases(); CardDatabaseManager::getInstance()->loadCardDatabases();
MainWindow ui; MainWindow ui;

View file

@ -57,11 +57,22 @@ const QVariantHash &CardInfo::getPropertiesHash() const
void CardInfo::setProperty(const QString &_name, const QString &_value) void CardInfo::setProperty(const QString &_name, const QString &_value)
{ {
ensurePropertiesLoaded(); ensurePropertiesLoaded();
if (propertiesCache.value(_name).toString() == _value) {
return;
}
propertiesCache.insert(_name, _value); propertiesCache.insert(_name, _value);
propertiesBlob = serializeProperties(propertiesCache); propertiesBlob = serializeProperties(propertiesCache);
emit cardInfoChanged(smartThis); emit cardInfoChanged(smartThis);
} }
void CardInfo::setProperties(const QVariantHash &_props)
{
ensurePropertiesLoaded();
propertiesCache = _props;
propertiesBlob = serializeProperties(propertiesCache);
emit cardInfoChanged(smartThis);
}
CardInfo::CardInfo(const QString &_name, CardInfo::CardInfo(const QString &_name,
const QString &_text, const QString &_text,
bool _isToken, bool _isToken,
@ -201,13 +212,16 @@ void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info)
void CardInfo::combineLegalities(const QVariantHash &props) void CardInfo::combineLegalities(const QVariantHash &props)
{ {
ensurePropertiesLoaded();
QHashIterator<QString, QVariant> it(props); QHashIterator<QString, QVariant> it(props);
while (it.hasNext()) { while (it.hasNext()) {
it.next(); it.next();
if (it.key().startsWith("format-")) { if (it.key().startsWith("format-")) {
smartThis->setProperty(it.key(), it.value().toString()); propertiesCache.insert(it.key(), it.value());
} }
} }
propertiesBlob = serializeProperties(propertiesCache);
emit cardInfoChanged(smartThis);
} }
void CardInfo::refreshCachedSets() void CardInfo::refreshCachedSets()

View file

@ -308,6 +308,7 @@ public:
return getPropertiesHash().value(propertyName).toString(); return getPropertiesHash().value(propertyName).toString();
} }
void setProperty(const QString &_name, const QString &_value); void setProperty(const QString &_name, const QString &_value);
void setProperties(const QVariantHash &_props);
[[nodiscard]] bool hasProperty(const QString &propertyName) const [[nodiscard]] bool hasProperty(const QString &propertyName) const
{ {
return getPropertiesHash().contains(propertyName); return getPropertiesHash().contains(propertyName);

View file

@ -99,7 +99,9 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases()
LoadStatus loadStatus = NotLoaded; LoadStatus loadStatus = NotLoaded;
// Try the binary cache first: a cache hit avoids re-parsing the (large) XML. // Try the binary cache first: a cache hit avoids re-parsing the (large) XML.
if (loadFromCache(data)) { const QStringList customPaths = collectCustomDatabasePaths();
const QByteArray sourceHash = computeSourceHash(customPaths);
if (loadFromCache(data, sourceHash)) {
qCInfo(CardDatabaseLoadingLog) << "Loaded card database from binary cache"; qCInfo(CardDatabaseLoadingLog) << "Loaded card database from binary cache";
loadStatus = Ok; loadStatus = Ok;
} else { } else {
@ -110,30 +112,36 @@ LoadStatus CardDatabaseLoader::doLoadCardDatabases()
// find all custom card databases, recursively & following symlinks // find all custom card databases, recursively & following symlinks
// then load them alphabetically // then load them alphabetically
const QStringList customPaths = collectCustomDatabasePaths();
for (int i = 0; i < customPaths.size(); ++i) { for (int i = 0; i < customPaths.size(); ++i) {
const auto &p = customPaths.at(i); const auto &p = customPaths.at(i);
qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")"; qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")";
loadCardDatabase(p, data); loadCardDatabase(p, data);
} }
saveToCache(data); if (!saveToCache(data, sourceHash)) {
qCWarning(CardDatabaseLoadingLog) << "Failed to write binary cache to" << cachePath();
}
} }
} }
// 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. // against the fully-built snapshot.
database->refreshCachedReverseRelatedCards(data.cards);
if (loadStatus == Ok) { if (loadStatus == Ok) {
database->refreshCachedReverseRelatedCards(data.cards);
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success"; qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success";
emit databaseDataReady(std::move(data)); emit databaseDataReady(std::move(data));
emit loadingFinished(); emit loadingFinished();
// NOTE: checkUnknownSets() is intentionally NOT called here. During // During the front-loaded parse in main() this runs before MainWindow
// the front-loaded parse in main() this runs before MainWindow exists, // exists, so cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled
// so cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would be // would be emitted with no receivers. Skip the check on the first load;
// emitted with no receivers. MainWindow::startupConfigCheck() calls // MainWindow::startupConfigCheck() calls checkUnknownSets() once its
// checkUnknownSets() once its signal connections are live. // signal connections are live. On subsequent reloads (e.g. path change)
// MainWindow exists and signals have live receivers.
if (initialLoadComplete) {
database->checkUnknownSets();
}
initialLoadComplete = true;
} else { } else {
qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed"; qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed";
emit loadingFailed(); // bring up the settings dialog emit loadingFailed(); // bring up the settings dialog
@ -147,7 +155,7 @@ QString CardDatabaseLoader::cachePath() const
return pathProvider->getCardDatabasePath() + ".cache"; return pathProvider->getCardDatabasePath() + ".cache";
} }
QByteArray CardDatabaseLoader::computeSourceHash() const QByteArray CardDatabaseLoader::computeSourceHash(const QStringList &customPaths) 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).
@ -160,7 +168,7 @@ QByteArray CardDatabaseLoader::computeSourceHash() const
const QStringList inputs = QStringList() const QStringList inputs = QStringList()
<< pathProvider->getCardDatabasePath() << pathProvider->getTokenDatabasePath() << pathProvider->getCardDatabasePath() << pathProvider->getTokenDatabasePath()
<< pathProvider->getSpoilerCardDatabasePath() << collectCustomDatabasePaths(); << pathProvider->getSpoilerCardDatabasePath() << customPaths;
for (const QString &path : inputs) { for (const QString &path : inputs) {
QFileInfo info(path); QFileInfo info(path);
if (info.exists()) { if (info.exists()) {
@ -175,22 +183,20 @@ QByteArray CardDatabaseLoader::computeSourceHash() const
return hash.result(); return hash.result();
} }
bool CardDatabaseLoader::loadFromCache(CardDatabaseData &data) bool CardDatabaseLoader::loadFromCache(CardDatabaseData &data, const QByteArray &sourceHash)
{ {
const QByteArray sourceHash = computeSourceHash();
if (sourceHash.isEmpty()) { if (sourceHash.isEmpty()) {
return false; return false;
} }
return CardDatabaseCache::read(cachePath(), data, sourceHash, priorityController); return CardDatabaseCache::read(cachePath(), data, sourceHash, priorityController);
} }
void CardDatabaseLoader::saveToCache(const CardDatabaseData &data) bool CardDatabaseLoader::saveToCache(const CardDatabaseData &data, const QByteArray &sourceHash)
{ {
const QByteArray sourceHash = computeSourceHash();
if (sourceHash.isEmpty()) { if (sourceHash.isEmpty()) {
return; return false;
} }
CardDatabaseCache::write(cachePath(), data, sourceHash); return CardDatabaseCache::write(cachePath(), data, sourceHash);
} }
QStringList CardDatabaseLoader::collectCustomDatabasePaths() const QStringList CardDatabaseLoader::collectCustomDatabasePaths() const

View file

@ -135,7 +135,7 @@ private:
* @brief Computes a hash identifying the current set of input database files. * @brief Computes a hash identifying the current set of input database files.
* @return Hash over the paths, sizes and modification times of all inputs. * @return Hash over the paths, sizes and modification times of all inputs.
*/ */
[[nodiscard]] QByteArray computeSourceHash() const; [[nodiscard]] QByteArray computeSourceHash(const QStringList &customPaths) const;
/** /**
* @brief Path of the binary cache file derived from the main card database path. * @brief Path of the binary cache file derived from the main card database path.
@ -146,15 +146,18 @@ private:
/** /**
* @brief Attempts to populate the snapshot from the binary cache. * @brief Attempts to populate the snapshot from the binary cache.
* @param data Snapshot to populate. * @param data Snapshot to populate.
* @param sourceHash Pre-computed hash identifying the current input files.
* @return True if a valid, up-to-date cache was read. * @return True if a valid, up-to-date cache was read.
*/ */
bool loadFromCache(CardDatabaseData &data); bool loadFromCache(CardDatabaseData &data, const QByteArray &sourceHash);
/** /**
* @brief Writes the snapshot to the binary cache for the current inputs. * @brief Writes the snapshot to the binary cache for the current inputs.
* @param data Snapshot to serialize. * @param data Snapshot to serialize.
* @param sourceHash Pre-computed hash identifying the current input files.
* @return True if the cache was written successfully.
*/ */
void saveToCache(const CardDatabaseData &data); bool saveToCache(const CardDatabaseData &data, const QByteArray &sourceHash);
private: private:
CardDatabase *database; /**< Non-owning pointer to the target CardDatabase. */ CardDatabase *database; /**< Non-owning pointer to the target CardDatabase. */
@ -164,6 +167,7 @@ private:
QBasicMutex *loadFromFileMutex = new QBasicMutex(); /**< Mutex for single-file loading. */ QBasicMutex *loadFromFileMutex = new QBasicMutex(); /**< Mutex for single-file loading. */
QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); /**< Mutex for reloading entire database. */ QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); /**< Mutex for reloading entire database. */
bool initialLoadComplete = false; /**< Set after the first successful load. */
}; };
#endif // COCKATRICE_CARD_DATABASE_LOADER_H #endif // COCKATRICE_CARD_DATABASE_LOADER_H

View file

@ -229,25 +229,27 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
// behaviour. Without this check, disabling a set has no effect on v3 databases. // behaviour. Without this check, disabling a set has no effect on v3 databases.
if (set->getEnabled()) { if (set->getEnabled()) {
PrintingInfo setInfo(set); PrintingInfo setInfo(set);
QVariantHash printingProps;
if (attrs.hasAttribute("muId")) { if (attrs.hasAttribute("muId")) {
setInfo.setProperty("muid", attrs.value("muId").toString()); printingProps.insert("muid", attrs.value("muId").toString());
} }
if (attrs.hasAttribute("uuId")) { if (attrs.hasAttribute("uuId")) {
setInfo.setProperty("uuid", attrs.value("uuId").toString()); printingProps.insert("uuid", attrs.value("uuId").toString());
} }
if (attrs.hasAttribute("picURL")) { if (attrs.hasAttribute("picURL")) {
setInfo.setProperty("picurl", attrs.value("picURL").toString()); printingProps.insert("picurl", attrs.value("picURL").toString());
} }
if (attrs.hasAttribute("num")) { if (attrs.hasAttribute("num")) {
setInfo.setProperty("num", attrs.value("num").toString()); printingProps.insert("num", attrs.value("num").toString());
} }
if (attrs.hasAttribute("rarity")) { if (attrs.hasAttribute("rarity")) {
setInfo.setProperty("rarity", attrs.value("rarity").toString()); printingProps.insert("rarity", attrs.value("rarity").toString());
} }
setInfo.setProperties(printingProps);
_sets[setName].append(setInfo); _sets[setName].append(setInfo);
} }
// related cards // related cards

View file

@ -315,13 +315,15 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
auto set = internalAddSet(setName); auto set = internalAddSet(setName);
if (set->getEnabled()) { if (set->getEnabled()) {
PrintingInfo printingInfo(set); PrintingInfo printingInfo(set);
QVariantHash printingProps;
for (QXmlStreamAttribute attr : attrs) { for (QXmlStreamAttribute attr : attrs) {
QString attrName = attr.name().toString(); QString attrName = attr.name().toString();
if (attrName == "picURL") { if (attrName == "picURL") {
attrName = "picurl"; attrName = "picurl";
} }
printingInfo.setProperty(attrName, attr.value().toString()); printingProps.insert(attrName, attr.value().toString());
} }
printingInfo.setProperties(printingProps);
// This is very much a hack and not the right place to // This is very much a hack and not the right place to
// put this check, as it requires a reload of Cockatrice // put this check, as it requires a reload of Cockatrice

View file

@ -27,7 +27,17 @@ void PrintingInfo::ensurePropertiesLoaded() const
void PrintingInfo::setProperty(const QString &_name, const QString &_value) void PrintingInfo::setProperty(const QString &_name, const QString &_value)
{ {
ensurePropertiesLoaded(); ensurePropertiesLoaded();
if (propertiesCache.value(_name).toString() == _value) {
return;
}
propertiesCache.insert(_name, _value); propertiesCache.insert(_name, _value);
setProperties(propertiesCache);
}
void PrintingInfo::setProperties(const QVariantHash &_props)
{
ensurePropertiesLoaded();
propertiesCache = _props;
QDataStream out(&propertiesBlob, QIODevice::WriteOnly); QDataStream out(&propertiesBlob, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_6_4); out.setVersion(QDataStream::Qt_6_4);
out << propertiesCache; out << propertiesCache;

View file

@ -127,6 +127,7 @@ public:
* @param _value The string value to assign. * @param _value The string value to assign.
*/ */
void setProperty(const QString &_name, const QString &_value); void setProperty(const QString &_name, const QString &_value);
void setProperties(const QVariantHash &_props);
/** /**
* @brief Stores the pre-serialized properties blob and marks the materialized * @brief Stores the pre-serialized properties blob and marks the materialized

View file

@ -92,6 +92,7 @@ void CardDatabaseSettings::saveSets(const QVector<ICardSetPriorityController::Se
}); });
QMutexLocker lock(&setOptionsMutex); QMutexLocker lock(&setOptionsMutex);
ensureSetOptionsLoaded();
for (const auto &entry : data) { for (const auto &entry : data) {
SetOptions &o = setOptionsCache[entry.shortName]; SetOptions &o = setOptionsCache[entry.shortName];
o.sortKey = entry.sortKey; o.sortKey = entry.sortKey;

View file

@ -291,12 +291,13 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
// per-set properties // per-set properties
PrintingInfo printingInfo = PrintingInfo(currentSet); PrintingInfo printingInfo = PrintingInfo(currentSet);
QVariantHash printingProps;
for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) { for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) {
QString mtgjsonProperty = i.key(); QString mtgjsonProperty = i.key();
QString xmlPropertyName = i.value(); QString xmlPropertyName = i.value();
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty); QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
if (!propertyValue.isEmpty()) { if (!propertyValue.isEmpty()) {
printingInfo.setProperty(xmlPropertyName, propertyValue); printingProps.insert(xmlPropertyName, propertyValue);
} }
} }
@ -304,7 +305,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName"); QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName");
QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName"); QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName");
if (!flavorName.isEmpty()) { if (!flavorName.isEmpty()) {
printingInfo.setProperty("flavorName", flavorName); printingProps.insert("flavorName", flavorName);
} }
// Identifiers // Identifiers
@ -313,10 +314,12 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
QString xmlPropertyName = i.value(); QString xmlPropertyName = i.value();
QString propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty); QString propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty);
if (!propertyValue.isEmpty()) { if (!propertyValue.isEmpty()) {
printingInfo.setProperty(xmlPropertyName, propertyValue); printingProps.insert(xmlPropertyName, propertyValue);
} }
} }
printingInfo.setProperties(printingProps);
QString numComponent; QString numComponent;
const QString numProperty = printingInfo.getProperty("num"); const QString numProperty = printingInfo.getProperty("num");
const QChar lastChar = numProperty.isEmpty() ? QChar() : numProperty.back(); const QChar lastChar = numProperty.isEmpty() ? QChar() : numProperty.back();