code wiki / _hdl_build / nx_torrent_shell.nx

nx_torrent_shell.nx source

↩ module page · 32 lines · 4849 B

1// nx_torrent_shell.nx -- R4a: the Nishi-EMITTED /torrent frontend (the gs_shell pattern -- a Nishi organ emits 2// the markup + the irreducible last-mile fetch bindings; NOT hand-authored JS product logic, per the JS-revert 3// lesson). It is the operator's torrent MANAGER served behind the front gateway at nishifamily.com/torrent: 4// - add row: magnet input + a DESTINATION selector (media=SFW / gallery=NSFW) + Add -> POST /torrent/api/add 5// body "magnet=..&area=media|gallery". The SERVER enforces (a family viewer's area=gallery is denied); 6// the selector just expresses intent. 7// - status poll GET /torrent/api/status -> render each download (name / % / peers / state) + a Watch button. 8// - Watch -> <video src=/torrent/stream/<id>> (the daemon's Range/206 stream-while-downloading path). 9// ts_emit(out) writes the page into out (returns len) so the gateway can serve it; main writes it to stdout 10// (the gate captures + checks the required elements). license_tier: ORIGINAL 11import "nx_syscalls.nx" 12const TS_MAGIC_65536: i64 = 65536 13 14const TS_HTML: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Torrent</title><style>body{font-family:system-ui,sans-serif;max-width:860px;margin:4vh auto;padding:0 16px;color:#cdd7e6;background:#0b1019}h1{font-size:1.25rem;color:#e8eef7}.row{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}input,select{padding:9px;border:1px solid #2a3550;border-radius:5px;background:#121a28;color:#e8eef7}input#mag{flex:1;min-width:220px}button{padding:9px 16px;background:#2d6cdf;color:#fff;border:0;border-radius:5px;cursor:pointer}#msg{min-height:1.2em;color:#7c8aa5;font-size:.85rem;margin:6px 0}.it{padding:10px;margin:7px 0;background:#121a28;border-left:3px solid #2d6cdf;border-radius:4px}.bar{height:6px;background:#1c2740;border-radius:3px;overflow:hidden;margin:6px 0}.fill{height:6px;background:#2d6cdf}.meta{font-size:.8rem;color:#7c8aa5}video{width:100%;margin-top:10px;background:#000;border-radius:6px;display:none}</style></head><body><h1>Nishi Torrent</h1><div class=row><input id=mag placeholder=\"magnet:?xt=urn:btih:...\"><select id=area title=\"download destination\"><option value=media>Media (SFW)</option><option value=gallery>Gallery (NSFW)</option></select><button onclick=add()>Add</button></div><div id=msg></div><div id=list></div><video id=pl controls></video><script>function $(i){return document.getElementById(i)}function M(t){$('msg').textContent=t}async function add(){var m=$('mag').value.trim();if(!m){M('paste a magnet');return}M('adding...');try{var r=await fetch('/torrent/api/add',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'magnet='+encodeURIComponent(m)+'&area='+encodeURIComponent($('area').value)});if(r.status===403){M('not allowed: that area is operator-only');return}var ct=(r.headers.get('content-type')||'');if(ct.indexOf('json')<0){M('Preview only \\u2014 the torrent backend is not live on the hub yet.');return}var j=await r.json();M(j.id?('added '+j.id):(j.error||'error'));$('mag').value=''}catch(e){M('Preview only \\u2014 backend not live yet.')}}function watch(id){var v=$('pl');v.style.display='block';v.src='/torrent/stream/'+encodeURIComponent(id);v.play()}function pct(d){return d.totalPieces>0?Math.floor(d.havePieces*100/d.totalPieces):0}async function refresh(){try{var r=await fetch('/torrent/api/status');var ct=(r.headers.get('content-type')||'');if(ct.indexOf('json')<0){M('Preview \\u2014 backend not live on the hub yet; this is the frontend only.');return}var j=await r.json();var L=$('list');L.innerHTML='';(j.downloads||[]).forEach(function(d){var p=pct(d);var el=document.createElement('div');el.className='it';el.innerHTML='<b>'+(d.name||d.id)+'</b> &mdash; '+d.status+'<div class=bar><div class=fill style=\"width:'+p+'%\"></div></div><div class=meta>'+p+'% &middot; '+(d.peers||0)+' peers &middot; '+Math.floor((d.done||0)/1048576)+' MB</div>';var b=document.createElement('button');b.textContent='\\u25b6 Watch';b.onclick=function(){watch(d.id)};el.appendChild(b);L.appendChild(el)})}catch(e){}}refresh();setInterval(refresh,2000);</script></body></html>" as *u8 15 16func ts_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 17// emit the page into out (NUL-terminated). returns length. 18// NOTE: bind the const to a LOCAL *u8 before indexing -- indexing a `const *u8` directly crashes nx_cc 19// ("empty .s after retries", a known compiler gotcha); indexing the local is fine. 20func ts_emit(out: *u8) -> i64 { 21 let s: *u8 = TS_HTML 22 var i: i64 = 0 23 while s[i] != (0 as u8) { out[i] = s[i]; i = i + 1 } 24 out[i] = 0 as u8 25 return i 26} 27 28func main() -> i64 { 29 let buf: *u8 = sys_mmap(TS_MAGIC_65536); let n: i64 = ts_emit(buf) 30 sys_write(1, buf, n) 31 return 0 32}