code wiki / _hdl_build / nx_manga_shelf_lib.nx

nx_manga_shelf_lib.nx source

↩ module page · 638 lines · 24166 B

1// nx_manga_shelf_lib.nx -- LOCAL comics/manga SHELF catalog lib for the media server (/api/shelf). 2// The operator's "browse before download" complaint, LOCAL half: browse what we ALREADY OWN -- series grid 3// -> volume list -> the guided zoom reader. ZERO network: everything derives from disk truth. 4// A book joins the shelf iff (a) it is a comic archive (cbz/cbr/cb7), or (b) its calibre author dir is 5// listed in shelf_authors.txt (operator-editable data: manga/comics wearing epub/pdf clothes), or (c) its 6// extracted reader slug already carries cbx.json (PROVEN comic -- it opened in the zoom reader before). 7// Series grouping is data-driven: shelf_markers.txt volume markers (case-insensitive; the EARLIEST hit cuts 8// the title into series name + volume number) plus a same-author first-token fallback for mashed one-shot 9// dumps ("swordsofsorrow vampirella ..."). Multi-format calibre dirs dedupe to ONE volume (cbz > epub > 10// kindle > pdf). Volumes open through the EXISTING /api/open extract-on-demand chain -> /library/zoom; 11// covers ride the EXISTING /api/cover. Pure lib (no socket, no fork) so nx_manga_shelf_gate proves the 12// grouping in isolation -- the nx_galx_manga/nx_media_cover_lib lib+gate split. license_tier: ORIGINAL 13import "nx_syscalls.nx" 14const MSL_MAGIC_1469598103: i64 = 1469598103 15const MSL_MAGIC_72057594037927931: i64 = 72057594037927931 16const MSL_MAGIC_1024: i64 = 1024 17const MSL_MAGIC_2048: i64 = 2048 18const MSL_MAGIC_8192: i64 = 8192 19 20const MSL_IDX: *u8 = "knowledge/staging/media/idx_book.wsl" 21const MSL_AUTHORS: *u8 = "knowledge/staging/media/shelf_authors.txt" 22const MSL_MARKERS: *u8 = "knowledge/staging/media/shelf_markers.txt" 23const MSL_READER: *u8 = "knowledge/staging/media/reader/" 24const MSL_MAXVOL: i64 = 4096 25const MSL_MAXSER: i64 = 1024 26 27// ---- tiny string kit (no per-call mmap: these run thousands of times per request) ---- 28func msl_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 29func msl_cats(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } return off + i } 30func msl_catn(dst: *u8, off: i64, v: i64) -> i64 { 31 if v <= 0 { dst[off] = 48 as u8; return off + 1 } 32 var m: i64 = v 33 var k: i64 = 0 34 while m > 0 { k = k + 1; m = m / 10 } 35 var w: i64 = off + k 36 var m2: i64 = v 37 while m2 > 0 { w = w - 1; dst[w] = (48 + (m2 % 10)) as u8; m2 = m2 / 10 } 38 return off + k 39} 40func msl_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 41func msl_hexc(d: i64) -> i64 { if d < 10 { return 48 + d } return 87 + d } 42 43// EXACT hash twin of nx_media_server ms_pathslug / nx_media_cover_lib mc_pathslug -- the slug MUST match the 44// open/extract chain or the cbx.json probe looks in the wrong reader dir. Slice form, mmap-free. 45func msl_pathslug(p: *u8, ls: i64, le: i64, out: *u8) -> i64 { 46 var h: i64 = MSL_MAGIC_1469598103 47 var i: i64 = ls 48 while i < le { h = (h * 131 + (p[i] as i64)) % MSL_MAGIC_72057594037927931; i = i + 1 } 49 if h < 0 { h = 0 - h } 50 out[0] = 98 as u8 51 var k: i64 = 14 52 var hh: i64 = h 53 while k > 0 { out[k] = msl_hexc(hh % 16) as u8; hh = hh / 16; k = k - 1 } 54 out[15] = 0 as u8 55 return 15 56} 57 58// 1 iff the extracted reader dir for this NAS path already carries cbx.json (a PROVEN comic). 59// sc = caller scratch >= 640 bytes (slug at +0, probe path at +32) -- no per-call mmap. 60func msl_probe_cbx(p: *u8, ls: i64, le: i64, sc: *u8) -> i64 { 61 let slug: *u8 = sc 62 let jp: *u8 = ((sc as i64) + 32) as *u8 63 msl_pathslug(p, ls, le, slug) 64 var o: i64 = msl_cats(jp, 0, MSL_READER) 65 var i: i64 = 0 66 while slug[i] != (0 as u8) { jp[o] = slug[i]; o = o + 1; i = i + 1 } 67 o = msl_cats(jp, o, "/cbx.json" as *u8) 68 jp[o] = 0 as u8 69 let fd: i64 = sys_openat_rd(jp) 70 if fd < 0 { return 0 } 71 sys_close(fd) 72 return 1 73} 74 75// shelf format rank: cbz/cbr/cb7=4 (born comics) > epub=3 > mobi/azw/azw3/prc=2 > pdf=1; 0 = not eligible. 76func msl_ext_rank(p: *u8, ls: i64, le: i64) -> i64 { 77 var dot: i64 = 0 - 1 78 var i: i64 = ls 79 while i < le { if p[i] == (46 as u8) { dot = i } i = i + 1 } 80 if dot < 0 { return 0 } 81 if dot + 3 >= le { return 0 } 82 let e1: i64 = msl_lc(p[dot + 1] as i64) 83 let e2: i64 = msl_lc(p[dot + 2] as i64) 84 let e3: i64 = msl_lc(p[dot + 3] as i64) 85 if e1 == 99 { if e2 == 98 { return 4 } } 86 if e1 == 101 { if e2 == 112 { if e3 == 117 { return 3 } } } 87 if e1 == 109 { if e2 == 111 { if e3 == 98 { return 2 } } } 88 if e1 == 97 { if e2 == 122 { if e3 == 119 { return 2 } } } 89 if e1 == 112 { if e2 == 114 { if e3 == 99 { return 2 } } } 90 if e1 == 112 { if e2 == 100 { if e3 == 102 { return 1 } } } 91 return 0 92} 93 94// calibre title dirs end with " (<calibre id>)" -- strip a digits-only trailing paren group. Returns new len. 95func msl_strip_id(p: *u8, ls: i64, le: i64) -> i64 { 96 let n: i64 = le - ls 97 if n < 4 { return n } 98 if p[le - 1] != (41 as u8) { return n } 99 var i: i64 = le - 2 100 var digits: i64 = 0 101 var open: i64 = 0 - 1 102 var go: i64 = 1 103 while go == 1 { 104 if i < ls { go = 0 } else { 105 let c: i64 = p[i] as i64 106 var isd: i64 = 0 107 if c >= 48 { if c <= 57 { isd = 1 } } 108 if c == 40 { open = i; go = 0 } else { if isd == 1 { digits = digits + 1; i = i - 1 } else { go = 0 } } 109 } 110 } 111 if open > ls { if digits > 0 { if p[open - 1] == (32 as u8) { return open - 1 - ls } } } 112 return n 113} 114 115// case-insensitive slice equality 116func msl_ci_eq(a: *u8, als: i64, ale: i64, b: *u8, bls: i64, ble: i64) -> i64 { 117 if ale - als != ble - bls { return 0 } 118 let n: i64 = ale - als 119 var i: i64 = 0 120 while i < n { if msl_lc(a[als + i] as i64) != msl_lc(b[bls + i] as i64) { return 0 } i = i + 1 } 121 return 1 122} 123 124// case-insensitive lexicographic compare of two slices: -1 / 0 / 1 125func msl_ci_cmp(a: *u8, als: i64, ale: i64, b: *u8, bls: i64, ble: i64) -> i64 { 126 let an: i64 = ale - als 127 let bn: i64 = ble - bls 128 var n: i64 = an 129 if bn < n { n = bn } 130 var i: i64 = 0 131 while i < n { 132 let ca: i64 = msl_lc(a[als + i] as i64) 133 let cb: i64 = msl_lc(b[bls + i] as i64) 134 if ca < cb { return 0 - 1 } 135 if ca > cb { return 1 } 136 i = i + 1 137 } 138 if an < bn { return 0 - 1 } 139 if an > bn { return 1 } 140 return 0 141} 142 143// case-insensitive find of NUL-terminated needle in hay[ls..le); absolute pos or -1. 144func msl_ci_find(hay: *u8, ls: i64, le: i64, needle: *u8) -> i64 { 145 let nl: i64 = msl_slen(needle) 146 if nl == 0 { return 0 - 1 } 147 var i: i64 = ls 148 while i + nl <= le { 149 var k: i64 = 0 150 var hit: i64 = 1 151 while k < nl { if msl_lc(hay[i + k] as i64) != msl_lc(needle[k] as i64) { hit = 0; k = nl } else { k = k + 1 } } 152 if hit == 1 { return i } 153 i = i + 1 154 } 155 return 0 - 1 156} 157 158// 1 iff the author slice exactly matches one line of shelf_authors.txt (CR tolerated). 159func msl_author_listed(a: *u8, ls: i64, le: i64, ab: *u8, an: i64) -> i64 { 160 let want: i64 = le - ls 161 if want <= 0 { return 0 } 162 var i: i64 = 0 163 var lstart: i64 = 0 164 while i <= an { 165 var nl: i64 = 0 166 if i == an { nl = 1 } else { if ab[i] == (10 as u8) { nl = 1 } } 167 if nl == 1 { 168 var ll: i64 = i - lstart 169 if ll > 0 { if ab[lstart + ll - 1] == (13 as u8) { ll = ll - 1 } } 170 if ll == want { 171 var k: i64 = 0 172 var eq: i64 = 1 173 while k < ll { if ab[lstart + k] != a[ls + k] { eq = 0; k = ll } else { k = k + 1 } } 174 if eq == 1 { return 1 } 175 } 176 lstart = i + 1 177 } 178 i = i + 1 179 } 180 return 0 181} 182 183// cut title[ls..le) at the EARLIEST volume-marker hit (shelf_markers.txt: one ci marker per line). 184// numout[0] = volume number right after the marker (0 if none); nomark[0] = 1 when no marker hit. 185// one = caller scratch >= 64B for the current marker. Returns the ABSOLUTE end of the trimmed series slice. 186func msl_marker_cut(t: *u8, ls: i64, le: i64, mk: *u8, mkn: i64, numout: *i64, nomark: *i64, one: *u8) -> i64 { 187 var best: i64 = 0 - 1 188 var bmlen: i64 = 0 189 var lstart: i64 = 0 190 var i: i64 = 0 191 while i <= mkn { 192 var nl: i64 = 0 193 if i == mkn { nl = 1 } else { if mk[i] == (10 as u8) { nl = 1 } } 194 if nl == 1 { 195 var ll: i64 = i - lstart 196 if ll > 0 { if mk[lstart + ll - 1] == (13 as u8) { ll = ll - 1 } } 197 if ll > 0 { if ll < 60 { 198 var k: i64 = 0 199 while k < ll { one[k] = mk[lstart + k]; k = k + 1 } 200 one[ll] = 0 as u8 201 let pos: i64 = msl_ci_find(t, ls, le, one) 202 if pos >= 0 { 203 var better: i64 = 0 204 if best < 0 { better = 1 } else { if pos < best { better = 1 } } 205 if better == 1 { best = pos; bmlen = ll } 206 } 207 } } 208 lstart = i + 1 209 } 210 i = i + 1 211 } 212 numout[0] = 0 213 nomark[0] = 0 214 if best < 0 { nomark[0] = 1; return le } 215 var p: i64 = best + bmlen 216 var skip: i64 = 0 217 var go1: i64 = 1 218 while go1 == 1 { 219 if p >= le { go1 = 0 } else { 220 let c: i64 = t[p] as i64 221 var isd: i64 = 0 222 if c >= 48 { if c <= 57 { isd = 1 } } 223 if isd == 1 { go1 = 0 } else { skip = skip + 1; if skip > 8 { go1 = 0 } else { p = p + 1 } } 224 } 225 } 226 var num: i64 = 0 227 var nd: i64 = 0 228 var go2: i64 = 1 229 while go2 == 1 { 230 if p >= le { go2 = 0 } else { 231 let c2: i64 = t[p] as i64 232 var isd2: i64 = 0 233 if c2 >= 48 { if c2 <= 57 { isd2 = 1 } } 234 if isd2 == 1 { if nd < 5 { num = num * 10 + (c2 - 48); nd = nd + 1; p = p + 1 } else { go2 = 0 } } else { go2 = 0 } 235 } 236 } 237 numout[0] = num 238 var e: i64 = best 239 var go3: i64 = 1 240 while go3 == 1 { 241 if e <= ls { go3 = 0 } else { 242 let c3: i64 = t[e - 1] as i64 243 var tr: i64 = 0 244 if c3 == 32 { tr = 1 } 245 if c3 == 44 { tr = 1 } 246 if c3 == 45 { tr = 1 } 247 if c3 == 58 { tr = 1 } 248 if c3 == 95 { tr = 1 } 249 if c3 == 46 { tr = 1 } 250 if tr == 1 { e = e - 1 } else { go3 = 0 } 251 } 252 } 253 if e <= ls { nomark[0] = 1; numout[0] = 0; return le } 254 return e 255} 256 257// JSON-escaped slice append: " -> \" , backslash -> \\ , control bytes -> space. 258func msl_jesc(dst: *u8, off0: i64, s: *u8, ls: i64, le: i64) -> i64 { 259 var off: i64 = off0 260 var i: i64 = ls 261 while i < le { 262 let c: i64 = s[i] as i64 263 if c == 34 { dst[off] = 92 as u8; off = off + 1; dst[off] = 34 as u8; off = off + 1 } else { 264 if c == 92 { dst[off] = 92 as u8; off = off + 1; dst[off] = 92 as u8; off = off + 1 } else { 265 if c < 32 { dst[off] = 32 as u8; off = off + 1 } else { 266 dst[off] = s[i]; off = off + 1 } } } 267 i = i + 1 268 } 269 return off 270} 271 272// ---- context: ONE *i64 block so every pass has a small signature ---- 273// 0 idxbuf 1 idxlen 2 nvol 3 markers 4 markerslen 5 authors 6 authorslen 7 nser 274// 8 v_poff 9 v_plen 10 v_aoff 11 v_alen 12 v_toff 13 v_tlen 14 v_soff 15 v_slen 275// 16 v_num 17 v_rank 18 v_x 19 v_nomark 20 v_serid 21 v_doff 22 v_dlen 276// 23 s_soff 24 s_slen 25 s_aoff 26 s_alen 27 scratch(1024: probe@0 marker@640 numbox@768 nmbox@776) 277func msl_ctx_new() -> *i64 { 278 let ctx: *i64 = sys_mmap(512) as *i64 279 var s: i64 = 8 280 while s <= 22 { ctx[s] = sys_mmap(MSL_MAXVOL * 8) as i64; s = s + 1 } 281 var s2: i64 = 23 282 while s2 <= 26 { ctx[s2] = sys_mmap(MSL_MAXSER * 8) as i64; s2 = s2 + 1 } 283 ctx[27] = sys_mmap(MSL_MAGIC_1024) as i64 284 ctx[2] = 0 285 ctx[7] = 0 286 return ctx 287} 288 289// parse ONE idx line [ls..le) -> maybe a volume slot. calibre shape .../<author>/<titledir>/<file>.<ext> 290func msl_add_line(ctx: *i64, ls: i64, le: i64) -> i64 { 291 let buf: *u8 = ctx[0] as *u8 292 var sl1: i64 = 0 - 1 293 var sl2: i64 = 0 - 1 294 var sl3: i64 = 0 - 1 295 var i: i64 = ls 296 while i < le { if buf[i] == (47 as u8) { sl1 = sl2; sl2 = sl3; sl3 = i } i = i + 1 } 297 if sl1 < 0 { return 0 } 298 let rank: i64 = msl_ext_rank(buf, sl3 + 1, le) 299 if rank == 0 { return 0 } 300 let aoff: i64 = sl1 + 1 301 let alen: i64 = sl2 - aoff 302 let doff: i64 = sl2 + 1 303 let dlen: i64 = sl3 - doff 304 if alen <= 0 { return 0 } 305 if dlen <= 0 { return 0 } 306 let nvol: i64 = ctx[2] 307 let vpo: *i64 = ctx[8] as *i64 308 let vpl: *i64 = ctx[9] as *i64 309 let vao: *i64 = ctx[10] as *i64 310 let val: *i64 = ctx[11] as *i64 311 let vrk: *i64 = ctx[17] as *i64 312 let vx: *i64 = ctx[18] as *i64 313 let vdo: *i64 = ctx[21] as *i64 314 let vdl: *i64 = ctx[22] as *i64 315 // dedupe: same author + same title dir = the SAME calibre book in another format -> keep the best rank 316 var j: i64 = 0 317 while j < nvol { 318 if msl_ci_eq(buf, aoff, aoff + alen, buf, vao[j], vao[j] + val[j]) == 1 { 319 if msl_ci_eq(buf, doff, doff + dlen, buf, vdo[j], vdo[j] + vdl[j]) == 1 { 320 if rank > vrk[j] { 321 vpo[j] = ls 322 vpl[j] = le - ls 323 vrk[j] = rank 324 vx[j] = msl_probe_cbx(buf, ls, le, ctx[27] as *u8) 325 } 326 return 0 327 } 328 } 329 j = j + 1 330 } 331 if nvol >= MSL_MAXVOL { return 0 } 332 // shelf admission: born comic OR allowlisted author OR PROVEN comic (extracted cbx.json on disk) 333 var incl: i64 = 0 334 var xf: i64 = 0 - 1 335 if rank == 4 { incl = 1 } 336 if incl == 0 { if msl_author_listed(buf, aoff, aoff + alen, ctx[5] as *u8, ctx[6]) == 1 { incl = 1 } } 337 if incl == 0 { 338 xf = msl_probe_cbx(buf, ls, le, ctx[27] as *u8) 339 if xf == 0 { return 0 } 340 incl = 1 341 } 342 if xf < 0 { xf = msl_probe_cbx(buf, ls, le, ctx[27] as *u8) } 343 let tlen: i64 = msl_strip_id(buf, doff, doff + dlen) 344 let numbox: *i64 = (ctx[27] + 768) as *i64 345 let nmbox: *i64 = (ctx[27] + 776) as *i64 346 let send: i64 = msl_marker_cut(buf, doff, doff + tlen, ctx[3] as *u8, ctx[4], numbox, nmbox, (ctx[27] + 640) as *u8) 347 let vto: *i64 = ctx[12] as *i64 348 let vtl: *i64 = ctx[13] as *i64 349 let vso: *i64 = ctx[14] as *i64 350 let vsl: *i64 = ctx[15] as *i64 351 let vnm: *i64 = ctx[16] as *i64 352 let vnk: *i64 = ctx[19] as *i64 353 let vsr: *i64 = ctx[20] as *i64 354 vpo[nvol] = ls 355 vpl[nvol] = le - ls 356 vao[nvol] = aoff 357 val[nvol] = alen 358 vdo[nvol] = doff 359 vdl[nvol] = dlen 360 vto[nvol] = doff 361 vtl[nvol] = tlen 362 vso[nvol] = doff 363 vsl[nvol] = send - doff 364 vnm[nvol] = numbox[0] 365 vrk[nvol] = rank 366 vx[nvol] = xf 367 vnk[nvol] = nmbox[0] 368 vsr[nvol] = 0 - 1 369 ctx[2] = nvol + 1 370 return 1 371} 372 373// pass 1: every idx line -> volume slots 374func msl_scan(ctx: *i64) -> i64 { 375 let buf: *u8 = ctx[0] as *u8 376 let n: i64 = ctx[1] 377 var i: i64 = 0 378 while i < n { 379 let lstart: i64 = i 380 var lend: i64 = lstart 381 var stop: i64 = 0 382 while stop == 0 { if lend >= n { stop = 1 } else { if buf[lend] == (10 as u8) { stop = 1 } else { lend = lend + 1 } } } 383 var ce: i64 = lend 384 if ce > lstart { if buf[ce - 1] == (13 as u8) { ce = ce - 1 } } 385 if ce > lstart { msl_add_line(ctx, lstart, ce) } 386 i = lend + 1 387 } 388 return ctx[2] 389} 390 391// first space inside [ts..te) or -1 392func msl_tok_end(buf: *u8, ts: i64, te: i64) -> i64 { 393 var p: i64 = ts 394 while p < te { if buf[p] == (32 as u8) { return p } p = p + 1 } 395 return 0 - 1 396} 397 398// pass 2: same-author markerless titles sharing a first token (len>=4) group under that token 399// (one tile with N one-shots beats N sibling tiles -- the mashed "swordsofsorrow ..." dumps). 400func msl_firsttok(ctx: *i64) -> i64 { 401 let buf: *u8 = ctx[0] as *u8 402 let nvol: i64 = ctx[2] 403 let vao: *i64 = ctx[10] as *i64 404 let val: *i64 = ctx[11] as *i64 405 let vto: *i64 = ctx[12] as *i64 406 let vtl: *i64 = ctx[13] as *i64 407 let vso: *i64 = ctx[14] as *i64 408 let vsl: *i64 = ctx[15] as *i64 409 let vnk: *i64 = ctx[19] as *i64 410 var i: i64 = 0 411 while i < nvol { 412 if vnk[i] == 1 { 413 let ts: i64 = vto[i] 414 let te: i64 = ts + vtl[i] 415 let sp: i64 = msl_tok_end(buf, ts, te) 416 if sp >= 0 { if sp - ts >= 4 { 417 var cnt: i64 = 0 418 var j: i64 = 0 419 while j < nvol { 420 if vnk[j] == 1 { if msl_ci_eq(buf, vao[i], vao[i] + val[i], buf, vao[j], vao[j] + val[j]) == 1 { 421 let ts2: i64 = vto[j] 422 let sp2: i64 = msl_tok_end(buf, ts2, ts2 + vtl[j]) 423 if sp2 >= 0 { if msl_ci_eq(buf, ts, sp, buf, ts2, sp2) == 1 { cnt = cnt + 1 } } 424 } } 425 j = j + 1 426 } 427 if cnt >= 2 { 428 var j2: i64 = 0 429 while j2 < nvol { 430 if vnk[j2] == 1 { if msl_ci_eq(buf, vao[i], vao[i] + val[i], buf, vao[j2], vao[j2] + val[j2]) == 1 { 431 let ts3: i64 = vto[j2] 432 let sp3: i64 = msl_tok_end(buf, ts3, ts3 + vtl[j2]) 433 if sp3 >= 0 { if msl_ci_eq(buf, ts, sp, buf, ts3, sp3) == 1 { 434 vso[j2] = ts3 435 vsl[j2] = sp3 - ts3 436 vnk[j2] = 0 437 } } 438 } } 439 j2 = j2 + 1 440 } 441 } 442 } } 443 } 444 i = i + 1 445 } 446 return 0 447} 448 449// pass 3: (author, series) -> series table + per-volume series id 450func msl_group(ctx: *i64) -> i64 { 451 let buf: *u8 = ctx[0] as *u8 452 let nvol: i64 = ctx[2] 453 let vao: *i64 = ctx[10] as *i64 454 let val: *i64 = ctx[11] as *i64 455 let vso: *i64 = ctx[14] as *i64 456 let vsl: *i64 = ctx[15] as *i64 457 let vsr: *i64 = ctx[20] as *i64 458 let sso: *i64 = ctx[23] as *i64 459 let ssl: *i64 = ctx[24] as *i64 460 let sao: *i64 = ctx[25] as *i64 461 let sal: *i64 = ctx[26] as *i64 462 var nser: i64 = 0 463 var i: i64 = 0 464 while i < nvol { 465 var found: i64 = 0 - 1 466 var s: i64 = 0 467 while s < nser { 468 if found < 0 { if msl_ci_eq(buf, vso[i], vso[i] + vsl[i], buf, sso[s], sso[s] + ssl[s]) == 1 { 469 if msl_ci_eq(buf, vao[i], vao[i] + val[i], buf, sao[s], sao[s] + sal[s]) == 1 { found = s } } } 470 s = s + 1 471 } 472 if found < 0 { if nser < MSL_MAXSER { 473 sso[nser] = vso[i] 474 ssl[nser] = vsl[i] 475 sao[nser] = vao[i] 476 sal[nser] = val[i] 477 found = nser 478 nser = nser + 1 479 } } 480 vsr[i] = found 481 i = i + 1 482 } 483 ctx[7] = nser 484 return nser 485} 486 487// alphabetical (ci) series order via insertion sort of ids 488func msl_sort_series(ctx: *i64, sidx: *i64) -> i64 { 489 let buf: *u8 = ctx[0] as *u8 490 let nser: i64 = ctx[7] 491 let sso: *i64 = ctx[23] as *i64 492 let ssl: *i64 = ctx[24] as *i64 493 var i: i64 = 0 494 while i < nser { sidx[i] = i; i = i + 1 } 495 var a: i64 = 1 496 while a < nser { 497 let key: i64 = sidx[a] 498 var b: i64 = a - 1 499 var go: i64 = 1 500 while go == 1 { 501 if b < 0 { go = 0 } else { 502 if msl_ci_cmp(buf, sso[sidx[b]], sso[sidx[b]] + ssl[sidx[b]], buf, sso[key], sso[key] + ssl[key]) == 1 { 503 sidx[b + 1] = sidx[b] 504 b = b - 1 505 } else { go = 0 } 506 } 507 } 508 sidx[b + 1] = key 509 a = a + 1 510 } 511 return 0 512} 513 514// 1 iff vol a sorts BEFORE vol b (volume number asc, then title ci) 515func msl_vol_before(ctx: *i64, a: i64, b: i64) -> i64 { 516 let buf: *u8 = ctx[0] as *u8 517 let vnm: *i64 = ctx[16] as *i64 518 let vto: *i64 = ctx[12] as *i64 519 let vtl: *i64 = ctx[13] as *i64 520 if vnm[a] < vnm[b] { return 1 } 521 if vnm[a] > vnm[b] { return 0 } 522 if msl_ci_cmp(buf, vto[a], vto[a] + vtl[a], buf, vto[b], vto[b] + vtl[b]) < 0 { return 1 } 523 return 0 524} 525 526// collect + sort one series' volume ids; returns count (cap 1024) 527func msl_series_vols(ctx: *i64, sid: i64, vids: *i64) -> i64 { 528 let nvol: i64 = ctx[2] 529 let vsr: *i64 = ctx[20] as *i64 530 var k: i64 = 0 531 var i: i64 = 0 532 while i < nvol { if vsr[i] == sid { if k < MSL_MAGIC_1024 { vids[k] = i; k = k + 1 } } i = i + 1 } 533 var a: i64 = 1 534 while a < k { 535 let key: i64 = vids[a] 536 var b: i64 = a - 1 537 var go: i64 = 1 538 while go == 1 { 539 if b < 0 { go = 0 } else { 540 if msl_vol_before(ctx, key, vids[b]) == 1 { vids[b + 1] = vids[b]; b = b - 1 } else { go = 0 } 541 } 542 } 543 vids[b + 1] = key 544 a = a + 1 545 } 546 return k 547} 548 549// one series object -> out; returns new write offset 550func msl_emit_series(ctx: *i64, out: *u8, w0: i64, cap: i64, sid: i64, vids: *i64) -> i64 { 551 let buf: *u8 = ctx[0] as *u8 552 let sso: *i64 = ctx[23] as *i64 553 let ssl: *i64 = ctx[24] as *i64 554 let sao: *i64 = ctx[25] as *i64 555 let sal: *i64 = ctx[26] as *i64 556 let vpo: *i64 = ctx[8] as *i64 557 let vpl: *i64 = ctx[9] as *i64 558 let vto: *i64 = ctx[12] as *i64 559 let vtl: *i64 = ctx[13] as *i64 560 let vnm: *i64 = ctx[16] as *i64 561 let vx: *i64 = ctx[18] as *i64 562 let nv: i64 = msl_series_vols(ctx, sid, vids) 563 var w: i64 = w0 564 w = msl_cats(out, w, "{\"t\":\"" as *u8) 565 w = msl_jesc(out, w, buf, sso[sid], sso[sid] + ssl[sid]) 566 w = msl_cats(out, w, "\",\"a\":\"" as *u8) 567 w = msl_jesc(out, w, buf, sao[sid], sao[sid] + sal[sid]) 568 w = msl_cats(out, w, "\",\"n\":" as *u8) 569 w = msl_catn(out, w, nv) 570 w = msl_cats(out, w, ",\"vols\":[" as *u8) 571 var k: i64 = 0 572 while k < nv { 573 if w < cap - MSL_MAGIC_2048 { 574 let v: i64 = vids[k] 575 if k > 0 { w = msl_cats(out, w, "," as *u8) } 576 w = msl_cats(out, w, "{\"t\":\"" as *u8) 577 w = msl_jesc(out, w, buf, vto[v], vto[v] + vtl[v]) 578 w = msl_cats(out, w, "\",\"p\":\"" as *u8) 579 w = msl_jesc(out, w, buf, vpo[v], vpo[v] + vpl[v]) 580 w = msl_cats(out, w, "\",\"v\":" as *u8) 581 w = msl_catn(out, w, vnm[v]) 582 w = msl_cats(out, w, ",\"x\":" as *u8) 583 w = msl_catn(out, w, vx[v]) 584 w = msl_cats(out, w, "}" as *u8) 585 } 586 k = k + 1 587 } 588 w = msl_cats(out, w, "]}" as *u8) 589 return w 590} 591 592func msl_emit(ctx: *i64, out: *u8, cap: i64) -> i64 { 593 let nser: i64 = ctx[7] 594 let sidx: *i64 = sys_mmap(MSL_MAXSER * 8) as *i64 595 let vids: *i64 = sys_mmap(MSL_MAGIC_8192) as *i64 596 msl_sort_series(ctx, sidx) 597 var w: i64 = 0 598 w = msl_cats(out, w, "{\"series\":[" as *u8) 599 var i: i64 = 0 600 var emitted: i64 = 0 601 while i < nser { 602 if w < cap - MSL_MAGIC_8192 { 603 if emitted > 0 { w = msl_cats(out, w, "," as *u8) } 604 w = msl_emit_series(ctx, out, w, cap, sidx[i], vids) 605 emitted = emitted + 1 606 } 607 i = i + 1 608 } 609 w = msl_cats(out, w, "],\"total\":" as *u8) 610 w = msl_catn(out, w, nser) 611 w = msl_cats(out, w, "}" as *u8) 612 out[w] = 0 as u8 613 return w 614} 615 616// build the whole shelf JSON (NUL-terminated) into out. Missing idx -> empty catalog, never an error page. 617func msl_shelf_json(out: *u8, cap: i64) -> i64 { 618 let ctx: *i64 = msl_ctx_new() 619 let lb: *i64 = sys_mmap(16) as *i64 620 let ib: *u8 = sys_read_file(MSL_IDX, lb) 621 if (ib as i64) == 0 { 622 let w0: i64 = msl_cats(out, 0, "{\"series\":[],\"total\":0}" as *u8) 623 out[w0] = 0 as u8 624 return w0 625 } 626 ctx[0] = ib as i64 627 ctx[1] = lb[0] 628 let mb: *i64 = sys_mmap(16) as *i64 629 let mk: *u8 = sys_read_file(MSL_MARKERS, mb) 630 if (mk as i64) == 0 { ctx[3] = 0; ctx[4] = 0 } else { ctx[3] = mk as i64; ctx[4] = mb[0] } 631 let ab2: *i64 = sys_mmap(16) as *i64 632 let au: *u8 = sys_read_file(MSL_AUTHORS, ab2) 633 if (au as i64) == 0 { ctx[5] = 0; ctx[6] = 0 } else { ctx[5] = au as i64; ctx[6] = ab2[0] } 634 msl_scan(ctx) 635 msl_firsttok(ctx) 636 msl_group(ctx) 637 return msl_emit(ctx, out, cap) 638}