299 lines
11 KiB
C
299 lines
11 KiB
C
/* mp_notif.c — 通知域:优先 launcher IPC 活跃集,回退 TF 归档(查询/撤/清)。 */
|
||
#include "mp.h"
|
||
|
||
|
||
int notif_csv_field(const char **pp, char *out, int outcap)
|
||
{
|
||
const char *p = *pp;
|
||
int o = 0;
|
||
if (*p == '"') {
|
||
p++;
|
||
while (*p && *p != '"') {
|
||
if (*p == '"' && p[1] == '"') {
|
||
if (o < outcap - 1) out[o++] = '"';
|
||
p += 2;
|
||
} else {
|
||
if (o < outcap - 1) out[o++] = *p;
|
||
p++;
|
||
}
|
||
}
|
||
if (*p == '"') p++;
|
||
} else {
|
||
while (*p && *p != ',' && *p != '\n' && *p != '\r') {
|
||
if (o < outcap - 1) out[o++] = *p;
|
||
p++;
|
||
}
|
||
}
|
||
if (o < outcap) { out[o] = 0; }
|
||
else if (outcap > 0) { out[outcap - 1] = 0; }
|
||
if (*p == ',') p++;
|
||
*pp = p;
|
||
return 0;
|
||
}
|
||
|
||
int notif_parse_line(const char *line, notif_rec_t *r)
|
||
{
|
||
const char *p = line;
|
||
char f1[32], f2[16], f6[8], f7[16], f11[8], f12[8], f13[8];
|
||
char app[40], icon[64], cat[40], sum[96], body[128], tgt[64];
|
||
notif_csv_field(&p, f1, sizeof f1);
|
||
notif_csv_field(&p, f2, sizeof f2);
|
||
notif_csv_field(&p, app, sizeof app);
|
||
notif_csv_field(&p, icon, sizeof icon);
|
||
notif_csv_field(&p, cat, sizeof cat);
|
||
notif_csv_field(&p, f6, sizeof f6);
|
||
notif_csv_field(&p, f7, sizeof f7);
|
||
notif_csv_field(&p, sum, sizeof sum);
|
||
notif_csv_field(&p, body, sizeof body);
|
||
notif_csv_field(&p, tgt, sizeof tgt);
|
||
notif_csv_field(&p, f11, sizeof f11);
|
||
notif_csv_field(&p, f12, sizeof f12);
|
||
notif_csv_field(&p, f13, sizeof f13);
|
||
r->ms = strtoll(f1, NULL, 10);
|
||
r->id = (unsigned)strtoul(f2, NULL, 10);
|
||
r->urgency = atoi(f6);
|
||
r->progress = atoi(f7);
|
||
r->ongoing = atoi(f11);
|
||
r->media = atoi(f12);
|
||
r->dismissed = atoi(f13);
|
||
snprintf(r->app_id, sizeof r->app_id, "%s", app);
|
||
snprintf(r->app_icon, sizeof r->app_icon, "%s", icon);
|
||
snprintf(r->category, sizeof r->category, "%s", cat);
|
||
snprintf(r->summary, sizeof r->summary, "%s", sum);
|
||
snprintf(r->body, sizeof r->body, "%s", body);
|
||
snprintf(r->target, sizeof r->target, "%s", tgt);
|
||
return 0;
|
||
}
|
||
|
||
int notif_load_list(notif_rec_t *recs, int maxrec)
|
||
{
|
||
FILE *f = fopen(g_notif_log, "r");
|
||
if (!f) return -1;
|
||
int total = 0, head = 0;
|
||
char line[1400];
|
||
notif_rec_t r;
|
||
while (fgets(line, sizeof line, f)) {
|
||
if (notif_parse_line(line, &r) != 0) continue;
|
||
recs[head] = r;
|
||
head = (head + 1) % maxrec;
|
||
if (total < maxrec) total++;
|
||
}
|
||
fclose(f);
|
||
if (total < maxrec) return total; /* 未满窗:recs[0..total-1] 即 oldest..newest */
|
||
/* 满窗:oldest 在 recs[head],重排为 oldest..newest 连续 */
|
||
notif_rec_t tmp[NOTIF_PREVIEW_MAX];
|
||
for (int i = 0; i < maxrec; i++) tmp[i] = recs[(head + i) % maxrec];
|
||
memcpy(recs, tmp, (size_t)maxrec * sizeof recs[0]);
|
||
return total;
|
||
}
|
||
|
||
const char *notif_app_name(const char *app_id)
|
||
{
|
||
if (!strcmp(app_id, "system")) return "系统";
|
||
return app_id;
|
||
}
|
||
|
||
void notif_emit_item(sbuf_t *out, const notif_rec_t *r)
|
||
{
|
||
sA(out, "{\"id\":%u,\"app_id\":", r->id);
|
||
sA_str(out, r->app_id);
|
||
sA(out, ",\"app_name\":");
|
||
sA_str(out, notif_app_name(r->app_id));
|
||
sA(out, ",\"app_icon\":");
|
||
sA_str(out, r->app_icon);
|
||
sA(out, ",\"category\":");
|
||
sA_str(out, r->category);
|
||
sA(out, ",\"summary\":");
|
||
sA_str(out, r->summary);
|
||
sA(out, ",\"body\":");
|
||
sA_str(out, r->body);
|
||
sA(out, ",\"urgency\":%d,\"ts\":%lld,\"progress\":", r->urgency, r->ms > 0 ? r->ms / 1000 : 0);
|
||
if (r->progress >= 0) sA(out, "%d", r->progress); else sA(out, "null");
|
||
sA(out, ",\"ongoing\":%s,\"media\":%s,\"target_activity\":",
|
||
r->ongoing ? "true" : "false", r->media ? "true" : "false");
|
||
sA_str(out, r->target);
|
||
sA(out, ",\"dismissed\":%s}", r->dismissed ? "true" : "false");
|
||
}
|
||
|
||
int notif_ipc_query(notif_rec_t *recs, int maxrec)
|
||
{
|
||
int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS);
|
||
if (fd < 0) return -1;
|
||
int cnt = -1;
|
||
if (ipc_hello(fd) < 0) goto done;
|
||
if (send_frame(fd, MSG_NOTIFY_QUERY, 8, NULL, 0) < 0) goto done;
|
||
uint8_t buf[MEOW_IPC_MAX + MEOW_IPC_HDR_LEN];
|
||
uint16_t type; uint32_t rid;
|
||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||
if (n < (int)sizeof(m_notify_query_result_t)) goto done;
|
||
if (type != MSG_NOTIFY_QUERY_RESULT) goto done;
|
||
m_notify_query_result_t qr;
|
||
memcpy(&qr, buf, sizeof qr);
|
||
if (qr.code != MEOW_OK) goto done;
|
||
uint16_t tl = qr.text_len;
|
||
if (tl >= sizeof qr.text) tl = (uint16_t)(sizeof qr.text - 1);
|
||
if (tl == 0) { cnt = 0; goto done; } /* 无活跃通知 */
|
||
jv_t *arr = jv_parse(qr.text, tl);
|
||
if (!arr) goto done;
|
||
if (arr->type != J_ARR) { jv_free(arr); goto done; }
|
||
int c = 0;
|
||
for (jv_t *e = arr->child; e && c < maxrec; e = e->next) {
|
||
if (e->type != J_OBJ) continue;
|
||
notif_rec_t r; memset(&r, 0, sizeof r);
|
||
r.progress = -1;
|
||
r.urgency = 0;
|
||
jv_t *v;
|
||
if ((v = jv_obj_get(e, "id")) && v->type == J_NUM) r.id = (unsigned)v->inum;
|
||
if ((v = jv_obj_get(e, "app_id")) && v->type == J_STR) snprintf(r.app_id, sizeof r.app_id, "%s", v->str);
|
||
if ((v = jv_obj_get(e, "app_icon")) && v->type == J_STR) snprintf(r.app_icon, sizeof r.app_icon, "%s", v->str);
|
||
if ((v = jv_obj_get(e, "category")) && v->type == J_STR) snprintf(r.category, sizeof r.category, "%s", v->str);
|
||
if ((v = jv_obj_get(e, "summary")) && v->type == J_STR) snprintf(r.summary, sizeof r.summary, "%s", v->str);
|
||
if ((v = jv_obj_get(e, "body")) && v->type == J_STR) snprintf(r.body, sizeof r.body, "%s", v->str);
|
||
if ((v = jv_obj_get(e, "target_activity")) && v->type == J_STR) snprintf(r.target, sizeof r.target, "%s", v->str);
|
||
if ((v = jv_obj_get(e, "urgency")) && v->type == J_NUM) r.urgency = (int)v->inum;
|
||
/* ts 为 epoch 毫秒 → 存入 r.ms(emit 时 /1000 出秒,与 TF 同口径) */
|
||
if ((v = jv_obj_get(e, "ts")) && v->type == J_NUM) r.ms = (long long)v->inum;
|
||
if ((v = jv_obj_get(e, "progress")) && v->type == J_NUM) r.progress = (int)v->inum; /* <0=无;json 无该键则 -1 */
|
||
if ((v = jv_obj_get(e, "ongoing")) && v->type == J_NUM) r.ongoing = (int)v->inum;
|
||
if ((v = jv_obj_get(e, "media")) && v->type == J_NUM) r.media = (int)v->inum;
|
||
recs[c++] = r;
|
||
}
|
||
jv_free(arr);
|
||
/* 按 ms 升序(oldest..newest),与 TF 路径一致(emit 时倒序遍历= newest first)。 */
|
||
for (int i = 1; i < c; i++) { notif_rec_t key = recs[i]; int j = i - 1;
|
||
while (j >= 0 && recs[j].ms > key.ms) { recs[j + 1] = recs[j]; j--; } recs[j + 1] = key; }
|
||
cnt = c;
|
||
done:
|
||
close(fd);
|
||
return cnt;
|
||
}
|
||
|
||
int notif_ipc_dismiss(unsigned id)
|
||
{
|
||
int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS);
|
||
if (fd < 0) return -1;
|
||
int rc = -1;
|
||
if (ipc_hello(fd) < 0) goto done;
|
||
uint32_t nid = (uint32_t)id;
|
||
if (send_frame(fd, MSG_NOTIFY_DISMISS, 9, &nid, sizeof nid) < 0) goto done;
|
||
uint8_t buf[64];
|
||
uint16_t type; uint32_t rid;
|
||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||
if (n < (int)sizeof(m_ack_t)) goto done;
|
||
if (type != MSG_ACK) goto done;
|
||
m_ack_t a; memcpy(&a, buf, sizeof a);
|
||
rc = (a.code == MEOW_OK) ? 0 : -1;
|
||
done:
|
||
close(fd);
|
||
return rc;
|
||
}
|
||
|
||
int notif_ipc_dismiss_all(int *out_count)
|
||
{
|
||
int fd = ipc_connect_tmo(g_sock_launcher, IPC_TIMEOUT_MS);
|
||
if (fd < 0) return -1;
|
||
int rc = -1;
|
||
if (ipc_hello(fd) < 0) goto done;
|
||
if (send_frame(fd, MSG_NOTIFY_DISMISS_ALL, 10, NULL, 0) < 0) goto done;
|
||
uint8_t buf[64];
|
||
uint16_t type; uint32_t rid;
|
||
int n = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||
if (n < (int)sizeof(m_ack_t)) goto done;
|
||
if (type != MSG_ACK) goto done;
|
||
m_ack_t a; memcpy(&a, buf, sizeof a);
|
||
if (a.code == MEOW_OK) { if (out_count) *out_count = (int)a.status; rc = 0; }
|
||
done:
|
||
close(fd);
|
||
return rc;
|
||
}
|
||
|
||
int api_notifications(int sock)
|
||
{
|
||
notif_rec_t recs[NOTIF_PREVIEW_MAX];
|
||
const char *source = "ipc";
|
||
int n = notif_ipc_query(recs, NOTIF_PREVIEW_MAX);
|
||
if (n < 0) { /* IPC 不可达 → 回退 TF 归档 */
|
||
n = notif_load_list(recs, NOTIF_PREVIEW_MAX);
|
||
source = (n < 0) ? "none" : "tf";
|
||
if (n < 0) n = 0;
|
||
}
|
||
sbuf_t arr = {0};
|
||
int first = 1;
|
||
for (int i = n - 1; i >= 0; i--) { /* newest first */
|
||
if (!first) sA(&arr, ",");
|
||
first = 0;
|
||
notif_emit_item(&arr, &recs[i]);
|
||
}
|
||
sbuf_t s = {0};
|
||
sA(&s, "{\"count\":%d,\"source\":\"%s\",\"items\":[", n, source);
|
||
if (arr.n) sA_append(&s, arr.b, arr.n);
|
||
sA(&s, "],\"notifications\":[");
|
||
if (arr.n) sA_append(&s, arr.b, arr.n);
|
||
sA(&s, "]}");
|
||
sbuf_free(&arr);
|
||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||
sbuf_free(&s);
|
||
return rc;
|
||
}
|
||
|
||
int notif_rewrite(int mode, unsigned del_id)
|
||
{
|
||
if (!g_notif_log || !g_notif_log[0]) return -1;
|
||
char tmppath[1400];
|
||
snprintf(tmppath, sizeof tmppath, "%s.panel.tmp", g_notif_log);
|
||
FILE *in = fopen(g_notif_log, "r");
|
||
if (!in) return 0;
|
||
FILE *out = fopen(tmppath, "w");
|
||
if (!out) { fclose(in); return -1; }
|
||
int removed = 0;
|
||
char line[1400];
|
||
while (fgets(line, sizeof line, in)) {
|
||
notif_rec_t r;
|
||
int parsed = (notif_parse_line(line, &r) == 0);
|
||
int drop = 0;
|
||
if (mode == 1) {
|
||
if (!parsed) drop = 1;
|
||
else drop = !(r.ongoing || r.media || r.progress >= 0 || !strcmp(r.category, "media"));
|
||
} else {
|
||
if (parsed && r.id == del_id) drop = 1;
|
||
}
|
||
if (drop) removed++;
|
||
else fputs(line, out);
|
||
}
|
||
fclose(in);
|
||
if (fclose(out) != 0) return -1;
|
||
if (rename(tmppath, g_notif_log) < 0) { unlink(tmppath); return -1; }
|
||
return removed;
|
||
}
|
||
|
||
int api_notification_delete(int sock, unsigned id)
|
||
{
|
||
/* 优先 IPC 联动 launcher 内存(launcher 撤下时自动归档留档);IPC 不可达/未受理回退 TF 重写。 */
|
||
if (notif_ipc_dismiss(id) == 0) {
|
||
char d[64];
|
||
snprintf(d, sizeof d, "{\"id\":%u,\"dismissed\":true}", id);
|
||
return send_json(sock, 200, d);
|
||
}
|
||
int removed = notif_rewrite(0, id);
|
||
if (removed < 0) return send_err(sock, 500, "INTERNAL", "写通知存储失败");
|
||
if (removed == 0) return send_err(sock, 404, "NOT_FOUND", "通知不存在");
|
||
char d[64];
|
||
snprintf(d, sizeof d, "{\"id\":%u,\"dismissed\":true}", id);
|
||
return send_json(sock, 200, d);
|
||
}
|
||
|
||
int api_notification_dismiss_all(int sock)
|
||
{
|
||
int cleared = 0;
|
||
if (notif_ipc_dismiss_all(&cleared) == 0) { /* 联动 launcher 内存,返回清除数 */
|
||
char d[64];
|
||
snprintf(d, sizeof d, "{\"dismissed\":%d}", cleared);
|
||
return send_json(sock, 200, d);
|
||
}
|
||
int removed = notif_rewrite(1, 0); /* IPC 不可达 → 回退重写 TF 日志 */
|
||
if (removed < 0) return send_err(sock, 500, "INTERNAL", "写通知存储失败");
|
||
char d[64];
|
||
snprintf(d, sizeof d, "{\"dismissed\":%d}", removed);
|
||
return send_json(sock, 200, d);
|
||
}
|