Merge pull request #1556 from ctrlaltca/carddatabase_changes

Reimplemented PictureLoader as a singleton
This commit is contained in:
Zach 2015-09-23 01:21:37 -04:00
commit 064c89ba5c
21 changed files with 657 additions and 705 deletions

View file

@ -96,6 +96,7 @@ SET(cockatrice_SOURCES
src/qt-json/json.cpp
src/soundengine.cpp
src/pending_command.cpp
src/pictureloader.cpp
src/shortcutssettings.cpp
src/sequenceEdit/sequenceedit.cpp
src/sequenceEdit/shortcutstab.cpp

View file

@ -9,6 +9,7 @@
#include "carddatabase.h"
#include "cardinfowidget.h"
#include "abstractcarditem.h"
#include "pictureloader.h"
#include "settingscache.h"
#include "main.h"
#include "gamescene.h"
@ -45,7 +46,8 @@ void AbstractCardItem::pixmapUpdated()
void AbstractCardItem::cardInfoUpdated()
{
info = db->getCard(name);
connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated()));
if(info)
connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated()));
}
void AbstractCardItem::setRealZValue(qreal _zValue)
@ -93,7 +95,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
QPixmap translatedPixmap;
// don't even spend time trying to load the picture if our size is too small
if(translatedSize.width() > 10)
imageSource->getPixmap(translatedSize.toSize(), translatedPixmap);
PictureLoader::getPixmap(translatedPixmap, imageSource, translatedSize.toSize());
painter->save();
QColor bgColor = Qt::transparent;
@ -191,10 +193,12 @@ void AbstractCardItem::setName(const QString &_name)
return;
emit deleteCardInfoPopup(name);
disconnect(info, 0, this, 0);
if(info)
disconnect(info, 0, this, 0);
name = _name;
info = db->getCard(name);
connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated()));
if(info)
connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated()));
update();
}

View file

@ -1,20 +1,16 @@
#include "carddatabase.h"
#include "pictureloader.h"
#include "settingscache.h"
#include "thememanager.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QPainter>
#include <QUrl>
#include <QSet>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QDebug>
#include <QImageReader>
#include <QSettings>
#include <QMessageBox>
const int CardDatabase::versionNeeded = 3;
@ -88,38 +84,6 @@ void SetList::sortByKey()
qSort(begin(), end(), KeyCompareFunctor());
}
class SetList::EnabledAndKeyCompareFunctor {
public:
inline bool operator()(CardSet *a, CardSet *b) const
{
if(a->getEnabled())
{
if(b->getEnabled())
{
// both enabled: sort by key
return a->getSortKey() < b->getSortKey();
} else {
// only a enabled
return true;
}
} else {
if(b->getEnabled())
{
// only b enabled
return false;
} else {
// both disabled: sort by key
return a->getSortKey() < b->getSortKey();
}
}
}
};
void SetList::sortByEnabledAndKey()
{
qSort(begin(), end(), EnabledAndKeyCompareFunctor());
}
int SetList::getEnabledSetsNum()
{
int num=0;
@ -195,334 +159,6 @@ void SetList::guessSortKeys()
}
}
PictureToLoad::PictureToLoad(CardInfo *_card, bool _hq)
: card(_card), setIndex(0), hq(_hq)
{
if (card) {
sortedSets = card->getSets();
sortedSets.sortByEnabledAndKey();
}
}
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;
}
QStringList PictureLoader::md5Blacklist = QStringList()
<< "db0c48db407a907c16ade38de048a441"; // card back returned by gatherer when card is not found
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("!name_lower!", QUrl::toPercentEncoding(card->getCorrectedName().toLower()));
picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid)));
if (set)
{
picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName()));
picUrl.replace("!setcode_lower!", QUrl::toPercentEncoding(set->getShortName().toLower()));
picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName()));
picUrl.replace("!setname_lower!", QUrl::toPercentEncoding(set->getLongName().toLower()));
}
if (
picUrl.contains("!name!") ||
picUrl.contains("!name_lower!") ||
picUrl.contains("!setcode!") ||
picUrl.contains("!setcode_lower!") ||
picUrl.contains("!setname!") ||
picUrl.contains("!setname_lower!") ||
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();
}
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (statusCode == 301 || statusCode == 302) {
QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
QNetworkRequest req(redirectUrl);
qDebug() << "following redirect:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
networkManager->get(req);
return;
}
const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader
// check if the image is blacklisted
QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
if(md5Blacklist.contains(md5sum))
{
qDebug() << "Picture downloaded, but blacklisted (" << md5sum << "), will consider it as not found";
picDownloadFailed();
reply->deleteLater();
startNextPicDownload();
return;
}
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,
@ -539,7 +175,6 @@ CardInfo::CardInfo(CardDatabase *_db,
int _tableRow,
const SetList &_sets,
const QStringMap &_customPicURLs,
const QStringMap &_customPicURLsHq,
MuidMap _muIds
)
: db(_db),
@ -556,7 +191,6 @@ CardInfo::CardInfo(CardDatabase *_db,
upsideDownArt(_upsideDownArt),
loyalty(_loyalty),
customPicURLs(_customPicURLs),
customPicURLsHq(_customPicURLsHq),
muIds(_muIds),
cipt(_cipt),
tableRow(_tableRow)
@ -570,7 +204,7 @@ CardInfo::CardInfo(CardDatabase *_db,
CardInfo::~CardInfo()
{
clearPixmapCache();
PictureLoader::clearPixmapCache(this);
}
QString CardInfo::getMainCardType() const
@ -617,82 +251,6 @@ void CardInfo::addToSet(CardSet *set)
sets << set;
}
void CardInfo::loadPixmap(QPixmap &pixmap)
{
if(QPixmapCache::find(pixmapCacheKey, &pixmap))
return;
pixmap = QPixmap();
if (getName().isEmpty()) {
pixmap = QPixmap("theme:cardback");
return;
}
db->loadImage(this);
}
void CardInfo::imageLoaded(const QImage &image)
{
if (!image.isNull()) {
if(upsideDownArt)
{
QImage mirrorImage = image.mirrored(true, true);
QPixmapCache::insert(pixmapCacheKey, QPixmap::fromImage(mirrorImage));
} else {
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()) + QString::number(size.height());
if(QPixmapCache::find(key, &pixmap))
return;
QPixmap bigPixmap;
loadPixmap(bigPixmap);
if (bigPixmap.isNull()) {
if (getName().isEmpty()) {
pixmap = pixmap = QPixmap("theme:cardback");
} else {
pixmap = QPixmap(); // null
return;
}
}
pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, 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);
@ -731,10 +289,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
if(!tmpString.isEmpty())
xml.writeAttribute("picURL", tmpString);
tmpString = info->getCustomPicURLHq(tmpSet);
if(!tmpString.isEmpty())
xml.writeAttribute("picURLHq", tmpString);
xml.writeCharacters(tmpSet);
xml.writeEndElement();
}
@ -767,37 +321,18 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
}
CardDatabase::CardDatabase(QObject *parent)
: QObject(parent), noCard(0), loadStatus(NotLoaded)
: QObject(parent), 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(themeManager, SIGNAL(themeChanged()), noCard, SLOT(updatePixmapCache()));
}
CardDatabase::~CardDatabase()
{
clear();
delete noCard;
pictureLoader->deleteLater();
pictureLoaderThread->wait();
delete pictureLoaderThread;
}
void CardDatabase::clear()
@ -839,6 +374,15 @@ CardInfo *CardDatabase::getCard(const QString &cardName, bool createIfNotFound)
return getCardFromMap(cards, cardName, createIfNotFound);
}
QList<CardInfo *> CardDatabase::getCards(const QStringList &cardNames)
{
QList<CardInfo *> cardInfos;
foreach(QString cardName, cardNames)
cardInfos.append(getCardFromMap(cards, cardName, false));
return cardInfos;
}
CardInfo *CardDatabase::getCardBySimpleName(const QString &cardName, bool createIfNotFound) {
QString simpleName = CardInfo::simplifyName(cardName);
return getCardFromMap(simpleNameCards, simpleName, createIfNotFound);
@ -866,21 +410,6 @@ SetList CardDatabase::getSetList() const
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();
QPixmapCache::clear();
}
void CardDatabase::loadSetsFromXml(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
@ -918,7 +447,7 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
if (xml.name() == "card") {
QString name, manacost, cmc, type, pt, text;
QStringList colors, relatedCards;
QStringMap customPicURLs, customPicURLsHq;
QStringMap customPicURLs;
MuidMap muids;
SetList sets;
int tableRow = 0;
@ -951,9 +480,6 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
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() == "related")
@ -971,24 +497,24 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
}
if (isToken == tokens) {
addCard(new CardInfo(this, name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, upsideDown, loyalty, cipt, tableRow, sets, customPicURLs, customPicURLsHq, muids));
addCard(new CardInfo(this, name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, upsideDown, loyalty, cipt, tableRow, sets, customPicURLs, muids));
}
}
}
}
CardInfo *CardDatabase::getCardFromMap(CardNameMap &cardMap, const QString &cardName, bool createIfNotFound) {
if (cardName.isEmpty())
return noCard;
else if (cardMap.contains(cardName))
if (cardMap.contains(cardName))
return cardMap.value(cardName);
else if (createIfNotFound) {
if (createIfNotFound) {
CardInfo *newCard = new CardInfo(this, cardName, true);
newCard->addToSet(getSet(CardDatabase::TOKENS_SETNAME));
cardMap.insert(cardName, newCard);
return newCard;
} else
return 0;
}
return 0;
}
LoadStatus CardDatabase::loadFromFile(const QString &fileName, bool tokens)
@ -1061,26 +587,6 @@ bool CardDatabase::saveToFile(const QString &fileName, bool tokens)
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();
@ -1159,31 +665,6 @@ QStringList CardDatabase::getAllMainCardTypes() const
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();
}
void CardDatabase::checkUnknownSets()
{
SetList sets = getSetList();

View file

@ -8,17 +8,9 @@
#include <QDataStream>
#include <QList>
#include <QXmlStreamReader>
#include <QNetworkRequest>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QPixmapCache>
class CardDatabase;
class CardInfo;
class QNetworkAccessManager;
class QNetworkReply;
class QNetworkRequest;
typedef QMap<QString, QString> QStringMap;
@ -55,9 +47,7 @@ public:
class SetList : public QList<CardSet *> {
private:
class KeyCompareFunctor;
class EnabledAndKeyCompareFunctor;
public:
void sortByEnabledAndKey();
void sortByKey();
void guessSortKeys();
void enableAllUnknown();
@ -67,53 +57,6 @@ public:
int getUnknownSetsNum();
};
class PictureToLoad {
private:
CardInfo *card;
SetList sortedSets;
int setIndex;
bool hq;
public:
PictureToLoad(CardInfo *_card = 0, bool _hq = true);
CardInfo *getCard() const { return card; }
CardSet *getCurrentSet() const;
QString getSetName() const;
bool nextSet();
bool getHq() const { return hq; }
void setHq(bool _hq) { hq = _hq; }
};
class PictureLoader : public QObject {
Q_OBJECT
private:
QString _picsPath;
QList<PictureToLoad> loadQueue;
QMutex mutex;
QNetworkAccessManager *networkManager;
QList<PictureToLoad> cardsToDownload;
PictureToLoad cardBeingLoaded;
PictureToLoad cardBeingDownloaded;
bool picDownload, picDownloadHq, downloadRunning, loadQueueRunning;
void startNextPicDownload();
QString getPicUrl();
static QStringList md5Blacklist;
public:
PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent = 0);
~PictureLoader();
void setPicsPath(const QString &path);
void setPicDownload(bool _picDownload);
void setPicDownloadHq(bool _picDownloadHq);
void loadImage(CardInfo *card);
private slots:
void picDownloadFinished(QNetworkReply *reply);
void picDownloadFailed();
public slots:
void processLoadQueue();
signals:
void startLoadQueue();
void imageLoaded(CardInfo *card, const QImage &image);
};
class CardInfo : public QObject {
Q_OBJECT
private:
@ -138,7 +81,7 @@ private:
QStringList relatedCards;
bool upsideDownArt;
int loyalty;
QStringMap customPicURLs, customPicURLsHq;
QStringMap customPicURLs;
MuidMap muIds;
bool cipt;
int tableRow;
@ -160,7 +103,6 @@ public:
int _tableRow = 0,
const SetList &_sets = SetList(),
const QStringMap &_customPicURLs = QStringMap(),
const QStringMap &_customPicURLsHq = QStringMap(),
MuidMap muids = MuidMap()
);
~CardInfo();
@ -173,6 +115,7 @@ public:
const QString &getCardType() const { return cardtype; }
const QString &getPowTough() const { return powtough; }
const QString &getText() const { return text; }
const QString &getPixmapCacheKey() const { return pixmapCacheKey; }
const int &getLoyalty() const { return loyalty; }
bool getCipt() const { return cipt; }
void setManaCost(const QString &_manaCost) { manacost = _manaCost; emit cardInfoChanged(this); }
@ -185,7 +128,6 @@ public:
const QStringList &getRelatedCards() const { return relatedCards; }
bool getUpsideDownArt() const { return upsideDownArt; }
QString getCustomPicURL(const QString &set) const { return customPicURLs.value(set); }
QString getCustomPicURLHq(const QString &set) const { return customPicURLsHq.value(set); }
int getMuId(const QString &set) const { return muIds.value(set); }
QString getMainCardType() const;
QString getCorrectedName() const;
@ -193,22 +135,15 @@ public:
void setTableRow(int _tableRow) { tableRow = _tableRow; }
void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(this); }
void setCustomPicURL(const QString &_set, const QString &_customPicURL) { customPicURLs.insert(_set, _customPicURL); }
void setCustomPicURLHq(const QString &_set, const QString &_customPicURL) { customPicURLsHq.insert(_set, _customPicURL); }
void setMuId(const QString &_set, const int &_muId) { muIds.insert(_set, _muId); }
void addToSet(CardSet *set);
void loadPixmap(QPixmap &pixmap);
void getPixmap(QSize size, QPixmap &pixmap);
void clearPixmapCache();
void clearPixmapCacheMiss();
void imageLoaded(const QImage &image);
void emitPixmapUpdated() { emit pixmapUpdated(); }
/**
* Simplify a name to have no punctuation and lowercase all letters, for
* less strict name-matching.
*/
static QString simplifyName(const QString &name);
public slots:
void updatePixmapCache();
signals:
void pixmapUpdated();
void cardInfoChanged(CardInfo *card);
@ -237,10 +172,6 @@ protected:
*/
SetNameMap sets;
CardInfo *noCard;
QThread *pictureLoaderThread;
PictureLoader *pictureLoader;
LoadStatus loadStatus;
bool detectedFirstRun;
private:
@ -258,13 +189,14 @@ public:
void clear();
void addCard(CardInfo *card);
void removeCard(CardInfo *card);
CardInfo *getCard(const QString &cardName = QString(), bool createIfNotFound = true);
CardInfo *getCard(const QString &cardName = QString(), bool createIfNotFound = false);
QList <CardInfo *> getCards(const QStringList &cardNames);
/*
* Get a card by its simple name. The name will be simplified in this
* function, so you don't need to simplify it beforehand.
*/
CardInfo *getCardBySimpleName(const QString &cardName = QString(), bool createIfNotFound = true);
CardInfo *getCardBySimpleName(const QString &cardName = QString(), bool createIfNotFound = false);
CardSet *getSet(const QString &setName);
QList<CardInfo *> getCardList() const { return cards.values(); }
@ -275,20 +207,12 @@ public:
QStringList getAllMainCardTypes() const;
LoadStatus getLoadStatus() const { return loadStatus; }
bool getLoadSuccess() const { return loadStatus == Ok; }
void cacheCardPixmaps(const QStringList &cardNames);
void loadImage(CardInfo *card);
bool hasDetectedFirstRun();
public slots:
void clearPixmapCache();
LoadStatus loadCardDatabase(const QString &path, bool tokens = false);
void loadCustomCardDatabases(const QString &path);
void emitCardListChanged();
private slots:
void imageLoaded(CardInfo *card, QImage image);
void picDownloadChanged();
void picDownloadHqChanged();
void picsPathChanged();
void loadCardDatabase();
void loadTokenDatabase();
signals:

View file

@ -93,7 +93,8 @@ void CardFrame::setCard(CardInfo *card)
if (info)
disconnect(info, 0, this, 0);
info = card;
connect(info, SIGNAL(destroyed()), this, SLOT(clear()));
if(info)
connect(info, SIGNAL(destroyed()), this, SLOT(clear()));
text->setCard(info);
pic->setCard(info);
}

View file

@ -6,6 +6,7 @@
#include "carditem.h"
#include "carddatabase.h"
#include "pictureloader.h"
#include "main.h"
CardInfoPicture::CardInfoPicture(QWidget *parent)
@ -21,7 +22,8 @@ void CardInfoPicture::setCard(CardInfo *card)
if (info)
disconnect(info, 0, this, 0);
info = card;
connect(info, SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
if(info)
connect(info, SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap()));
updatePixmap();
}
@ -40,13 +42,13 @@ void CardInfoPicture::updatePixmap()
void CardInfoPicture::loadPixmap()
{
if(info)
info->getPixmap(size(), resizedPixmap);
PictureLoader::getPixmap(resizedPixmap, info, size());
else
resizedPixmap = QPixmap();
if (resizedPixmap.isNull())
db->getCard()->getPixmap(size(), resizedPixmap);
PictureLoader::getPixmap(resizedPixmap, db->getCard(), size());
}
void CardInfoPicture::paintEvent(QPaintEvent *)

View file

@ -53,13 +53,23 @@ CardInfoText::CardInfoText(QWidget *parent)
void CardInfoText::setCard(CardInfo *card)
{
nameLabel2->setText(card->getName());
manacostLabel2->setText(card->getManaCost());
colorLabel2->setText(card->getColors().join(""));
cardtypeLabel2->setText(card->getCardType());
powtoughLabel2->setText(card->getPowTough());
loyaltyLabel2->setText(card->getLoyalty() > 0 ? QString::number(card->getLoyalty()) : QString());
textLabel->setText(card->getText());
if(card) {
nameLabel2->setText(card->getName());
manacostLabel2->setText(card->getManaCost());
colorLabel2->setText(card->getColors().join(""));
cardtypeLabel2->setText(card->getCardType());
powtoughLabel2->setText(card->getPowTough());
loyaltyLabel2->setText(card->getLoyalty() > 0 ? QString::number(card->getLoyalty()) : QString());
textLabel->setText(card->getText());
} else {
nameLabel2->setText("");
manacostLabel2->setText("");
colorLabel2->setText("");
cardtypeLabel2->setText("");
powtoughLabel2->setText("");
loyaltyLabel2->setText("");
textLabel->setText("");
}
}
void CardInfoText::retranslateUi()

View file

@ -8,6 +8,7 @@
#include "cardinfowidget.h"
#include "carditem.h"
#include "carddatabase.h"
#include "pictureloader.h"
#include "main.h"
#include "settingscache.h"
@ -195,10 +196,10 @@ void CardInfoWidget::updatePixmap()
return;
QPixmap resizedPixmap;
info->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio), resizedPixmap);
PictureLoader::getPixmap(resizedPixmap, info, QSize(pixmapWidth, pixmapWidth * aspectRatio));
if (resizedPixmap.isNull())
getCard()->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio), resizedPixmap);
PictureLoader::getPixmap(resizedPixmap, getCard(), QSize(pixmapWidth, pixmapWidth * aspectRatio));
cardPicture->setPixmap(resizedPixmap);
}

View file

@ -107,7 +107,8 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->save();
transformPainter(painter, translatedSize, tapAngle);
QStringList ptDbSplit = db->getCard(name)->getPowTough().split("/");
CardInfo *card = db->getCard(name);
QStringList ptDbSplit = card ? card->getPowTough().split("/") : QStringList();
QStringList ptSplit = pt.split("/");
if (getFaceDown() || ptDbSplit.at(0) != ptSplit.at(0) || ptDbSplit.at(1) != ptSplit.at(1))

View file

@ -44,7 +44,6 @@ GeneralSettingsPage::GeneralSettingsPage()
}
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq());
updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates());
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
@ -53,20 +52,22 @@ GeneralSettingsPage::GeneralSettingsPage()
pixmapCacheEdit.setSingleStep(64);
pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize());
pixmapCacheEdit.setSuffix(" MB");
picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq());
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
highQualityURLEdit = new QLineEdit(settingsCache->getPicUrlHq());
highQualityURLEdit->setEnabled(settingsCache->getPicDownloadHq());
defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl());
fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback());
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
connect(&picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int)));
connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
connect(&picDownloadHqCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
connect(highQualityURLEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlHq(QString)));
connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
connect(defaultUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrl(QString)));
connect(fallbackUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlFallback(QString)));
connect(&defaultUrlRestoreButton, SIGNAL(clicked()), this, SLOT(defaultUrlRestoreButtonClicked()));
connect(&fallbackUrlRestoreButton, SIGNAL(clicked()), this, SLOT(fallbackUrlRestoreButtonClicked()));
setEnabledStatus(settingsCache->getPicDownload());
QGridLayout *personalGrid = new QGridLayout;
personalGrid->addWidget(&languageLabel, 0, 0);
@ -74,15 +75,18 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&pixmapCacheLabel, 1, 0);
personalGrid->addWidget(&pixmapCacheEdit, 1, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 2, 0);
personalGrid->addWidget(&picDownloadCheckBox, 3, 0);
personalGrid->addWidget(&picDownloadHqCheckBox, 4, 0);
personalGrid->addWidget(&highQualityURLLabel, 5, 0);
personalGrid->addWidget(highQualityURLEdit, 5, 1);
personalGrid->addWidget(&highQualityURLLinkLabel, 6, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 6, 0);
personalGrid->addWidget(&picDownloadCheckBox, 3, 0, 1, 3);
personalGrid->addWidget(&defaultUrlLabel, 4, 0, 1, 1);
personalGrid->addWidget(defaultUrlEdit, 4, 1, 1, 1);
personalGrid->addWidget(&defaultUrlRestoreButton, 4, 2, 1, 1);
personalGrid->addWidget(&fallbackUrlLabel, 5, 0, 1, 1);
personalGrid->addWidget(fallbackUrlEdit, 5, 1, 1, 1);
personalGrid->addWidget(&fallbackUrlRestoreButton, 5, 2, 1, 1);
personalGrid->addWidget(&urlLinkLabel, 6, 1, 1, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 7, 0, 1, 3);
highQualityURLLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
highQualityURLLinkLabel.setOpenExternalLinks(true);
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
personalGroupBox = new QGroupBox;
personalGroupBox->setLayout(personalGrid);
@ -154,6 +158,20 @@ QString GeneralSettingsPage::languageName(const QString &qmFile)
return translator.translate("GeneralSettingsPage", "English");
}
void GeneralSettingsPage::defaultUrlRestoreButtonClicked()
{
QString path = PIC_URL_DEFAULT;
defaultUrlEdit->setText(path);
settingsCache->setPicUrl(path);
}
void GeneralSettingsPage::fallbackUrlRestoreButtonClicked()
{
QString path = PIC_URL_FALLBACK;
fallbackUrlEdit->setText(path);
settingsCache->setPicUrlFallback(path);
}
void GeneralSettingsPage::deckPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"));
@ -238,7 +256,6 @@ void GeneralSettingsPage::retranslateUi()
personalGroupBox->setTitle(tr("Personal settings"));
languageLabel.setText(tr("Language:"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
picDownloadHqCheckBox.setText(tr("Download card pictures from a custom URL"));
pathsGroupBox->setTitle(tr("Paths"));
deckPathLabel.setText(tr("Decks directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
@ -246,15 +263,21 @@ void GeneralSettingsPage::retranslateUi()
cardDatabasePathLabel.setText(tr("Card database:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
pixmapCacheLabel.setText(tr("Picture cache size:"));
highQualityURLLabel.setText(tr("Custom Card Download URL:"));
highQualityURLLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ")));
defaultUrlLabel.setText(tr("Primary download URL:"));
fallbackUrlLabel.setText(tr("Fallback download URL:"));
urlLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ")));
clearDownloadedPicsButton.setText(tr("Reset/Clear Downloaded Pictures"));
updateNotificationCheckBox.setText(tr("Notify when new client features are available"));
defaultUrlRestoreButton.setText(tr("Reset"));
fallbackUrlRestoreButton.setText(tr("Reset"));
}
void GeneralSettingsPage::setEnabledStatus(bool status)
{
highQualityURLEdit->setEnabled(status);
defaultUrlEdit->setEnabled(status);
fallbackUrlEdit->setEnabled(status);
defaultUrlRestoreButton.setEnabled(status);
fallbackUrlRestoreButton.setEnabled(status);
}
AppearanceSettingsPage::AppearanceSettingsPage()

View file

@ -45,6 +45,8 @@ private slots:
void tokenDatabasePathButtonClicked();
void languageBoxChanged(int index);
void setEnabledStatus(bool);
void defaultUrlRestoreButtonClicked();
void fallbackUrlRestoreButtonClicked();
private:
QStringList findQmFiles();
QString languageName(const QString &qmFile);
@ -53,13 +55,13 @@ private:
QLineEdit *picsPathEdit;
QLineEdit *cardDatabasePathEdit;
QLineEdit *tokenDatabasePathEdit;
QLineEdit *highQualityURLEdit;
QLineEdit *defaultUrlEdit;
QLineEdit *fallbackUrlEdit;
QSpinBox pixmapCacheEdit;
QGroupBox *personalGroupBox;
QGroupBox *pathsGroupBox;
QComboBox languageBox;
QCheckBox picDownloadCheckBox;
QCheckBox picDownloadHqCheckBox;
QCheckBox updateNotificationCheckBox;
QLabel languageLabel;
QLabel pixmapCacheLabel;
@ -68,9 +70,12 @@ private:
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
QLabel highQualityURLLabel;
QLabel highQualityURLLinkLabel;
QLabel defaultUrlLabel;
QLabel fallbackUrlLabel;
QLabel urlLinkLabel;
QPushButton clearDownloadedPicsButton;
QPushButton defaultUrlRestoreButton;
QPushButton fallbackUrlRestoreButton;
};
class AppearanceSettingsPage : public AbstractSettingsPage {

View file

@ -0,0 +1,450 @@
#include "pictureloader.h"
#include "carddatabase.h"
#include "main.h"
#include "settingscache.h"
#include <QApplication>
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPainter>
#include <QPixmapCache>
#include <QSet>
#include <QSvgRenderer>
#include <QThread>
#include <QUrl>
class PictureToLoad::EnabledAndKeyCompareFunctor {
public:
inline bool operator()(CardSet *a, CardSet *b) const
{
if(a->getEnabled())
{
if(b->getEnabled())
{
// both enabled: sort by key
return a->getSortKey() < b->getSortKey();
} else {
// only a enabled
return true;
}
} else {
if(b->getEnabled())
{
// only b enabled
return false;
} else {
// both disabled: sort by key
return a->getSortKey() < b->getSortKey();
}
}
}
};
PictureToLoad::PictureToLoad(CardInfo *_card)
: card(_card), setIndex(0)
{
if (card) {
sortedSets = card->getSets();
qSort(sortedSets.begin(), sortedSets.end(), EnabledAndKeyCompareFunctor());
}
}
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;
}
QStringList PictureLoader::md5Blacklist = QStringList()
<< "db0c48db407a907c16ade38de048a441"; // card back returned by gatherer when card is not found
PictureLoader::PictureLoader()
: QObject(0),
downloadRunning(false), loadQueueRunning(false)
{
picsPath = settingsCache->getPicsPath();
picDownload = settingsCache->getPicDownload();
connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
networkManager = new QNetworkAccessManager(this);
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
pictureLoaderThread = new QThread;
pictureLoaderThread->start(QThread::LowPriority);
moveToThread(pictureLoaderThread);
}
PictureLoader::~PictureLoader()
{
pictureLoaderThread->deleteLater();
}
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 << ")";
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 << ")";
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 << ")";
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)
{
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;
picUrl = muid ? settingsCache->getPicUrl() : settingsCache->getPicUrlFallback();
picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName()));
picUrl.replace("!name_lower!", QUrl::toPercentEncoding(card->getCorrectedName().toLower()));
picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid)));
if (set)
{
picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName()));
picUrl.replace("!setcode_lower!", QUrl::toPercentEncoding(set->getShortName().toLower()));
picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName()));
picUrl.replace("!setname_lower!", QUrl::toPercentEncoding(set->getLongName().toLower()));
}
if (
picUrl.contains("!name!") ||
picUrl.contains("!name_lower!") ||
picUrl.contains("!setcode!") ||
picUrl.contains("!setcode_lower!") ||
picUrl.contains("!setname!") ||
picUrl.contains("!setname_lower!") ||
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;
imageLoaded(cardBeingDownloaded.getCard(), QImage());
}
}
void PictureLoader::picDownloadFinished(QNetworkReply *reply)
{
if (reply->error()) {
qDebug() << "Download failed:" << reply->errorString();
}
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (statusCode == 301 || statusCode == 302) {
QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
QNetworkRequest req(redirectUrl);
qDebug() << "following redirect:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
networkManager->get(req);
return;
}
const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader
// check if the image is blacklisted
QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
if(md5Blacklist.contains(md5sum))
{
qDebug() << "Picture downloaded, but blacklisted (" << md5sum << "), will consider it as not found";
picDownloadFailed();
reply->deleteLater();
startNextPicDownload();
return;
}
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();
}
imageLoaded(cardBeingDownloaded.getCard(), testImage);
} else {
picDownloadFailed();
}
reply->deleteLater();
startNextPicDownload();
}
void PictureLoader::enqueueImageLoad(CardInfo *card)
{
QMutexLocker locker(&mutex);
// avoid queueing the same card more than once
if(card == 0 || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard())
return;
foreach(PictureToLoad pic, loadQueue)
{
if(pic.getCard() == card)
return;
}
loadQueue.append(PictureToLoad(card));
emit startLoadQueue();
}
void PictureLoader::picDownloadChanged()
{
QMutexLocker locker(&mutex);
picDownload = settingsCache->getPicDownload();
QPixmapCache::clear();
}
void PictureLoader::picsPathChanged()
{
QMutexLocker locker(&mutex);
picsPath = settingsCache->getPicsPath();
QPixmapCache::clear();
}
void PictureLoader::getPixmap(QPixmap &pixmap, CardInfo *card, QSize size)
{
QPixmap bigPixmap;
if(card)
{
// search for an exact size copy of the picure in cache
QString key = card->getPixmapCacheKey();
QString sizekey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height());
if(QPixmapCache::find(sizekey, &pixmap))
return;
// load the image and create a copy of the correct size
if(QPixmapCache::find(key, &bigPixmap))
{
pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert(sizekey, pixmap);
return;
}
}
// load a temporary back picture
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height());
if(!QPixmapCache::find(backCacheKey, &pixmap))
{
pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert(backCacheKey, pixmap);
}
if(card)
{
// add the card to the load queue
getInstance().enqueueImageLoad(card);
}
}
void PictureLoader::imageLoaded(CardInfo *card, const QImage &image)
{
if(image.isNull())
return;
if(card->getUpsideDownArt())
{
QImage mirrorImage = image.mirrored(true, true);
QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(mirrorImage));
} else {
QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(image));
}
card->emitPixmapUpdated();
}
void PictureLoader::clearPixmapCache(CardInfo *card)
{
//qDebug() << "Deleting pixmap for" << name;
if(card)
QPixmapCache::remove(card->getPixmapCacheKey());
}
void PictureLoader::clearPixmapCache()
{
QPixmapCache::clear();
}
void PictureLoader::cacheCardPixmaps(QList<CardInfo *> cards)
{
QPixmap tmp;
// never cache more than 300 cards at once for a single deck
int max = qMin(cards.size(), 300);
for (int i = 0; i < max; ++i)
{
CardInfo * card = cards.at(i);
if(!card)
continue;
QString key = card->getPixmapCacheKey();
if(QPixmapCache::find(key, &tmp))
continue;
getInstance().enqueueImageLoad(card);
}
}

View file

@ -0,0 +1,78 @@
#ifndef PICTURELOADER_H
#define PICTURELOADER_H
#include <QMap>
#include <QList>
#include <QNetworkRequest>
#include <QMutex>
class CardInfo;
class CardSet;
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class PictureToLoad {
private:
class EnabledAndKeyCompareFunctor;
CardInfo *card;
QList<CardSet *> sortedSets;
int setIndex;
bool hq;
public:
PictureToLoad(CardInfo *_card = 0);
CardInfo *getCard() const { return card; }
CardSet *getCurrentSet() const;
QString getSetName() const;
bool nextSet();
};
class PictureLoader : public QObject {
Q_OBJECT
public:
static PictureLoader& getInstance()
{
static PictureLoader instance;
return instance;
}
private:
PictureLoader();
~PictureLoader();
// Don't implement
PictureLoader(PictureLoader const&);
void operator=(PictureLoader const&);
static QStringList md5Blacklist;
QThread *pictureLoaderThread;
QString picsPath;
QList<PictureToLoad> loadQueue;
QMutex mutex;
QNetworkAccessManager *networkManager;
QList<PictureToLoad> cardsToDownload;
PictureToLoad cardBeingLoaded;
PictureToLoad cardBeingDownloaded;
bool picDownload, downloadRunning, loadQueueRunning;
void startNextPicDownload();
void imageLoaded(CardInfo *card, const QImage &image);
QString getPicUrl();
public:
void enqueueImageLoad(CardInfo *card);
static void getPixmap(QPixmap &pixmap, CardInfo *card, QSize size);
static void clearPixmapCache(CardInfo *card);
static void clearPixmapCache();
static void cacheCardPixmaps(QList<CardInfo *> cards);
private slots:
void picDownloadFinished(QNetworkReply *reply);
void picDownloadFailed();
void picDownloadChanged();
void picsPathChanged();
public slots:
void processLoadQueue();
signals:
void startLoadQueue();
};
#endif

View file

@ -165,12 +165,9 @@ SettingsCache::SettingsCache()
pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
picDownload = settings->value("personal/picturedownload", true).toBool();
picDownloadHq = settings->value("personal/picturedownloadhq", true).toBool();
picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString();
picUrlHq = settings->value("personal/picUrlHq", PIC_URL_HQ_DEFAULT).toString();
picUrlFallback = settings->value("personal/picUrlFallback", PIC_URL_FALLBACK).toString();
picUrlHqFallback = settings->value("personal/picUrlHqFallback", PIC_URL_HQ_FALLBACK).toString();
mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray();
notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool();
@ -325,37 +322,18 @@ void SettingsCache::setPicDownload(int _picDownload)
emit picDownloadChanged();
}
void SettingsCache::setPicDownloadHq(int _picDownloadHq)
{
picDownloadHq = _picDownloadHq;
settings->setValue("personal/picturedownloadhq", picDownloadHq);
emit picDownloadHqChanged();
}
void SettingsCache::setPicUrl(const QString &_picUrl)
{
picUrl = _picUrl;
settings->setValue("personal/picUrl", picUrl);
}
void SettingsCache::setPicUrlHq(const QString &_picUrlHq)
{
picUrlHq = _picUrlHq;
settings->setValue("personal/picUrlHq", picUrlHq);
}
void SettingsCache::setPicUrlFallback(const QString &_picUrlFallback)
{
picUrlFallback = _picUrlFallback;
settings->setValue("personal/picUrlFallback", picUrlFallback);
}
void SettingsCache::setPicUrlHqFallback(const QString &_picUrlHqFallback)
{
picUrlHqFallback = _picUrlHqFallback;
settings->setValue("personal/picUrlHqFallback", picUrlHqFallback);
}
void SettingsCache::setNotificationsEnabled(int _notificationsEnabled)
{
notificationsEnabled = _notificationsEnabled;

View file

@ -14,8 +14,6 @@
// the falbacks are used for cards without a muid
#define PIC_URL_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"
#define PIC_URL_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"
#define PIC_URL_HQ_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"
#define PIC_URL_HQ_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"
// size should be a multiple of 64
#define PIXMAPCACHE_SIZE_DEFAULT 2047
#define PIXMAPCACHE_SIZE_MIN 64
@ -32,7 +30,6 @@ signals:
void tokenDatabasePathChanged();
void themeChanged();
void picDownloadChanged();
void picDownloadHqChanged();
void displayCardNamesChanged();
void horizontalHandChanged();
void handJustificationChanged();
@ -60,7 +57,6 @@ private:
QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates;
bool picDownload;
bool picDownloadHq;
bool notificationsEnabled;
bool spectatorNotificationsEnabled;
bool doubleClickToPlay;
@ -87,9 +83,7 @@ private:
bool ignoreUnregisteredUsers;
bool ignoreUnregisteredUserMessages;
QString picUrl;
QString picUrlHq;
QString picUrlFallback;
QString picUrlHqFallback;
QString clientID;
int pixmapCacheSize;
bool scaleCards;
@ -127,7 +121,6 @@ public:
QString getChatMentionColor() const { return chatMentionColor; }
QString getChatHighlightColor() const { return chatHighlightColor; }
bool getPicDownload() const { return picDownload; }
bool getPicDownloadHq() const { return picDownloadHq; }
bool getNotificationsEnabled() const { return notificationsEnabled; }
bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; }
bool getNotifyAboutUpdates() const { return notifyAboutUpdates; }
@ -160,9 +153,7 @@ public:
bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; }
bool getIgnoreUnregisteredUserMessages() const { return ignoreUnregisteredUserMessages; }
QString getPicUrl() const { return picUrl; }
QString getPicUrlHq() const { return picUrlHq; }
QString getPicUrlFallback() const { return picUrlFallback; }
QString getPicUrlHqFallback() const { return picUrlHqFallback; }
int getPixmapCacheSize() const { return pixmapCacheSize; }
bool getScaleCards() const { return scaleCards; }
bool getShowMessagePopup() const { return showMessagePopups; }
@ -204,7 +195,6 @@ public slots:
void setChatMentionColor(const QString &_chatMentionColor);
void setChatHighlightColor(const QString &_chatHighlightColor);
void setPicDownload(int _picDownload);
void setPicDownloadHq(int _picDownloadHq);
void setNotificationsEnabled(int _notificationsEnabled);
void setSpectatorNotificationsEnabled(int _spectatorNotificationsEnabled);
void setDoubleClickToPlay(int _doubleClickToPlay);
@ -231,9 +221,7 @@ public slots:
void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages);
void setPicUrl(const QString &_picUrl);
void setPicUrlHq(const QString &_picUrlHq);
void setPicUrlFallback(const QString &_picUrlFallback);
void setPicUrlHqFallback(const QString &_picUrlHqFallback);
void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardScaling(const int _scaleCards);
void setShowMessagePopups(const int _showMessagePopups);

View file

@ -26,6 +26,7 @@
#include "tab_deck_editor.h"
#include "window_sets.h"
#include "carddatabase.h"
#include "pictureloader.h"
#include "carddatabasemodel.h"
#include "decklistmodel.h"
#include "cardinfowidget.h"
@ -1058,7 +1059,7 @@ void TabDeckEditor::setDeck(DeckLoader *_deck)
deckView->expandAll();
setModified(false);
db->cacheCardPixmaps(deckModel->getDeckList()->getCardList());
PictureLoader::cacheCardPixmaps(db->getCards(deckModel->getDeckList()->getCardList()));
deckView->expandAll();
setModified(false);
}

View file

@ -32,6 +32,7 @@
#include "main.h"
#include "settingscache.h"
#include "carddatabase.h"
#include "pictureloader.h"
#include "replay_timeline_widget.h"
#include "lineeditcompleter.h"
@ -242,7 +243,7 @@ void DeckViewContainer::deckSelectFinished(const Response &r)
{
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
DeckLoader newDeck(QString::fromStdString(resp.deck()));
db->cacheCardPixmaps(newDeck.getCardList());
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
setDeck(newDeck);
}
@ -1044,7 +1045,7 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event, int /*e
DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
if (playerInfo.has_deck_list()) {
DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list()));
db->cacheCardPixmaps(newDeck.getCardList());
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
deckViewContainer->setDeck(newDeck);
player->setDeck(newDeck);
}

View file

@ -1,6 +1,8 @@
#include "window_sets.h"
#include "setsmodel.h"
#include "pictureloader.h"
#include "main.h"
#include <QTreeView>
#include <QGridLayout>
#include <QHeaderView>
@ -123,7 +125,7 @@ WndSets::~WndSets()
void WndSets::actSave()
{
model->save(db);
db->clearPixmapCache();
PictureLoader::clearPixmapCache();
QMessageBox::information(this, tr("Success"), tr("The sets database has been saved successfully."));
close();
}

View file

@ -29,7 +29,6 @@
<xs:extension base="xs:string">
<xs:attribute type="xs:int" name="muId" use="optional"/>
<xs:attribute type="xs:anyURI" name="picUrl" use="optional"/>
<xs:attribute type="xs:anyURI" name="picUrlHq" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

View file

@ -12,6 +12,7 @@ SET(oracle_SOURCES
src/oraclewizard.cpp
src/oracleimporter.cpp
../cockatrice/src/carddatabase.cpp
../cockatrice/src/pictureloader.cpp
../cockatrice/src/settingscache.cpp
../cockatrice/src/shortcutssettings.cpp
../cockatrice/src/settings/carddatabasesettings.cpp

View file

@ -2,6 +2,7 @@
#define ORACLEIMPORTER_H
#include <QMap>
#include <QVariant>
#include <carddatabase.h>