Several download URLs have arrived! (#3494)

* Cockatrice Picture loader uses better defined URLs now
URLs are defined on the Card Management tab
Instead of Primary/Backup, you can now define a list of URLs
List of URLs can be drag/dropped for priority ordering
Oracle now uses scryfallId > mtgjsonUUID for !uuid!

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* Simplify to QStringList and remove metacall

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* fix issues brought up by Dae. Also fix how the defaults load to account for first time users.

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* clangify

* Fix save settings on row moved (#3495)

* merge model fix, and reclangify

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* Sources > Resources

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>
This commit is contained in:
Zach H 2019-01-08 15:18:06 -05:00 committed by GitHub
parent 4eda7cda9e
commit b0e643ecc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 354 additions and 230 deletions

View file

@ -114,6 +114,7 @@ SET(cockatrice_SOURCES
src/settings/messagesettings.cpp
src/settings/gamefilterssettings.cpp
src/settings/layoutssettings.cpp
src/settings/downloadsettings.cpp
src/update_downloader.cpp
src/logger.cpp
src/releasechannel.cpp

View file

@ -45,8 +45,6 @@ GeneralSettingsPage::GeneralSettingsPage()
languageBox.setCurrentIndex(i);
}
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
// updates
QList<ReleaseChannel *> channels = settingsCache->getUpdateReleaseChannels();
foreach (ReleaseChannel *chan, channels) {
@ -64,27 +62,15 @@ GeneralSettingsPage::GeneralSettingsPage()
pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize());
pixmapCacheEdit.setSuffix(" MB");
defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl());
fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback());
showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup());
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(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), settingsCache,
SLOT(setUpdateReleaseChannel(int)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
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()));
connect(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool)));
setEnabledStatus(settingsCache->getPicDownload());
auto *personalGrid = new QGridLayout;
personalGrid->addWidget(&languageLabel, 0, 0);
personalGrid->addWidget(&languageBox, 0, 1);
@ -94,18 +80,6 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&pixmapCacheEdit, 2, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 3, 0);
personalGrid->addWidget(&showTipsOnStartup, 4, 0);
personalGrid->addWidget(&picDownloadCheckBox, 5, 0);
personalGrid->addWidget(&urlLinkLabel, 5, 1);
personalGrid->addWidget(&defaultUrlLabel, 6, 0, 1, 1);
personalGrid->addWidget(defaultUrlEdit, 6, 1, 1, 1);
personalGrid->addWidget(&defaultUrlRestoreButton, 6, 2, 1, 1);
personalGrid->addWidget(&fallbackUrlLabel, 7, 0, 1, 1);
personalGrid->addWidget(fallbackUrlEdit, 7, 1, 1, 1);
personalGrid->addWidget(&fallbackUrlRestoreButton, 7, 2, 1, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 8, 1);
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
personalGroupBox = new QGroupBox;
personalGroupBox->setLayout(personalGrid);
@ -194,20 +168,6 @@ QString GeneralSettingsPage::languageName(const QString &qmFile)
return translator.translate("i18n", DEFAULT_LANG_NAME);
}
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,30 +198,6 @@ void GeneralSettingsPage::picsPathButtonClicked()
settingsCache->setPicsPath(path);
}
void GeneralSettingsPage::clearDownloadedPicsButtonClicked()
{
QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/";
QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
bool outerSuccessRemove = true;
for (int i = 0; i < dirs.length(); i++) {
QString currentPath = picsPath + dirs.at(i) + "/";
QStringList files = QDir(currentPath).entryList(QDir::Files);
bool innerSuccessRemove = true;
for (int j = 0; j < files.length(); j++)
if (!QDir(currentPath).remove(files.at(j))) {
qDebug() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8();
outerSuccessRemove = false;
innerSuccessRemove = false;
}
if (innerSuccessRemove)
QDir(picsPath).rmdir(dirs.at(i));
}
if (outerSuccessRemove)
QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
else
QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
}
void GeneralSettingsPage::cardDatabasePathButtonClicked()
{
QString path = QFileDialog::getOpenFileName(this, tr("Choose path"));
@ -291,7 +227,6 @@ void GeneralSettingsPage::retranslateUi()
{
personalGroupBox->setTitle(tr("Personal settings"));
languageLabel.setText(tr("Language:"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
if (settingsCache->getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
@ -305,26 +240,11 @@ void GeneralSettingsPage::retranslateUi()
cardDatabasePathLabel.setText(tr("Card database:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
pixmapCacheLabel.setText(tr("Picture cache size:"));
defaultUrlLabel.setText(tr("Primary download URL:"));
fallbackUrlLabel.setText(tr("Fallback download URL:"));
urlLinkLabel.setText(
QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to set a custom picture url")));
clearDownloadedPicsButton.setText(tr("Reset/clear downloaded pictures"));
updateReleaseChannelLabel.setText(tr("Update channel"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
defaultUrlRestoreButton.setText(tr("Reset"));
fallbackUrlRestoreButton.setText(tr("Reset"));
showTipsOnStartup.setText(tr("Show tips on startup"));
}
void GeneralSettingsPage::setEnabledStatus(bool status)
{
defaultUrlEdit->setEnabled(status);
fallbackUrlEdit->setEnabled(status);
defaultUrlRestoreButton.setEnabled(status);
fallbackUrlRestoreButton.setEnabled(status);
}
AppearanceSettingsPage::AppearanceSettingsPage()
{
QString themeName = settingsCache->getThemeName();
@ -498,6 +418,15 @@ void UserInterfaceSettingsPage::retranslateUi()
DeckEditorSettingsPage::DeckEditorSettingsPage()
{
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&resetDownloadURLs, SIGNAL(clicked()), this, SLOT(resetDownloadedURLsButtonClicked()));
auto *lpGeneralGrid = new QGridLayout;
auto *lpSpoilerGrid = new QGridLayout;
@ -515,9 +444,46 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
// Update the GUI depending on if the box is ticked or not
setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked());
// Create the layout
lpGeneralGrid->addWidget(&mcGeneralMessageLabel, 0, 0);
urlList = new QListWidget;
urlList->setSelectionMode(QAbstractItemView::SingleSelection);
urlList->setAlternatingRowColors(true);
urlList->setDragEnabled(true);
urlList->setDragDropMode(QAbstractItemView::InternalMove);
connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this,
SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int)));
for (int i = 0; i < settingsCache->downloads().getCount(); i++)
urlList->addItem(settingsCache->downloads().getDownloadUrlAt(i));
auto aAdd = new QAction(this);
aAdd->setIcon(QPixmap("theme:icons/increment"));
connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL()));
auto aEdit = new QAction(this);
aEdit->setIcon(QPixmap("theme:icons/pencil"));
connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL()));
auto aRemove = new QAction(this);
aRemove->setIcon(QPixmap("theme:icons/decrement"));
connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL()));
auto *messageToolBar = new QToolBar;
messageToolBar->setOrientation(Qt::Vertical);
messageToolBar->addAction(aAdd);
messageToolBar->addAction(aRemove);
messageToolBar->addAction(aEdit);
messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
auto *messageListLayout = new QHBoxLayout;
messageListLayout->addWidget(messageToolBar);
messageListLayout->addWidget(urlList);
// Top Layout
lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0);
lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1);
lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2);
lpGeneralGrid->addWidget(&urlLinkLabel, 2, 0);
lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 2, 1);
// Spoiler Layout
lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0);
lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0);
lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1);
@ -543,6 +509,94 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
setLayout(lpMainLayout);
}
void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked()
{
settingsCache->downloads().clear();
urlList->clear();
urlList->addItems(settingsCache->downloads().getAllURLs());
QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset."));
}
void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked()
{
QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/";
QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
bool outerSuccessRemove = true;
for (const auto &dir : dirs) {
QString currentPath = picsPath + dir + "/";
QStringList files = QDir(currentPath).entryList(QDir::Files);
bool innerSuccessRemove = true;
for (int j = 0; j < files.length(); j++) {
if (!QDir(currentPath).remove(files.at(j))) {
qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8();
outerSuccessRemove = false;
innerSuccessRemove = false;
}
qInfo() << "Removed " << currentPath << files.at(j);
}
if (innerSuccessRemove) {
bool success = QDir(picsPath).rmdir(dir);
if (!success) {
qInfo() << "Failed to remove inner directory" << picsPath;
} else {
qInfo() << "Removed" << currentPath;
}
}
}
if (outerSuccessRemove) {
QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
} else {
QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
}
}
void DeckEditorSettingsPage::actAddURL()
{
bool ok;
QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok);
if (ok) {
urlList->addItem(msg);
storeSettings();
}
}
void DeckEditorSettingsPage::actRemoveURL()
{
if (urlList->currentItem() != nullptr) {
delete urlList->takeItem(urlList->currentRow());
storeSettings();
}
}
void DeckEditorSettingsPage::actEditURL()
{
if (urlList->currentItem()) {
QString oldText = urlList->currentItem()->text();
bool ok;
QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok);
if (ok) {
urlList->currentItem()->setText(msg);
storeSettings();
}
}
}
void DeckEditorSettingsPage::storeSettings()
{
qInfo() << "URL Priority Reset";
settingsCache->downloads().clear();
for (int i = 0; i < urlList->count(); i++) {
qInfo() << "Priority" << i << ":" << urlList->item(i)->text();
settingsCache->downloads().setDownloadUrlAt(i, urlList->item(i)->text());
}
}
void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int)
{
storeSettings();
}
void DeckEditorSettingsPage::updateSpoilers()
{
// Disable the button so the user can only press it once at a time
@ -603,14 +657,18 @@ void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput)
void DeckEditorSettingsPage::retranslateUi()
{
mpGeneralGroupBox->setTitle(tr("URL Download Priority"));
mpSpoilerGroupBox->setTitle(tr("Spoilers"));
mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically"));
mcSpoilerSaveLabel.setText(tr("Spoiler Location:"));
mcGeneralMessageLabel.setText(tr("Hey, something's here finally!"));
lastUpdatedLabel.setText(tr("Last Updated") + ": " + getLastUpdateTime());
infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" +
tr("Press the button to manually update without relaunching") + "\n\n" +
tr("Do not close settings until manual update complete"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
urlLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL")));
clearDownloadedPicsButton.setText(tr("Delete Downloaded Images"));
resetDownloadURLs.setText(tr("Reset Download URLs"));
}
MessagesSettingsPage::MessagesSettingsPage()
@ -689,12 +747,16 @@ MessagesSettingsPage::MessagesSettingsPage()
aAdd = new QAction(this);
aAdd->setIcon(QPixmap("theme:icons/increment"));
aAdd->setStatusTip(tr("Add New URL"));
connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd()));
aEdit = new QAction(this);
aEdit->setIcon(QPixmap("theme:icons/pencil"));
aEdit->setStatusTip(tr("Edit URL"));
connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit()));
aRemove = new QAction(this);
aRemove->setIcon(QPixmap("theme:icons/decrement"));
aRemove->setStatusTip(tr("Remove URL"));
connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove()));
auto *messageToolBar = new QToolBar;
@ -798,7 +860,7 @@ void MessagesSettingsPage::actEdit()
void MessagesSettingsPage::actRemove()
{
if (messageList->currentItem()) {
if (messageList->currentItem() != nullptr) {
delete messageList->takeItem(messageList->currentRow());
storeSettings();
}
@ -1000,7 +1062,7 @@ void DlgSettings::setTab(int index)
void DlgSettings::updateLanguage()
{
qApp->removeTranslator(translator);
qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
installNewTranslator();
}
@ -1094,7 +1156,7 @@ void DlgSettings::retranslateUi()
generalButton->setText(tr("General"));
appearanceButton->setText(tr("Appearance"));
userInterfaceButton->setText(tr("User Interface"));
deckEditorButton->setText(tr("Deck Editor"));
deckEditorButton->setText(tr("Card Sources"));
messagesButton->setText(tr("Chat"));
soundButton->setText(tr("Sound"));
shortcutsButton->setText(tr("Shortcuts"));

View file

@ -43,13 +43,9 @@ private slots:
void deckPathButtonClicked();
void replaysPathButtonClicked();
void picsPathButtonClicked();
void clearDownloadedPicsButtonClicked();
void cardDatabasePathButtonClicked();
void tokenDatabasePathButtonClicked();
void languageBoxChanged(int index);
void setEnabledStatus(bool);
void defaultUrlRestoreButtonClicked();
void fallbackUrlRestoreButtonClicked();
private:
QStringList findQmFiles();
@ -59,13 +55,10 @@ private:
QLineEdit *picsPathEdit;
QLineEdit *cardDatabasePathEdit;
QLineEdit *tokenDatabasePathEdit;
QLineEdit *defaultUrlEdit;
QLineEdit *fallbackUrlEdit;
QSpinBox pixmapCacheEdit;
QGroupBox *personalGroupBox;
QGroupBox *pathsGroupBox;
QComboBox languageBox;
QCheckBox picDownloadCheckBox;
QCheckBox updateNotificationCheckBox;
QComboBox updateReleaseChannelBox;
QLabel languageLabel;
@ -75,13 +68,7 @@ private:
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
QLabel defaultUrlLabel;
QLabel fallbackUrlLabel;
QLabel urlLinkLabel;
QLabel updateReleaseChannelLabel;
QPushButton clearDownloadedPicsButton;
QPushButton defaultUrlRestoreButton;
QPushButton fallbackUrlRestoreButton;
QCheckBox showTipsOnStartup;
};
@ -143,19 +130,30 @@ public:
QString getLastUpdateTime();
private slots:
void storeSettings();
void urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int);
void setSpoilersEnabled(bool);
void spoilerPathButtonClicked();
void updateSpoilers();
void unlockSettings();
void actAddURL();
void actRemoveURL();
void actEditURL();
void clearDownloadedPicsButtonClicked();
void resetDownloadedURLsButtonClicked();
private:
QPushButton clearDownloadedPicsButton;
QPushButton resetDownloadURLs;
QLabel urlLinkLabel;
QCheckBox picDownloadCheckBox;
QListWidget *urlList;
QCheckBox mcDownloadSpoilersCheckBox;
QLabel msDownloadSpoilersLabel;
QGroupBox *mpGeneralGroupBox;
QGroupBox *mpSpoilerGroupBox;
QLineEdit *mpSpoilerSavePathLineEdit;
QLabel mcSpoilerSaveLabel;
QLabel mcGeneralMessageLabel;
QLabel lastUpdatedLabel;
QLabel infoOnSpoilersLabel;
QPushButton *mpSpoilerPathButton;

View file

@ -25,35 +25,9 @@
// never cache more than 300 cards at once for a single deck
#define CACHED_CARD_PER_DECK_MAX 300
// Other URLs we can use (TODO: Make this less messy)
#define GATHERER_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"
#define GATHERER_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"
class PictureToLoad::SetDownloadPriorityComparator
{
public:
/*
* Returns true if a has higher download priority than b
* Enabled sets have priority over disabled sets
* Both groups follows the user-defined order
*/
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
{
if (a->getEnabled()) {
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
} else {
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
}
}
};
PictureToLoad::PictureToLoad(CardInfoPtr _card) : card(std::move(_card))
{
/* #2479 will expand this into a list of Urls */
urlTemplates.append(settingsCache->getPicUrl());
urlTemplates.append(settingsCache->getPicUrlFallback());
urlTemplates.append(GATHERER_DEFAULT);
urlTemplates.append(GATHERER_FALLBACK);
urlTemplates = settingsCache->downloads().getAllURLs();
if (card) {
sortedSets = card->getSets();
@ -78,7 +52,7 @@ void PictureToLoad::populateSetUrls()
}
}
foreach (QString urlTemplate, urlTemplates) {
for (const QString &urlTemplate : urlTemplates) {
QString transformedUrl = transformUrl(urlTemplate);
if (!transformedUrl.isEmpty()) {
@ -121,10 +95,8 @@ QString PictureToLoad::getSetName() const
}
}
QStringList PictureLoaderWorker::md5Blacklist = QStringList()
<< "db0c48db407a907c16ade38de048a441"; // card back returned
// by gatherer when
// card is not found
// Card back returned by gatherer when card is not found
QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441";
PictureLoaderWorker::PictureLoaderWorker() : QObject(nullptr), downloadRunning(false), loadQueueRunning(false)
{
@ -151,12 +123,12 @@ PictureLoaderWorker::~PictureLoaderWorker()
void PictureLoaderWorker::processLoadQueue()
{
if (loadQueueRunning)
if (loadQueueRunning) {
return;
}
loadQueueRunning = true;
forever
{
while (true) {
mutex.lock();
if (loadQueue.isEmpty()) {
mutex.unlock();
@ -169,18 +141,21 @@ void PictureLoaderWorker::processLoadQueue()
QString setName = cardBeingLoaded.getSetName();
QString cardName = cardBeingLoaded.getCard()->getName();
QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName();
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Trying to load picture";
if (cardImageExistsOnDisk(setName, correctedCardName))
if (cardImageExistsOnDisk(setName, correctedCardName)) {
continue;
}
if (picDownload) {
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName
<< "]: Picture not found on disk, trying to download";
cardsToDownload.append(cardBeingLoaded);
cardBeingLoaded.clear();
if (!downloadRunning)
if (!downloadRunning) {
startNextPicDownload();
}
} else {
if (cardBeingLoaded.nextSet()) {
qDebug() << "PictureLoader: [card: " << cardName << " set: " << setName
@ -227,22 +202,22 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre
// 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(); i++) {
imgReader.setFileName(picsPaths.at(i));
for (const auto &picsPath : picsPaths) {
imgReader.setFileName(picsPath);
if (imgReader.read(&image)) {
qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
<< "]: Picture found on disk.";
imageLoaded(cardBeingLoaded.getCard(), image);
return true;
}
imgReader.setFileName(picsPaths.at(i) + ".full");
imgReader.setFileName(picsPath + ".full");
if (imgReader.read(&image)) {
qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
<< "]: Picture.full found on disk.";
imageLoaded(cardBeingLoaded.getCard(), image);
return true;
}
imgReader.setFileName(picsPaths.at(i) + ".xlhq");
imgReader.setFileName(picsPath + ".xlhq");
if (imgReader.read(&image)) {
qDebug() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
<< "]: Picture.xlhq found on disk.";
@ -254,7 +229,7 @@ bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &corre
return false;
}
QString PictureToLoad::transformUrl(QString urlTemplate) const
QString PictureToLoad::transformUrl(const QString &urlTemplate) const
{
/* This function takes Url templates and substitutes actual card details
into the url. This is used for making Urls with follow a predictable format
@ -289,7 +264,7 @@ QString PictureToLoad::transformUrl(QString urlTemplate) const
transformMap["!setname_lower!"] = QString();
}
foreach (QString prop, transformMap.keys()) {
for (const QString &prop : transformMap.keys()) {
if (transformedUrl.contains(prop)) {
if (!transformMap[prop].isEmpty()) {
transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop]));
@ -380,8 +355,8 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
return;
}
const QByteArray &picData = reply->peek(reply->size()); // peek is used to keep the data in the buffer
// for use by QImageReader
// peek is used to keep the data in the buffer for use by QImageReader
const QByteArray &picData = reply->peek(reply->size());
if (imageIsBlackListed(picData)) {
qDebug() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
@ -403,8 +378,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
// prior to reading the
// QImageReader data
// into a QImage object, as that wipes the QImageReader buffer
if (extension == ".jpeg")
if (extension == ".jpeg") {
extension = ".jpg";
}
if (imgReader.read(&testImage)) {
QString setName = cardBeingDownloaded.getSetName();
@ -418,8 +394,9 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
QFile newPic(picsPath + "/downloadedPics/" + setName + "/" +
cardBeingDownloaded.getCard()->getCorrectedName() + extension);
if (!newPic.open(QIODevice::WriteOnly))
if (!newPic.open(QIODevice::WriteOnly)) {
return;
}
newPic.write(picData);
newPic.close();
}
@ -444,15 +421,16 @@ void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card)
QMutexLocker locker(&mutex);
// avoid queueing the same card more than once
if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard())
if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) {
return;
}
foreach (PictureToLoad pic, loadQueue) {
for (const PictureToLoad &pic : loadQueue) {
if (pic.getCard() == card)
return;
}
foreach (PictureToLoad pic, cardsToDownload) {
for (const PictureToLoad &pic : cardsToDownload) {
if (pic.getCard() == card)
return;
}
@ -474,7 +452,7 @@ void PictureLoaderWorker::picsPathChanged()
customPicsPath = settingsCache->getCustomPicsPath();
}
PictureLoader::PictureLoader() : QObject(0)
PictureLoader::PictureLoader() : QObject(nullptr)
{
worker = new PictureLoaderWorker;
connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
@ -501,20 +479,21 @@ void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size)
void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size)
{
if (card == nullptr)
if (card == nullptr) {
return;
}
// 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))
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
QPixmap bigPixmap;
if (QPixmapCache::find(key, &bigPixmap)) {
pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert(sizekey, pixmap);
QPixmapCache::insert(sizeKey, pixmap);
return;
}
@ -540,9 +519,10 @@ void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image)
void PictureLoader::clearPixmapCache(CardInfoPtr card)
{
if (card)
if (card) {
QPixmapCache::remove(card->getPixmapCacheKey());
}
}
void PictureLoader::clearPixmapCache()
{
@ -554,13 +534,15 @@ void PictureLoader::cacheCardPixmaps(QList<CardInfoPtr> cards)
QPixmap tmp;
int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX);
for (int i = 0; i < max; ++i) {
CardInfoPtr card = cards.at(i);
if (!card)
const CardInfoPtr &card = cards.at(i);
if (!card) {
continue;
}
QString key = card->getPixmapCacheKey();
if (QPixmapCache::find(key, &tmp))
if (QPixmapCache::find(key, &tmp)) {
continue;
}
getInstance().worker->enqueueImageLoad(card);
}

View file

@ -14,7 +14,23 @@ class QThread;
class PictureToLoad
{
private:
class SetDownloadPriorityComparator;
class SetDownloadPriorityComparator
{
public:
/*
* Returns true if a has higher download priority than b
* Enabled sets have priority over disabled sets
* Both groups follows the user-defined order
*/
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
{
if (a->getEnabled()) {
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
} else {
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
}
}
};
CardInfoPtr card;
QList<CardSetPtr> sortedSets;
@ -24,7 +40,8 @@ private:
CardSetPtr currentSet;
public:
PictureToLoad(CardInfoPtr _card = CardInfoPtr());
explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr());
CardInfoPtr getCard() const
{
return card;
@ -42,7 +59,7 @@ public:
return currentSet;
}
QString getSetName() const;
QString transformUrl(QString urlTemplate) const;
QString transformUrl(const QString &urlTemplate) const;
bool nextSet();
bool nextUrl();
void populateSetUrls();
@ -52,8 +69,8 @@ class PictureLoaderWorker : public QObject
{
Q_OBJECT
public:
PictureLoaderWorker();
~PictureLoaderWorker();
explicit PictureLoaderWorker();
~PictureLoaderWorker() override;
void enqueueImageLoad(CardInfoPtr card);
@ -70,8 +87,8 @@ private:
PictureToLoad cardBeingDownloaded;
bool picDownload, downloadRunning, loadQueueRunning;
void startNextPicDownload();
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardname);
bool imageIsBlackListed(const QByteArray &picData);
bool cardImageExistsOnDisk(QString &, QString &);
bool imageIsBlackListed(const QByteArray &);
private slots:
void picDownloadFinished(QNetworkReply *reply);
void picDownloadFailed();
@ -96,8 +113,8 @@ public:
}
private:
PictureLoader();
~PictureLoader();
explicit PictureLoader();
~PictureLoader() override;
// Singleton - Don't implement copy constructor and assign operator
PictureLoader(PictureLoader const &);
void operator=(PictureLoader const &);

View file

@ -0,0 +1,56 @@
#include "downloadsettings.h"
#include "settingsmanager.h"
DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr)
: SettingsManager(settingPath + "downloads.ini", parent)
{
downloadURLs = getValue("urls", "downloads").value<QStringList>();
}
void DownloadSettings::setDownloadUrlAt(int index, const QString &url)
{
downloadURLs.insert(index, url);
setValue(QVariant::fromValue(downloadURLs), "urls", "downloads");
}
/**
* If reset or first run, this method contains the default URLs we will populate
*/
QStringList DownloadSettings::getAllURLs()
{
// First run, these will be empty
if (downloadURLs.count() == 0) {
populateDefaultURLs();
}
return downloadURLs;
}
void DownloadSettings::populateDefaultURLs()
{
downloadURLs.clear();
downloadURLs.append("https://api.scryfall.com/cards/!uuid!?format=image");
downloadURLs.append("https://api.scryfall.com/cards/multiverse/!cardid!?format=image");
downloadURLs.append("http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card");
downloadURLs.append("http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card");
setValue(QVariant::fromValue(downloadURLs), "urls", "downloads");
}
QString DownloadSettings::getDownloadUrlAt(int index)
{
if (0 <= index && index < downloadURLs.size()) {
return downloadURLs[index];
}
return "";
}
int DownloadSettings::getCount()
{
return downloadURLs.size();
}
void DownloadSettings::clear()
{
downloadURLs.clear();
}

View file

@ -0,0 +1,28 @@
#ifndef COCKATRICE_DOWNLOADSETTINGS_H
#define COCKATRICE_DOWNLOADSETTINGS_H
#include "settingsmanager.h"
#include <QObject>
class DownloadSettings : public SettingsManager
{
Q_OBJECT
friend class SettingsCache;
public:
explicit DownloadSettings(const QString &, QObject *);
QStringList getAllURLs();
QString getDownloadUrlAt(int);
void setDownloadUrlAt(int, const QString &);
int getCount();
void clear();
private:
QStringList downloadURLs;
private:
void populateDefaultURLs();
};
#endif // COCKATRICE_DOWNLOADSETTINGS_H

View file

@ -164,6 +164,7 @@ SettingsCache::SettingsCache()
messageSettings = new MessageSettings(settingsPath, this);
gameFiltersSettings = new GameFiltersSettings(settingsPath, this);
layoutsSettings = new LayoutsSettings(settingsPath, this);
downloadSettings = new DownloadSettings(settingsPath, this);
if (!QFile(settingsPath + "global.ini").exists())
translateLegacySettings();
@ -220,9 +221,6 @@ SettingsCache::SettingsCache()
picDownload = settings->value("personal/picturedownload", true).toBool();
picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString();
picUrlFallback = settings->value("personal/picUrlFallback", PIC_URL_FALLBACK).toString();
mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray();
tokenDialogGeometry = settings->value("interface/token_dialog_geometry").toByteArray();
notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool();
@ -415,18 +413,6 @@ void SettingsCache::setPicDownload(int _picDownload)
emit picDownloadChanged();
}
void SettingsCache::setPicUrl(const QString &_picUrl)
{
picUrl = _picUrl;
settings->setValue("personal/picUrl", picUrl);
}
void SettingsCache::setPicUrlFallback(const QString &_picUrlFallback)
{
picUrlFallback = _picUrlFallback;
settings->setValue("personal/picUrlFallback", picUrlFallback);
}
void SettingsCache::setNotificationsEnabled(int _notificationsEnabled)
{
notificationsEnabled = static_cast<bool>(_notificationsEnabled);

View file

@ -2,6 +2,7 @@
#define SETTINGSCACHE_H
#include "settings/carddatabasesettings.h"
#include "settings/downloadsettings.h"
#include "settings/gamefilterssettings.h"
#include "settings/layoutssettings.h"
#include "settings/messagesettings.h"
@ -13,10 +14,6 @@
class ReleaseChannel;
// Fallbacks used for cards w/o MultiverseId
#define PIC_URL_DEFAULT "https://api.scryfall.com/cards/multiverse/!cardid!?format=image"
#define PIC_URL_FALLBACK "https://api.scryfall.com/cards/named?fuzzy=!name!&format=image"
// size should be a multiple of 64
#define PIXMAPCACHE_SIZE_DEFAULT 2047
#define PIXMAPCACHE_SIZE_MIN 64
@ -61,6 +58,7 @@ private:
MessageSettings *messageSettings;
GameFiltersSettings *gameFiltersSettings;
LayoutsSettings *layoutsSettings;
DownloadSettings *downloadSettings;
QByteArray mainWindowGeometry;
QByteArray tokenDialogGeometry;
@ -303,14 +301,6 @@ public:
{
return ignoreUnregisteredUserMessages;
}
QString getPicUrl() const
{
return picUrl;
}
QString getPicUrlFallback() const
{
return picUrlFallback;
}
int getPixmapCacheSize() const
{
return pixmapCacheSize;
@ -430,6 +420,10 @@ public:
{
return *layoutsSettings;
}
DownloadSettings &downloads() const
{
return *downloadSettings;
}
bool getIsPortableBuild() const
{
return isPortableBuild;
@ -478,8 +472,6 @@ public slots:
void setSoundThemeName(const QString &_soundThemeName);
void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages);
void setPicUrl(const QString &_picUrl);
void setPicUrlFallback(const QString &_picUrlFallback);
void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardScaling(const int _scaleCards);
void setShowMessagePopups(const int _showMessagePopups);

View file

@ -22,6 +22,7 @@ SET(oracle_SOURCES
../cockatrice/src/settings/messagesettings.cpp
../cockatrice/src/settings/gamefilterssettings.cpp
../cockatrice/src/settings/layoutssettings.cpp
../cockatrice/src/settings/downloadsettings.cpp
../cockatrice/src/thememanager.cpp
../cockatrice/src/qt-json/json.cpp
../cockatrice/src/releasechannel.cpp

View file

@ -97,7 +97,7 @@ CardInfoPtr OracleImporter::addCard(const QString &setName,
bool mArtifact = false;
if (cardType.endsWith("Artifact")) {
for (int i = 0; i < cardTextRows.size(); ++i) {
cardTextRows[i].remove(QRegularExpression("\\\".*?\\\""));
cardTextRows[i].remove(QRegularExpression(R"(\".*?\")"));
if (cardTextRows[i].contains("{T}") && cardTextRows[i].contains("to your mana pool")) {
mArtifact = true;
}
@ -124,6 +124,7 @@ CardInfoPtr OracleImporter::addCard(const QString &setName,
cards.insert(cardName, card);
}
card->setMuId(setName, cardId);
card->setUuId(setName, cardUuId);
card->setSetNumber(setName, setNumber);
@ -152,7 +153,7 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data)
QString setNumber;
QString rarity;
QString cardLoyalty;
bool upsideDown = false;
bool upsideDown;
QMap<int, QVariantMap> splitCards;
while (it.hasNext()) {
@ -187,14 +188,14 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data)
: QString("");
cardText = map.contains("text") ? map.value("text").toString() : QString("");
cardId = map.contains("multiverseId") ? map.value("multiverseId").toInt() : 0;
cardUuId = map.contains("uuid") ? map.value("uuid").toString() : QString("");
cardUuId = map.contains("scryfallId") ? map.value("scryfallId").toString() : QString("");
setNumber = map.contains("number") ? map.value("number").toString() : QString("");
rarity = map.contains("rarity") ? map.value("rarity").toString() : QString("");
cardLoyalty = map.contains("loyalty") ? map.value("loyalty").toString() : QString("");
colors = map.contains("colors") ? map.value("colors").toStringList() : QStringList();
relatedCards = QList<CardRelation *>();
if (map.contains("names"))
foreach (const QString &name, map.value("names").toStringList()) {
for (const QString &name : map.value("names").toStringList()) {
if (name != cardName)
relatedCards.append(new CardRelation(name, true));
}
@ -218,18 +219,18 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data)
// split cards handling - get all unique card muids
QList<int> muids = splitCards.uniqueKeys();
foreach (int muid, muids) {
for (int muid : muids) {
// get all cards for this specific muid
QList<QVariantMap> maps = splitCards.values(muid);
QStringList names;
// now, reorder the cards using the ordered list of names
QMap<int, QVariantMap> orderedMaps;
foreach (QVariantMap map, maps) {
for (const QVariantMap &inner_map : maps) {
if (names.isEmpty())
names = map.contains("names") ? map.value("names").toStringList() : QStringList();
QString name = map.value("name").toString();
names = inner_map.contains("names") ? inner_map.value("names").toStringList() : QStringList();
QString name = inner_map.value("name").toString();
int index = names.indexOf(name);
orderedMaps.insertMulti(index, map);
orderedMaps.insertMulti(index, inner_map);
}
// clean variables
@ -248,51 +249,51 @@ int OracleImporter::importTextSpoiler(CardSetPtr set, const QVariant &data)
// loop cards and merge their contents
QString prefix = QString(" // ");
QString prefix2 = QString("\n\n---\n\n");
foreach (QVariantMap map, orderedMaps.values()) {
if (map.contains("name")) {
for (const QVariantMap &inner_map : orderedMaps.values()) {
if (inner_map.contains("name")) {
if (!cardName.isEmpty())
cardName += (orderedMaps.count() > 2) ? QString("/") : prefix;
cardName += map.value("name").toString();
cardName += inner_map.value("name").toString();
}
if (map.contains("manaCost")) {
if (inner_map.contains("manaCost")) {
if (!cardCost.isEmpty())
cardCost += prefix;
cardCost += map.value("manaCost").toString();
cardCost += inner_map.value("manaCost").toString();
}
if (map.contains("convertedManaCost")) {
if (inner_map.contains("convertedManaCost")) {
if (!cmc.isEmpty())
cmc += prefix;
cmc += map.value("convertedManaCost").toString();
cmc += inner_map.value("convertedManaCost").toString();
}
if (map.contains("type")) {
if (inner_map.contains("type")) {
if (!cardType.isEmpty())
cardType += prefix;
cardType += map.value("type").toString();
cardType += inner_map.value("type").toString();
}
if (map.contains("power") || map.contains("toughness")) {
if (inner_map.contains("power") || inner_map.contains("toughness")) {
if (!cardPT.isEmpty())
cardPT += prefix;
cardPT += map.value("power").toString() + QString('/') + map.value("toughness").toString();
cardPT += inner_map.value("power").toString() + QString('/') + inner_map.value("toughness").toString();
}
if (map.contains("text")) {
if (inner_map.contains("text")) {
if (!cardText.isEmpty())
cardText += prefix2;
cardText += map.value("text").toString();
cardText += inner_map.value("text").toString();
}
if (map.contains("uuid")) {
if (inner_map.contains("uuid")) {
if (cardUuId.isEmpty())
cardUuId = map.value("uuid").toString();
cardUuId = inner_map.value("uuid").toString();
}
if (map.contains("number")) {
if (inner_map.contains("number")) {
if (setNumber.isEmpty())
setNumber = map.value("number").toString();
setNumber = inner_map.value("number").toString();
}
if (map.contains("rarity")) {
if (inner_map.contains("rarity")) {
if (rarity.isEmpty())
rarity = map.value("rarity").toString();
rarity = inner_map.value("rarity").toString();
}
colors << map.value("colors").toStringList();
colors << inner_map.value("colors").toStringList();
}
colors.removeDuplicates();