"use strict";

var J_U2K = 1;

(function () {
    var x = sread('nosubtle');
    if (x === '0' || x === '1')
        nosubtle = parseInt(x);
    if ((nosubtle > 1 && !CHROME && !FIREFOX) ||
        (nosubtle > 2 && !CHROME) ||
        (CHROME && nosubtle > VCHROME) ||
        !WebAssembly)
        nosubtle = 0;
})();


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,
    up2k_hooks = [],
    hws = [],
    hws_ok = 0,
    hws_ng = false,
    sha_js = WebAssembly ? 'hw' : 'ac',  // ff53,c57,sa11
    m = 'will use ' + sha_js + ' instead of native sha512 due to';

try {
    if (nosubtle)
        throw 'chickenbit';
    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) {
        console.log('up2k init failed:', ex);
        toast.err(10, 'could not initialize up2k\n\n' + basenames(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) {
        tx(now, "have");
        flag.owner = [flag.id, now];
        flag.ours = true;
    };
    var do_want = function (now) {
        tx(now, "want");
    };
    flag.take = function (now) {
        if (flag.ours) {
            do_take(now);
            return;
        }
        if (flag.owner && now - flag.owner[1] > 12000) {
            flag.owner = null;
        }
        if (flag.wants && now - flag.wants[1] > 12000) {
            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, uc, st) {
    var r = this;
    r.act = act;
    r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
    r.tab = [];
    r.hq = {};
    r.head = 0;
    r.tail = -1;
    r.wsz = 3;
    r.npotato = 99;
    r.modn = 0;
    r.modv = 0;
    r.mod0 = null;

    var markup = {
        '404': '<span class="err">' + L.utl_404 + '</span>',
        'ERROR': '<span class="err">' + L.utl_err + '</span>',
        'OS-error': '<span class="err">' + L.utl_oserr + '</span>',
        'found': '<span class="inf">' + L.utl_found + '</span>',
        'defer': '<span class="inf">' + L.utl_defer + '</span>',
        'YOLO': '<span class="inf">' + L.utl_yolo + '</span>',
        'done': '<span class="ok">' + L.utl_done + '</span>',
    };

    r.addfile = function (entry, sz, draw) {
        r.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
        });
        r.ctr["q"]++;
        if (!draw)
            return;

        r.drawcard("q");
        if (r.act == "q") {
            r.addrow(r.tab.length - 1);
        }
        if (r.act == "bz") {
            r.bzw();
        }
    };

    r.is_act = function (card) {
        if (uc.potato && !uc.fsearch)
            return false;

        if (r.act == "done")
            return card == "ok" || card == "ng";

        return r.act == card;
    }

    r.seth = function (nfile, field, html) {
        var fo = r.tab[nfile];
        field = ['hn', 'ht', 'hp'][field];
        if (fo[field] === html)
            return;

        fo[field] = html;
        if (!r.is_act(fo.in))
            return;

        var k = 'f' + nfile + '' + field.slice(1),
            obj = ebi(k);

        obj.innerHTML = field == 'ht' ? (markup[html] || html) : html;
        if (field == 'hp') {
            obj.style.color = '';
            obj.style.background = '';
            delete r.hq[nfile];
        }
    };

    r.setab = function (nfile, nblocks) {
        var t = [];
        for (var a = 0; a < nblocks; a++)
            t.push(0);

        r.tab[nfile].cb = t;
    };

    r.setat = function (nfile, blocktab) {
        var fo = r.tab[nfile], bd = 0;

        for (var a = 0; a < blocktab.length; a++)
            bd += blocktab[a];

        fo.bd = bd;
        fo.bd0 = bd;
        fo.cb = blocktab;
    };

    r.perc = function (bd, bd0, sz, t0) {
        var td = Date.now() - t0,
            p = bd * 100.0 / sz,
            nb = bd - bd0,
            spd = nb / (td / 1000),
            eta = spd ? (sz - bd) / spd : 3599;

        return [p, s2ms(eta), spd / (1024 * 1024)];
    };

    r.hashed = function (fobj) {
        var fo = r.tab[fobj.n],
            nb = fo.bt * (++fo.nh / fo.cb.length),
            p = r.perc(nb, 0, fobj.size, fobj.t_hashing);

        fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MiB/s';
        if (!r.is_act(fo.in))
            return;

        var o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];

        r.hq[fobj.n] = [fo.hp, '#fff', 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)'];
    };

    r.prog = function (fobj, nchunk, cbd) {
        var fo = r.tab[fobj.n],
            delta = cbd - fo.cb[nchunk];

        fo.cb[nchunk] = cbd;
        fo.bd += delta;

        if (!fo.bd)
            return;

        var p = r.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
        fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MiB/s';

        if (!r.is_act(fo.in))
            return;

        var obj = ebi('f' + fobj.n + 'p'),
            o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];

        if (!obj) {
            var msg = [
                "act", r.act,
                "in", fo.in,
                "is_act", r.is_act(fo.in),
                "head", r.head,
                "tail", r.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, r.tab[id].in, r.is_act(fo.in), ds[a].textContent);
            }

            for (var a = 0, aa = r.tab.length; a < aa; a++)
                if (r.is_act(r.tab[a].in))
                    console.log("tab %d/%d = sz %s", a, aa, r.tab[a].bt);

            throw new Error('see console');
        }

        r.hq[fobj.n] = [fo.hp, '#fff', 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)'];
    };

    r.move = function (nfile, newcat) {
        var fo = r.tab[nfile],
            oldcat = fo.in,
            bz_act = r.act == "bz";

        if (oldcat == newcat)
            return;

        fo.in = newcat;
        r.ctr[oldcat]--;
        r.ctr[newcat]++;

        while (st.car < r.tab.length && has(['ok', 'ng'], r.tab[st.car].in))
            st.car++;

        r.drawcard(oldcat);
        r.drawcard(newcat);
        if (r.is_act(newcat)) {
            r.tail = Math.max(r.tail, nfile + 1);
            if (!ebi('f' + nfile))
                r.addrow(nfile);
        }
        else if (r.is_act(oldcat)) {
            while (r.head < Math.min(r.tab.length, r.tail) && r.precard[r.tab[r.head].in])
                r.head++;

            if (!bz_act) {
                qsr("#f" + nfile);
            }
        }
        else return;

        if (bz_act)
            r.bzw();
    };

    r.bzw = function () {
        var mod = 0,
            t0 = Date.now(),
            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 (r.head - first > r.wsz) {
            qsr('#f' + (first++));
            mod++;
        }
        while (last - r.tail < r.wsz && last < r.tab.length - 1) {
            var obj = ebi('f' + (++last));
            if (!obj) {
                r.addrow(last);
                mod++;
            }
        }
        if (mod && r.modn < 200 && ebi('repl').offsetTop) {
            if (++r.modn >= 10) {
                if (r.modn == 10)
                    r.mod0 = Date.now();

                r.modv += Date.now() - t0;
            }

            if (r.modn >= 200) {
                var n = r.modn - 10,
                    ipu = r.modv / n,
                    spu = (Date.now() - r.mod0) / n,
                    ir = spu / ipu;

                console.log('bzw:', f2f(ipu, 2), ' spu:', f2f(spu, 2), ' ir:', f2f(ir, 2), ' tab:', r.tab.length);
                // efficiency estimates;
                // ir: 5=16% 4=50%,30% 27=100%
                // ipu: 2.7=16% 2=30% 1.6=50% 1.8=100% (ng for big files)
                if (ipu >= 1.5 && ir <= 9 && r.tab.length >= 1000 && r.tab[Math.floor(r.tab.length / 3)].bt <= 1024 * 1024 * 4)
                    r.go_potato();
            }
        }
    };

    r.potatolabels = function () {
        var ode = ebi('u2depotato'),
            oen = ebi('u2enpotato');

        if (!ode)
            return;

        ode.style.display = uc.potato ? '' : 'none';
        oen.style.display = uc.potato ? 'none' : '';
    }

    r.potato = function () {
        ebi('u2tabw').style.minHeight = '';
        QS('#u2cards a[act="bz"]').click();
        timer[uc.potato ? "add" : "rm"](draw_potato);
        timer[uc.potato ? "rm" : "add"](apply_html);
        r.potatolabels();
    };

    r.go_potato = function () {
        r.go_potato = noop;
        var ode = mknod('div', 'u2depotato'),
            oen = mknod('div', 'u2enpotato'),
            u2f = ebi('u2foot'),
            btn = ebi('potato');

        ode.innerHTML = L.u_depot;
        oen.innerHTML = L.u_enpot;

        if (sread('potato') === null) {
            btn.click();
            toast.inf(30, L.u_gotpot);
            sdrop('potato');
        }

        u2f.appendChild(ode);
        u2f.appendChild(oen);
        ode.onclick = oen.onclick = btn.onclick;
        r.potatolabels();
    };

    function draw_potato() {
        if (++r.npotato < 2)
            return;

        r.npotato = 0;
        var html = [L.u_pott.format(r.ctr.ok, r.ctr.ng, r.ctr.bz, r.ctr.q)];

        while (r.head < r.tab.length && has(["ok", "ng"], r.tab[r.head].in))
            r.head++;

        var act = null;
        if (r.head < r.tab.length)
            act = r.tab[r.head];

        if (act)
            html.push("<p>file {0} of {1} : &nbsp; {2} &nbsp; <code>{3}</code></p>\n<div>{4}</div>".format(
                r.head + 1, r.tab.length, act.ht, act.hp, act.hn));

        html = html.join('\n');
        if (r.hpotato == html)
            return;

        r.hpotato = html;
        ebi('u2mu').innerHTML = html;
    }

    function apply_html() {
        var oq = {}, n = 0;
        for (var k in r.hq) {
            var o = ebi('f' + k + 'p');
            if (!o)
                continue;

            oq[k] = o;
            n++;
        }
        if (!n)
            return;

        for (var k in oq) {
            var o = oq[k],
                v = r.hq[k];

            o.innerHTML = v[0];
            o.style.color = v[1];
            o.style.background = v[2];
        }
        r.hq = {};
    }

    r.drawcard = function (cat) {
        var cards = QSA('#u2cards>a>span');

        if (cat == "q") {
            cards[4].innerHTML = r.ctr[cat];
            return;
        }
        if (cat == "bz") {
            cards[3].innerHTML = r.ctr[cat];
            return;
        }

        cards[2].innerHTML = r.ctr["ok"] + r.ctr["ng"];

        if (cat == "ng") {
            cards[1].innerHTML = r.ctr[cat];
        }
        if (cat == "ok") {
            cards[0].innerHTML = r.ctr[cat];
        }
    };

    r.changecard = function (card) {
        r.act = card;
        r.precard = has(["ok", "ng", "done"], r.act) ? {} : r.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 };
        r.postcard = has(["ok", "ng", "done"], r.act) ? { "bz": 1, "q": 1 } : r.act == "bz" ? { "q": 1 } : {};
        r.head = -1;
        r.tail = -1;
        var html = [];
        for (var a = 0; a < r.tab.length; a++) {
            var rt = r.tab[a].in;
            if (r.is_act(rt)) {
                html.push(r.genrow(a, true));

                r.tail = a;
                if (r.head == -1)
                    r.head = a;
            }
        }
        if (r.head == -1) {
            for (var a = 0; a < r.tab.length; a++) {
                var rt = r.tab[a].in;
                if (r.precard[rt]) {
                    r.head = a + 1;
                    r.tail = a;
                }
                else if (r.postcard[rt]) {
                    r.head = a;
                    r.tail = a - 1;
                    break;
                }
            }
        }

        if (r.head < 0)
            r.head = 0;

        if (card == "bz") {
            for (var a = r.head - 1; a >= r.head - r.wsz && a >= 0; a--) {
                html.unshift(r.genrow(a, true).replace(/><td>/, "><td>a "));
            }
            for (var a = r.tail + 1; a <= r.tail + r.wsz && a < r.tab.length; a++) {
                html.push(r.genrow(a, true).replace(/><td>/, "><td>b "));
            }
        }
        var el = ebi('u2tab');
        el.tBodies[0].innerHTML = html.join('\n');
        el.className = (uc.fsearch ? 'srch ' : 'up ') + r.act;
    };

    r.genrow = function (nfile, as_html) {
        var row = r.tab[nfile],
            td1 = '<td id="f' + nfile,
            td = '</td>' + td1,
            ret = td1 + 'n">' + row.hn +
                td + 't">' + (markup[row.ht] || row.ht) +
                td + 'p" class="prog">' + row.hp + '</td>';

        if (as_html)
            return '<tr id="f' + nfile + '">' + ret + '</tr>';

        var obj = mknod('tr', 'f' + nfile);
        obj.innerHTML = ret;
        return obj;
    };

    r.addrow = function (nfile) {
        var tr = r.genrow(nfile);
        ebi('u2tab').tBodies[0].appendChild(tr);
    };

    btns = QSA(btns + '>a[act]');
    for (var a = 0; a < btns.length; a++) {
        btns[a].onclick = function (e) {
            ev(e);
            var newtab = this.getAttribute('act');
            var go = function () {
                for (var b = 0; b < btns.length; b++) {
                    btns[b].className = (
                        btns[b].getAttribute('act') == newtab) ? 'act' : '';
                }
                r.changecard(newtab);
            }
            var nf = r.ctr[newtab];
            if (nf === undefined)
                nf = r.ctr["ok"] + r.ctr["ng"];

            if (nf < 9000)
                return go();

            modal.confirm(L.u_bigtab.format(nf), go, null);
        };
    }

    r.changecard(r.act);
    r.potato();
}


function Donut(uc, st) {
    var r = this,
        el = null,
        psvg = null,
        tenstrobe = null,
        tstrober = null,
        strobes = [],
        o = 20 * 2 * Math.PI,
        optab = QS('#ops a[data-dest="up2k"]');

    optab.setAttribute('ico', optab.textContent);

    function svg(v) {
        var ico = v !== undefined,
            bg = ico ? '#333' : 'transparent',
            fg = '#fff',
            fsz = 52,
            rc = 32;

        if (r.eta && (r.eta > 99 || (uc.fsearch ? st.time.hashing : st.time.uploading) < 20))
            r.eta = null;

        if (r.eta) {
            if (r.eta < 10) {
                fg = '#fa0';
                fsz = 72;
            }
            rc = 8;
        }

        return (
            '<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">\n' +
            (ico ? '<rect width="100%" height="100%" rx="' + rc + '" fill="#333" />\n' :
                '<circle stroke="white" stroke-width="6" r="3" cx="32" cy="32" />\n') +
            (r.eta ? (
                '<text x="55%" y="58%" dominant-baseline="middle" text-anchor="middle"' +
                ' font-family="sans-serif" font-weight="bold" font-size="' + fsz + 'px"' +
                ' fill="' + fg + '">' + r.eta + '</text></svg>'
            ) : (
                '<circle class="donut" stroke="white" fill="' + bg +
                '" stroke-dashoffset="' + (ico ? v : o) + '" stroke-dasharray="' + o + ' ' + o +
                '" transform="rotate(270 32 32)" stroke-width="12" r="20" cx="32" cy="32" /></svg>'
            ))
        );
    }

    function pos() {
        return uc.fsearch ?
            Math.max(st.bytes.hashed, st.bytes.finished) :
            st.bytes.inflight + st.bytes.finished;
    }

    r.on = function (ya) {
        r.fc = r.tc = r.dc = 99;
        r.eta = null;
        r.base = pos();
        optab.innerHTML = ya ? svg() : optab.getAttribute('ico');
        el = QS('#ops a .donut');
        clearTimeout(tenstrobe);
        if (!ya) {
            favico.upd();
            wintitle();
            if (document.visibilityState == 'hidden')
                tenstrobe = setTimeout(r.enstrobe, 500); //debounce
        }
    };

    r.do = function () {
        if (!el)
            return;

        var t = st.bytes.total - r.base,
            v = pos() - r.base,
            ofs = o - o * v / t;

        if (!uc.potato || ++r.dc >= 4) {
            el.style.strokeDashoffset = ofs;
            r.dc = 0;
        }

        if (++r.tc >= 10) {
            var s = r.eta === null ? 'paused' : r.eta > 60 ? shumantime(r.eta) : (r.eta + 's');
            wintitle("{0}%, {1}, #{2}, ".format(
                f2f(v * 100 / t, 1), s, st.files.length - st.nfile.upload), true);
            r.tc = 0;
        }

        if (favico.txt) {
            if (++r.fc < 10 && r.eta && r.eta > 99)
                return;

            var s = svg(ofs);
            if (s == psvg || (r.eta === null && r.fc < 10))
                return;

            favico.upd('', s);
            psvg = s;
            r.fc = 0;
        }
    };

    r.enstrobe = function () {
        strobes = ['████████████████', '________________', '████████████████'];
        tstrober = setInterval(strobe, 300);

        if (uc.upsfx && actx && actx.state != 'suspended')
            sfx_nice();

        // firefox may forget that filedrops are user-gestures so it can skip this:
        if (uc.upnag && Notification && Notification.permission == 'granted')
            new Notification(uc.nagtxt);
    }

    function strobe() {
        var txt = strobes.pop();
        wintitle(txt, false);
        if (!txt)
            clearInterval(tstrober);
    }
}

function sfx_nice() {
    if (true) {
        var osc = actx.createOscillator(),
            gain = actx.createGain(),
            gg = gain.gain,
            ft = [660, 880, 440, 660, 880],
            ofs = 0;

        osc.connect(gain);
        gain.connect(actx.destination);
        var ct = actx.currentTime + 0.03;

        osc.type = 'triangle';
        while (ft.length)
            osc.frequency.setTargetAtTime(
                ft.shift(), ct + (ofs += 0.05), 0.001);

        gg.value = 0.15;
        gg.setTargetAtTime(0.8, ct, 0.01);
        gg.setTargetAtTime(0.3, ct + 0.13, 0.01);
        gg.setTargetAtTime(0, ct + ofs + 0.05, 0.02);

        osc.start();
        setTimeout(function () {
            osc.stop();
            osc.disconnect();
            gain.disconnect();
        }, 500);
    }
}


function bind_fsearch_explain() {
    var o = QSA('.fsearch_explain');
    for (var a = 0, aa = o.length; a < aa; a++)
        o[a].onclick = fsearch_explain;
}


function fsearch_explain() {
    var a = this.getAttribute('a');

    if (a == "u")
        return toast.inf(60, L.ue_ab);

    if (a == "r")
        return toast.inf(60, L.ue_ro + (acct == '*' ? L.ue_nl : L.ue_la).format(acct));

    if (bcfg_get('fsearch', false))
        return toast.inf(60, L.ue_sr);

    return toast.inf(60, L.ue_ta);
}


function up2k_init(subtle) {
    var r = {
        "tact": Date.now(),
        "init_deps": init_deps,
        "set_fsearch": set_fsearch,
        "gotallfiles": [gotallfiles]  // hooks
    };

    setTimeout(function () {
        if (WebAssembly && !hws.length)
            fetch(SR + '/.cpr/w/deps/sha512.hw.js?_=' + TS);
    }, 1000);

    function showmodal(msg) {
        ebi('u2notbtn').innerHTML = msg;
        ebi('u2btn').style.display = 'none';
        ebi('u2notbtn').style.display = 'block';
        ebi('u2conf').style.opacity = '0.5';
    }

    function unmodal() {
        ebi('u2notbtn').style.display = 'none';
        ebi('u2conf').style.opacity = '1';
        ebi('u2btn').style.display = '';
        ebi('u2notbtn').innerHTML = '';
    }

    var suggest_up2k = L.u_su2k;

    function got_deps() {
        return subtle || window.asmCrypto || window.hashwasm;
    }

    var loading_deps = false;
    function init_deps() {
        if (!loading_deps && !got_deps()) {
            var fn = 'sha512.' + sha_js + '.js',
                m = L.u_https1 + ' <a href="' + (location + '').replace(':', 's:') + '">' + L.u_https2 + '</a> ' + L.u_https3;

            showmodal('<h1>loading ' + fn + '</h1>');
            import_js(SR + '/.cpr/w/deps/' + fn, unmodal);

            if (HTTPS) {
                // chrome<37 firefox<34 edge<12 opera<24 safari<7
                m = L.u_ancient;
                setmsg('');
            }
            qsr('#u2depmsg');
            var o = mknod('div', 'u2depmsg');
            o.innerHTML = nosubtle ? '' : m;
            ebi('u2foot').appendChild(o);
            setmonclick();
        }
        loading_deps = true;
    }

    if (perms.length && !has(perms, 'read') && has(perms, 'write'))
        goto('up2k');

    function setmsg(msg, type) {
        if (msg !== undefined) {
            ebi('u2err').className = type;
            ebi('u2err').innerHTML = msg;
        }
        else {
            ebi('u2err').className = '';
            ebi('u2err').innerHTML = '';
        }
        setmonclick();
    }

    function setmonclick() {
        var x;
        x = ebi('u2yea'); if (x) x.onclick = go2up2k;
        x = ebi('u2nah'); if (x) x.onclick = go2bup;
    }

    function un2k(msg) {
        setmsg(msg, 'err');
        return false;
    }

    setmsg(suggest_up2k, 'msg');

    var u2szs = u2sz.split(','),
        u2sz_min = parseInt(u2szs[0]),
        u2sz_tgt = parseInt(u2szs[1]),
        u2sz_max = parseInt(u2szs[2]);

    var parallel_uploads = ebi('nthread').value = icfg_get('nthread', u2j),
        stitch_tgt = ebi('u2szg').value = icfg_get('u2sz', u2sz_tgt),
        uc = {},
        fdom_ctr = 0,
        biggest_file = 0;

    bcfg_bind(uc, 'rand', 'u2rand', false, null, false);
    bcfg_bind(uc, 'multitask', 'multitask', true, null, false);
    bcfg_bind(uc, 'potato', 'potato', false, set_potato, false);
    bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
    bcfg_bind(uc, 'umod', 'umod', false, null, false);
    bcfg_bind(uc, 'u2ts', 'u2ts', !u2ts.endsWith('u'), set_u2ts, false);
    bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);

    bcfg_bind(uc, 'flag_en', 'flag_en', false, apply_flag_cfg);
    bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo);
    bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null);
    bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
    bcfg_bind(uc, 'hashw', 'hashw', !!WebAssembly && !(CHROME && MOBILE) && (!subtle || !CHROME || VCHROME > 136), set_hashw);
    bcfg_bind(uc, 'hwasm', 'nosubtle', nosubtle, set_nosubtle);
    bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
    bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);

    uc.ow = parseInt(sread('u2ow', ['0', '1', '2', '3']) || u2ow);
    uc.owt = ['🛡️', '🕒', '♻️', '⏭️'];
    function set_ow() {
        QS('label[for="u2ow"]').innerHTML = uc.owt[uc.ow];
        ebi('u2ow').checked  = true; //cosmetic
    }
    ebi('u2ow').onclick = function (e) {
        ev(e);
        if (++uc.ow > 3)
            uc.ow = 0;
        swrite('u2ow', uc.ow);
        set_ow();
        if (uc.ow && uc.ow !== 3 &&  !has(perms, 'delete'))
            toast.warn(10, L.u_enoow, 'noow');
        else if (toast.tag == 'noow')
            toast.hide();
    };
    set_ow();

    var st = {
        "files": [],
        "nfile": {
            "hash": 0,
            "upload": 0
        },
        "seen": {},
        "todo": {
            "head": [],
            "hash": [],
            "handshake": [],
            "upload": []
        },
        "busy": {
            "head": [],
            "hash": [],
            "handshake": [],
            "upload": []
        },
        "bytes": {
            "total": 0,
            "hashed": 0,
            "inflight": 0,
            "uploaded": 0,
            "finished": 0
        },
        "time": {
            "hashing": 0.01,
            "uploading": 0.01,
            "busy": 0.01
        },
        "eta": {
            "h": "",
            "u": "",
            "t": ""
        },
        "etaw": {
            "h": [['', 0, 0, 0]],
            "u": [['', 0, 0, 0]],
            "t": [['', 0, 0, 0]]
        },
        "etac": {
            "h": 0,
            "u": 0,
            "t": 0
        },
        "car": 0,
        "nre": 0,
        "slow_io": null,
        "oserr": false,
        "modn": 0,
        "modv": 0,
        "mod0": null
    };

    function push_t(arr, t) {
        var sort = arr.length && arr[arr.length - 1].n > t.n;
        arr.push(t);
        if (sort)
            arr.sort(function (a, b) {
                return a.n < b.n ? -1 : 1;
            });
    }

    var pvis = new U2pvis("bz", '#u2cards', uc, st),
        donut = new Donut(uc, st);

    r.ui = pvis;
    r.st = st;
    r.uc = uc;

    if (!window.File || !window.FileReader || !window.FileList || !File.prototype || !File.prototype.slice)
        return un2k(L.u_ever);

    var flag = false;
    apply_flag_cfg();
    set_fsearch();

    function nav() {
        start_actx();

        var uf = function () { ebi('file' + fdom_ctr).click(); },
            ud = function () { ebi('dir' + fdom_ctr).click(); };

        // too buggy on chrome <= 72
        var m = / Chrome\/([0-9]+)\./.exec(UA);
        if (m && parseInt(m[1]) < 73)
            return uf();

        // phones dont support folder upload
        if (MOBILE)
            return uf();

        modal.confirm(L.u_nav_m, uf, ud, null, L.u_nav_b);
    }
    ebi('u2btn').onclick = nav;

    var nenters = 0;
    function ondrag(e) {
        if (++nenters <= 0)
            nenters = 1;

        if (onover.call(this, e))
            return true;

        var mup, up = QS('#up_zd');
        var msr, sr = QS('#srch_zd');
        if (!has(perms, 'write'))
            mup = L.u_ewrite;
        if (!has(perms, 'read'))
            msr = L.u_eread;
        if (!have_up2k_idx)
            msr = L.u_enoi;

        up.querySelector('span').textContent = mup || L.udt_drop;
        sr.querySelector('span').textContent = msr || L.udt_drop;
        clmod(up, 'err', mup);
        clmod(sr, 'err', msr);
        clmod(up, 'ok', !mup);
        clmod(sr, 'ok', !msr);
        ebi('up_dz').setAttribute('err', mup || '');
        ebi('srch_dz').setAttribute('err', msr || '');
    }
    function onoverb(e) {
        // zones are alive; disable cuo2duo branch
        document.body.ondragover = document.body.ondrop = null;
        return onover.call(this, e);
    }
    function onover(e) {
        return onovercmn(this, e, false);
    }
    function onoverbtn(e) {
        return onovercmn(this, e, true);
    }
    function onovercmn(self, e, btn) {
        try {
            var ok = false, dt = e.dataTransfer.types;
            for (var a = 0; a < dt.length; a++)
                if (dt[a] == 'Files')
                    ok = true;
                else if (dt[a] == 'text/uri-list') {
                    if (btn) {
                        ok = true;
                        if (toast.txt == L.u_uri)
                            toast.hide();
                    }
                    else
                        return toast.inf(10, L.u_uri) || true;
                }

            if (!ok)
                return true;
        }
        catch (ex) { }

        ev(e);
        try {
            e.dataTransfer.dropEffect = 'copy';
            e.dataTransfer.effectAllowed = 'copy';
        }
        catch (ex) {
            document.body.ondragenter = document.body.ondragleave = document.body.ondragover = null;
            return modal.alert(L.u_nodrop);
        }
        if (btn)
            return;

        clmod(ebi('drops'), 'vis', 1);
        var v = self.getAttribute('v');
        if (v)
            clmod(ebi(v), 'hl', 1);
    }
    function offdrag(e) {
        noope(e);

        var v = this.getAttribute('v');
        if (v)
            clmod(ebi(v), 'hl');

        if (--nenters <= 0) {
            clmod(ebi('drops'), 'vis');
            clmod(ebi('up_dz'), 'hl');
            clmod(ebi('srch_dz'), 'hl');
            // cuo2duo:
            document.body.ondragover = onover;
            document.body.ondrop = gotfile;
        }
    }
    document.body.ondragenter = ondrag;
    document.body.ondragleave = offdrag;
    document.body.ondragover = onover;
    document.body.ondrop = gotfile;
    ebi('u2btn').ondrop = gotfile;
    ebi('u2btn').ondragover = onoverbtn;

    var drops = [ebi('up_dz'), ebi('srch_dz')];
    for (var a = 0; a < 2; a++) {
        drops[a].ondragenter = ondrag;
        drops[a].ondragover = onoverb;
        drops[a].ondragleave = offdrag;
        drops[a].ondrop = gotfile;
    }
    ebi('drops').onclick = offdrag;  // old ff

    function gotdir(e) {
        ev(e);
        var good_files = [],
            nil_files = [],
            bad_files = [];

        for (var a = 0, aa = e.target.files.length; a < aa; a++) {
            var fobj = e.target.files[a],
                name = fobj.webkitRelativePath,
                dst = good_files;

            try {
                if (!name)
                    throw 1;

                if (fobj.size < 1)
                    dst = nil_files;
            }
            catch (ex) {
                dst = bad_files;
            }
            dst.push([fobj, name]);
        }

        if (!good_files.length && bad_files.length)
            return toast.err(30, L.u_notdir);

        return read_dirs(null, [], [], good_files, nil_files, bad_files);
    }

    function gotfile(e) {
        ev(e);
        nenters = 0;
        offdrag.call(this);
        var dz = this && this.getAttribute('id');
        if (!dz && e && e.clientY)
            // cuo2duo fallback
            dz = e.clientY < window.innerHeight / 2 ? 'up_dz' : 'srch_dz';

        var err = this.getAttribute('err');
        if (err)
            return modal.alert('sorry, ' + err);

        toast.inf(0, L.u_scan);

        if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
            tgl_fsearch();

        if (!QS('#op_up2k.act'))
            goto('up2k');

        var files,
            is_itemlist = false;

        if (e.dataTransfer) {
            if (e.dataTransfer.items) {
                files = e.dataTransfer.items; // DataTransferItemList
                is_itemlist = true;
            }
            else files = e.dataTransfer.files; // FileList
        }
        else files = e.target.files;

        if (!files || !files.length)
            return toast.err(0, 'no files selected??');

        more_one_file();
        var bad_files = [],
            nil_files = [],
            good_files = [],
            dirs = [];

        for (var a = 0; a < files.length; a++) {
            var fobj = files[a],
                dst = good_files;

            if (is_itemlist) {
                if (fobj.kind !== 'file' && fobj.type !== 'text/uri-list')
                    continue;

                try {
                    var wi = fobj.getAsEntry ? fobj.getAsEntry() : fobj.webkitGetAsEntry();
                    if (wi.isDirectory) {
                        dirs.push(wi);
                        continue;
                    }
                }
                catch (ex) { }
                fobj = fobj.getAsFile();
                if (!fobj)
                    continue;
            }
            try {
                if (fobj.size < 1)
                    dst = nil_files;
            }
            catch (ex) {
                dst = bad_files;
            }
            dst.push([fobj, fobj.name]);
        }
        start_actx();  // good enough for chrome; not firefox
        return read_dirs(null, [], dirs, good_files, nil_files, bad_files);
    }

    function rd_flatten(pf, dirs) {
        var ret = jcp(pf);
        for (var a = 0; a < dirs.length; a++)
            ret.push(dirs.fullPath || '');

        ret.sort();
        return ret;
    }

    var rd_missing_ref = [];
    function read_dirs(rd, pf, dirs, good, nil, bad, spins) {
        spins = spins || 0;
        if (++spins == 5)
            rd_missing_ref = rd_flatten(pf, dirs);

        if (spins == 200) {
            var missing = rd_flatten(pf, dirs),
                match = rd_missing_ref.length == missing.length,
                aa = match ? missing.length : 0;

            missing.sort();
            for (var a = 0; a < aa; a++)
                if (rd_missing_ref[a] != missing[a])
                    match = false;

            if (match) {
                var msg = [L.u_dirstuck.format(missing.length) + '<ul>'];
                for (var a = 0; a < Math.min(20, missing.length); a++)
                    msg.push('<li>' + esc(missing[a]) + '</li>');

                return modal.alert(msg.join('') + '</ul>', function () {
                    read_dirs(rd, [], [], good, nil, bad, spins);
                });
            }
            spins = 0;
        }

        if (!dirs.length) {
            if (!pf.length)
                // call first hook, pass list of remaining hooks to call
                return r.gotallfiles[0](good, nil, bad, r.gotallfiles.slice(1));

            console.log("retry pf, " + pf.length);
            setTimeout(function () {
                read_dirs(rd, pf, dirs, good, nil, bad, spins);
            }, 50);
            return;
        }

        if (!rd)
            rd = dirs[0].createReader();

        rd.readEntries(function (ents) {
            var ngot = 0;
            ents.forEach(function (dn) {
                if (dn.isDirectory) {
                    dirs.push(dn);
                }
                else {
                    var name = dn.fullPath;
                    if (name.startsWith('/'))
                        name = name.slice(1);

                    pf.push(name);
                    dn.file(function (fobj) {
                        apop(pf, name);
                        var dst = good;
                        try {
                            if (fobj.size < 1)
                                dst = nil;
                        }
                        catch (ex) {
                            dst = bad;
                        }
                        dst.push([fobj, name]);
                    });
                }
                ngot += 1;
            });
            if (!ngot) {
                dirs.shift();
                rd = null;
            }
            read_dirs(rd, pf, dirs, good, nil, bad, spins);
        }, function () {
            var dn = dirs[0],
                name = dn.fullPath;

            if (name.startsWith('/'))
                name = name.slice(1);

            bad.push([dn, name + '/']);
            read_dirs(null, pf, dirs.slice(1), good, nil, bad, spins);
        });
    }

    function gotallfiles(good_files, nil_files, bad_files) {
        if (toast.txt == L.u_scan)
            toast.hide();

        if (uc.fsearch && !uc.turbo)
            nil_files = [];

        var ntot = good_files.concat(nil_files, bad_files).length;
        if (bad_files.length) {
            var msg = L.u_badf.format(bad_files.length, ntot);
            for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++)
                msg += '-- ' + esc(bad_files[a][1]) + '\n';

            msg += L.u_just1;
            return modal.alert(msg, function () {
                start_actx();
                gotallfiles(good_files, nil_files, []);
            });
        }

        if (nil_files.length) {
            var msg = L.u_blankf.format(nil_files.length, ntot);
            for (var a = 0, aa = Math.min(20, nil_files.length); a < aa; a++)
                msg += '-- ' + esc(nil_files[a][1]) + '\n';

            msg += L.u_just1;
            return modal.confirm(msg, function () {
                start_actx();
                gotallfiles(good_files.concat(nil_files), [], []);
            }, function () {
                start_actx();
                gotallfiles(good_files, [], []);
            });
        }

        var fps = new Set(), pdp = '';
        for (var a = 0; a < good_files.length; a++) {
            var fp = good_files[a][1],
                dp = vsplit(fp)[0];
            fps.add(fp);
            if (pdp != dp) {
                pdp = dp;
                dp = dp.slice(0, -1);
                while (dp) {
                    fps.add(dp);
                    dp = vsplit(dp)[0].slice(0, -1);
                }
            }
        }

        var junk = [], rmi = [];
        for (var a = 0; a < good_files.length; a++) {
            var fn = good_files[a][1];
            if (fn.indexOf("/.") < 0 && fn.indexOf("/__MACOS") < 0)
                continue;

            if (/\/__MACOS|\/\.(DS_Store|AppleDouble|LSOverride|DocumentRevisions-|fseventsd|Spotlight-V[0-9]|TemporaryItems|Trashes|VolumeIcon\.icns|com\.apple\.timemachine\.donotpresent|AppleDB|AppleDesktop|apdisk)/.exec(fn)) {
                junk.push(good_files[a]);
                rmi.push(a);
                continue;
            }

            if (fn.indexOf("/._") + 1 &&
                fps.has(fn.replace("/._", "/")) &&
                fn.split("/").pop().startsWith("._") &&
                !has(rmi, a)
            ) {
                junk.push(good_files[a]);
                rmi.push(a);
            }
        }

        if (!junk.length)
            return gotallfiles2(good_files);

        junk.sort();
        rmi.sort(function (a, b) { return a - b; });

        var msg = L.u_applef.format(junk.length, good_files.length);
        for (var a = 0, aa = Math.min(1000, junk.length); a < aa; a++)
            msg += '-- ' + esc(junk[a][1]) + '\n';

        return modal.confirm(msg, function () {
            for (var a = rmi.length - 1; a >= 0; a--)
                good_files.splice(rmi[a], 1);

            start_actx();
            gotallfiles2(good_files);
        }, function () {
            start_actx();
            gotallfiles2(good_files);
        });
    }

    function gotallfiles2(good_files) {
        good_files.sort(function (a, b) {
            return a[1] < b[1] ? -1 : 1;
        });

        var msg = [];

        if (lifetime)
            msg.push('<b>' + L.u_up_life.format(lhumantime(st.lifetime || lifetime)) + '</b>\n\n');

        if (FIREFOX && good_files.length > 3000)
            msg.push(L.u_ff_many + "\n\n");

        msg.push(L.u_asku.format(good_files.length, esc(uricom_dec(get_evpath()))) + '<ul>');
        for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
            msg.push('<li>' + esc(good_files[a][1]) + '</li>');

        if (uc.ask_up && !uc.fsearch)
            return modal.confirm(msg.join('') + '</ul>', function () {
                start_actx();
                up_them(good_files);
                if (have_up2k_idx)
                    toast.inf(15, L.u_unpt, L.u_unpt);
            }, null);

        up_them(good_files);
    }

    function up_them(good_files) {
        start_actx();
        draw_turbo();
        var evpath = get_evpath(),
            draw_each = good_files.length < 50;

        if (WebAssembly && !hws.length) {
            var nw = Math.min(navigator.hardwareConcurrency || 4, 16);

            if (CHROME) {
                // chrome-bug 383568268 // #124
                nw = Math.max(1, (nw > 4 ? 4 : (nw - 1)));
                if (VCHROME < 137)
                nw = (subtle && !MOBILE && nw > 2) ? 2 : nw;
            }

            var x = sread('u2hashers') || window.u2hashers;
            if (x) {
                console.log('u2hashers is overriding default-value ' + nw);
                nw = parseInt(x);
            }

            for (var a = 0; a < nw; a++)
                hws.push(new Worker(SR + '/.cpr/w/deps/sha512.hw.js?_=' + TS));

            if (!subtle)
                for (var a = 0; a < hws.length; a++)
                    hws[a].postMessage(['nosubtle']);

            console.log(hws.length + " hashers");
        }

        if (!uc.az)
            good_files.sort(function (a, b) {
                return a[0].size - b[0].size;
            });

        for (var a = 0; a < good_files.length; a++) {
            var fobj = good_files[a][0],
                name = good_files[a][1],
                fdir = evpath,
                now = Date.now(),
                lmod = (uc.u2ts && fobj.lastModified) || 0,
                ofs = name.lastIndexOf('/') + 1;

            if (ofs) {
                fdir += url_enc(name.slice(0, ofs));
                name = name.slice(ofs);
            }

            var entry = {
                "n": st.files.length,
                "t0": now,
                "fobj": fobj,
                "name": name,
                "size": fobj.size || 0,
                "lmod": lmod / 1000,
                "purl": fdir,
                "done": false,
                "bytes_uploaded": 0,
                "hash": []
            },
                key = name + '\n' + entry.size + '\n' + lmod + '\n' + uc.fsearch;

            if (uc.fsearch)
                entry.srch = 1;
            else if (uc.rand) {
                entry.rand = true;
                entry.name = 'a\n' + entry.name;
            }
            else if (uc.umod)
                entry.umod = true;

            if (biggest_file < entry.size)
                biggest_file = entry.size;

            try {
                if (st.seen[fdir][key])
                    continue;
            }
            catch (ex) {
                st.seen[fdir] = {};
            }

            st.seen[fdir][key] = 1;

            pvis.addfile([
                uc.fsearch ? esc(entry.name) : linksplit(
                    entry.purl + uricom_enc(entry.name), window.up_site).join(' / '),
                '📐 ' + L.u_hashing,
                ''
            ], entry.size, draw_each);

            st.bytes.total += entry.size;
            st.files.push(entry);
            if (uc.turbo)
                push_t(st.todo.head, entry);
            else
                push_t(st.todo.hash, entry);
        }
        if (!draw_each) {
            pvis.drawcard("q");
            pvis.changecard(pvis.act);
        }
        ebi('u2tabw').className = 'ye';

        setTimeout(function () {
            if (!actx || actx.state != 'suspended' || toast.visible)
                return;

            toast.warn(30, '<div id="actx_go">' + L.u_actx + '</div>');
            ebi('actx_go').onclick = function () {
                start_actx();
                toast.inf(3, 'thanks!');
            };
        }, 500);
    }

    function more_one_file() {
        fdom_ctr++;
        var elm = mknod('div');
        elm.innerHTML = (
            '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" tabindex="-1" />' +
            '<input id="dir{0}" type="file" name="dir{0}[]" multiple="multiple" tabindex="-1" webkitdirectory />'
        ).format(fdom_ctr);
        ebi('u2form').appendChild(elm);
        ebi('file' + fdom_ctr).onchange = gotfile;
        ebi('dir' + fdom_ctr).onchange = gotdir;
    }
    more_one_file();

    function linklist() {
		var ret = [],
            base = (window.up_site || location.origin).replace(/\/$/, '');

        for (var a = 0; a < st.files.length; a++) {
            var t = st.files[a],
                url = t.purl + uricom_enc(t.name);

            if (t.fk)
                url += '?k=' + t.fk;

            ret.push(base + url);
        }
        return ret.join('\r\n');
    }

    ebi('luplinks').onclick = function (e) {
        ev(e);
        modal.alert(linklist());
    };

    ebi('cuplinks').onclick = function (e) {
        ev(e);
        var txt = linklist();
        cliptxt(txt + '\n', function () {
            toast.inf(5, L.un_clip.format(txt.split('\n').length));
        });
    };

    var etaref = 0, etaskip = 0, utw_minh = 0, utw_read = 0, utw_card = 0;
    function etafun() {
        var nhash = st.busy.head.length + st.busy.hash.length + st.todo.head.length + st.todo.hash.length,
            nsend = st.busy.upload.length + st.todo.upload.length,
            now = Date.now(),
            td = (now - (etaref || now)) / 1000.0;

        etaref = now;
        if (td > 1.2)
            td = 0.05;

        //ebi('acc_info').innerHTML = humantime(st.time.busy) + ' ' + f2f(now / 1000, 1);

        if (utw_card != pvis.act) {
            utw_card = pvis.act;
            utw_read = 9001;
            ebi('u2tabw').style.minHeight = '0px';
        }

        if (++utw_read >= 20) {
            utw_read = 0;
            utw_minh = parseInt(ebi('u2tabw').style.minHeight || '0');
        }

        var minh = QS('#op_up2k.act') && st.is_busy ? Math.max(utw_minh, ebi('u2tab').offsetHeight + 32) : 0;
        if (utw_minh < minh || !utw_minh) {
            utw_minh = minh;
            ebi('u2tabw').style.minHeight = utw_minh + 'px';
        }

        if (!nhash) {
            var h = L.u_etadone.format(humansize(st.bytes.hashed, 2), pvis.ctr.ok + pvis.ctr.ng);
            if (st.eta.h !== h) {
                st.eta.h = ebi('u2etah').innerHTML = h;
                console.log('{0} hash, {1} up, {2} busy'.format(
                    f2f(st.time.hashing, 1),
                    f2f(st.time.uploading, 1),
                    f2f(st.time.busy, 1)));
            }
        }

        if (!nsend && !nhash) {
            var h = L.u_etadone.format(humansize(st.bytes.uploaded, 2), pvis.ctr.ok + pvis.ctr.ng);

            if (st.eta.u !== h)
                st.eta.u = ebi('u2etau').innerHTML = h;

            if (st.eta.t !== h)
                st.eta.t = ebi('u2etat').innerHTML = h;
        }

        if (!st.busy.hash.length && !hashing_permitted())
            nhash = 0;

        if (!parallel_uploads || !(nhash + nsend) || (flag && flag.owner && !flag.ours))
            return;

        var t = [];
        if (nhash) {
            st.time.hashing += td;
            t.push(['u2etah', st.bytes.hashed, st.bytes.hashed, st.time.hashing]);
            if (uc.fsearch) {
                st.time.busy += td;
                t.push(['u2etat', st.bytes.hashed, st.bytes.hashed, st.time.hashing]);
            }
        }

        var b_up = st.bytes.inflight + st.bytes.uploaded,
            b_fin = st.bytes.inflight + st.bytes.finished;

        if (nsend) {
            st.time.uploading += td;
            t.push(['u2etau', b_up, b_fin, st.time.uploading]);
        }
        if ((nhash || nsend) && !uc.fsearch) {
            if (!b_fin) {
                ebi('u2etat').innerHTML = L.u_etaprep;
            }
            else {
                st.time.busy += td;
                t.push(['u2etat', b_fin, b_fin, st.time.busy]);
            }
        }
        for (var a = 0; a < t.length; a++) {
            var hid = t[a][0],
                eid = hid.slice(-1),
                etaw = st.etaw[eid];

            if (st.etac[eid] > 100) {  // num chunks
                st.etac[eid] = 0;
                etaw.push(jcp(t[a]));
                if (etaw.length > 5)
                    etaw.shift();
            }

            var h = etaw[0],
                rem = st.bytes.total - t[a][2],
                bps = (t[a][1] - h[1]) / Math.max(0.1, t[a][3] - h[3]),
                eta = Math.floor(rem / bps);

            if (t[a][1] < 1024 || t[a][3] < 0.1) {
                ebi(hid).innerHTML = L.u_etaprep;
                continue;
            }

            donut.eta = eta;
            st.eta[eid] = '{0}, {1}/s, {2}'.format(
                humansize(rem, 2), humansize(bps, 1), humantime(eta));

            if (!etaskip)
                ebi(hid).innerHTML = st.eta[eid];
        }
        if (++etaskip > 2)
            etaskip = 0;
    }

    function got_oserr() {
        if (!hws.length || !uc.hashw || st.oserr)
            return;

        st.oserr = true;
        var msg = HTTPS ? L.u_emtleak3 : L.u_emtleak2.format((location + '').replace(':', 's:'));
        modal.alert(L.u_emtleak1 + msg + (CHROME ? L.u_emtleakc : FIREFOX ? L.u_emtleakf : ''));
    }

    /////
    ////
    ///   actuator
    //

    function handshakes_permitted() {
        if (!st.todo.handshake.length)
            return true;

        var t = st.todo.handshake[0],
            cd = t.cooldown;

        if (cd && cd > Date.now())
            return false;

        // keepalive or verify
        if (t.keepalive ||
            t.t_uploaded)
            return true;

        if (parallel_uploads <
            st.busy.handshake.length)
            return false;

        if (t.n - st.car > Math.max(8, parallel_uploads))
            // prevent runahead from a stuck upload (slow server hdd)
            return false;

        if ((uc.multitask ? parallel_uploads : 0) <
            st.todo.upload.length +
            st.busy.upload.length)
            return false;

        return true;
    }

    function hashing_permitted() {
        if (!parallel_uploads)
            return false;

        var nhs = st.todo.handshake.length + st.busy.handshake.length,
            nup = st.todo.upload.length + st.busy.upload.length;

        if (uc.multitask) {
            if (nhs + nup < parallel_uploads)
                return true;

            if (!uc.az)
                return nhs < 2;

            var ahead = st.bytes.hashed - st.bytes.finished,
                nmax = ahead < biggest_file / 8 ? 32 : 16;

            return ahead < biggest_file && nhs < nmax;
        }
        return handshakes_permitted() && 0 == nhs + nup;
    }

    var tasker = (function () {
        var running = false;

        var defer = function () {
            running = false;
        }

        var taskerd = function () {
            if (running)
                return;

            if (crashed || !got_deps())
                return defer();

            running = true;
            while (true) {
                var now = Date.now(),
                    blocktime = now - r.tact,
                    was_busy = !!st.is_busy,
                    is_busy = !!(  // gzip take the wheel
                        st.car < st.files.length ||
                        st.busy.hash.length ||
                        st.todo.hash.length ||
                        st.busy.handshake.length ||
                        st.todo.handshake.length ||
                        st.busy.upload.length ||
                        st.todo.upload.length ||
                        st.busy.head.length ||
                        st.todo.head.length);

                if (blocktime > 2500)
                    console.log('main thread blocked for ' + blocktime);

                r.tact = now;

                if (was_busy && !is_busy) {
                    var nre = 0, nf = 0;
                    for (var a = 0; a < st.files.length; a++)
                        if (st.files[a].want_recheck)
                            nre++;
                    console.log('nre', nre, 'st', st.nre);
                    if (st.nre != nre) {
                        st.nre = nre;
                        nf = st.files.length;
                    }
                    for (var a = 0; a < nf; a++) {
                        var t = st.files[a];
                        if (t.want_recheck) {
                            t.rechecks++;
                            t.want_recheck = false;
                            push_t(st.todo.handshake, t);
                        }
                    }
                    is_busy = !!st.todo.handshake.length;
                    try {
                        if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused))
                            treectl.goto();
                    }
                    catch (ex) { }
                }

                if (was_busy != is_busy) {
                    st.is_busy = is_busy;

                    window[(is_busy ? "add" : "remove") +
                        "EventListener"]("beforeunload", warn_uploader_busy);

                    donut.on(is_busy);

                    if (!is_busy) {
                        uptoast();
                        //throw console.hist.join('\n');
                    }
                    else {
                        timer.add(donut.do);
                        timer.add(etafun, false);
                        ebi('u2etas').style.textAlign = 'left';
                    }
                    etafun();
                }

                if (flag) {
                    if (is_busy) {
                        flag.take(now);
                        ebi('u2flagblock').style.display = flag.ours ? '' : 'block';
                        if (!flag.ours)
                            return defer();
                    }
                    else if (flag.ours) {
                        flag.give();
                    }
                }

                if (st.bytes.inflight && (st.bytes.inflight < 0 || !st.busy.upload.length)) {
                    console.log('insane inflight ' + st.bytes.inflight);
                    st.bytes.inflight = 0;
                }

                var mou_ikkai = false;

                var nprev = -1;
                for (var a = 0; a < st.todo.upload.length; a++) {
                    var nf = st.todo.upload[a].nfile;
                    if (nprev == nf)
                        continue;

                    nprev = nf;
                    var t = st.files[nf];
                    if (now - t.t_busied > 1000 * 30 &&
                        now - t.t_handshake > 1000 * (21600 - 1800)
                    ) {
                        apop(st.todo.handshake, t);
                        st.todo.handshake.unshift(t);
                        t.keepalive = true;
                    }
                }

                if (st.todo.head.length &&
                    st.busy.head.length < parallel_uploads &&
                    (!is_busy || st.todo.head[0].n - st.car < parallel_uploads * 2)) {
                    exec_head();
                    mou_ikkai = true;
                }

                if (handshakes_permitted() &&
                    st.todo.handshake.length) {
                    exec_handshake();
                    mou_ikkai = true;
                }

                if (st.todo.upload.length &&
                    st.busy.upload.length < parallel_uploads &&
                    can_upload_next()) {
                    exec_upload();
                    mou_ikkai = true;
                }

                if (hashing_permitted() &&
                    st.todo.hash.length &&
                    !st.busy.hash.length) {
                    exec_hash();
                    mou_ikkai = true;
                }

                if (is_busy && st.modn < 100) {
                    var t0 = Date.now() + (ebi('repl').offsetTop ? 0 : 0);

                    if (++st.modn >= 10) {
                        if (st.modn == 10)
                            st.mod0 = Date.now();

                        st.modv += Date.now() - t0;
                    }

                    if (st.modn >= 100) {
                        var n = st.modn - 10,
                            ipu = st.modv / n,
                            spu = (Date.now() - st.mod0) / n,
                            ir = spu / ipu;

                        console.log('tsk:', f2f(ipu, 2), ' spu:', f2f(spu, 2), ' ir:', f2f(ir, 2));
                        // efficiency estimates;
                        // ir: 8=16% 11=60% 16=90% 24=100%
                        // ipu: 1=40% .8=60% .3=100%
                        if (ipu >= 0.5 && ir <= 15)
                            pvis.go_potato();
                    }
                }

                if (!mou_ikkai || crashed)
                    return defer();
            }
        }
        timer.add(taskerd, true);
        return taskerd;
    })();

    function uptoast() {
        if (st.busy.handshake.length)
            return;

        for (var a = 0; a < st.files.length; a++) {
            var t = st.files[a];
            if (t.want_recheck && t.rechecks < 999)
                return;
        }

        var sr = uc.fsearch,
            ok = pvis.ctr.ok,
            ng = pvis.ctr.ng,
            spd = Math.floor(st.bytes.finished / st.time.busy),
            suf = '\n\n{0} @ {1}/s'.format(shumantime(st.time.busy), humansize(spd)),
            t = uc.ask_up ? 0 : 10;

        console.log('toast', ok, ng);

        if (ok && ng)
            toast.warn(t, uc.nagtxt = (sr ? L.ur_sm : L.ur_um).format(ok, ng) + suf);
        else if (ok > 1)
            toast.ok(t, uc.nagtxt = (sr ? L.ur_aso : L.ur_auo).format(ok) + suf);
        else if (ok)
            toast.ok(t, uc.nagtxt = (sr ? L.ur_1so : L.ur_1uo) + suf);
        else if (ng > 1)
            toast.err(t, uc.nagtxt = (sr ? L.ur_asn : L.ur_aun).format(ng) + suf);
        else if (ng)
            toast.err(t, uc.nagtxt = (sr ? L.ur_1sn : L.ur_1un) + suf);

        timer.rm(etafun);
        timer.rm(donut.do);
        ebi('u2tabw').style.minHeight = '0px';
        utw_minh = 0;

        if (pvis.act == 'bz')
            pvis.changecard('bz');

        var n = st.files.length - 1,
            f = n >= 0 && st.files[n];
        if (f && !f.srch) {
            var xhr = new XHR(),
                ct = 'application/x-www-form-urlencoded;charset=UTF-8';
            xhr.open('POST', f.purl, true);
            xhr.setRequestHeader('Content-Type', ct);
            if (xhr.overrideMimeType)
                xhr.overrideMimeType('Content-Type', ct);
            xhr.send('msg=upload-queue-empty;' + uricom_enc(f.name));
        }
    }

    function chill(t) {
        var now = Date.now();
        if ((t.coolmul || 0) < 5 || now - t.cooldown < t.coolmul * 700)
            t.coolmul = Math.min((t.coolmul || 0.5) * 2, 32);

        var cd = now + 1000 * (t.coolmul + Math.random() * 4 + 2);
        t.cooldown = Math.floor(Math.max(cd, t.cooldown || 1));
        return t;
    }

    /////
    ////
    ///   hashing
    //

    // https://gist.github.com/jonleighton/958841
    function buf2b64(arrayBuffer) {
        var base64 = '',
            cset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
            src = new Uint8Array(arrayBuffer),
            nbytes = src.byteLength,
            byteRem = nbytes % 3,
            mainLen = nbytes - byteRem,
            a, b, c, d, chunk;

        for (var i = 0; i < mainLen; i = i + 3) {
            chunk = (src[i] << 16) | (src[i + 1] << 8) | src[i + 2];
            // create 8*3=24bit segment then split into 6bit segments
            a = (chunk & 16515072) >> 18; // (2^6 - 1) << 18
            b = (chunk & 258048) >> 12; // (2^6 - 1) << 12
            c = (chunk & 4032) >> 6; // (2^6 - 1) << 6
            d = chunk & 63; // 2^6 - 1

            // Convert the raw binary segments to the appropriate ASCII encoding
            base64 += cset[a] + cset[b] + cset[c] + cset[d];
        }

        if (byteRem == 1) {
            chunk = src[mainLen];
            a = (chunk & 252) >> 2; // (2^6 - 1) << 2
            b = (chunk & 3) << 4; // 2^2 - 1  (zero 4 LSB)
            base64 += cset[a] + cset[b];//+ '==';
        }
        else if (byteRem == 2) {
            chunk = (src[mainLen] << 8) | src[mainLen + 1];
            a = (chunk & 64512) >> 10; // (2^6 - 1) << 10
            b = (chunk & 1008) >> 4; // (2^6 - 1) << 4
            c = (chunk & 15) << 2; // 2^4 - 1  (zero 2 LSB)
            base64 += cset[a] + cset[b] + cset[c];//+ '=';
        }

        return base64;
    }

    function hex2u8(txt) {
        return new Uint8Array(txt.match(/.{2}/g).map(function (b) { return parseInt(b, 16); }));
    }

    function get_chunksize(filesize) {
        var chunksize = 1024 * 1024,
            stepsize = 512 * 1024;

        while (true) {
            for (var mul = 1; mul <= 2; mul++) {
                var nchunks = Math.ceil(filesize / chunksize);
                if (nchunks <= 256 || (chunksize >= 32 * 1024 * 1024 && nchunks <= 4096))
                    return chunksize;

                chunksize += stepsize;
                stepsize *= mul;
            }
        }
    }

    function exec_hash() {
        var t = st.todo.hash.shift();
        if (!t.size)
            return st.todo.handshake.push(t);

        st.busy.hash.push(t);
        st.nfile.hash = t.n;
        t.t_hashing = Date.now();

        var bpend = 0,
            nchunk = 0,
            chunksize = get_chunksize(t.size),
            nchunks = Math.ceil(t.size / chunksize),
            csz_mib = chunksize / 1048576,
            tread = t.t_hashing,
            cache_buf = null,
            cache_car = 0,
            cache_cdr = 0,
            hashers = 0,
            hashtab = {};

        // resolving subtle.digest w/o worker takes 1sec on blur if the actx hack breaks
        var use_workers = hws.length && !hws_ng && uc.hashw && (nchunks > 1 || document.visibilityState == 'hidden'),
            hash_par = (!subtle && !use_workers) ? 0 : csz_mib < 48 ? 2 : csz_mib < 96 ? 1 : 0;

        pvis.setab(t.n, nchunks);
        pvis.move(t.n, 'bz');

        if (use_workers)
            return wexec_hash(t, chunksize, nchunks);

        var segm_next = function () {
            if (nchunk >= nchunks || bpend)
                return false;

            var nch = nchunk++,
                car = nch * chunksize,
                cdr = Math.min(chunksize + car, t.size);

            st.bytes.hashed += cdr - car;
            st.etac.h++;

            if (MOBILE && CHROME && st.slow_io === null && nch == 1 && cdr - car >= 1024 * 512) {
                var spd = Math.floor((cdr - car) / (Date.now() + 1 - tread));
                st.slow_io = spd < 40 * 1024;
                console.log('spd {0}, slow: {1}'.format(spd, st.slow_io));
            }

            if (cdr <= cache_cdr && car >= cache_car) {
                try {
                    var ofs = car - cache_car,
                        ofs2 = ofs + (cdr - car),
                        buf = cache_buf.subarray(ofs, ofs2);

                    hash_calc(nch, buf);
                }
                catch (ex) {
                    vis_exh(ex + '', 'up2k.js', '', '', ex);
                }
                return;
            }

            var reader = new FileReader(),
                fr_cdr = cdr;

            if (st.slow_io) {
                var step = cdr - car,
                    tgt = 48 * 1048576;

                while (step && fr_cdr - car < tgt)
                    fr_cdr += step;
                if (fr_cdr - car > tgt && fr_cdr > cdr)
                    fr_cdr -= step;
                if (fr_cdr > t.size)
                    fr_cdr = t.size;
            }

            var orz = function (e) {
                bpend = 0;
                var buf = e.target.result;
                if (fr_cdr > cdr) {
                    cache_buf = new Uint8Array(buf);
                    cache_car = car;
                    cache_cdr = fr_cdr;
                    buf = cache_buf.subarray(0, cdr - car);
                }
                if (hashers < hash_par)
                    segm_next();

                hash_calc(nch, buf);
            };
            reader.onload = function (e) {
                try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
            };
            reader.onerror = function () {
                var err = esc('' + reader.error),
                    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 + ' @ ' + car);
                    console.log('OS-error', reader.error, '@', car);
                    handled = true;
                    got_oserr();
                }

                if (handled) {
                    pvis.move(t.n, 'ng');
                    apop(st.busy.hash, t);
                    st.bytes.finished += t.size;
                    return;
                }

                toast.err(0, 'y o u   b r o k e    i t\nfile: ' + esc(t.name + '') + '\nerror: ' + err);
            };
            bpend = 1;
            tread = Date.now();
            reader.readAsArrayBuffer(t.fobj.slice(car, fr_cdr));

            return true;
        };

        var hash_calc = function (nch, buf) {
            hashers++;
            var orz = function (hashbuf) {
                var hslice = new Uint8Array(hashbuf).subarray(0, 33),
                    b64str = buf2b64(hslice);

                hashers--;
                hashtab[nch] = b64str;
                t.hash.push(nch);
                pvis.hashed(t);
                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, L.u_hashdone);
                pvis.seth(t.n, 1, '📦 wait');
                apop(st.busy.hash, t);
                st.todo.handshake.push(t);
                tasker();
            };

            var hash_done = function (hashbuf) {
                try { orz(hashbuf); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
            };

            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);
        };
        segm_next();
    }

    function wexec_hash(t, chunksize, nchunks) {
        var nchunk = 0,
            reading = 0,
            max_readers = 1,
            opt_readers = 2,
            failed = false,
            free = [],
            busy = {},
            nbusy = 0,
            init = 0,
            ninit = 0,
            hashtab = {},
            mem = (MOBILE ? 128 : 256) * 1024 * 1024;

        if (!hws_ok)
            init = setInterval(function() {
                if (ninit < hws_ok) {
                    ninit = hws_ok;
                    return toast.inf(10, 'initializing webworkers ({0}/{1})'.format(hws_ok, hws.length), "iwwt");
                }
                clearInterval(init);
                hws_ng = true;
                toast.warn(30, 'webworkers failed to start\n\nwill be a bit slower due to\nhashing on main-thread');
                apop(st.busy.hash, t);
                st.todo.hash.unshift(t);
                exec_hash();
            }, 5000);

        for (var a = 0; a < hws.length; a++) {
            var w = hws[a];
            w.onmessage = onmsg;
            if (init)
                w.postMessage(['ping']);
            if (mem > 0)
                free.push(w);
            mem -= chunksize;
        }

        function go_next() {
            if (st.slow_io && uc.multitask)
                // android-chrome filereader latency is ridiculous but scales linearly
                // (unlike every other platform which instead suffers on parallel reads...)
                max_readers = opt_readers = free.length;

            if (reading >= max_readers || !free.length || nchunk >= nchunks)
                return;

            var w = free.pop(),
                car = nchunk * chunksize,
                cdr = Math.min(chunksize + car, t.size);

            //console.log('[P ] %d read bgin (%d reading, %d busy)', nchunk, reading + 1, nbusy + 1);
            w.postMessage([nchunk, t.fobj, car, cdr]);
            busy[nchunk] = w;
            nbusy++;
            reading++;
            nchunk++;
            if (Date.now() - up2k.tact > 1500)
                tasker();
        }

        function go_fail() {
            failed = true;
            if (nbusy)
                return;
            apop(st.busy.hash, t);
            st.bytes.finished += t.size;
        }

        function onmsg(d) {
            if (hws_ng)
                return;

            d = d.data;
            var k = d[0];

            if (k == "pong")
                if (++hws_ok == hws.length) {
                    clearInterval(init);
                    if (toast.tag == 'iwwt')
                        toast.hide();
                    go_next();
                }

            if (k == "panic")
                return vis_exh(d[1], 'up2k.js', '', '', d[1]);

            if (k == "fail") {
                var nchunk = d[1];
                free.push(busy[nchunk]);
                delete busy[nchunk];
                nbusy--;
                reading--;

                pvis.seth(t.n, 1, d[1]);
                pvis.seth(t.n, 2, d[2]);
                console.log(d[1], d[2]);
                if (d[1] == 'OS-error')
                    got_oserr();

                pvis.move(t.n, 'ng');
                return go_fail();
            }

            if (k == "ferr")
                return toast.err(0, 'y o u   b r o k e    i t\nfile: ' + esc(t.name + '') + '\nerror: ' + d[1]);

            if (k == "read") {
                reading--;
                if (MOBILE && CHROME && st.slow_io === null && d[1] == 1 && d[2] > 1024 * 512) {
                    var spd = Math.floor(d[2] / d[3]);
                    st.slow_io = spd < 40 * 1024;
                    console.log('spd {0}, slow: {1}'.format(spd, st.slow_io));
                }
                //console.log('[P ] %d read DONE (%d reading, %d busy)', d[1], reading, nbusy);
                return go_next();
            }

            if (k == "done") {
                var nchunk = d[1],
                    hslice = d[2],
                    sz = d[3];

                free.push(busy[nchunk]);
                delete busy[nchunk];
                nbusy--;

                //console.log('[P ] %d HASH DONE (%d reading, %d busy)', nchunk, reading, nbusy);

                hashtab[nchunk] = buf2b64(hslice);
                st.bytes.hashed += sz;
                t.hash.push(nchunk);
                pvis.hashed(t);

                if (failed)
                    return go_fail();

                if (t.hash.length < nchunks)
                    return nbusy < opt_readers && go_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, L.u_hashdone);
                pvis.seth(t.n, 1, '📦 wait');
                apop(st.busy.hash, t);
                st.todo.handshake.push(t);
                tasker();
            }
        }
        if (!init)
            go_next();
    }

    /////
    ////
    ///   head
    //

    function exec_head() {
        var t = st.todo.head.shift();
        if (t.done)
            return console.log('done; skip head1', t.name, t);

        st.busy.head.push(t);

        var xhr = new XMLHttpRequest();
        xhr.onerror = xhr.ontimeout = function () {
            console.log('head onerror, retrying', t.name, t);
            if (!toast.visible)
                toast.warn(9.98, L.u_enethd + "\n\nfile: " + esc(t.name), t);

            apop(st.busy.head, t);
            st.todo.head.unshift(t);
        };
        function orz(e) {
            if (t.done)
                return console.log('done; skip head2', t.name, t);

            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 && uc.datechk) {
                    srv_ts = new Date(srv_ts) / 1000;
                    ok = Math.abs(srv_ts - t.lmod) < 2;
                }
            }
            apop(st.busy.head, t);
            if (!ok && !t.srch) {
                push_t(st.todo.hash, t);
                tasker();
                return;
            }

            t.done = true;
            t.fobj = null;
            st.bytes.hashed += t.size;
            st.bytes.finished += t.size;
            pvis.move(t.n, 'bz');
            pvis.seth(t.n, 1, ok ? 'YOLO' : '404');
            pvis.seth(t.n, 2, "turbo'd");
            pvis.move(t.n, ok ? 'ok' : 'ng');
            tasker();
        };
        xhr.onload = function (e) {
            try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
        };

        xhr.open('HEAD', t.purl + uricom_enc(t.name), true);
        xhr.timeout = 34000;
        xhr.send();
    }

    /////
    ////
    ///   handshake
    //

    function exec_handshake() {
        var t = st.todo.handshake.shift(),
            keepalive = t.keepalive,
            me = Date.now();

        if (t.done)
            return console.log('done; skip hs', t.name, t);

        st.busy.handshake.push(t);
        t.keepalive = undefined;
        t.t_busied = me;

        if (keepalive)
            console.log("sending keepalive handshake", t.name, t);

        if (!t.srch && !t.t_handshake)
            pvis.seth(t.n, 2, L.u_hs);

        var xhr = new XMLHttpRequest();
        xhr.onerror = xhr.ontimeout = function () {
            if (t.t_busied != me)  // t.done ok
                return console.log('zombie handshake onerror', t.name, t);

            if (!toast.visible)
                toast.warn(9.98, L.u_eneths + "\n\nfile: " + esc(t.name), t);

            console.log('handshake onerror, retrying', t.name, t);
            apop(st.busy.handshake, t);
            st.todo.handshake.unshift(chill(t));
            t.keepalive = keepalive;
        };
        var orz = function (e) {
            if (t.t_busied != me || t.done)
                return console.log('zombie handshake onload', t.name, t);

            if (xhr.status == 200) {
                try {
                    var response = JSON.parse(xhr.responseText);
                }
                catch (ex) {
                    apop(st.busy.handshake, t);
                    st.todo.handshake.unshift(chill(t));
                    var txt = t.t_uploading ? L.u_ehsfin : t.srch ? L.u_ehssrch : L.u_ehsinit;
                    return toast.err(0, txt + '\n\n' + L.badreply + ':\n\n' + unpre(xhr.responseText));
                }

                t.t_handshake = Date.now();
                if (keepalive) {
                    apop(st.busy.handshake, t);
                    tasker();
                    return;
                }

                if (toast.tag === t)
                    toast.ok(5, L.u_fixed);

                if (!response.name) {
                    var msg = '',
                        smsg = '';

                    if (!response || !response.hits || !response.hits.length) {
                        smsg = '404';
                        msg = (L.u_s404 + ' <a href="#" class="fsearch_explain" a="' +
                            (has(perms, 'write') ? 'w' : 'r') + '">(' + L.u_expl + ')</a>');
                        timer.add(bind_fsearch_explain);
                    }
                    else {
                        smsg = 'found';
                        var msg = [];
                        for (var a = 0, aa = Math.min(20, response.hits.length); a < aa; a++) {
                            var hit = response.hits[a],
                                tr = unix2ui(hit.ts),
                                tu = unix2ui(t.lmod),
                                diff = parseInt(t.lmod) - parseInt(hit.ts),
                                cdiff = (Math.abs(diff) <= 2) ? '3c0' : 'f0b',
                                sdiff = '<span style="color:#' + cdiff + '">diff ' + diff;

                            msg.push(linksplit(hit.rp, window.up_site).join(' / ') + '<br /><small>' + tr + ' (srv), ' + tu + ' (You), ' + sdiff + '</small></span>');
                        }
                        msg = msg.join('<br />\n');
                    }
                    pvis.seth(t.n, 2, msg);
                    pvis.seth(t.n, 1, smsg);
                    pvis.move(t.n, smsg == '404' ? 'ng' : 'ok');
                    apop(st.busy.handshake, t);
                    st.bytes.finished += t.size;
                    t.done = true;
                    t.fobj = null;
                    tasker();
                    return;
                }

                t.sprs = response.sprs;

                var fk = response.fk,
                    rsp_purl = url_enc(response.purl),
                    rename = rsp_purl !== t.purl || response.name !== t.name;

                if (rename || fk) {
                    if (rename)
                        console.log("server-rename [" + t.purl + "] [" + t.name + "] to [" + rsp_purl + "] [" + response.name + "]");

                    t.purl = rsp_purl;
                    t.name = response.name;

                    var url = t.purl + uricom_enc(t.name);
                    if (fk) {
                        t.fk = fk;
                        url += '?k=' + fk;
                    }

                    pvis.seth(t.n, 0, linksplit(url, window.up_site).join(' / '));
                }

                var chunksize = get_chunksize(t.size),
                    cdr_idx = Math.ceil(t.size / chunksize) - 1,
                    cdr_sz = (t.size % chunksize) || chunksize,
                    cbd = [];

                for (var a = 0; a <= cdr_idx; a++) {
                    cbd.push(a == cdr_idx ? cdr_sz : chunksize);
                }

                t.postlist = [];
                t.wark = response.wark;
                var missing = response.hash;
                for (var a = 0; a < missing.length; a++) {
                    var idx = t.hash.indexOf(missing[a]);
                    if (idx < 0)
                        return modal.alert('wtf negative index for hash "{0}" in task:\n{1}'.format(
                            missing[a], esc(JSON.stringify(t))));

                    t.postlist.push(idx);
                    cbd[idx] = 0;
                }

                pvis.setat(t.n, cbd);
                pvis.prog(t, 0, cbd[0]);

                var done = true,
                    msg = 'done';

                if (t.postlist.length) {
                    if (t.rechecks && QS('#opa_del.act'))
                        toast.inf(30, L.u_started, L.u_unpt);

                    var arr = st.todo.upload,
                        sort = arr.length && arr[arr.length - 1].nfile > t.n;

                    if (!t.stitch_sz) {
                        // keep all connections busy
                        var bpc = (st.bytes.total - st.bytes.finished) / (parallel_uploads || 1),
                            ocs = 1024 * 1024,
                            stp = 1024 * 512,
                            ccs = ocs;
                        while (ccs < bpc) {
                            ocs = ccs;
                            ccs += stp; if (ccs < bpc) ocs = ccs;
                            ccs += stp; stp *= 2;
                        }
                        ocs = Math.floor(ocs / 1024 / 1024);
                        t.stitch_sz = Math.min(ocs, stitch_tgt);
                    }

                    for (var a = 0; a < t.postlist.length; a++) {
                        var nparts = [], tbytes = 0, stitch = t.stitch_sz;
                        if (t.nojoin && t.nojoin - t.postlist.length < 6)
                            stitch = 1;

                        --a;
                        for (var b = 0; b < stitch; b++) {
                            nparts.push(t.postlist[++a]);
                            tbytes += chunksize;
                            if (tbytes + chunksize > stitch * 1024 * 1024 || t.postlist[a + 1] - t.postlist[a] !== 1)
                                break;
                        }
                        arr.push({
                            'nfile': t.n,
                            'nparts': nparts
                        });
                    }
                    t.nojoin = 0;

                    msg = null;
                    done = false;

                    if (sort)
                        arr.sort(function (a, b) {
                            return a.nfile < b.nfile ? -1 :
                            /*  */ a.nfile > b.nfile ? 1 :
                            /*  */ a.nparts[0] < b.nparts[0] ? -1 : 1;
                        });
                }

                if (msg)
                    pvis.seth(t.n, 1, msg);

                apop(st.busy.handshake, t);

                if (done) {
                    t.done = true;
                    t.fobj = null;
                    st.bytes.finished += t.size - t.bytes_uploaded;
                    var spd1 = (t.size / ((t.t_hashed - t.t_hashing) / 1000.)) / (1024 * 1024.),
                        spd2 = (t.size / ((t.t_uploaded - t.t_uploading) / 1000.)) / (1024 * 1024.);

                    pvis.seth(t.n, 2, 'hash {0}, up {1} MiB/s'.format(
                        f2f(spd1, 2), !isNum(spd2) ? '--' : f2f(spd2, 2)));

                    pvis.move(t.n, 'ok');
                }
                else {
                    if (t.t_uploaded)
                        chill(t);

                    t.t_uploaded = undefined;
                }
                tasker();
            }
            else {
                pvis.seth(t.n, 1, "ERROR");
                pvis.seth(t.n, 2, L.u_ehstmp, t);
                apop(st.busy.handshake, t);

                var err = "",
                    cls = "ERROR",
                    rsp = unpre(xhr.responseText),
                    ofs = rsp.lastIndexOf('\nURL: ');

                if (ofs !== -1)
                    rsp = rsp.slice(0, ofs);

                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;
                    st.todo.handshake.unshift(t);
                    return;
                }

                var err_pend = rsp.indexOf('partial upload exists at a different') + 1,
                    err_srcb = rsp.indexOf('source file busy; please try again') + 1,
                    err_plug = rsp.indexOf('upload blocked by x') + 1,
                    err_dupe = rsp.indexOf('upload rejected, file already exists') + 1,
                    err_exists = rsp.indexOf('upload rejected, a file with that name already exists') + 1;

                if (err_pend || err_srcb || err_plug || err_dupe || err_exists) {
                    err = rsp;
                    ofs = err.indexOf('\n/');
                    if (ofs !== -1) {
                        err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' / ');
                    }
                    if (!t.rechecks)
                        t.rechecks = 0;
                    if (t.rechecks < 999 && (err_pend || err_srcb)) {
                        t.want_recheck = true;
                        if (st.busy.upload.length || st.busy.handshake.length || st.bytes.uploaded) {
                            err = L.u_dupdefer;
                            cls = 'defer';
                        }
                    }
                    if (err_pend) {
                        err += ' <a href="#" class="fsearch_explain" a="u">(' + L.u_expl + ')</a>';
                        timer.add(bind_fsearch_explain);
                    }
                }

                if (err != "") {
                    if (!t.t_uploading)
                        st.bytes.finished += t.size;

                    pvis.seth(t.n, 1, cls);
                    pvis.seth(t.n, 2, err);
                    pvis.move(t.n, 'ng');

                    tasker();
                    return;
                }

                st.todo.handshake.unshift(chill(t));

                if (rsp.indexOf('server HDD is full') + 1)
                    return toast.err(0, L.u_ehsdf + "\n\n" + rsp.replace(/.*; /, ''));

                err = t.t_uploading ? L.u_ehsfin : t.srch ? L.u_ehssrch : L.u_ehsinit;
                xhrchk(xhr, err + "\n\nfile: " + esc(t.name) + "\n\nerror ", "404, target folder not found", "warn", t);
            }
        }
        xhr.onload = function (e) {
            try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
        };

        var req = {
            "name": t.name,
            "size": t.size,
            "lmod": t.lmod,
            "life": st.lifetime,
            "hash": t.hash
        };
        if (t.srch)
            req.srch = 1;
        else if (t.rand)
            req.rand = true;
        else if (t.umod)
            req.umod = true;

        if (!t.srch) {
            if (uc.ow == 1)
                req.replace = 'mt';
            if (uc.ow == 2)
                req.replace = true;
            if (uc.ow == 3)
                req.replace = 'skip';
        }

        xhr.open('POST', t.purl, true);
        xhr.responseType = 'text';
        xhr.timeout = 42000 + (t.srch || t.t_uploaded ? 0 :
            (t.size / (1048 * 20))); // safededup 20M/s hdd
        xhr.send(JSON.stringify(req));
    }

    /////
    ////
    ///   upload
    //

    function can_upload_next() {
        var upt = st.todo.upload[0],
            upf = st.files[upt.nfile],
            nhs = st.busy.handshake.length,
            hs = nhs && st.busy.handshake[0],
            now = Date.now();

        if (nhs >= 16)
            return false;

        if (hs && hs.t_uploaded && Date.now() - hs.t_busied > 10000)
            // verification HS possibly held back by uploads
            return false;

        for (var a = 0, aa = st.busy.handshake.length; a < aa; a++) {
            var hs = st.busy.handshake[a];
            if (hs.n < upt.nfile && hs.t_busied > now - 10 * 1000 && !st.files[hs.n].bytes_uploaded)
                return false;  // handshake race; wait for lexically first
        }

        if (upf.sprs)
            return true;

        for (var a = 0, aa = st.busy.upload.length; a < aa; a++)
            if (st.busy.upload[a].nfile == upt.nfile)
                return false;

        return true;
    }

    function exec_upload() {
        var upt = st.todo.upload.shift(),
            t = st.files[upt.nfile],
            nparts = upt.nparts,
            pcar = nparts[0],
            pcdr = nparts[nparts.length - 1],
            maxsz = (u2sz_max > 1 ? u2sz_max : 2040) * 1024 * 1024;

        if (t.done)
            return console.log('done; skip chunk', t.name, t);

        st.busy.upload.push(upt);
        st.nfile.upload = upt.nfile;

        if (!t.t_uploading)
            t.t_uploading = Date.now();

        pvis.seth(t.n, 1, "🚀 " + L.ul_send);

        var chunksize = get_chunksize(t.size),
            car = pcar * chunksize,
            cdr = (pcdr + 1) * chunksize;

        if (cdr >= t.size)
            cdr = t.size;

        if (cdr - car <= maxsz)
            return upload_sub(t, upt, pcar, pcdr, car, cdr, chunksize, car, []);

        var car0 = car, subs = [];
        while (car < cdr) {
            subs.push([car, Math.min(cdr, car + maxsz)]);
            car += maxsz;
        }
        upload_sub(t, upt, pcar, pcdr, 0, 0, chunksize, car0, subs);
    }

    function upload_sub(t, upt, pcar, pcdr, car, cdr, chunksize, car0, subs) {
        var nparts = upt.nparts,
            is_sub = subs.length;

        if (is_sub) {
            var x = subs.shift();
            car = x[0];
            cdr = x[1];
        }

        var snpart = is_sub ? ('' + pcar + '(' + (car-car0) +'+'+ (cdr-car)) :
                pcar == pcdr ? pcar : ('' + pcar + '~' + pcdr);

        var orz = function (xhr) {
            st.bytes.inflight -= xhr.bsent;
            var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
            if (txt.indexOf('upload blocked by x') + 1) {
                apop(st.busy.upload, upt);
                for (var a = pcar; a <= pcdr; a++)
                    apop(t.postlist, a);
                pvis.seth(t.n, 1, "ERROR");
                pvis.seth(t.n, 2, txt.split(/\n/)[0]);
                pvis.move(t.n, 'ng');
                return;
            }
            if (xhr.status == 200) {
                car = car0;
                if (subs.length)
                    return upload_sub(t, upt, pcar, pcdr, 0, 0, chunksize, car0, subs);

                var bdone = cdr - car;
                for (var a = pcar; a <= pcdr; a++) {
                    pvis.prog(t, a, Math.min(bdone, chunksize));
                    bdone -= chunksize;
                }
                st.bytes.finished += cdr - car;
                st.bytes.uploaded += cdr - car;
                t.bytes_uploaded += cdr - car;
                t.cooldown = t.coolmul = 0;
                st.etac.u++;
                st.etac.t++;
            }
            else if (txt.indexOf('already got that') + 1 ||
                txt.indexOf('already being written') + 1) {
                t.nojoin = t.nojoin || t.postlist.length;
                console.log("ignoring dupe-segment with backoff", t.nojoin, t.name, t);
                if (!toast.visible && st.todo.upload.length < 4)
                    toast.inf(10, L.u_cbusy);
            }
            else {
                xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), esc(t.name)), "404, target folder not found (???)", "warn", t);
                chill(t);
            }
            orz2(xhr);
        }
        var orz2 = function (xhr) {
            apop(st.busy.upload, upt);
            for (var a = pcar; a <= pcdr; a++)
                apop(t.postlist, a);
            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(),
                bfin = Math.floor(st.bytes.finished / 1024 / 1024),
                btot = Math.floor(st.bytes.total / 1024 / 1024);

            xhr.upload.onprogress = function (xev) {
                var nb = xev.loaded,
                    db = nb - xhr.bsent;

                if (!db)
                    return;

                st.bytes.inflight += db;
                xhr.bsent = nb;
                if (!IE)
                    xhr.timeout = 64000 + Date.now() - xhr.t0;
                pvis.prog(t, pcar, nb);
            };
            xhr.onload = function (xev) {
                try { orz(xhr); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
            };
            xhr.onerror = xhr.ontimeout = function (xev) {
                if (crashed)
                    return;

                st.bytes.inflight -= (xhr.bsent || 0);
                xhr.bsent = 0;

                if (!toast.visible)
                    toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), esc(t.name)), t);

                t.nojoin = t.nojoin || t.postlist.length;  // maybe rproxy postsize limit
                console.log('chunkpit onerror,', t.name, t);
                orz2(xhr);
            };

            var chashes = [],
                ctxt = t.hash[pcar],
                plen = Math.floor(192 / nparts.length);

            plen = plen > 9 ? 9 : plen < 2 ? 2 : plen;
            for (var a = pcar + 1; a <= pcdr; a++)
                chashes.push(t.hash[a].slice(0, plen));

            if (chashes.length)
                ctxt += ',' + plen + ',' + chashes.join('');

            xhr.open('POST', t.purl, true);
            xhr.setRequestHeader("X-Up2k-Hash", ctxt);
            xhr.setRequestHeader("X-Up2k-Wark", t.wark);
            if (is_sub)
                xhr.setRequestHeader("X-Up2k-Subc", car - car0);

            xhr.setRequestHeader("X-Up2k-Stat", "{0}/{1}/{2}/{3} {4}/{5} {6}".format(
                pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin,
                st.eta.t.indexOf('/s, ')+1 ? st.eta.t.split(' ').pop() : 'x'));

            xhr.setRequestHeader('Content-Type', 'application/octet-stream');
            if (xhr.overrideMimeType)
                xhr.overrideMimeType('Content-Type', 'application/octet-stream');

            xhr.bsent = 0;
            xhr.t0 = Date.now();
            xhr.timeout = 1000 * (IE ? 1234 : 42);
            xhr.responseType = 'text';
            xhr.send(t.fobj.slice(car, cdr));
        }
        do_send();
    }

    /////
    ////
    ///   config ui
    //

    function onresize(e) {
        // 10x faster than matchMedia('(min-width
        var bar = ebi('ops'),
            wpx = window.innerWidth,
            fpx = parseInt(getComputedStyle(bar)['font-size']),
            wem = wpx * 1.0 / fpx,
            wide = wem > 57 ? 'w' : '',
            parent = ebi(wide ? 'u2btn_cw' : 'u2btn_ct'),
            btn = ebi('u2btn');

        if (btn.parentNode !== parent) {
            parent.appendChild(btn);
            ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
        }

        wide = wem > 86 ? 'ww' : wide;
        parent = ebi(wide == 'ww' ? 'u2c3w' : 'u2c3t');
        var its = [ebi('u2etaw'), ebi('u2cards')];
        if (its[0].parentNode !== parent) {
            ebi('u2conf').className = wide;
            for (var a = 0; a < 2; a++) {
                parent.appendChild(its[a]);
                its[a].className = wide;
            }
        }
    }
    onresize100.add(onresize, true);

    if (MOBILE) {
        // android-chrome wobbles for a bit; firefox / iOS-safari are OK
        setTimeout(onresize, 20);
        setTimeout(onresize, 100);
        setTimeout(onresize, 500);
    }

    var o = QSA('#u2conf .c *[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 (anymod(e))
            return;

        var k = e.key || e.code;

        if (k == 'ArrowUp')
            bumpthread(1);

        if (k == '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 !== v)
                return;

            if (v > 64) {
                var p = obj.selectionStart;
                v = obj.value = 64;
                obj.selectionStart = obj.selectionEnd = p;
            }

            parallel_uploads = v;
            if (v == u2j)
                sdrop('nthread');
            else
                swrite('nthread', v);

            clmod(obj, 'err');
            return;
        }

        parallel_uploads += dir;

        if (parallel_uploads < 0)
            parallel_uploads = 0;

        if (parallel_uploads > 16)
            parallel_uploads = 16;

        if (parallel_uploads > 6)
            toast.warn(10, L.u_maxconn);
        else if (toast.txt == L.u_maxconn)
            toast.hide();

        obj.value = parallel_uploads;
        bumpthread({ "target": 1 });
    }

    var read_u2sz = function () {
        var el = ebi('u2szg'), n = parseInt(el.value);
        stitch_tgt = n = (
            isNaN(n) ? u2sz_tgt :
            n < u2sz_min ? u2sz_min :
            n > u2sz_max ? u2sz_max : n
        );
        if (n == u2sz_tgt) sdrop('u2sz'); else swrite('u2sz', n);
        if (el.value != n) el.value = n;
    };
    ebi('u2szg').addEventListener('blur', read_u2sz);
    ebi('u2szg').onkeydown = function (e) {
        if (anymod(e)) return;
        var k = e.key || e.code,
            n = k == 'ArrowUp' ? 1 : k == 'ArrowDown' ? -1 : 0;
        if (!n) return;
        this.value = parseInt(this.value) + n;
        read_u2sz();
    }

    function tgl_fsearch() {
        set_fsearch(!uc.fsearch);
    }

    function draw_turbo() {
        if (turbolvl < 0 && uc.turbo) {
            bcfg_set('u2turbo', uc.turbo = false);
            toast.err(10, L.u_turbo_c);
        }

        if (uc.turbo && !has(perms, 'read')) {
            bcfg_set('u2turbo', uc.turbo = false);
            toast.warn(30, L.u_turbo_g);
        }

        var msg = (turbolvl || !uc.turbo) ? null : uc.fsearch ? L.u_ts : L.u_tu,
            html = ebi('u2foot').innerHTML;

        if (msg && html.indexOf(msg) + 1)
            return;

        qsr('#u2turbomsg');
        if (!msg)
            return;

        var o = mknod('div', 'u2turbomsg');
        o.innerHTML = msg;
        ebi('u2foot').appendChild(o);
    }
    draw_turbo();

    function draw_life() {
        var el = ebi('u2life');
        if (!lifetime) {
            el.style.display = 'none';
            el.innerHTML = '';
            st.lifetime = 0;
            return;
        }
        el.style.display = uc.fsearch ? 'none' : '';
        el.innerHTML = '<div>' + L.u_life_cfg + '</div><div>' + L.u_life_est + '</div><div id="undor"></div>';
        set_life(Math.min(lifetime, icfg_get('lifetime', lifetime)));
        ebi('lifem').oninput = ebi('lifeh').oninput = mod_life;
        ebi('lifem').onkeydown = ebi('lifeh').onkeydown = kd_life;
        tt.att(ebi('u2life'));
    }
    draw_life();

    function mod_life(e) {
        var el = e.target,
            pow = parseInt(el.getAttribute('p')),
            v = parseInt(el.value);

        if (!isNum(v))
            return;

        if (toast.tag == mod_life)
            toast.hide();

        v *= pow;
        if (v > lifetime) {
            v = lifetime;
            toast.warn(20, L.u_life_max.format(lhumantime(lifetime)), mod_life);
        }

        swrite('lifetime', v);
        set_life(v);
    }

    function kd_life(e) {
        var el = e.target,
            k = e.key || e.code,
            d = k == 'ArrowUp' ? 1 : k == 'ArrowDown' ? -1 : 0;

        if (anymod(e) || !d)
            return;

        el.value = parseInt(el.value) + d;
        mod_life(e);
    }

    function set_life(v) {
        //ebi('lifes').value = v;
        ebi('lifem').value = parseInt(v / 60);
        ebi('lifeh').value = parseInt(v / 3600);

        var undo = have_unpost - (v ? lifetime - v : 0);
        ebi('undor').innerHTML = undo <= 0 ?
            L.u_unp_ng : L.u_unp_ok.format(lhumantime(undo));

        st.lifetime = v;
        rel_life();
    }

    function rel_life() {
        if (!lifetime)
            return;

        try {
            ebi('lifew').innerHTML = unix2ui((st.lifetime || lifetime) +
                Date.now() / 1000 - new Date().getTimezoneOffset() * 60
            ).replace(' ', ', ').slice(0, -3);
        }
        catch (ex) { }
    }
    setInterval(rel_life, 9000);

    function set_potato() {
        pvis.potato();
        set_fsearch();
    }

    function set_fsearch(new_state) {
        var fixed = false,
            persist = new_state !== undefined,
            preferred = bcfg_get('fsearch', undefined),
            can_write = false;

        if (!ebi('fsearch')) {
            new_state = false;
        }
        else if (perms.length) {
            if (!(can_write = has(perms, 'write'))) {
                new_state = true;
                fixed = true;
            }
            if (!has(perms, 'read') || !have_up2k_idx) {
                new_state = false;
                fixed = true;
            }
            if (new_state === undefined && preferred === undefined)
                new_state = can_write ? false : have_up2k_idx ? true : undefined;
        }

        if (new_state === undefined)
            new_state = preferred;

        if (new_state !== undefined)
            if (persist)
                bcfg_set('fsearch', uc.fsearch = new_state);
            else
                bcfg_upd_ui('fsearch', uc.fsearch = new_state);

        try {
            clmod(ebi('u2c3w'), 's', !can_write);
            QS('label[for="fsearch"]').style.display = QS('#fsearch').style.display = fixed ? 'none' : '';
        }
        catch (ex) { }

        try {
            var ico = uc.fsearch ? '🔎' : '🚀',
                desc = uc.fsearch ? L.ul_btns : L.ul_btnu;

            clmod(ebi('op_up2k'), 'srch', uc.fsearch);
            ebi('u2bm').innerHTML = ico + '&nbsp; <sup>' + desc + '</sup>';
        }
        catch (ex) { }

        ebi('u2tab').className = (uc.fsearch ? 'srch ' : 'up ') + pvis.act;

        var potato = uc.potato && !uc.fsearch;
        ebi('u2cards').style.display = ebi('u2tab').style.display = potato ? 'none' : '';
        ebi('u2mu').style.display = potato ? '' : 'none';

        if (u2ts.startsWith('f') || !sread('u2ts'))
            uc.u2ts = bcfg_upd_ui('u2ts', !u2ts.endsWith('u'));

        draw_turbo();
        draw_life();
        onresize();
    }

    function apply_flag_cfg() {
        if (uc.flag_en && !flag) {
            try {
                flag = up2k_flagbus();
            }
            catch (ex) {
                toast.err(5, "not supported on your browser:\n" + esc(basenames(ex)));
                bcfg_set('flag_en', uc.flag_en = false);
            }
        }
        else if (!uc.flag_en && flag) {
            if (flag.ours)
                flag.give();

            flag.ch.close();
            flag = false;
        }
    }

    function set_u2sort(en) {
        if (u2sort.indexOf('f') < 0)
            return;

        var fen = uc.az = u2sort.indexOf('n') + 1;
        bcfg_upd_ui('u2sort', fen);
        if (en != fen)
            toast.warn(10, L.ul_btnlk);
    }

    function set_u2ts(en) {
        if (u2ts.indexOf('f') < 0)
            return;

        var fen = !u2ts.endsWith('u');
        bcfg_upd_ui('u2ts', fen);
        if (en != fen)
            toast.warn(10, L.ul_btnlk);
    }

    function set_hashw() {
        if (!WebAssembly) {
            bcfg_set('hashw', uc.hashw = false);
            toast.err(10, L.u_nowork);
        }
    }

    function set_nosubtle(v) {
        if (!WebAssembly)
            return toast.err(10, L.u_nowork);
        modal.confirm(L.lang_set, location.reload.bind(location), null);
    }

    function set_upnag(en) {
        function nopenag() {
            bcfg_set('upnag', uc.upnag = false);
            toast.err(10, "https only");
        }

        function chknag() {
            if (Notification.permission != 'granted')
                nopenag();
        }

        if (!Notification || !HTTPS)
            return nopenag();

        if (en && Notification.permission == 'default')
            Notification.requestPermission().then(chknag, chknag);

        set_upsfx(en);
    }

    function set_upsfx(en) {
        if (!en)
            return;

        toast.inf(10, 'OK -- <a href="#" id="nagtest">test it!</a>')

        ebi('nagtest').onclick = function () {
            start_actx();
            uc.nagtxt = ':^)';
            setTimeout(donut.enstrobe, 200);
        };
    }

    if (uc.upnag && (!Notification || Notification.permission != 'granted'))
        bcfg_set('upnag', uc.upnag = false);

    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').oninput = bumpthread;

    ebi('u2etas').onclick = function (e) {
        ev(e);
        clmod(ebi('u2etas'), 'o', 't');
    };

    set_fsearch();
    bumpthread({ "target": 1 });
    if (parallel_uploads < 1)
        bumpthread(1);

    setTimeout(function () {
        for (var a = 0; a < up2k_hooks.length; a++)
            up2k_hooks[a]();
    }, 1);

    return r;
}


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();
favico.init();
ebi('ico1').onclick = function () {
    var a = favico.txt == this.textContent;
    swrite('icot', a ? 'c' : this.textContent);
    swrite('icof', a ? 'fc5' : '000');
    swrite('icob', a ? '222' : '');
    favico.init();
};


if (QS('#op_up2k.act'))
    goto_up2k();

apply_perms({ "perms": perms, "frand": frand, "u2ts": u2ts });
if (ls0)
    fileman.render();


(function () {
    goto();
    var op = sread('opmode');
    if (op !== null && op !== '.')
        try {
            goto(op);
        }
        catch (ex) { }
})();

J_U2K = 2;
