Compare commits

...

29 Commits

Author SHA1 Message Date
ed
10362aa02e v0.11.18 2021-06-18 00:30:37 +02:00
ed
0a8e759fe6 v0.11.17 2021-06-17 00:31:38 +02:00
ed
d70981cdd1 fix eq param input 2021-06-17 00:29:14 +02:00
ed
e08c03b886 audio-filters: expose gain control 2021-06-16 22:25:29 +02:00
ed
56086e8984 ux: contrast tweaks + fix anchor-scroll 2021-06-16 21:38:30 +02:00
ed
1aa9033022 add play/pause hotkey 2021-06-16 19:19:29 +02:00
ed
076e103d53 ux: responsive settings layout 2021-06-16 19:10:32 +02:00
ed
38c00ea8fc print thumbnail cleanup summary 2021-06-16 18:57:10 +02:00
ed
415757af43 mention the symlink-scanner too 2021-06-16 18:37:23 +02:00
ed
e72ed8c0ed mention some essentials 2021-06-16 18:29:29 +02:00
ed
32f9c6b5bb v0.11.16 2021-06-16 01:51:18 +02:00
ed
6251584ef6 fix .13dB clipping with all-zero eq 2021-06-15 23:37:44 +00:00
ed
f3e413bc28 icons 2021-06-16 00:01:07 +02:00
ed
6f6cc8f3f8 move eq to the player settings tab 2021-06-15 22:26:39 +02:00
ed
8b081e9e69 media player: continue to next folder 2021-06-15 22:19:53 +02:00
ed
c8a510d10e fully hide columns when minimized 2021-06-15 21:43:37 +02:00
ed
6f834f6679 sticky tree header 2021-06-15 21:07:27 +02:00
ed
cf2d6650ac audio-eq: flatten frequency response 2021-06-15 21:06:00 +02:00
ed
cd52dea488 v0.11.15 2021-06-15 00:01:11 +02:00
ed
6ea75df05d add audio equalizer 2021-06-14 23:58:56 +02:00
ed
4846e1e8d6 mention num.clients for rproxy 2021-06-14 19:27:34 +02:00
ed
fc024f789d v0.11.14 2021-06-14 03:05:50 +02:00
ed
473e773aea fix deadlock 2021-06-14 00:55:11 +00:00
ed
48a2e1a353 add threadwatcher 2021-06-14 01:57:18 +02:00
ed
6da63fbd79 up2k-cli: recover from lost handshakes 2021-06-14 01:01:06 +02:00
ed
5bec37fcee fix cosmetic login glitch 2021-06-14 00:28:08 +02:00
ed
3fd0ba0a31 oh right its the other way around 2021-06-13 22:49:55 +02:00
ed
241a143366 add --rproxy for explicit proxy level 2021-06-13 22:22:31 +02:00
ed
a537064da7 custom-css example to add filetype icons 2021-06-13 00:49:28 +02:00
34 changed files with 1465 additions and 481 deletions

View File

@@ -62,6 +62,14 @@ download [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/do
running the sfx without arguments (for example doubleclicking it on Windows) will give everyone full access to the current folder; see `-h` for help if you want accounts and volumes etc running the sfx without arguments (for example doubleclicking it on Windows) will give everyone full access to the current folder; see `-h` for help if you want accounts and volumes etc
some recommended options:
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
* in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
you may also want these, especially on servers: you may also want these, especially on servers:
* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service * [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https) * [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
@@ -101,7 +109,7 @@ summary: all planned features work! now please enjoy the bloatening
* ☑ FUSE client (read-only) * ☑ FUSE client (read-only)
* browser * browser
* ☑ tree-view * ☑ tree-view
*media player *audio player
* ☑ thumbnails * ☑ thumbnails
* ☑ images using Pillow * ☑ images using Pillow
* ☑ videos using FFmpeg * ☑ videos using FFmpeg
@@ -163,7 +171,7 @@ the browser has the following hotkeys
* `0..9` jump to 10%..90% * `0..9` jump to 10%..90%
* `U/O` skip 10sec back/forward * `U/O` skip 10sec back/forward
* `J/L` prev/next song * `J/L` prev/next song
* `J` also starts playing the folder * `M` play/pause (also starts playing the folder)
* in the grid view: * in the grid view:
* `S` toggle multiselect * `S` toggle multiselect
* `A/D` zoom * `A/D` zoom
@@ -301,7 +309,7 @@ the same arguments can be set as volume flags, in addition to `d2d` and `d2t` fo
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*` * `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
note: note:
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and cause `e2ts` to reindex those * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `cdhash`, this has the following consequences: you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `cdhash`, this has the following consequences:

View File

@@ -1,3 +1,8 @@
# when running copyparty behind a reverse-proxy,
# make sure that copyparty allows at least as many clients as the proxy does,
# so run copyparty with -nc 512 if your nginx has the default limits
# (worker_processes 1, worker_connections 512)
upstream cpp { upstream cpp {
server 127.0.0.1:3923; server 127.0.0.1:3923;
keepalive 120; keepalive 120;

View File

@@ -23,7 +23,7 @@ from textwrap import dedent
from .__init__ import E, WINDOWS, VT100, PY2 from .__init__ import E, WINDOWS, VT100, PY2
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
from .svchub import SvcHub from .svchub import SvcHub
from .util import py_desc, align_tab, IMPLICATIONS from .util import py_desc, align_tab, IMPLICATIONS, alltrace
HAVE_SSL = True HAVE_SSL = True
try: try:
@@ -182,6 +182,16 @@ def sighandler(sig=None, frame=None):
print("\n".join(msg)) print("\n".join(msg))
def stackmon(fp, ival):
ctr = 0
while True:
ctr += 1
time.sleep(ival)
st = "{}, {}\n{}".format(ctr, time.time(), alltrace())
with open(fp, "wb") as f:
f.write(st.encode("utf-8", "replace"))
def run_argparse(argv, formatter): def run_argparse(argv, formatter):
ap = argparse.ArgumentParser( ap = argparse.ArgumentParser(
formatter_class=formatter, formatter_class=formatter,
@@ -222,10 +232,6 @@ def run_argparse(argv, formatter):
"print,get" prints the data in the log and returns GET "print,get" prints the data in the log and returns GET
(leave out the ",get" to return an error instead) (leave out the ",get" to return an error instead)
--ciphers help = available ssl/tls ciphers,
--ssl-ver help = available ssl/tls versions,
default is what python considers safe, usually >= TLS1
values for --ls: values for --ls:
"USR" is a user to browse as; * is anonymous, ** is all users "USR" is a user to browse as; * is anonymous, ** is all users
"VOL" is a single volume to scan, default is * (all vols) "VOL" is a single volume to scan, default is * (all vols)
@@ -244,27 +250,45 @@ def run_argparse(argv, formatter):
) )
# fmt: off # fmt: off
ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file") ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file")
ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
ap.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients") ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores") ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores")
ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account") ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account, USER:PASS; example [ed:wark")
ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume") ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed")
ap.add_argument("-q", action="store_true", help="quiet")
ap.add_argument("-ed", action="store_true", help="enable ?dots") ap.add_argument("-ed", action="store_true", help="enable ?dots")
ap.add_argument("-emp", action="store_true", help="enable markdown plugins") ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate") ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
ap.add_argument("-nih", action="store_true", help="no info hostname")
ap.add_argument("-nid", action="store_true", help="no info disk-usage")
ap.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads") ap.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)") ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms") ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
ap2 = ap.add_argument_group('appearance options') ap2 = ap.add_argument_group('network options')
ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include") ap2.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
ap2.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
ap2 = ap.add_argument_group('SSL/TLS options')
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ssl/tls ciphers; [help] shows available ciphers")
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
ap2 = ap.add_argument_group('opt-outs')
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
ap2.add_argument("-nih", action="store_true", help="no info hostname")
ap2.add_argument("-nid", action="store_true", help="no info disk-usage")
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
ap2 = ap.add_argument_group('safety options')
ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
ap2.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
ap2 = ap.add_argument_group('logging options')
ap2.add_argument("-q", action="store_true", help="quiet")
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
ap2 = ap.add_argument_group('admin panel options') ap2 = ap.add_argument_group('admin panel options')
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)") ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
@@ -299,22 +323,14 @@ def run_argparse(argv, formatter):
ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin") ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline") ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
ap2 = ap.add_argument_group('SSL/TLS options') ap2 = ap.add_argument_group('appearance options')
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls") ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="ssl/tls versions to allow")
ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers")
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
ap2 = ap.add_argument_group('debug options') ap2 = ap.add_argument_group('debug options')
ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes")
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile") ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir") ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing") ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header") ap2.add_argument("--stackmon", metavar="P,S", help="write stacktrace to Path every S second")
ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
return ap.parse_args(args=argv[1:]) return ap.parse_args(args=argv[1:])
# fmt: on # fmt: on
@@ -354,6 +370,16 @@ def main(argv=None):
except AssertionError: except AssertionError:
al = run_argparse(argv, Dodge11874) al = run_argparse(argv, Dodge11874)
if al.stackmon:
fp, f = al.stackmon.rsplit(",", 1)
f = int(f)
t = threading.Thread(
target=stackmon,
args=(fp, f),
)
t.daemon = True
t.start()
# propagate implications # propagate implications
for k1, k2 in IMPLICATIONS: for k1, k2 in IMPLICATIONS:
if getattr(al, k1): if getattr(al, k1):

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 11, 13) VERSION = (0, 11, 18)
CODENAME = "the grid" CODENAME = "the grid"
BUILD_DT = (2021, 6, 12) BUILD_DT = (2021, 6, 18)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -68,6 +68,7 @@ class MpWorker(object):
# self.logw("work: [{}]".format(d[0])) # self.logw("work: [{}]".format(d[0]))
if dest == "shutdown": if dest == "shutdown":
self.httpsrv.shutdown()
self.logw("ok bye") self.logw("ok bye")
sys.exit(0) sys.exit(0)
return return

View File

@@ -25,6 +25,7 @@ class BrokerThr(object):
def shutdown(self): def shutdown(self):
# self.log("broker", "shutting down") # self.log("broker", "shutting down")
self.httpsrv.shutdown()
pass pass
def put(self, want_retval, dest, *args): def put(self, want_retval, dest, *args):

View File

@@ -104,9 +104,20 @@ class HttpCli(object):
v = self.headers.get("connection", "").lower() v = self.headers.get("connection", "").lower()
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0" self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
v = self.headers.get("x-forwarded-for", None) n = self.args.rproxy
if v is not None and self.conn.addr[0] in ["127.0.0.1", "::1"]: if n:
self.ip = v.split(",")[0] v = self.headers.get("x-forwarded-for")
if v and self.conn.addr[0] in ["127.0.0.1", "::1"]:
if n > 0:
n -= 1
vs = v.split(",")
try:
self.ip = vs[n].strip()
except:
self.ip = vs[-1].strip()
self.log("rproxy={} oob x-fwd {}".format(self.args.rproxy, v), c=3)
self.log_src = self.conn.set_rproxy(self.ip) self.log_src = self.conn.set_rproxy(self.ip)
if self.args.ihead: if self.args.ihead:
@@ -245,10 +256,11 @@ class HttpCli(object):
if self.is_rclone: if self.is_rclone:
return "" return ""
cmap = {"pw": "cppwd"}
kv = { kv = {
k: v k: v
for k, v in self.uparam.items() 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) kv.update(add)
if not kv: if not kv:
@@ -570,10 +582,17 @@ class HttpCli(object):
try: try:
dst = os.path.join(vfs.realpath, rem) dst = os.path.join(vfs.realpath, rem)
os.makedirs(fsenc(dst)) os.makedirs(fsenc(dst))
except: except OSError as ex:
if not os.path.isdir(fsenc(dst)): 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(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) x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body)
ret = x.get() ret = x.get()
if sub: if sub:
@@ -758,8 +777,13 @@ class HttpCli(object):
try: try:
os.mkdir(fsenc(fn)) 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: except:
raise Pebkac(500, "mkdir failed, check the logs") raise Pebkac(500, min_ex())
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/") vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
self.redirect(vpath) self.redirect(vpath)
@@ -1176,7 +1200,7 @@ class HttpCli(object):
# #
# send reply # 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.update(NO_CACHE)
self.out_headers["Accept-Ranges"] = "bytes" self.out_headers["Accept-Ranges"] = "bytes"
@@ -1423,33 +1447,8 @@ class HttpCli(object):
if self.args.no_stack: if self.args.no_stack:
raise Pebkac(403, "disabled by argv") raise Pebkac(403, "disabled by argv")
threads = {} ret = "<pre>{}\n{}".format(time.time(), alltrace())
names = dict([(t.ident, t.name) for t in threading.enumerate()]) self.reply(ret.encode("utf-8"))
for tid, stack in sys._current_frames().items():
name = "{} ({:x})".format(names.get(tid), tid)
threads[name] = stack
rret = []
bret = []
for name, stack in sorted(threads.items()):
ret = ["\n\n# {}".format(name)]
pad = None
for fn, lno, name, line in traceback.extract_stack(stack):
fn = os.sep.join(fn.split(os.sep)[-3:])
ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
if line:
ret.append(" " + str(line.strip()))
if "self.not_empty.wait()" in line:
pad = " " * 4
if pad:
bret += [ret[0]] + [pad + x for x in ret[1:]]
else:
rret += ret
ret = rret + bret
ret = ("<pre>" + "\n".join(ret)).encode("utf-8")
self.reply(ret)
def tx_tree(self): def tx_tree(self):
top = self.uparam["tree"] or "" top = self.uparam["tree"] or ""

View File

@@ -43,6 +43,7 @@ class HttpConn(object):
self.ico = Ico(self.args) self.ico = Ico(self.args)
self.t0 = time.time() self.t0 = time.time()
self.stopping = False
self.nbyte = 0 self.nbyte = 0
self.workload = 0 self.workload = 0
self.u2idx = None 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.lf_url = re.compile(self.args.lf_url) if self.args.lf_url else None
self.set_rproxy() 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): def set_rproxy(self, ip=None):
if ip is None: if ip is None:
color = 36 color = 36
@@ -174,7 +183,7 @@ class HttpConn(object):
if not self.sr: if not self.sr:
self.sr = Unrecv(self.s) self.sr = Unrecv(self.s)
while True: while not self.stopping:
if self.is_mp: if self.is_mp:
self.workload += 50 self.workload += 50
if self.workload >= 2 ** 31: if self.workload >= 2 ** 31:

View File

@@ -80,7 +80,14 @@ class HttpSrv(object):
return len(self.clients) return len(self.clients)
def shutdown(self): 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): def thr_client(self, sck, addr):
"""thread managing one tcp client""" """thread managing one tcp client"""
@@ -100,25 +107,35 @@ class HttpSrv(object):
thr.daemon = True thr.daemon = True
thr.start() thr.start()
fno = sck.fileno()
try: try:
if self.args.log_conn: if self.args.log_conn:
self.log("%s %s" % addr, "|%sC-crun" % ("-" * 6,), c="1;30") self.log("%s %s" % addr, "|%sC-crun" % ("-" * 6,), c="1;30")
cli.run() 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: finally:
sck = cli.s sck = cli.s
if self.args.log_conn: if self.args.log_conn:
self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 7,), c="1;30") self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 7,), c="1;30")
try: try:
fno = sck.fileno()
sck.shutdown(socket.SHUT_RDWR) sck.shutdown(socket.SHUT_RDWR)
sck.close() sck.close()
except (OSError, socket.error) as ex: except (OSError, socket.error) as ex:
if not MACOS: if not MACOS:
self.log( self.log(
"%s %s" % addr, "%s %s" % addr,
"shut({}): {}".format(sck.fileno(), ex), "shut({}): {}".format(fno, ex),
c="1;30", c="1;30",
) )
if ex.errno not in [10038, 10054, 107, 57, 9]: if ex.errno not in [10038, 10054, 107, 57, 9]:

View File

@@ -16,6 +16,7 @@ if not PY2:
def have_ff(cmd): def have_ff(cmd):
if PY2: if PY2:
print("# checking {}".format(cmd))
cmd = (cmd + " -version").encode("ascii").split(b" ") cmd = (cmd + " -version").encode("ascii").split(b" ")
try: try:
sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE).communicate() sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE).communicate()

View File

@@ -21,6 +21,7 @@ class TcpSrv(object):
self.log = hub.log self.log = hub.log
self.num_clients = Counter() self.num_clients = Counter()
self.stopping = False
ip = "127.0.0.1" ip = "127.0.0.1"
eps = {ip: "local only"} eps = {ip: "local only"}
@@ -67,7 +68,7 @@ class TcpSrv(object):
ip, port = srv.getsockname() ip, port = srv.getsockname()
self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port)) self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port))
while True: while not self.stopping:
if self.args.log_conn: if self.args.log_conn:
self.log("tcpsrv", "|%sC-ncli" % ("-" * 1,), c="1;30") self.log("tcpsrv", "|%sC-ncli" % ("-" * 1,), c="1;30")
@@ -80,6 +81,9 @@ class TcpSrv(object):
ready, _, _ = select.select(self.srv, [], []) ready, _, _ = select.select(self.srv, [], [])
for srv in ready: for srv in ready:
if self.stopping:
break
sck, addr = srv.accept() sck, addr = srv.accept()
sip, sport = srv.getsockname() sip, sport = srv.getsockname()
if self.args.log_conn: if self.args.log_conn:
@@ -95,6 +99,13 @@ class TcpSrv(object):
self.hub.broker.put(False, "httpconn", sck, addr) self.hub.broker.put(False, "httpconn", sck, addr)
def shutdown(self): def shutdown(self):
self.stopping = True
try:
for srv in self.srv:
srv.close()
except:
pass
self.log("tcpsrv", "ok bye") self.log("tcpsrv", "ok bye")
def detect_interfaces(self, listen_ips): def detect_interfaces(self, listen_ips):

View File

@@ -154,7 +154,8 @@ class ThumbSrv(object):
histpath = self.asrv.vfs.histtab[ptop] histpath = self.asrv.vfs.histtab[ptop]
tpath = thumb_path(histpath, rem, mtime, fmt) tpath = thumb_path(histpath, rem, mtime, fmt)
abspath = os.path.join(ptop, rem) abspath = os.path.join(ptop, rem)
cond = threading.Condition() cond = threading.Condition(self.mutex)
do_conv = False
with self.mutex: with self.mutex:
try: try:
self.busy[tpath].append(cond) self.busy[tpath].append(cond)
@@ -172,6 +173,9 @@ class ThumbSrv(object):
f.write(fsenc(os.path.dirname(abspath))) f.write(fsenc(os.path.dirname(abspath)))
self.busy[tpath] = [cond] self.busy[tpath] = [cond]
do_conv = True
if do_conv:
self.q.put([abspath, tpath]) self.q.put([abspath, tpath])
self.log("conv {} \033[0m{}".format(tpath, abspath), c=6) self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
@@ -181,7 +185,7 @@ class ThumbSrv(object):
break break
with cond: with cond:
cond.wait() cond.wait(3)
try: try:
st = os.stat(tpath) st = os.stat(tpath)
@@ -335,29 +339,32 @@ class ThumbSrv(object):
interval = self.args.th_clean interval = self.args.th_clean
while True: while True:
time.sleep(interval) time.sleep(interval)
ndirs = 0
for vol, histpath in self.asrv.vfs.histtab.items(): for vol, histpath in self.asrv.vfs.histtab.items():
if histpath.startswith(vol): if histpath.startswith(vol):
self.log("\033[Jcln {}/\033[A".format(histpath)) self.log("\033[Jcln {}/\033[A".format(histpath))
else: else:
self.log("\033[Jcln {} ({})/\033[A".format(histpath, vol)) self.log("\033[Jcln {} ({})/\033[A".format(histpath, vol))
self.clean(histpath) ndirs += self.clean(histpath)
self.log("\033[Jcln ok") self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
def clean(self, histpath): def clean(self, histpath):
# self.log("cln {}".format(histpath)) thumbpath = os.path.join(histpath, "th")
# self.log("cln {}".format(thumbpath))
maxage = self.args.th_maxage maxage = self.args.th_maxage
now = time.time() now = time.time()
prev_b64 = None prev_b64 = None
prev_fp = None prev_fp = None
try: try:
ents = os.listdir(histpath) ents = os.listdir(thumbpath)
except: except:
return return 0
ndirs = 0
for f in sorted(ents): for f in sorted(ents):
fp = os.path.join(histpath, f) fp = os.path.join(thumbpath, f)
cmp = fp.lower().replace("\\", "/") cmp = fp.lower().replace("\\", "/")
# "top" or b64 prefix/full (a folder) # "top" or b64 prefix/full (a folder)
@@ -372,10 +379,11 @@ class ThumbSrv(object):
break break
if safe: if safe:
ndirs += 1
self.log("rm -rf [{}]".format(fp)) self.log("rm -rf [{}]".format(fp))
shutil.rmtree(fp, ignore_errors=True) shutil.rmtree(fp, ignore_errors=True)
else: else:
self.clean(fp) ndirs += self.clean(fp)
continue continue
# thumb file # thumb file
@@ -397,3 +405,5 @@ class ThumbSrv(object):
prev_b64 = b64 prev_b64 = b64
prev_fp = fp prev_fp = fp
return ndirs

View File

@@ -16,7 +16,7 @@ import traceback
import subprocess as sp import subprocess as sp
from copy import deepcopy from copy import deepcopy
from .__init__ import WINDOWS, ANYWIN from .__init__ import WINDOWS, ANYWIN, PY2
from .util import ( from .util import (
Pebkac, Pebkac,
Queue, Queue,
@@ -134,7 +134,7 @@ class Up2k(object):
def get_state(self): def get_state(self):
mtpq = 0 mtpq = 0
q = "select count(w) from mt where k = 't:mtp'" 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: if got_lock:
for cur in self.cur.values(): for cur in self.cur.values():
try: try:
@@ -1022,7 +1022,7 @@ class Up2k(object):
now = time.time() now = time.time()
job = None job = None
with self.mutex: with self.mutex:
cur = self.cur.get(cj["ptop"], None) cur = self.cur.get(cj["ptop"])
reg = self.registry[cj["ptop"]] reg = self.registry[cj["ptop"]]
if cur: if cur:
if self.no_expr_idx: if self.no_expr_idx:
@@ -1180,7 +1180,7 @@ class Up2k(object):
def handle_chunk(self, ptop, wark, chash): def handle_chunk(self, ptop, wark, chash):
with self.mutex: with self.mutex:
job = self.registry[ptop].get(wark, None) job = self.registry[ptop].get(wark)
if not job: if not job:
known = " ".join([x for x in self.registry[ptop].keys()]) known = " ".join([x for x in self.registry[ptop].keys()])
self.log("unknown wark [{}], known: {}".format(wark, known)) self.log("unknown wark [{}], known: {}".format(wark, known))
@@ -1245,7 +1245,7 @@ class Up2k(object):
return ret, dst return ret, dst
def idx_wark(self, ptop, wark, rd, fn, lmod, sz): def idx_wark(self, ptop, wark, rd, fn, lmod, sz):
cur = self.cur.get(ptop, None) cur = self.cur.get(ptop)
if not cur: if not cur:
return False return False
@@ -1414,7 +1414,7 @@ class Up2k(object):
newest = max(x["poke"] for _, x in reg.items()) if reg else 0 newest = max(x["poke"] for _, x in reg.items()) if reg else 0
etag = [len(reg), newest] etag = [len(reg), newest]
if etag == prev.get(ptop, None): if etag == prev.get(ptop):
return return
try: try:

View File

@@ -254,6 +254,34 @@ def trace(*args, **kwargs):
nuprint(msg) nuprint(msg)
def alltrace():
threads = {}
names = dict([(t.ident, t.name) for t in threading.enumerate()])
for tid, stack in sys._current_frames().items():
name = "{} ({:x})".format(names.get(tid), tid)
threads[name] = stack
rret = []
bret = []
for name, stack in sorted(threads.items()):
ret = ["\n\n# {}".format(name)]
pad = None
for fn, lno, name, line in traceback.extract_stack(stack):
fn = os.sep.join(fn.split(os.sep)[-3:])
ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
if line:
ret.append(" " + str(line.strip()))
if "self.not_empty.wait()" in line:
pad = " " * 4
if pad:
bret += [ret[0]] + [pad + x for x in ret[1:]]
else:
rret += ret
return "\n".join(rret + bret)
def min_ex(): def min_ex():
et, ev, tb = sys.exc_info() et, ev, tb = sys.exc_info()
tb = traceback.extract_tb(tb, 2) tb = traceback.extract_tb(tb, 2)

View File

@@ -25,6 +25,35 @@ html, body {
body { body {
padding-bottom: 5em; 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,
#path * { #path * {
font-size: 1em; font-size: 1em;
@@ -53,6 +82,7 @@ body {
#files tbody a { #files tbody a {
display: block; display: block;
padding: .3em 0; padding: .3em 0;
scroll-margin-top: 45vh;
} }
#files tbody div a { #files tbody div a {
color: #f5a; color: #f5a;
@@ -68,7 +98,6 @@ a, #files tbody div a:last-child {
text-decoration: underline; text-decoration: underline;
} }
#files thead { #files thead {
background: #333;
position: sticky; position: sticky;
top: 0; top: 0;
} }
@@ -76,29 +105,30 @@ a, #files tbody div a:last-child {
color: #999; color: #999;
font-weight: normal; font-weight: normal;
} }
#files tr:hover { #files tr:hover td {
background: #1c1c1c; background: #1c1c1c;
} }
#files thead th { #files thead th {
padding: .5em 1.3em .3em 1.3em; padding: .5em .3em .3em .3em;
border-right: 2px solid #3c3c3c;
border-bottom: 2px solid #444;
background: #333;
cursor: pointer; cursor: pointer;
} }
#files thead th+th {
border-left: 2px solid #2a2a2a;
}
#files thead th:last-child { #files thead th:last-child {
background: #444; border-right: none;
border-radius: .7em .7em 0 0;
} }
#files thead th:first-child { #files tbody {
background: #222; background: #222;
} }
#files tbody,
#files thead th:nth-child(2) {
background: #222;
border-radius: 0 .7em 0 0;
}
#files td { #files td {
margin: 0; margin: 0;
padding: 0 .5em; padding: 0 .5em;
border-bottom: 1px solid #111; border-bottom: 1px solid #111;
border-left: 1px solid #2c2c2c;
} }
#files td+td+td { #files td+td+td {
max-width: 30em; max-width: 30em;
@@ -185,9 +215,17 @@ a, #files tbody div a:last-child {
margin: -.2em; margin: -.2em;
} }
#files tbody a.play.act { #files tbody a.play.act {
color: #840; color: #720;
text-shadow: 0 0 .3em #b80; 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, #files tbody tr.sel td,
#ggrid a.sel, #ggrid a.sel,
html.light #ggrid a.sel { html.light #ggrid a.sel {
@@ -209,11 +247,17 @@ html.light #ggrid a.sel {
box-shadow: 0 .1em 1.2em #b36; 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 */ 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; opacity: .7;
box-shadow: 0 0 1em #b36;
filter: contrast(130%) brightness(107%); 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 { #files tr.sel a {
color: #fff; color: #fff;
} }
@@ -483,20 +527,48 @@ html.light #ggrid a.sel {
margin: .5em; margin: .5em;
} }
.opview input[type=text] { .opview input[type=text] {
color: #fff;
background: #383838; background: #383838;
color: #fff;
border: none; border: none;
box-shadow: 0 0 .3em #222; box-shadow: 0 0 .3em #222;
border-bottom: 1px solid #fc5; border-bottom: 1px solid #fc5;
border-radius: .2em; border-radius: .2em;
padding: .2em .3em; padding: .2em .3em;
} }
.opview input.err {
background: #a20;
border-color: #f00;
box-shadow: 0 0 .7em #f00;
text-shadow: 1px 1px 0 #500;
outline: none;
}
input[type="checkbox"]+label { input[type="checkbox"]+label {
color: #f5a; color: #f5a;
} }
input[type="checkbox"]:checked+label { input[type="checkbox"]:checked+label {
color: #fc5; color: #fc5;
} }
input.eq_gain {
width: 3em;
text-align: center;
margin: 0 .6em;
}
#audio_eq table {
border-collapse: collapse;
}
#audio_eq td {
text-align: center;
}
#audio_eq a.eq_step {
font-size: 1.5em;
display: block;
padding: 0;
}
#au_eq {
display: block;
margin-top: .5em;
padding: 1.3em .3em;
}
@@ -563,6 +635,7 @@ input[type="checkbox"]:checked+label {
} }
#wrap { #wrap {
margin-top: 2em; margin-top: 2em;
min-height: 90vh;
} }
#tree { #tree {
display: none; display: none;
@@ -575,6 +648,12 @@ input[type="checkbox"]:checked+label {
overscroll-behavior-y: none; overscroll-behavior-y: none;
scrollbar-color: #eb0 #333; scrollbar-color: #eb0 #333;
} }
#treeh {
background: #333;
position: sticky;
z-index: 1;
top: 0;
}
#thx_ff { #thx_ff {
padding: 5em 0; padding: 5em 0;
} }
@@ -600,6 +679,7 @@ input[type="checkbox"]:checked+label {
box-shadow: 0 .1em .2em #222 inset; box-shadow: 0 .1em .2em #222 inset;
border-radius: .3em; border-radius: .3em;
margin: .2em; margin: .2em;
white-space: pre;
position: relative; position: relative;
top: -.2em; top: -.2em;
} }
@@ -667,34 +747,20 @@ input[type="checkbox"]:checked+label {
font-size: 2em; font-size: 2em;
white-space: nowrap; white-space: nowrap;
} }
#files th:hover .cfg, #files th:hover .cfg {
#files th.min .cfg {
display: block; display: block;
width: 1em; width: 1em;
border-radius: .2em; border-radius: .2em;
margin: -1.3em auto 0 auto; margin: -1.3em auto 0 auto;
background: #444; background: #444;
} }
#files th.min .cfg { #files>thead>tr>th.min,
margin: -.6em; #files td.min {
} display: none;
#files>thead>tr>th.min span {
position: absolute;
transform: rotate(270deg);
background: linear-gradient(90deg, rgba(68,68,68,0), rgba(68,68,68,0.5) 70%, #444);
margin-left: -4.6em;
padding: .4em;
top: 5.4em;
width: 8em;
text-align: right;
letter-spacing: .04em;
} }
#files td:nth-child(2n) { #files td:nth-child(2n) {
color: #f5a; color: #f5a;
} }
#files td.min a {
display: none;
}
#files tr.play td, #files tr.play td,
#files tr.play div a { #files tr.play div a {
background: #fc4; background: #fc4;
@@ -709,47 +775,32 @@ input[type="checkbox"]:checked+label {
color: #300; color: #300;
background: #fea; background: #fea;
} }
#op_cfg { .opwide {
max-width: none; max-width: none;
margin-right: 1.5em; margin-right: 1.5em;
} }
#op_cfg>div>a { .opwide>div {
display: inline-block;
vertical-align: top;
border-left: .2em solid #4c4c4c;
margin-left: .5em;
padding-left: .5em;
}
.opwide>div.fill {
display: block;
}
.opwide>div>div>a {
line-height: 2em; line-height: 2em;
} }
#op_cfg>div>span { #op_cfg>div>div>span {
display: inline-block; display: inline-block;
padding: .2em .4em; padding: .2em .4em;
} }
#op_cfg h3 { .opbox h3 {
margin: .8em 0 0 .6em; margin: .8em 0 0 .6em;
padding: 0; padding: 0;
border-bottom: 1px solid #555; 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 { #thumbs {
opacity: .3; opacity: .3;
} }
@@ -804,7 +855,6 @@ html.light #ghead {
content: '📂'; content: '📂';
line-height: 0; line-height: 0;
font-size: 2em; font-size: 2em;
display: inline-block;
margin: -.7em .1em -.5em -.3em; margin: -.7em .1em -.5em -.3em;
} }
#ggrid a:hover { #ggrid a:hover {
@@ -857,6 +907,15 @@ html.light {
background: #eee; background: #eee;
text-shadow: none; 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 #ops,
html.light .opbox, html.light .opbox,
html.light #srch_form { html.light #srch_form {
@@ -919,13 +978,14 @@ html.light #files {
} }
html.light #files thead th { html.light #files thead th {
background: #eee; background: #eee;
border-radius: 0; border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
} }
html.light #files tr td { html.light #files thead th {
border-top: 1px solid #ddd; border-left: 1px solid #f7f7f7;
} }
html.light #files td { html.light #files td {
border-bottom: 1px solid #f7f7f7; border-color: #ddd #fff #fff #ddd;
} }
html.light #files tbody tr:last-child td { html.light #files tbody tr:last-child td {
border-bottom: .2em solid #ccc; border-bottom: .2em solid #ccc;
@@ -933,25 +993,28 @@ html.light #files tbody tr:last-child td {
html.light #files td:nth-child(2n) { html.light #files td:nth-child(2n) {
color: #d38; color: #d38;
} }
html.light #files tr:hover td { html.light #files tr.play td:nth-child(2n) {
background: #fff; color: #c16;
} }
html.light #files tbody a.play { html.light #files tbody a.play {
color: #c0f; color: #c0f;
} }
html.light tr.play td { html.light #files tbody a.play.act {
color: #90c;
}
html.light #files tr.play td {
background: #fc5; background: #fc5;
border-color: #eb1;
}
html.light #files tr:hover td {
background: #fff;
} }
html.light tr.play a { html.light tr.play a {
color: #406; color: #406;
} }
html.light #files th:hover .cfg, html.light #files th:hover .cfg {
html.light #files th.min .cfg {
background: #ccc; background: #ccc;
} }
html.light #files > thead > tr > th.min span {
background: linear-gradient(90deg, rgba(204,204,204,0), rgba(204,204,204,0.5) 70%, #ccc);
}
html.light #blocked { html.light #blocked {
background: #eee; background: #eee;
} }
@@ -961,7 +1024,21 @@ html.light #blk_abrt a {
box-shadow: 0 .2em .4em #ddd; box-shadow: 0 .2em .4em #ddd;
} }
html.light #widget a { html.light #widget a {
color: #fc5; color: #06a;
}
html.light #wtoggle,
html.light #widgeti {
background: #eee;
}
html.light #wtoggle {
box-shadow: 0 0 .5em #bbb;
}
html.light #widget.open {
border-top: .2em solid #f7f7f7;
}
html.light #wzip,
html.light #wnp {
border-color: #ccc;
} }
html.light #files tr.sel:hover td { html.light #files tr.sel:hover td {
background: #c37; background: #c37;
@@ -978,20 +1055,15 @@ html.light #files tr.sel a.play.act {
html.light input[type="checkbox"] + label { html.light input[type="checkbox"] + label {
color: #333; color: #333;
} }
html.light .opwide>div {
border-color: #ccc;
}
html.light .opview input[type="text"] { html.light .opview input[type="text"] {
background: #fff; background: #fff;
color: #333; color: #333;
box-shadow: 0 0 2px #888; box-shadow: 0 0 2px #888;
border-color: #38d; 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 #u2tab a>span,
html.light #files td div span { html.light #files td div span {
color: #000; color: #000;
@@ -1001,9 +1073,6 @@ html.light #path {
text-shadow: none; text-shadow: none;
box-shadow: 0 0 .3em #bbb; box-shadow: 0 0 .3em #bbb;
} }
html.light #path a {
color: #333;
}
html.light #path a:not(:last-child)::after { html.light #path a:not(:last-child)::after {
border-color: #ccc; border-color: #ccc;
background: none; background: none;
@@ -1012,7 +1081,7 @@ html.light #path a:not(:last-child)::after {
} }
html.light #path a:hover { html.light #path a:hover {
background: none; background: none;
color: #60a; color: #90d;
} }
html.light #files tbody div a { html.light #files tbody div a {
color: #d38; color: #d38;
@@ -1022,6 +1091,9 @@ html.light #files tr.sel a:hover {
color: #000; color: #000;
background: #fff; background: #fff;
} }
html.light #treeh {
background: #eee;
}
html.light #tree { html.light #tree {
scrollbar-color: #a70 #ddd; scrollbar-color: #a70 #ddd;
} }

View File

@@ -15,18 +15,19 @@
<body> <body>
<div id="ops"> <div id="ops">
<a href="#" data-dest="" data-desc="close submenu">---</a> <a href="#" data-dest="" tt="close submenu">---</a>
{%- if have_up2k_idx %} {%- if have_up2k_idx %}
<a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,&lt;br /&gt;&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,&lt;br /&gt;&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a> <a href="#" data-perm="read" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,&lt;br /&gt;&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,&lt;br /&gt;&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a>
<a href="#" data-dest="up2k" data-desc="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a> <a href="#" data-dest="up2k" tt="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>
{%- else %} {%- else %}
<a href="#" data-perm="write" data-dest="up2k" data-desc="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a> <a href="#" data-perm="write" data-dest="up2k" tt="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>
{%- endif %} {%- endif %}
<a href="#" data-perm="write" data-dest="bup" data-desc="bup: basic uploader, even supports netscape 4.0">🎈</a> <a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>
<a href="#" data-perm="write" data-dest="mkdir" data-desc="mkdir: create a new directory">📂</a> <a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>
<a href="#" data-perm="read write" data-dest="new_md" data-desc="new-md: create a new markdown document">📝</a> <a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>
<a href="#" data-perm="write" data-dest="msg" data-desc="msg: send a message to the server log">📟</a> <a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>
<a href="#" data-dest="cfg" data-desc="configuration options">⚙️</a> <a href="#" data-dest="player" tt="media player options">🎺</a>
<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>
<div id="opdesc"></div> <div id="opdesc"></div>
</div> </div>
@@ -39,36 +40,41 @@
<div id="srch_q"></div> <div id="srch_q"></div>
</div> </div>
<div id="op_player" class="opview opbox opwide"></div>
{%- include 'upload.html' %} {%- include 'upload.html' %}
<div id="op_cfg" class="opview opbox"> <div id="op_cfg" class="opview opbox opwide">
<div>
<h3>switches</h3> <h3>switches</h3>
<div> <div>
<a id="tooltips" class="tgl btn" href="#">tooltips</a> <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>
<a id="lightmode" class="tgl btn" href="#">lightmode</a> <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>
<a id="griden" class="tgl btn" href="#">the grid</a> <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">the grid</a>
<a id="thumbs" class="tgl btn" href="#">thumbs</a> <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>
</div>
</div> </div>
{%- if have_zip %} {%- if have_zip %}
<h3>folder download</h3> <div><h3>folder download</h3><div id="arc_fmt"></div></div>
<div id="arc_fmt"></div>
{%- endif %} {%- endif %}
<h3>key notation</h3> <div><h3>key notation</h3><div id="key_notation"></div></div>
<div id="key_notation"></div> <div class="fill"><h3>hidden columns</h3><div id="hcols"></div></div>
</div> </div>
<h1 id="path"> <h1 id="path">
<a href="#" id="entree">🌲</a> <a href="#" id="entree" tt="show directory tree$NHotkey: B">🌲</a>
{%- for n in vpnodes %} {%- for n in vpnodes %}
<a href="/{{ n[0] }}">{{ n[1] }}</a> <a href="/{{ n[0] }}">{{ n[1] }}</a>
{%- endfor %} {%- endfor %}
</h1> </h1>
<div id="tree"> <div id="tree">
<a href="#" id="detree">🍞...</a> <div id="treeh">
<a href="#" id="detree" tt="show breadcrumbs$NHotkey: B">🍞...</a>
<a href="#" class="btn" step="2" id="twobytwo">+</a> <a href="#" class="btn" step="2" id="twobytwo">+</a>
<a href="#" class="btn" step="-2" id="twig">&ndash;</a> <a href="#" class="btn" step="-2" id="twig">&ndash;</a>
<a href="#" class="tgl btn" id="dyntree">a</a> <a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>
</div>
<ul id="treeul"></ul> <ul id="treeul"></ul>
<div id="thx_ff">&nbsp;</div> <div id="thx_ff">&nbsp;</div>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
var ofun = audio_eq.apply.bind(audio_eq);
audio_eq.apply = function () {
var ac1 = mp.ac;
ofun();
var ac = mp.ac,
w = 2048,
h = 256;
if (!audio_eq.filters.length) {
audio_eq.ana = null;
return;
}
var can = ebi('fft_can');
if (!can) {
can = mknod('canvas');
can.setAttribute('id', 'fft_can');
can.style.cssText = 'position:absolute;left:0;bottom:5em;width:' + w + 'px;height:' + h + 'px;z-index:9001';
document.body.appendChild(can);
can.width = w;
can.height = h;
}
var cc = can.getContext('2d');
if (!ac)
return;
var ana = ac.createAnalyser();
ana.smoothingTimeConstant = 0;
ana.fftSize = 8192;
audio_eq.filters[0].connect(ana);
audio_eq.ana = ana;
var buf = new Uint8Array(ana.frequencyBinCount),
colw = can.width / buf.length;
cc.fillStyle = '#fc0';
function draw() {
if (ana == audio_eq.ana)
requestAnimationFrame(draw);
ana.getByteFrequencyData(buf);
cc.clearRect(0, 0, can.width, can.height);
/*var x = 0, w = 1;
for (var a = 0; a < buf.length; a++) {
cc.fillRect(x, h - buf[a], w, h);
x += w;
}*/
var mul = Math.pow(w, 4) / buf.length;
for (var x = 0; x < w; x++) {
var a = Math.floor(Math.pow(x, 4) / mul),
v = buf[a];
cc.fillRect(x, h - v, 1, v);
}
}
draw();
};
audio_eq.apply();

View File

@@ -59,7 +59,7 @@
<h1>login for more:</h1> <h1>login for more:</h1>
<ul> <ul>
<form method="post" enctype="multipart/form-data" action="/{{ url_suf }}"> <form method="post" enctype="multipart/form-data" action="/">
<input type="hidden" name="act" value="login" /> <input type="hidden" name="act" value="login" />
<input type="password" name="cppwd" /> <input type="password" name="cppwd" />
<input type="submit" value="Login" /> <input type="submit" value="Login" />

View File

@@ -804,6 +804,14 @@ function up2k_init(subtle) {
var mou_ikkai = false; var mou_ikkai = false;
if (st.busy.handshake.length > 0 &&
st.busy.handshake[0].busied < Date.now() - 30 * 1000
) {
console.log("retrying stuck handshake");
var t = st.busy.handshake.shift();
st.todo.handshake.unshift(t);
}
if (st.todo.handshake.length > 0 && if (st.todo.handshake.length > 0 &&
st.busy.handshake.length == 0 && ( st.busy.handshake.length == 0 && (
st.todo.handshake[0].t4 || ( st.todo.handshake[0].t4 || (
@@ -1019,11 +1027,27 @@ function up2k_init(subtle) {
// //
function exec_handshake() { function exec_handshake() {
var t = st.todo.handshake.shift(); var t = st.todo.handshake.shift(),
me = Date.now();
st.busy.handshake.push(t); st.busy.handshake.push(t);
t.busied = me;
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onerror = function () {
if (t.busied != me) {
console.log('zombie handshake onerror,', t);
return;
}
console.log('handshake onerror, retrying');
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
st.todo.handshake.unshift(t);
};
xhr.onload = function (e) { xhr.onload = function (e) {
if (t.busied != me) {
console.log('zombie handshake onload,', t);
return;
}
if (xhr.status == 200) { if (xhr.status == 200) {
var response = JSON.parse(xhr.responseText); var response = JSON.parse(xhr.responseText);
@@ -1254,31 +1278,11 @@ function up2k_init(subtle) {
window.addEventListener('resize', onresize); window.addEventListener('resize', onresize);
onresize(); onresize();
function desc_show(e) { var o = QSA('#u2conf *[tt]');
var cfg = sread('tooltips');
if (cfg !== null && cfg != '1')
return;
var msg = this.getAttribute('alt'),
cdesc = ebi('u2cdesc');
cdesc.innerHTML = msg.replace(/\$N/g, "<br />");
cdesc.setAttribute('class', 'show');
}
function desc_hide(e) {
ebi('u2cdesc').setAttribute('class', '');
}
var o = QSA('#u2conf *[alt]');
for (var a = o.length - 1; a >= 0; a--) { for (var a = o.length - 1; a >= 0; a--) {
o[a].parentNode.getElementsByTagName('input')[0].setAttribute('alt', o[a].getAttribute('alt')); o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt'));
}
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;
} }
tt.init();
function bumpthread(dir) { function bumpthread(dir) {
try { try {
@@ -1426,5 +1430,7 @@ function warn_uploader_busy(e) {
} }
tt.init();
if (QS('#op_up2k.act')) if (QS('#op_up2k.act'))
goto_up2k(); goto_up2k();

View File

@@ -211,29 +211,6 @@
box-shadow: none; box-shadow: none;
opacity: .2; 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 { #u2foot {
color: #fff; color: #fff;
font-style: italic; font-style: italic;
@@ -286,10 +263,6 @@ html.light #u2conf .txtbox.err {
background: #f96; background: #f96;
color: #300; color: #300;
} }
html.light #u2cdesc {
background: #fff;
border: none;
}
html.light #op_up2k.srch #u2btn { html.light #op_up2k.srch #u2btn {
border-color: #a80; border-color: #a80;
} }

View File

@@ -39,20 +39,20 @@
<td><br />parallel uploads:</td> <td><br />parallel uploads:</td>
<td rowspan="2"> <td rowspan="2">
<input type="checkbox" id="multitask" /> <input type="checkbox" id="multitask" />
<label for="multitask" alt="continue hashing other files while uploading">🏃</label> <label for="multitask" tt="continue hashing other files while uploading">🏃</label>
</td> </td>
<td rowspan="2"> <td rowspan="2">
<input type="checkbox" id="ask_up" /> <input type="checkbox" id="ask_up" />
<label for="ask_up" alt="ask for confirmation befofre upload starts">💭</label> <label for="ask_up" tt="ask for confirmation befofre upload starts">💭</label>
</td> </td>
<td rowspan="2"> <td rowspan="2">
<input type="checkbox" id="flag_en" /> <input type="checkbox" id="flag_en" />
<label for="flag_en" alt="ensure only one tab is uploading at a time $N (other tabs must have this enabled too)">💤</label> <label for="flag_en" tt="ensure only one tab is uploading at a time $N (other tabs must have this enabled too)">💤</label>
</td> </td>
{%- if have_up2k_idx %} {%- if have_up2k_idx %}
<td data-perm="read" rowspan="2"> <td data-perm="read" rowspan="2">
<input type="checkbox" id="fsearch" /> <input type="checkbox" id="fsearch" />
<label for="fsearch" alt="don't actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)">🔎</label> <label for="fsearch" tt="don't actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)">🔎</label>
</td> </td>
{%- endif %} {%- endif %}
<td data-perm="read" rowspan="2" id="u2btn_cw"></td> <td data-perm="read" rowspan="2" id="u2btn_cw"></td>
@@ -66,8 +66,6 @@
</tr> </tr>
</table> </table>
<div id="u2cdesc"></div>
<div id="u2notbtn"></div> <div id="u2notbtn"></div>
<div id="u2btn_ct"> <div id="u2btn_ct">
@@ -80,11 +78,11 @@
</div> </div>
<div id="u2cards"> <div id="u2cards">
<a href="#" act="ok">ok <span>0</span></a><a <a href="#" act="ok" tt="completed successfully">ok <span>0</span></a><a
href="#" act="ng">ng <span>0</span></a><a href="#" act="ng" tt="failed / rejected / not-found">ng <span>0</span></a><a
href="#" act="done">done <span>0</span></a><a href="#" act="done" tt="ok and ng combined">done <span>0</span></a><a
href="#" act="bz" class="act">busy <span>0</span></a><a href="#" act="bz" tt="hashing or uploading" class="act">busy <span>0</span></a><a
href="#" act="q">que <span>0</span></a> href="#" act="q" tt="idle, pending">que <span>0</span></a>
</div> </div>
<table id="u2tab"> <table id="u2tab">
@@ -92,7 +90,7 @@
<tr> <tr>
<td>filename</td> <td>filename</td>
<td>status</td> <td>status</td>
<td>progress<a href="#" id="u2cleanup">cleanup</a></td> <td>progress<a href="#" id="u2cleanup" tt="remove completed uploads$N(makes it possible to upload a file after searching for it)">cleanup</a></td>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>

View File

@@ -528,3 +528,63 @@ function hist_replace(url) {
console.log("h-repl " + url); console.log("h-repl " + url);
history.replaceState(url, url, 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, "<br />");
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;
})();

View File

@@ -1,8 +1,19 @@
## [`minimal-up2k.html`](minimal-up2k.html) # example `.epilogue.html`
* save as `.epilogue.html` inside a folder to [simplify the ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png) save one of these as `.epilogue.html` inside a folder to customize it:
## [`browser.css`](browser.css) * [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
* example for `--css-browser`
# example browser-css
point `--css-browser` to one of these by URL:
* [`browser.css`](browser.css) changes the background
* [`browser-icons.css`](browser-icons.css) adds filetype icons
# other stuff
## [`rclone.md`](rclone.md) ## [`rclone.md`](rclone.md)
* notes on using rclone as a fuse client/server * notes on using rclone as a fuse client/server

95
docs/biquad.html Normal file
View File

@@ -0,0 +1,95 @@
<!DOCTYPE html><html><head></head><body><script>
setTimeout(location.reload.bind(location), 700);
document.documentElement.scrollLeft = 0;
var can = document.createElement('canvas'),
cc = can.getContext('2d'),
w = 2048,
h = 1024;
w = 2048;
can.width = w;
can.height = h;
document.body.appendChild(can);
can.style.cssText = 'width:' + w + 'px;height:' + h + 'px';
cc.fillStyle = '#000';
cc.fillRect(0, 0, w, h);
var cfg = [ // hz, q, g
[31.25 * 0.88, 0, 1.4], // shelf
[31.25 * 1.04, 0.7, 0.96], // peak
[62.5, 0.7, 1],
[125, 0.8, 1],
[250, 0.9, 1.03],
[500, 0.9, 1.1],
[1000, 0.9, 1.1],
[2000, 0.9, 1.105],
[4000, 0.88, 1.05],
[8000 * 1.006, 0.73, 1.24],
//[16000 * 1.00, 0.5, 1.75], // peak.v1
//[16000 * 1.19, 0, 1.8] // shelf.v1
[16000 * 0.89, 0.7, 1.26], // peak
[16000 * 1.13, 0.82, 1.09], // peak
[16000 * 1.205, 0, 1.9] // shelf
];
var freqs = new Float32Array(22000),
sum = new Float32Array(freqs.length),
ac = new AudioContext(),
step = w / freqs.length,
colors = [
'rgba(255, 0, 0, 0.7)',
'rgba(0, 224, 0, 0.7)',
'rgba(0, 64, 255, 0.7)'
];
var order = [];
for (var a = 0; a < cfg.length; a += 2)
order.push(a);
for (var a = 1; a < cfg.length; a += 2)
order.push(a);
for (var ia = 0; ia < order.length; ia++) {
var a = order[ia],
fi = ac.createBiquadFilter(),
mag = new Float32Array(freqs.length),
phase = new Float32Array(freqs.length);
for (var b = 0; b < freqs.length; b++)
freqs[b] = b;
fi.type = a == 0 ? 'lowshelf' : a == cfg.length - 1 ? 'highshelf' : 'peaking';
fi.frequency.value = cfg[a][0];
fi.Q.value = cfg[a][1];
fi.gain.value = 1;
fi.getFrequencyResponse(freqs, mag, phase);
cc.fillStyle = colors[a % colors.length];
for (var b = 0; b < sum.length; b++) {
mag[b] -= 1;
sum[b] += mag[b] * cfg[a][2];
var y = h - (mag[b] * h * 3);
cc.fillRect(b * step, y, step, h - y);
cc.fillRect(b * step - 1, y - 1, 3, 3);
}
}
var min = 999999, max = 0;
for (var a = 0; a < sum.length; a++) {
min = Math.min(min, sum[a]);
max = Math.max(max, sum[a]);
}
cc.fillStyle = 'rgba(255,255,255,1)';
for (var a = 0; a < sum.length; a++) {
var v = (sum[a] - min) / (max - min);
cc.fillRect(a * step, 0, step, v * h / 2);
}
cc.fillRect(0, 460, w, 1);
</script></body></html>

68
docs/browser-icons.css Normal file
View File

@@ -0,0 +1,68 @@
/* put filetype icons inline with text
#ggrid>a>span:before,
#ggrid>a>span.dir:before {
display: inline;
line-height: 0;
font-size: 1.7em;
margin: -.7em .1em -.5em -.6em;
}
*/
/* move folder icons top-left */
#ggrid>a>span.dir:before {
content: initial;
}
#ggrid>a[href$="/"]:before {
content: '📂';
display: block;
position: absolute;
margin: -.1em -.4em;
text-shadow: 0 0 .1em #000;
font-size: 2em;
}
/* put filetype icons top-left */
#ggrid>a:before {
display: block;
position: absolute;
margin: -.1em -.4em;
text-shadow: 0 0 .1em #000;
font-size: 2em;
}
/* video */
#ggrid>a:is(
[href$=".mkv"i],
[href$=".mp4"i],
[href$=".webm"i],
):before {
content: '📺';
}
/* audio */
#ggrid>a:is(
[href$=".mp3"i],
[href$=".ogg"i],
[href$=".opus"i],
[href$=".flac"i],
[href$=".m4a"i],
[href$=".aac"i],
):before {
content: '🎵';
}
/* image */
#ggrid>a:is(
[href$=".jpg"i],
[href$=".jpeg"i],
[href$=".png"i],
[href$=".gif"i],
[href$=".webp"i],
):before {
content: '🎨';
}

View File

@@ -1,5 +1,5 @@
html { html {
background: url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed; background: #333 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
} }
#files th { #files th {
background: rgba(32, 32, 32, 0.9) !important; background: rgba(32, 32, 32, 0.9) !important;
@@ -12,7 +12,7 @@ html {
html.light { html.light {
background: url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed; background: #eee url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
} }
html.light #files th { html.light #files th {
background: rgba(255, 255, 255, 0.9) !important; background: rgba(255, 255, 255, 0.9) !important;

View File

@@ -86,6 +86,9 @@ var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.quer
# get the size and video-id of all youtube vids in folder, assuming filename ends with -id.ext, and create a copyparty search query # get the size and video-id of all youtube vids in folder, assuming filename ends with -id.ext, and create a copyparty search query
find -maxdepth 1 -printf '%s %p\n' | sort -n | awk '!/-([0-9a-zA-Z_-]{11})\.(mkv|mp4|webm)$/{next} {sub(/\.[^\.]+$/,"");n=length($0);v=substr($0,n-10);print $1, v}' | tee /dev/stderr | awk 'BEGIN {p="("} {printf("%s name like *-%s.* ",p,$2);p="or"} END {print ")\n"}' | cat >&2 find -maxdepth 1 -printf '%s %p\n' | sort -n | awk '!/-([0-9a-zA-Z_-]{11})\.(mkv|mp4|webm)$/{next} {sub(/\.[^\.]+$/,"");n=length($0);v=substr($0,n-10);print $1, v}' | tee /dev/stderr | awk 'BEGIN {p="("} {printf("%s name like *-%s.* ",p,$2);p="or"} END {print ")\n"}' | cat >&2
# unique stacks in a stackdump
f=a; rm -rf stacks; mkdir stacks; grep -E '^#' $f | while IFS= read -r n; do awk -v n="$n" '!$0{o=0} o; $0==n{o=1}' <$f >stacks/f; h=$(sha1sum <stacks/f | cut -c-16); mv stacks/f stacks/$h-"$n"; done ; find stacks/ | sort | uniq -cw24
## ##
## sqlite3 stuff ## sqlite3 stuff

32
docs/tcp-debug.sh Normal file
View File

@@ -0,0 +1,32 @@
(cd ~/dev/copyparty && strace -Tttyyvfs 256 -o strace.strace python3 -um copyparty -i 127.0.0.1 --http-only --stackmon /dev/shm/cpps,10 ) 2>&1 | tee /dev/stderr > ~/log-copyparty-$(date +%Y-%m%d-%H%M%S).txt
14/Jun/2021:16:34:02 1623688447.212405 death
14/Jun/2021:16:35:02 1623688502.420860 back
tcpdump -nni lo -w /home/ed/lo.pcap
# 16:35:25.324662 IP 127.0.0.1.48632 > 127.0.0.1.3920: Flags [F.], seq 849, ack 544, win 359, options [nop,nop,TS val 809396796 ecr 809396796], length 0
tcpdump -nnr /home/ed/lo.pcap | awk '/ > 127.0.0.1.3920: /{sub(/ > .*/,"");sub(/.*\./,"");print}' | sort -n | uniq | while IFS= read -r port; do echo; tcpdump -nnr /home/ed/lo.pcap 2>/dev/null | grep -E "\.$port( > |: F)" | sed -r 's/ > .*, /, /'; done | grep -E '^16:35:0.*length [^0]' -C50
16:34:02.441732 IP 127.0.0.1.48638, length 0
16:34:02.441738 IP 127.0.0.1.3920, length 0
16:34:02.441744 IP 127.0.0.1.48638, length 0
16:34:02.441756 IP 127.0.0.1.48638, length 791
16:34:02.441759 IP 127.0.0.1.3920, length 0
16:35:02.445529 IP 127.0.0.1.48638, length 0
16:35:02.489194 IP 127.0.0.1.3920, length 0
16:35:02.515595 IP 127.0.0.1.3920, length 216
16:35:02.515600 IP 127.0.0.1.48638, length 0
grep 48638 "$(find ~ -maxdepth 1 -name log-copyparty-\*.txt | sort | tail -n 1)"
1623688502.510380 48638 rh
1623688502.511291 48638 Unrecv direct ...
1623688502.511827 48638 rh = 791
16:35:02.518 127.0.0.1 48638 shut(8): [Errno 107] Socket not connected
Exception in thread httpsrv-0.1-48638:
grep 48638 ~/dev/copyparty/strace.strace
14561 16:35:02.506310 <... accept4 resumed> {sa_family=AF_INET, sin_port=htons(48638), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC) = 8<TCP:[127.0.0.1:3920->127.0.0.1:48638]> <0.000012>
15230 16:35:02.510725 write(1<pipe:[256639555]>, "1623688502.510380 48638 rh\n", 27 <unfinished ...>

View File

@@ -92,20 +92,34 @@ chmod 755 \
copyparty-extras/copyparty-*/{scripts,bin}/* copyparty-extras/copyparty-*/{scripts,bin}/*
# extract and repack the sfx with less features enabled # extract the sfx
( cd copyparty-extras/sfx-full/ ( cd copyparty-extras/sfx-full/
./copyparty-sfx.py -h ./copyparty-sfx.py -h
cd ../copyparty-*/
./scripts/make-sfx.sh re no-ogv no-cm
) )
# put new sfx into copyparty-extras/sfx-lite/, repack() {
# fuse client into copyparty-extras/,
# 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, # copy lite-sfx.py to ./copyparty,
# delete extracted source code # delete extracted source code
( cd copyparty-extras/ ( cd copyparty-extras/
mv copyparty-*/dist/* sfx-lite/
mv copyparty-*/bin/copyparty-fuse.py . mv copyparty-*/bin/copyparty-fuse.py .
cp -pv sfx-lite/copyparty-sfx.py ../copyparty cp -pv sfx-lite/copyparty-sfx.py ../copyparty
rm -rf copyparty-{0..9}*.*.*{0..9} rm -rf copyparty-{0..9}*.*.*{0..9}
@@ -119,6 +133,7 @@ true
# create the bundle # create the bundle
printf '\n\n'
fn=copyparty-$(date +%Y-%m%d-%H%M%S).tgz fn=copyparty-$(date +%Y-%m%d-%H%M%S).tgz
tar -czvf "$od/$fn" * tar -czvf "$od/$fn" *
cd "$od" cd "$od"

View File

@@ -11,6 +11,10 @@ echo
# `re` does a repack of an sfx which you already executed once # `re` does a repack of an sfx which you already executed once
# (grabs files from the sfx-created tempdir), overrides `clean` # (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 # `no-ogv` saves ~500k by removing the opus/vorbis audio codecs
# (only affects apple devices; everything else has native support) # (only affects apple devices; everything else has native support)
# #
@@ -167,7 +171,7 @@ find .. -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
echo use smol web deps echo use smol web deps
rm -f copyparty/web/deps/*.full.* copyparty/web/Makefile rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
# it's fine dw # it's fine dw
grep -lE '\.full\.(js|css)' copyparty/web/* | grep -lE '\.full\.(js|css)' copyparty/web/* |

105
scripts/test/race.py Normal file
View File

@@ -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()

View File

@@ -28,6 +28,7 @@ class Cfg(Namespace):
a=a, a=a,
v=v, v=v,
c=c, c=c,
rproxy=0,
ed=False, ed=False,
no_zip=False, no_zip=False,
no_scandir=False, no_scandir=False,

View File

@@ -24,6 +24,7 @@ class Cfg(Namespace):
"hist": None, "hist": None,
"no_hash": False, "no_hash": False,
"css_browser": None, "css_browser": None,
"rproxy": 0,
} }
ex.update(ex2) ex.update(ex2)
super(Cfg, self).__init__(a=a, v=v, c=c, **ex) super(Cfg, self).__init__(a=a, v=v, c=c, **ex)