code wiki / (root) / nx_mvault_fetch.nx

nx_mvault_fetch.nx source

↩ module page · 501 lines · 22746 B

1// nx_mvault_fetch.nx -- album/gallery ingest for the vault (a BunkrDownloader-class 2// capability, sovereign and data-driven). 3// 4// OPERATOR DECISION 2026-07-31: adapters are a DATA TABLE, not code. A new site is 5// an INSERT, not a rebuild. That is rule 11 (policy in data, never buried in code) 6// and it is also what keeps this from becoming the patch cascade that per-site 7// scraper code always becomes -- every host that needs a `if host == "..."` branch 8// is a redeploy, and the branch list is never complete. 9// 10// WHAT AN ADAPTER SAYS: album pages differ only in WHERE the links live. So a row 11// describes that shape -- 12// link_tag/link_attr/link_pfx where the per-item links are on an ALBUM page 13// item_tag/item_attr where the DIRECT media url is on an ITEM page 14// Two-hop by design: album -> item page -> CDN url is the near-universal shape, 15// and a one-hop site is expressed by leaving the item rule empty. 16// 17// ★★★ ORDERING: this writes into the COLLECTION AXIS (nx_mvault_coll) so every 18// fetched file records WHICH ALBUM and WHICH SITE it came from AT INGEST TIME. 19// Ingesting first and grouping later is what produced the 07-30 backfill of 20// 35,615 mis-typed records; provenance captured at ingest is free, recovered 21// later it is a full-corpus pass. 22// 23// This module is the OFFLINE half -- adapter storage + extraction. It is pure and 24// fully gateable with no network, which is deliberate: the parsing rules are where 25// the bugs live, and they should be provable without a live site. 26// license_tier: ORIGINAL 27 28import "nx_syscalls.nx" 29import "nx_canon_cid.nx" 30import "nx_registry.nx" 31import "nx_mvault_record.nx" // mv_rec_field -- read adapter record fields 32import "nx_mvault_tag.nx" // mv_u_dec -- decimal into the JSON report 33import "nx_mvault_coll.nx" // the collection axis this ingest writes into 34import "nx_https_fetch_lib.nx" // hf_fetch (pages) / hf_fetch_to_file (media) 35 36const MVF_PFX: *u8 = "knowledge/store/mvfetch-\x00" 37const MVF_KP: *u8 = "ad:\x00" 38const MVF_IDS: *u8 = "ad:ids\x00" 39 40const MVF_REC_CAP: i64 = 4096 41const MVF_URL_CAP: i64 = 2048 42 43func mvf_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 44func mvf_cat(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 } 45 46func mvf_lower(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 47 48// case-insensitive: does buf[at..] begin with s? 49func mvf_at_ci(buf: *u8, n: i64, at: i64, s: *u8) -> i64 { 50 var i: i64 = 0 51 while s[i] != (0 as u8) { 52 if at + i >= n { return 0 } 53 if mvf_lower(buf[at + i] as i64) != mvf_lower(s[i] as i64) { return 0 } 54 i = i + 1 55 } 56 return 1 57} 58 59// does buf[at..at+len) begin with the NUL-terminated prefix? 60func mvf_has_pfx(buf: *u8, at: i64, len: i64, pfx: *u8) -> i64 { 61 var i: i64 = 0 62 while pfx[i] != (0 as u8) { 63 if i >= len { return 0 } 64 if buf[at + i] != pfx[i] { return 0 } 65 i = i + 1 66 } 67 return 1 68} 69 70// ---- URL HELPERS ---------------------------------------------------------- 71// host of "scheme://host/path" -> out (NUL-terminated); returns length. 72func mvf_host_of(url: *u8, out: *u8) -> i64 { 73 let n: i64 = mvf_strlen(url) 74 var i: i64 = 0 75 var start: i64 = 0 76 while i + 2 < n { 77 if url[i] == (58 as u8) { if url[i+1] == (47 as u8) { if url[i+2] == (47 as u8) { start = i + 3; i = n } } } 78 i = i + 1 79 } 80 var o: i64 = 0 81 var j: i64 = start 82 while j < n { 83 let c: u8 = url[j] 84 if c == (47 as u8) { j = n } else { 85 if c == (58 as u8) { j = n } else { out[o] = c; o = o + 1; j = j + 1 } 86 } 87 } 88 out[o] = 0 as u8 89 return o 90} 91 92// scheme://host of a url -> out; returns length. 93func mvf_origin_of(url: *u8, out: *u8) -> i64 { 94 let n: i64 = mvf_strlen(url) 95 var start: i64 = 0 96 var i: i64 = 0 97 while i + 2 < n { 98 if url[i] == (58 as u8) { if url[i+1] == (47 as u8) { if url[i+2] == (47 as u8) { start = i + 3; i = n } } } 99 i = i + 1 100 } 101 var o: i64 = 0 102 var j: i64 = 0 103 while j < start { out[o] = url[j]; o = o + 1; j = j + 1 } 104 j = start 105 while j < n { if url[j] == (47 as u8) { j = n } else { out[o] = url[j]; o = o + 1; j = j + 1 } } 106 out[o] = 0 as u8 107 return o 108} 109 110// Resolve href against a base url. Absolute (has "://") passes through; 111// root-relative ("/f/x") gets scheme://host; anything else is refused (0) rather 112// than guessed -- a mis-resolved url silently downloads the WRONG file, so a 113// shape we do not understand must not be turned into a plausible-looking one. 114func mvf_resolve(base: *u8, href: *u8, out: *u8, cap: i64) -> i64 { 115 let hn: i64 = mvf_strlen(href) 116 if hn == 0 { return 0 } 117 var i: i64 = 0 118 var abs: i64 = 0 119 while i + 2 < hn { if href[i]==(58 as u8) { if href[i+1]==(47 as u8) { if href[i+2]==(47 as u8) { abs=1; i=hn } } } i = i + 1 } 120 if abs == 1 { 121 if hn + 1 > cap { return 0 - 1 } 122 var k: i64 = 0 123 while k < hn { out[k] = href[k]; k = k + 1 } 124 out[hn] = 0 as u8 125 return hn 126 } 127 if href[0] != (47 as u8) { return 0 } 128 let org: *u8 = sys_mmap(MVF_URL_CAP) 129 let ol: i64 = mvf_origin_of(base, org) 130 if ol + hn + 1 > cap { sys_munmap(org, MVF_URL_CAP); return 0 - 1 } 131 var o: i64 = 0 132 while o < ol { out[o] = org[o]; o = o + 1 } 133 var q: i64 = 0 134 while q < hn { out[o] = href[q]; o = o + 1; q = q + 1 } 135 out[o] = 0 as u8 136 sys_munmap(org, MVF_URL_CAP) 137 return o 138} 139 140// ---- EXTRACTION ----------------------------------------------------------- 141// Pull every <tag ... attr="value"> whose value starts with pfx (pfx "" = any), 142// newline-separated, into out. Returns bytes written, or -1 if it does not fit. 143// 144// REFUSES rather than truncating, same contract as reg_index after the 145// 2026-07-31 root fix: a half-read album that looks complete is how a downloader 146// silently skips files and reports success. 147// 148// Deliberately a TAG+ATTRIBUTE scanner, not a regex over the whole document: 149// the adapter names the tag and attribute, so the rule stays legible as DATA and 150// cannot accidentally match a url mentioned in prose or a script blob. 151func mvf_extract(html: *u8, n: i64, tag: *u8, attr: *u8, pfx: *u8, out: *u8, cap: i64) -> i64 { 152 var o: i64 = 0 153 var i: i64 = 0 154 while i < n { 155 if html[i] == (60 as u8) { 156 if mvf_at_ci(html, n, i + 1, tag) == 1 { 157 let tl: i64 = mvf_strlen(tag) 158 var p: i64 = i + 1 + tl 159 var okb: i64 = 0 160 if p < n { if html[p]==(32 as u8) { okb=1 } if html[p]==(9 as u8) { okb=1 } if html[p]==(10 as u8) { okb=1 } if html[p]==(13 as u8) { okb=1 } } 161 if okb == 1 { 162 // scan attributes until the tag closes 163 var g: i64 = 1 164 while g == 1 { 165 if p >= n { g = 0 } else { 166 if html[p] == (62 as u8) { g = 0 } else { 167 if mvf_at_ci(html, n, p, attr) == 1 { 168 // NOTE: NishiLang has no `break`, and the first cut of this used 169 // `q = n + 1` as a loop-exit sentinel -- which exits the loop but 170 // DESTROYS the very index the next step needs, so every extraction 171 // returned 0. Caught by the gate before this shipped. Skip whitespace 172 // with an explicit flag and leave the cursor where it landed. 173 var q: i64 = p + mvf_strlen(attr) 174 var gq: i64 = 1 175 while gq == 1 { if q >= n { gq = 0 } else { if html[q]==(32 as u8) { q = q + 1 } else { gq = 0 } } } 176 if q <= n { if q < n { if html[q] == (61 as u8) { 177 var v: i64 = q + 1 178 var gv: i64 = 1 179 while gv == 1 { if v >= n { gv = 0 } else { if html[v]==(32 as u8) { v = v + 1 } else { gv = 0 } } } 180 if v <= n { 181 var quote: i64 = 0 182 if v < n { if html[v]==(34 as u8) { quote=34; v=v+1 } else { if html[v]==(39 as u8) { quote=39; v=v+1 } } } 183 var e: i64 = v 184 var g2: i64 = 1 185 while g2 == 1 { 186 if e >= n { g2 = 0 } else { 187 if quote != 0 { if (html[e] as i64) == quote { g2 = 0 } else { e = e + 1 } } 188 else { if html[e]==(32 as u8) { g2=0 } else { if html[e]==(62 as u8) { g2=0 } else { e=e+1 } } } 189 } 190 } 191 let vlen: i64 = e - v 192 if vlen > 0 { if mvf_has_pfx(html, v, vlen, pfx) == 1 { 193 if o + vlen + 1 > cap { return 0 - 1 } 194 var w: i64 = 0 195 while w < vlen { out[o] = html[v + w]; o = o + 1; w = w + 1 } 196 out[o] = 10 as u8; o = o + 1 197 } } 198 p = e 199 } 200 } } } 201 } 202 p = p + 1 203 } 204 } 205 } 206 i = p 207 } 208 } 209 } 210 i = i + 1 211 } 212 return o 213} 214 215// ---- ADAPTER TABLE (the data) --------------------------------------------- 216func mvf_adapter_rec(out: *u8, host: *u8, link_tag: *u8, link_attr: *u8, link_pfx: *u8, 217 item_tag: *u8, item_attr: *u8, ua: *u8) -> i64 { 218 let keys: *i64 = sys_mmap(8 * 7) as *i64 219 let vals: *i64 = sys_mmap(8 * 7) as *i64 220 let k_host: *u8 = "host" as *u8 221 let k_lt: *u8 = "link_tag" as *u8 222 let k_la: *u8 = "link_attr" as *u8 223 let k_lp: *u8 = "link_pfx" as *u8 224 let k_it: *u8 = "item_tag" as *u8 225 let k_ia: *u8 = "item_attr" as *u8 226 let k_ua: *u8 = "ua" as *u8 227 keys[0]=k_host as i64; vals[0]=host as i64 228 keys[1]=k_lt as i64; vals[1]=link_tag as i64 229 keys[2]=k_la as i64; vals[2]=link_attr as i64 230 keys[3]=k_lp as i64; vals[3]=link_pfx as i64 231 keys[4]=k_it as i64; vals[4]=item_tag as i64 232 keys[5]=k_ia as i64; vals[5]=item_attr as i64 233 keys[6]=k_ua as i64; vals[6]=ua as i64 234 let rl: i64 = canon_encode(keys, vals, 7, out) 235 sys_munmap(keys as *u8, 8 * 7) 236 sys_munmap(vals as *u8, 8 * 7) 237 return rl 238} 239 240func mvf_adapter_put_pfx(prefix: *u8, host: *u8, link_tag: *u8, link_attr: *u8, link_pfx: *u8, 241 item_tag: *u8, item_attr: *u8, ua: *u8) -> i64 { 242 let rec: *u8 = sys_mmap(MVF_REC_CAP) 243 let rl: i64 = mvf_adapter_rec(rec, host, link_tag, link_attr, link_pfx, item_tag, item_attr, ua) 244 let rc: i64 = reg_put(prefix, MVF_KP, MVF_IDS, host, rec, rl) 245 sys_munmap(rec, MVF_REC_CAP) 246 return rc 247} 248 249// Find the adapter for a host, trying the most specific first and then dropping 250// leading labels: img.bunkr.si -> bunkr.si -> si. So one row covers a whole 251// family of mirror domains, which is exactly how these hosts multiply, WITHOUT 252// the row having to enumerate them. 253func mvf_adapter_find_pfx(prefix: *u8, host: *u8, ptrout: *i64, lenout: *i64) -> i64 { 254 let n: i64 = mvf_strlen(host) 255 var start: i64 = 0 256 while start < n { 257 let cand: *u8 = ((host as i64) + start) as *u8 258 if reg_get(prefix, MVF_KP, cand, ptrout, lenout) == 1 { return 1 } 259 // drop to just past the next dot 260 var d: i64 = start 261 var moved: i64 = 0 262 while d < n { if host[d] == (46 as u8) { start = d + 1; moved = 1; d = n } else { d = d + 1 } } 263 if moved == 0 { start = n } 264 } 265 return 0 266} 267 268func mvf_adapter_list_pfx(prefix: *u8, out: *u8, cap: i64) -> i64 { 269 return reg_index(prefix, MVF_IDS, out, cap) 270} 271 272// ---- ONLINE HALF: album -> vault ------------------------------------------ 273 274// Last path segment of a url, SANITISED, into out. Refuses (0) a name that is 275// empty or carries a path separator or ".." -- a remote server chooses this 276// string, so it is untrusted input at a boundary (rule 12) and a crafted one 277// would otherwise write outside the destination directory. 278func mvf_basename(url: *u8, out: *u8, cap: i64) -> i64 { 279 let n: i64 = mvf_strlen(url) 280 var start: i64 = 0 281 var i: i64 = 0 282 while i < n { if url[i] == (47 as u8) { start = i + 1 } i = i + 1 } 283 var e: i64 = start 284 while e < n { if url[e] == (63 as u8) { e = n } else { e = e + 1 } } 285 var end: i64 = start 286 var j: i64 = start 287 while j < n { if url[j] == (63 as u8) { j = n } else { end = j + 1; j = j + 1 } } 288 let ln: i64 = end - start 289 if ln <= 0 { return 0 } 290 if ln + 1 > cap { return 0 } 291 var o: i64 = 0 292 while o < ln { 293 let c: u8 = url[start + o] 294 if c == (47 as u8) { return 0 } 295 if c == (92 as u8) { return 0 } 296 out[o] = c 297 o = o + 1 298 } 299 out[ln] = 0 as u8 300 if ln >= 2 { if out[0] == (46 as u8) { if out[1] == (46 as u8) { return 0 } } } 301 return ln 302} 303 304// Read a NUL-terminated field out of an adapter record. 305func mvf_field(rec: *u8, rl: i64, key: *u8, out: *u8, cap: i64) -> i64 { 306 let po: *i64 = sys_mmap(16) as *i64 307 let lo: *i64 = sys_mmap(16) as *i64 308 if mv_rec_field(rec, rl, key, mvf_strlen(key), po, lo) != 1 { out[0] = 0 as u8; return 0 } 309 let n: i64 = lo[0] 310 if n + 1 > cap { out[0] = 0 as u8; return 0 } 311 let src: *u8 = po[0] as *u8 312 var i: i64 = 0 313 while i < n { out[i] = src[i]; i = i + 1 } 314 out[n] = 0 as u8 315 return n 316} 317 318const MVF_PAGE_CAP: i64 = 8388608 319const MVF_LINKS_CAP: i64 = 1048576 320const MVF_ITEMS_CAP: i64 = 1048576 321const MVF_MAX_ITEMS: i64 = 4096 322 323// Ingest one album URL into the vault, recording its SITE and ALBUM provenance 324// at ingest time. 325// 326// ★ PROVENANCE IS CAPTURED HERE, NOT LATER. Recovering "which album was this" 327// after the fact means a full-corpus pass -- that is exactly what the 07-30 328// classifier backfill cost (35,615 records). Captured at ingest it is free. 329// 330// Returns items ingested, or negative: -1 no adapter, -2 album fetch failed, 331// -3 no links found. Writes a JSON summary into report. 332func mvf_fetch_album_pfx(cpfx: *u8, store: i64, album_url: *u8, dest_dir: *u8, 333 report: *u8, rcap: i64) -> i64 { 334 let host: *u8 = sys_mmap(512) 335 mvf_host_of(album_url, host) 336 337 let po: *i64 = sys_mmap(16) as *i64 338 let lo: *i64 = sys_mmap(16) as *i64 339 if mvf_adapter_find(host, po, lo) != 1 { return 0 - 1 } 340 let rec: *u8 = po[0] as *u8 341 let rl: i64 = lo[0] 342 343 let l_tag: *u8 = sys_mmap(128) 344 let l_attr: *u8 = sys_mmap(128) 345 let l_pfx: *u8 = sys_mmap(256) 346 let i_tag: *u8 = sys_mmap(128) 347 let i_attr: *u8 = sys_mmap(128) 348 mvf_field(rec, rl, "link_tag" as *u8, l_tag, 128) 349 mvf_field(rec, rl, "link_attr" as *u8, l_attr, 128) 350 mvf_field(rec, rl, "link_pfx" as *u8, l_pfx, 256) 351 mvf_field(rec, rl, "item_tag" as *u8, i_tag, 128) 352 mvf_field(rec, rl, "item_attr" as *u8, i_attr, 128) 353 354 // ---- album page ---- 355 let page: *u8 = sys_mmap(MVF_PAGE_CAP) 356 let pn: i64 = hf_fetch(store, album_url, 0, 0, page, MVF_PAGE_CAP) 357 if pn <= 0 { return 0 - 2 } 358 var boff: i64 = hf_body_off(page, pn) 359 if boff < 0 { boff = 0 } 360 let body: *u8 = ((page as i64) + boff) as *u8 361 362 let links: *u8 = sys_mmap(MVF_LINKS_CAP) 363 let ln: i64 = mvf_extract(body, pn - boff, l_tag, l_attr, l_pfx, links, MVF_LINKS_CAP) 364 if ln <= 0 { return 0 - 3 } 365 366 // ---- declare the two collections this album's files belong to ---- 367 let site_col: *u8 = sys_mmap(256) 368 let alb_col: *u8 = sys_mmap(256) 369 mvc_declare_pfx(cpfx, "site" as *u8, host, host, host, album_url, "-" as *u8, site_col) 370 let akey: *u8 = sys_mmap(1024) 371 if mvf_basename(album_url, akey, 1024) == 0 { mvf_cat(akey, 0, "album" as *u8); akey[5] = 0 as u8 } 372 mvc_declare_pfx(cpfx, "album" as *u8, host, akey, akey, album_url, site_col, alb_col) 373 374 let items: *u8 = sys_mmap(MVF_ITEMS_CAP) 375 var io: i64 = 0 376 var got: i64 = 0 377 var failed: i64 = 0 378 var truncated: i64 = 0 379 380 let one: *u8 = sys_mmap(MVF_URL_CAP) 381 let direct: *u8 = sys_mmap(MVF_URL_CAP) 382 let ipage: *u8 = sys_mmap(MVF_PAGE_CAP) 383 let dbuf: *u8 = sys_mmap(MVF_LINKS_CAP) 384 let path: *u8 = sys_mmap(2048) 385 let base: *u8 = sys_mmap(1024) 386 let cid: *u8 = sys_mmap(256) 387 let szbox: *i64 = sys_mmap(16) as *i64 388 let stbox: *i64 = sys_mmap(16) as *i64 389 390 var ls: i64 = 0 391 var i: i64 = 0 392 while i <= ln { 393 var eol: i64 = 0 394 if i == ln { eol = 1 } else { if links[i] == (10 as u8) { eol = 1 } } 395 if eol == 1 { 396 let hl: i64 = i - ls 397 if hl > 0 { 398 if got >= MVF_MAX_ITEMS { truncated = 1 } else { 399 let href: *u8 = sys_mmap(MVF_URL_CAP) 400 var q: i64 = 0 401 while q < hl { href[q] = links[ls + q]; q = q + 1 } 402 href[hl] = 0 as u8 403 404 var ok: i64 = 1 405 if mvf_resolve(album_url, href, one, MVF_URL_CAP) <= 0 { ok = 0 } 406 407 // second hop: the item PAGE carries the direct CDN url 408 if ok == 1 { if i_tag[0] != (0 as u8) { 409 let inn: i64 = hf_fetch(store, one, 0, 0, ipage, MVF_PAGE_CAP) 410 if inn <= 0 { ok = 0 } else { 411 var ibo: i64 = hf_body_off(ipage, inn) 412 if ibo < 0 { ibo = 0 } 413 let dn: i64 = mvf_extract(((ipage as i64) + ibo) as *u8, inn - ibo, i_tag, i_attr, "" as *u8, dbuf, MVF_LINKS_CAP) 414 if dn <= 0 { ok = 0 } else { 415 // take the FIRST extracted direct url 416 var cut: i64 = 0 417 var z: i64 = 0 418 while z < dn { if dbuf[z] == (10 as u8) { cut = z; z = dn } else { z = z + 1 } } 419 if cut == 0 { cut = dn } 420 let tmp: *u8 = sys_mmap(MVF_URL_CAP) 421 var w: i64 = 0 422 while w < cut { tmp[w] = dbuf[w]; w = w + 1 } 423 tmp[cut] = 0 as u8 424 if mvf_resolve(one, tmp, direct, MVF_URL_CAP) <= 0 { ok = 0 } 425 } 426 } 427 } else { 428 var c2: i64 = 0 429 while one[c2] != (0 as u8) { direct[c2] = one[c2]; c2 = c2 + 1 } 430 direct[c2] = 0 as u8 431 } } 432 433 // ---- stream to disk, then CID the FILE (never the buffer) ---- 434 if ok == 1 { 435 if mvf_basename(direct, base, 1024) == 0 { ok = 0 } else { 436 var pO: i64 = mvf_cat(path, 0, dest_dir) 437 path[pO] = 47 as u8; pO = pO + 1 438 pO = mvf_cat(path, pO, base) 439 path[pO] = 0 as u8 440 let wfd: i64 = sys_openat_wr(path, 420) 441 if wfd < 0 { ok = 0 } else { 442 let wn: i64 = hf_fetch_to_file(store, direct, 0, 0, wfd, 0, stbox) 443 sys_close(wfd) 444 if wn <= 0 { ok = 0; sys_unlinkat(path) } 445 } 446 } 447 } 448 if ok == 1 { 449 if cid_of_file_chunk(path, 4194304, szbox, cid) <= 0 { ok = 0 } 450 } 451 if ok == 1 { 452 let cl: i64 = mvf_strlen(cid) 453 if io + cl + 2 < MVF_ITEMS_CAP { 454 var y: i64 = 0 455 while y < cl { items[io] = cid[y]; io = io + 1; y = y + 1 } 456 items[io] = 10 as u8; io = io + 1 457 got = got + 1 458 } 459 } else { failed = failed + 1 } 460 } 461 } 462 ls = i + 1 463 } 464 i = i + 1 465 } 466 467 // ---- one segment per collection, both directions of provenance ---- 468 var linked: i64 = 0 469 if io > 0 { 470 linked = mvc_batch_add_pfx(cpfx, alb_col, items, io) 471 mvc_batch_add_pfx(cpfx, site_col, items, io) 472 } 473 474 var ro: i64 = mvf_cat(report, 0, "{\"album\":\"" as *u8) 475 ro = mvf_cat(report, ro, alb_col) 476 ro = mvf_cat(report, ro, "\",\"site\":\"" as *u8) 477 ro = mvf_cat(report, ro, site_col) 478 ro = mvf_cat(report, ro, "\",\"found\":" as *u8) 479 ro = mv_u_dec(report, ro, mvc_count_lines(links, ln)) 480 ro = mvf_cat(report, ro, ",\"ingested\":" as *u8) 481 ro = mv_u_dec(report, ro, got) 482 ro = mvf_cat(report, ro, ",\"linked\":" as *u8) 483 ro = mv_u_dec(report, ro, linked) 484 ro = mvf_cat(report, ro, ",\"failed\":" as *u8) 485 ro = mv_u_dec(report, ro, failed) 486 // NEVER a silent cap: if the envelope truncated the album, SAY SO in the 487 // report, or a partial ingest reads as a complete one. 488 ro = mvf_cat(report, ro, ",\"truncated\":" as *u8) 489 ro = mv_u_dec(report, ro, truncated) 490 ro = mvf_cat(report, ro, "}\n" as *u8) 491 report[ro] = 0 as u8 492 return got 493} 494 495// ---- production-prefix wrappers ------------------------------------------- 496func mvf_adapter_put(host: *u8, link_tag: *u8, link_attr: *u8, link_pfx: *u8, item_tag: *u8, item_attr: *u8, ua: *u8) -> i64 { 497 return mvf_adapter_put_pfx(MVF_PFX, host, link_tag, link_attr, link_pfx, item_tag, item_attr, ua) 498} 499func mvf_adapter_find(host: *u8, ptrout: *i64, lenout: *i64) -> i64 { return mvf_adapter_find_pfx(MVF_PFX, host, ptrout, lenout) } 500func mvf_adapter_list(out: *u8, cap: i64) -> i64 { return mvf_adapter_list_pfx(MVF_PFX, out, cap) } 501func mvf_fetch_album(store: i64, album_url: *u8, dest_dir: *u8, report: *u8, rcap: i64) -> i64 { return mvf_fetch_album_pfx(MVC_PFX, store, album_url, dest_dir, report, rcap) }