mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-22 17:45:09 -07:00
* [PictureLoader] Add user-configurable per-host request caps Picture downloads were throttled to a uniform 10 requests/second per host with no way to tune a specific server. A rate-limited API host (Scryfall caps at 10 req/s) can trip 429s during bursts, and CDN hosts with no rate limit were throttled needlessly. Introduce developer-owned per-host caps that users can only ever lower, never raise, exposed in the download settings page: - DownloadSettings::DEVELOPER_HOST_CAPS sets the ceiling per host (api.scryfall.com 9, cards.scryfall.io unlimited, others 10). - A new hostRequestLimits setting stores user overrides in downloads.ini; clampHostRequestLimit() bounds them to [1, devCap] so a user can reduce api.scryfall.com to 5 but never raise it above 9. - The picture worker seeds, halves on 429, and recovers its sustained per-host allowance against the effective ceiling instead of the global maximum, and skips per-host accounting entirely for unlocked hosts (cards.scryfall.io) while global pacing and 429 backoff still apply. - The deck editor settings page gains one spinbox per known host, each clamped to its developer cap. * [PictureLoader] Let unlocked hosts skip dispatch pacing; adjust limits per URL Two refinements to the per-host request caps: - Unlocked hosts (UNLIMITED_HOST_QUOTA, e.g. cards.scryfall.io) no longer wait on the 100ms dispatch pacing or consume the global per-second quota. dispatchQueuedRequest fires their queued requests back-to-back, bounded only by their 429 backoff window and Qt's per-host connection pool, so an unthrottled CDN is not artificially slowed. - The deck editor download settings page replaces the static grid of one spinbox per known host with an "Adjust Rate Limit" toolbar action on the URL list. It picks the host out of the selected URL and clamps the entry against the developer cap table (including for user-added URLs). Also fixes a review finding: resetRequestQuota could write the UNLIMITED_HOST_QUOTA sentinel (-1) into the sustained per-host quota when a host became unlocked mid-run, permanently poisoning its allowance. Stale entries for unlocked hosts are now dropped, and the per-second seed is clamped against the effective ceiling so a lowered limit applies immediately. * [PictureLoader] Cap unlocked host bursts and adapt them to 429s * [PictureLoader] Store per-host limits readably and show them per URL * [PictureLoader] Make dispatch and rate-limit bookkeeping key on the real host Addresses ZeldaZach's round-4 review nits: - Dispatch now resolves the cached-redirect chain before the in-flight gate, so a redirect learned after a URL was queued can no longer bypass the MAX_IN_FLIGHT_PER_HOST cap and drain the whole queue onto the redirect target, which may carry its own developer cap. processSingleRequest does the same so the allowance math keys on the host that is actually hit. - The per-host in-flight slot is released when the reply is destroyed (with the worker as the connection context) rather than on a 'finished' connection bound to the work object, so an aborted reply or a work object deleted while a reply is pending can never permanently shrink the fast path's concurrency. - storeSettings only prunes limits for hosts with neither a URL nor a developer cap, so throttles on redirect targets (api.scryfall.com -> cards.scryfall.io) survive URL removal. - Unlocked hosts are offered 0..UNLOCKED_HOST_LIMIT_MAX (50) in the rate limit dialog, matching clampHostRequestLimit() and the documented hand-editable range, so values written into downloads.ini are no longer silently rewritten on the next edit. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
80 lines
3.1 KiB
C++
80 lines
3.1 KiB
C++
/**
|
|
* @file download_settings.h
|
|
* @ingroup NetworkSettings
|
|
*/
|
|
//! \todo Document this file.
|
|
|
|
#ifndef COCKATRICE_DOWNLOADSETTINGS_H
|
|
#define COCKATRICE_DOWNLOADSETTINGS_H
|
|
|
|
#include "settings_manager.h"
|
|
|
|
#include <QHash>
|
|
|
|
class DownloadSettings : public SettingsManager
|
|
{
|
|
Q_OBJECT
|
|
friend class SettingsCache;
|
|
|
|
static const QStringList DEFAULT_DOWNLOAD_URLS;
|
|
static const QString SCRYFALL_NAMED_LOCALIZED_URL;
|
|
static const QHash<QString, int> DEVELOPER_HOST_CAPS;
|
|
|
|
public:
|
|
/** @brief Per-host request allowance (requests/second) when no developer cap applies. */
|
|
static constexpr int DEFAULT_HOST_REQUEST_LIMIT = 10;
|
|
/** @brief Floor for any per-host request allowance. */
|
|
static constexpr int MIN_HOST_REQUEST_LIMIT = 1;
|
|
/**
|
|
* @brief Upper bound offered to the user when lowering an unlocked host's allowance.
|
|
*
|
|
* Unlocked hosts have no developer cap, so `clampHostRequestLimit` puts no upper bound on
|
|
* them; this only bounds what the settings dialog offers, and matches the widest paced
|
|
* allowance a user is documented to be able to hand-edit in `downloads.ini`. Choosing 0
|
|
* below this restores the "unlimited" fast path.
|
|
*/
|
|
static constexpr int UNLOCKED_HOST_LIMIT_MAX = 50;
|
|
/** @brief Developer cap marking a host as never throttled per host or by the dispatch pacing. */
|
|
static constexpr int UNLIMITED_HOST_QUOTA = -1;
|
|
|
|
/**
|
|
* @brief Developer-set per-host allowance ceilings (requests/second), keyed by host.
|
|
*
|
|
* Hosts not present default to DEFAULT_HOST_REQUEST_LIMIT. An entry of
|
|
* UNLIMITED_HOST_QUOTA marks a host that users may still lower, but that is never
|
|
* throttled per host by default. Users can never raise a host's allowance above its
|
|
* developer cap.
|
|
*/
|
|
static const QHash<QString, int> &getDeveloperHostCaps();
|
|
|
|
explicit DownloadSettings(const QString &, QObject *);
|
|
|
|
QStringList getAllURLs() const;
|
|
void setDownloadUrls(const QStringList &downloadURLs);
|
|
void resetToDefaultURLs();
|
|
[[nodiscard]] bool addLocalizedScryfallUrl();
|
|
[[nodiscard]] bool getPicDownload() const;
|
|
void setPicDownload(bool _picDownload);
|
|
[[nodiscard]] bool getDownloadSpoilersStatus() const;
|
|
void setDownloadSpoilerStatus(bool _spoilerStatus);
|
|
|
|
/** @brief User-set per-host request allowances (requests/second). Missing hosts use the developer default. */
|
|
QHash<QString, int> getHostRequestLimits() const;
|
|
void setHostRequestLimits(const QHash<QString, int> &hostRequestLimits);
|
|
|
|
/**
|
|
* @brief Clamps the user's requested allowance for a host against its developer cap.
|
|
* @param host The host to clamp for
|
|
* @param requested The user-requested allowance in requests/second
|
|
* @return The effective allowance. Users may lower a host's allowance but never raise it
|
|
* above the developer cap; hosts with UNLIMITED_HOST_QUOTA have no upper bound.
|
|
*/
|
|
[[nodiscard]] int clampHostRequestLimit(const QString &host, int requested) const;
|
|
|
|
signals:
|
|
void picDownloadChanged();
|
|
void downloadSpoilerStatusChanged();
|
|
void hostRequestLimitsChanged();
|
|
};
|
|
|
|
#endif // COCKATRICE_DOWNLOADSETTINGS_H
|