"use strict"; function goto_up2k() { if (up2k === false) return goto('bup'); if (!up2k) return setTimeout(goto_up2k, 100); up2k.init_deps(); } // chrome requires https to use crypto.subtle, // usually it's undefined but some chromes throw on invoke var up2k = null, sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11 m = 'will use ' + sha_js + ' instead of native sha512 due to'; try { var cf = crypto.subtle || crypto.webkitSubtle; cf.digest('SHA-512', new Uint8Array(1)).then( function (x) { console.log('sha-ok'); up2k = up2k_init(cf); }, function (x) { console.log(m, x); up2k = up2k_init(false); } ); } catch (ex) { console.log(m, ex); try { up2k = up2k_init(false); } catch (ex) { } } treectl.onscroll(); function up2k_flagbus() { var flag = { "id": Math.floor(Math.random() * 1024 * 1024 * 1023 * 2), "ch": new BroadcastChannel("up2k_flagbus"), "ours": false, "owner": null, "wants": null, "act": false, "last_tx": ["x", null] }; var dbg = function (who, msg) { console.log('flagbus(' + flag.id + '): [' + who + '] ' + msg); }; flag.ch.onmessage = function (e) { var who = e.data[0], what = e.data[1]; if (who == flag.id) { dbg(who, 'hi me (??)'); return; } flag.act = Date.now(); if (what == "want") { // lowest id wins, don't care if that's us if (who < flag.id) { dbg(who, 'wants (ack)'); flag.wants = [who, flag.act]; } else { dbg(who, 'wants (ign)'); } } else if (what == "have") { dbg(who, 'have'); flag.owner = [who, flag.act]; } else if (what == "give") { if (flag.owner && flag.owner[0] == who) { flag.owner = null; dbg(who, 'give (ok)'); } else { dbg(who, 'give, INVALID, ' + flag.owner); } } else if (what == "hi") { dbg(who, 'hi'); flag.ch.postMessage([flag.id, "hey"]); } else { dbg('?', e.data); } }; var tx = function (now, msg) { var td = now - flag.last_tx[1]; if (td > 500 || flag.last_tx[0] != msg) { dbg('*', 'tx ' + msg); flag.ch.postMessage([flag.id, msg]); flag.last_tx = [msg, now]; } }; var do_take = function (now) { //dbg('*', 'do_take'); tx(now, "have"); flag.owner = [flag.id, now]; flag.ours = true; }; var do_want = function (now) { //dbg('*', 'do_want'); tx(now, "want"); }; flag.take = function (now) { if (flag.ours) { do_take(now); return; } if (flag.owner && now - flag.owner[1] > 5000) { flag.owner = null; } if (flag.wants && now - flag.wants[1] > 5000) { flag.wants = null; } if (!flag.owner && !flag.wants) { do_take(now); return; } do_want(now); }; flag.give = function () { dbg('#', 'put give'); flag.ch.postMessage([flag.id, "give"]); flag.owner = null; flag.ours = false; }; flag.ch.postMessage([flag.id, 'hi']); return flag; } function U2pvis(act, btns) { this.act = act; this.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 }; this.tab = []; this.head = 0; this.tail = -1; this.wsz = 3; this.addfile = function (entry, sz, draw) { this.tab.push({ "hn": entry[0], "ht": entry[1], "hp": entry[2], "in": 'q', "nh": 0, //hashed "nd": 0, //done "cb": [], // bytes done in chunk "bt": sz, // bytes total "bd": 0, // bytes done "bd0": 0 // upload start }); this.ctr["q"]++; if (!draw) return; this.drawcard("q"); if (this.act == "q") { this.addrow(this.tab.length - 1); } if (this.act == "bz") { this.bzw(); } }; this.is_act = function (card) { if (this.act == "done") return card == "ok" || card == "ng"; return this.act == card; } this.seth = function (nfile, field, html) { var fo = this.tab[nfile]; field = ['hn', 'ht', 'hp'][field]; if (fo[field] === html) return; fo[field] = html; if (!this.is_act(fo.in)) return; var obj = ebi('f{0}{1}'.format(nfile, field.slice(1))); obj.innerHTML = html; if (field == 'hp') { obj.style.color = ''; obj.style.background = ''; } }; this.setab = function (nfile, nblocks) { var t = []; for (var a = 0; a < nblocks; a++) t.push(0); this.tab[nfile].cb = t; }; this.setat = function (nfile, blocktab) { this.tab[nfile].cb = blocktab; var bd = 0; for (var a = 0; a < blocktab.length; a++) bd += blocktab[a]; this.tab[nfile].bd = bd; this.tab[nfile].bd0 = bd; }; this.perc = function (bd, bd0, sz, t0) { var td = Date.now() - t0, p = bd * 100.0 / sz, nb = bd - bd0, spd = nb / (td / 1000), eta = (sz - bd) / spd; return [p, s2ms(eta), spd / (1024 * 1024)]; }; this.hashed = function (fobj) { var fo = this.tab[fobj.n], nb = fo.bt * (++fo.nh / fo.cb.length), p = this.perc(nb, 0, fobj.size, fobj.t_hashing); fo.hp = '{0}%, {1}, {2} MB/s'.format( p[0].toFixed(2), p[1], p[2].toFixed(2) ); if (!this.is_act(fo.in)) return; var obj = ebi('f{0}p'.format(fobj.n)), o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0]; obj.innerHTML = fo.hp; obj.style.color = '#fff'; obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)'; }; this.prog = function (fobj, nchunk, cbd) { var fo = this.tab[fobj.n], delta = cbd - fo.cb[nchunk]; fo.cb[nchunk] = cbd; fo.bd += delta; var p = this.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading); fo.hp = '{0}%, {1}, {2} MB/s'.format( p[0].toFixed(2), p[1], p[2].toFixed(2) ); if (!this.is_act(fo.in)) return; var obj = ebi('f{0}p'.format(fobj.n)), o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0]; if (!obj) { //} || true) { var msg = [ "act", this.act, "in", fo.in, "is_act", this.is_act(fo.in), "head", this.head, "tail", this.tail, "nfile", fobj.n, "name", fobj.name, "sz", fobj.size, "bytesDelta", delta, "bytesDone", fo.bd, ], m2 = '', ds = QSA("#u2tab>tbody>tr>td:first-child>a:last-child"); for (var a = 0; a < msg.length; a += 2) m2 += msg[a] + '=' + msg[a + 1] + ', '; console.log(m2); for (var a = 0, aa = ds.length; a < aa; a++) { var id = ds[a].parentNode.getAttribute('id').slice(1, -1); console.log("dom %d/%d = [%s] in(%s) is_act(%s) %s", a, aa, id, this.tab[id].in, this.is_act(fo.in), ds[a].textContent); } for (var a = 0, aa = this.tab.length; a < aa; a++) if (this.is_act(this.tab[a].in)) console.log("tab %d/%d = sz %s", a, aa, this.tab[a].bt); throw new Error('see console'); } obj.innerHTML = fo.hp; obj.style.color = '#fff'; obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)'; }; this.move = function (nfile, newcat) { var fo = this.tab[nfile], oldcat = fo.in, bz_act = this.act == "bz"; if (oldcat == newcat) return; fo.in = newcat; this.ctr[oldcat]--; this.ctr[newcat]++; this.drawcard(oldcat); this.drawcard(newcat); if (this.is_act(newcat)) { this.tail = Math.max(this.tail, nfile + 1); if (!ebi('f' + nfile)) this.addrow(nfile); } else if (this.is_act(oldcat)) { while (this.head < Math.min(this.tab.length, this.tail) && this.precard[this.tab[this.head].in]) this.head++; if (!bz_act) { var tr = ebi("f" + nfile); tr.parentNode.removeChild(tr); } } else return; if (bz_act) this.bzw(); }; this.bzw = function () { var first = QS('#u2tab>tbody>tr:first-child'); if (!first) return; var last = QS('#u2tab>tbody>tr:last-child'); first = parseInt(first.getAttribute('id').slice(1)); last = parseInt(last.getAttribute('id').slice(1)); while (this.head - first > this.wsz) { var obj = ebi('f' + (first++)); if (obj) obj.parentNode.removeChild(obj); } while (last - this.tail < this.wsz && last < this.tab.length - 2) { var obj = ebi('f' + (++last)); if (!obj) this.addrow(last); } }; this.drawcard = function (cat) { var cards = QSA('#u2cards>a>span'); if (cat == "q") { cards[4].innerHTML = this.ctr[cat]; return; } if (cat == "bz") { cards[3].innerHTML = this.ctr[cat]; return; } cards[2].innerHTML = this.ctr["ok"] + this.ctr["ng"]; if (cat == "ng") { cards[1].innerHTML = this.ctr[cat]; } if (cat == "ok") { cards[0].innerHTML = this.ctr[cat]; } }; this.changecard = function (card) { this.act = card; this.precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 }; this.postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {}; this.head = -1; this.tail = -1; var html = []; for (var a = 0; a < this.tab.length; a++) { var rt = this.tab[a].in; if (this.is_act(rt)) { html.push(this.genrow(a, true)); this.tail = a; if (this.head == -1) this.head = a; } } if (this.head == -1) { for (var a = 0; a < this.tab.length; a++) { var rt = this.tab[a].in; if (this.precard[rt]) { this.head = a + 1; this.tail = a; } else if (this.postcard[rt]) { this.head = a; this.tail = a - 1; break; } } } if (this.head < 0) this.head = 0; if (card == "bz") { for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) { html.unshift(this.genrow(a, true).replace(/>
excessive filereader latency (" + td + " ms), increasing readahead
"; min_filebuf = 32 * 1024 * 1024; } } hash_calc(nch, e.target.result); } reader.onload = function (e) { try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); } }; reader.onerror = function () { var err = reader.error + ''; var handled = false; if (err.indexOf('NotReadableError') !== -1 || // win10-chrome defender err.indexOf('NotFoundError') !== -1 // macos-firefox permissions ) { pvis.seth(t.n, 1, 'OS-error'); pvis.seth(t.n, 2, err); handled = true; } if (handled) { pvis.move(t.n, 'ng'); apop(st.busy.hash, t); st.bytes.uploaded += t.size; return tasker(); } alert('y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err); }; reader.readAsArrayBuffer( bobslice.call(t.fobj, car, cdr)); return true; }; var hash_calc = function (nch, buf) { while (segm_next()); var hash_done = function (hashbuf) { var hslice = new Uint8Array(hashbuf).subarray(0, 33), b64str = buf2b64(hslice); hashtab[nch] = b64str; t.hash.push(nch); pvis.hashed(t); bpend -= buf.byteLength; if (t.hash.length < nchunks) { return segm_next(); } t.hash = []; for (var a = 0; a < nchunks; a++) { t.hash.push(hashtab[a]); } t.t_hashed = Date.now(); pvis.seth(t.n, 2, 'hashing done'); pvis.seth(t.n, 1, '📦 wait'); apop(st.busy.hash, t); st.todo.handshake.push(t); tasker(); }; if (subtle) subtle.digest('SHA-512', buf).then(hash_done); else setTimeout(function () { var u8buf = new Uint8Array(buf); if (sha_js == 'hw') { hashwasm.sha512(u8buf).then(function (v) { hash_done(hex2u8(v)) }); } else { var hasher = new asmCrypto.Sha512(); hasher.process(u8buf); hasher.finish(); hash_done(hasher.result); } }, 1); }; t.t_hashing = Date.now(); segm_next(); } ///// //// /// head // function exec_head() { var t = st.todo.head.shift(); st.busy.head.push(t); var xhr = new XMLHttpRequest(); xhr.onerror = function () { console.log('head onerror, retrying', t); apop(st.busy.head, t); st.todo.head.unshift(t); tasker(); }; function orz(e) { var ok = false; if (xhr.status == 200) { var srv_sz = xhr.getResponseHeader('Content-Length'), srv_ts = xhr.getResponseHeader('Last-Modified'); ok = t.size == srv_sz; if (ok && datechk) { srv_ts = new Date(srv_ts) / 1000; ok = Math.abs(srv_ts - t.lmod) < 2; } } apop(st.busy.head, t); if (!ok) return push_t(st.todo.hash, t); t.done = true; st.bytes.hashed += t.size; st.bytes.uploaded += t.size; pvis.seth(t.n, 1, 'YOLO'); pvis.seth(t.n, 2, "turbo'd"); pvis.move(t.n, 'ok'); }; xhr.onload = function (e) { try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); } }; xhr.open('HEAD', t.purl + t.name, true); xhr.send(); } ///// //// /// handshake // function exec_handshake() { var t = st.todo.handshake.shift(), keepalive = t.keepalive, me = Date.now(); st.busy.handshake.push(t); t.keepalive = undefined; t.t_busied = me; if (keepalive) console.log("sending keepalive handshake", t); var xhr = new XMLHttpRequest(); xhr.onerror = function () { if (t.t_busied != me) { console.log('zombie handshake onerror,', t); return; } console.log('handshake onerror, retrying', t); apop(st.busy.handshake, t); st.todo.handshake.unshift(t); t.keepalive = keepalive; tasker(); }; function orz(e) { if (t.t_busied != me) { console.log('zombie handshake onload,', t); return; } if (xhr.status == 200) { t.t_handshake = Date.now(); if (keepalive) { apop(st.busy.handshake, t); return; } var response = JSON.parse(xhr.responseText); if (!response.name) { var msg = '', smsg = ''; if (!response || !response.hits || !response.hits.length) { smsg = '404'; msg = 'not found on server (explain)'; } else { smsg = 'found'; var hit = response.hits[0], msg = linksplit(hit.rp).join(''), tr = unix2iso(hit.ts), tu = unix2iso(t.lmod), diff = parseInt(t.lmod) - parseInt(hit.ts), cdiff = (Math.abs(diff) <= 2) ? '3c0' : 'f0b', sdiff = 'diff ' + diff; msg += '') === 0) rsp = rsp.slice(5); if (rsp.indexOf('rate-limit ') !== -1) { var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0]; console.log("rate-limit: " + penalty); t.cooldown = Date.now() + parseFloat(penalty) * 1000; apop(st.busy.handshake, t); st.todo.handshake.unshift(t); return; } st.bytes.uploaded += t.size; if (rsp.indexOf('partial upload exists') !== -1 || rsp.indexOf('file already exists') !== -1) { err = rsp; ofs = err.indexOf('\n/'); if (ofs !== -1) { err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2)).join(' '); } } if (err != "") { pvis.seth(t.n, 1, "ERROR"); pvis.seth(t.n, 2, err); pvis.move(t.n, 'ng'); apop(st.busy.handshake, t); tasker(); return; } alert("server broke; hs-err {0} on file [{1}]:\n".format( xhr.status, t.name) + ( (xhr.response && xhr.response.err) || (xhr.responseText && xhr.responseText) || "no further information")); } } xhr.onload = function (e) { try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); } }; var req = { "name": t.name, "size": t.size, "lmod": t.lmod, "hash": t.hash }; if (fsearch) req.srch = 1; xhr.open('POST', t.purl, true); xhr.responseType = 'text'; xhr.send(JSON.stringify(req)); } ///// //// /// upload // function exec_upload() { var upt = st.todo.upload.shift(); st.busy.upload.push(upt); var npart = upt.npart, t = st.files[upt.nfile]; if (!t.t_uploading) t.t_uploading = Date.now(); pvis.seth(t.n, 1, "🚀 send"); var chunksize = get_chunksize(t.size), car = npart * chunksize, cdr = car + chunksize; if (cdr >= t.size) cdr = t.size; function orz(xhr) { var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + ''; if (xhr.status == 200) { pvis.prog(t, npart, cdr - car); st.bytes.uploaded += cdr - car; t.bytes_uploaded += cdr - car; } else if (txt.indexOf('already got that') !== -1) { console.log("ignoring dupe-segment error", t); } else { alert("server broke; cu-err {0} on file [{1}]:\n".format( xhr.status, t.name) + (txt || "no further information")); return; } apop(st.busy.upload, upt); apop(t.postlist, npart); if (!t.postlist.length) { t.t_uploaded = Date.now(); pvis.seth(t.n, 1, 'verifying'); st.todo.handshake.unshift(t); } tasker(); } function do_send() { var xhr = new XMLHttpRequest(); xhr.upload.onprogress = function (xev) { pvis.prog(t, npart, xev.loaded); }; xhr.onload = function (xev) { try { orz(xhr); } catch (ex) { vis_exh(ex + '', '', '', '', ex); } }; xhr.onerror = function (xev) { if (crashed) return; console.log('chunkpit onerror, retrying', t); do_send(); }; xhr.open('POST', t.purl, true); xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]); xhr.setRequestHeader("X-Up2k-Wark", t.wark); xhr.setRequestHeader('Content-Type', 'application/octet-stream'); if (xhr.overrideMimeType) xhr.overrideMimeType('Content-Type', 'application/octet-stream'); xhr.responseType = 'text'; xhr.send(bobslice.call(t.fobj, car, cdr)); } do_send(); } ///// //// /// config ui // function onresize(e) { var bar = ebi('ops'), wpx = innerWidth, fpx = parseInt(getComputedStyle(bar)['font-size']), wem = wpx * 1.0 / fpx, wide = wem > 54, parent = ebi(wide && has(perms, 'write') ? 'u2btn_cw' : 'u2btn_ct'), btn = ebi('u2btn'); //console.log([wpx, fpx, wem]); if (btn.parentNode !== parent) { parent.appendChild(btn); ebi('u2conf').setAttribute('class', wide ? 'has_btn' : ''); ebi('u2cards').setAttribute('class', wide ? 'w' : ''); } } window.addEventListener('resize', onresize); onresize(); if (is_touch) { // android-chrome wobbles for a bit; firefox / iOS-safari are OK setTimeout(onresize, 20); setTimeout(onresize, 100); setTimeout(onresize, 500); } var o = QSA('#u2conf *[tt]'); for (var a = o.length - 1; a >= 0; a--) { o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt')); } tt.att(QS('#u2conf')); function bumpthread2(e) { if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing) return; if (e.code == 'ArrowUp') bumpthread(1); if (e.code == 'ArrowDown') bumpthread(-1); } function bumpthread(dir) { try { dir.stopPropagation(); dir.preventDefault(); } catch (ex) { } var obj = ebi('nthread'); if (dir.target) { clmod(obj, 'err', 1); var v = Math.floor(parseInt(obj.value)); if (v < 0 || v > 64 || v !== v) return; parallel_uploads = v; swrite('nthread', v); clmod(obj, 'err'); return; } parallel_uploads += dir; if (parallel_uploads < 0) parallel_uploads = 0; if (parallel_uploads > 16) parallel_uploads = 16; obj.value = parallel_uploads; bumpthread({ "target": 1 }) } function tgl_multitask() { multitask = !multitask; bcfg_set('multitask', multitask); } function tgl_ask_up() { ask_up = !ask_up; bcfg_set('ask_up', ask_up); } function tgl_fsearch() { set_fsearch(!fsearch); } function tgl_turbo() { turbo = !turbo; bcfg_set('u2turbo', turbo); draw_turbo(); } function tgl_datechk() { datechk = !datechk; bcfg_set('u2tdate', datechk); } function draw_turbo() { var msgu = 'WARNING: turbo enabled, client may not detect and resume incomplete uploads; see turbo-button tooltip
', msgs = 'WARNING: turbo enabled, search may give false-positives; see turbo-button tooltip
', msg = fsearch ? msgs : msgu, omsg = fsearch ? msgu : msgs, html = ebi('u2foot').innerHTML, ohtml = html; if (turbo && html.indexOf(msg) === -1) html = html.replace(omsg, '') + msg; else if (!turbo) html = html.replace(msgu, '').replace(msgs, ''); if (html !== ohtml) ebi('u2foot').innerHTML = html; } draw_turbo(); function set_fsearch(new_state) { var fixed = false; if (!ebi('fsearch')) { new_state = false; } else if (perms.length) { if (!has(perms, 'write')) { new_state = true; fixed = true; } if (!has(perms, 'read')) { new_state = false; fixed = true; } } if (new_state !== undefined) { fsearch = new_state; bcfg_set('fsearch', fsearch); } try { QS('label[for="fsearch"]').style.display = QS('#fsearch').style.display = fixed ? 'none' : ''; } catch (ex) { } try { var fun = fsearch ? 'add' : 'remove', ico = fsearch ? '🔎' : '🚀', desc = fsearch ? 'Search' : 'Upload'; ebi('op_up2k').classList[fun]('srch'); ebi('u2bm').innerHTML = ico + ' ' + desc + ''; } catch (ex) { } draw_turbo(); onresize(); } function tgl_flag_en() { flag_en = !flag_en; bcfg_set('flag_en', flag_en); apply_flag_cfg(); } function apply_flag_cfg() { if (flag_en && !flag) { try { flag = up2k_flagbus(); } catch (ex) { console.log("flag error: " + ex.toString()); tgl_flag_en(); } } else if (!flag_en && flag) { flag.ch.close(); flag = false; } } function nop(e) { ev(e); this.click(); } ebi('nthread_add').onclick = function (e) { ev(e); bumpthread(1); }; ebi('nthread_sub').onclick = function (e) { ev(e); bumpthread(-1); }; ebi('nthread').onkeydown = bumpthread2; ebi('nthread').addEventListener('input', bumpthread, false); ebi('multitask').addEventListener('click', tgl_multitask, false); ebi('ask_up').addEventListener('click', tgl_ask_up, false); ebi('flag_en').addEventListener('click', tgl_flag_en, false); ebi('u2turbo').addEventListener('click', tgl_turbo, false); ebi('u2tdate').addEventListener('click', tgl_datechk, false); var o = ebi('fsearch'); if (o) o.addEventListener('click', tgl_fsearch, false); var nodes = ebi('u2conf').getElementsByTagName('a'); for (var a = nodes.length - 1; a >= 0; a--) nodes[a].addEventListener('touchend', nop, false); set_fsearch(); bumpthread({ "target": 1 }); if (parallel_uploads < 1) bumpthread(1); return { "init_deps": init_deps, "set_fsearch": set_fsearch } } function warn_uploader_busy(e) { e.preventDefault(); e.returnValue = ''; return "upload in progress, click abort and use the file-tree to navigate instead"; } tt.init(); if (QS('#op_up2k.act')) goto_up2k(); apply_perms(perms); (function () { goto(); var op = sread('opmode'); if (op !== null && op !== '.') try { goto(op); } catch (ex) { } })();