refactor(panel): 前后端按模块拆分+屏幕/控制合并页 — 后端meowpanel.c 6372行拆src/*.c+mp.h(235函数原型,20端点output byte-identical,0warning)+前端拆mp_shared/mp_pages/meowpanel三层;屏幕·遥控合并(左屏实时画面+右MD3 SVG矢量按钮无emoji);重编后热更TF卡
This commit is contained in:
1 parent
f3ae06d7cd
commit
2dc4cad454
26 files changed
+9317
-2755
No files matched your search
@@ -0,0 +1,242 @@
|
||||
/* mp_apps.c — 应用域:MeowAPPs 扫描 + nyai 安装/导出/移除。 */
|
||||
#include "mp.h"
|
||||
|
||||
|
||||
int apps_scan_dir(const char *appdir, const char *name, sbuf_t *out, int want_detail)
|
||||
{
|
||||
char pj[1024];
|
||||
snprintf(pj, sizeof pj, "%s/%s/app.json", appdir, name);
|
||||
char buf[8192];
|
||||
if (read_small_file(pj, buf, sizeof buf) <= 0) {
|
||||
/* 解析失败项:invalid */
|
||||
if (!want_detail) {
|
||||
sA(out, "{\"package\":");
|
||||
sA_str(out, name);
|
||||
sA(out, ",\"name\":null,\"version\":null,\"size\":0,\"installed\":true,\"invalid\":true}");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
jv_t *root = jv_parse(buf, (int)strlen(buf));
|
||||
if (!root) {
|
||||
if (!want_detail) {
|
||||
sA(out, "{\"package\":");
|
||||
sA_str(out, name);
|
||||
sA(out, ",\"name\":null,\"version\":null,\"size\":0,\"installed\":true,\"invalid\":true}");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
jv_t *jpkg = jv_obj_get(root, "package");
|
||||
jv_t *jname = jv_obj_get(root, "name");
|
||||
jv_t *jver = jv_obj_get(root, "version");
|
||||
jv_t *jexec = jv_obj_get(root, "exec");
|
||||
jv_t *jfw = jv_obj_get(root, "framework");
|
||||
jv_t *jim = jv_obj_get(root, "immersive");
|
||||
jv_t *jfull = jv_obj_get(root, "fullscreen");
|
||||
const char *pkg = (jpkg && jpkg->type == J_STR) ? jpkg->str : name;
|
||||
const char *nm = (jname && jname->type == J_STR) ? jname->str : "";
|
||||
const char *ver = (jver && jver->type == J_STR) ? jver->str : "";
|
||||
const char *exec = (jexec && jexec->type == J_STR) ? jexec->str : "";
|
||||
/* 目录大小(递归统计,限 8 层防爆) -- 简化:stat 直接子项合计 */
|
||||
long long dsize = 0;
|
||||
snprintf(pj, sizeof pj, "%s/%s", appdir, name);
|
||||
DIR *d = opendir(pj);
|
||||
if (d) {
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d))) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
char full[1024];
|
||||
snprintf(full, sizeof full, "%s/%s/%s", appdir, name, e->d_name);
|
||||
struct stat st;
|
||||
if (stat(full, &st) == 0) dsize += (long long)st.st_size;
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
if (!want_detail) {
|
||||
sA(out, "{\"package\":");
|
||||
sA_str(out, pkg);
|
||||
sA(out, ",\"name\":");
|
||||
sA_str(out, nm[0] ? nm : NULL);
|
||||
sA(out, ",\"version\":");
|
||||
sA_str(out, ver[0] ? ver : NULL);
|
||||
sA(out, ",\"exec\":");
|
||||
sA_str(out, exec);
|
||||
sA(out, ",\"framework\":%lld,\"immersive\":%lld,\"size\":%lld,\"installed\":true,\"mtime\":%lld}",
|
||||
jfw ? jfw->inum : 0, jim ? jim->inum : 0, dsize, (long long)time(NULL));
|
||||
} else {
|
||||
sA(out, "{\"package\":");
|
||||
sA_str(out, pkg);
|
||||
sA(out, ",\"name\":");
|
||||
sA_str(out, nm[0] ? nm : NULL);
|
||||
sA(out, ",\"version\":");
|
||||
sA_str(out, ver[0] ? ver : NULL);
|
||||
sA(out, ",\"exec\":");
|
||||
sA_str(out, exec);
|
||||
sA(out, ",\"framework\":%lld,\"immersive\":%lld,\"fullscreen\":%lld,\"size\":%lld,\"path\":",
|
||||
jfw ? jfw->inum : 0, jim ? jim->inum : 0, jfull ? jfull->inum : 0, dsize);
|
||||
sA_str(out, pj);
|
||||
sA(out, ",\"routes\":{},\"services\":[],\"depends\":[],\"description\":\"\"}");
|
||||
}
|
||||
jv_free(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_apps_list(int sock)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
DIR *d = opendir(MEOWAPPS_DIR);
|
||||
if (!d) return send_err(sock, 404, "NOT_FOUND", "MeowAPPs 目录不存在");
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"count\":0,\"apps\":[");
|
||||
int first = 1, count = 0;
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d))) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
if (!strcmp(e->d_name, "fonts") || !strcmp(e->d_name, "logs") || !strcmp(e->d_name, "backgrounds")) continue;
|
||||
if (strstr(e->d_name, ".bak")) continue;
|
||||
char full[1024];
|
||||
snprintf(full, sizeof full, "%s/%s", MEOWAPPS_DIR, e->d_name);
|
||||
struct stat st;
|
||||
if (stat(full, &st) < 0 || !S_ISDIR(st.st_mode)) continue;
|
||||
if (!first) sA(&s, ",");
|
||||
first = 0;
|
||||
apps_scan_dir(MEOWAPPS_DIR, e->d_name, &s, 0);
|
||||
count++;
|
||||
}
|
||||
closedir(d);
|
||||
sA(&s, "]}");
|
||||
/* count 前端不强制,但协议要求;用两次扫描不可取,故以 "count" 占位并回填:改为在 s 中替换 */
|
||||
/* 简化:直接输出 apps 数组,前端只读 d.apps(count 忽略)。 */
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
(void)count;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int api_apps_info(int sock, const char *pkg)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
if (strchr(pkg, '/') || strchr(pkg, ':')) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||||
char appdir[1024];
|
||||
snprintf(appdir, sizeof appdir, "%s/%s", MEOWAPPS_DIR, pkg);
|
||||
struct stat st;
|
||||
if (stat(appdir, &st) < 0 || !S_ISDIR(st.st_mode)) return send_err(sock, 404, "NOT_FOUND", "应用未安装");
|
||||
sbuf_t s = {0};
|
||||
if (apps_scan_dir(MEOWAPPS_DIR, pkg, &s, 1) < 0)
|
||||
return send_err(sock, 404, "NOT_FOUND", "app.json 解析失败");
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
const char *nyai_bin(void)
|
||||
{
|
||||
/* nyai 已进固件(/usr/bin,权威);旧版固件回退卡上 MeowOS/opt/bin */
|
||||
if (!access(NYAI_PATH, X_OK)) return NYAI_PATH;
|
||||
return NYAI_PATH_CARD;
|
||||
}
|
||||
|
||||
int api_apps_install(int sock, const req_t *r)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
if (access(nyai_bin(), X_OK) < 0) return send_err(sock, 500, "INTERNAL", "nyai 不可用(固件与卡上均无)");
|
||||
char mpkpath[1024];
|
||||
int ispath = 0;
|
||||
if (strncasecmp(r->content_type, "multipart/form-data", 19) != 0) {
|
||||
/* JSON {path} */
|
||||
if (r->body && r->body[0]) {
|
||||
jv_t *root = jv_parse(r->body, (int)r->body_len);
|
||||
if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON");
|
||||
jv_t *pj = jv_obj_get(root, "path");
|
||||
if (pj && pj->type == J_STR) {
|
||||
snprintf(mpkpath, sizeof mpkpath, "%s", pj->str);
|
||||
ispath = 1;
|
||||
}
|
||||
jv_free(root);
|
||||
}
|
||||
if (!ispath) return send_err(sock, 400, "BAD_REQUEST", "缺 path 或 multipart file");
|
||||
} else {
|
||||
/* 保存上传 file 到临时文件 */
|
||||
snprintf(mpkpath, sizeof mpkpath, "%s/tmp", TF_CARD_DIR);
|
||||
mkdir(mpkpath, 0777);
|
||||
char tmp[1024];
|
||||
snprintf(tmp, sizeof tmp, "%s/tmp/upload.mpk", TF_CARD_DIR);
|
||||
if (multipart_extract_file(r, tmp) < 0) return send_err(sock, 400, "BAD_REQUEST", "上传解析失败");
|
||||
snprintf(mpkpath, sizeof mpkpath, "%s", tmp);
|
||||
}
|
||||
if (access(mpkpath, R_OK) < 0) return send_err(sock, 404, "NOT_FOUND", ".mpk 文件不存在");
|
||||
char cmd[1400];
|
||||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin '%s' install '%s' 2>&1", nyai_bin(), mpkpath);
|
||||
char out[4096];
|
||||
int code = run_cmd(cmd, out, sizeof out);
|
||||
if (code != 0) return send_err(sock, 422, "UNSUPPORTED", out[0] ? out : "nyai install 失败");
|
||||
/* 包名 = 文件名茎;版本从 app.json 读 */
|
||||
char stem[256];
|
||||
const char *base = strrchr(mpkpath, '/');
|
||||
base = base ? base + 1 : mpkpath;
|
||||
snprintf(stem, sizeof stem, "%.*s", (int)(sizeof stem - 1), base);
|
||||
char *dot = strrchr(stem, '.');
|
||||
if (dot) *dot = 0;
|
||||
char appdir[1024], pj[1100];
|
||||
snprintf(appdir, sizeof appdir, "%s/%s", MEOWAPPS_DIR, stem);
|
||||
jv_t *approot = NULL;
|
||||
char version[16] = "";
|
||||
int upgraded = 0;
|
||||
snprintf(pj, sizeof pj, "%s/app.json", appdir);
|
||||
char jbuf[8192];
|
||||
if (access(pj, R_OK) == 0 && read_small_file(pj, jbuf, sizeof jbuf) > 0)
|
||||
approot = jv_parse(jbuf, (int)strlen(jbuf));
|
||||
if (approot) {
|
||||
jv_t *v = jv_obj_get(approot, "version");
|
||||
if (v && v->type == J_STR) snprintf(version, sizeof version, "%s", v->str);
|
||||
jv_free(approot);
|
||||
}
|
||||
if (access(mpkpath, R_OK) == 0 && ispath && strstr(out, "upgrad")) upgraded = 1;
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"package\":");
|
||||
sA_str(&s, stem);
|
||||
sA(&s, ",\"version\":");
|
||||
sA_str(&s, version[0] ? version : NULL);
|
||||
sA(&s, ",\"installed\":true,\"upgraded\":%s,\"message\":\"已安装\"}", upgraded ? "true" : "false");
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int api_apps_export(int sock, const char *pkg)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
if (strchr(pkg, '/') || strchr(pkg, ':')) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||||
char tmp[1100];
|
||||
char card_tmp[1200];
|
||||
snprintf(card_tmp, sizeof card_tmp, "%s/tmp", TF_CARD_DIR);
|
||||
mkdir(card_tmp, 0777);
|
||||
snprintf(tmp, sizeof tmp, "%s/tmp/%s.mpk", TF_CARD_DIR, pkg);
|
||||
unlink(tmp);
|
||||
char cmd[1400];
|
||||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin '%s' export '%s' '%s' 2>&1", nyai_bin(), pkg, tmp);
|
||||
char out[1024];
|
||||
int code = run_cmd(cmd, out, sizeof out);
|
||||
if (code != 0 || access(tmp, R_OK) < 0) return send_err(sock, 500, "INTERNAL", out[0] ? out : "nyai export 失败");
|
||||
char dn[256];
|
||||
snprintf(dn, sizeof dn, "%s.mpk", pkg);
|
||||
return send_file(sock, 200, "application/octet-stream", tmp, dn);
|
||||
}
|
||||
|
||||
int api_apps_remove(int sock, const char *pkg, const req_t *r)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
if (strchr(pkg, '/') || strchr(pkg, ':')) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||||
char purgebuf[8] = "0";
|
||||
if (r->query[0]) qparam(r->query, "purge", purgebuf, sizeof purgebuf);
|
||||
int use_purge = atoi(purgebuf);
|
||||
char cmd[1400];
|
||||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin '%s' remove '%s'%s 2>&1", nyai_bin(), pkg, use_purge ? " --purge" : "");
|
||||
char out[1024];
|
||||
int code = run_cmd(cmd, out, sizeof out);
|
||||
if (code != 0) return send_err(sock, 500, "INTERNAL", out[0] ? out : "nyai remove 失败");
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"removed\":true,\"purged\":%s}", use_purge ? "true" : "false");
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
Reference in new issue
Block a user