163 lines
4.6 KiB
C
163 lines
4.6 KiB
C
/* models.c - GET {base_url}/models 与响应体解析 */
|
|
#include "models.h"
|
|
#include "http.h"
|
|
#include "i18n.h"
|
|
#include "json.h"
|
|
#include "util.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define MODELS_MAX 512 /* 再多就不是给人挑的了, 多半是服务端出了什么问题 */
|
|
|
|
static int already_have(char **list, size_t n, const char *id) {
|
|
for (size_t i = 0; i < n; i++)
|
|
if (strcmp(list[i], id) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
char **models_parse(const char *json, size_t *out_n) {
|
|
if (out_n)
|
|
*out_n = 0;
|
|
if (!json || !*json)
|
|
return NULL;
|
|
|
|
JsonValue *root = json_parse(json, NULL, 0);
|
|
if (!root)
|
|
return NULL;
|
|
|
|
/* 标准形状是 {"data": [...]}; 也见过直接回一个裸数组的兼容实现 */
|
|
const JsonValue *arr = NULL;
|
|
if (root->type == JSON_ARRAY) {
|
|
arr = root;
|
|
} else {
|
|
JsonValue *data = json_object_get(root, "data");
|
|
if (data && data->type == JSON_ARRAY)
|
|
arr = data;
|
|
}
|
|
if (!arr) {
|
|
json_free(root);
|
|
return NULL;
|
|
}
|
|
|
|
size_t cap = json_array_size(arr) + 1;
|
|
if (cap > MODELS_MAX)
|
|
cap = MODELS_MAX;
|
|
char **list = xcalloc(cap ? cap : 1, sizeof *list);
|
|
size_t n = 0;
|
|
|
|
for (size_t i = 0; i < json_array_size(arr) && n < cap; i++) {
|
|
const JsonValue *item = json_array_get(arr, i);
|
|
/* 元素可能是字符串本身, 也可能是 {"id": "..."} */
|
|
const char *id = NULL;
|
|
if (item && item->type == JSON_STRING)
|
|
id = json_string_value(item);
|
|
else if (item)
|
|
id = json_get_string(item, "id");
|
|
|
|
if (!id || !*id)
|
|
continue;
|
|
if (already_have(list, n, id))
|
|
continue; /* 有些服务端会重复列 */
|
|
list[n++] = xstrdup(id);
|
|
}
|
|
|
|
json_free(root);
|
|
if (n == 0) {
|
|
free(list);
|
|
return NULL;
|
|
}
|
|
|
|
/* 保序: 服务端给的顺序是它自己的偏好, 我们不去替它排序 */
|
|
if (out_n)
|
|
*out_n = n;
|
|
return list;
|
|
}
|
|
|
|
int models_is_empty_list(const char *json) {
|
|
if (!json || !*json)
|
|
return 0;
|
|
JsonValue *root = json_parse(json, NULL, 0);
|
|
if (!root)
|
|
return 0;
|
|
|
|
const JsonValue *arr = NULL;
|
|
if (root->type == JSON_ARRAY) {
|
|
arr = root;
|
|
} else {
|
|
JsonValue *data = json_object_get(root, "data");
|
|
if (data && data->type == JSON_ARRAY)
|
|
arr = data;
|
|
}
|
|
int empty = (arr && json_array_size((JsonValue *)arr) == 0);
|
|
json_free(root);
|
|
return empty;
|
|
}
|
|
|
|
void models_free(char **list, size_t n) {
|
|
if (!list)
|
|
return;
|
|
for (size_t i = 0; i < n; i++)
|
|
free(list[i]);
|
|
free(list);
|
|
}
|
|
|
|
char **models_fetch(const LigentConfig *cfg, size_t *out_n, int verbose,
|
|
char *err, size_t errlen) {
|
|
if (out_n)
|
|
*out_n = 0;
|
|
if (err && errlen)
|
|
err[0] = '\0';
|
|
|
|
const char *bu = (cfg && cfg->base_url) ? cfg->base_url : "";
|
|
if (!*bu) {
|
|
if (err && errlen)
|
|
snprintf(err, errlen, "%s", t("models.err.no_base_url"));
|
|
return NULL;
|
|
}
|
|
size_t bl = strlen(bu);
|
|
while (bl > 0 && bu[bl - 1] == '/')
|
|
bl--;
|
|
char *url = xasprintf("%.*s/models", (int)bl, bu);
|
|
|
|
char *body = NULL;
|
|
long status = 0;
|
|
char buf[512];
|
|
/* 读列表是只读的, 值得重试; 用默认退避策略 */
|
|
HttpRetry rt = HTTP_RETRY_DEFAULT;
|
|
int rc = http_get(url, cfg->api_key, cfg->timeout, verbose, &rt, &body,
|
|
&status, NULL, buf, sizeof buf);
|
|
free(url);
|
|
|
|
char **list = NULL;
|
|
if (rc != 0) {
|
|
if (err && errlen)
|
|
snprintf(err, errlen, "%s",
|
|
buf[0] ? buf : t("models.err.request_failed"));
|
|
} else {
|
|
list = models_parse(body, out_n);
|
|
if (!list && err && errlen) {
|
|
if (models_is_empty_list(body))
|
|
snprintf(err, errlen, "%s",
|
|
tf("models.err.empty_list", "status", i18n_num(status),
|
|
NULL));
|
|
else if (body && *body) {
|
|
/* 只回显开头一段: 服务端偶尔回一大坨 HTML, 全塞进错误里没法读
|
|
*/
|
|
char head[121];
|
|
snprintf(head, sizeof head, "%.120s", body);
|
|
snprintf(err, errlen, "%s",
|
|
tf("models.err.unparsable", "status", i18n_num(status),
|
|
"body", head, NULL));
|
|
} else
|
|
snprintf(err, errlen, "%s",
|
|
tf("models.err.empty_body", "status", i18n_num(status),
|
|
NULL));
|
|
}
|
|
}
|
|
free(body);
|
|
return list;
|
|
}
|