[UserListDelegate] Consider providerId (#7018)

Co-authored-by: Lukas Brübach <lukas.bruebach@bdosecurity.de>
This commit is contained in:
BruebachL 2026-06-27 11:23:55 -04:00 committed by GitHub
parent 2914874720
commit 6dc974a05d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 211 additions and 53 deletions

View file

@ -5,9 +5,9 @@
#include <QPointer> #include <QPointer>
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
static QString makeKey(const QString &user, const QString &card) static QString makeKey(const QString &user, const QString &card, const QString &providerId)
{ {
return user + u'|' + card; return user + u'|' + card + u'|' + providerId;
} }
UserCardArtProvider::UserCardArtProvider(QObject *parent) : QObject(parent) UserCardArtProvider::UserCardArtProvider(QObject *parent) : QObject(parent)
@ -31,13 +31,13 @@ const QMap<QString, QPixmap> &UserCardArtProvider::cache() const
return cardArtCache; return cardArtCache;
} }
void UserCardArtProvider::requestCardArt(const QString &userName, const QString &cardName) void UserCardArtProvider::requestCardArt(const QString &userName, const QString &cardName, const QString &providerId)
{ {
if (cardName.isEmpty()) { if (cardName.isEmpty()) {
return; return;
} }
const QString key = makeKey(userName, cardName); const QString key = makeKey(userName, cardName, providerId);
if (cardArtCache.contains(key) || pending.contains(key)) { if (cardArtCache.contains(key) || pending.contains(key)) {
return; return;
@ -83,15 +83,16 @@ void UserCardArtProvider::processQueue()
const QString key = queue.dequeue(); const QString key = queue.dequeue();
const QStringList parts = key.split(u'|'); const QStringList parts = key.split(u'|');
if (parts.size() != 2) { if (parts.size() != 3) {
pending.remove(key); pending.remove(key);
continue; continue;
} }
const QString userName = parts.at(0); const QString userName = parts.at(0);
const QString cardName = parts.at(1); const QString cardName = parts.at(1);
const QString providerId = parts.at(2);
ExactCard card = CardDatabaseManager::query()->getCard({cardName}); ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId});
if (!card) { if (!card) {
pending.remove(key); pending.remove(key);

View file

@ -14,7 +14,7 @@ class UserCardArtProvider : public QObject
public: public:
explicit UserCardArtProvider(QObject *parent = nullptr); explicit UserCardArtProvider(QObject *parent = nullptr);
void requestCardArt(const QString &userName, const QString &cardName); void requestCardArt(const QString &userName, const QString &cardName, const QString &providerId);
const QMap<QString, QPixmap> &cache() const; const QMap<QString, QPixmap> &cache() const;
static QPixmap cropCardArt(const QPixmap &fullRes); static QPixmap cropCardArt(const QPixmap &fullRes);

View file

@ -70,7 +70,7 @@ void CardArtPreviewWidget::paintEvent(QPaintEvent *)
QString(), // userName not needed for override path QString(), // userName not needed for override path
nullptr, // no cache nullptr, // no cache
params, params,
&sourcePixmap // 👈 direct pixmap &sourcePixmap // direct pixmap
); );
// Avatar placeholder so the left-margin interaction is visible // Avatar placeholder so the left-margin interaction is visible
@ -174,6 +174,13 @@ void UserCardArtSettingsDialog::setupUi()
{ {
initializeSearchBar(); initializeSearchBar();
providerComboBox = new QComboBox;
connect(providerComboBox, &QComboBox::currentIndexChanged, this, [this]() {
currentParams.cardProviderId = providerComboBox->currentData().toString();
reloadPreview();
onParamChanged();
});
marginLSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctL, 0.01); marginLSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctL, 0.01);
marginRSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctR, 0.01); marginRSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctR, 0.01);
verticalOffsetSpin = makeSpinBox(0.0, 1.0, currentParams.verticalOffset, 0.01); verticalOffsetSpin = makeSpinBox(0.0, 1.0, currentParams.verticalOffset, 0.01);
@ -181,6 +188,7 @@ void UserCardArtSettingsDialog::setupUi()
auto *form = new QFormLayout; auto *form = new QFormLayout;
form->addRow(tr("Card name:"), searchBar); form->addRow(tr("Card name:"), searchBar);
form->addRow(tr("Card ProviderId:"), providerComboBox);
form->addRow(tr("Left margin (%):"), marginLSpin); form->addRow(tr("Left margin (%):"), marginLSpin);
form->addRow(tr("Right margin (%):"), marginRSpin); form->addRow(tr("Right margin (%):"), marginRSpin);
form->addRow(tr("Vertical offset:"), verticalOffsetSpin); form->addRow(tr("Vertical offset:"), verticalOffsetSpin);
@ -219,6 +227,32 @@ void UserCardArtSettingsDialog::setupUi()
connect(zoomSpin, &QDoubleSpinBox::valueChanged, this, &UserCardArtSettingsDialog::onParamChanged); connect(zoomSpin, &QDoubleSpinBox::valueChanged, this, &UserCardArtSettingsDialog::onParamChanged);
} }
void UserCardArtSettingsDialog::populateProviderCombo(const QString &cardName)
{
providerComboBox->clear();
auto card = CardDatabaseManager::query()->getCard({cardName});
const auto &sets = card.getInfo().getSets();
for (const auto &printings : sets) {
for (const auto &p : printings) {
QString setName = p.getSet()->getLongName();
QString collector = p.getProperty("num");
QString uuid = p.getUuid();
QString label = setName;
if (!collector.isEmpty()) {
label += " #" + collector;
}
providerComboBox->addItem(label, uuid);
}
}
}
void UserCardArtSettingsDialog::onCardNameChanged(const QString &name) void UserCardArtSettingsDialog::onCardNameChanged(const QString &name)
{ {
if (name.isEmpty()) { if (name.isEmpty()) {
@ -231,27 +265,68 @@ void UserCardArtSettingsDialog::onCardNameChanged(const QString &name)
if (!card) { if (!card) {
currentPixmap = QPixmap(); currentPixmap = QPixmap();
preview->setPixmap(currentPixmap); preview->setPixmap(currentPixmap);
providerComboBox->clear();
return; return;
} }
currentParams.cardName = name; currentParams.cardName = name;
populateProviderCombo(name);
if (providerComboBox->count() == 0) {
// No printings found for this card; nothing to preview.
currentPixmap = QPixmap();
preview->setPixmap(currentPixmap);
currentParams.cardProviderId.clear();
return;
}
currentParams.cardProviderId = providerComboBox->currentData().toString();
reloadPreview();
}
void UserCardArtSettingsDialog::reloadPreview()
{
if (currentParams.cardName.isEmpty()) {
return;
}
ExactCard card = CardDatabaseManager::query()->getCard({currentParams.cardName, currentParams.cardProviderId});
if (!card) {
return;
}
// CardPictureLoader::getPixmap() is async on a cache miss: it enqueues a
// background download and returns a null pixmap immediately. When that
// download finishes, CardPictureLoader::imageLoaded() caches the result
// and calls card.emitPixmapUpdated(), which emits pixmapUpdated() on the
// underlying CardInfo (see exact_card.h). Listen for that, scoped to
// whichever CardInfo we just asked for, so the preview catches up once
// the image actually arrives instead of staying on the placeholder.
//
// Disconnect any previous listener first -- otherwise switching cards
// repeatedly stacks up connections to old CardInfo objects, each of
// which would still fire reloadPreview() (harmlessly, but wastefully)
// whenever ITS art finishes loading later.
disconnect(pixmapUpdatedConnection);
QPixmap fullRes; QPixmap fullRes;
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040)); CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
if (fullRes.isNull()) { if (fullRes.isNull()) {
connect(card.getCardPtr().data(), &CardInfo::pixmapUpdated, this, [this, card](const PrintingInfo &) { // Not loaded yet -- wait for the signal instead of giving up.
disconnect(card.getCardPtr().data(), &CardInfo::pixmapUpdated, this, nullptr); // card.getCardPtr() is a CardInfoPtr (QSharedPointer<CardInfo>);
QPixmap loaded; // .data() gives the raw QObject* needed for connect().
CardPictureLoader::getPixmap(loaded, card, QSize(745, 1040)); CardInfo *cardInfo = card.getCardPtr().data();
currentPixmap = UserCardArtProvider::cropCardArt(loaded); if (cardInfo) {
preview->setPixmap(currentPixmap); pixmapUpdatedConnection = connect(cardInfo, &CardInfo::pixmapUpdated, this, [this]() { reloadPreview(); });
}); }
return; return;
} }
currentPixmap = UserCardArtProvider::cropCardArt(fullRes); currentPixmap = UserCardArtProvider::cropCardArt(fullRes);
preview->setPixmap(currentPixmap); preview->setPixmap(currentPixmap);
preview->setParams(currentParams);
} }
void UserCardArtSettingsDialog::onParamChanged() void UserCardArtSettingsDialog::onParamChanged()

View file

@ -3,6 +3,7 @@
#include "user_list_painter.h" #include "user_list_painter.h"
#include <QComboBox>
#include <QDialog> #include <QDialog>
#include <QPixmap> #include <QPixmap>
@ -43,10 +44,12 @@ public:
private slots: private slots:
void onCardNameChanged(const QString &name); void onCardNameChanged(const QString &name);
void reloadPreview();
void onParamChanged(); void onParamChanged();
private: private:
void setupUi(); void setupUi();
void populateProviderCombo(const QString &cardName);
void initializeSearchBar(); void initializeSearchBar();
QDoubleSpinBox *makeSpinBox(double min, double max, double value, double step); QDoubleSpinBox *makeSpinBox(double min, double max, double value, double step);
@ -57,6 +60,10 @@ private:
CardSearchModel *searchModel; CardSearchModel *searchModel;
CardCompleterProxyModel *proxyModel; CardCompleterProxyModel *proxyModel;
QComboBox *providerComboBox;
QMetaObject::Connection pixmapUpdatedConnection;
QDoubleSpinBox *marginLSpin; QDoubleSpinBox *marginLSpin;
QDoubleSpinBox *marginRSpin; QDoubleSpinBox *marginRSpin;
QDoubleSpinBox *verticalOffsetSpin; QDoubleSpinBox *verticalOffsetSpin;

View file

@ -542,7 +542,7 @@ void UserInfoPopup::showForUser(const QString &userName,
const CardArtParams params = (m_cardArtParamsMap && m_cardArtParamsMap->contains(userName)) const CardArtParams params = (m_cardArtParamsMap && m_cardArtParamsMap->contains(userName))
? m_cardArtParamsMap->value(userName) ? m_cardArtParamsMap->value(userName)
: CardArtParams{}; : CardArtParams{};
const QString artKey = userName + u'|' + params.cardName; const QString artKey = userName + u'|' + params.cardName + u'|' + params.cardProviderId;
const QPixmap cardArt = (m_cardArtCache && !params.cardName.isEmpty()) ? m_cardArtCache->value(artKey) : QPixmap{}; const QPixmap cardArt = (m_cardArtCache && !params.cardName.isEmpty()) ? m_cardArtCache->value(artKey) : QPixmap{};
m_header->setUserData(userInfo, online, avatar, cardArt, params); m_header->setUserData(userInfo, online, avatar, cardArt, params);

View file

@ -73,9 +73,9 @@ void UserListPainter::drawBackground(QPainter *painter,
painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2); painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2);
} }
static QString makeKey(const QString &user, const QString &card) static QString makeKey(const QString &user, const QString &card, const QString &providerId)
{ {
return user + u'|' + card; return user + u'|' + card + u'|' + providerId;
} }
void UserListPainter::drawCardArt(QPainter *painter, void UserListPainter::drawCardArt(QPainter *painter,
@ -95,7 +95,7 @@ void UserListPainter::drawCardArt(QPainter *painter,
return; return;
} }
const QString key = makeKey(userName, params.cardName); const QString key = makeKey(userName, params.cardName, params.cardProviderId);
if (!cardArtCache->contains(key)) { if (!cardArtCache->contains(key)) {
return; return;

View file

@ -18,6 +18,7 @@ class ServerInfo_User;
struct CardArtParams struct CardArtParams
{ {
QString cardName = ""; QString cardName = "";
QString cardProviderId = "";
double marginPctL = 0.33; double marginPctL = 0.33;
double marginPctR = 0.02; double marginPctR = 0.02;
double verticalOffset = 0.35; double verticalOffset = 0.35;

View file

@ -904,12 +904,13 @@ void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)
const auto &cap = user.card_art_params(); const auto &cap = user.card_art_params();
CardArtParams params; CardArtParams params;
params.cardName = QString::fromStdString(cap.card_name()); params.cardName = QString::fromStdString(cap.card_name());
params.cardProviderId = QString::fromStdString(cap.card_provider_id());
params.marginPctL = cap.margin_pct_l(); params.marginPctL = cap.margin_pct_l();
params.marginPctR = cap.margin_pct_r(); params.marginPctR = cap.margin_pct_r();
params.verticalOffset = cap.vertical_offset(); params.verticalOffset = cap.vertical_offset();
params.zoom = cap.zoom(); params.zoom = cap.zoom();
cardArtParamsMap.insert(userName, params); cardArtParamsMap.insert(userName, params);
cardArtProvider->requestCardArt(userName, params.cardName); cardArtProvider->requestCardArt(userName, params.cardName, params.cardProviderId);
} else { } else {
cardArtParamsMap.remove(userName); // clear stale params on removal cardArtParamsMap.remove(userName); // clear stale params on removal
} }

View file

@ -27,7 +27,7 @@ int CardArtRulesModel::rowCount(const QModelIndex &parent) const
int CardArtRulesModel::columnCount(const QModelIndex &parent) const int CardArtRulesModel::columnCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent); Q_UNUSED(parent);
return 3; return 4;
} }
QVariant CardArtRulesModel::data(const QModelIndex &index, int role) const QVariant CardArtRulesModel::data(const QModelIndex &index, int role) const
@ -43,8 +43,10 @@ QVariant CardArtRulesModel::data(const QModelIndex &index, int role) const
case 0: case 0:
return e.cardName; return e.cardName;
case 1: case 1:
return e.mode; return e.cardProviderId;
case 2: case 2:
return e.mode;
case 3:
return e.reason; return e.reason;
} }
} }
@ -62,8 +64,10 @@ QVariant CardArtRulesModel::headerData(int section, Qt::Orientation orientation,
case 0: case 0:
return tr("Card"); return tr("Card");
case 1: case 1:
return tr("Mode"); return tr("ProviderId");
case 2: case 2:
return tr("Mode");
case 3:
return tr("Reason"); return tr("Reason");
default: default:
return {}; return {};
@ -97,6 +101,15 @@ QString CardArtRulesModel::cardAt(int row) const
return entries[row].cardName; return entries[row].cardName;
} }
const CardArtRulesModel::Entry *CardArtRulesModel::entryAt(int row) const
{
if (row < 0 || row >= static_cast<int>(entries.size())) {
return nullptr;
}
return &entries[row];
}
void CardArtRulesModel::onRefreshFinished(const Response &r) void CardArtRulesModel::onRefreshFinished(const Response &r)
{ {
if (r.response_code() != Response::RespOk) { if (r.response_code() != Response::RespOk) {
@ -109,8 +122,8 @@ void CardArtRulesModel::onRefreshFinished(const Response &r)
entries.clear(); entries.clear();
for (const auto &e : resp.entries()) { for (const auto &e : resp.entries()) {
entries.push_back({QString::fromStdString(e.card_name()), QString::fromStdString(e.mode()), entries.push_back({QString::fromStdString(e.card_name()), QString::fromStdString(e.card_provider_id()),
QString::fromStdString(e.reason())}); QString::fromStdString(e.mode()), QString::fromStdString(e.reason())});
} }
endResetModel(); endResetModel();
@ -128,6 +141,7 @@ void TabCardArtRules::setupUi()
initSearchBar(); initSearchBar();
providerComboBox = new QComboBox;
modeBox = new QComboBox; modeBox = new QComboBox;
reasonEdit = new QLineEdit; reasonEdit = new QLineEdit;
@ -146,6 +160,7 @@ void TabCardArtRules::setupUi()
auto *form = new QFormLayout; auto *form = new QFormLayout;
form->addRow(tr("Card:"), searchEdit); form->addRow(tr("Card:"), searchEdit);
form->addRow(tr("ProviderId:"), providerComboBox);
form->addRow(tr("Mode:"), modeBox); form->addRow(tr("Mode:"), modeBox);
form->addRow(tr("Reason:"), reasonEdit); form->addRow(tr("Reason:"), reasonEdit);
@ -204,6 +219,34 @@ void TabCardArtRules::initSearchBar()
}); });
connect(searchCompleter, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this, connect(searchCompleter, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
[this](const QString &name) { searchEdit->setText(name); }); [this](const QString &name) { searchEdit->setText(name); });
connect(searchEdit, &QLineEdit::editingFinished, this,
[this]() { populateProviderCombo(searchEdit->text().trimmed()); });
}
void TabCardArtRules::populateProviderCombo(const QString &cardName)
{
providerComboBox->clear();
auto card = CardDatabaseManager::query()->getCard({cardName});
const auto &sets = card.getInfo().getSets();
for (const auto &printings : sets) {
for (const auto &p : printings) {
QString setName = p.getSet()->getLongName();
QString collector = p.getProperty("num");
QString uuid = p.getUuid();
QString label = setName;
if (!collector.isEmpty()) {
label += " #" + collector;
}
providerComboBox->addItem(label, uuid);
}
}
} }
void TabCardArtRules::retranslateUi() void TabCardArtRules::retranslateUi()
@ -222,6 +265,7 @@ void TabCardArtRules::addRule()
{ {
Command_AddCardArtRule cmd; Command_AddCardArtRule cmd;
cmd.set_card_name(searchEdit->text().toStdString()); cmd.set_card_name(searchEdit->text().toStdString());
cmd.set_card_provider_id(providerComboBox->currentData().toString().toStdString());
cmd.set_mode(modeBox->currentText().toStdString()); cmd.set_mode(modeBox->currentText().toStdString());
cmd.set_reason(reasonEdit->text().toStdString()); cmd.set_reason(reasonEdit->text().toStdString());
@ -238,7 +282,10 @@ void TabCardArtRules::removeSelected()
} }
Command_RemoveCardArtRule cmd; Command_RemoveCardArtRule cmd;
cmd.set_card_name(tableModel->cardAt(idx.row()).toStdString()); const auto e = tableModel->entryAt(idx.row());
cmd.set_card_name(e->cardName.toStdString());
cmd.set_card_provider_id(e->cardProviderId.toStdString());
client->sendCommand(client->prepareModeratorCommand(cmd)); client->sendCommand(client->prepareModeratorCommand(cmd));

View file

@ -20,6 +20,7 @@ public:
struct Entry struct Entry
{ {
QString cardName; QString cardName;
QString cardProviderId;
QString mode; QString mode;
QString reason; QString reason;
}; };
@ -35,6 +36,7 @@ public:
void clear(); void clear();
QString cardAt(int row) const; QString cardAt(int row) const;
const Entry *entryAt(int row) const;
private slots: private slots:
void onRefreshFinished(const Response &r); void onRefreshFinished(const Response &r);
@ -70,11 +72,13 @@ private:
QLineEdit *searchEdit; QLineEdit *searchEdit;
void initSearchBar(); void initSearchBar();
void populateProviderCombo(const QString &cardName);
QCompleter *searchCompleter; QCompleter *searchCompleter;
CardDatabaseModel *cardDbModel; CardDatabaseModel *cardDbModel;
CardDatabaseDisplayModel *cardDbDisplayModel; CardDatabaseDisplayModel *cardDbDisplayModel;
CardSearchModel *cardSearchModel; CardSearchModel *cardSearchModel;
CardCompleterProxyModel *cardProxyModel; CardCompleterProxyModel *cardProxyModel;
QComboBox *providerComboBox;
QComboBox *modeBox; QComboBox *modeBox;
QLineEdit *reasonEdit; QLineEdit *reasonEdit;

View file

@ -116,8 +116,9 @@ message Command_AddCardArtRule {
} }
optional string card_name = 1; optional string card_name = 1;
optional string mode = 2; // "ALLOW" or "DENY" optional string card_provider_id = 2;
optional string reason = 3; optional string mode = 3; // "ALLOW" or "DENY"
optional string reason = 4;
} }
message Command_RemoveCardArtRule { message Command_RemoveCardArtRule {
@ -126,6 +127,7 @@ message Command_RemoveCardArtRule {
} }
optional string card_name = 1; optional string card_name = 1;
optional string card_provider_id = 2;
} }
message Command_ListCardArtRules { message Command_ListCardArtRules {

View file

@ -3,8 +3,9 @@ import "response.proto";
message Response_CardArtRuleEntry { message Response_CardArtRuleEntry {
optional string card_name = 1; optional string card_name = 1;
optional string mode = 2; optional string card_provider_id = 2;
optional string reason = 3; optional string mode = 3;
optional string reason = 4;
} }
message Response_ListCardArtRules { message Response_ListCardArtRules {

View file

@ -15,10 +15,11 @@ message ServerInfo_User {
}; };
message CardArtParams { message CardArtParams {
optional string card_name = 1; optional string card_name = 1;
optional double margin_pct_l = 2 [default = 0.33]; optional string card_provider_id = 2;
optional double margin_pct_r = 3 [default = 0.02]; optional double margin_pct_l = 3 [default = 0.33];
optional double vertical_offset = 4 [default = 0.35]; optional double margin_pct_r = 4 [default = 0.02];
optional double zoom = 5 [default = 1.0]; optional double vertical_offset = 5 [default = 0.35];
optional double zoom = 6 [default = 1.0];
}; };
optional string name = 1; optional string name = 1;

View file

@ -212,8 +212,9 @@ message Command_SetCardArtParams {
optional Command_SetCardArtParams ext = 1025; optional Command_SetCardArtParams ext = 1025;
} }
optional string card_name = 1; optional string card_name = 1;
optional double margin_pct_l = 2; optional string card_provider_id = 2;
optional double margin_pct_r = 3; optional double margin_pct_l = 3;
optional double vertical_offset = 4; optional double margin_pct_r = 4;
optional double zoom = 5; optional double vertical_offset = 5;
optional double zoom = 6;
} }

View file

@ -3,12 +3,13 @@ ALTER TABLE `cockatrice_users` ADD COLUMN `card_art_params` TEXT DEFAULT NULL, A
CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` ( CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`card_name` varchar(255) NOT NULL, `card_name` varchar(255) NOT NULL,
`card_provider_id` varchar(255) NOT NULL,
`mode` enum('ALLOW','DENY') NOT NULL, `mode` enum('ALLOW','DENY') NOT NULL,
`reason` varchar(255) DEFAULT NULL, `reason` varchar(255) DEFAULT NULL,
`created_by` int(7) unsigned DEFAULT NULL, `created_by` int(7) unsigned DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uniq_card_name` (`card_name`), UNIQUE KEY `uniq_provider_card_name` (`card_provider_id`, `card_name`),
KEY `idx_mode` (`mode`), KEY `idx_mode` (`mode`),
FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`) FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`)
ON DELETE SET NULL ON DELETE SET NULL

View file

@ -305,12 +305,13 @@ CREATE TABLE IF NOT EXISTS `cockatrice_audit` (
CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` ( CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT, `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`card_name` varchar(255) NOT NULL, `card_name` varchar(255) NOT NULL,
`card_provider_id` varchar(255) NOT NULL,
`mode` enum('ALLOW','DENY') NOT NULL, `mode` enum('ALLOW','DENY') NOT NULL,
`reason` varchar(255) DEFAULT NULL, `reason` varchar(255) DEFAULT NULL,
`created_by` int(7) unsigned DEFAULT NULL, `created_by` int(7) unsigned DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uniq_card_name` (`card_name`), UNIQUE KEY `uniq_provider_card_name` (`card_provider_id`, `card_name`),
KEY `idx_mode` (`mode`), KEY `idx_mode` (`mode`),
FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`) FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`)
ON DELETE SET NULL ON DELETE SET NULL

View file

@ -693,6 +693,9 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer
if (obj.contains("card_name")) { if (obj.contains("card_name")) {
cap->set_card_name(obj["card_name"].toString().toStdString()); cap->set_card_name(obj["card_name"].toString().toStdString());
} }
if (obj.contains("card_provider_id")) {
cap->set_card_provider_id(obj["card_provider_id"].toString().toStdString());
}
if (obj.contains("marginPctL")) { if (obj.contains("marginPctL")) {
cap->set_margin_pct_l(obj["marginPctL"].toDouble(0.33)); cap->set_margin_pct_l(obj["marginPctL"].toDouble(0.33));
} }

View file

@ -1577,11 +1577,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Comm
return Response::RespOk; return Response::RespOk;
} }
bool AbstractServerSocketInterface::isCardNameAllowed(const QString &cardName) bool AbstractServerSocketInterface::isCardNameAllowed(const QString &cardName, const QString &cardProviderId)
{ {
QSqlQuery *q = sqlInterface->prepareQuery("SELECT mode FROM {prefix}_card_art_name_rules WHERE card_name = :name"); QSqlQuery *q = sqlInterface->prepareQuery(
"SELECT mode FROM {prefix}_card_art_name_rules WHERE card_name = :name AND card_provider_id = :provider");
q->bindValue(":name", cardName); q->bindValue(":name", cardName);
q->bindValue(":provider", cardProviderId);
if (!sqlInterface->execSqlQuery(q)) { if (!sqlInterface->execSqlQuery(q)) {
qWarning() << "Card art rule lookup failed; failing open for" << cardName; qWarning() << "Card art rule lookup failed; failing open for" << cardName;
@ -1603,8 +1605,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
} }
const QString cardName = QString::fromStdString(cmd.card_name()); const QString cardName = QString::fromStdString(cmd.card_name());
const QString cardProviderId = QString::fromStdString(cmd.card_provider_id());
if (cardName.length() > MAX_NAME_LENGTH) { if (cardName.length() > MAX_NAME_LENGTH || cardProviderId.length() > MAX_NAME_LENGTH) {
return Response::RespInvalidData; return Response::RespInvalidData;
} }
@ -1620,7 +1623,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
return Response::RespOk; return Response::RespOk;
} }
if (!isCardNameAllowed(cardName)) { if (!isCardNameAllowed(cardName, cardProviderId)) {
return Response::RespFunctionNotAllowed; return Response::RespFunctionNotAllowed;
} }
@ -1633,6 +1636,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
QJsonObject obj; QJsonObject obj;
obj["card_name"] = cardName; obj["card_name"] = cardName;
obj["card_provider_id"] = cardProviderId;
obj["marginPctL"] = marginPctL; obj["marginPctL"] = marginPctL;
obj["marginPctR"] = marginPctR; obj["marginPctR"] = marginPctR;
obj["verticalOffset"] = verticalOffset; obj["verticalOffset"] = verticalOffset;
@ -1649,6 +1653,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
// Keep the in-memory userInfo in sync // Keep the in-memory userInfo in sync
auto *cap = userInfo->mutable_card_art_params(); auto *cap = userInfo->mutable_card_art_params();
cap->set_card_name(cmd.card_name()); cap->set_card_name(cmd.card_name());
cap->set_card_provider_id(cmd.card_provider_id());
cap->set_margin_pct_l(marginPctL); cap->set_margin_pct_l(marginPctL);
cap->set_margin_pct_r(marginPctR); cap->set_margin_pct_r(marginPctR);
cap->set_vertical_offset(verticalOffset); cap->set_vertical_offset(verticalOffset);
@ -1664,21 +1669,23 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAddCardArtRule(const Co
ResponseContainer &) ResponseContainer &)
{ {
const QString cardName = QString::fromStdString(cmd.card_name()); const QString cardName = QString::fromStdString(cmd.card_name());
const QString cardProviderId = QString::fromStdString(cmd.card_provider_id());
const QString mode = QString::fromStdString(cmd.mode()); const QString mode = QString::fromStdString(cmd.mode());
if (mode != "ALLOW" && mode != "DENY") { if (mode != "ALLOW" && mode != "DENY") {
return Response::RespInvalidData; return Response::RespInvalidData;
} }
if (cardName.isEmpty() || cardName.length() > MAX_NAME_LENGTH) { if (cardName.isEmpty() || cardName.length() > MAX_NAME_LENGTH || cardProviderId.length() > MAX_NAME_LENGTH) {
return Response::RespInvalidData; return Response::RespInvalidData;
} }
QSqlQuery *q = sqlInterface->prepareQuery("INSERT INTO {prefix}_card_art_name_rules " QSqlQuery *q = sqlInterface->prepareQuery("INSERT INTO {prefix}_card_art_name_rules "
"(card_name, mode, reason, created_by) " "(card_name, card_provider_id, mode, reason, created_by) "
"VALUES (:name, :mode, :reason, :uid) " "VALUES (:name, :provider, :mode, :reason, :uid) "
"ON DUPLICATE KEY UPDATE mode=:mode2, reason=:reason2"); "ON DUPLICATE KEY UPDATE mode=:mode2, reason=:reason2");
q->bindValue(":name", cardName); q->bindValue(":name", cardName);
q->bindValue(":provider", cardProviderId);
q->bindValue(":mode", mode); q->bindValue(":mode", mode);
q->bindValue(":mode2", mode); q->bindValue(":mode2", mode);
q->bindValue(":reason", QString::fromStdString(cmd.reason())); q->bindValue(":reason", QString::fromStdString(cmd.reason()));
@ -1696,12 +1703,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveCardArtRule(const
ResponseContainer &) ResponseContainer &)
{ {
auto cardName = QString::fromStdString(cmd.card_name()); auto cardName = QString::fromStdString(cmd.card_name());
if (cardName.length() > MAX_NAME_LENGTH) { auto cardProviderId = QString::fromStdString(cmd.card_provider_id());
if (cardName.length() > MAX_NAME_LENGTH || cardProviderId.length() > MAX_NAME_LENGTH) {
return Response::RespInvalidData; return Response::RespInvalidData;
} }
QSqlQuery *q = sqlInterface->prepareQuery("DELETE FROM {prefix}_card_art_name_rules WHERE card_name=:name"); QSqlQuery *q = sqlInterface->prepareQuery(
"DELETE FROM {prefix}_card_art_name_rules WHERE card_name=:name AND card_provider_id=:provider");
q->bindValue(":name", cardName); q->bindValue(":name", cardName);
q->bindValue(":provider", cardProviderId);
if (!sqlInterface->execSqlQuery(q)) { if (!sqlInterface->execSqlQuery(q)) {
return Response::RespInternalError; return Response::RespInternalError;
@ -1713,7 +1723,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveCardArtRule(const
Response::ResponseCode AbstractServerSocketInterface::cmdListCardArtRules(const Command_ListCardArtRules &, Response::ResponseCode AbstractServerSocketInterface::cmdListCardArtRules(const Command_ListCardArtRules &,
ResponseContainer &rc) ResponseContainer &rc)
{ {
QSqlQuery *q = sqlInterface->prepareQuery("SELECT card_name, mode, reason FROM {prefix}_card_art_name_rules"); QSqlQuery *q = sqlInterface->prepareQuery(
"SELECT card_name, card_provider_id, mode, reason FROM {prefix}_card_art_name_rules");
if (!sqlInterface->execSqlQuery(q)) { if (!sqlInterface->execSqlQuery(q)) {
return Response::RespInternalError; return Response::RespInternalError;
@ -1724,8 +1735,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdListCardArtRules(const
while (q->next()) { while (q->next()) {
auto *entry = re->add_entries(); auto *entry = re->add_entries();
entry->set_card_name(q->value(0).toString().toStdString()); entry->set_card_name(q->value(0).toString().toStdString());
entry->set_mode(q->value(1).toString().toStdString()); entry->set_card_provider_id(q->value(1).toString().toStdString());
entry->set_reason(q->value(2).toString().toStdString()); entry->set_mode(q->value(2).toString().toStdString());
entry->set_reason(q->value(3).toString().toStdString());
} }
rc.setResponseExtension(re); rc.setResponseExtension(re);

View file

@ -129,7 +129,7 @@ private:
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc); Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc); Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
bool isCardNameAllowed(const QString &cardName); bool isCardNameAllowed(const QString &cardName, const QString &cardProviderId);
Response::ResponseCode cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer &); Response::ResponseCode cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer &);
Response::ResponseCode cmdAddCardArtRule(const Command_AddCardArtRule &cmd, ResponseContainer &); Response::ResponseCode cmdAddCardArtRule(const Command_AddCardArtRule &cmd, ResponseContainer &);
Response::ResponseCode cmdRemoveCardArtRule(const Command_RemoveCardArtRule &cmd, ResponseContainer &); Response::ResponseCode cmdRemoveCardArtRule(const Command_RemoveCardArtRule &cmd, ResponseContainer &);