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 @@
{%- if have_zip %}
@@ -62,7 +62,7 @@
- 🌲
+ 🌲
{%- for n in vpnodes %}
{{ n[1] }}
{%- endfor %}
@@ -70,10 +70,10 @@
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 = (
'
' +
'
' +
- '
' +
+ '
' +
'
' +
'
' +
'
' +
@@ -44,15 +44,35 @@ var have_webp = null;
var mpl = (function () {
ebi('op_player').innerHTML = (
+ '
' +
+
'
' +
'
');
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 | '],
+ '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 @@
parallel uploads: |
-
+
|
-
+
|
-
+
|
{%- if have_up2k_idx %}
-
+
|
{%- endif %}
|
@@ -66,8 +66,6 @@
-
-
@@ -80,11 +78,11 @@
@@ -92,7 +90,7 @@
filename |
status |
- progresscleanup |
+ progresscleanup |
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()