servatrice/cockatrice/src/carddatabase.cpp

1100 lines
33 KiB
C++

#include "carddatabase.h"
#include "settingscache.h"
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QSettings>
#include <QSvgRenderer>
#include <QPainter>
#include <QUrl>
#include <QSet>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QDebug>
#include <QImageReader>
const int CardDatabase::versionNeeded = 3;
const char* CardDatabase::TOKENS_SETNAME = "TK";
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSet *set)
{
xml.writeStartElement("set");
xml.writeTextElement("name", set->getShortName());
xml.writeTextElement("longname", set->getLongName());
xml.writeTextElement("settype", set->getSetType());
xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate));
xml.writeEndElement();
return xml;
}
CardSet::CardSet(const QString &_shortName, const QString &_longName, const QString &_setType, const QDate &_releaseDate)
: shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType)
{
loadSetOptions();
}
QString CardSet::getCorrectedShortName() const
{
// Because windows is horrible.
QSet<QString> invalidFileNames;
invalidFileNames << "CON" << "PRN" << "AUX" << "NUL" << "COM1" << "COM2" <<
"COM3" << "COM4" << "COM5" << "COM6" << "COM7" << "COM8" << "COM9" <<
"LPT1" << "LPT2" << "LPT3" << "LPT4" << "LPT5" << "LPT6" << "LPT7" <<
"LPT8" << "LPT9";
return invalidFileNames.contains(shortName) ? shortName + "_" : shortName;
}
void CardSet::setSortKey(unsigned int _sortKey)
{
sortKey = _sortKey;
QSettings settings;
settings.beginGroup("sets");
settings.beginGroup(shortName);
settings.setValue("sortkey", sortKey);
}
void CardSet::loadSetOptions()
{
QSettings settings;
settings.beginGroup("sets");
settings.beginGroup(shortName);
sortKey = settings.value("sortkey", 0).toInt();
enabled = settings.value("enabled", false).toBool();
isknown = settings.value("isknown", false).toBool();
// qDebug() << "load set" << shortName << "key" << sortKey;
}
void CardSet::setEnabled(bool _enabled)
{
enabled = _enabled;
QSettings settings;
settings.beginGroup("sets");
settings.beginGroup(shortName);
settings.setValue("enabled", enabled);
}
void CardSet::setIsKnown(bool _isknown)
{
isknown = _isknown;
QSettings settings;
settings.beginGroup("sets");
settings.beginGroup(shortName);
settings.setValue("isknown", isknown);
}
class SetList::CompareFunctor {
public:
inline bool operator()(CardSet *a, CardSet *b) const
{
return a->getSortKey() < b->getSortKey();
}
};
void SetList::sortByKey()
{
qSort(begin(), end(), CompareFunctor());
}
int SetList::getEnabledSetsNum()
{
int num=0;
for (int i = 0; i < size(); ++i)
{
CardSet *set = at(i);
if(set->getEnabled())
++num;
}
return num;
}
int SetList::getUnknownSetsNum()
{
int num=0;
for (int i = 0; i < size(); ++i)
{
CardSet *set = at(i);
if(!set->getIsKnown())
++num;
}
return num;
}
void SetList::enableAllUnknown()
{
for (int i = 0; i < size(); ++i)
{
CardSet *set = at(i);
if(!set->getIsKnown())
{
set->setIsKnown(true);
set->setEnabled(true);
}
}
}
void SetList::enableAll()
{
for (int i = 0; i < size(); ++i)
{
CardSet *set = at(i);
set->setIsKnown(true);
set->setEnabled(true);
}
}
void SetList::markAllAsKnown()
{
for (int i = 0; i < size(); ++i)
{
CardSet *set = at(i);
if(!set->getIsKnown())
{
set->setIsKnown(true);
}
}
}
void SetList::guessSortKeys()
{
// sort by release date DESC; invalid dates to the bottom.
QDate distantFuture(2050, 1, 1);
int aHundredYears = 36500;
for (int i = 0; i < size(); ++i)
{
CardSet *set = at(i);
QDate date = set->getReleaseDate();
if(date.isNull())
set->setSortKey(aHundredYears);
else
set->setSortKey(date.daysTo(distantFuture));
}
}
PictureToLoad::PictureToLoad(CardInfo *_card, bool _hq)
: card(_card), setIndex(0), hq(_hq)
{
if (card) {
sortedSets = card->getSets();
sortedSets.sortByKey();
}
}
bool PictureToLoad::nextSet()
{
if (setIndex == sortedSets.size() - 1)
return false;
++setIndex;
return true;
}
QString PictureToLoad::getSetName() const
{
if (setIndex < sortedSets.size())
return sortedSets[setIndex]->getCorrectedShortName();
else
return QString("");
}
CardSet *PictureToLoad::getCurrentSet() const
{
if (setIndex < sortedSets.size())
return sortedSets[setIndex];
else
return 0;
}
PictureLoader::PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent)
: QObject(parent),
_picsPath(__picsPath), picDownload(_picDownload), picDownloadHq(_picDownloadHq),
downloadRunning(false), loadQueueRunning(false)
{
connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
networkManager = new QNetworkAccessManager(this);
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
}
PictureLoader::~PictureLoader()
{
// This does not work with the destroyed() signal as this destructor is called after the main event loop is done.
thread()->quit();
}
void PictureLoader::processLoadQueue()
{
if (loadQueueRunning)
return;
loadQueueRunning = true;
forever {
mutex.lock();
if (loadQueue.isEmpty()) {
mutex.unlock();
loadQueueRunning = false;
return;
}
cardBeingLoaded = loadQueue.takeFirst();
mutex.unlock();
QString setName = cardBeingLoaded.getSetName();
QString correctedCardname = cardBeingLoaded.getCard()->getCorrectedName();
qDebug() << "Trying to load picture (set: " << setName << " card: " << correctedCardname << ")";
//The list of paths to the folders in which to search for images
QList<QString> picsPaths = QList<QString>() << _picsPath + "/CUSTOM/" + correctedCardname;
if(!setName.isEmpty())
{
picsPaths << _picsPath + "/" + setName + "/" + correctedCardname
<< _picsPath + "/downloadedPics/" + setName + "/" + correctedCardname;
}
QImage image;
QImageReader imgReader;
imgReader.setDecideFormatFromContent(true);
bool found = false;
//Iterates through the list of paths, searching for images with the desired name with any QImageReader-supported extension
for (int i = 0; i < picsPaths.length() && !found; i ++) {
imgReader.setFileName(picsPaths.at(i));
if (imgReader.read(&image)) {
qDebug() << "Picture found on disk (set: " << setName << " card: " << correctedCardname << ")";
emit imageLoaded(cardBeingLoaded.getCard(), image);
found = true;
break;
}
imgReader.setFileName(picsPaths.at(i) + ".full");
if (imgReader.read(&image)) {
qDebug() << "Picture.full found on disk (set: " << setName << " card: " << correctedCardname << ")";
emit imageLoaded(cardBeingLoaded.getCard(), image);
found = true;
}
}
if (!found) {
if (picDownload) {
qDebug() << "Picture NOT found, trying to download (set: " << setName << " card: " << correctedCardname << ")";
cardsToDownload.append(cardBeingLoaded);
cardBeingLoaded=0;
if (!downloadRunning)
startNextPicDownload();
} else {
if (cardBeingLoaded.nextSet())
{
qDebug() << "Picture NOT found and download disabled, moving to next set (newset: " << setName << " card: " << correctedCardname << ")";
mutex.lock();
loadQueue.prepend(cardBeingLoaded);
cardBeingLoaded=0;
mutex.unlock();
} else {
qDebug() << "Picture NOT found, download disabled, no more sets to try: BAILING OUT (oldset: " << setName << " card: " << correctedCardname << ")";
emit imageLoaded(cardBeingLoaded.getCard(), QImage());
}
}
}
}
}
QString PictureLoader::getPicUrl()
{
if (!picDownload) return QString("");
CardInfo *card = cardBeingDownloaded.getCard();
CardSet *set=cardBeingDownloaded.getCurrentSet();
QString picUrl = QString("");
// if sets have been defined for the card, they can contain custom picUrls
if(set)
{
// first check if Hq is enabled and a custom Hq card url exists in cards.xml
if(picDownloadHq)
{
picUrl = card->getCustomPicURLHq(set->getShortName());
if (!picUrl.isEmpty())
return picUrl;
}
// then, test for a custom, non-Hq card url in cards.xml
picUrl = card->getCustomPicURL(set->getShortName());
if (!picUrl.isEmpty())
return picUrl;
}
// if a card has a muid, use the default url; if not, use the fallback
int muid = set ? card->getMuId(set->getShortName()) : 0;
if(muid)
picUrl = picDownloadHq ? settingsCache->getPicUrlHq() : settingsCache->getPicUrl();
else
picUrl = picDownloadHq ? settingsCache->getPicUrlHqFallback() : settingsCache->getPicUrlFallback();
picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName()));
picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid)));
if (set) {
picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName()));
picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName()));
}
if (picUrl.contains("!name!") ||
picUrl.contains("!setcode!") ||
picUrl.contains("!setname!") ||
picUrl.contains("!cardid!")) {
qDebug() << "Insufficient card data to download" << card->getName() << "Url:" << picUrl;
return QString("");
}
return picUrl;
}
void PictureLoader::startNextPicDownload()
{
if (cardsToDownload.isEmpty()) {
cardBeingDownloaded = 0;
downloadRunning = false;
return;
}
downloadRunning = true;
cardBeingDownloaded = cardsToDownload.takeFirst();
QString picUrl = getPicUrl();
if (picUrl.isEmpty()) {
downloadRunning = false;
picDownloadFailed();
} else {
QUrl url(picUrl);
QNetworkRequest req(url);
qDebug() << "starting picture download:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
networkManager->get(req);
}
}
void PictureLoader::picDownloadFailed()
{
if (cardBeingDownloaded.nextSet())
{
qDebug() << "Picture NOT found, download failed, moving to next set (newset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
mutex.lock();
loadQueue.prepend(cardBeingDownloaded);
mutex.unlock();
emit startLoadQueue();
} else {
qDebug() << "Picture NOT found, download failed, no more sets to try: BAILING OUT (oldset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
cardBeingDownloaded = 0;
emit imageLoaded(cardBeingDownloaded.getCard(), QImage());
}
}
void PictureLoader::picDownloadFinished(QNetworkReply *reply)
{
QString picsPath = _picsPath;
if (reply->error()) {
qDebug() << "Download failed:" << reply->errorString();
}
const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader
QImage testImage;
QImageReader imgReader;
imgReader.setDecideFormatFromContent(true);
imgReader.setDevice(reply);
QString extension = "." + imgReader.format(); //the format is determined prior to reading the QImageReader data into a QImage object, as that wipes the QImageReader buffer
if (extension == ".jpeg")
extension = ".jpg";
if (imgReader.read(&testImage)) {
QString setName = cardBeingDownloaded.getSetName();
if(!setName.isEmpty())
{
if (!QDir().mkpath(picsPath + "/downloadedPics/" + setName)) {
qDebug() << picsPath + "/downloadedPics/" + setName + " could not be created.";
return;
}
QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + extension);
if (!newPic.open(QIODevice::WriteOnly))
return;
newPic.write(picData);
newPic.close();
}
emit imageLoaded(cardBeingDownloaded.getCard(), testImage);
} else {
picDownloadFailed();
}
reply->deleteLater();
startNextPicDownload();
}
void PictureLoader::loadImage(CardInfo *card)
{
QMutexLocker locker(&mutex);
// avoid queueing the same card more than once
if(card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard())
return;
foreach(PictureToLoad pic, loadQueue)
{
if(pic.getCard() == card)
return;
}
loadQueue.append(PictureToLoad(card));
emit startLoadQueue();
}
void PictureLoader::setPicsPath(const QString &path)
{
QMutexLocker locker(&mutex);
_picsPath = path;
}
void PictureLoader::setPicDownload(bool _picDownload)
{
QMutexLocker locker(&mutex);
picDownload = _picDownload;
}
void PictureLoader::setPicDownloadHq(bool _picDownloadHq)
{
QMutexLocker locker(&mutex);
picDownloadHq = _picDownloadHq;
}
CardInfo::CardInfo(CardDatabase *_db,
const QString &_name,
bool _isToken,
const QString &_manacost,
const QString &_cmc,
const QString &_cardtype,
const QString &_powtough,
const QString &_text,
const QStringList &_colors,
int _loyalty,
bool _cipt,
int _tableRow,
const SetList &_sets,
const QStringMap &_customPicURLs,
const QStringMap &_customPicURLsHq,
MuidMap _muIds)
: db(_db),
name(_name),
isToken(_isToken),
sets(_sets),
manacost(_manacost),
cmc(_cmc),
cardtype(_cardtype),
powtough(_powtough),
text(_text),
colors(_colors),
loyalty(_loyalty),
customPicURLs(_customPicURLs),
customPicURLsHq(_customPicURLsHq),
muIds(_muIds),
cipt(_cipt),
tableRow(_tableRow)
{
pixmapCacheKey = QLatin1String("card_") + name;
simpleName = CardInfo::simplifyName(name);
for (int i = 0; i < sets.size(); i++)
sets[i]->append(this);
}
CardInfo::~CardInfo()
{
clearPixmapCache();
}
QString CardInfo::getMainCardType() const
{
QString result = getCardType();
/*
Legendary Artifact Creature - Golem
Instant // Instant
*/
int pos;
if ((pos = result.indexOf('-')) != -1)
result.remove(pos, result.length());
if ((pos = result.indexOf("")) != -1)
result.remove(pos, result.length());
if ((pos = result.indexOf("//")) != -1)
result.remove(pos, result.length());
result = result.simplified();
/*
Legendary Artifact Creature
Instant
*/
if ((pos = result.lastIndexOf(' ')) != -1)
result = result.mid(pos + 1);
/*
Creature
Instant
*/
return result;
}
QString CardInfo::getCorrectedName() const
{
QString result = name;
// Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental?
return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' ');
}
void CardInfo::addToSet(CardSet *set)
{
set->append(this);
sets << set;
}
void CardInfo::loadPixmap(QPixmap &pixmap)
{
if(QPixmapCache::find(pixmapCacheKey, &pixmap))
return;
pixmap = QPixmap();
if (getName().isEmpty()) {
pixmap.load(settingsCache->getCardBackPicturePath());
return;
}
db->loadImage(this);
}
void CardInfo::imageLoaded(const QImage &image)
{
if (!image.isNull()) {
QPixmapCache::insert(pixmapCacheKey, QPixmap::fromImage(image));
emit pixmapUpdated();
}
}
void CardInfo::getPixmap(QSize size, QPixmap &pixmap)
{
QString key = QLatin1String("card_") + name + QLatin1Char('_') + QString::number(size.width());
if(QPixmapCache::find(key, &pixmap))
return;
QPixmap bigPixmap;
loadPixmap(bigPixmap);
if (bigPixmap.isNull()) {
if (!getName().isEmpty()) {
pixmap = QPixmap(); // null
return;
} else {
pixmap = QPixmap(size);
pixmap.fill(Qt::transparent);
QSvgRenderer svg(QString(":/back.svg"));
QPainter painter(&pixmap);
svg.render(&painter, QRectF(0, 0, size.width(), size.height()));
}
} else {
pixmap = bigPixmap.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QPixmapCache::insert(key, pixmap);
}
void CardInfo::clearPixmapCache()
{
//qDebug() << "Deleting pixmap for" << name;
QPixmapCache::remove(pixmapCacheKey);
}
void CardInfo::clearPixmapCacheMiss()
{
QPixmap pixmap;
if(!QPixmapCache::find(pixmapCacheKey, &pixmap))
return;
if (pixmap.isNull())
clearPixmapCache();
}
void CardInfo::updatePixmapCache()
{
qDebug() << "Updating pixmap cache for" << name;
clearPixmapCache();
QPixmap tmp;
loadPixmap(tmp);
emit pixmapUpdated();
}
QString CardInfo::simplifyName(const QString &name) {
QString simpleName(name);
// So Aetherling would work, but not Ætherling since 'Æ' would get replaced
// with nothing.
simpleName.replace("æ", "ae");
simpleName.replace("Æ", "AE");
// Replace Jötun Grunt with Jotun Grunt.
simpleName = simpleName.normalized(QString::NormalizationForm_KD);
// Replace dashes with spaces so that we can say "garruk the veil cursed"
// instead of the unintuitive "garruk the veilcursed".
simpleName = simpleName.replace("-", " ");
simpleName.remove(QRegExp("[^a-zA-Z0-9 ]"));
simpleName = simpleName.toLower();
return simpleName;
}
static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
{
xml.writeStartElement("card");
xml.writeTextElement("name", info->getName());
const SetList &sets = info->getSets();
QString tmpString;
QString tmpSet;
for (int i = 0; i < sets.size(); i++) {
xml.writeStartElement("set");
tmpSet=sets[i]->getShortName();
xml.writeAttribute("muId", QString::number(info->getMuId(tmpSet)));
tmpString = info->getCustomPicURL(tmpSet);
if(!tmpString.isEmpty())
xml.writeAttribute("picURL", tmpString);
tmpString = info->getCustomPicURLHq(tmpSet);
if(!tmpString.isEmpty())
xml.writeAttribute("picURLHq", tmpString);
xml.writeCharacters(tmpSet);
xml.writeEndElement();
}
const QStringList &colors = info->getColors();
for (int i = 0; i < colors.size(); i++)
xml.writeTextElement("color", colors[i]);
xml.writeTextElement("manacost", info->getManaCost());
xml.writeTextElement("cmc", info->getCmc());
xml.writeTextElement("type", info->getCardType());
if (!info->getPowTough().isEmpty())
xml.writeTextElement("pt", info->getPowTough());
xml.writeTextElement("tablerow", QString::number(info->getTableRow()));
xml.writeTextElement("text", info->getText());
if (info->getMainCardType() == "Planeswalker")
xml.writeTextElement("loyalty", QString::number(info->getLoyalty()));
if (info->getCipt())
xml.writeTextElement("cipt", "1");
if (info->getIsToken())
xml.writeTextElement("token", "1");
xml.writeEndElement(); // card
return xml;
}
CardDatabase::CardDatabase(QObject *parent)
: QObject(parent), noCard(0), loadStatus(NotLoaded)
{
connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
connect(settingsCache, SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabase()));
connect(settingsCache, SIGNAL(tokenDatabasePathChanged()), this, SLOT(loadTokenDatabase()));
connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
connect(settingsCache, SIGNAL(picDownloadHqChanged()), this, SLOT(picDownloadHqChanged()));
loadCardDatabase();
loadTokenDatabase();
pictureLoaderThread = new QThread;
pictureLoader = new PictureLoader(settingsCache->getPicsPath(), settingsCache->getPicDownload(), settingsCache->getPicDownloadHq());
pictureLoader->moveToThread(pictureLoaderThread);
connect(pictureLoader, SIGNAL(imageLoaded(CardInfo *, const QImage &)), this, SLOT(imageLoaded(CardInfo *, const QImage &)));
pictureLoaderThread->start(QThread::LowPriority);
noCard = new CardInfo(this);
QPixmap tmp;
noCard->loadPixmap(tmp); // cache pixmap for card back
connect(settingsCache, SIGNAL(cardBackPicturePathChanged()), noCard, SLOT(updatePixmapCache()));
}
CardDatabase::~CardDatabase()
{
clear();
delete noCard;
pictureLoader->deleteLater();
pictureLoaderThread->wait();
delete pictureLoaderThread;
}
void CardDatabase::clear()
{
QHashIterator<QString, CardSet *> setIt(sets);
while (setIt.hasNext()) {
setIt.next();
delete setIt.value();
}
sets.clear();
QHashIterator<QString, CardInfo *> i(cards);
while (i.hasNext()) {
i.next();
delete i.value();
}
cards.clear();
// The pointers themselves were already deleted, so we don't delete them
// again.
simpleNameCards.clear();
}
void CardDatabase::addCard(CardInfo *card)
{
cards.insert(card->getName(), card);
simpleNameCards.insert(card->getSimpleName(), card);
emit cardAdded(card);
}
void CardDatabase::removeCard(CardInfo *card)
{
cards.remove(card->getName());
simpleNameCards.remove(card->getSimpleName());
emit cardRemoved(card);
}
CardInfo *CardDatabase::getCard(const QString &cardName, bool createIfNotFound) {
return getCardFromMap(cards, cardName, createIfNotFound);
}
CardInfo *CardDatabase::getCardBySimpleName(const QString &cardName, bool createIfNotFound) {
QString simpleName = CardInfo::simplifyName(cardName);
return getCardFromMap(simpleNameCards, simpleName, createIfNotFound);
}
CardSet *CardDatabase::getSet(const QString &setName)
{
if (sets.contains(setName))
return sets.value(setName);
else {
CardSet *newSet = new CardSet(setName);
sets.insert(setName, newSet);
return newSet;
}
}
SetList CardDatabase::getSetList() const
{
SetList result;
QHashIterator<QString, CardSet *> i(sets);
while (i.hasNext()) {
i.next();
result << i.value();
}
return result;
}
void CardDatabase::clearPixmapCache()
{
// This also clears the cards in simpleNameCards since they point to the
// same object.
QHashIterator<QString, CardInfo *> i(cards);
while (i.hasNext()) {
i.next();
i.value()->clearPixmapCache();
}
if (noCard)
noCard->clearPixmapCache();
}
void CardDatabase::loadSetsFromXml(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement)
break;
if (xml.name() == "set") {
QString shortName, longName, setType;
QDate releaseDate;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement)
break;
if (xml.name() == "name")
shortName = xml.readElementText();
else if (xml.name() == "longname")
longName = xml.readElementText();
else if (xml.name() == "settype")
setType = xml.readElementText();
else if (xml.name() == "releasedate")
releaseDate = QDate::fromString(xml.readElementText(), Qt::ISODate);
}
CardSet * newSet = getSet(shortName);
newSet->setLongName(longName);
newSet->setSetType(setType);
newSet->setReleaseDate(releaseDate);
}
}
}
void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
{
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement)
break;
if (xml.name() == "card") {
QString name, manacost, cmc, type, pt, text;
QStringList colors;
QStringMap customPicURLs, customPicURLsHq;
MuidMap muids;
SetList sets;
int tableRow = 0;
int loyalty = 0;
bool cipt = false;
bool isToken = false;
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement)
break;
if (xml.name() == "name")
name = xml.readElementText();
else if (xml.name() == "manacost")
manacost = xml.readElementText();
else if (xml.name() == "cmc")
cmc = xml.readElementText();
else if (xml.name() == "type")
type = xml.readElementText();
else if (xml.name() == "pt")
pt = xml.readElementText();
else if (xml.name() == "text")
text = xml.readElementText();
else if (xml.name() == "set") {
QXmlStreamAttributes attrs = xml.attributes();
QString setName = xml.readElementText();
sets.append(getSet(setName));
if (attrs.hasAttribute("muId")) {
muids[setName] = attrs.value("muId").toString().toInt();
}
if (attrs.hasAttribute("picURL")) {
customPicURLs[setName] = attrs.value("picURL").toString();
}
if (attrs.hasAttribute("picURLHq")) {
customPicURLsHq[setName] = attrs.value("picURLHq").toString();
}
} else if (xml.name() == "color")
colors << xml.readElementText();
else if (xml.name() == "tablerow")
tableRow = xml.readElementText().toInt();
else if (xml.name() == "cipt")
cipt = (xml.readElementText() == "1");
else if (xml.name() == "loyalty")
loyalty = xml.readElementText().toInt();
else if (xml.name() == "token")
isToken = xml.readElementText().toInt();
}
if (isToken == tokens) {
addCard(new CardInfo(this, name, isToken, manacost, cmc, type, pt, text, colors, loyalty, cipt, tableRow, sets, customPicURLs, customPicURLsHq, muids));
}
}
}
}
CardInfo *CardDatabase::getCardFromMap(CardNameMap &cardMap, const QString &cardName, bool createIfNotFound) {
if (cardName.isEmpty())
return noCard;
else if (cardMap.contains(cardName))
return cardMap.value(cardName);
else if (createIfNotFound) {
CardInfo *newCard = new CardInfo(this, cardName, true);
newCard->addToSet(getSet(CardDatabase::TOKENS_SETNAME));
cardMap.insert(cardName, newCard);
return newCard;
} else
return 0;
}
LoadStatus CardDatabase::loadFromFile(const QString &fileName, bool tokens)
{
QFile file(fileName);
file.open(QIODevice::ReadOnly);
if (!file.isOpen())
return FileError;
QXmlStreamReader xml(&file);
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::StartElement) {
if (xml.name() != "cockatrice_carddatabase")
return Invalid;
int version = xml.attributes().value("version").toString().toInt();
if (version < versionNeeded) {
qDebug() << "loadFromFile(): Version too old: " << version;
return VersionTooOld;
}
while (!xml.atEnd()) {
if (xml.readNext() == QXmlStreamReader::EndElement)
break;
if (xml.name() == "sets")
loadSetsFromXml(xml);
else if (xml.name() == "cards")
loadCardsFromXml(xml, tokens);
}
}
}
qDebug() << cards.size() << "cards in" << sets.size() << "sets loaded";
if (cards.isEmpty()) return NoCards;
return Ok;
}
bool CardDatabase::saveToFile(const QString &fileName, bool tokens)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly))
return false;
QXmlStreamWriter xml(&file);
xml.setAutoFormatting(true);
xml.writeStartDocument();
xml.writeStartElement("cockatrice_carddatabase");
xml.writeAttribute("version", QString::number(versionNeeded));
if (!tokens) {
xml.writeStartElement("sets");
QHashIterator<QString, CardSet *> setIterator(sets);
while (setIterator.hasNext())
xml << setIterator.next().value();
xml.writeEndElement(); // sets
}
xml.writeStartElement("cards");
QHashIterator<QString, CardInfo *> cardIterator(cards);
while (cardIterator.hasNext()) {
CardInfo *card = cardIterator.next().value();
if (tokens == card->getIsToken()) {
xml << card;
}
}
xml.writeEndElement(); // cards
xml.writeEndElement(); // cockatrice_carddatabase
xml.writeEndDocument();
return true;
}
void CardDatabase::picDownloadChanged()
{
pictureLoader->setPicDownload(settingsCache->getPicDownload());
if (settingsCache->getPicDownload()) {
QHashIterator<QString, CardInfo *> cardIterator(cards);
while (cardIterator.hasNext())
cardIterator.next().value()->clearPixmapCacheMiss();
}
}
void CardDatabase::picDownloadHqChanged()
{
pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq());
if (settingsCache->getPicDownloadHq()) {
QHashIterator<QString, CardInfo *> cardIterator(cards);
while (cardIterator.hasNext())
cardIterator.next().value()->clearPixmapCacheMiss();
}
}
void CardDatabase::emitCardListChanged()
{
emit cardListChanged();
}
LoadStatus CardDatabase::loadCardDatabase(const QString &path, bool tokens)
{
LoadStatus tempLoadStatus = NotLoaded;
if (!path.isEmpty())
tempLoadStatus = loadFromFile(path, tokens);
if (tempLoadStatus == Ok) {
SetList allSets;
QHashIterator<QString, CardSet *> setsIterator(sets);
while (setsIterator.hasNext())
allSets.append(setsIterator.next().value());
allSets.sortByKey();
emit cardListChanged();
}
if (!tokens) {
loadStatus = tempLoadStatus;
qDebug() << "loadCardDatabase(): Path = " << path << " Status = " << loadStatus;
}
return tempLoadStatus;
}
void CardDatabase::loadCardDatabase()
{
loadCardDatabase(settingsCache->getCardDatabasePath(), false);
}
void CardDatabase::loadTokenDatabase()
{
loadCardDatabase(settingsCache->getTokenDatabasePath(), true);
}
QStringList CardDatabase::getAllColors() const
{
QSet<QString> colors;
QHashIterator<QString, CardInfo *> cardIterator(cards);
while (cardIterator.hasNext()) {
const QStringList &cardColors = cardIterator.next().value()->getColors();
if (cardColors.isEmpty())
colors.insert("X");
else
for (int i = 0; i < cardColors.size(); ++i)
colors.insert(cardColors[i]);
}
return colors.toList();
}
QStringList CardDatabase::getAllMainCardTypes() const
{
QSet<QString> types;
QHashIterator<QString, CardInfo *> cardIterator(cards);
while (cardIterator.hasNext())
types.insert(cardIterator.next().value()->getMainCardType());
return types.toList();
}
void CardDatabase::cacheCardPixmaps(const QStringList &cardNames)
{
QPixmap tmp;
// never cache more than 300 cards at once for a single deck
int max = qMin(cardNames.size(), 300);
for (int i = 0; i < max; ++i)
getCard(cardNames[i])->loadPixmap(tmp);
}
void CardDatabase::loadImage(CardInfo *card)
{
pictureLoader->loadImage(card);
}
void CardDatabase::imageLoaded(CardInfo *card, QImage image)
{
card->imageLoaded(image);
}
void CardDatabase::picsPathChanged()
{
pictureLoader->setPicsPath(settingsCache->getPicsPath());
clearPixmapCache();
}