From f6c7f3355fd47da1fe03587eb0681e8040ecb34b Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sat, 22 Aug 2015 17:22:08 +0200 Subject: [PATCH] Reimplemented PictureLoader as a singleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removed remaining picture handling from card database and cardinfo * removed the “noCard object” hack --- cockatrice/CMakeLists.txt | 1 + cockatrice/src/abstractcarditem.cpp | 12 +- cockatrice/src/carddatabase.cpp | 565 ++-------------------------- cockatrice/src/carddatabase.h | 87 +---- cockatrice/src/cardframe.cpp | 3 +- cockatrice/src/cardinfopicture.cpp | 8 +- cockatrice/src/cardinfotext.cpp | 24 +- cockatrice/src/cardinfowidget.cpp | 5 +- cockatrice/src/dlg_settings.cpp | 63 +++- cockatrice/src/dlg_settings.h | 13 +- cockatrice/src/pictureloader.cpp | 450 ++++++++++++++++++++++ cockatrice/src/pictureloader.h | 78 ++++ cockatrice/src/settingscache.cpp | 22 -- cockatrice/src/settingscache.h | 12 - cockatrice/src/tab_deck_editor.cpp | 3 +- cockatrice/src/tab_game.cpp | 5 +- cockatrice/src/window_sets.cpp | 4 +- doc/cards.xsd | 1 - oracle/CMakeLists.txt | 1 + oracle/src/oracleimporter.h | 1 + 20 files changed, 654 insertions(+), 704 deletions(-) create mode 100644 cockatrice/src/pictureloader.cpp create mode 100644 cockatrice/src/pictureloader.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index d9a4800c..aaf68691 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -95,6 +95,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 diff --git a/cockatrice/src/abstractcarditem.cpp b/cockatrice/src/abstractcarditem.cpp index dcd68fa1..a177eabe 100644 --- a/cockatrice/src/abstractcarditem.cpp +++ b/cockatrice/src/abstractcarditem.cpp @@ -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(); } diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index 3e5a1489..ae85bfc1 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -1,20 +1,16 @@ #include "carddatabase.h" +#include "pictureloader.h" #include "settingscache.h" #include "thememanager.h" #include + +#include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include 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 picsPaths = QList() << _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 CardDatabase::getCards(const QStringList &cardNames) +{ + QList 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 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 cardIterator(cards); - while (cardIterator.hasNext()) - cardIterator.next().value()->clearPixmapCacheMiss(); - } -} - -void CardDatabase::picDownloadHqChanged() -{ - pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq()); - if (settingsCache->getPicDownloadHq()) { - QHashIterator 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(); diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h index df3995da..e60c594b 100644 --- a/cockatrice/src/carddatabase.h +++ b/cockatrice/src/carddatabase.h @@ -8,17 +8,9 @@ #include #include #include -#include -#include -#include -#include -#include class CardDatabase; class CardInfo; -class QNetworkAccessManager; -class QNetworkReply; -class QNetworkRequest; typedef QMap QStringMap; @@ -55,9 +47,7 @@ public: class SetList : public QList { 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 loadQueue; - QMutex mutex; - QNetworkAccessManager *networkManager; - QList 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,14 @@ 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); /** * 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 +171,6 @@ protected: */ SetNameMap sets; - CardInfo *noCard; - - QThread *pictureLoaderThread; - PictureLoader *pictureLoader; LoadStatus loadStatus; bool detectedFirstRun; private: @@ -258,13 +188,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 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 getCardList() const { return cards.values(); } @@ -275,20 +206,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: diff --git a/cockatrice/src/cardframe.cpp b/cockatrice/src/cardframe.cpp index d91a8ecf..c0b92041 100644 --- a/cockatrice/src/cardframe.cpp +++ b/cockatrice/src/cardframe.cpp @@ -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); } diff --git a/cockatrice/src/cardinfopicture.cpp b/cockatrice/src/cardinfopicture.cpp index 9bce273e..10ce0978 100644 --- a/cockatrice/src/cardinfopicture.cpp +++ b/cockatrice/src/cardinfopicture.cpp @@ -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 *) diff --git a/cockatrice/src/cardinfotext.cpp b/cockatrice/src/cardinfotext.cpp index ace2a95f..ce6524b8 100644 --- a/cockatrice/src/cardinfotext.cpp +++ b/cockatrice/src/cardinfotext.cpp @@ -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() diff --git a/cockatrice/src/cardinfowidget.cpp b/cockatrice/src/cardinfowidget.cpp index 8ee5663c..807221c6 100644 --- a/cockatrice/src/cardinfowidget.cpp +++ b/cockatrice/src/cardinfowidget.cpp @@ -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); } diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 8f84001f..6c3b982a 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -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("%2").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ"))); + defaultUrlLabel.setText(tr("Primary download URL:")); + fallbackUrlLabel.setText(tr("Fallback download URL:")); + urlLinkLabel.setText(QString("%2").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() diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index 7b746564..52d3c9ba 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -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 { diff --git a/cockatrice/src/pictureloader.cpp b/cockatrice/src/pictureloader.cpp new file mode 100644 index 00000000..5bf85041 --- /dev/null +++ b/cockatrice/src/pictureloader.cpp @@ -0,0 +1,450 @@ +#include "pictureloader.h" +#include "carddatabase.h" +#include "main.h" +#include "settingscache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 picsPaths = QList() << 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)); + } + + emit card->pixmapUpdated(); +} + +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 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); + } +} diff --git a/cockatrice/src/pictureloader.h b/cockatrice/src/pictureloader.h new file mode 100644 index 00000000..7c21312d --- /dev/null +++ b/cockatrice/src/pictureloader.h @@ -0,0 +1,78 @@ +#ifndef PICTURELOADER_H +#define PICTURELOADER_H + +#include +#include +#include +#include + +class CardInfo; +class CardSet; +class QNetworkAccessManager; +class QNetworkReply; +class QThread; + +class PictureToLoad { +private: + class EnabledAndKeyCompareFunctor; + + CardInfo *card; + QList 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 loadQueue; + QMutex mutex; + QNetworkAccessManager *networkManager; + QList 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 cards); +private slots: + void picDownloadFinished(QNetworkReply *reply); + void picDownloadFailed(); + + void picDownloadChanged(); + void picsPathChanged(); +public slots: + void processLoadQueue(); +signals: + void startLoadQueue(); +}; + +#endif diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index d5eb062d..738515cd 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -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; diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index 722ccac1..036f6966 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -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); diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index d750e83b..1e27434b 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -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); } diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 86b6308d..be3c8cf6 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -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); } diff --git a/cockatrice/src/window_sets.cpp b/cockatrice/src/window_sets.cpp index b647e0a9..5e0a49e2 100644 --- a/cockatrice/src/window_sets.cpp +++ b/cockatrice/src/window_sets.cpp @@ -1,6 +1,8 @@ #include "window_sets.h" #include "setsmodel.h" +#include "pictureloader.h" #include "main.h" + #include #include #include @@ -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(); } diff --git a/doc/cards.xsd b/doc/cards.xsd index d10032a7..e5b33e35 100644 --- a/doc/cards.xsd +++ b/doc/cards.xsd @@ -29,7 +29,6 @@ - diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 05c8a2e4..c7c7cb28 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -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 diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index 888eabaf..17092ea5 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -2,6 +2,7 @@ #define ORACLEIMPORTER_H #include +#include #include