mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-06 01:53:25 -08:00
Fix Deckstats and TappedOut request lifetime and result handling (#7705)
* Fix deck analysis request lifetime, result URLs and form encoding [skip ci] * Preserve unknown cards in deck analysis exports * Share deck analysis URL validation and drop the unused request latch Both analysis interfaces carried a copy of normalizeAnalysisUrl; move it to one header next to them. They are constructed per analysis and analyzeDeck is called once, so the analysisStarted latch was unreachable; remove it from both. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
75d7764643
commit
28597ef716
5 changed files with 159 additions and 70 deletions
|
|
@ -1,5 +1,6 @@
|
|||
#include "deck_stats_interface.h"
|
||||
|
||||
#include "deck_analysis_url.h"
|
||||
#include "libcockatrice/card/card_info.h"
|
||||
#include "libcockatrice/card/database/card_database_querier.h"
|
||||
#include "libcockatrice/deck_list/tree/inner_deck_list_node.h"
|
||||
|
|
@ -10,12 +11,14 @@
|
|||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPointer>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QTextDocumentFragment>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QWidget>
|
||||
#include <functional>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
|
@ -29,30 +32,56 @@ DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent)
|
|||
connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished);
|
||||
}
|
||||
|
||||
void DeckStatsInterface::showError(const QString &message)
|
||||
{
|
||||
// The notice belongs to the originating editor, not this one-shot request.
|
||||
// Retire the request before opening UI: opening a window may reenter Qt.
|
||||
QWidget *owner = qobject_cast<QWidget *>(parent());
|
||||
auto *notice = new QMessageBox(QMessageBox::Critical, tr("Deck analysis"), message, QMessageBox::Ok, owner);
|
||||
notice->setTextFormat(Qt::PlainText);
|
||||
notice->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
||||
notice->setAttribute(Qt::WA_DeleteOnClose);
|
||||
deleteLater();
|
||||
notice->open();
|
||||
}
|
||||
|
||||
void DeckStatsInterface::queryFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), reply->errorString());
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
if (replyHandled) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString data(reply->readAll());
|
||||
replyHandled = true;
|
||||
// All terminal paths release the reply before showing UI or invoking an
|
||||
// external URL handler, either of which may destroy the originating editor.
|
||||
reply->deleteLater();
|
||||
|
||||
static const QRegularExpression rx("<meta property=\"og:url\" content=\"([^\"]+)\"");
|
||||
auto match = rx.match(data);
|
||||
if (!match.hasMatch()) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("The reply from the server could not be parsed."));
|
||||
deleteLater();
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
showError(reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QString deckUrl = match.captured(1);
|
||||
QDesktopServices::openUrl(deckUrl);
|
||||
const QString data = QString::fromUtf8(reply->readAll());
|
||||
static const QRegularExpression rx(
|
||||
R"(<meta\b(?=[^>]*\bproperty\s*=\s*(['"])og:url\1)[^>]*\bcontent\s*=\s*(['"])(.*?)\2)",
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption);
|
||||
const auto match = rx.match(data);
|
||||
if (!match.hasMatch()) {
|
||||
showError(tr("The reply from Deckstats did not contain a deck address."));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString address = QTextDocumentFragment::fromHtml(match.captured(3)).toPlainText();
|
||||
QUrl deckUrl = reply->url().resolved(QUrl(address, QUrl::StrictMode));
|
||||
if (address.trimmed().isEmpty() || !normalizeAnalysisUrl(deckUrl, QStringLiteral("deckstats.net"))) {
|
||||
showError(tr("The reply from Deckstats contained an invalid deck address."));
|
||||
return;
|
||||
}
|
||||
|
||||
QPointer<DeckStatsInterface> guard(this);
|
||||
deleteLater();
|
||||
if (!QDesktopServices::openUrl(deckUrl) && guard) {
|
||||
showError(tr("Could not open your browser. Open this address to see the analyzed deck:\n%1")
|
||||
.arg(deckUrl.toDisplayString()));
|
||||
}
|
||||
}
|
||||
|
||||
void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray &data)
|
||||
|
|
@ -60,12 +89,8 @@ void DeckStatsInterface::getAnalyzeRequestData(const DeckList &deck, QByteArray
|
|||
DeckList deckWithoutTokens;
|
||||
copyDeckWithoutTokens(deck, deckWithoutTokens);
|
||||
|
||||
QUrl params;
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
|
||||
urlQuery.addQueryItem("decktitle", deck.getName());
|
||||
params.setQuery(urlQuery);
|
||||
data.append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
data.append("deck=" + QUrl::toPercentEncoding(deckWithoutTokens.writeToString_Plain()));
|
||||
data.append("&decktitle=" + QUrl::toPercentEncoding(deck.getName()));
|
||||
}
|
||||
|
||||
void DeckStatsInterface::analyzeDeck(const DeckList &deck)
|
||||
|
|
@ -84,7 +109,9 @@ void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList
|
|||
{
|
||||
auto copyIfNotAToken = [&destination](const auto node, const auto card) {
|
||||
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
|
||||
if (dbCard && !dbCard->getIsToken() && node->getName() != DECK_ZONE_MAYBEBOARD) {
|
||||
// Missing local card data is not evidence that a card is a token. Keep
|
||||
// its original name and quantity so the service analyzes the whole deck.
|
||||
if ((!dbCard || !dbCard->getIsToken()) && node->getName() != DECK_ZONE_MAYBEBOARD) {
|
||||
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
|
||||
addedCard->setNumber(card->getNumber());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue