三特性: 字体分层(gen_font.py --charset + meow_font_reload_tier + font.load_full + 尺寸标记) + 全屏机制/状态栏收合(meow_ui_sys/input) + 通知#6 TF转储(CSV+10MB+开机清板载+内存表)
This commit is contained in:
1 parent
0b4d1d3215
commit
a2673d5922
18 files changed
+819
-80
No files matched your search
@@ -24,6 +24,11 @@
|
||||
#include <strings.h> /* strcasecmp */
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h> /* mkdir / stat */
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h> /* open/close/write/pread/lseek/ftruncate */
|
||||
#include <fcntl.h> /* O_* */
|
||||
#include <errno.h> /* errno/strerror */
|
||||
|
||||
#include "meow_notif.h"
|
||||
#include "meow_settings.h" /* B2a:读 notify.* 配额(MEOW_OK/MEOW_ERR_*) */
|
||||
@@ -89,6 +94,192 @@ static void n_strncpy(char *dst, const char *src, size_t len)
|
||||
dst[len - 1] = 0;
|
||||
}
|
||||
|
||||
/* ================= TF 卡转储(通知#6 / M2c,NOTIFICATION_SYSTEM.md §5/§8)=================
|
||||
* 每条通知一行 CSV 追加到 <卡根>/MeowOS/notifications/notifications.log,并按日志总大小
|
||||
* 滚动截断(保留最新 10MB)。系统文件统一放 <卡根>/MeowOS/ 下(TF_CARD_LAYOUT.md)。 */
|
||||
#define MEOW_NOTIF_TF_DIR "/usr/data/mnt/sd_0/MeowOS/notifications"
|
||||
#define MEOW_NOTIF_TF_LOG MEOW_NOTIF_TF_DIR "/notifications.log"
|
||||
#define MEOW_NOTIF_LOG_MAX_BYTES ((size_t)10 * 1024 * 1024) /* §8:保留最新 10MB */
|
||||
|
||||
/* 单行 CSV 上限:app_id(32)/category(24)/summary(64)/body(96)/target_activity(48)
|
||||
* 全引号转义后仍远小于此。 */
|
||||
#define MEOW_NOTIF_CSV_LINE_MAX 1024
|
||||
|
||||
static const char *g_tf_path = MEOW_NOTIF_TF_LOG;
|
||||
static size_t g_tf_max = MEOW_NOTIF_LOG_MAX_BYTES;
|
||||
|
||||
/* 递归 mkdir -p:沿 path 把每一级目录建出来(EEXIST 无害;无卡/只读则静默失败) */
|
||||
static void tf_mkdirs(const char *path)
|
||||
{
|
||||
char tmp[512];
|
||||
n_strncpy(tmp, path, sizeof tmp);
|
||||
for (char *p = tmp + 1; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
mkdir(tmp, 0755);
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 全量写入(处理短写/EINTR) */
|
||||
static int tf_write_all(int fd, const char *buf, size_t len)
|
||||
{
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
ssize_t w = write(fd, buf + off, len - off);
|
||||
if (w < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
off += (size_t)w;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* CSV 字段:去换行(\r\n->空格,保证单行)+ 转义(双引号包裹,内部 " 翻倍) */
|
||||
static void csv_field(char *dst, size_t dstsz, const char *src)
|
||||
{
|
||||
if (!dst || dstsz == 0) return;
|
||||
size_t o = 0;
|
||||
dst[o++] = '"';
|
||||
for (const char *p = src ? src : ""; *p; p++) {
|
||||
if (o + 4 > dstsz) break; /* 留 2 字节给翻倍引号 + 收尾引号 + NUL */
|
||||
if (*p == '\r' || *p == '\n') dst[o++] = ' '; /* 去换行 */
|
||||
else if (*p == '"') { dst[o++] = '"'; dst[o++] = '"'; }
|
||||
else dst[o++] = *p;
|
||||
}
|
||||
if (o + 1 >= dstsz) o = dstsz - 1;
|
||||
dst[o++] = '"';
|
||||
dst[o] = '\0';
|
||||
}
|
||||
|
||||
/* 生成一行 CSV:
|
||||
* timestamp,id,app_id,category,urgency,title,body,target_activity,ongoing,media,dismissed
|
||||
* title<-summary;urgency:meow_notif 结构无该字段(保持既有 API),列值留空 ""。
|
||||
* 返回写入长度(不含 NUL)。 */
|
||||
static size_t tf_line(const meow_notif_t *n, char *buf, size_t bufsz)
|
||||
{
|
||||
char a_app [2 + 2 * MEOW_NOTIF_APP_ID_MAX + 2];
|
||||
char a_cat [2 + 2 * MEOW_NOTIF_CAT_MAX + 2];
|
||||
char a_sum [2 + 2 * MEOW_NOTIF_SUMMARY_MAX + 2];
|
||||
char a_body[2 + 2 * MEOW_NOTIF_BODY_MAX + 2];
|
||||
char a_tgt [2 + 2 * MEOW_NOTIF_ACTIVITY_MAX + 2];
|
||||
csv_field(a_app, sizeof a_app, n->app_id);
|
||||
csv_field(a_cat, sizeof a_cat, n->category);
|
||||
csv_field(a_sum, sizeof a_sum, n->summary);
|
||||
csv_field(a_body, sizeof a_body, n->body);
|
||||
csv_field(a_tgt, sizeof a_tgt, n->target_activity);
|
||||
int r = snprintf(buf, bufsz,
|
||||
"%llu,%u,%s,%s,\"\",%s,%s,%s,%u,%u,%u\n",
|
||||
(unsigned long long)n->created_ms,
|
||||
(unsigned)n->id,
|
||||
a_app, a_cat, a_sum, a_body, a_tgt,
|
||||
n->ongoing ? 1u : 0u,
|
||||
n->media ? 1u : 0u,
|
||||
n->dismissed ? 1u : 0u);
|
||||
if (r < 0) return 0;
|
||||
return ((size_t)r < bufsz) ? (size_t)r : bufsz - 1;
|
||||
}
|
||||
|
||||
/* 滚动截断:文件超过 g_tf_max 时删除最旧行(从尾部读窗口定位第一条换行作为保留起点),
|
||||
* 把结尾 ≤max 字节的完整行重写回文件头。窗口内无换行则保留整窗(只保证 ≤max)。 */
|
||||
static int tf_trim(const char *path)
|
||||
{
|
||||
int fd = open(path, O_RDWR);
|
||||
if (fd < 0) return -1;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0) { close(fd); return -1; }
|
||||
off_t size = st.st_size;
|
||||
if (size <= (off_t)g_tf_max) { close(fd); return 0; }
|
||||
|
||||
off_t start = size - (off_t)g_tf_max; /* 保留窗口起点 */
|
||||
off_t keep = start; /* 最终保留起点(行对齐后) */
|
||||
int found = 0;
|
||||
char chunk[8192];
|
||||
for (off_t pos = start; pos < size && !found; ) {
|
||||
size_t want = (size_t)((size - pos) < (off_t)sizeof chunk ? (size - pos) : (off_t)sizeof chunk);
|
||||
ssize_t rd = pread(fd, chunk, want, pos);
|
||||
if (rd <= 0) break;
|
||||
for (ssize_t i = 0; i < rd; i++) {
|
||||
if (chunk[i] == '\n') { keep = pos + i + 1; found = 1; break; }
|
||||
}
|
||||
pos += rd;
|
||||
if (rd < (ssize_t)want) break; /* 读到文件尾 */
|
||||
}
|
||||
|
||||
/* 重写:把 [keep, size) 拷贝回文件头,再 ftruncate 掉尾部旧数据 */
|
||||
off_t remain = size - keep;
|
||||
if (lseek(fd, 0, SEEK_SET) < 0) { close(fd); return -1; }
|
||||
off_t wpos = 0;
|
||||
char buf[8192];
|
||||
while (remain > 0) {
|
||||
size_t want = (size_t)(remain < (off_t)sizeof buf ? remain : (off_t)sizeof buf);
|
||||
ssize_t rd = pread(fd, buf, want, keep + wpos);
|
||||
if (rd <= 0) break;
|
||||
if (tf_write_all(fd, buf, (size_t)rd) < 0) { close(fd); return -1; }
|
||||
wpos += rd;
|
||||
remain -= rd;
|
||||
}
|
||||
ftruncate(fd, wpos);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 把一条通知追加为一行 CSV 到 TF 日志(含 mkdir -p 与 10MB 滚动截断)。
|
||||
* 返回 NOTIF_OK(0);无卡/不可写/参数非法返回负(降级:仅打点,不崩)。 */
|
||||
int meow_notif_dump_tf(const meow_notif_t *n)
|
||||
{
|
||||
if (!n) return NOTIF_ERR_INVAL;
|
||||
if (!g_tf_path || !g_tf_path[0]) return NOTIF_ERR_INVAL;
|
||||
tf_mkdirs(g_tf_path); /* 目录不存在则 mkdir -p*/
|
||||
|
||||
char line[MEOW_NOTIF_CSV_LINE_MAX];
|
||||
size_t len = tf_line(n, line, sizeof line);
|
||||
if (len == 0) return NOTIF_ERR_INVAL;
|
||||
|
||||
int fd = open(g_tf_path, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||||
if (fd < 0) {
|
||||
notif_log("tf: append open fail %s (%s)", g_tf_path, strerror(errno));
|
||||
return NOTIF_ERR_IO;
|
||||
}
|
||||
if (tf_write_all(fd, line, len) != 0) {
|
||||
close(fd);
|
||||
notif_log("tf: append write fail %s (%s)", g_tf_path, strerror(errno));
|
||||
return NOTIF_ERR_IO;
|
||||
}
|
||||
long size = -1;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == 0) size = (long)st.st_size;
|
||||
close(fd);
|
||||
|
||||
if (size > (long)g_tf_max) { /* >10MB:滚动截断,保留最新 */
|
||||
tf_trim(g_tf_path);
|
||||
notif_log("tf: rolled to <= %zuB", g_tf_max);
|
||||
}
|
||||
return NOTIF_OK;
|
||||
}
|
||||
|
||||
/* 开机清板载通知:清空转储日志文件,保留目录(通知#6 / §8:启动时清板载) */
|
||||
void meow_notif_clear_tf(void)
|
||||
{
|
||||
if (!g_tf_path || !g_tf_path[0]) return;
|
||||
tf_mkdirs(g_tf_path);
|
||||
int fd = open(g_tf_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) close(fd);
|
||||
else notif_log("tf: clear fail %s (%s)", g_tf_path, strerror(errno));
|
||||
}
|
||||
|
||||
/* 覆盖转储日志路径/滚动上限(宿主单测用;NULL/0 恢复默认) */
|
||||
void meow_notif_set_tf_path(const char *path)
|
||||
{
|
||||
g_tf_path = (path && path[0]) ? path : MEOW_NOTIF_TF_LOG;
|
||||
}
|
||||
void meow_notif_set_tf_max(size_t max_bytes)
|
||||
{
|
||||
g_tf_max = max_bytes ? max_bytes : MEOW_NOTIF_LOG_MAX_BYTES;
|
||||
}
|
||||
|
||||
/* ---- 配额读取(B2a;键缺省/取失败用默认值)---- */
|
||||
static void read_quota(void)
|
||||
{
|
||||
@@ -113,7 +304,11 @@ int meow_notif_init(void)
|
||||
g_dumped_total = 0;
|
||||
read_quota(); /* B2a:notify.* 配额 */
|
||||
g_inited = 1;
|
||||
notif_log("init: slots=%d default dump_cb=log-only (M2c -> TF)", MEOW_NOTIF_MAX_SLOTS);
|
||||
/* 通知#6:启动清板载通知 + 内存表(内存表上面已 memset;这里清 TF 转储日志,
|
||||
* 保留目录)。每会话从零开始(通知会话级),不被电源事件清(before_poweroff 只管内存)。 */
|
||||
meow_notif_clear_tf();
|
||||
notif_log("init: slots=%d tf_log=%s (dump_cb wired by launcher -> meow_notif_dump_tf)",
|
||||
MEOW_NOTIF_MAX_SLOTS, g_tf_path);
|
||||
return NOTIF_OK;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user