faster image processing
All checks were successful
ESP32 Build & Release / build (push) Successful in 1m16s
All checks were successful
ESP32 Build & Release / build (push) Successful in 1m16s
This commit is contained in:
@@ -37,6 +37,8 @@ static const char *TAG = "scryfall";
|
||||
|
||||
#define EVT_CARD_CACHED (1U << 0)
|
||||
#define EVT_CARD_FAILED (1U << 1)
|
||||
#define EVT_IMG_CACHED (1U << 2)
|
||||
#define EVT_IMG_FAILED (1U << 3)
|
||||
|
||||
typedef struct {
|
||||
char name[CARD_NAME_MAX];
|
||||
@@ -49,6 +51,11 @@ static TaskHandle_t s_net_task;
|
||||
static volatile uint32_t s_result_bits = 0;
|
||||
static char s_last_uid[UID_MAX] = {0};
|
||||
|
||||
static volatile uint32_t s_image_bits = 0;
|
||||
static char s_last_img_uid[UID_MAX] = {0};
|
||||
static volatile bool s_image_fetching = false;
|
||||
static char s_fetching_img_uid[UID_MAX] = {0};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* URL helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -181,7 +188,8 @@ static esp_err_t http_request(const char *url, http_sink_t *sink)
|
||||
.timeout_ms = HTTP_TIMEOUT_MS,
|
||||
.event_handler = http_event_handler,
|
||||
.user_data = sink,
|
||||
.buffer_size = 2048,
|
||||
.buffer_size = 4096,
|
||||
.buffer_size_tx = 1024,
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
.keep_alive_enable = false,
|
||||
};
|
||||
@@ -271,7 +279,7 @@ static const char *get_string(const cJSON *item, const char *fallback)
|
||||
/* Cache metadata */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
|
||||
static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid, bool signal_ui)
|
||||
{
|
||||
char json_path[64];
|
||||
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
|
||||
@@ -302,9 +310,19 @@ static esp_err_t scryfall_do_fetch(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);
|
||||
if (signal_ui) {
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = EVT_CARD_CACHED;
|
||||
snprintf(s_last_img_uid, sizeof(s_last_img_uid), "%s", uid);
|
||||
s_image_bits = EVT_IMG_CACHED;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGI(TAG, "json valid but %s missing, fetching art...", jpg_path);
|
||||
if (signal_ui) {
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = EVT_CARD_CACHED;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "cache entry %s is invalid or card mismatch (expected '%s'), re-fetching...", json_path, card_name);
|
||||
remove(json_path);
|
||||
@@ -323,6 +341,10 @@ static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
|
||||
if (ret != ESP_OK || body == NULL) {
|
||||
free(body);
|
||||
ESP_LOGE(TAG, "scryfall request failed for '%s'", card_name);
|
||||
if (signal_ui) {
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = EVT_CARD_FAILED;
|
||||
}
|
||||
return ret != ESP_OK ? ret : ESP_FAIL;
|
||||
}
|
||||
|
||||
@@ -330,11 +352,19 @@ static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
|
||||
free(body);
|
||||
if (root == NULL) {
|
||||
ESP_LOGE(TAG, "JSON parse failed for '%s'", card_name);
|
||||
if (signal_ui) {
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = EVT_CARD_FAILED;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON *obj = cJSON_GetObjectItem(root, "object");
|
||||
if (obj && cJSON_IsString(obj) && strcmp(obj->valuestring, "error") == 0) {
|
||||
if (signal_ui) {
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = EVT_CARD_FAILED;
|
||||
}
|
||||
ESP_LOGE(TAG, "Scryfall API error: %s",
|
||||
get_string(cJSON_GetObjectItem(root, "details"), "unknown error"));
|
||||
cJSON_Delete(root);
|
||||
@@ -414,25 +444,55 @@ static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
/* 4. Download cover art (best effort) */
|
||||
/* Signal metadata ready immediately so UI can display card text and stats */
|
||||
if (signal_ui) {
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = (ret == ESP_OK) ? EVT_CARD_CACHED : EVT_CARD_FAILED;
|
||||
}
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
cJSON_Delete(root);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 4. Download cover art in background (best effort) */
|
||||
if (image_url && strlen(image_url) > 0) {
|
||||
char jpg_path[64];
|
||||
snprintf(jpg_path, sizeof(jpg_path), STORAGE_CARDS_DIR "/%s.jpg", uid);
|
||||
char enc_img[512];
|
||||
url_encode(image_url, enc_img, sizeof(enc_img));
|
||||
char proxy_url[600];
|
||||
/* wsrv.nl converts Scryfall's progressive JPEG to a lightweight baseline JPEG preserving original aspect ratio */
|
||||
snprintf(proxy_url, sizeof(proxy_url), "https://wsrv.nl/?url=%s&w=200&h=150&fit=inside&output=jpg", enc_img);
|
||||
/* wsrv.nl converts Scryfall's progressive JPEG to lightweight baseline JPEG (q=65 for 2-3x faster download & smaller flash footprint) */
|
||||
snprintf(proxy_url, sizeof(proxy_url), "https://wsrv.nl/?url=%s&w=200&h=150&fit=inside&output=jpg&q=65", enc_img);
|
||||
|
||||
if (signal_ui) {
|
||||
snprintf(s_fetching_img_uid, sizeof(s_fetching_img_uid), "%s", uid);
|
||||
s_image_fetching = true;
|
||||
}
|
||||
|
||||
esp_err_t dl = http_download_file(proxy_url, jpg_path);
|
||||
if (dl != ESP_OK) {
|
||||
ESP_LOGW(TAG, "proxied image download failed, trying direct: %s", image_url);
|
||||
dl = http_download_file(image_url, jpg_path);
|
||||
}
|
||||
|
||||
if (signal_ui) {
|
||||
s_image_fetching = false;
|
||||
snprintf(s_last_img_uid, sizeof(s_last_img_uid), "%s", uid);
|
||||
s_image_bits = (dl == ESP_OK) ? EVT_IMG_CACHED : EVT_IMG_FAILED;
|
||||
}
|
||||
|
||||
if (dl != ESP_OK) {
|
||||
ESP_LOGW(TAG, "image download failed: %s", image_url);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "image cached to %s", jpg_path);
|
||||
}
|
||||
} else {
|
||||
if (signal_ui) {
|
||||
s_image_fetching = false;
|
||||
snprintf(s_last_img_uid, sizeof(s_last_img_uid), "%s", uid);
|
||||
s_image_bits = EVT_IMG_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
@@ -445,10 +505,7 @@ static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
|
||||
|
||||
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;
|
||||
return scryfall_do_fetch(card_name, uid, true);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -486,8 +543,8 @@ static void prefetch_mappings(void)
|
||||
cJSON_ArrayForEach(item, root) {
|
||||
uid = item->string;
|
||||
if (uid == NULL || item->valuestring == NULL) continue;
|
||||
/* Call scryfall_do_fetch directly so we do not clobber UI fetch signaling */
|
||||
scryfall_do_fetch(item->valuestring, uid);
|
||||
/* Call scryfall_do_fetch directly with signal_ui=false so we do not clobber UI fetch signaling */
|
||||
scryfall_do_fetch(item->valuestring, uid, false);
|
||||
/* Rate-limit delay: Scryfall requests 50-100ms between queries */
|
||||
vTaskDelay(pdMS_TO_TICKS(150));
|
||||
}
|
||||
@@ -567,4 +624,35 @@ const char *scryfall_last_result_uid(void)
|
||||
void scryfall_consume_result(void)
|
||||
{
|
||||
s_result_bits = 0;
|
||||
}
|
||||
|
||||
bool scryfall_has_pending_image(void)
|
||||
{
|
||||
return (s_image_bits & (EVT_IMG_CACHED | EVT_IMG_FAILED)) != 0;
|
||||
}
|
||||
|
||||
bool scryfall_last_image_success(void)
|
||||
{
|
||||
return (s_image_bits & EVT_IMG_CACHED) != 0;
|
||||
}
|
||||
|
||||
const char *scryfall_last_image_uid(void)
|
||||
{
|
||||
return s_last_img_uid;
|
||||
}
|
||||
|
||||
void scryfall_consume_image(void)
|
||||
{
|
||||
s_image_bits = 0;
|
||||
}
|
||||
|
||||
bool scryfall_is_fetching_image(const char *uid)
|
||||
{
|
||||
if (!s_image_fetching) {
|
||||
return false;
|
||||
}
|
||||
if (uid == NULL || uid[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
return (strcasecmp(s_fetching_img_uid, uid) == 0);
|
||||
}
|
||||
Reference in New Issue
Block a user