diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 11b5275e6..c7d8888bf 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -22,6 +22,7 @@ set(cockatrice_SOURCES src/client/tapped_out_interface.cpp src/client/update_downloader.cpp src/database/card_database.cpp + src/database/card_database_loader.cpp src/database/card_database_manager.cpp src/database/model/card_database_model.cpp src/database/model/card_database_display_model.cpp diff --git a/cockatrice/src/database/card_database.cpp b/cockatrice/src/database/card_database.cpp index 13d2ef1a3..8cf990d13 100644 --- a/cockatrice/src/database/card_database.cpp +++ b/cockatrice/src/database/card_database.cpp @@ -22,17 +22,13 @@ CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoa qRegisterMetaType("CardInfoPtr"); qRegisterMetaType("CardSetPtr"); - // add new parsers here - availableParsers << new CockatriceXml4Parser; - availableParsers << new CockatriceXml3Parser; - - for (auto &parser : availableParsers) { - connect(parser, &ICardDatabaseParser::addCard, this, &CardDatabase::addCard, Qt::DirectConnection); - connect(parser, &ICardDatabaseParser::addSet, this, &CardDatabase::addSet, Qt::DirectConnection); - } - - connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this, - &CardDatabase::loadCardDatabases); + // create loader and wire it up + loader = new CardDatabaseLoader(this, this); + // re-emit loader signals (so other code doesn't need to know about internals) + connect(loader, &CardDatabaseLoader::loadingFinished, this, &CardDatabase::cardDatabaseLoadingFinished); + connect(loader, &CardDatabaseLoader::loadingFailed, this, &CardDatabase::cardDatabaseLoadingFailed); + connect(loader, &CardDatabaseLoader::newSetsFound, this, &CardDatabase::cardDatabaseNewSetsFound); + connect(loader, &CardDatabaseLoader::allNewSetsEnabled, this, &CardDatabase::cardDatabaseAllNewSetsEnabled); } CardDatabase::~CardDatabase() @@ -60,90 +56,14 @@ void CardDatabase::clear() loadStatus = NotLoaded; } -LoadStatus CardDatabase::loadFromFile(const QString &fileName) +void CardDatabase::loadCardDatabases() { - QFile file(fileName); - file.open(QIODevice::ReadOnly); - if (!file.isOpen()) { - return FileError; - } - - for (auto parser : availableParsers) { - file.reset(); - if (parser->getCanParseFile(fileName, file)) { - file.reset(); - parser->parseFile(file); - return Ok; - } - } - - return Invalid; + loadStatus = loader->loadCardDatabases(); } -LoadStatus CardDatabase::loadCardDatabase(const QString &path) +bool CardDatabase::saveCustomTokensToFile() { - auto startTime = QTime::currentTime(); - LoadStatus tempLoadStatus = NotLoaded; - if (!path.isEmpty()) { - QMutexLocker locker(loadFromFileMutex); - tempLoadStatus = loadFromFile(path); - } - - int msecs = startTime.msecsTo(QTime::currentTime()); - qCInfo(CardDatabaseLoadingLog) << "Loaded card database: Path =" << path << "Status =" << tempLoadStatus - << "Cards =" << cards.size() << "Sets =" << sets.size() - << QString("%1ms").arg(msecs); - - return tempLoadStatus; -} - -LoadStatus CardDatabase::loadCardDatabases() -{ - QMutexLocker locker(reloadDatabaseMutex); - qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started"; - - clear(); // remove old db - - loadStatus = loadCardDatabase(SettingsCache::instance().getCardDatabasePath()); // load main card database - loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database - loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database - - // find all custom card databases, recursively & following symlinks - // then load them alphabetically - for (int i = 0, n = collectCustomDatabasePaths().size(); i < n; ++i) { - const auto &path = collectCustomDatabasePaths().at(i); - qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << path << ")"; - loadCardDatabase(path); - } - - // AFTER all the cards have been loaded - - // resolve the reverse-related tags - - refreshCachedReverseRelatedCards(); - - if (loadStatus == Ok) { - checkUnknownSets(); // update deck editors, etc - qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success"; - emit cardDatabaseLoadingFinished(); - } else { - qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed"; - emit cardDatabaseLoadingFailed(); // bring up the settings dialog - } - - return loadStatus; -} - -QStringList CardDatabase::collectCustomDatabasePaths() -{ - QDirIterator it(SettingsCache::instance().getCustomCardDatabasePath(), {"*.xml"}, QDir::Files, - QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - - QStringList paths; - while (it.hasNext()) - paths << it.next(); - paths.sort(); - return paths; + return loader->saveCustomTokensToFile(); } void CardDatabase::refreshCachedReverseRelatedCards() @@ -365,8 +285,6 @@ SetList CardDatabase::getSetList() const return result; } - - /** * Finds the PrintingInfo in the cardInfo that has the given uuid field. * @@ -593,22 +511,3 @@ void CardDatabase::notifyEnabledSetsChanged() // inform the carddatabasemodels that they need to re-check their list of cards emit cardDatabaseEnabledSetsChanged(); } - -bool CardDatabase::saveCustomTokensToFile() -{ - QString fileName = SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardSet::TOKENS_SETNAME + ".xml"; - - SetNameMap tmpSets; - CardSetPtr customTokensSet = getSet(CardSet::TOKENS_SETNAME); - tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet); - - CardNameMap tmpCards; - for (const CardInfoPtr &card : cards) { - if (card->getSets().contains(CardSet::TOKENS_SETNAME)) { - tmpCards.insert(card->getName(), card); - } - } - - availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); - return true; -} diff --git a/cockatrice/src/database/card_database.h b/cockatrice/src/database/card_database.h index d3d98d368..eeb9cb845 100644 --- a/cockatrice/src/database/card_database.h +++ b/cockatrice/src/database/card_database.h @@ -3,6 +3,7 @@ #include "../card/exact_card.h" #include "../common/card_ref.h" +#include "card_database_loader.h" #include #include @@ -14,20 +15,6 @@ #include inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database"); -inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); -inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); - -class ICardDatabaseParser; - -enum LoadStatus -{ - Ok, - VersionTooOld, - Invalid, - NotLoaded, - FileError, - NoCards -}; class CardDatabase : public QObject { @@ -48,6 +35,9 @@ protected: */ SetNameMap sets; + // loader responsible for file discovery & parsing + CardDatabaseLoader *loader; + LoadStatus loadStatus; QVector availableParsers; @@ -56,32 +46,33 @@ private: void checkUnknownSets(); void refreshCachedReverseRelatedCards(); - QBasicMutex *reloadDatabaseMutex = new QBasicMutex(), *clearDatabaseMutex = new QBasicMutex(), - *loadFromFileMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(), + QBasicMutex *clearDatabaseMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(), *removeCardMutex = new QBasicMutex(); public: explicit CardDatabase(QObject *parent = nullptr); ~CardDatabase() override; - void clear(); + void removeCard(CardInfoPtr card); + void clear(); [[nodiscard]] CardInfoPtr getCardInfo(const QString &cardName) const; [[nodiscard]] QList getCardInfos(const QStringList &cardNames) const; - QList getCards(const QList &cardRefs) const; [[nodiscard]] ExactCard getCard(const CardRef &cardRef) const; + [[nodiscard]] QList getCards(const QList &cardRefs) const; [[nodiscard]] ExactCard getPreferredCard(const CardInfoPtr &cardInfo) const; - - static PrintingInfo findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId); [[nodiscard]] PrintingInfo getPreferredPrinting(const QString &cardName) const; [[nodiscard]] PrintingInfo getPreferredPrinting(const CardInfoPtr &cardInfo) const; + QString getPreferredPrintingProviderId(const QString &cardName) const; + bool isPreferredPrinting(const CardRef &cardRef) const; + + static PrintingInfo findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId); [[nodiscard]] PrintingInfo getSpecificPrinting(const CardRef &cardRef) const; PrintingInfo getSpecificPrinting(const QString &cardName, const QString &setShortName, const QString &collectorNumber) const; - QString getPreferredPrintingProviderId(const QString &cardName) const; - bool isPreferredPrinting(const CardRef &cardRef) const; + ExactCard getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const; [[nodiscard]] ExactCard guessCard(const CardRef &cardRef) const; @@ -101,8 +92,6 @@ public: } SetList getSetList() const; CardInfoPtr getCardFromMap(const CardNameMap &cardMap, const QString &cardName) const; - LoadStatus loadFromFile(const QString &fileName); - bool saveCustomTokensToFile(); QStringList getAllMainCardTypes() const; QMap getAllMainCardTypesWithCount() const; QMap getAllSubCardTypesWithCount() const; @@ -113,14 +102,12 @@ public: void enableAllUnknownSets(); void markAllSetsAsKnown(); void notifyEnabledSetsChanged(); - static QStringList collectCustomDatabasePaths(); public slots: - LoadStatus loadCardDatabases(); void addCard(CardInfoPtr card); void addSet(CardSetPtr set); -protected slots: - LoadStatus loadCardDatabase(const QString &path); + void loadCardDatabases(); + bool saveCustomTokensToFile(); signals: void cardDatabaseLoadingFinished(); void cardDatabaseLoadingFailed(); @@ -129,6 +116,8 @@ signals: void cardDatabaseEnabledSetsChanged(); void cardAdded(CardInfoPtr card); void cardRemoved(CardInfoPtr card); + + friend class CardDatabaseLoader; }; #endif diff --git a/cockatrice/src/database/card_database_loader.cpp b/cockatrice/src/database/card_database_loader.cpp new file mode 100644 index 000000000..fbb3e6b67 --- /dev/null +++ b/cockatrice/src/database/card_database_loader.cpp @@ -0,0 +1,153 @@ +#include "card_database_loader.h" + +#include "../settings/cache_settings.h" +#include "card_database.h" +#include "parser/cockatrice_xml_3.h" +#include "parser/cockatrice_xml_4.h" + +#include +#include +#include +#include + +CardDatabaseLoader::CardDatabaseLoader(QObject *parent, CardDatabase *db) : QObject(parent), database(db) +{ + // instantiate available parsers here and connect them to the database + availableParsers << new CockatriceXml4Parser; + availableParsers << new CockatriceXml3Parser; + + for (auto *p : availableParsers) { + // connect parser outputs to the database adders + connect(p, &ICardDatabaseParser::addCard, database, &CardDatabase::addCard, Qt::DirectConnection); + connect(p, &ICardDatabaseParser::addSet, database, &CardDatabase::addSet, Qt::DirectConnection); + } + + // when SettingsCache's path changes, trigger reloads + connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this, + &CardDatabaseLoader::loadCardDatabases); +} + +CardDatabaseLoader::~CardDatabaseLoader() +{ + qDeleteAll(availableParsers); + availableParsers.clear(); +} + +LoadStatus CardDatabaseLoader::loadFromFile(const QString &fileName) +{ + QFile file(fileName); + file.open(QIODevice::ReadOnly); + if (!file.isOpen()) { + return FileError; + } + + for (auto parser : availableParsers) { + file.reset(); + if (parser->getCanParseFile(fileName, file)) { + file.reset(); + parser->parseFile(file); + return Ok; + } + } + + return Invalid; +} + +LoadStatus CardDatabaseLoader::loadCardDatabase(const QString &path) +{ + auto startTime = QTime::currentTime(); + LoadStatus tempLoadStatus = NotLoaded; + if (!path.isEmpty()) { + QMutexLocker locker(loadFromFileMutex); + tempLoadStatus = loadFromFile(path); + } + + int msecs = startTime.msecsTo(QTime::currentTime()); + qCInfo(CardDatabaseLoadingLog) << "Loaded card database: Path =" << path << "Status =" << tempLoadStatus + << "Cards =" << (database ? database->cards.size() : 0) + << "Sets =" << (database ? database->sets.size() : 0) << QString("%1ms").arg(msecs); + + return tempLoadStatus; +} + +LoadStatus CardDatabaseLoader::loadCardDatabases() +{ + QMutexLocker locker(reloadDatabaseMutex); + + if (!database) { + qCWarning(CardDatabaseLoadingLog) << "Loader has no database pointer"; + emit loadingFailed(); + return FileError; + } + emit loadingStarted(); + qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started"; + + database->clear(); // remove old db + + LoadStatus loadStatus = + loadCardDatabase(SettingsCache::instance().getCardDatabasePath()); // load main card database + loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database + loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database + + // find all custom card databases, recursively & following symlinks + // then load them alphabetically + const QStringList customPaths = collectCustomDatabasePaths(); + for (int i = 0; i < customPaths.size(); ++i) { + const auto &p = customPaths.at(i); + qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << p << ")"; + loadCardDatabase(p); + } + + // AFTER all the cards have been loaded + + // resolve the reverse-related tags + + database->refreshCachedReverseRelatedCards(); + + if (loadStatus == Ok) { + database->checkUnknownSets(); // update deck editors, etc + qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success"; + emit loadingFinished(); + } else { + qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed"; + emit loadingFailed(); // bring up the settings dialog + } + + return loadStatus; +} + +QStringList CardDatabaseLoader::collectCustomDatabasePaths() const +{ + QDirIterator it(SettingsCache::instance().getCustomCardDatabasePath(), {"*.xml"}, QDir::Files, + QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + + QStringList paths; + while (it.hasNext()) + paths << it.next(); + paths.sort(); + return paths; +} + +bool CardDatabaseLoader::saveCustomTokensToFile() +{ + if (!database) { + qCWarning(CardDatabaseLog) << "saveCustomTokensToFile: database pointer missing"; + return false; + } + + QString fileName = SettingsCache::instance().getCustomCardDatabasePath() + "/" + CardSet::TOKENS_SETNAME + ".xml"; + + SetNameMap tmpSets; + CardSetPtr customTokensSet = database->getSet(CardSet::TOKENS_SETNAME); + tmpSets.insert(CardSet::TOKENS_SETNAME, customTokensSet); + + CardNameMap tmpCards; + for (const CardInfoPtr &card : database->cards) { + if (card->getSets().contains(CardSet::TOKENS_SETNAME)) { + tmpCards.insert(card->getName(), card); + } + } + + availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); + return true; +} \ No newline at end of file diff --git a/cockatrice/src/database/card_database_loader.h b/cockatrice/src/database/card_database_loader.h new file mode 100644 index 000000000..37d3c4d75 --- /dev/null +++ b/cockatrice/src/database/card_database_loader.h @@ -0,0 +1,57 @@ +#ifndef COCKATRICE_CARD_DATABASE_LOADER_H +#define COCKATRICE_CARD_DATABASE_LOADER_H + +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); +inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); + +class CardDatabase; +class ICardDatabaseParser; + +enum LoadStatus +{ + Ok, + VersionTooOld, + Invalid, + NotLoaded, + FileError, + NoCards +}; + +class CardDatabaseLoader : public QObject +{ + Q_OBJECT +public: + explicit CardDatabaseLoader(QObject *parent, CardDatabase *db); + ~CardDatabaseLoader() override; + +public slots: + LoadStatus loadCardDatabases(); // discover & load the configured databases + LoadStatus loadCardDatabase(const QString &path); // load a single file + bool saveCustomTokensToFile(); // write tokens to custom DB path + +signals: + void loadingStarted(); + void loadingFinished(); + void loadingFailed(); + void newSetsFound(int numSets, const QStringList &setNames); + void allNewSetsEnabled(); + +private: + LoadStatus loadFromFile(const QString &fileName); // internal helper + QStringList collectCustomDatabasePaths() const; + + CardDatabase *database; // non-owning pointer to the container + + // parsers + QList availableParsers; + + QBasicMutex *loadFromFileMutex = new QBasicMutex(); + QBasicMutex *reloadDatabaseMutex = new QBasicMutex(); +}; + +#endif // COCKATRICE_CARD_DATABASE_LOADER_H