various bug fixes
This commit is contained in:
@@ -29,7 +29,7 @@ static const char *TAG = "scryfall";
|
||||
#define MAX_ATTEMPTS (2)
|
||||
|
||||
#define JSON_BUF_MAX (128 * 1024)
|
||||
#define NAME_MAX (128)
|
||||
#define CARD_NAME_MAX (128)
|
||||
#define UID_MAX (24)
|
||||
#define FIELD_STR_MAX (256)
|
||||
#define ORACLE_MAX (768)
|
||||
@@ -38,7 +38,7 @@ static const char *TAG = "scryfall";
|
||||
#define EVT_CARD_CACHED (1U << 0)
|
||||
|
||||
typedef struct {
|
||||
char name[NAME_MAX];
|
||||
char name[CARD_NAME_MAX];
|
||||
char uid[UID_MAX];
|
||||
} fetch_request_t;
|
||||
|
||||
@@ -163,13 +163,20 @@ static esp_err_t http_request(const char *url, http_sink_t *sink)
|
||||
}
|
||||
|
||||
esp_http_client_set_method(client, HTTP_METHOD_GET);
|
||||
esp_http_client_set_header(client, "User-Agent", "MTGCompanion/1.0");
|
||||
esp_http_client_set_header(client, "Accept", "*/*");
|
||||
|
||||
esp_err_t ret = esp_http_client_perform(client);
|
||||
int status = esp_http_client_get_status_code(client);
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
ESP_LOGD(TAG, "GET %s -> %s", url, esp_err_to_name(ret));
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGD(TAG, "GET %s -> %s (status %d)", url, esp_err_to_name(ret), status);
|
||||
if (ret == ESP_OK && status >= 200 && status < 300) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (ret == ESP_OK && (status < 200 || status >= 300)) {
|
||||
ESP_LOGW(TAG, "HTTP GET returned status %d for %s", status, url);
|
||||
}
|
||||
if (sink->err != ESP_OK) {
|
||||
return sink->err;
|
||||
}
|
||||
@@ -227,14 +234,48 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
{
|
||||
char json_path[64];
|
||||
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
|
||||
char jpg_path[64];
|
||||
snprintf(jpg_path, sizeof(jpg_path), STORAGE_CARDS_DIR "/%s.jpg", uid);
|
||||
|
||||
if (storage_file_exists(json_path)) {
|
||||
ESP_LOGI(TAG, "cache hit %s", json_path);
|
||||
return ESP_OK;
|
||||
char *chk = NULL;
|
||||
size_t chk_len = 0;
|
||||
bool valid = false;
|
||||
if (storage_read_text(json_path, &chk, &chk_len) == ESP_OK && chk != NULL) {
|
||||
cJSON *cj = cJSON_Parse(chk);
|
||||
if (cj) {
|
||||
cJSON *n = cJSON_GetObjectItem(cj, "name");
|
||||
cJSON *t = cJSON_GetObjectItem(cj, "type");
|
||||
cJSON *o = cJSON_GetObjectItem(cj, "oracle");
|
||||
/* Verify name matches the requested card name */
|
||||
if (n && n->valuestring && strcasecmp(n->valuestring, card_name) == 0) {
|
||||
if ((t && t->valuestring && strlen(t->valuestring) > 0) ||
|
||||
(o && o->valuestring && strlen(o->valuestring) > 0)) {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
cJSON_Delete(cj);
|
||||
}
|
||||
free(chk);
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "cache entry %s is invalid or card mismatch (expected '%s'), re-fetching...", json_path, card_name);
|
||||
remove(json_path);
|
||||
remove(jpg_path);
|
||||
}
|
||||
}
|
||||
|
||||
/* 1. GET card JSON */
|
||||
char url[512];
|
||||
char enc[NAME_MAX * 3];
|
||||
char enc[CARD_NAME_MAX * 3];
|
||||
url_encode(card_name, enc, sizeof(enc));
|
||||
snprintf(url, sizeof(url), SCRYFALL_API SCRYFALL_CARD_ARTS_URL "%s", enc);
|
||||
|
||||
@@ -253,6 +294,14 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON *obj = cJSON_GetObjectItem(root, "object");
|
||||
if (obj && cJSON_IsString(obj) && strcmp(obj->valuestring, "error") == 0) {
|
||||
ESP_LOGE(TAG, "Scryfall API error: %s",
|
||||
get_string(cJSON_GetObjectItem(root, "details"), "unknown error"));
|
||||
cJSON_Delete(root);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* 2. Extract fields */
|
||||
const char *name = get_string(cJSON_GetObjectItem(root, "name"), card_name);
|
||||
const char *mana = NULL;
|
||||
@@ -265,40 +314,79 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
const char *type_line = get_string(cJSON_GetObjectItem(root, "type_line"), "");
|
||||
const char *power = get_string(cJSON_GetObjectItem(root, "power"), "");
|
||||
const char *toughness = get_string(cJSON_GetObjectItem(root, "toughness"), "");
|
||||
const char *loyalty = get_string(cJSON_GetObjectItem(root, "loyalty"), "");
|
||||
const char *oracle = get_string(cJSON_GetObjectItem(root, "oracle_text"), "");
|
||||
const char *image_url = NULL;
|
||||
cJSON *uris = cJSON_GetObjectItem(root, "image_uris");
|
||||
|
||||
cJSON *faces = cJSON_GetObjectItem(root, "card_faces");
|
||||
if (faces && cJSON_IsArray(faces) && cJSON_GetArraySize(faces) > 0) {
|
||||
cJSON *face0 = cJSON_GetArrayItem(faces, 0);
|
||||
if (face0) {
|
||||
if (uris == NULL) {
|
||||
uris = cJSON_GetObjectItem(face0, "image_uris");
|
||||
}
|
||||
if (strlen(oracle) == 0) {
|
||||
oracle = get_string(cJSON_GetObjectItem(face0, "oracle_text"), "");
|
||||
}
|
||||
if (strlen(mana) == 0) {
|
||||
mana = get_string(cJSON_GetObjectItem(face0, "mana_cost"), "");
|
||||
}
|
||||
if (strlen(type_line) == 0) {
|
||||
type_line = get_string(cJSON_GetObjectItem(face0, "type_line"), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uris) {
|
||||
image_url = get_string(cJSON_GetObjectItem(uris, "normal"), "");
|
||||
image_url = get_string(cJSON_GetObjectItem(uris, "art_crop"), "");
|
||||
if (strlen(image_url) == 0) {
|
||||
image_url = get_string(cJSON_GetObjectItem(uris, "normal"), "");
|
||||
}
|
||||
}
|
||||
const char *price_usd = NULL;
|
||||
cJSON *prices = cJSON_GetObjectItem(root, "prices");
|
||||
if (prices) {
|
||||
price_usd = get_string(cJSON_GetObjectItem(prices, "usd"), "");
|
||||
}
|
||||
const char *rarity = get_string(cJSON_GetObjectItem(root, "rarity"), "");
|
||||
|
||||
/* 3. Persist metadata */
|
||||
char meta[1536];
|
||||
snprintf(meta, sizeof(meta),
|
||||
"{\n"
|
||||
" \"name\": \"%s\",\n"
|
||||
" \"mana_cost\": \"%s\",\n"
|
||||
" \"type\": \"%s\",\n"
|
||||
" \"power\": \"%s\",\n"
|
||||
" \"toughness\": \"%s\",\n"
|
||||
" \"oracle\": \"%s\",\n"
|
||||
" \"price_usd\": \"%s\"\n"
|
||||
"}\n",
|
||||
name, mana, type_line, power, toughness, oracle,
|
||||
price_usd ? price_usd : "");
|
||||
/* 3. Persist metadata with proper JSON escaping */
|
||||
cJSON *card_obj = cJSON_CreateObject();
|
||||
if (card_obj) {
|
||||
cJSON_AddStringToObject(card_obj, "name", name);
|
||||
cJSON_AddStringToObject(card_obj, "mana_cost", mana);
|
||||
cJSON_AddStringToObject(card_obj, "type", type_line);
|
||||
cJSON_AddStringToObject(card_obj, "rarity", rarity);
|
||||
cJSON_AddStringToObject(card_obj, "power", power);
|
||||
cJSON_AddStringToObject(card_obj, "toughness", toughness);
|
||||
cJSON_AddStringToObject(card_obj, "loyalty", loyalty);
|
||||
cJSON_AddStringToObject(card_obj, "oracle", oracle);
|
||||
cJSON_AddStringToObject(card_obj, "price_usd", price_usd ? price_usd : "");
|
||||
|
||||
ret = storage_write_text(json_path, meta);
|
||||
char *meta_str = cJSON_PrintUnformatted(card_obj);
|
||||
cJSON_Delete(card_obj);
|
||||
if (meta_str != NULL) {
|
||||
ret = storage_write_text(json_path, meta_str);
|
||||
free(meta_str);
|
||||
} else {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
}
|
||||
} else {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
/* 4. Download cover art (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);
|
||||
esp_err_t dl = http_download_file(image_url, jpg_path);
|
||||
char proxy_url[600];
|
||||
/* wsrv.nl converts Scryfall's progressive JPEG to a lightweight 200x110 baseline JPEG */
|
||||
snprintf(proxy_url, sizeof(proxy_url), "https://wsrv.nl/?url=%s&w=200&h=110&fit=cover&output=jpg", image_url);
|
||||
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 (dl != ESP_OK) {
|
||||
ESP_LOGW(TAG, "image download failed: %s", image_url);
|
||||
} else {
|
||||
@@ -341,13 +429,8 @@ static void prefetch_mappings(void)
|
||||
cJSON *item = NULL;
|
||||
cJSON_ArrayForEach(item, root) {
|
||||
uid = item->string;
|
||||
if (uid == NULL) continue;
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), STORAGE_CARDS_DIR "/%s.json", uid);
|
||||
if (!storage_file_exists(path)) {
|
||||
ESP_LOGI(TAG, "prefetching '%s' for %s", item->valuestring, uid);
|
||||
scryfall_ensure_cached(item->valuestring, uid);
|
||||
}
|
||||
if (uid == NULL || item->valuestring == NULL) continue;
|
||||
scryfall_ensure_cached(item->valuestring, uid);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
@@ -397,7 +480,7 @@ esp_err_t scryfall_fetch_request(const char *card_name, const char *uid)
|
||||
if (s_fetch_queue == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (strlen(card_name) >= NAME_MAX || strlen(uid) >= UID_MAX || strlen(card_name) == 0) {
|
||||
if (strlen(card_name) >= CARD_NAME_MAX || strlen(uid) >= UID_MAX || strlen(card_name) == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
fetch_request_t req;
|
||||
|
||||
Reference in New Issue
Block a user