/* logic/library.c — 书库目录扫描 + 自然排序(纯 C)。 * * 扫描分两步: * 1) scan_info():递归探测一个目录是否「最终含图」,并带回直接图片数 / 含图子目录数 / * 缩略图源(优先本目录自然序第一张,其次第一个含图子目录); * 2) mr_library_scan():只对目标目录的**直接子目录**调用 scan_info(),把 nav=1 的目录 * 收进结果并自然排序——因此根视图不会列出后代条目,进入子目录时再重新扫描该层。 * * 内存:条目数组倍增分配;路径/名称定长,随 mr_library_free() 释放。 */ #include "library.h" #include #include #include #include #include #include #include /* 系统目录不参与书库(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; }