This commit is contained in:
2026-09-12 03:40:10 +02:00
parent 029ceee69a
commit 8ac465229c
20 changed files with 220 additions and 422 deletions

View File

@@ -36,6 +36,7 @@ static const char *TAG = "scryfall";
#define IMAGE_URL_MAX (512)
#define EVT_CARD_CACHED (1U << 0)
#define EVT_CARD_FAILED (1U << 1)
typedef struct {
char name[CARD_NAME_MAX];
@@ -45,7 +46,7 @@ typedef struct {
static QueueHandle_t s_fetch_queue;
static TaskHandle_t s_net_task;
static uint32_t s_result_bits = 0;
static volatile uint32_t s_result_bits = 0;
static char s_last_uid[UID_MAX] = {0};
/* ------------------------------------------------------------------ */
@@ -98,9 +99,9 @@ static esp_err_t http_on_body(http_sink_t *sink, const char *data, size_t len)
return ESP_OK;
}
if (len > sink->cap - sink->len) {
/* grow, never beyond cap */
size_t req = sink->len + len;
/* Reserve space for data plus 1 byte for null terminator */
if (len + 1 > sink->cap - sink->len) {
size_t req = sink->len + len + 1;
if (req > JSON_BUF_MAX) {
sink->err = ESP_ERR_NO_MEM;
return ESP_FAIL;
@@ -148,6 +149,14 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
static esp_err_t http_request(const char *url, http_sink_t *sink)
{
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
if (sink->file != NULL) {
rewind(sink->file);
ftruncate(fileno(sink->file), 0);
} else {
sink->len = 0;
}
sink->err = ESP_OK;
esp_http_client_config_t cfg = {
.url = url,
.transport_type = HTTP_TRANSPORT_OVER_SSL,
@@ -230,7 +239,7 @@ static const char *get_string(const cJSON *item, const char *fallback)
/* Cache metadata */
/* ------------------------------------------------------------------ */
esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
{
char json_path[64];
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
@@ -261,8 +270,6 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
if (valid) {
if (storage_file_exists(jpg_path)) {
ESP_LOGI(TAG, "cache hit %s and %s", json_path, jpg_path);
s_result_bits |= EVT_CARD_CACHED;
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
return ESP_OK;
}
ESP_LOGI(TAG, "json valid but %s missing, fetching art...", jpg_path);
@@ -397,13 +404,19 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
cJSON_Delete(root);
if (ret == ESP_OK) {
s_result_bits |= EVT_CARD_CACHED;
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
ESP_LOGI(TAG, "card '%s' cached under %s", card_name, uid);
}
return ret;
}
static esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
{
esp_err_t ret = scryfall_do_fetch(card_name, uid);
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
s_result_bits = (ret == ESP_OK) ? EVT_CARD_CACHED : EVT_CARD_FAILED;
return ret;
}
/* ------------------------------------------------------------------ */
/* net_task */
/* ------------------------------------------------------------------ */
@@ -431,6 +444,8 @@ static void prefetch_mappings(void)
uid = item->string;
if (uid == NULL || item->valuestring == NULL) continue;
scryfall_ensure_cached(item->valuestring, uid);
/* Rate-limit delay: Scryfall requests 50-100ms between queries */
vTaskDelay(pdMS_TO_TICKS(100));
}
cJSON_Delete(root);
}
@@ -490,6 +505,11 @@ esp_err_t scryfall_fetch_request(const char *card_name, const char *uid)
}
bool scryfall_has_pending_result(void)
{
return (s_result_bits & (EVT_CARD_CACHED | EVT_CARD_FAILED)) != 0;
}
bool scryfall_last_result_success(void)
{
return (s_result_bits & EVT_CARD_CACHED) != 0;
}
@@ -501,5 +521,5 @@ const char *scryfall_last_result_uid(void)
void scryfall_consume_result(void)
{
s_result_bits &= ~EVT_CARD_CACHED;
s_result_bits = 0;
}