code wiki / (root) / nx_arcmine.nx

nx_arcmine.nx source

↩ module page · 468 lines · 27072 B

1// nx_arcmine.nx -- INDEX-FIRST domain media harvester. The rung that makes a sovereign archive EXCEED 2// the Wayback Machine on media completeness (the 2026-06-29 goal: page3.com / megastar.co.uk day-by-day 3// WITH their media). 4// 5// module: nishi-core.archive.arcmine 6// depends: nx_medrec.nx (admission), nx_cdx_parse.nx (rows/fields), nx_https_fetch_follow.nx 7// usage: nx_arcmine <domain> [page] [min_bytes] [max_fetch] [mime] 8// capability: LIVE FETCH, read-only against the archive. Additive + IDEMPOTENT (Rule 10). 9// 10// *WHY INDEX-FIRST, AND WHY IT BEATS THE PAGE-WALK -- the whole point of this organ: 11// nx_archive_site_viewer discovers media by RENDERING a captured page and reading its <img> tags. That 12// ceiling is structural: it can only ever find media referenced by the pages it happens to render, at the 13// one timestamp it renders them. Every image on a page never rendered, on a page never captured, or 14// referenced only from a since-changed layout, is INVISIBLE to it -- and its absence is invisible too. 15// The CDX index knows every image the crawler EVER saw across the whole host and all time. Enumerating 16// from the index is exhaustive BY CONSTRUCTION rather than by luck of which page we happened to open. 17// That is the difference between browsing an archive and MINING one. 18// 19// *DEDUPE BY DIGEST, MEASURED 2026-08-06: captures of one URL are byte-identical. Real sample -- 20// page3.com/3zine_pages/html_preview/01/ held 21 status-200 capture rows that collapse to just 6 unique 21// digests (01.jpg alone: 6 captures, ONE digest HSTRJJRQ.., sizes 27236..27245 = 9 bytes of WARC record 22// framing, not pixels). Fetching per-capture would spend 3.5x the bandwidth for byte-identical output. 23// collapse=digest is therefore applied at the QUERY, so the redundancy never crosses the network at all. 24// *AND THE COROLLARY THAT KILLED A PLANNED CHANGE: because captures are byte-identical, choosing the 25// LARGEST capture over the EARLIEST buys exactly nothing on this corpus. That tweak was measured, found 26// worthless, and NOT shipped. 27// 28// *TOMBSTONES ARE REJECTED FROM THE INDEX (nx_medrec), before a single byte is fetched -- a host's 29// "image not found" placeholder is a structurally valid JPEG that magic-byte validation cannot fail. 30// 31// *IDEMPOTENT BY CONSTRUCTION: the output filename IS the content digest, so a re-run rewrites identical 32// bytes and can never duplicate or corrupt. Safe to run twice, or to resume a partial page. 33// *PACING: max_fetch bounds one invocation deliberately. The archive is a shared resource and we have 34// rate-limited ourselves off hosts before by sweeping too fast; the operator advances pages at their own 35// cadence. OWED: wire pace_before(host) from nx_crawl_pace so politeness is structural, not procedural. 36// license_tier: ORIGINAL 37import "nx_medrec.nx" 38import "nx_x509_trust_store.nx" 39import "nx_trust_store_load_from_certdata.nx" 40import "nx_https_fetch_follow.nx" 41 42const AM_CAP: i64 = 4194304 43const AM_ROWCAP: i64 = 4000 44const AM_PATH: i64 = 1024 45const AM_URLBUF: i64 = 4096 46const AM_DIRMODE: i64 = 0x1ed 47const AM_FILEMODE: i64 = 0x1a4 48const AM_DEF_MIN: i64 = 2048 49const AM_DEF_MAX: i64 = 40 50 51func aw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 52func an(v: i64) -> i64 { if v==0 { sys_write(1,"0" as *u8,1); return 0 } var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } let t: *u8=sys_mmap(24); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } let o: *u8=sys_mmap(24); var w: i64=0; var q: i64=k-1; while q>=0 { o[w]=t[q]; w=w+1; q=q-1 } sys_write(1,o,w); return 0 } 53func aslice(buf: *u8, off: i64, len: i64) -> i64 { sys_write(1, ((buf as i64)+off) as *u8, len); return 0 } 54func ap(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { buf[off+i]=s[i]; i=i+1 } return off+i } 55func apsl(dst: *u8, off: i64, src: *u8, so: i64, sl: i64) -> i64 { var i: i64=0; while i<sl { dst[off+i]=src[so+i]; i=i+1 } return off+sl } 56func apn(buf: *u8, off: i64, v: i64) -> i64 { if v==0 { buf[off]=0x30 as u8; return off+1 } var m: i64=v; let t: *u8=sys_mmap(24); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } var o: i64=off; var q: i64=k-1; while q>=0 { buf[o]=t[q]; o=o+1; q=q-1 } return o } 57func s2i(s: *u8) -> i64 { var v: i64=0; var i: i64=0; var any: i64=0; while s[i]!=(0 as u8) { let c: i64=s[i] as i64; if c<0x30 { return 0-1 } if c>0x39 { return 0-1 } v=v*10+(c-0x30); any=1; i=i+1 } if any==0 { return 0-1 } return v } 58 59// real image magic -- the STRUCTURE check. Kept because a fetch can still return markup or a truncated 60// body even after the INDEX admitted the row; nx_medrec covers identity, this covers form. 61func is_image(buf: *u8, n: i64) -> i64 { 62 if n<4 { return 0 } 63 let a: i64=buf[0] as i64; let b: i64=buf[1] as i64; let c: i64=buf[2] as i64; let d: i64=buf[3] as i64 64 if a==0x47 { if b==0x49 { if c==0x46 { return 1 } } } 65 if a==0xff { if b==0xd8 { if c==0xff { return 1 } } } 66 if a==0x89 { if b==0x50 { if c==0x4e { if d==0x47 { return 1 } } } } 67 if a==0x42 { if b==0x4d { return 1 } } 68 return 0 69} 70// *A PARTIAL WRITE MUST NEVER OCCUPY THE FINAL PATH. The skip check is a pure EXISTENCE test, so a 71// truncated file left by a failed write is skipped on every later run -- permanent corruption that 72// reports as "already had" forever and is invisible in the counters. Write to <path>.part and rename 73// into place only once the FULL byte count is down; a crash then leaves a .part (which the skip check 74// ignores and the next run overwrites), never half an image at the real name. 75// *Adding the skip optimisation silently changed the meaning of "the file is there" from "we fetched it" 76// to "something is at that path". This restores the first meaning, which is the one the skip relies on. 77func save_file(path: *u8, buf: *u8, n: i64) -> i64 { 78 let tmp: *u8 = sys_mmap(AM_PATH) 79 var t: i64 = ap(tmp, 0, path) 80 t = ap(tmp, t, ".part" as *u8) 81 tmp[t]=0 as u8 82 let fd: i64=sys_openat_wr(tmp, AM_FILEMODE) 83 if fd<0 { return 0 } 84 var off: i64=0 85 while off<n { 86 let w: i64=sys_write(fd, ((buf as i64)+off) as *u8, n-off) 87 if w<=0 { sys_close(fd); sys_unlinkat(tmp); return 0 } 88 off=off+w 89 } 90 sys_close(fd) 91 if off != n { sys_unlinkat(tmp); return 0 } 92 if sys_renameat(tmp, path) < 0 { sys_unlinkat(tmp); return 0 } 93 return 1 94} 95// *THE DIGEST IS KNOWN FROM THE INDEX BEFORE ANY FETCH, so an already-harvested image can be skipped 96// without spending a request. Measured need: re-running a page to continue past max_fetch was re-fetching 97// every image already stored -- idempotent, but it spends a shared rate-limited resource for zero gain. 98func have_file(path: *u8) -> i64 { let fd: i64=sys_openat_rd(path); if fd<0 { return 0 } sys_close(fd); return 1 } 99// *THE REVERSE INDEX THAT LETS THE PAGE RENDERER SKIP THE NETWORK ENTIRELY. 100// nx_archive_site_viewer rebuilds a captured page and must resolve each <img src> to bytes. Fetching them 101// is what makes it slow enough to be killed mid-run. But this harvester ALREADY stores those bytes, 102// content-addressed by digest -- the only thing missing is a URL -> digest lookup, because the sidecars are 103// keyed the other way. byurl/<canonhash>.txt closes that loop: the renderer becomes a local, network-free, 104// millisecond operation over whatever the harvester has already banked. 105// 106// *CANONICALISATION IS THE WHOLE TRICK. The CDX 'original' column reads http://www.page3.com:80/foo.jpg 107// while the page's own markup says /foo.jpg or http://www.page3.com/foo.jpg. Hashing either verbatim gives 108// two different keys and the join silently never matches -- it would look like "the archive doesn't have it" 109// rather than "we hashed it wrong". So both sides hash the SAME reduced form: scheme dropped, leading www. 110// dropped, :80/:443 dropped, host lowercased, query dropped, path kept verbatim. 111func canon_hash(buf: *u8, off: i64, len: i64) -> i64 { 112 let end0: i64 = off + len 113 var p: i64 = off 114 var i: i64 = off 115 var sch: i64 = 0 - 1 116 while i + 2 < end0 { 117 if buf[i]==(58 as u8) { if buf[i+1]==(47 as u8) { if buf[i+2]==(47 as u8) { if sch < 0 { sch = i + 3 } } } } 118 i = i + 1 119 } 120 // *PATH ONLY -- the host is DELIBERATELY excluded. byurl/ already lives under 121 // web_assets/archive/<host>/, so the host is established by the DIRECTORY and is redundant here. 122 // Worse, including it is actively WRONG: the CDX 'original' column carries an absolute url 123 // (http://www.page3.com:80/x.jpg) while a page's own markup usually carries a relative one (/x.jpg), 124 // so a host+path key hashes those two forms DIFFERENTLY and the join never matches -- silently, 125 // presenting as "the archive lacks this image" rather than "we hashed it wrong". Path-only makes 126 // both forms agree by construction. (This replaced a host+path version that had exactly that bug, 127 // caught by hand-trace before it ever ran.) 128 if sch >= 0 { 129 p = end0 130 i = sch 131 var found: i64 = 0 132 while i < end0 { 133 if found == 0 { if buf[i]==(47 as u8) { p = i; found = 1 } } 134 i = i + 1 135 } 136 } 137 var h: i64 = 1125899906842597 138 i = p 139 while i < end0 { 140 let c: i64 = buf[i] as i64 141 if c == 63 { i = end0 } else { 142 // case folded: measured, megastar serves 3pxShadow.png whose CDX urlkey is 3pxshadow.png, 143 // so case genuinely differs between the two sides of this join. 144 h = (h * 131) + mr_lc(c) 145 i = i + 1 146 } 147 } 148 if h < 0 { h = 0 - h } 149 return h & 0x7fffffffffffffff 150} 151func append_file(path: *u8, buf: *u8, n: i64) -> i64 { 152 let fd: i64=sys_openat_append(path, AM_FILEMODE) 153 if fd<0 { return 0 } 154 var off: i64=0 155 while off<n { let w: i64=sys_write(fd, ((buf as i64)+off) as *u8, n-off); if w<=0 { sys_close(fd); return 0 } off=off+w } 156 sys_close(fd) 157 return 1 158} 159func fetch_retry(url: *u8, store: *TrustStore, out: *u8, cap: i64, st: *i64, tries: i64) -> i64 { 160 var t: i64=0; var n: i64=0-1 161 while t<tries { 162 n=nx_https_fetch_follow(url, store, out, cap, 6, st) 163 if n>0 { if st[0]==200 { t=tries } else { t=t+1 } } else { t=t+1 } 164 } 165 return n 166} 167// *THE EXTENSION MUST COME FROM THE ROW, NOT FROM THE QUERY. Measured 2026-08-06: megastar.co.uk serves 168// babes-2.jpg with recorded mimetype image/PNG at 31,078 B -- a real content image whose extension and 169// content-type disagree. Deriving the stored extension from the query filter would name it .jpg, and 170// nx_archive_server sets Content-Type BY EXTENSION, so the browser would be handed image/jpeg for PNG 171// bytes and render nothing. Per-row mime is in the index, available BEFORE the fetch, so the skip check 172// still works. Era formats included (Rule 25) -- a 90s corpus is not all jpeg. 173func ext_for(mime: *u8) -> *u8 { 174 if mr_ci_has(mime, "png") == 1 { return ".png" } 175 if mr_ci_has(mime, "gif") == 1 { return ".gif" } 176 if mr_ci_has(mime, "webp") == 1 { return ".webp" } 177 if mr_ci_has(mime, "avif") == 1 { return ".avif" } 178 if mr_ci_has(mime, "bmp") == 1 { return ".bmp" } 179 if mr_ci_has(mime, "icon") == 1 { return ".ico" } 180 if mr_ci_has(mime, "svg") == 1 { return ".svg" } 181 if mr_ci_has(mime, "tiff") == 1 { return ".tif" } 182 if mr_ci_has(mime, "xbitmap") == 1 { return ".xbm" } 183 if mr_ci_has(mime, "pcx") == 1 { return ".pcx" } 184 return ".jpg" 185} 186// per-row extension: copy the mimetype slice out of the CDX row, then classify it. 187func ext_from_row(buf: *u8, off: i64, len: i64, scratch: *u8) -> *u8 { 188 var i: i64 = 0 189 while i < len { scratch[i] = buf[off+i]; i = i + 1 } 190 scratch[len] = 0 as u8 191 return ext_for(scratch) 192} 193 194// *THE SEARCHABLE SIGNAL LIVES IN THE FILENAME, AND CONTENT-ADDRESSING THROWS IT AWAY. Storing an image 195// as TEOIGMBT..jpg is exactly right for dedupe and catastrophic for retrieval: megastar.co.uk names its 196// files alex_simwise-1.jpg / alicia_keys.jpg / angels_and_demons-1.jpg -- person and subject names, the 197// precise tokens a query will use. A harvest nobody can query is not a search capability. 198// So each stored image also gets a TEXT SIDECAR carrying the original url, the TOKENISED filename 199// (separators -> spaces so BM25 can match "alex simwise"), host, capture timestamp, digest and size. 200// nx_corpus_ingest <metadir> <host> then ingests that directory into the domain's seg_store shard, and the 201// archive becomes queryable through the SAME front door as every other nishi-search corpus. 202// Rule 15/22: compose the proven ingester; do not write a second index writer. 203 204// THE WHOLE PATH, not just the filename -- query and extension stripped, separators folded to spaces. 205// *MEASURED CORRECTION 2026-08-06: the filename is the searchable signal only SOMETIMES. megastar.co.uk 206// mixes human-readable names (alex_simwise-1.jpg, alicia_keys.jpg, 280200_condoms.jpg) with opaque CMS ids 207// (sMEG01MTE0NDQxOTgxMzc.jpg, which tokenises to nothing a human would ever type). For that second class 208// the PATH is the only signal there is: /babenews/news/2006/04/07/ yields category and date. Tokenising the 209// last segment alone would have indexed a large slice of the corpus as unsearchable noise. 210func tok_name(dst: *u8, o: i64, src: *u8, so: i64, sl: i64) -> i64 { 211 let end0: i64 = so + sl 212 var sch: i64 = 0 - 1 213 var i: i64 = so 214 while i + 2 < end0 { 215 if src[i]==(58 as u8) { if src[i+1]==(47 as u8) { if src[i+2]==(47 as u8) { if sch < 0 { sch = i + 3 } } } } 216 i = i + 1 217 } 218 if sch < 0 { sch = so } 219 var start: i64 = sch 220 var found: i64 = 0 221 i = sch 222 while i < end0 { 223 if found == 0 { if src[i]==(47 as u8) { start = i + 1; found = 1 } } 224 i = i + 1 225 } 226 var end: i64 = end0 227 i = start 228 while i < end { if src[i]==(63 as u8) { end = i } else { i=i+1 } } 229 var dot: i64 = 0-1 230 i = start 231 while i < end { if src[i]==(46 as u8) { dot = i } i=i+1 } 232 if dot > start { end = dot } 233 var p: i64 = o 234 i = start 235 while i < end { 236 let c: i64 = src[i] as i64 237 var w: i64 = c 238 if c==95 { w=32 } 239 if c==45 { w=32 } 240 if c==43 { w=32 } 241 if c==37 { w=32 } 242 if c==47 { w=32 } 243 dst[p]=w as u8 244 p=p+1; i=i+1 245 } 246 return p 247} 248 249func main(argc: i64, argv: *i64) -> i64 { 250 aw("=== nx_arcmine: INDEX-FIRST domain media harvest (exceed Wayback on completeness) ===\n" as *u8) 251 if argc < 2 { aw("usage: nx_arcmine <domain> [page] [min_bytes] [max_fetch] [mime]\n" as *u8); return 2 } 252 let host: *u8 = argv[1] as *u8 253 var page: i64 = 0 254 var minb: i64 = AM_DEF_MIN 255 var maxf: i64 = AM_DEF_MAX 256 var mime: *u8 = "image/.*" as *u8 257 if argc >= 3 { let v: i64=s2i(argv[2] as *u8); if v>=0 { page=v } } 258 if argc >= 4 { let v: i64=s2i(argv[3] as *u8); if v>=0 { minb=v } } 259 if argc >= 5 { let v: i64=s2i(argv[4] as *u8); if v>=0 { maxf=v } } 260 if argc >= 6 { mime = argv[5] as *u8 } 261 262 aw("host=" as *u8); aw(host); aw(" page=" as *u8); an(page) 263 aw(" min_bytes=" as *u8); an(minb); aw(" max_fetch=" as *u8); an(maxf) 264 aw(" mime=" as *u8); aw(mime); aw("\n" as *u8) 265 266 sys_mkdir("web_assets" as *u8, AM_DIRMODE) 267 sys_mkdir("web_assets/archive" as *u8, AM_DIRMODE) 268 let dir: *u8 = sys_mmap(AM_PATH) 269 var dof: i64 = ap(dir, 0, "web_assets/archive/" as *u8) 270 dof = ap(dir, dof, host); dir[dof]=0 as u8 271 sys_mkdir(dir, AM_DIRMODE) 272 let mdir: *u8 = sys_mmap(AM_PATH) 273 var mof: i64 = ap(mdir, 0, dir); mof = ap(mdir, mof, "/media" as *u8); mdir[mof]=0 as u8 274 sys_mkdir(mdir, AM_DIRMODE) 275 let tdir: *u8 = sys_mmap(AM_PATH) 276 var tof: i64 = ap(tdir, 0, dir); tof = ap(tdir, tof, "/meta" as *u8); tdir[tof]=0 as u8 277 sys_mkdir(tdir, AM_DIRMODE) 278 let bdir: *u8 = sys_mmap(AM_PATH) 279 var bof: i64 = ap(bdir, 0, dir); bof = ap(bdir, bof, "/byurl" as *u8); bdir[bof]=0 as u8 280 sys_mkdir(bdir, AM_DIRMODE) 281 aw("media dir: " as *u8); aw(mdir); aw("\n" as *u8) 282 aw("byurl dir: " as *u8); aw(bdir); aw(" (url->digest reverse index for the page renderer)\n" as *u8) 283 aw("meta dir: " as *u8); aw(tdir); aw(" (nx_corpus_ingest <metadir> " as *u8); aw(host); aw(" -> searchable)\n" as *u8) 284 285 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, AM_CAP) 286 if r<=0 { aw("trust store load failed\n" as *u8); return 1 } 287 let store: *TrustStore = r as *TrustStore 288 let st: *i64 = sys_mmap(8) as *i64 289 290 // ---- 1. ENUMERATE FROM THE INDEX. collapse=digest kills the byte-identical redundancy server-side. 291 let cu: *u8 = sys_mmap(AM_URLBUF) 292 var co: i64 = ap(cu, 0, "https://web.archive.org/cdx/search/cdx?url=" as *u8) 293 co = ap(cu, co, host) 294 co = ap(cu, co, "&matchType=domain&filter=statuscode:200&filter=mimetype:" as *u8) 295 co = ap(cu, co, mime) 296 co = ap(cu, co, "&collapse=digest&output=text&limit=300&page=" as *u8) 297 co = apn(cu, co, page) 298 cu[co]=0 as u8 299 aw("\nCDX: " as *u8); aw(cu); aw("\n" as *u8) 300 301 let cdxbuf: *u8 = sys_mmap(AM_CAP) 302 let n: i64 = fetch_retry(cu, store, cdxbuf, AM_CAP, st, 4) 303 if n<=0 { aw("CDX fetch failed (transport)\n" as *u8); return 1 } 304 if st[0]!=200 { aw("CDX status " as *u8); an(st[0]); aw(" -- rate limited? back off and retry\n" as *u8); return 1 } 305 306 let RS: *i64 = sys_mmap(AM_ROWCAP*8) as *i64 307 let RE: *i64 = sys_mmap(AM_ROWCAP*8) as *i64 308 let rows: i64 = cdx_rows(cdxbuf, n, RS, RE, AM_ROWCAP) 309 aw("index rows (deduped by digest) = " as *u8); an(rows); aw("\n" as *u8) 310 if rows<=0 { aw("page " as *u8); an(page); aw(" is EMPTY -- end of the index for this host/mime\n" as *u8); return 0 } 311 312 // ---- 2. ADMIT FROM THE INDEX, then fetch only what survives. 313 let o2: *i64 = sys_mmap(64) as *i64 314 let ub: *u8 = sys_mmap(AM_URLBUF) 315 let img: *u8 = sys_mmap(AM_CAP) 316 let path: *u8 = sys_mmap(AM_PATH) 317 let mpath: *u8 = sys_mmap(AM_PATH) 318 let doc: *u8 = sys_mmap(AM_URLBUF) 319 let mscratch: *u8 = sys_mmap(AM_PATH) 320 let gpath: *u8 = sys_mmap(AM_PATH) 321 let doc2: *u8 = sys_mmap(AM_URLBUF) 322 let bpath: *u8 = sys_mmap(AM_PATH) 323 let bref: *u8 = sys_mmap(AM_PATH) 324 325 var seen: i64=0; var adm: i64=0; var rjt: i64=0; var rjs: i64=0; var rjz: i64=0; var rju: i64=0 326 var got: i64=0; var bad: i64=0; var nonimg: i64=0; var bytes: i64=0; var skip: i64=0; var metas: i64=0; var byurls: i64=0 327 var i: i64=0 328 while i<rows { 329 seen=seen+1 330 let v: i64 = mr_row_admit(cdxbuf, RS[i], RE[i], minb, o2) 331 if v == MR_REJ_TOMBSTONE { rjt=rjt+1 } 332 if v == MR_REJ_STATUS { rjs=rjs+1 } 333 if v == MR_REJ_TOOSMALL { rjz=rjz+1 } 334 if v == MR_UNKNOWN { rju=rju+1 } 335 if v == MR_ADMIT { 336 adm=adm+1 337 // CONTENT-ADDRESS FIRST. The digest is an INDEX field, so the destination path is known 338 // before any network decision -- which is what makes the skip below free. 339 cdx_field(cdxbuf, RS[i], RE[i], MR_F_MIME, o2) 340 let xt: *u8 = ext_from_row(cdxbuf, o2[0], o2[1], mscratch) 341 cdx_field(cdxbuf, RS[i], RE[i], MR_F_DIGEST, o2) 342 var p: i64 = ap(path, 0, mdir) 343 p = ap(path, p, "/" as *u8) 344 p = apsl(path, p, cdxbuf, o2[0], o2[1]) 345 p = ap(path, p, xt) 346 path[p]=0 as u8 347 // SEARCH SIDECAR, BUILT FROM INDEX DATA ONLY and written unconditionally. *This must not live 348 // on the fetch path: images harvested before sidecars existed would then be SKIPPED and never 349 // gain metadata -- permanently unsearchable, and silently so. Depending on no fetch makes the 350 // skip path a BACKFILL instead of a dead end. 351 var mp: i64 = ap(mpath, 0, tdir) 352 mp = ap(mpath, mp, "/" as *u8) 353 cdx_field(cdxbuf, RS[i], RE[i], MR_F_DIGEST, o2) 354 mp = apsl(mpath, mp, cdxbuf, o2[0], o2[1]) 355 mp = ap(mpath, mp, ".txt" as *u8) 356 mpath[mp]=0 as u8 357 if have_file(mpath)==0 { 358 cdx_field(cdxbuf, RS[i], RE[i], MR_F_ORIG, o2) 359 let suo: i64=o2[0]; let sul: i64=o2[1] 360 var d: i64 = ap(doc, 0, "archived image\nurl: " as *u8) 361 d = apsl(doc, d, cdxbuf, suo, sul) 362 d = ap(doc, d, "\nname: " as *u8) 363 d = tok_name(doc, d, cdxbuf, suo, sul) 364 d = ap(doc, d, "\nhost: " as *u8) 365 d = ap(doc, d, host) 366 d = ap(doc, d, "\ncaptured: " as *u8) 367 cdx_field(cdxbuf, RS[i], RE[i], MR_F_TS, o2) 368 d = apsl(doc, d, cdxbuf, o2[0], o2[1]) 369 d = ap(doc, d, "\ndigest: " as *u8) 370 cdx_field(cdxbuf, RS[i], RE[i], MR_F_DIGEST, o2) 371 d = apsl(doc, d, cdxbuf, o2[0], o2[1]) 372 d = ap(doc, d, "\nindex_bytes: " as *u8) 373 d = apn(doc, d, mr_row_size(cdxbuf, RS[i], RE[i], o2)) 374 d = ap(doc, d, "\nmedia: " as *u8) 375 d = ap(doc, d, path) 376 d = ap(doc, d, "\n" as *u8) 377 if save_file(mpath, doc, d)==1 { metas=metas+1 } 378 // BROWSABLE SURFACE, built incrementally. Appended inside the SAME have_file guard as the 379 // sidecar, so exactly one tile exists per unique digest no matter how often a page is re-run. 380 var g: i64 = ap(gpath, 0, dir); g = ap(gpath, g, "/index.html" as *u8); gpath[g]=0 as u8 381 if have_file(gpath)==0 { 382 var h: i64 = ap(doc2, 0, "<!doctype html><meta charset=utf-8><title>archive: " as *u8) 383 h = ap(doc2, h, host) 384 h = ap(doc2, h, "</title><style>body{background:#111;color:#ddd;font:14px system-ui;margin:0;padding:16px}h1{font-size:16px;font-weight:600}a{color:#8bf}figure{display:inline-block;margin:6px;vertical-align:top;max-width:240px}img{max-width:240px;height:auto;display:block;background:#222}figcaption{font-size:11px;color:#999;word-break:break-all;padding-top:4px}</style>\n<h1>sovereign archive &mdash; " as *u8) 385 h = ap(doc2, h, host) 386 h = ap(doc2, h, " &mdash; recovered from the Wayback index</h1>\n" as *u8) 387 append_file(gpath, doc2, h) 388 } 389 var t: i64 = ap(doc2, 0, "<figure><a href=\"media/" as *u8) 390 cdx_field(cdxbuf, RS[i], RE[i], MR_F_DIGEST, o2) 391 t = apsl(doc2, t, cdxbuf, o2[0], o2[1]); t = ap(doc2, t, xt) 392 t = ap(doc2, t, "\"><img loading=lazy src=\"media/" as *u8) 393 t = apsl(doc2, t, cdxbuf, o2[0], o2[1]); t = ap(doc2, t, xt) 394 t = ap(doc2, t, "\"></a><figcaption>" as *u8) 395 cdx_field(cdxbuf, RS[i], RE[i], MR_F_ORIG, o2) 396 t = tok_name(doc2, t, cdxbuf, o2[0], o2[1]) 397 t = ap(doc2, t, "<br>" as *u8) 398 cdx_field(cdxbuf, RS[i], RE[i], MR_F_TS, o2) 399 t = apsl(doc2, t, cdxbuf, o2[0], o2[1]) 400 t = ap(doc2, t, "</figcaption></figure>\n" as *u8) 401 append_file(gpath, doc2, t) 402 } 403 // *BYURL GETS ITS OWN GUARD -- it must NOT hang off the sidecar's. 404 // Nested under have_file(mpath)==0 it could only ever be written when a SIDECAR WAS NEW, and 405 // every one of the 727 megastar + 34 page3 sidecars already on disk predates byurl. The 406 // reverse index would have been 0 of 761 while every run reported success, and the join it 407 // exists to serve would have looked broken when it had simply never been populated. 408 // *A SINGLE GUARD COVERING N INDEPENDENT ARTIFACTS MEANS N-1 OF THEM CAN NEVER BACKFILL. 409 // The question is not "have I processed this row" but "does THIS artifact exist", and those 410 // stop being the same question the moment a second artifact is added. 411 cdx_field(cdxbuf, RS[i], RE[i], MR_F_ORIG, o2) 412 var bp: i64 = ap(bpath, 0, bdir) 413 bp = ap(bpath, bp, "/" as *u8) 414 bp = apn(bpath, bp, mr_canon_hash(cdxbuf, o2[0], o2[1])) 415 bp = ap(bpath, bp, ".txt" as *u8) 416 bpath[bp]=0 as u8 417 if have_file(bpath)==0 { 418 cdx_field(cdxbuf, RS[i], RE[i], MR_F_DIGEST, o2) 419 var bd: i64 = apsl(bref, 0, cdxbuf, o2[0], o2[1]) 420 bd = ap(bref, bd, xt) 421 bref[bd]=0x0a as u8; bd=bd+1 422 if save_file(bpath, bref, bd)==1 { byurls=byurls+1 } 423 } 424 if have_file(path)==1 { skip=skip+1 } else { 425 if got < maxf { 426 // timestamp (field 1) + original url (field 2) -> the RAW archived bytes via id_ 427 cdx_field(cdxbuf, RS[i], RE[i], MR_F_TS, o2) 428 let tso: i64=o2[0]; let tsl: i64=o2[1] 429 cdx_field(cdxbuf, RS[i], RE[i], MR_F_ORIG, o2) 430 let uo: i64=o2[0]; let ul: i64=o2[1] 431 var q: i64 = ap(ub, 0, "https://web.archive.org/web/" as *u8) 432 q = apsl(ub, q, cdxbuf, tso, tsl) 433 q = ap(ub, q, "id_/" as *u8) 434 q = apsl(ub, q, cdxbuf, uo, ul) 435 ub[q]=0 as u8 436 let m: i64 = fetch_retry(ub, store, img, AM_CAP, st, 3) 437 if m>0 { 438 if st[0]==200 { 439 if is_image(img, m)==1 { 440 if save_file(path, img, m)==1 { got=got+1; bytes=bytes+m } else { bad=bad+1 } 441 } else { nonimg=nonimg+1 } 442 } else { bad=bad+1 } 443 } else { bad=bad+1 } 444 } 445 } 446 } 447 i=i+1 448 } 449 450 aw("\n--- ARCMINE page " as *u8); an(page); aw(" ---\n" as *u8) 451 aw(" index rows seen = " as *u8); an(seen); aw("\n" as *u8) 452 aw(" ADMITTED = " as *u8); an(adm); aw("\n" as *u8) 453 aw(" rejected tombstone = " as *u8); an(rjt); aw("\n" as *u8) 454 aw(" rejected status = " as *u8); an(rjs); aw("\n" as *u8) 455 aw(" rejected too-small = " as *u8); an(rjz); aw("\n" as *u8) 456 aw(" unknown (fail-closed)= " as *u8); an(rju); aw("\n" as *u8) 457 aw(" FETCHED+STORED = " as *u8); an(got); aw("\n" as *u8) 458 aw(" fetch failed = " as *u8); an(bad); aw("\n" as *u8) 459 aw(" fetched-but-not-image= " as *u8); an(nonimg); aw("\n" as *u8) 460 aw(" already had (skipped)= " as *u8); an(skip); aw("\n" as *u8) 461 aw(" search sidecars = " as *u8); an(metas); aw("\n" as *u8) 462 aw(" byurl reverse-index = " as *u8); an(byurls); aw("\n" as *u8) 463 aw(" bytes stored = " as *u8); an(bytes); aw("\n" as *u8) 464 let remain: i64 = adm - skip - got 465 if remain > 0 { aw(" NOTE: max_fetch capped this run -- " as *u8); an(remain); aw(" admitted rows NOT yet fetched. Re-run to continue.\n" as *u8) } 466 aw(" next: nx_arcmine " as *u8); aw(host); aw(" " as *u8); an(page+1); aw("\n" as *u8) 467 return 0 468}