code wiki / _hdl_build / nx_media_cover_lib.nx

nx_media_cover_lib.nx source

↩ module page · 229 lines · 11508 B

1// nx_media_cover_lib.nx -- SOVEREIGN book-cover resolution for the media server (/api/cover). 2// A book's embedded cover is extracted to knowledge/staging/media/reader/<slug>/<coverfile> as a side effect of 3// decoding (nx_epub_book / nx_mobi_book / nx_kf8_book all write the image + record "cover":"<file>" in book.json). 4// This lib maps a NAS book path -> that on-disk cover image, extracting on cache-miss, so the library grid can show 5// real covers WITHOUT generating or downloading anything (operator: no internet, extract embedded only). Shared by 6// nx_media_server (serve) and nx_media_cover_gate (test) so the slug + resolution logic has ONE definition. 7// license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_itoa_lib.nx" // THE shared MSB-first emitter -- mc_dec composes it 10const MC_MAGIC_1469598103: i64 = 1469598103 11const MC_MAGIC_72057594037927931: i64 = 72057594037927931 12const MC_MAGIC_262144: i64 = 262144 13 14const MC_READER_ROOT: *u8 = "knowledge/staging/media/reader/" 15 16func mc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 17func mc_app(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { dst[off+i] = src[i]; i = i + 1 } return off + n } 18func mc_apps(dst: *u8, off: i64, s: *u8) -> i64 { return mc_app(dst, off, s, mc_slen(s)) } 19// ★COMPOSES nxi_buf (2026-08-15, found by nx_itoaclone). Same three defects as every clone of this 20// shape: a negative emitted ZERO BYTES, INT64_MIN would emit a lone '-', and sys_mmap(28) PER CALL was 21// never freed. Identical contract (digits only, no NUL, returns the offset after them). 22func mc_dec(dst: *u8, off: i64, v: i64) -> i64 { return nxi_buf(dst, off, v) } 23func mc_hexc(d: i64) -> i64 { if d < 10 { return 48 + d } return 87 + d } 24 25// 1 if hay[0..hl] contains the needle (path-traversal guard). 26func mc_contains(hay: *u8, hl: i64, needle: *u8) -> i64 { 27 let nl: i64 = mc_slen(needle) 28 if nl == 0 { return 0 } 29 var i: i64 = 0 30 while i + nl <= hl { 31 var k: i64 = 0; var hit: i64 = 1 32 while k < nl { if hay[i+k] != needle[k] { hit = 0; k = nl } else { k = k + 1 } } 33 if hit == 1 { return 1 } 34 i = i + 1 35 } 36 return 0 37} 38 39// EXACT copy of nx_media_server ms_pathslug -- the slug MUST match the one the open/extract path uses, or the 40// cover dir won't be found. 'b' + 14 hex of an FNV-ish hash of the full NAS path. 41func mc_pathslug(p: *u8, n: i64, out: *u8) -> i64 { 42 var h: i64 = MC_MAGIC_1469598103 43 var i: i64 = 0 44 while i < n { h = (h * 131 + (p[i] as i64)) % MC_MAGIC_72057594037927931; i = i + 1 } 45 if h < 0 { h = 0 - h } 46 out[0] = 98 as u8 47 let tmp: *u8 = sys_mmap(32) 48 var k: i64 = 0; var hh: i64 = h 49 while k < 14 { tmp[k] = mc_hexc(hh % 16) as u8; hh = hh / 16; k = k + 1 } 50 var j: i64 = 0 51 while j < 14 { out[1 + j] = tmp[13 - j]; j = j + 1 } 52 out[15] = 0 as u8 53 return 15 54} 55 56// content-type by extension for the served image (jpg/jpeg/png/gif/webp); octet-stream otherwise. 57func mc_ctype(path: *u8) -> *u8 { 58 let n: i64 = mc_slen(path) 59 var dot: i64 = 0 - 1; var i: i64 = 0 60 while i < n { if path[i] == (46 as u8) { dot = i } i = i + 1 } 61 if dot < 0 { return "application/octet-stream" as *u8 } 62 let e1: i64 = (path[dot+1] as i64) | 32 63 var e2: i64 = 0; if dot + 2 < n { e2 = (path[dot+2] as i64) | 32 } 64 var e3: i64 = 0; if dot + 3 < n { e3 = (path[dot+3] as i64) | 32 } 65 if e1 == 106 { if e2 == 112 { return "image/jpeg" as *u8 } } // jp(g/eg) 66 if e1 == 112 { if e2 == 110 { if e3 == 103 { return "image/png" as *u8 } } } // png 67 if e1 == 103 { if e2 == 105 { if e3 == 102 { return "image/gif" as *u8 } } } // gif 68 if e1 == 119 { if e2 == 101 { if e3 == 98 { return "image/webp" as *u8 } } } // web(p) 69 return "application/octet-stream" as *u8 70} 71 72// pull the "cover":"<file>" value out of book.json into out (NUL-terminated). returns length (0 if absent). 73func mc_cover_field(jbuf: *u8, jn: i64, out: *u8, cap: i64) -> i64 { 74 let key: *u8 = "\"cover\":\"" as *u8 75 let kl: i64 = mc_slen(key) 76 var i: i64 = 0 77 while i + kl <= jn { 78 var hit: i64 = 1; var k: i64 = 0 79 while k < kl { if jbuf[i+k] != key[k] { hit = 0; k = kl } else { k = k + 1 } } 80 if hit == 1 { 81 var s: i64 = i + kl; var o: i64 = 0 82 while s < jn { if jbuf[s] == (34 as u8) { s = jn } else { if o < cap - 1 { out[o] = jbuf[s]; o = o + 1 } s = s + 1 } } 83 out[o] = 0 as u8 84 return o 85 } 86 i = i + 1 87 } 88 out[0] = 0 as u8 89 return 0 90} 91 92// fork the right decoder to extract the cover to reader/<slug>/. epub -> nx_epub_book, mobi/azw/azw3 -> 93// nx_mobi_book (full extraction; cover recorded in book.json). cbz/cbr/cb7 -> nx_cbx_pages --cover 94// (FAST: only the natural-first page image is pulled to <slug>/cover.jpg -- a shelf of comics must not 95// pay a full 100MB unpack per cover request). pdf/fb2 remain text-card fallbacks. 96func mc_extract(path: *u8, pn: i64, slug: *u8) -> i64 { 97 var dot: i64 = 0 - 1; var x: i64 = 0 98 while x < pn { if path[x] == (46 as u8) { dot = x } x = x + 1 } 99 if dot < 0 { return 0 } 100 if dot + 3 >= pn { return 0 } 101 let e1: i64 = (path[dot+1] as i64) | 32 102 let e2: i64 = (path[dot+2] as i64) | 32 103 let e3: i64 = (path[dot+3] as i64) | 32 104 var ext: *u8 = 0 as *u8 105 var coverarg: i64 = 0 106 if e1 == 101 { if e2 == 112 { if e3 == 117 { ext = "./_offc/nx_epub_book.elf\x00" as *u8 } } } // epub 107 if e1 == 109 { if e2 == 111 { if e3 == 98 { ext = "./_offc/nx_mobi_book.elf\x00" as *u8 } } } // mob(i) 108 if e1 == 97 { if e2 == 122 { if e3 == 119 { ext = "./_offc/nx_mobi_book.elf\x00" as *u8 } } } // azw(/azw3) 109 if e1 == 99 { if e2 == 98 { ext = "./_offc/nx_cbx_pages.elf\x00" as *u8; coverarg = 1 } } // cb(z/r/7) -> --cover fast path 110 if (ext as i64) == 0 { return 0 } 111 let pid: i64 = sys_fork() 112 if pid == 0 { 113 let av: *i64 = sys_mmap(64) as *i64 114 av[0] = ext as i64; av[1] = path as i64; av[2] = slug as i64 115 if coverarg == 1 { av[3] = "--cover" as *u8 as i64; av[4] = 0 } else { av[3] = 0 } 116 sys_execve(ext, av, 0 as *i64) 117 sys_exit(127) 118 } 119 let st: *i64 = sys_mmap(8) as *i64 120 sys_wait4(pid, st, 0) 121 return 1 122} 123 124// pull the FIRST "file":"<name>" out of cbx.json (pages are natural-sorted, so first = page 0 = the cover). 125func mc_first_page_field(jbuf: *u8, jn: i64, out: *u8, cap: i64) -> i64 { 126 let key: *u8 = "\"file\":\"" as *u8 127 let kl: i64 = mc_slen(key) 128 var i: i64 = 0 129 while i + kl <= jn { 130 var hit: i64 = 1; var k: i64 = 0 131 while k < kl { if jbuf[i+k] != key[k] { hit = 0; k = kl } else { k = k + 1 } } 132 if hit == 1 { 133 var s: i64 = i + kl; var o: i64 = 0 134 while s < jn { if jbuf[s] == (34 as u8) { s = jn } else { if o < cap - 1 { out[o] = jbuf[s]; o = o + 1 } s = s + 1 } } 135 out[o] = 0 as u8 136 return o 137 } 138 i = i + 1 139 } 140 out[0] = 0 as u8 141 return 0 142} 143 144// try to resolve a cover for an ALREADY-EXTRACTED slug dir (no forking): 1 = outfull set, 0 = nothing yet. 145// Order: book.json "cover" field -> <slug>/cover.jpg (comic fast-cover) -> cbx.json page 0 (comics + imgbooks). 146func mc_try_resolved(slug: *u8, outfull: *u8) -> i64 { 147 let jp: *u8 = sys_mmap(512); var jo: i64 = 0 148 jo = mc_apps(jp, jo, MC_READER_ROOT) 149 var si: i64 = 0; while slug[si] != (0 as u8) { jp[jo] = slug[si]; jo = jo + 1; si = si + 1 } 150 let base: i64 = jo 151 // 1) book.json "cover" 152 jo = mc_apps(jp, base, "/book.json" as *u8); jp[jo] = 0 as u8 153 let lb: *i64 = sys_mmap(8) as *i64 154 let jbuf: *u8 = sys_read_file(jp, lb) 155 if (jbuf as i64) != 0 { 156 let cover: *u8 = sys_mmap(256) 157 let cn: i64 = mc_cover_field(jbuf, lb[0], cover, 256) 158 if cn > 0 { 159 var fo: i64 = 0 160 fo = mc_apps(outfull, fo, MC_READER_ROOT) 161 var s2: i64 = 0; while slug[s2] != (0 as u8) { outfull[fo] = slug[s2]; fo = fo + 1; s2 = s2 + 1 } 162 outfull[fo] = 47 as u8; fo = fo + 1 163 var ci: i64 = 0; while cover[ci] != (0 as u8) { outfull[fo] = cover[ci]; fo = fo + 1; ci = ci + 1 } 164 outfull[fo] = 0 as u8 165 let f1: i64 = sys_openat_rd(outfull) 166 if f1 >= 0 { sys_close(f1); return 1 } 167 } 168 } 169 // 2) <slug>/cover.jpg (written by nx_cbx_pages --cover) 170 jo = mc_apps(jp, base, "/cover.jpg" as *u8); jp[jo] = 0 as u8 171 let f2: i64 = sys_openat_rd(jp) 172 if f2 >= 0 { 173 sys_close(f2) 174 var fo2: i64 = 0; var s3: i64 = 0 175 while jp[s3] != (0 as u8) { outfull[fo2] = jp[s3]; fo2 = fo2 + 1; s3 = s3 + 1 } 176 outfull[fo2] = 0 as u8 177 return 1 178 } 179 // 3) cbx.json page 0 (an extracted comic / promoted image-book IS its own cover source) 180 jo = mc_apps(jp, base, "/cbx.json" as *u8); jp[jo] = 0 as u8 181 let lb3: *i64 = sys_mmap(8) as *i64 182 let cbuf: *u8 = sys_read_file(jp, lb3) 183 if (cbuf as i64) != 0 { 184 let pg: *u8 = sys_mmap(256) 185 let pn3: i64 = mc_first_page_field(cbuf, lb3[0], pg, 256) 186 if pn3 > 0 { 187 var fo3: i64 = 0 188 fo3 = mc_apps(outfull, fo3, MC_READER_ROOT) 189 var s4: i64 = 0; while slug[s4] != (0 as u8) { outfull[fo3] = slug[s4]; fo3 = fo3 + 1; s4 = s4 + 1 } 190 outfull[fo3] = 47 as u8; fo3 = fo3 + 1 191 var pi: i64 = 0; while pg[pi] != (0 as u8) { outfull[fo3] = pg[pi]; fo3 = fo3 + 1; pi = pi + 1 } 192 outfull[fo3] = 0 as u8 193 let f3: i64 = sys_openat_rd(outfull) 194 if f3 >= 0 { sys_close(f3); return 1 } 195 } 196 } 197 return 0 198} 199 200// resolve a NAS book path -> its on-disk cover image file (extracting on cache-miss). 201// returns 1 = ok (outfull = cover file path, NUL-terminated); 0 = no cover; -1 = rejected (path traversal). 202// Resolution order (mc_try_resolved): book.json "cover" -> <slug>/cover.jpg -> cbx.json page 0; one 203// extraction attempt on cold miss (epub/mobi full decode; cbz/cbr FAST --cover), then re-resolve. 204func mc_cover_resolve(path: *u8, pn: i64, outfull: *u8, cap: i64) -> i64 { 205 if mc_contains(path, pn, ".." as *u8) == 1 { return 0 - 1 } 206 let slug: *u8 = sys_mmap(32); mc_pathslug(path, pn, slug) 207 if mc_try_resolved(slug, outfull) == 1 { return 1 } 208 if mc_extract(path, pn, slug) == 0 { return 0 } 209 if mc_try_resolved(slug, outfull) == 1 { return 1 } 210 return 0 211} 212 213// serve the resolved cover image over an HTTP connection (streamed, with a day of cache). Caller already 214// resolved + authorized. Closes cfd. 215func mc_serve_image(cfd: i64, full: *u8) -> i64 { 216 let ct: *u8 = mc_ctype(full) 217 let fd: i64 = sys_openat_rd(full) 218 if fd < 0 { let d: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"; sys_write(cfd, d, mc_slen(d)); sys_close(cfd); return 0 } 219 let sz: i64 = sys_lseek(fd, 0, 2); sys_lseek(fd, 0, 0) 220 let hdr: *u8 = sys_mmap(256); var h: i64 = 0 221 h = mc_apps(hdr, h, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8); h = mc_apps(hdr, h, ct) 222 h = mc_apps(hdr, h, "\r\nCache-Control: max-age=86400\r\nContent-Length: " as *u8); h = mc_dec(hdr, h, sz) 223 h = mc_apps(hdr, h, "\r\nConnection: close\r\n\r\n" as *u8) 224 sys_write(cfd, hdr, h) 225 let chunk: *u8 = sys_mmap(MC_MAGIC_262144); var go: i64 = 1 226 while go == 1 { let r: i64 = sys_read(fd, chunk, MC_MAGIC_262144); if r <= 0 { go = 0 } else { sys_write(cfd, chunk, r) } } 227 sys_close(fd); sys_close(cfd) 228 return 0 229}