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}