From 10362aa02ea7c0dfd77c6cf8ca62e7fada7d13b5 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 18 Jun 2021 00:30:37 +0200 Subject: [PATCH] v0.11.18 --- copyparty/__version__.py | 4 +- copyparty/broker_mpw.py | 1 + copyparty/broker_thr.py | 1 + copyparty/httpcli.py | 23 ++- copyparty/httpconn.py | 11 +- copyparty/httpsrv.py | 21 +- copyparty/mtag.py | 1 + copyparty/tcpsrv.py | 13 +- copyparty/up2k.py | 4 +- copyparty/web/browser.css | 101 +++++---- copyparty/web/browser.html | 32 +-- copyparty/web/browser.js | 397 +++++++++++++++++++++--------------- copyparty/web/up2k.js | 28 +-- copyparty/web/upload.css | 27 --- copyparty/web/upload.html | 22 +- copyparty/web/util.js | 60 ++++++ scripts/copyparty-repack.sh | 27 ++- scripts/make-sfx.sh | 4 + scripts/test/race.py | 105 ++++++++++ 19 files changed, 584 insertions(+), 298 deletions(-) create mode 100644 scripts/test/race.py diff --git a/copyparty/__version__.py b/copyparty/__version__.py index 4f6a08f0..06949a1a 100644 --- a/copyparty/__version__.py +++ b/copyparty/__version__.py @@ -1,8 +1,8 @@ # coding: utf-8 -VERSION = (0, 11, 17) +VERSION = (0, 11, 18) CODENAME = "the grid" -BUILD_DT = (2021, 6, 17) +BUILD_DT = (2021, 6, 18) S_VERSION = ".".join(map(str, VERSION)) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) diff --git a/copyparty/broker_mpw.py b/copyparty/broker_mpw.py index d82202a2..7ff2f2ef 100644 --- a/copyparty/broker_mpw.py +++ b/copyparty/broker_mpw.py @@ -68,6 +68,7 @@ class MpWorker(object): # self.logw("work: [{}]".format(d[0])) if dest == "shutdown": + self.httpsrv.shutdown() self.logw("ok bye") sys.exit(0) return diff --git a/copyparty/broker_thr.py b/copyparty/broker_thr.py index 64decbc2..da2e056b 100644 --- a/copyparty/broker_thr.py +++ b/copyparty/broker_thr.py @@ -25,6 +25,7 @@ class BrokerThr(object): def shutdown(self): # self.log("broker", "shutting down") + self.httpsrv.shutdown() pass def put(self, want_retval, dest, *args): diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index abef296f..f680597e 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -256,10 +256,11 @@ class HttpCli(object): if self.is_rclone: return "" + cmap = {"pw": "cppwd"} kv = { k: v for k, v in self.uparam.items() - if k not in rm and self.cookies.get(k) != v + if k not in rm and self.cookies.get(cmap.get(k, k)) != v } kv.update(add) if not kv: @@ -581,10 +582,17 @@ class HttpCli(object): try: dst = os.path.join(vfs.realpath, rem) os.makedirs(fsenc(dst)) - except: - if not os.path.isdir(fsenc(dst)): + except OSError as ex: + if ex.errno == 13: + raise Pebkac(500, "the server OS denied write-access") + + if ex.errno == 17: raise Pebkac(400, "some file got your folder name") + raise Pebkac(500, min_ex()) + except: + raise Pebkac(500, min_ex()) + x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body) ret = x.get() if sub: @@ -769,8 +777,13 @@ class HttpCli(object): try: os.mkdir(fsenc(fn)) + except OSError as ex: + if ex.errno == 13: + raise Pebkac(500, "the server OS denied write-access") + + raise Pebkac(500, "mkdir failed:\n" + min_ex()) except: - raise Pebkac(500, "mkdir failed, check the logs") + raise Pebkac(500, min_ex()) vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/") self.redirect(vpath) @@ -1187,7 +1200,7 @@ class HttpCli(object): # # send reply - if not is_compressed: + if not is_compressed and "cache" not in self.uparam: self.out_headers.update(NO_CACHE) self.out_headers["Accept-Ranges"] = "bytes" diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py index db7e7f86..4dda1c13 100644 --- a/copyparty/httpconn.py +++ b/copyparty/httpconn.py @@ -43,6 +43,7 @@ class HttpConn(object): self.ico = Ico(self.args) self.t0 = time.time() + self.stopping = False self.nbyte = 0 self.workload = 0 self.u2idx = None @@ -50,6 +51,14 @@ class HttpConn(object): self.lf_url = re.compile(self.args.lf_url) if self.args.lf_url else None self.set_rproxy() + def shutdown(self): + self.stopping = True + try: + self.s.shutdown(socket.SHUT_RDWR) + self.s.close() + except: + pass + def set_rproxy(self, ip=None): if ip is None: color = 36 @@ -174,7 +183,7 @@ class HttpConn(object): if not self.sr: self.sr = Unrecv(self.s) - while True: + while not self.stopping: if self.is_mp: self.workload += 50 if self.workload >= 2 ** 31: diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index be81c422..b0e13d0c 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -80,7 +80,14 @@ class HttpSrv(object): return len(self.clients) def shutdown(self): - self.log("ok bye") + clients = list(self.clients.keys()) + for cli in clients: + try: + cli.shutdown() + except: + pass + + self.log("httpsrv-n", "ok bye") def thr_client(self, sck, addr): """thread managing one tcp client""" @@ -100,25 +107,35 @@ class HttpSrv(object): thr.daemon = True thr.start() + fno = sck.fileno() try: if self.args.log_conn: self.log("%s %s" % addr, "|%sC-crun" % ("-" * 6,), c="1;30") cli.run() + except (OSError, socket.error) as ex: + if ex.errno not in [10038, 10054, 107, 57, 9]: + self.log( + "%s %s" % addr, + "run({}): {}".format(fno, ex), + c=6, + ) + finally: sck = cli.s if self.args.log_conn: self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 7,), c="1;30") try: + fno = sck.fileno() sck.shutdown(socket.SHUT_RDWR) sck.close() except (OSError, socket.error) as ex: if not MACOS: self.log( "%s %s" % addr, - "shut({}): {}".format(sck.fileno(), ex), + "shut({}): {}".format(fno, ex), c="1;30", ) if ex.errno not in [10038, 10054, 107, 57, 9]: diff --git a/copyparty/mtag.py b/copyparty/mtag.py index 4ae7a3dd..daf3a233 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -16,6 +16,7 @@ if not PY2: def have_ff(cmd): if PY2: + print("# checking {}".format(cmd)) cmd = (cmd + " -version").encode("ascii").split(b" ") try: sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE).communicate() diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index db7beaf2..0b90e075 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -21,6 +21,7 @@ class TcpSrv(object): self.log = hub.log self.num_clients = Counter() + self.stopping = False ip = "127.0.0.1" eps = {ip: "local only"} @@ -67,7 +68,7 @@ class TcpSrv(object): ip, port = srv.getsockname() self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port)) - while True: + while not self.stopping: if self.args.log_conn: self.log("tcpsrv", "|%sC-ncli" % ("-" * 1,), c="1;30") @@ -80,6 +81,9 @@ class TcpSrv(object): ready, _, _ = select.select(self.srv, [], []) for srv in ready: + if self.stopping: + break + sck, addr = srv.accept() sip, sport = srv.getsockname() if self.args.log_conn: @@ -95,6 +99,13 @@ class TcpSrv(object): self.hub.broker.put(False, "httpconn", sck, addr) def shutdown(self): + self.stopping = True + try: + for srv in self.srv: + srv.close() + except: + pass + self.log("tcpsrv", "ok bye") def detect_interfaces(self, listen_ips): diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 2aeb0cc1..e454037d 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -16,7 +16,7 @@ import traceback import subprocess as sp from copy import deepcopy -from .__init__ import WINDOWS, ANYWIN +from .__init__ import WINDOWS, ANYWIN, PY2 from .util import ( Pebkac, Queue, @@ -134,7 +134,7 @@ class Up2k(object): def get_state(self): mtpq = 0 q = "select count(w) from mt where k = 't:mtp'" - got_lock = self.mutex.acquire(timeout=0.5) + got_lock = False if PY2 else self.mutex.acquire(timeout=0.5) if got_lock: for cur in self.cur.values(): try: diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index b0b1c17f..b11c2e92 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -25,6 +25,35 @@ html, body { body { padding-bottom: 5em; } +#tt { + position: fixed; + max-width: 34em; + background: #222; + border: 0 solid #555; + overflow: hidden; + margin-top: 1em; + padding: 0 1em; + height: 0; + opacity: .1; + transition: opacity 0.14s, height 0.14s, padding 0.14s; + box-shadow: 0 .2em .5em #222; + border-radius: .4em; + z-index: 9001; +} +#tt.show { + padding: 1em; + height: auto; + border-width: .2em 0; + opacity: 1; +} +#tt code { + background: #3c3c3c; + padding: .2em .3em; + border-top: 1px solid #777; + border-radius: .3em; + font-family: monospace, monospace; + line-height: 2em; +} #path, #path * { font-size: 1em; @@ -189,6 +218,14 @@ a, #files tbody div a:last-child { color: #720; text-shadow: 0 0 .3em #b80; } +#ggrid a.play, +html.light #ggrid a.play { + color: #fff; + background: #750; + border-color: #c90; + border-top: 1px solid #da4; + box-shadow: 0 .1em 1.2em #b83; +} #files tbody tr.sel td, #ggrid a.sel, html.light #ggrid a.sel { @@ -210,11 +247,17 @@ html.light #ggrid a.sel { box-shadow: 0 .1em 1.2em #b36; transition: all 0.2s cubic-bezier(.2, 2.2, .5, 1); /* https://cubic-bezier.com/#.4,2,.7,1 */ } -#ggrid a.sel img { +#ggrid a.sel img, +#ggrid a.play img { opacity: .7; - box-shadow: 0 0 1em #b36; filter: contrast(130%) brightness(107%); } +#ggrid a.sel img { + box-shadow: 0 0 1em #b36; +} +#ggrid a.play img { + box-shadow: 0 0 1em #b83; +} #files tr.sel a { color: #fff; } @@ -739,7 +782,7 @@ input.eq_gain { .opwide>div { display: inline-block; vertical-align: top; - border-left: .2em solid #444; + border-left: .2em solid #4c4c4c; margin-left: .5em; padding-left: .5em; } @@ -758,31 +801,6 @@ input.eq_gain { padding: 0; border-bottom: 1px solid #555; } -#opdesc { - display: none; -} -#ops:hover #opdesc { - display: block; - background: linear-gradient(0deg,#555, #4c4c4c 80%, #444); - box-shadow: 0 .3em 1em #222; - padding: 1em; - border-radius: .3em; - position: absolute; - z-index: 3; - top: 6em; - right: 1.5em; -} -#ops:hover #opdesc.off { - display: none; -} -#opdesc code { - background: #3c3c3c; - padding: .2em .3em; - border-top: 1px solid #777; - border-radius: .3em; - font-family: monospace, monospace; - line-height: 2em; -} #thumbs { opacity: .3; } @@ -889,6 +907,15 @@ html.light { background: #eee; text-shadow: none; } +html.light #tt { + background: #fff; + border-color: #888; + box-shadow: 0 .3em 1em rgba(0,0,0,0.4); +} +html.light #tt code { + background: #060; + color: #fff; +} html.light #ops, html.light .opbox, html.light #srch_form { @@ -972,6 +999,9 @@ html.light #files tr.play td:nth-child(2n) { html.light #files tbody a.play { color: #c0f; } +html.light #files tbody a.play.act { + color: #90c; +} html.light #files tr.play td { background: #fc5; border-color: #eb1; @@ -1026,7 +1056,7 @@ html.light input[type="checkbox"] + label { color: #333; } html.light .opwide>div { - border-color: #ddd; + border-color: #ccc; } html.light .opview input[type="text"] { background: #fff; @@ -1034,14 +1064,6 @@ html.light .opview input[type="text"] { box-shadow: 0 0 2px #888; border-color: #38d; } -html.light #ops:hover #opdesc { - background: #fff; - box-shadow: 0 .3em 1em #ccc; -} -html.light #opdesc code { - background: #060; - color: #fff; -} html.light #u2tab a>span, html.light #files td div span { color: #000; @@ -1051,9 +1073,6 @@ html.light #path { text-shadow: none; box-shadow: 0 0 .3em #bbb; } -html.light #path a { - color: #333; -} html.light #path a:not(:last-child)::after { border-color: #ccc; background: none; @@ -1062,7 +1081,7 @@ html.light #path a:not(:last-child)::after { } html.light #path a:hover { background: none; - color: #60a; + color: #90d; } html.light #files tbody div a { color: #d38; diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index c3c40dec..ac3cc561 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -15,19 +15,19 @@
- --- + --- {%- if have_up2k_idx %} - 🔎 - 🚀 + 🔎 + 🚀 {%- else %} - 🚀 + 🚀 {%- endif %} - 🎈 - 📂 - 📝 - 📟 - 🎺 - ⚙️ + 🎈 + 📂 + 📝 + 📟 + 🎺 + ⚙️
@@ -48,10 +48,10 @@

switches

- ℹ️ tooltips + ℹ️ tooltips ☀️ lightmode - 田 the grid - 🖼️ thumbs + 田 the grid + 🖼️ thumbs
{%- if have_zip %} @@ -62,7 +62,7 @@

- 🌲 + 🌲 {%- for n in vpnodes %} {{ n[1] }} {%- endfor %} @@ -70,10 +70,10 @@
- 🍞... + 🍞... + - a + a
     
    diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 278f12ec..15e79294 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -11,17 +11,17 @@ function dbg(msg) { ebi('widget').innerHTML = ( '
    ' + 'sel.
    allsel.
    inv.zip' + + ' href="#" id="selall" tt="select all files">sel.
    allsel.
    inv.zip' + '
    📋irc📋txt' + + ' href="#" id="npirc" tt="copy irc-formatted track info">📋irc📋txt' + '♫' + '
    ' + '
    ' + - ' ' + + ' ' + ' ' + ' ' + ' ' + @@ -44,15 +44,35 @@ var have_webp = null; var mpl = (function () { ebi('op_player').innerHTML = ( + '

    switches

    ' + + 'preload' + + '/np clip' + + '
    ' + + '

    playback mode

    ' + '

    audio equalizer

    '); var r = { - "pb_mode": sread('pb_mode') || 'loop-folder' + "pb_mode": sread('pb_mode') || 'loop-folder', + "preload": bcfg_get('au_preload', true), + "clip": bcfg_get('au_npclip', false) + }; + + ebi('au_preload').onclick = function (e) { + ev(e); + r.preload = !r.preload; + bcfg_set('au_preload', r.preload); + }; + + ebi('au_npclip').onclick = function (e) { + ev(e); + r.clip = !r.clip; + bcfg_set('au_npclip', r.clip); + clmod(ebi('wtoggle'), 'np', r.clip && mp.au); }; function draw_pb_mode() { @@ -80,7 +100,9 @@ function MPlayer() { this.id = Date.now(); this.au = null; this.au_native = null; + this.au_native2 = null; this.au_ogvjs = null; + this.au_ogvjs2 = null; this.tracks = {}; this.order = []; @@ -135,7 +157,30 @@ function MPlayer() { } this.order = order; }; + + this.preload = function (url) { + var au = null; + if (need_ogv_for(url)) { + au = mp.au_ogvjs2; + if (!au && window['OGVPlayer']) { + au = new OGVPlayer(); + au.preload = "auto"; + this.au_ogvjs2 = au; + } + } else { + au = mp.au_native2; + if (!au) { + au = new Audio(); + au.preload = "auto"; + this.au_native2 = au; + } + } + if (au) { + au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache'); + } + }; } + addcrc(); var mp = new MPlayer(); makeSortable(ebi('files'), mp.read_order.bind(mp)); @@ -236,12 +281,12 @@ function canvas_cfg(can) { } -function glossy_grad(can, hsl) { +function glossy_grad(can, h, s, l) { var g = can.ctx.createLinearGradient(0, 0, 0, can.h), - s = [0, 0.49, 0.50, 1]; + p = [0, 0.49, 0.50, 1]; - for (var a = 0; a < hsl.length; a++) - g.addColorStop(s[a], 'hsl(' + hsl[a] + ')'); + for (var a = 0; a < p.length; a++) + g.addColorStop(p[a], 'hsl(' + h + ',' + s[a] + '%,' + l[a] + '%)'); return g; } @@ -266,16 +311,12 @@ var pbar = (function () { var bc = r.buf, bctx = bc.ctx, - sm = bc.w * 1.0 / mp.au.duration; + sm = bc.w * 1.0 / mp.au.duration, + gk = bc.h + '' + light; - if (gradh != bc.h) { - gradh = bc.h; - grad = glossy_grad(bc, [ - '85,35%,42%', - '85,40%,49%', - '85,37%,47%', - '85,35%,42%' - ]); + if (gradh != gk) { + gradh = gk; + grad = glossy_grad(bc, 85, [35, 40, 37, 35], light ? [45, 56, 50, 45] : [42, 51, 47, 42]); } bctx.fillStyle = grad; bctx.clearRect(0, 0, bc.w, bc.h); @@ -297,12 +338,11 @@ var pbar = (function () { sm = bc.w * 1.0 / mp.au.duration; pctx.clearRect(0, 0, pc.w, pc.h); - pctx.fillStyle = 'rgba(204,255,128,0.15)'; + pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)'; for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++) pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h); - pctx.fillStyle = '#9b7'; - pctx.fillStyle = 'rgba(192,255,96,0.5)'; + pctx.fillStyle = light ? 'rgba(0,64,0,0.5)' : 'rgba(192,255,96,0.5)'; for (var p = 1, mins = mp.au.duration / 60; p <= mins; p++) pctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h); @@ -349,20 +389,11 @@ var vbar = (function () { } r.draw = function () { - if (gradh != h) { - gradh = h; - grad1 = glossy_grad(r.can, [ - '50,45%,42%', - '50,50%,49%', - '50,47%,47%', - '50,45%,42%' - ]); - grad2 = glossy_grad(r.can, [ - '205,10%,16%', - '205,15%,20%', - '205,13%,18%', - '205,10%,16%' - ]); + var gh = h + '' + light; + if (gradh != gh) { + gradh = gh; + grad1 = glossy_grad(r.can, 50, light ? [50, 55, 52, 48] : [45, 52, 47, 43], light ? [54, 60, 52, 47] : [42, 51, 47, 42]); + grad2 = glossy_grad(r.can, 205, [10, 15, 13, 10], [16, 20, 18, 16]); } ctx.fillStyle = grad2; ctx.fillRect(0, 0, w, h); ctx.fillStyle = grad1; ctx.fillRect(0, 0, w * mp.vol, h); @@ -430,6 +461,8 @@ function seek_au_sec(seek) { // ogv.js breaks on .play() during playback if (mp.au === mp.au_native) mp.au.play(); + + mpui.progress_updater(); } @@ -443,6 +476,14 @@ function song_skip(n) { else play(mp.order[n == -1 ? mp.order.length - 1 : 0]); } +function next_song(e) { + ev(e); + return song_skip(1); +} +function prev_song(e) { + ev(e); + return song_skip(-1); +} function playpause(e) { @@ -452,6 +493,8 @@ function playpause(e) { mp.au.play(); else mp.au.pause(); + + mpui.progress_updater(); } else play(0); @@ -461,14 +504,8 @@ function playpause(e) { // hook up the widget buttons (function () { ebi('bplay').onclick = playpause; - ebi('bprev').onclick = function (e) { - ev(e); - song_skip(-1); - }; - ebi('bnext').onclick = function (e) { - ev(e); - song_skip(1); - }; + ebi('bprev').onclick = prev_song; + ebi('bnext').onclick = next_song; ebi('barpos').onclick = function (e) { if (!mp.au) { return play(0); @@ -483,42 +520,54 @@ function playpause(e) { // periodic tasks -(function () { - var nth = 0, - last_skip_url = ''; +var mpui = (function () { + var r = {}, + nth = 0, + timeout = null, + preloaded = null; + + r.progress_updater = function () { + clearTimeout(timeout); - var progress_updater = function () { if (!mp.au) { widget.paused(true); + return; } - else { - // indicate playback state in ui - widget.paused(mp.au.paused); - // draw current position in song - if (!mp.au.paused) - pbar.drawpos(); + // indicate playback state in ui + widget.paused(mp.au.paused); - // occasionally draw buffered regions - if (++nth == 10) { - pbar.drawbuf(); - nth = 0; - } + // draw current position in song + if (!mp.au.paused) + pbar.drawpos(); - // switch to next track if approaching the end - if (last_skip_url != mp.au.src) { - var pos = mp.au.currentTime, - len = mp.au.duration; + // occasionally draw buffered regions + if (++nth == 5) { + pbar.drawbuf(); + nth = 0; + } - if (pos > 0 && pos > len - 0.1) { - last_skip_url = mp.au.src; - song_skip(1); + // preload next song + if (mpl.preload && preloaded != mp.au.src) { + var pos = mp.au.currentTime, + len = mp.au.duration; + + if (pos > 0 && pos > len - 10) { + preloaded = mp.au.src; + try { + mp.preload(ebi(mp.order[mp.order.indexOf(mp.au.tid) + 1]).href); + } + catch (ex) { + console.log("preload failed", ex); } } } - setTimeout(progress_updater, 100); + + if (!mp.au.paused) + timeout = setTimeout(r.progress_updater, 100); }; - progress_updater(); + r.progress_updater(); + return r; })(); @@ -545,6 +594,11 @@ try { catch (ex) { } +function need_ogv_for(url) { + return need_ogv && /\.(ogg|opus)$/i.test(url); +} + + var audio_eq = (function () { var r = { "en": false, @@ -708,7 +762,7 @@ var audio_eq = (function () { } var html = [''], + 'enable'], h2 = [], h3 = [], h4 = []; var vs = []; @@ -772,7 +826,7 @@ function play(tid, seek, call_depth) { tn = 0; } else if (mpl.pb_mode == 'next-folder') { - treectl.ls_cb = function () { song_skip(1); }; + treectl.ls_cb = next_song; return tree_neigh(1); } } @@ -782,7 +836,7 @@ function play(tid, seek, call_depth) { tn = mp.order.length - 1; } else if (mpl.pb_mode == 'next-folder') { - treectl.ls_cb = function () { song_skip(-1); }; + treectl.ls_cb = prev_song; return tree_neigh(-1); } } @@ -798,7 +852,7 @@ function play(tid, seek, call_depth) { var attempt_play = true; var url = mp.tracks[tid]; - if (need_ogv && /\.(ogg|opus)$/i.test(url)) { + if (need_ogv_for(url)) { if (mp.au_ogvjs) { mp.au = mp.au_ogvjs; } @@ -806,7 +860,8 @@ function play(tid, seek, call_depth) { mp.au = mp.au_ogvjs = new OGVPlayer(); attempt_play = false; mp.au.addEventListener('error', evau_error, true); - mp.au.addEventListener('progress', pbar.drawpos, false); + mp.au.addEventListener('progress', pbar.drawpos); + mp.au.addEventListener('ended', next_song); widget.open(); } else { @@ -826,7 +881,8 @@ function play(tid, seek, call_depth) { if (!mp.au_native) { mp.au = mp.au_native = new Audio(); mp.au.addEventListener('error', evau_error, true); - mp.au.addEventListener('progress', pbar.drawpos, false); + mp.au.addEventListener('progress', pbar.drawpos); + mp.au.addEventListener('ended', next_song); widget.open(); } mp.au = mp.au_native; @@ -835,7 +891,7 @@ function play(tid, seek, call_depth) { audio_eq.apply(); mp.au.tid = tid; - mp.au.src = url; + mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache'); mp.au.volume = mp.expvol(); var oid = 'a' + tid; setclass(oid, 'play act'); @@ -844,7 +900,9 @@ function play(tid, seek, call_depth) { clmod(trs[a], 'play'); } ebi(oid).parentElement.parentElement.className += ' play'; - clmod(ebi('wtoggle'), 'np', 1); + clmod(ebi('wtoggle'), 'np', mpl.clip); + if (window.thegrid) + thegrid.loadsel(); try { if (attempt_play) @@ -868,6 +926,7 @@ function play(tid, seek, call_depth) { o.setAttribute('id', oid); } + mpui.progress_updater(); pbar.drawbuf(); return true; } @@ -875,7 +934,7 @@ function play(tid, seek, call_depth) { alert('playback failed: ' + ex); } setclass(oid, 'play'); - setTimeout('song_skip(1));', 500); + setTimeout(next_song, 500); } @@ -949,6 +1008,8 @@ function autoplay_blocked(seek) { mp.au.play(); if (seek) seek_au_sec(seek); + else + mpui.progress_updater(); }; na.onclick = unblocked; } @@ -982,9 +1043,9 @@ var thegrid = (function () { gfiles.style.display = 'none'; gfiles.innerHTML = ( '
    ' + - 'multiselect   zoom ' + - ' ' + - '+   sort by: ' + + 'multiselect   zoom ' + + ' ' + + '+   sort by: ' + 'name, ' + 'size, ' + 'date, ' + @@ -1007,9 +1068,7 @@ var thegrid = (function () { ev(e); r.thumbs = !r.thumbs; bcfg_set('thumbs', r.thumbs); - if (r.en) { - loadgrid(); - } + r.setdirty(); }; ebi('griden').onclick = function (e) { @@ -1097,6 +1156,9 @@ var thegrid = (function () { } r.loadsel = function () { + if (r.dirty) + return; + var ths = QSA('#ggrid>a'), have_sel = !!QS('#files tr.sel'); @@ -1115,6 +1177,9 @@ var thegrid = (function () { if (have_webp === null) return setTimeout(loadgrid, 50); + lfiles.style.display = 'none'; + gfiles.style.display = 'block'; + if (!r.dirty) return r.loadsel(); @@ -1156,9 +1221,8 @@ var thegrid = (function () { html.push('' + ao.innerHTML + ''); } - lfiles.style.display = 'none'; - gfiles.style.display = 'block'; ebi('ggrid').innerHTML = html.join('\n'); + r.dirty = false; r.bagit(); r.loadsel(); } @@ -1201,7 +1265,10 @@ var thegrid = (function () { function tree_neigh(n) { var links = QSA('#treeul li>a+a'); if (!links.length) { - alert('switch to the tree for that'); + treectl.dir_cb = function () { + tree_neigh(n); + }; + treectl.entree(); return; } var act = -1; @@ -1227,7 +1294,8 @@ function tree_neigh(n) { function tree_up() { var act = QS('#treeul a.hl'); if (!act) { - alert('switch to the tree for that'); + treectl.dir_cb = tree_up; + treectl.entree(); return; } if (act.previousSibling.textContent == '-') @@ -1255,7 +1323,7 @@ document.onkeydown = function (e) { if (n !== 0) return song_skip(n); - if (k == 'KeyM') + if (k == 'KeyP') return playpause(); n = k == 'KeyU' ? -10 : k == 'KeyO' ? 10 : 0; @@ -1266,9 +1334,13 @@ document.onkeydown = function (e) { if (n !== 0) return tree_neigh(n); - if (k == 'KeyP') + if (k == 'KeyM') return tree_up(); + if (k == 'KeyB') + //return treectl.hidden ? treectl.show() : treectl.hide(); + return treectl.hidden ? treectl.entree() : treectl.detree(); + if (k == 'KeyG') return ebi('griden').click(); @@ -1318,6 +1390,7 @@ document.onkeydown = function (e) { } var trs = [], + orig_url = null, orig_html = null; for (var a = 0; a < sconf.length; a++) { @@ -1470,17 +1543,7 @@ document.onkeydown = function (e) { if (ofiles.getAttribute('ts') > this.ts) return; - if (!oldcfg.length) { - oldcfg = [ - ebi('path').style.display, - ebi('tree').style.display, - ebi('wrap').style.marginLeft - ]; - ebi('path').style.display = 'none'; - ebi('tree').style.display = 'none'; - ebi('wrap').style.marginLeft = '0'; - treectl.hidden = true; - } + treectl.hide(); var html = mk_files_header(tagord); html.push('
    '); @@ -1516,25 +1579,23 @@ document.onkeydown = function (e) { html.push(''); } - if (!orig_html) + if (!orig_html || orig_url != get_evpath()) { orig_html = ebi('files').innerHTML; + orig_url = get_evpath(); + } ofiles.innerHTML = html.join('\n'); ofiles.setAttribute("ts", this.ts); - filecols.set_style(); mukey.render(); reload_browser(); + filecols.set_style(['File Name']); ebi('unsearch').onclick = unsearch; } function unsearch(e) { ev(e); - ebi('path').style.display = oldcfg[0]; - ebi('tree').style.display = oldcfg[1]; - ebi('wrap').style.marginLeft = oldcfg[2]; - treectl.hidden = false; - oldcfg = []; + treectl.show(); ebi('files').innerHTML = orig_html; orig_html = null; msel.render(); @@ -1545,8 +1606,9 @@ document.onkeydown = function (e) { var treectl = (function () { var treectl = { - "hidden": false, - "ls_cb": null + "hidden": true, + "ls_cb": null, + "dir_cb": null }, entreed = false, fixedpos = false, @@ -1557,28 +1619,42 @@ var treectl = (function () { treesz = Math.min(Math.max(treesz, 4), 50); - function entree(e) { + treectl.entree = function (e) { ev(e); entreed = true; - ebi('path').style.display = 'none'; - - var tree = ebi('tree'); - tree.style.display = 'block'; - swrite('entreed', 'tree'); + get_tree("", get_evpath(), true); + treectl.show(); + } + + treectl.show = function () { + treectl.hidden = false; + if (!entreed) { + ebi('path').style.display = 'inline-block'; + return; + } + ebi('path').style.display = 'none'; + ebi('tree').style.display = 'block'; window.addEventListener('scroll', onscroll); window.addEventListener('resize', onresize); onresize(); - } + }; - function detree(e) { + treectl.detree = function (e) { ev(e); entreed = false; - ebi('tree').style.display = 'none'; - ebi('path').style.display = 'inline-block'; - ebi('wrap').style.marginLeft = '0'; swrite('entreed', 'na'); + + treectl.hide(); + ebi('path').style.display = 'inline-block'; + } + + treectl.hide = function () { + treectl.hidden = true; + ebi('path').style.display = 'none'; + ebi('tree').style.display = 'none'; + ebi('wrap').style.marginLeft = '0'; window.removeEventListener('resize', onresize); window.removeEventListener('scroll', onscroll); } @@ -1714,6 +1790,12 @@ var treectl = (function () { despin('#tree'); reload_tree(); onresize(); + + var fun = treectl.dir_cb; + if (fun) { + treectl.dir_cb = null; + fun(); + } } function reload_tree() { @@ -1901,13 +1983,13 @@ var treectl = (function () { onresize(); } - ebi('entree').onclick = entree; - ebi('detree').onclick = detree; + ebi('entree').onclick = treectl.entree; + ebi('detree').onclick = treectl.detree; ebi('dyntree').onclick = dyntree; ebi('twig').onclick = scaletree; ebi('twobytwo').onclick = scaletree; if (sread('entreed') == 'tree') - entree(); + treectl.entree(); window.onpopstate = function (e) { console.log("h-pop " + e.state); @@ -2055,9 +2137,12 @@ var filecols = (function () { toggle(t.textContent); } - var set_style = function () { + var set_style = function (unhide) { hidden.sort(); + if (!unhide) + unhide = []; + var html = [], hcols = ebi('hcols'); @@ -2082,7 +2167,7 @@ var filecols = (function () { var name = span[0].textContent, cls = false; - if (has(hidden, name)) { + if (has(hidden, name) && !has(unhide, name)) { ohidden.push(a); cls = true; } @@ -2254,34 +2339,15 @@ function addcrc() { } +var light; (function () { - var tt = bcfg_get('tooltips', true); - - function set_tooltip(e) { - ev(e); - var o = ebi('opdesc'); - o.innerHTML = this.getAttribute('data-desc'); - o.setAttribute('class', tt ? '' : 'off'); - } - - var btns = QSA('#ops, #ops>a'); - for (var a = 0; a < btns.length; a++) { - btns[a].onmouseenter = set_tooltip; - } - - ebi('tooltips').onclick = function (e) { - ev(e); - tt = !tt; - bcfg_set('tooltips', tt); - }; -})(); - - -(function () { - var light = bcfg_get('lightmode', false); + light = bcfg_get('lightmode', false); function freshen() { document.documentElement.setAttribute("class", light ? "light" : ""); + pbar.drawbuf(); + pbar.drawpos(); + vbar.draw(); } ebi('lightmode').onclick = function (e) { @@ -2300,24 +2366,35 @@ var arcfmt = (function () { return { "render": function () { } }; var html = [], - arcfmts = ["tar", "zip", "zip_dos", "zip_crc"], - arcv = ["tar", "zip=utf8", "zip", "zip=crc"]; + fmts = [ + ["tar", "tar", "plain gnutar file"], + ["zip", "zip=utf8", "zip with utf8 filenames (maybe wonky on windows 7 and older)"], + ["zip_dos", "zip", "zip with traditional cp437 filenames, for really old software"], + ["zip_crc", "zip=crc", "cp437 with crc32 computed early for truly ancient software$N(takes longer to process before download can start)"] + ]; - for (var a = 0; a < arcfmts.length; a++) { - var k = arcfmts[a]; + for (var a = 0; a < fmts.length; a++) { + var k = fmts[a][0]; html.push( - '' + - ''); + '' + + ''); } ebi('arc_fmt').innerHTML = html.join('\n'); - var fmt = sread("arc_fmt") || "zip"; + var fmt = sread("arc_fmt"); + if (!ebi('arcfmt_' + fmt)) + fmt = "zip"; + ebi('arcfmt_' + fmt).checked = true; function render() { - var arg = arcv[arcfmts.indexOf(fmt)], + var arg = null, tds = QSA('#files tbody td:first-child a'); + for (var a = 0; a < fmts.length; a++) + if (fmts[a][0] == fmt) + arg = fmts[a][1]; + for (var a = 0, aa = tds.length; a < aa; a++) { var o = tds[a], txt = o.textContent, href = o.getAttribute('href'); if (txt != 'tar' && txt != 'zip') diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js index fb69ad36..b7aec5d4 100644 --- a/copyparty/web/up2k.js +++ b/copyparty/web/up2k.js @@ -1278,31 +1278,11 @@ function up2k_init(subtle) { window.addEventListener('resize', onresize); onresize(); - function desc_show(e) { - var cfg = sread('tooltips'); - if (cfg !== null && cfg != '1') - return; - - var msg = this.getAttribute('alt'), - cdesc = ebi('u2cdesc'); - - cdesc.innerHTML = msg.replace(/\$N/g, "
    "); - cdesc.setAttribute('class', 'show'); - } - function desc_hide(e) { - ebi('u2cdesc').setAttribute('class', ''); - } - var o = QSA('#u2conf *[alt]'); + var o = QSA('#u2conf *[tt]'); for (var a = o.length - 1; a >= 0; a--) { - o[a].parentNode.getElementsByTagName('input')[0].setAttribute('alt', o[a].getAttribute('alt')); - } - var o = QSA('#u2conf *[alt]'); - for (var a = 0; a < o.length; a++) { - o[a].onfocus = desc_show; - o[a].onblur = desc_hide; - o[a].onmouseenter = desc_show; - o[a].onmouseleave = desc_hide; + o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt')); } + tt.init(); function bumpthread(dir) { try { @@ -1450,5 +1430,7 @@ function warn_uploader_busy(e) { } +tt.init(); + if (QS('#op_up2k.act')) goto_up2k(); diff --git a/copyparty/web/upload.css b/copyparty/web/upload.css index 589d4721..0ff5edea 100644 --- a/copyparty/web/upload.css +++ b/copyparty/web/upload.css @@ -211,29 +211,6 @@ box-shadow: none; opacity: .2; } -#u2cdesc { - position: absolute; - width: 34em; - left: calc(50% - 15em); - background: #222; - border: 0 solid #555; - text-align: center; - overflow: hidden; - margin: 0 -2em; - padding: 0 1em; - height: 0; - opacity: .1; - transition: all 0.14s ease-in-out; - box-shadow: 0 .2em .5em #222; - border-radius: .4em; - z-index: 1; -} -#u2cdesc.show { - padding: 1em; - height: auto; - border-width: .2em 0; - opacity: 1; -} #u2foot { color: #fff; font-style: italic; @@ -286,10 +263,6 @@ html.light #u2conf .txtbox.err { background: #f96; color: #300; } -html.light #u2cdesc { - background: #fff; - border: none; -} html.light #op_up2k.srch #u2btn { border-color: #a80; } diff --git a/copyparty/web/upload.html b/copyparty/web/upload.html index ec22fdb0..7d215973 100644 --- a/copyparty/web/upload.html +++ b/copyparty/web/upload.html @@ -39,20 +39,20 @@ {%- if have_up2k_idx %} {%- endif %} @@ -66,8 +66,6 @@
    ', - 'enable

    parallel uploads:
    - + - + - + - +
    -
    -
    @@ -80,11 +78,11 @@
    @@ -92,7 +90,7 @@ - + diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 4427ff33..70dd0d9e 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -528,3 +528,63 @@ function hist_replace(url) { console.log("h-repl " + url); history.replaceState(url, url, url); } + + +var tt = (function () { + var r = { + "tt": mknod("div"), + "en": bcfg_get('tooltips', true), + }; + + r.tt.setAttribute('id', 'tt'); + document.body.appendChild(r.tt); + + function show() { + var cfg = sread('tooltips'); + if (cfg !== null && cfg != '1') + return; + + var msg = this.getAttribute('tt'); + if (!msg) + return; + + var pos = this.getBoundingClientRect(), + left = pos.left < window.innerWidth / 2, + top = pos.top < window.innerHeight / 2; + + r.tt.style.top = top ? pos.bottom + 'px' : 'auto'; + r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px'; + r.tt.style.left = left ? pos.left + 'px' : 'auto'; + r.tt.style.right = left ? 'auto' : (window.innerWidth - pos.right) + 'px'; + + r.tt.innerHTML = msg.replace(/\$N/g, "
    "); + clmod(r.tt, 'show', 1); + } + + function hide() { + clmod(r.tt, 'show'); + } + + r.init = function () { + var _show = r.en ? show : null, + _hide = r.en ? hide : null; + + var o = QSA('*[tt]'); + for (var a = o.length - 1; a >= 0; a--) { + o[a].onfocus = _show; + o[a].onblur = _hide; + o[a].onmouseenter = _show; + o[a].onmouseleave = _hide; + } + hide(); + }; + + ebi('tooltips').onclick = function (e) { + ev(e); + r.en = !r.en; + bcfg_set('tooltips', r.en); + r.init(); + }; + + return r; +})(); diff --git a/scripts/copyparty-repack.sh b/scripts/copyparty-repack.sh index 6ff859eb..dbda26f4 100755 --- a/scripts/copyparty-repack.sh +++ b/scripts/copyparty-repack.sh @@ -92,20 +92,34 @@ chmod 755 \ copyparty-extras/copyparty-*/{scripts,bin}/* -# extract and repack the sfx with less features enabled +# extract the sfx ( cd copyparty-extras/sfx-full/ ./copyparty-sfx.py -h -cd ../copyparty-*/ -./scripts/make-sfx.sh re no-ogv no-cm ) -# put new sfx into copyparty-extras/sfx-lite/, -# fuse client into copyparty-extras/, +repack() { + + # do the repack + (cd copyparty-extras/copyparty-*/ + ./scripts/make-sfx.sh $2 + ) + + # put new sfx into copyparty-extras/$name/, + ( cd copyparty-extras/ + mv copyparty-*/dist/* $1/ + ) +} + +repack sfx-full "re gz no-sh" +repack sfx-lite "re no-ogv no-cm" +repack sfx-lite "re no-ogv no-cm gz no-sh" + + +# move fuse client into copyparty-extras/, # copy lite-sfx.py to ./copyparty, # delete extracted source code ( cd copyparty-extras/ -mv copyparty-*/dist/* sfx-lite/ mv copyparty-*/bin/copyparty-fuse.py . cp -pv sfx-lite/copyparty-sfx.py ../copyparty rm -rf copyparty-{0..9}*.*.*{0..9} @@ -119,6 +133,7 @@ true # create the bundle +printf '\n\n' fn=copyparty-$(date +%Y-%m%d-%H%M%S).tgz tar -czvf "$od/$fn" * cd "$od" diff --git a/scripts/make-sfx.sh b/scripts/make-sfx.sh index 8821a7b0..fab6928c 100755 --- a/scripts/make-sfx.sh +++ b/scripts/make-sfx.sh @@ -11,6 +11,10 @@ echo # `re` does a repack of an sfx which you already executed once # (grabs files from the sfx-created tempdir), overrides `clean` # +# `gz` creates a gzip-compressed python sfx instead of bzip2 +# +# `no-sh` makes just the python sfx, skips the sh/unix sfx +# # `no-ogv` saves ~500k by removing the opus/vorbis audio codecs # (only affects apple devices; everything else has native support) # diff --git a/scripts/test/race.py b/scripts/test/race.py new file mode 100644 index 00000000..09922e60 --- /dev/null +++ b/scripts/test/race.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +import os +import sys +import time +import json +import threading +import http.client + + +class Conn(object): + def __init__(self, ip, port): + self.s = http.client.HTTPConnection(ip, port, timeout=260) + self.st = [] + + def get(self, vpath): + self.st = [time.time()] + + self.s.request("GET", vpath) + self.st.append(time.time()) + + ret = self.s.getresponse() + self.st.append(time.time()) + + if ret.status < 200 or ret.status >= 400: + raise Exception(ret.status) + + ret = ret.read() + self.st.append(time.time()) + + return ret + + def get_json(self, vpath): + ret = self.get(vpath) + return json.loads(ret) + + +class CState(threading.Thread): + def __init__(self, cs): + threading.Thread.__init__(self) + self.daemon = True + self.cs = cs + self.start() + + def run(self): + colors = [5, 1, 3, 2, 7] + remotes = [] + remotes_ok = False + while True: + time.sleep(0.001) + if not remotes_ok: + remotes = [] + remotes_ok = True + for conn in self.cs: + try: + remotes.append(conn.s.sock.getsockname()[1]) + except: + remotes.append("?") + remotes_ok = False + + m = [] + for conn, remote in zip(self.cs, remotes): + stage = len(conn.st) + m.append(f"\033[3{colors[stage]}m{remote}") + + m = " ".join(m) + print(f"{m}\033[0m\n\033[A", end="") + + +def allget(cs, urls): + thrs = [] + for c, url in zip(cs, urls): + t = threading.Thread(target=c.get, args=(url,)) + t.start() + thrs.append(t) + + for t in thrs: + t.join() + + +def main(): + os.system("") + + ip, port = sys.argv[1].split(":") + port = int(port) + + cs = [] + for _ in range(64): + cs.append(Conn(ip, 3923)) + + CState(cs) + + urlbase = "/doujin/c95" + j = cs[0].get_json(f"{urlbase}?ls") + urls = [] + for d in j["dirs"]: + urls.append(f"{urlbase}/{d['href']}?th=w") + + for n in range(100): + print(n) + allget(cs, urls) + + +if __name__ == "__main__": + main()
    filename statusprogresscleanupprogresscleanup