- 新增顶层 tuku/ 工程(package dev.tuku.app、二进制 tuku):app.c/internal 头/logic/ui 分层、app.json、build/pack 脚本 - 书库:从 TF 卡根浏览文件夹,含图目录=一本书;logic/library 纯 C 扫描+自然排序(page2<page10) - 流式缩略图:后台线程解码+meow_img_downscale(96),首帧/滚动不阻塞;磁盘缓存 MeowData/<pkg>/cache(mtime 失效+LRU) - 列表 meow_listview 虚拟化 + meow_scroll fling/snap;行=缩略图+目录名+页数 - 主题:全部语义色 + meow_theme_add_listener 即时重绘;明暗/风格族两态可读 - 导航:meow_view page push/pop、TopAppBar ⚙→设置;reader/settings/lock 最小桩 - 接缝:mr_reader_get/set_direction(默认 RTL)、lock_view_should_show()(桩 0) - 宿主 gcc 测试 46 项 PASS;交叉构建静态 MIPS32r2 ELF、-Wall -Wextra 零警告
248 lines
8.0 KiB
C
248 lines
8.0 KiB
C
/* logic/library.c — 书库目录扫描 + 自然排序(纯 C)。
|
|
*
|
|
* 扫描分两步:
|
|
* 1) scan_info():递归探测一个目录是否「最终含图」,并带回直接图片数 / 含图子目录数 /
|
|
* 缩略图源(优先本目录自然序第一张,其次第一个含图子目录);
|
|
* 2) mr_library_scan():只对目标目录的**直接子目录**调用 scan_info(),把 nav=1 的目录
|
|
* 收进结果并自然排序——因此根视图不会列出后代条目,进入子目录时再重新扫描该层。
|
|
*
|
|
* 内存:条目数组倍增分配;路径/名称定长,随 mr_library_free() 释放。
|
|
*/
|
|
#include "library.h"
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <sys/stat.h>
|
|
|
|
/* 系统目录不参与书库(SD 根上的 MeowOS 目录树)。 */
|
|
static int ignored_dir_name(const char *name)
|
|
{
|
|
static const char *const skip[] = { "MeowAPPs", "MeowData", "MeowOS", "lost+found" };
|
|
if (!name || name[0] == '.') return 1;
|
|
for (unsigned i = 0; i < sizeof skip / sizeof skip[0]; i++)
|
|
if (strcmp(name, skip[i]) == 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int copy_str(char *dst, int dstsz, const char *src)
|
|
{
|
|
if (!dst || dstsz <= 0) return -1;
|
|
if (!src) { dst[0] = '\0'; return 0; }
|
|
int n = (int)strlen(src);
|
|
if (n >= dstsz) return -1;
|
|
memcpy(dst, src, (size_t)n + 1);
|
|
return 0;
|
|
}
|
|
|
|
static int join_path(char *out, int outsz, const char *dir, const char *name)
|
|
{
|
|
if (!out || outsz <= 0 || !dir || !name) return -1;
|
|
int n;
|
|
size_t dl = strlen(dir);
|
|
if (dl > 0 && dir[dl - 1] == '/')
|
|
n = snprintf(out, (size_t)outsz, "%s%s", dir, name);
|
|
else
|
|
n = snprintf(out, (size_t)outsz, "%s/%s", dir, name);
|
|
return (n > 0 && n < outsz) ? 0 : -1;
|
|
}
|
|
|
|
int mr_path_is_image(const char *name)
|
|
{
|
|
if (!name) return 0;
|
|
const char *dot = strrchr(name, '.');
|
|
if (!dot || dot == name) return 0;
|
|
dot++;
|
|
return strcasecmp(dot, "png") == 0 || strcasecmp(dot, "jpg") == 0 ||
|
|
strcasecmp(dot, "jpeg") == 0 || strcasecmp(dot, "bmp") == 0;
|
|
}
|
|
|
|
int mr_natural_cmp(const char *a, const char *b)
|
|
{
|
|
const unsigned char *pa = (const unsigned char *)a;
|
|
const unsigned char *pb = (const unsigned char *)b;
|
|
if (!a || !b) return (a == b) ? 0 : (a ? 1 : -1);
|
|
|
|
while (*pa && *pb) {
|
|
if (isdigit(*pa) && isdigit(*pb)) {
|
|
const unsigned char *za = pa, *zb = pb;
|
|
while (*pa == '0') pa++;
|
|
while (*pb == '0') pb++;
|
|
const unsigned char *da = pa, *db = pb;
|
|
while (isdigit(*pa)) pa++;
|
|
while (isdigit(*pb)) pb++;
|
|
|
|
size_t la = (size_t)(pa - da), lb = (size_t)(pb - db);
|
|
if (la != lb) return la < lb ? -1 : 1;
|
|
if (la > 0) {
|
|
int c = memcmp(da, db, la);
|
|
if (c != 0) return c < 0 ? -1 : 1;
|
|
}
|
|
size_t oa = (size_t)(da - za), ob = (size_t)(db - zb);
|
|
if (oa != ob) return oa < ob ? -1 : 1;
|
|
continue;
|
|
}
|
|
|
|
int ca = tolower(*pa), cb = tolower(*pb);
|
|
if (ca != cb) return ca < cb ? -1 : 1;
|
|
pa++;
|
|
pb++;
|
|
}
|
|
if (*pa) return 1;
|
|
if (*pb) return -1;
|
|
return strcmp(a, b) < 0 ? -1 : (strcmp(a, b) > 0 ? 1 : 0);
|
|
}
|
|
|
|
typedef struct scan_info {
|
|
int pages; /* 直接图片数 */
|
|
int subdirs; /* 含图子目录数 */
|
|
int nav; /* 1=本目录最终含图(可直接看或可导航到图) */
|
|
char cover[MR_PATH_MAX]; /* 缩略图源 */
|
|
char first_name[MR_NAME_MAX]; /* 本目录自然序第一张图片的文件名(比较用) */
|
|
} scan_info;
|
|
|
|
static void scan_dir(const char *path, int depth, int max_depth, scan_info *si)
|
|
{
|
|
memset(si, 0, sizeof *si);
|
|
DIR *d = opendir(path);
|
|
if (!d) return;
|
|
|
|
struct dirent *de;
|
|
while ((de = readdir(d)) != NULL) {
|
|
const char *name = de->d_name;
|
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
|
if (ignored_dir_name(name)) continue;
|
|
|
|
char full[MR_PATH_MAX];
|
|
if (join_path(full, (int)sizeof full, path, name) != 0) continue;
|
|
|
|
struct stat st;
|
|
if (lstat(full, &st) != 0) continue;
|
|
if (S_ISLNK(st.st_mode)) continue;
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
if (depth >= max_depth) continue;
|
|
scan_info child;
|
|
scan_dir(full, depth + 1, max_depth, &child);
|
|
if (!child.nav) continue;
|
|
si->subdirs++;
|
|
if (!si->cover[0] && child.cover[0])
|
|
copy_str(si->cover, (int)sizeof si->cover, child.cover);
|
|
} else if (S_ISREG(st.st_mode) && mr_path_is_image(name)) {
|
|
si->pages++;
|
|
if (!si->first_name[0] || mr_natural_cmp(name, si->first_name) < 0) {
|
|
copy_str(si->first_name, (int)sizeof si->first_name, name);
|
|
copy_str(si->cover, (int)sizeof si->cover, full);
|
|
}
|
|
}
|
|
}
|
|
closedir(d);
|
|
si->nav = (si->pages > 0 || si->subdirs > 0);
|
|
}
|
|
|
|
void mr_library_free(mr_library *lib)
|
|
{
|
|
if (!lib) return;
|
|
free(lib->items);
|
|
lib->items = NULL;
|
|
lib->count = 0;
|
|
lib->cap = 0;
|
|
}
|
|
|
|
static int lib_add(mr_library *lib, const mr_library_entry *e)
|
|
{
|
|
if (lib->count >= lib->cap) {
|
|
int cap = lib->cap ? lib->cap * 2 : 16;
|
|
mr_library_entry *ni = (mr_library_entry *)realloc(lib->items,
|
|
(size_t)cap * sizeof *ni);
|
|
if (!ni) return -1;
|
|
lib->items = ni;
|
|
lib->cap = cap;
|
|
}
|
|
lib->items[lib->count++] = *e;
|
|
return 0;
|
|
}
|
|
|
|
static int entry_cmp(const void *pa, const void *pb)
|
|
{
|
|
const mr_library_entry *a = (const mr_library_entry *)pa;
|
|
const mr_library_entry *b = (const mr_library_entry *)pb;
|
|
return mr_natural_cmp(a->name, b->name);
|
|
}
|
|
|
|
int mr_library_scan(mr_library *lib, const char *dir, int max_depth)
|
|
{
|
|
if (!lib) return -1;
|
|
mr_library_free(lib);
|
|
if (!dir || !dir[0]) return -1;
|
|
|
|
if (max_depth <= 0 || max_depth > MR_LIB_MAX_DEPTH) max_depth = MR_LIB_MAX_DEPTH;
|
|
|
|
DIR *d = opendir(dir);
|
|
if (!d) return -1;
|
|
|
|
struct dirent *de;
|
|
while ((de = readdir(d)) != NULL) {
|
|
const char *name = de->d_name;
|
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
|
if (ignored_dir_name(name)) continue;
|
|
|
|
char full[MR_PATH_MAX];
|
|
if (join_path(full, (int)sizeof full, dir, name) != 0) continue;
|
|
|
|
struct stat st;
|
|
if (lstat(full, &st) != 0) continue;
|
|
if (S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode)) continue;
|
|
|
|
scan_info si;
|
|
scan_dir(full, 1, max_depth, &si);
|
|
if (!si.nav) continue;
|
|
|
|
mr_library_entry e;
|
|
memset(&e, 0, sizeof e);
|
|
if (copy_str(e.name, (int)sizeof e.name, name) != 0) continue;
|
|
if (copy_str(e.path, (int)sizeof e.path, full) != 0) continue;
|
|
copy_str(e.cover, (int)sizeof e.cover, si.cover);
|
|
e.pages = si.pages;
|
|
e.subdirs = si.subdirs;
|
|
e.has_images = (si.pages > 0);
|
|
if (e.has_images)
|
|
snprintf(e.subtitle, sizeof e.subtitle, "%d page%s", e.pages,
|
|
e.pages == 1 ? "" : "s");
|
|
else
|
|
snprintf(e.subtitle, sizeof e.subtitle, "%d folder%s", e.subdirs,
|
|
e.subdirs == 1 ? "" : "s");
|
|
if (lib_add(lib, &e) != 0) {
|
|
closedir(d);
|
|
mr_library_free(lib);
|
|
return -1;
|
|
}
|
|
}
|
|
closedir(d);
|
|
|
|
if (lib->count > 1) qsort(lib->items, (size_t)lib->count, sizeof *lib->items, entry_cmp);
|
|
return lib->count;
|
|
}
|
|
|
|
int mr_path_parent(const char *path, char *out, int outsz)
|
|
{
|
|
if (!path || !out || outsz <= 0) return -1;
|
|
size_t n = strlen(path);
|
|
while (n > 1 && path[n - 1] == '/') n--;
|
|
if (n <= 1) {
|
|
if (outsz < 2) return -1;
|
|
out[0] = '/';
|
|
out[1] = '\0';
|
|
return 0;
|
|
}
|
|
while (n > 0 && path[n - 1] != '/') n--;
|
|
while (n > 1 && path[n - 1] == '/') n--;
|
|
if (n >= (size_t)outsz) return -1;
|
|
memcpy(out, path, n);
|
|
out[n] = '\0';
|
|
return 0;
|
|
}
|