Compare commits

...

26 Commits

Author SHA1 Message Date
ed
edbedcdad3 v1.1.3 2021-11-20 02:27:09 +01:00
ed
e4ae5f74e6 add tooltip indicator 2021-11-20 01:47:16 +01:00
ed
2c7ffe08d7 include sha512 as both hex and b64 in responses 2021-11-20 01:03:32 +01:00
ed
3ca46bae46 good oneliner 2021-11-20 00:20:34 +01:00
ed
7e82aaf843 simplify/improve up2k ui debounce 2021-11-20 00:03:15 +01:00
ed
315bd71adf limit turbo runahead 2021-11-20 00:01:14 +01:00
ed
2c612c9aeb ux 2021-11-19 21:31:05 +01:00
ed
36aee085f7 add timeouts to FFmpeg things 2021-11-16 22:22:09 +01:00
ed
d01bb69a9c u2cli: option to ignore inaccessible files 2021-11-16 21:53:00 +01:00
ed
c9b1c48c72 sizelimit registry + persist without e2d 2021-11-16 21:31:24 +01:00
ed
aea3843cf2 this is just noise 2021-11-16 21:28:50 +01:00
ed
131b6f4b9a workaround chrome rendering bug 2021-11-16 21:28:36 +01:00
ed
6efb8b735a better handling of python builds without sqlite3 2021-11-16 01:13:04 +01:00
ed
223b7af2ce more iOS jank 2021-11-16 00:05:35 +01:00
ed
e72c2a6982 add fastpath for using the eq as a pure gain control 2021-11-15 23:19:43 +01:00
ed
dd9b93970e autoenable aac transcoding when codec missing 2021-11-15 23:18:52 +01:00
ed
e4c7cd81a9 update readme 2021-11-15 20:28:53 +01:00
ed
12b3a62586 fix dumb mistakes 2021-11-15 20:13:16 +01:00
ed
2da3bdcd47 delay tooltips, fix #6 2021-11-15 03:56:17 +01:00
ed
c1dccbe0ba trick iphones into preloading natively 2021-11-15 03:01:11 +01:00
ed
9629fcde68 optionally enable seeking through os controls 2021-11-15 02:47:42 +01:00
ed
cae436b566 add client-option to disconnect on HTTP 304 2021-11-15 02:45:18 +01:00
ed
01714700ae more gapless fixes 2021-11-14 20:25:28 +01:00
ed
51e6c4852b retire ogvjs 2021-11-14 19:28:44 +01:00
ed
b206c5d64e handle multiple simultaneous uploads of the same file 2021-11-14 15:03:11 +01:00
ed
62c3272351 add option to simulate latency 2021-11-14 15:01:20 +01:00
23 changed files with 617 additions and 458 deletions

View File

@@ -78,6 +78,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [sfx](#sfx) - there are two self-contained "binaries"
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
* [install on android](#install-on-android)
* [reporting bugs](#reporting-bugs) - ideas for context to include in bug reports
* [building](#building)
* [dev env setup](#dev-env-setup)
* [just the sfx](#just-the-sfx)
@@ -161,9 +162,11 @@ feature summary
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
* ☑ audio player (with OS media controls and opus transcoding)
* ☑ image gallery with webm player
* ☑ textfile browser with syntax hilighting
* ☑ [thumbnails](#thumbnails)
* ☑ ...of images using Pillow
* ☑ ...of videos using FFmpeg
* ☑ ...of audio (spectrograms) using FFmpeg
* ☑ cache eviction (max-age; maybe max-size eventually)
* ☑ SPA (browse while uploading)
* if you use the navpane to navigate, not folders in the file list
@@ -233,6 +236,10 @@ some improvement ideas
## not my bugs
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
* "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day...
* Windows: folders cannot be accessed if the name ends with `.`
* python or windows bug
@@ -249,6 +256,7 @@ some improvement ideas
* is it possible to block read-access to folders unless you know the exact URL for a particular file inside?
* yes, using the [`g` permission](#accounts-and-volumes), see the examples there
* you can also do this with linux filesystem permissions; `chmod 111 music` will make it possible to access files and folders inside the `music` folder but not list the immediate contents -- also works with other software, not just copyparty
* can I make copyparty download a file to my server if I give it a URL?
* not officially, but there is a [terrible hack](https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/wget.py) which makes it possible
@@ -784,7 +792,7 @@ TLDR: yes
* internet explorer 6 to 8 behave the same
* firefox 52 and chrome 49 are the final winxp versions
* `*1` yes, but extremely slow (ie10: `1 MiB/s`, ie11: `270 KiB/s`)
* `*3` using a wasm decoder which consumes a bit more power
* `*3` iOS 11 and newer, opus only, and requires FFmpeg on the server
quick summary of more eccentric web-browsers trying to view a directory index:
@@ -973,6 +981,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
| GET | `?txt=iso-8859-1` | ...with specific charset |
| GET | `?th` | get image/video at URL as thumbnail |
| GET | `?th=opus` | convert audio file to 128kbps opus |
| GET | `?th=caf` | ...in the iOS-proprietary container |
| method | body | result |
|--|--|--|
@@ -1068,13 +1077,11 @@ pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `
reduce the size of an sfx by removing features
if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
* `584k` size of original sfx.py as of v1.1.0
* `392k` after `./scripts/make-sfx.sh re no-ogv`
* `310k` after `./scripts/make-sfx.sh re no-ogv no-cm`
* `269k` after `./scripts/make-sfx.sh re no-ogv no-cm no-hl`
* `393k` size of original sfx.py as of v1.1.3
* `310k` after `./scripts/make-sfx.sh re no-cm`
* `269k` after `./scripts/make-sfx.sh re no-cm no-hl`
the features you can opt to drop are
* `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files, saves ~192k
* `cm`/easymde, the "fancy" markdown editor, saves ~82k
* `hl`, prism, the syntax hilighter, saves ~41k
* `fnt`, source-code-pro, the monospace font, saves ~9k
@@ -1082,7 +1089,7 @@ the features you can opt to drop are
for the `re`pack to work, first run one of the sfx'es once to unpack it
**note:** you can also just download and run [scripts/copyparty-repack.sh](scripts/copyparty-repack.sh) -- this will grab the latest copyparty release from github and do a `no-ogv no-cm` repack; works on linux/macos (and windows with msys2 or WSL)
**note:** you can also just download and run [scripts/copyparty-repack.sh](scripts/copyparty-repack.sh) -- this will grab the latest copyparty release from github and do a few repacks; works on linux/macos (and windows with msys2 or WSL)
# install on android
@@ -1096,6 +1103,16 @@ echo $?
after the initial setup, you can launch copyparty at any time by running `copyparty` anywhere in Termux
# reporting bugs
ideas for context to include in bug reports
if something broke during an upload (replacing FILENAME with a part of the filename that broke):
```
journalctl -aS '48 hour ago' -u copyparty | grep -C10 FILENAME | tee bug.log
```
# building
## dev env setup

View File

@@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals
"""
up2k.py: upload to copyparty
2021-10-31, v0.11, ed <irc.rizon.net>, MIT-Licensed
2021-11-16, v0.12, ed <irc.rizon.net>, MIT-Licensed
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
- dependencies: requests
@@ -224,29 +224,47 @@ class CTermsize(object):
ss = CTermsize()
def statdir(top):
def _scd(err, top):
"""non-recursive listing of directory contents, along with stat() info"""
if hasattr(os, "scandir"):
with os.scandir(top) as dh:
for fh in dh:
yield [os.path.join(top, fh.name), fh.stat()]
else:
for name in os.listdir(top):
abspath = os.path.join(top, name)
with os.scandir(top) as dh:
for fh in dh:
abspath = os.path.join(top, fh.name)
try:
yield [abspath, fh.stat()]
except:
err.append(abspath)
def _lsd(err, top):
"""non-recursive listing of directory contents, along with stat() info"""
for name in os.listdir(top):
abspath = os.path.join(top, name)
try:
yield [abspath, os.stat(abspath)]
except:
err.append(abspath)
def walkdir(top):
if hasattr(os, "scandir"):
statdir = _scd
else:
statdir = _lsd
def walkdir(err, top):
"""recursive statdir"""
for ap, inf in sorted(statdir(top)):
for ap, inf in sorted(statdir(err, top)):
if stat.S_ISDIR(inf.st_mode):
for x in walkdir(ap):
yield x
try:
for x in walkdir(err, ap):
yield x
except:
err.append(ap)
else:
yield ap, inf
def walkdirs(tops):
def walkdirs(err, tops):
"""recursive statdir for a list of tops, yields [top, relpath, stat]"""
sep = "{0}".format(os.sep).encode("ascii")
for top in tops:
@@ -256,7 +274,7 @@ def walkdirs(tops):
stop = os.path.dirname(top)
if os.path.isdir(top):
for ap, inf in walkdir(top):
for ap, inf in walkdir(err, top):
yield stop, ap[len(stop) :].lstrip(sep), inf
else:
d, n = top.rsplit(sep, 1)
@@ -446,10 +464,21 @@ class Ctl(object):
nfiles = 0
nbytes = 0
for _, _, inf in walkdirs(ar.files):
err = []
for _, _, inf in walkdirs(err, ar.files):
nfiles += 1
nbytes += inf.st_size
if err:
eprint("\n# failed to access {} paths:\n".format(len(err)))
for x in err:
eprint(x.decode("utf-8", "replace") + "\n")
eprint("^ failed to access those {} paths ^\n\n".format(len(err)))
if not ar.ok:
eprint("aborting because --ok is not set\n")
return
eprint("found {0} files, {1}\n\n".format(nfiles, humansize(nbytes)))
self.nfiles = nfiles
self.nbytes = nbytes
@@ -460,7 +489,7 @@ class Ctl(object):
if ar.te:
req_ses.verify = ar.te
self.filegen = walkdirs(ar.files)
self.filegen = walkdirs([], ar.files)
if ar.safe:
self.safe()
else:
@@ -783,6 +812,7 @@ source file/folder selection uses rsync syntax, meaning that:
ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process")
ap.add_argument("-a", metavar="PASSWORD", help="password")
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
ap = app.add_argument_group("performance tweaks")
ap.add_argument("-j", type=int, metavar="THREADS", default=4, help="parallel connections")
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")

View File

@@ -376,6 +376,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload")
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even if copyparty thinks you're better off without")
ap2.add_argument("--no-symlink", action="store_true", help="duplicate file contents instead")
ap2.add_argument("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d")
ap2 = ap.add_argument_group('network options')
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
@@ -383,6 +384,7 @@ def run_argparse(argv, formatter):
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.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="socket write delay in seconds")
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="response delay in seconds")
ap2 = ap.add_argument_group('SSL/TLS options')
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
@@ -435,6 +437,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails")
ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds")
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
@@ -519,7 +522,7 @@ def main(argv=None):
if HAVE_SSL:
ensure_cert()
for k, v in zip(argv, argv[1:]):
for k, v in zip(argv[1:], argv[2:]):
if k == "-c":
supp = args_from_cfg(v)
argv.extend(supp)

View File

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

View File

@@ -126,7 +126,8 @@ class HttpCli(object):
self.loud_reply(unicode(ex), status=ex.code, volsan=True)
return self.keepalive
# time.sleep(0.4)
if self.args.rsp_slp:
time.sleep(self.args.rsp_slp)
# normalize incoming headers to lowercase;
# outgoing headers however are Correct-Case
@@ -227,8 +228,8 @@ class HttpCli(object):
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
ua = self.headers.get("user-agent", "")
self.is_rclone = ua.startswith("rclone/")
self.ua = self.headers.get("user-agent", "")
self.is_rclone = self.ua.startswith("rclone/")
if self.is_rclone:
uparam["raw"] = False
uparam["dots"] = False
@@ -283,12 +284,19 @@ class HttpCli(object):
n = "604800" if cache == "i" else cache or "69"
self.out_headers["Cache-Control"] = "max-age=" + n
def k304(self):
k304 = self.cookies.get("k304", "")
return k304 == "y" or ("; Trident/" in self.ua and not k304)
def send_headers(self, length, status=200, mime=None, headers=None):
response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
if length is not None:
response.append("Content-Length: " + unicode(length))
if status == 304 and self.k304():
self.keepalive = False
# close if unknown length, otherwise take client's preference
response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
@@ -428,6 +436,9 @@ class HttpCli(object):
if "ups" in self.uparam:
return self.tx_ups()
if "k304" in self.uparam:
return self.set_k304()
if "h" in self.uparam:
return self.tx_mounts()
@@ -505,7 +516,7 @@ class HttpCli(object):
return self.handle_stash()
if "save" in opt:
post_sz, _, _, path = self.dump_to_file()
post_sz, _, _, _, path = self.dump_to_file()
self.log("urlform: {} bytes, {}".format(post_sz, path))
elif "print" in opt:
reader, _ = self.get_body_reader()
@@ -621,7 +632,7 @@ class HttpCli(object):
with ren_open(fn, *open_a, **params) as f:
f, fn = f["orz"]
path = os.path.join(fdir, fn)
post_sz, _, sha_b64 = hashcopy(reader, f)
post_sz, sha_hex, sha_b64 = hashcopy(reader, f)
if lim:
lim.nup(self.ip)
@@ -645,13 +656,14 @@ class HttpCli(object):
time.time(),
)
return post_sz, sha_b64, remains, path
return post_sz, sha_hex, sha_b64, remains, path
def handle_stash(self):
post_sz, sha_b64, remains, path = self.dump_to_file()
post_sz, sha_hex, sha_b64, remains, path = self.dump_to_file()
spd = self._spd(post_sz)
self.log("{} wrote {}/{} bytes to {}".format(spd, post_sz, remains, path))
self.reply("{}\n{}\n".format(post_sz, sha_b64).encode("utf-8"))
m = "{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56])
self.reply(m.encode("utf-8"))
return True
def _spd(self, nbytes, add=True):
@@ -783,6 +795,10 @@ class HttpCli(object):
return True
def handle_search(self, body):
idx = self.conn.get_u2idx()
if not hasattr(idx, "p_end"):
raise Pebkac(500, "sqlite3 is not available on the server; cannot search")
vols = []
seen = {}
for vtop in self.rvol:
@@ -794,7 +810,6 @@ class HttpCli(object):
seen[vfs] = True
vols.append([vfs.vpath, vfs.realpath, vfs.flags])
idx = self.conn.get_u2idx()
t0 = time.time()
if idx.p_end:
penalty = 0.7
@@ -854,63 +869,63 @@ class HttpCli(object):
response = x.get()
chunksize, cstart, path, lastmod = response
if self.args.nw:
path = os.devnull
if remains > chunksize:
raise Pebkac(400, "your chunk is too big to fit")
self.log("writing {} #{} @{} len {}".format(path, chash, cstart, remains))
reader = read_socket(self.sr, remains)
f = None
fpool = not self.args.no_fpool
if fpool:
with self.mutex:
try:
f = self.u2fh.pop(path)
except:
pass
f = f or open(fsenc(path), "rb+", 512 * 1024)
try:
f.seek(cstart[0])
post_sz, _, sha_b64 = hashcopy(reader, f)
if self.args.nw:
path = os.devnull
if sha_b64 != chash:
raise Pebkac(
400,
"your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}".format(
post_sz, chash, sha_b64
),
)
if remains > chunksize:
raise Pebkac(400, "your chunk is too big to fit")
if len(cstart) > 1 and path != os.devnull:
self.log(
"clone {} to {}".format(
cstart[0], " & ".join(unicode(x) for x in cstart[1:])
)
)
ofs = 0
while ofs < chunksize:
bufsz = min(chunksize - ofs, 4 * 1024 * 1024)
f.seek(cstart[0] + ofs)
buf = f.read(bufsz)
for wofs in cstart[1:]:
f.seek(wofs + ofs)
f.write(buf)
self.log("writing {} #{} @{} len {}".format(path, chash, cstart, remains))
ofs += len(buf)
reader = read_socket(self.sr, remains)
self.log("clone {} done".format(cstart[0]))
finally:
if not fpool:
f.close()
else:
f = None
fpool = not self.args.no_fpool
if fpool:
with self.mutex:
self.u2fh.put(path, f)
try:
f = self.u2fh.pop(path)
except:
pass
f = f or open(fsenc(path), "rb+", 512 * 1024)
try:
f.seek(cstart[0])
post_sz, _, sha_b64 = hashcopy(reader, f)
if sha_b64 != chash:
m = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
raise Pebkac(400, m.format(post_sz, chash, sha_b64))
if len(cstart) > 1 and path != os.devnull:
self.log(
"clone {} to {}".format(
cstart[0], " & ".join(unicode(x) for x in cstart[1:])
)
)
ofs = 0
while ofs < chunksize:
bufsz = min(chunksize - ofs, 4 * 1024 * 1024)
f.seek(cstart[0] + ofs)
buf = f.read(bufsz)
for wofs in cstart[1:]:
f.seek(wofs + ofs)
f.write(buf)
ofs += len(buf)
self.log("clone {} done".format(cstart[0]))
finally:
if not fpool:
f.close()
else:
with self.mutex:
self.u2fh.put(path, f)
finally:
x = self.conn.hsrv.broker.put(True, "up2k.release_chunk", ptop, wark, chash)
x.get() # block client until released
x = self.conn.hsrv.broker.put(True, "up2k.confirm_chunk", ptop, wark, chash)
x = x.get()
@@ -957,15 +972,13 @@ class HttpCli(object):
def get_pwd_cookie(self, pwd):
if pwd in self.asrv.iacct:
msg = "login ok"
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
exp = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
dur = 60 * 60 * 24 * 365
else:
msg = "naw dude"
pwd = "x" # nosec
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
dur = None
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
return [ck, msg]
return [gencookie("cppwd", pwd, dur), msg]
def handle_mkdir(self):
new_dir = self.parser.require("name", 512)
@@ -1073,7 +1086,7 @@ class HttpCli(object):
f, fname = f["orz"]
abspath = os.path.join(fdir, fname)
self.log("writing to {}".format(abspath))
sz, sha512_hex, _ = hashcopy(p_data, f)
sz, sha_hex, sha_b64 = hashcopy(p_data, f)
if sz == 0:
raise Pebkac(400, "empty files in post")
@@ -1086,7 +1099,7 @@ class HttpCli(object):
bos.unlink(abspath)
raise
files.append([sz, sha512_hex, p_file, fname, abspath])
files.append([sz, sha_hex, sha_b64, p_file, fname, abspath])
dbv, vrem = vfs.get_dbv(rem)
self.conn.hsrv.broker.put(
False,
@@ -1138,7 +1151,7 @@ class HttpCli(object):
jmsg["error"] = errmsg
errmsg = "ERROR: " + errmsg
for sz, sha512, ofn, lfn, ap in files:
for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
vsuf = ""
if self.can_read and "fk" in vfs.flags:
vsuf = "?k=" + gen_filekey(
@@ -1149,8 +1162,13 @@ class HttpCli(object):
)[: vfs.flags["fk"]]
vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
sha512[:56], sz, quotep(vpath) + vsuf, html_escape(ofn, crlf=True), vsuf
msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
sha_hex[:56],
sha_b64,
sz,
quotep(vpath) + vsuf,
html_escape(ofn, crlf=True),
vsuf,
)
# truncated SHA-512 prevents length extension attacks;
# using SHA-512/224, optionally SHA-512/256 = :64
@@ -1160,7 +1178,8 @@ class HttpCli(object):
self.headers.get("host", "copyparty"),
vpath + vsuf,
),
"sha512": sha512[:56],
"sha512": sha_hex[:56],
"sha_b64": sha_b64,
"sz": sz,
"fn": lfn,
"fn_orig": ofn,
@@ -1378,8 +1397,7 @@ class HttpCli(object):
if "gzip" not in supported_editions:
decompress = True
else:
ua = self.headers.get("user-agent", "")
if re.match(r"MSIE [4-6]\.", ua) and " SV1" not in ua:
if re.match(r"MSIE [4-6]\.", self.ua) and " SV1" not in self.ua:
decompress = True
if not decompress:
@@ -1692,10 +1710,16 @@ class HttpCli(object):
tagq=vs["tagq"],
mtpq=vs["mtpq"],
url_suf=suf,
k304=self.k304(),
)
self.reply(html.encode("utf-8"))
return True
def set_k304(self):
ck = gencookie("k304", self.uparam["k304"], 60 * 60 * 24 * 365)
self.out_headers["Set-Cookie"] = ck
self.redirect("", "?h#cc")
def tx_404(self, is_403=False):
if self.args.vague_403:
m = '<h1>404 not found &nbsp;┐( ´ -`)┌</h1><p>or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
@@ -1812,13 +1836,16 @@ class HttpCli(object):
if not self.args.unpost:
raise Pebkac(400, "the unpost feature is disabled in server config")
idx = self.conn.get_u2idx()
if not hasattr(idx, "p_end"):
raise Pebkac(500, "sqlite3 is not available on the server; cannot unpost")
filt = self.uparam.get("filter")
lm = "ups [{}]".format(filt)
self.log(lm)
ret = []
t0 = time.time()
idx = self.conn.get_u2idx()
lim = time.time() - self.args.unpost
for vol in self.asrv.vfs.all_vols.values():
cur = idx.get_cur(vol.realpath)

View File

@@ -8,7 +8,7 @@ import shutil
import subprocess as sp
from .__init__ import PY2, WINDOWS, unicode
from .util import fsenc, fsdec, uncyg, REKOBO_LKEY
from .util import fsenc, fsdec, uncyg, runcmd, REKOBO_LKEY
from .bos import bos
@@ -73,7 +73,7 @@ class MParser(object):
raise Exception()
def ffprobe(abspath):
def ffprobe(abspath, timeout=10):
cmd = [
b"ffprobe",
b"-hide_banner",
@@ -82,10 +82,8 @@ def ffprobe(abspath):
b"--",
fsenc(abspath),
]
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
r = p.communicate()
txt = r[0].decode("utf-8", "replace")
return parse_ffprobe(txt)
rc = runcmd(cmd, timeout=timeout)
return parse_ffprobe(rc[1])
def parse_ffprobe(txt):

View File

@@ -397,7 +397,6 @@ class SvcHub(object):
def check_mp_enable(self):
if self.args.j == 1:
self.log("svchub", "multiprocessing disabled by argument -j 1")
return False
if mp.cpu_count() <= 1:

View File

@@ -30,7 +30,7 @@ class ThumbCli(object):
if is_vid and self.args.no_vthumb:
return None
want_opus = fmt == "opus"
want_opus = fmt in ("opus", "caf")
is_au = ext in FMT_FFA
if is_au:
if want_opus:

View File

@@ -90,7 +90,7 @@ def thumb_path(histpath, rem, mtime, fmt):
h = hashlib.sha512(fsenc(fn)).digest()
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
if fmt == "opus":
if fmt in ("opus", "caf"):
cat = "ac"
else:
fmt = "webp" if fmt == "w" else "jpg"
@@ -216,7 +216,7 @@ class ThumbSrv(object):
elif ext in FMT_FFV:
fun = self.conv_ffmpeg
elif ext in FMT_FFA:
if tpath.endswith(".opus"):
if tpath.endswith(".opus") or tpath.endswith(".caf"):
fun = self.conv_opus
else:
fun = self.conv_spec
@@ -349,7 +349,7 @@ class ThumbSrv(object):
def _run_ff(self, cmd):
# self.log((b" ".join(cmd)).decode("utf-8"))
ret, sout, serr = runcmd(cmd)
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
if ret != 0:
m = "FFmpeg failed (probably a corrupt video file):\n"
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
@@ -406,21 +406,45 @@ class ThumbSrv(object):
if "ac" not in ret:
raise Exception("not audio")
# fmt: off
cmd = [
b"ffmpeg",
b"-nostdin",
b"-v", b"error",
b"-hide_banner",
b"-i", fsenc(abspath),
b"-map", b"0:a:0",
b"-c:a", b"libopus",
b"-b:a", b"128k",
fsenc(tpath)
]
# fmt: on
src_opus = abspath.lower().endswith(".opus") or ret["ac"][1] == "opus"
want_caf = tpath.endswith(".caf")
tmp_opus = tpath
if want_caf:
tmp_opus = tpath.rsplit(".", 1)[0] + ".opus"
self._run_ff(cmd)
if not want_caf or (not src_opus and not bos.path.isfile(tmp_opus)):
# fmt: off
cmd = [
b"ffmpeg",
b"-nostdin",
b"-v", b"error",
b"-hide_banner",
b"-i", fsenc(abspath),
b"-map_metadata", b"-1",
b"-map", b"0:a:0",
b"-c:a", b"libopus",
b"-b:a", b"128k",
fsenc(tmp_opus)
]
# fmt: on
self._run_ff(cmd)
if want_caf:
# fmt: off
cmd = [
b"ffmpeg",
b"-nostdin",
b"-v", b"error",
b"-hide_banner",
b"-i", fsenc(abspath if src_opus else tmp_opus),
b"-map_metadata", b"-1",
b"-map", b"0:a:0",
b"-c:a", b"copy",
b"-f", b"caf",
fsenc(tpath)
]
# fmt: on
self._run_ff(cmd)
def poke(self, tdir):
if not self.poke_cd.poke(tdir):
@@ -461,7 +485,7 @@ class ThumbSrv(object):
thumbpath = os.path.join(histpath, cat)
# self.log("cln {}".format(thumbpath))
exts = ["jpg", "webp"] if cat == "th" else ["opus"]
exts = ["jpg", "webp"] if cat == "th" else ["opus", "caf"]
maxage = getattr(self.args, cat + "_maxage")
now = time.time()
prev_b64 = None

View File

@@ -73,6 +73,7 @@ class Up2k(object):
self.need_rescan = {}
self.dupesched = {}
self.registry = {}
self.droppable = {}
self.entags = {}
self.flags = {}
self.cur = {}
@@ -125,11 +126,11 @@ class Up2k(object):
all_vols = self.asrv.vfs.all_vols
have_e2d = self.init_indexes(all_vols)
if have_e2d:
thr = threading.Thread(target=self._snapshot, name="up2k-snapshot")
thr.daemon = True
thr.start()
thr = threading.Thread(target=self._snapshot, name="up2k-snapshot")
thr.daemon = True
thr.start()
if have_e2d:
thr = threading.Thread(target=self._hasher, name="up2k-hasher")
thr.daemon = True
thr.start()
@@ -295,7 +296,8 @@ class Up2k(object):
def _vis_reg_progress(self, reg):
ret = []
for _, job in reg.items():
ret.append(self._vis_job_progress(job))
if job["need"]:
ret.append(self._vis_job_progress(job))
return ret
@@ -483,26 +485,41 @@ class Up2k(object):
self.log("/{} {}".format(vpath, " ".join(sorted(a))), "35")
reg = {}
drp = None
path = os.path.join(histpath, "up2k.snap")
if "e2d" in flags and bos.path.exists(path):
if bos.path.exists(path):
with gzip.GzipFile(path, "rb") as f:
j = f.read().decode("utf-8")
reg2 = json.loads(j)
try:
drp = reg2["droppable"]
reg2 = reg2["registry"]
except:
pass
for k, job in reg2.items():
path = os.path.join(job["ptop"], job["prel"], job["name"])
if bos.path.exists(path):
reg[k] = job
job["poke"] = time.time()
job["busy"] = {}
else:
self.log("ign deleted file in snap: [{}]".format(path))
m = "loaded snap {} |{}|".format(path, len(reg.keys()))
if drp is None:
drp = [k for k, v in reg.items() if not v.get("need", [])]
else:
drp = [x for x in drp if x in reg]
m = "loaded snap {} |{}| ({})".format(path, len(reg.keys()), len(drp or []))
m = [m] + self._vis_reg_progress(reg)
self.log("\n".join(m))
self.flags[ptop] = flags
self.registry[ptop] = reg
self.droppable[ptop] = drp or []
self.regdrop(ptop, None)
if not HAVE_SQLITE3 or "e2d" not in flags or "d2d" in flags:
return None
@@ -1256,6 +1273,7 @@ class Up2k(object):
"at": at,
"hash": [],
"need": [],
"busy": {},
}
if job and wark in reg:
@@ -1338,6 +1356,7 @@ class Up2k(object):
"t0": now,
"hash": deepcopy(cj["hash"]),
"need": [],
"busy": {},
}
# client-provided, sanitized by _get_wark: name, size, lmod
for k in [
@@ -1444,6 +1463,14 @@ class Up2k(object):
if not nchunk:
raise Pebkac(400, "unknown chunk")
if chash in job["busy"]:
nh = len(job["hash"])
idx = job["hash"].index(chash)
m = "that chunk is already being written to:\n {}\n {} {}/{}\n {}"
raise Pebkac(400, m.format(wark, chash, idx, nh, job["name"]))
job["busy"][chash] = 1
job["poke"] = time.time()
chunksize = up2k_chunksize(job["size"])
@@ -1453,6 +1480,14 @@ class Up2k(object):
return [chunksize, ofs, path, job["lmod"]]
def release_chunk(self, ptop, wark, chash):
with self.mutex:
job = self.registry[ptop].get(wark)
if job:
job["busy"].pop(chash, None)
return [True]
def confirm_chunk(self, ptop, wark, chash):
with self.mutex:
try:
@@ -1463,6 +1498,8 @@ class Up2k(object):
except Exception as ex:
return "confirm_chunk, wark, " + repr(ex)
job["busy"].pop(chash, None)
try:
job["need"].remove(chash)
except Exception as ex:
@@ -1473,7 +1510,7 @@ class Up2k(object):
return ret, src
if self.args.nw:
# del self.registry[ptop][wark]
self.regdrop(ptop, wark)
return ret, dst
# windows cant rename open files
@@ -1505,9 +1542,9 @@ class Up2k(object):
a = [job[x] for x in "ptop wark prel name lmod size addr".split()]
a += [job.get("at") or time.time()]
if self.idx_wark(*a):
# self.log("pop " + wark + " " + dst + " finish_upload idx_wark", 4)
del self.registry[ptop][wark]
# in-memory registry is reserved for unfinished uploads
else:
self.regdrop(ptop, wark)
dupes = self.dupesched.pop(dst, [])
if not dupes:
@@ -1527,6 +1564,21 @@ class Up2k(object):
if cur:
cur.connection.commit()
def regdrop(self, ptop, wark):
t = self.droppable[ptop]
if wark:
t.append(wark)
if len(t) <= self.args.reg_cap:
return
n = len(t) - int(self.args.reg_cap / 2)
m = "up2k-registry [{}] has {} droppables; discarding {}"
self.log(m.format(ptop, len(t), n))
for k in t[:n]:
self.registry[ptop].pop(k, None)
self.droppable[ptop] = t[n:]
def idx_wark(self, ptop, wark, rd, fn, lmod, sz, ip, at):
cur = self.cur.get(ptop)
if not cur:
@@ -2042,7 +2094,8 @@ class Up2k(object):
bos.makedirs(histpath)
path2 = "{}.{}".format(path, os.getpid())
j = json.dumps(reg, indent=2, sort_keys=True).encode("utf-8")
body = {"droppable": self.droppable[ptop], "registry": reg}
j = json.dumps(body, indent=2, sort_keys=True).encode("utf-8")
with gzip.GzipFile(path2, "wb") as f:
f.write(j)

View File

@@ -104,6 +104,7 @@ MIMES = {
"txt": "text/plain",
"js": "text/javascript",
"opus": "audio/ogg; codecs=opus",
"caf": "audio/x-caf",
"mp3": "audio/mpeg",
"m4a": "audio/mp4",
"jpg": "image/jpeg",
@@ -821,6 +822,17 @@ def gen_filekey(salt, fspath, fsize, inode):
).decode("ascii")
def gencookie(k, v, dur):
v = v.replace(";", "")
if dur:
dt = datetime.utcfromtimestamp(time.time() + dur)
exp = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
else:
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
return "{}={}; Path=/; Expires={}; SameSite=Lax".format(k, v, exp)
def humansize(sz, terse=False):
for unit in ["B", "KiB", "MiB", "GiB", "TiB"]:
if sz < 1024:
@@ -1312,9 +1324,17 @@ def guess_mime(url, fallback="application/octet-stream"):
return ret
def runcmd(argv):
def runcmd(argv, timeout=None):
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = p.communicate()
if not timeout or PY2:
stdout, stderr = p.communicate()
else:
try:
stdout, stderr = p.communicate(timeout=timeout)
except sp.TimeoutExpired:
p.kill()
stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8", "replace")
stderr = stderr.decode("utf-8", "replace")
return [p.returncode, stdout, stderr]

View File

@@ -44,8 +44,9 @@ pre, code, tt, #doc, #doc>code {
margin-left: -.7em;
}
#files {
border-spacing: 0;
z-index: 1;
top: -.3em;
border-spacing: 0;
position: relative;
}
#files tbody a {
@@ -72,7 +73,7 @@ a, #files tbody div a:last-child {
}
#files thead {
position: sticky;
top: 0;
top: -1px;
}
#files thead a {
color: #999;
@@ -82,7 +83,7 @@ a, #files tbody div a:last-child {
background: #1c1c1c;
}
#files thead th {
padding: 0 .3em .3em .3em;
padding: .3em;
border-bottom: 1px solid #444;
cursor: pointer;
}
@@ -876,13 +877,15 @@ html.light #tree.nowrap .ntree a+a:hover {
}
#thumbs,
#au_fullpre,
#au_os_seek,
#au_osd_cv,
#u2tdate {
opacity: .3;
}
#griden.on+#thumbs,
#au_preload.on+#au_fullpre,
#au_os_ctl.on+#au_osd_cv,
#au_os_ctl.on+#au_os_seek,
#au_os_ctl.on+#au_os_seek+#au_osd_cv,
#u2turbo.on+#u2tdate {
opacity: 1;
}
@@ -1663,8 +1666,6 @@ html.light #bbox-overlay figcaption a {
#op_up2k {
padding: 0 1em 1em 1em;
min-height: 0;
transition: min-height .2s;
}
#drops {
display: none;
@@ -1829,13 +1830,17 @@ html.light #u2err.err {
#u2notbtn * {
line-height: 1.3em;
}
#u2tabw {
min-height: 0;
transition: min-height .2s;
margin: 3em auto;
}
#u2tab {
border-collapse: collapse;
margin: 3em auto;
width: calc(100% - 2em);
max-width: 100em;
}
#op_up2k.srch #u2tab {
#op_up2k.srch #u2tabf {
max-width: none;
}
#u2tab td {

View File

@@ -1,7 +1,5 @@
"use strict";
window.onerror = vis_exh;
function dbg(msg) {
ebi('path').innerHTML = msg;
}
@@ -117,7 +115,7 @@ ebi('op_up2k').innerHTML = (
'</div>\n' +
'<table id="u2tab">\n' +
'<div id="u2tabw"><table id="u2tab">\n' +
' <thead>\n' +
' <tr>\n' +
' <td>filename</td>\n' +
@@ -126,7 +124,7 @@ ebi('op_up2k').innerHTML = (
' </tr>\n' +
' </thead>\n' +
' <tbody></tbody>\n' +
'</table>\n' +
'</table></div>\n' +
'<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' +
'<p id="u2foot"></p>\n' +
@@ -307,6 +305,10 @@ function set_files_html(html) {
}
var ACtx = window.AudioContext || window.webkitAudioContext,
actx = ACtx && new ACtx();
var mpl = (function () {
var have_mctl = 'mediaSession' in navigator && window.MediaMetadata;
@@ -316,12 +318,13 @@ var mpl = (function () {
'<a href="#" class="tgl btn" id="au_fullpre" tt="try to preload the entire song;$N✔ enable on <b>unreliable</b> connections,$N❌ <b>disable</b> on slow connections probably">full</a>' +
'<a href="#" class="tgl btn" id="au_npclip" tt="show buttons for clipboarding the currently playing song">/np clip</a>' +
'<a href="#" class="tgl btn" id="au_os_ctl" tt="os integration (media hotkeys / osd)">os-ctl</a>' +
'<a href="#" class="tgl btn" id="au_osd_cv" tt="show album cover in osd">osd-cv</a>' +
'<a href="#" class="tgl btn" id="au_os_seek" tt="allow seeking through os integration">seek</a>' +
'<a href="#" class="tgl btn" id="au_osd_cv" tt="show album cover in osd">art</a>' +
'</div></div>' +
'<div><h3>playback mode</h3><div id="pb_mode">' +
'<a href="#" class="tgl btn" tt="loop the open folder">🔁 loop-folder</a>' +
'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
'<a href="#" class="tgl btn" tt="loop the open folder">🔁 loop</a>' +
'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next</a>' +
'</div></div>' +
(have_acode ? (
@@ -339,25 +342,33 @@ var mpl = (function () {
'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
var r = {
"pb_mode": sread('pb_mode') || 'loop-folder',
"pb_mode": (sread('pb_mode') || 'loop').split('-')[0],
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
};
bcfg_bind(r, 'preload', 'au_preload', true);
bcfg_bind(r, 'fullpre', 'au_fullpre', false);
bcfg_bind(r, 'osd_cv', 'au_osd_cv', true);
bcfg_bind(r, 'os_seek', 'au_os_seek', !IPHONE, announce);
bcfg_bind(r, 'osd_cv', 'au_osd_cv', true, announce);
bcfg_bind(r, 'clip', 'au_npclip', false, function (v) {
clmod(ebi('wtoggle'), 'np', v && mp.au);
});
bcfg_bind(r, 'ac_flac', 'ac_flac', true);
bcfg_bind(r, 'ac_aac', 'ac_aac', false);
bcfg_bind(r, 'ac_oth', 'ac_oth', true, reload_mp);
if (!have_acode)
r.ac_flac = r.ac_aac = r.ac_oth = false;
if (IPHONE) {
ebi('au_fullpre').style.display = 'none';
r.fullpre = false;
}
ebi('au_os_ctl').onclick = function (e) {
ev(e);
r.os_ctl = !r.os_ctl && have_mctl;
bcfg_set('au_os_ctl', r.os_ctl);
if (!have_mctl)
toast.err(5, 'need firefox 82+ or chrome 73+\n(or iOS 15+ supposedly)');
toast.err(5, 'need firefox 82+ or chrome 73+ or iOS 15+');
};
function draw_pb_mode() {
@@ -397,13 +408,15 @@ var mpl = (function () {
c = r.ac_flac;
else if (/\.(aac|m4a)$/i.exec(url))
c = r.ac_aac;
else if (/\.opus$/i.exec(url) && !can_ogg)
c = true;
else if (re_au_native.exec(url))
c = false;
if (!c)
return url;
return url + (url.indexOf('?') < 0 ? '?' : '&') + 'th=opus';
return url + (url.indexOf('?') < 0 ? '?' : '&') + 'th=' + (can_ogg ? 'opus' : 'caf');
};
r.pp = function () {
@@ -413,13 +426,13 @@ var mpl = (function () {
navigator.mediaSession.playbackState = mp.au && !mp.au.paused ? "playing" : "paused";
};
r.announce = function () {
function announce() {
if (!r.os_ctl)
return;
var np = get_np()[0],
fns = np.file.split(' - '),
artist = (np.circle ? np.circle + ' // ' : '') + (np.artist || (fns.length > 1 ? fns[0] : '')),
artist = (np.circle && np.circle != np.artist ? np.circle + ' // ' : '') + (np.artist || (fns.length > 1 ? fns[0] : '')),
tags = {
title: np.title || fns.pop()
};
@@ -455,12 +468,13 @@ var mpl = (function () {
navigator.mediaSession.metadata = new MediaMetadata(tags);
navigator.mediaSession.setActionHandler('play', playpause);
navigator.mediaSession.setActionHandler('pause', playpause);
navigator.mediaSession.setActionHandler('seekbackward', function () { seek_au_rel(-10); });
navigator.mediaSession.setActionHandler('seekforward', function () { seek_au_rel(10); });
navigator.mediaSession.setActionHandler('seekbackward', r.os_seek ? function () { seek_au_rel(-10); } : null);
navigator.mediaSession.setActionHandler('seekforward', r.os_seek ? function () { seek_au_rel(10); } : null);
navigator.mediaSession.setActionHandler('previoustrack', prev_song);
navigator.mediaSession.setActionHandler('nexttrack', next_song);
r.pp();
};
}
r.announce = announce;
r.stop = function () {
if (!r.os_ctl || !navigator.mediaSession.metadata)
@@ -471,14 +485,10 @@ var mpl = (function () {
};
r.unbuffer = function (url) {
for (var a = 0; a < 2; a++) {
var au = a ? mp.au_native2 : mp.au_ogvjs2;
if (au && (!url || au.src == url)) {
au.src = '';
au.load();
}
if (mp.au2 && (!url || mp.au2.rsrc == url)) {
mp.au2.src = mp.au2.rsrc = '';
mp.au2.load();
}
if (!url)
mpl.preload_url = null;
}
@@ -487,7 +497,18 @@ var mpl = (function () {
})();
var re_au_native = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
var can_ogg = true;
try {
can_ogg = new Audio().canPlayType('audio/ogg; codecs=opus') === 'probably';
if (document.documentMode)
can_ogg = true; // ie8-11
}
catch (ex) { }
var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i :
have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i,
re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv)$/i;
@@ -496,10 +517,8 @@ function MPlayer() {
var r = this;
r.id = Date.now();
r.au = null;
r.au_native = null;
r.au_native2 = null;
r.au_ogvjs = null;
r.au_ogvjs2 = null;
r.au = null;
r.au2 = null;
r.tracks = {};
r.order = [];
@@ -523,7 +542,7 @@ function MPlayer() {
}
}
r.vol = clamp(fcfg_get('vol', 0.5), 0, 1);
r.vol = clamp(fcfg_get('vol', IPHONE ? 1 : 0.5), 0, 1);
r.expvol = function (v) {
return 0.5 * v + 0.5 * v * v;
@@ -604,7 +623,7 @@ function MPlayer() {
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987';
mpl.preload_url = full ? url : null;
var t0 = Date.now();
if (full && !need_ogv_for(url))
if (full)
return fetch(url).then(function (x) {
var rd = x.body.getReader(), n = 0;
function spd() {
@@ -627,23 +646,8 @@ function MPlayer() {
drop();
});
var au = null;
if (need_ogv_for(url)) {
au = mp.au_ogvjs2;
if (!au && window['OGVPlayer'])
au = r.au_ogvjs2 = new OGVPlayer();
au.mdng = true;
bind_ogvjs();
} else {
au = mp.au_native2;
if (!au)
au = r.au_native2 = new Audio();
}
if (au) {
au.preload = "auto";
au.src = url;
}
mp.au2.preload = "auto";
mp.au2.src = mp.au2.rsrc = url;
};
}
@@ -804,7 +808,7 @@ var pbar = (function () {
bctx.clearRect(0, 0, bc.w, bc.h);
if (!mp.au || mp.au.mdng)
if (!mp.au)
return;
var sm = bc.w * 1.0 / mp.au.duration,
@@ -831,7 +835,7 @@ var pbar = (function () {
pctx.clearRect(0, 0, pc.w, pc.h);
if (!mp.au || mp.au.mdng || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
if (!mp.au || isNaN(adur = mp.au.duration) || isNaN(apos = mp.au.currentTime) || apos < 0 || adur < apos)
return; // not-init || unsupp-codec
var sm = bc.w * 1.0 / adur;
@@ -1101,18 +1105,18 @@ var mpui = (function () {
}
// preload next song
if (mpl.preload && !mp.au.mdng && preloaded != mp.au.src) {
if (mpl.preload && preloaded != mp.au.rsrc) {
var pos = mp.au.currentTime,
len = mp.au.duration,
rem = pos > 0 ? len - pos : 999,
rem = pos > 1 ? len - pos : 999,
full = null;
if (rem < (is_touch && IPHONE ? 34 : (mpl.fullpre ? 7 : 20))) {
preloaded = fpreloaded = mp.au.src;
if (rem < (mpl.fullpre ? 7 : 20)) {
preloaded = fpreloaded = mp.au.rsrc;
full = false;
}
else if (rem < 40 && mpl.fullpre && fpreloaded != mp.au.src) {
fpreloaded = mp.au.src;
else if (rem < 40 && mpl.fullpre && fpreloaded != mp.au.rsrc) {
fpreloaded = mp.au.rsrc;
full = true;
}
@@ -1146,36 +1150,6 @@ function ev_play(e) {
}
var need_ogv = true;
try {
need_ogv = new Audio().canPlayType('audio/ogg; codecs=opus') !== 'probably';
if (document.documentMode)
need_ogv = false; // ie8-11
}
catch (ex) { }
function need_ogv_for(url) {
return need_ogv && /\.(ogg|opus)|\?th=opus/i.test(url);
}
function start_sinegen() {
var af = 'data:audio/wav;base64,UklGRlaxAgBXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAATElTVBoAAABJTkZPSVNGVA4AAABMYXZmNTguNzYuMTAwAGRhdGEQsQIAAAB',
body = 'iArcE8AYCCeEKggzaDeQOmA/0D/QPmQ/kDtsNggzhCgMJ8Qa4BGMCAQCe/Un7EPn+9h/1fvMm8hzxaPAM8AzwZ/Ac8SXyfvMf9f32D/lI+539//9';
while (af.length < 235304)
af += body;
var au = new Audio(af.slice(0, 235304));
au.onplay = au.pause.bind(au);
au.volume = 0.5;
au.play();
return au;
}
var audio_eq = (function () {
var r = {
"en": false,
@@ -1183,15 +1157,18 @@ var audio_eq = (function () {
"gains": [4, 3, 2, 1, 0, 0, 1, 2, 3, 4],
"filters": [],
"amp": 0,
"last_au": null
"last_au": null,
"acst": {}
};
if (!actx)
ebi('audio_eq').parentNode.style.display = 'none';
// some browsers have insane high-frequency boost
// (or rather the actual problem is Q but close enough)
r.cali = (function () {
try {
var ac = new AudioContext(),
fi = ac.createBiquadFilter(),
var fi = actx.createBiquadFilter(),
freqs = new Float32Array(1),
mag = new Float32Array(1),
phase = new Float32Array(1);
@@ -1248,51 +1225,48 @@ var audio_eq = (function () {
QS('input.eq_gain[band="amp"]').value = r.amp;
};
r.apply = function () {
r.draw();
var Ctx = window.AudioContext || window.webkitAudioContext;
if (!Ctx)
bcfg_set('au_eq', false);
if (!Ctx || !mp.au)
return;
if (!r.en && !mp.ac)
return;
if (mp.au === mp.au_ogvjs)
return toast.warn(10, "apple devices can't equalize ogg/opus audio");
if (mp.ac) {
r.stop = function () {
if (r.filters.length)
for (var a = 0; a < r.filters.length; a++)
r.filters[a].disconnect();
mp.acs.disconnect();
}
if (!mp.ac || mp.au != r.last_au) {
if (mp.ac)
mp.ac.close();
r.last_au = mp.au;
mp.ac = new Ctx();
mp.acs = mp.ac.createMediaElementSource(mp.au);
}
r.filters = [];
if (!mp)
return;
if (mp.acs)
mp.acs.disconnect();
mp.acs = null;
};
r.apply = function () {
r.draw();
if (!actx)
bcfg_set('au_eq', false);
if (!actx || !mp.au || (!r.en && !mp.acs))
return;
r.stop();
mp.au.id = mp.au.id || Date.now();
mp.acs = r.acst[mp.au.id] = r.acst[mp.au.id] || actx.createMediaElementSource(mp.au);
if (!r.en) {
mp.acs.connect(mp.ac.destination);
mp.acs.connect(actx.destination);
return;
}
var max = 0;
for (var a = 0; a < r.gains.length; a++)
if (max < r.gains[a])
max = r.gains[a];
var min, max;
min = max = r.gains[0];
for (var a = 1; a < r.gains.length; a++) {
min = Math.min(min, r.gains[a]);
max = Math.max(max, r.gains[a]);
}
var gains = []
var gains = [];
for (var a = 0; a < r.gains.length; a++)
gains.push(r.gains[a] - max);
@@ -1301,8 +1275,8 @@ var audio_eq = (function () {
gains.push(t);
gains.unshift(gains[0]);
for (var a = 0; a < cfg.length; a++) {
var fi = mp.ac.createBiquadFilter();
for (var a = 0; a < cfg.length && min != max; a++) {
var fi = actx.createBiquadFilter();
fi.frequency.value = cfg[a][0];
fi.gain.value = cfg[a][2] * gains[a];
fi.Q.value = cfg[a][1];
@@ -1311,12 +1285,12 @@ var audio_eq = (function () {
}
// pregain, keep first in chain
fi = mp.ac.createGain();
fi = actx.createGain();
fi.gain.value = r.amp + 0.94; // +.137 dB measured; now -.25 dB and almost bitperfect
r.filters.push(fi);
for (var a = r.filters.length - 1; a >= 0; a--)
r.filters[a].connect(a > 0 ? r.filters[a - 1] : mp.ac.destination);
r.filters[a].connect(a > 0 ? r.filters[a - 1] : actx.destination);
mp.acs.connect(r.filters[r.filters.length - 1]);
}
@@ -1413,25 +1387,6 @@ var audio_eq = (function () {
})();
function bind_ogvjs() {
var a1 = mp.au_ogvjs,
a2 = mp.au_ogvjs2;
if (a2) {
a2.onerror = a2.onprogress = a2.onended = null;
a2.onloadedmetadata = a2.onloadeddata = function () {
a2.mdng = false;
};
}
a1.onerror = evau_error;
a1.onprogress = pbar.drawpos;
a1.onended = next_song;
a1.onloadedmetadata = a1.onloadeddata = function () {
a1.mdng = false;
};
}
// plays the tid'th audio file on the page
function play(tid, is_ev, seek, call_depth) {
if (mp.order.length == 0)
@@ -1451,20 +1406,20 @@ function play(tid, is_ev, seek, call_depth) {
}
if (tn >= mp.order.length) {
if (mpl.pb_mode == 'loop-folder') {
if (mpl.pb_mode == 'loop') {
tn = 0;
}
else if (mpl.pb_mode == 'next-folder') {
else if (mpl.pb_mode == 'next') {
treectl.ls_cb = next_song;
return tree_neigh(1);
}
}
if (tn < 0) {
if (mpl.pb_mode == 'loop-folder') {
if (mpl.pb_mode == 'loop') {
tn = mp.order.length - 1;
}
else if (mpl.pb_mode == 'next-folder') {
else if (mpl.pb_mode == 'next') {
treectl.ls_cb = prev_song;
return tree_neigh(-1);
}
@@ -1476,76 +1431,33 @@ function play(tid, is_ev, seek, call_depth) {
mp.au.pause();
clmod(ebi('a' + mp.au.tid), 'act');
}
// ogv.js breaks on .play() unless directly user-triggered
var attempt_play = true;
else {
mp.au = new Audio();
mp.au2 = new Audio();
mp.au.onerror = evau_error;
mp.au.onprogress = pbar.drawpos;
mp.au.onended = next_song;
widget.open();
}
var url = mpl.acode(mp.tracks[tid]);
if (need_ogv_for(url)) {
var m = /.* Version\/([0-9]+)\.[0-9\.]+ Mobile\/[^ ]+ Safari\/[0-9\.]+$/.exec(navigator.userAgent),
safari = m ? parseInt(m[1]) : 99;
if (mp.au_ogvjs) {
mp.au = mp.au_ogvjs;
}
else if (window['OGVPlayer']) {
try {
mp.au = mp.au_ogvjs = new OGVPlayer();
}
catch (ex) {
return toast.err(30, 'your browser cannot play ogg/vorbis/opus\n\n' + basenames(ex) +
'\n\n<a href="#" onclick="new OGVPlayer();">click here</a> for a full crash report');
}
attempt_play = is_ev;
mp.au.mdng = true;
bind_ogvjs();
widget.open();
}
else if (safari < 14) {
return toast.err(0, 'because this is an apple device,\nsafari 14 or newer is required to play ogg/vorbis/opus files\n\nyou are using safari ' + safari + '\n(every iOS browser is actually safari)');
}
else {
if (call_depth !== undefined)
return toast.err(0, 'failed to load ogv.js:\ncannot play ogg/opus in this browser\n(try a non-apple device)');
toast.inf(0, '<h1>loading ogv.js</h1><h2>thanks apple</h2>');
import_js('/.cpr/deps/ogv.js', function () {
toast.hide();
play(tid, false, seek, 1);
});
return;
}
}
else {
if (!mp.au_native) {
mp.au = mp.au_native = new Audio();
mp.au.onerror = evau_error;
mp.au.onprogress = pbar.drawpos;
mp.au.onended = next_song;
widget.open();
}
mp.au = mp.au_native;
}
audio_eq.apply();
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987';
if (mp.au_ogvjs2 && mp.au_ogvjs2.src === url) {
mp.au = mp.au_ogvjs2;
mp.au_ogvjs2 = mp.au_ogvjs;
mp.au_ogvjs = mp.au;
bind_ogvjs();
}
if (mp.au.src == url)
if (mp.au.rsrc == url)
mp.au.currentTime = 0;
else {
mp.au.mdng = mp.au == mp.au_ogvjs;
mp.au.src = url;
else if (mp.au2.rsrc == url) {
var t = mp.au;
mp.au = mp.au2;
mp.au2 = t;
t.onerror = t.onprogress = t.onended = null;
mp.au.onerror = evau_error;
mp.au.onprogress = pbar.drawpos;
mp.au.onended = next_song;
}
else
mp.au.src = mp.au.rsrc = url;
audio_eq.apply();
setTimeout(function () {
mpl.unbuffer(url);
@@ -1565,9 +1477,7 @@ function play(tid, is_ev, seek, call_depth) {
thegrid.loadsel();
try {
if (attempt_play)
mp.au.play();
mp.au.play();
if (mp.au.paused)
autoplay_blocked(seek);
else if (seek) {
@@ -1590,7 +1500,7 @@ function play(tid, is_ev, seek, call_depth) {
toast.err(0, esc('playback failed: ' + basenames(ex)));
}
clmod(ebi(oid), 'act');
setTimeout(next_song, 500);
setTimeout(next_song, 5000);
}
@@ -1611,6 +1521,15 @@ function evau_error(e) {
break;
case eplaya.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
err = "Your browser does not understand this audio format";
if (/\.(aac|m4a)(\?|$)/i.exec(eplaya.rsrc) && !mpl.ac_aac) {
try {
ebi('ac_aac').click();
QS('a.play.act').click();
toast.warn(10, 'your browser cannot play aac/m4a files;\ntranscoding to opus is now enabled');
return;
}
catch (ex) { }
}
break;
default:
err = 'Unknown Errol';
@@ -1633,15 +1552,9 @@ function autoplay_blocked(seek) {
fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
modal.confirm('<h6>play this audio file?</h6>\n«' + esc(fn) + '»', function () {
if (mp.au !== mp.au_ogvjs)
// chrome 91 may permanently taint on a failed play()
// depending on win10 settings or something? idk
mp.au_native = null;
else
// iOS browsers allow playing ogg/vorbis/opus in the background
// if there is an <audio> tag which at some point played audio
if (!mp.sinegen)
mp.sinegen = start_sinegen();
// chrome 91 may permanently taint on a failed play()
// depending on win10 settings or something? idk
mp.au = null;
play(tid, true, seek);
mp.fade_in();
@@ -3064,6 +2977,9 @@ document.onkeydown = function (e) {
if (widget.is_open)
return widget.close();
if (showfile.active())
return thegrid.setvis(true);
if (!treectl.hidden)
return treectl.detree();
@@ -3551,11 +3467,13 @@ var treectl = (function () {
}
r.textmode = function (ya) {
var chg = !r.texts != !ya;
r.texts = ya;
ebi('docul').style.display = ya ? '' : 'none';
ebi('treeul').style.display = ebi('treepar').style.display = ya ? 'none' : '';
clmod(ebi('filetree'), 'on', ya);
tree_scrollto();
if (chg)
tree_scrollto();
};
ebi('filetree').onclick = function (e) {
ev(e);
@@ -5051,6 +4969,7 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
function reload_mp() {
if (mp && mp.au) {
audio_eq.stop();
mp.au.pause();
mp.au = null;
}
@@ -5062,6 +4981,7 @@ function reload_mp() {
mpl.unbuffer();
mp = new MPlayer();
audio_eq.acst = {};
setTimeout(pbar.onresize, 1);
}

View File

@@ -38,7 +38,8 @@ a+a {
margin: -.2em 0 0 .5em;
}
.logout,
.btns a {
.btns a,
a.r {
color: #c04;
border-color: #c7a;
}
@@ -79,6 +80,11 @@ table {
margin-top: .3em;
text-align: right;
}
blockquote {
margin: 0 0 0 .6em;
padding: .7em 1em;
border-left: .3em solid rgba(128,128,128,0.5);
}
html.dark,
@@ -96,7 +102,8 @@ html.dark a {
border-color: #37a;
}
html.dark .logout,
html.dark .btns a {
html.dark .btns a,
html.dark a.r {
background: #804;
border-color: #c28;
}

View File

@@ -72,6 +72,16 @@
</ul>
{%- endif %}
<h1 id="cc">client config:</h1>
<ul>
{% if k304 %}
<li><a href="/?k304=n" class="r">disable k304</a> (currently enabled)
{%- else %}
<li><a href="/?k304=y">enable k304</a> (currently disabled)
{% endif %}
<blockquote>enabling this will disconnect your client on every HTTP 304, which can prevent some buggy browsers/proxies from getting stuck (suddenly not being able to load pages), <em>but</em> it will also make things slower in general</blockquote></li>
</ul>
<h1>login for more:</h1>
<ul>
<form method="post" enctype="multipart/form-data" action="/{{ qvpath }}">
@@ -84,8 +94,7 @@
<a href="#" id="repl">π</a>
<script>
if (localStorage.lightmode != 1)
document.documentElement.setAttribute("class", "dark");
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
</script>
<script src="/.cpr/util.js?_={{ ts }}"></script>

View File

@@ -116,6 +116,19 @@ html {
#toast.err #toastc {
background: #d06;
}
#tth {
color: #fff;
background: #111;
font-size: .9em;
padding: 0 .26em;
line-height: .97em;
border-radius: 1em;
position: absolute;
display: none;
}
#tth.act {
display: block;
}
#tt.b {
padding: 0 2em;
border-radius: .5em;
@@ -159,6 +172,10 @@ html.light #tt code {
html.light #tt em {
color: #d38;
}
html.light #tth {
color: #000;
background: #fff;
}
#modal {
position: fixed;
overflow: auto;

View File

@@ -1069,7 +1069,7 @@ function up2k_init(subtle) {
}
more_one_file();
var etaref = 0, etaskip = 0, op_minh = 0;
var etaref = 0, etaskip = 0, utw_minh = 0;
function etafun() {
var nhash = st.busy.head.length + st.busy.hash.length + st.todo.head.length + st.todo.hash.length,
nsend = st.busy.upload.length + st.todo.upload.length,
@@ -1082,13 +1082,10 @@ function up2k_init(subtle) {
//ebi('acc_info').innerHTML = humantime(st.time.busy) + ' ' + f2f(now / 1000, 1);
var op = ebi('op_up2k'),
uff = ebi('u2footfoot'),
minh = QS('#op_up2k.act') ? Math.max(op_minh, uff.offsetTop + uff.offsetHeight - op.offsetTop + 32) : 0;
if (minh > op_minh || !op_minh) {
op_minh = minh;
op.style.minHeight = op_minh + 'px';
var minh = QS('#op_up2k.act') && st.is_busy ? Math.max(utw_minh, ebi('u2tab').offsetHeight + 32) : 0;
if (utw_minh < minh || !utw_minh) {
utw_minh = minh;
ebi('u2tabw').style.minHeight = utw_minh + 'px';
}
if (!nhash)
@@ -1211,15 +1208,16 @@ function up2k_init(subtle) {
running = true;
while (true) {
var now = Date.now(),
is_busy = 0 !=
st.todo.head.length +
st.todo.hash.length +
st.todo.handshake.length +
st.todo.upload.length +
st.busy.head.length +
st.busy.hash.length +
st.busy.handshake.length +
st.busy.upload.length;
oldest_active = Math.min( // gzip take the wheel
st.todo.head.length ? st.todo.head[0].n : st.files.length,
st.todo.hash.length ? st.todo.hash[0].n : st.files.length,
st.todo.upload.length ? st.todo.upload[0].nfile : st.files.length,
st.todo.handshake.length ? st.todo.handshake[0].n : st.files.length,
st.busy.head.length ? st.busy.head[0].n : st.files.length,
st.busy.hash.length ? st.busy.hash[0].n : st.files.length,
st.busy.upload.length ? st.busy.upload[0].nfile : st.files.length,
st.busy.handshake.length ? st.busy.handshake[0].n : st.files.length),
is_busy = oldest_active < st.files.length;
if (was_busy && !is_busy) {
for (var a = 0; a < st.files.length; a++) {
@@ -1239,7 +1237,7 @@ function up2k_init(subtle) {
}
if (was_busy != is_busy) {
was_busy = is_busy;
st.is_busy = was_busy = is_busy;
window[(is_busy ? "add" : "remove") +
"EventListener"]("beforeunload", warn_uploader_busy);
@@ -1268,7 +1266,7 @@ function up2k_init(subtle) {
timer.rm(etafun);
timer.rm(donut.do);
op_minh = 0;
utw_minh = 0;
}
else {
timer.add(donut.do);
@@ -1320,7 +1318,8 @@ function up2k_init(subtle) {
}
if (st.todo.head.length &&
st.busy.head.length < parallel_uploads) {
st.busy.head.length < parallel_uploads &&
(!is_busy || st.todo.head[0].n - oldest_active < parallel_uploads * 2)) {
exec_head();
mou_ikkai = true;
}
@@ -1843,7 +1842,8 @@ function up2k_init(subtle) {
st.bytes.uploaded += cdr - car;
t.bytes_uploaded += cdr - car;
}
else if (txt.indexOf('already got that') !== -1) {
else if (txt.indexOf('already got that') + 1 ||
txt.indexOf('already being written') + 1) {
console.log("ignoring dupe-segment error", t);
}
else {
@@ -1851,6 +1851,9 @@ function up2k_init(subtle) {
xhr.status, t.name) + (txt || "no further information"));
return;
}
orz2(xhr);
}
function orz2(xhr) {
apop(st.busy.upload, upt);
apop(t.postlist, npart);
if (!t.postlist.length) {
@@ -1872,9 +1875,11 @@ function up2k_init(subtle) {
if (crashed)
return;
toast.err(9.98, "failed to upload a chunk,\n" + tries + " retries so far -- retrying in 10sec\n\n" + t.name);
if (!toast.visible)
toast.warn(9.98, "failed to upload a chunk;\nprobably harmless, continuing\n\n" + t.name);
console.log('chunkpit onerror,', ++tries, t);
setTimeout(do_send, 10 * 1000);
orz2(xhr);
};
xhr.open('POST', t.purl, true);
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);

View File

@@ -7,8 +7,7 @@ if (!window['console'])
var is_touch = 'ontouchstart' in window,
IPHONE = /iPhone|iPad|iPod/i.test(navigator.userAgent),
ANDROID = /android/i.test(navigator.userAgent),
IPHONE = is_touch && /iPhone|iPad|iPod/i.test(navigator.userAgent),
WINDOWS = navigator.platform ? navigator.platform == 'Win32' : /Windows/.test(navigator.userAgent);
@@ -181,6 +180,7 @@ function ignex(all) {
if (!all)
window.onerror = vis_exh;
}
window.onerror = vis_exh;
function noop() { }
@@ -286,15 +286,19 @@ function crc32(str) {
function clmod(el, cls, add) {
if (!el)
return false;
if (el.classList) {
var have = el.classList.contains(cls);
if (add == 't')
add = !have;
if (add != have)
el.classList[add ? 'add' : 'remove'](cls);
if (!add == !have)
return false;
return;
el.classList[add ? 'add' : 'remove'](cls);
return true;
}
var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g'),
@@ -305,12 +309,18 @@ function clmod(el, cls, add) {
var n2 = n1.replace(re, ' ') + (add ? ' ' + cls : '');
if (n1 != n2)
el.className = n2;
if (!n1 == !n2)
return false;
el.className = n2;
return true;
}
function clgot(el, cls) {
if (!el)
return;
if (el.classList)
return el.classList.contains(cls);
@@ -782,13 +792,18 @@ var timer = (function () {
var tt = (function () {
var r = {
"tt": mknod("div"),
"th": mknod("div"),
"en": true,
"el": null,
"skip": false
"skip": false,
"lvis": 0
};
r.th.innerHTML = '?';
r.tt.setAttribute('id', 'tt');
r.th.setAttribute('id', 'tth');
document.body.appendChild(r.tt);
document.body.appendChild(r.th);
var prev = null;
r.cshow = function () {
@@ -798,11 +813,25 @@ var tt = (function () {
prev = this;
};
r.show = function () {
if (r.skip) {
r.skip = false;
var tev;
r.dshow = function (e) {
clearTimeout(tev);
if (!r.getmsg(this))
return;
}
if (Date.now() - r.lvis < 400)
return r.show.bind(this)();
tev = setTimeout(r.show.bind(this), 800);
if (is_touch)
return;
this.addEventListener('mousemove', r.move);
clmod(r.th, 'act', 1);
r.move(e);
};
r.getmsg = function (el) {
if (QS('body.bbox-open'))
return;
@@ -810,7 +839,16 @@ var tt = (function () {
if (cfg !== null && cfg != '1')
return;
var msg = this.getAttribute('tt');
return el.getAttribute('tt');
};
r.show = function () {
clearTimeout(tev);
if (r.skip) {
r.skip = false;
return;
}
var msg = r.getmsg(this);
if (!msg)
return;
@@ -824,6 +862,7 @@ var tt = (function () {
if (dir.indexOf('u') + 1) top = false;
if (dir.indexOf('d') + 1) top = true;
clmod(r.th, 'act');
clmod(r.tt, 'b', big);
r.tt.style.left = '0';
r.tt.style.top = '0';
@@ -849,14 +888,27 @@ var tt = (function () {
r.hide = function (e) {
ev(e);
clearTimeout(tev);
window.removeEventListener('scroll', r.hide);
clmod(r.tt, 'show');
clmod(r.tt, 'b');
clmod(r.th, 'act');
if (clmod(r.tt, 'show'))
r.lvis = Date.now();
if (r.el)
r.el.removeEventListener('mouseleave', r.hide);
if (e && e.target)
e.target.removeEventListener('mousemove', r.move);
};
if (is_touch && IPHONE) {
r.move = function (e) {
r.th.style.left = (e.pageX + 12) + 'px';
r.th.style.top = (e.pageY + 12) + 'px';
};
if (IPHONE) {
var f1 = r.show,
f2 = r.hide,
q = [];
@@ -882,14 +934,14 @@ var tt = (function () {
r.att = function (ctr) {
var _cshow = r.en ? r.cshow : null,
_show = r.en ? r.show : null,
_dshow = r.en ? r.dshow : null,
_hide = r.en ? r.hide : null,
o = ctr.querySelectorAll('*[tt]');
for (var a = o.length - 1; a >= 0; a--) {
o[a].onfocus = _cshow;
o[a].onblur = _hide;
o[a].onmouseenter = _show;
o[a].onmouseenter = _dshow;
o[a].onmouseleave = _hide;
}
r.hide();

View File

@@ -80,6 +80,12 @@ shab64() { sp=$1; f="$2"; v=0; sz=$(stat -c%s "$f"); while true; do w=$((v+sp*10
command -v gdate && date() { gdate "$@"; }; while true; do t=$(date +%s.%N); (time wget http://127.0.0.1:3923/?ls -qO- | jq -C '.files[]|{sz:.sz,ta:.tags.artist,tb:.tags.".bpm"}|del(.[]|select(.==null))' | awk -F\" '/"/{t[$2]++} END {for (k in t){v=t[k];p=sprintf("%" (v+1) "s",v);gsub(/ /,"#",p);printf "\033[36m%s\033[33m%s ",k,p}}') 2>&1 | awk -v ts=$t 'NR==1{t1=$0} NR==2{sub(/.*0m/,"");sub(/s$/,"");t2=$0;c=2; if(t2>0.3){c=3} if(t2>0.8){c=1} } END{sub(/[0-9]{6}$/,"",ts);printf "%s \033[3%dm%s %s\033[0m\n",ts,c,t2,t1}'; sleep 0.1 || break; done
##
## track an up2k upload and print all chunks in file-order
grep '"name": "2021-07-18 02-17-59.mkv"' fug.log | head -n 1 | sed -r 's/.*"hash": \[//; s/\].*//' | tr '"' '\n' | grep -E '^[a-zA-Z0-9_-]{44}$' | while IFS= read -r cid; do cat -n fug.log | grep -vF '"purl": "' | grep -- "$cid"; echo; done | stdbuf -oL tr '\t' ' ' | while IFS=' ' read -r ln _ _ _ _ _ ts ip port msg; do [ -z "$msg" ] && echo && continue; printf '%6s [%s] [%s] %s\n' $ln "$ts" "$ip $port" "$msg"; read -r ln _ _ _ _ _ ts ip port msg < <(cat -n fug.log | tail -n +$((ln+1)) | grep -F "$ip $port" | head -n 1); printf '%6s [%s] [%s] %s\n' $ln "$ts" "$ip $port" "$msg"; done
##
## js oneliners
@@ -185,7 +191,7 @@ about:config >> devtools.debugger.prefs-schema-version = -1
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser,up2k}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser,up2k}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
# download all sfx versions
curl https://api.github.com/repos/9001/copyparty/releases?per_page=100 | jq -r '.[] | .tag_name + " " + .name' | tr -d '\r' | while read v t; do fn="copyparty $v $t.py"; [ -e "$fn" ] || curl https://github.com/9001/copyparty/releases/download/$v/copyparty-sfx.py -Lo "$fn"; done
curl https://api.github.com/repos/9001/copyparty/releases?per_page=100 | jq -r '.[] | .tag_name + " " + .name' | tr -d '\r' | while read v t; do fn="$(printf '%s\n' "copyparty $v $t.py" | tr / -)"; [ -e "$fn" ] || curl https://github.com/9001/copyparty/releases/download/$v/copyparty-sfx.py -Lo "$fn"; done
##

View File

@@ -140,10 +140,10 @@ repack() {
}
repack sfx-full "re gz no-sh"
repack sfx-ent "re no-dd no-ogv"
repack sfx-ent "re no-dd no-ogv gz no-sh"
repack sfx-lite "re no-dd no-ogv no-cm no-hl"
repack sfx-lite "re no-dd no-ogv no-cm no-hl gz no-sh"
repack sfx-ent "re no-dd"
repack sfx-ent "re no-dd gz no-sh"
repack sfx-lite "re no-dd no-cm no-hl"
repack sfx-lite "re no-dd no-cm no-hl gz no-sh"
# move fuse and up2k clients into copyparty-extras/,

View File

@@ -3,7 +3,6 @@ WORKDIR /z
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
ver_hashwasm=4.9.0 \
ver_marked=3.0.4 \
ver_ogvjs=1.8.4 \
ver_mde=2.15.0 \
ver_codemirror=5.62.3 \
ver_fontawesome=5.13.0 \
@@ -15,7 +14,6 @@ ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
RUN mkdir -p /z/dist/no-pk \
&& wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \
&& apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \
&& wget https://github.com/brion/ogv.js/releases/download/$ver_ogvjs/ogvjs-$ver_ogvjs.zip -O ogvjs.zip \
&& wget https://github.com/openpgpjs/asmcrypto.js/archive/$ver_asmcrypto.tar.gz -O asmcrypto.tgz \
&& wget https://github.com/markedjs/marked/archive/v$ver_marked.tar.gz -O marked.tgz \
&& wget https://github.com/Ionaru/easy-markdown-editor/archive/$ver_mde.tar.gz -O mde.tgz \
@@ -23,7 +21,6 @@ RUN mkdir -p /z/dist/no-pk \
&& wget https://github.com/FortAwesome/Font-Awesome/releases/download/$ver_fontawesome/fontawesome-free-$ver_fontawesome-web.zip -O fontawesome.zip \
&& wget https://github.com/google/zopfli/archive/zopfli-$ver_zopfli.tar.gz -O zopfli.tgz \
&& wget https://github.com/Daninet/hash-wasm/releases/download/v$ver_hashwasm/hash-wasm@$ver_hashwasm.zip -O hash-wasm.zip \
&& unzip ogvjs.zip \
&& (mkdir hash-wasm \
&& cd hash-wasm \
&& unzip ../hash-wasm.zip) \
@@ -77,21 +74,6 @@ RUN cd hash-wasm \
&& mv sha512.umd.min.js /z/dist/sha512.hw.js
# build ogvjs
RUN cd ogvjs-$ver_ogvjs \
&& cp -pv \
ogv-worker-audio.js \
ogv-demuxer-ogg-wasm.js \
ogv-demuxer-ogg-wasm.wasm \
ogv-decoder-audio-opus-wasm.js \
ogv-decoder-audio-opus-wasm.wasm \
ogv-decoder-audio-vorbis-wasm.js \
ogv-decoder-audio-vorbis-wasm.wasm \
/z/dist \
&& cp -pv \
ogv-es2017.js /z/dist/ogv.js
# build marked
COPY marked.patch /z/
COPY marked-ln.patch /z/

View File

@@ -16,9 +16,6 @@ help() { exec cat <<'EOF'
#
# `no-sh` makes just the python sfx, skips the sh/unix sfx
#
# `no-ogv` saves ~192k by removing the opus/vorbis audio codecs
# (only affects apple devices; everything else has native support)
#
# `no-cm` saves ~82k by removing easymde/codemirror
# (the fancy markdown editor)
#
@@ -75,7 +72,6 @@ while [ ! -z "$1" ]; do
clean) clean=1 ; ;;
re) repack=1 ; ;;
gz) use_gz=1 ; ;;
no-ogv) no_ogv=1 ; ;;
no-fnt) no_fnt=1 ; ;;
no-hl) no_hl=1 ; ;;
no-dd) no_dd=1 ; ;;
@@ -218,9 +214,6 @@ cat have | while IFS= read -r x; do
done
rm have
[ $no_ogv ] &&
rm -rf copyparty/web/deps/{dynamicaudio,ogv}*
[ $no_cm ] && {
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
echo h > copyparty/web/mde.html

View File

@@ -49,14 +49,6 @@ copyparty/web/deps/easymde.js,
copyparty/web/deps/marked.js,
copyparty/web/deps/mini-fa.css,
copyparty/web/deps/mini-fa.woff,
copyparty/web/deps/ogv-decoder-audio-opus-wasm.js,
copyparty/web/deps/ogv-decoder-audio-opus-wasm.wasm,
copyparty/web/deps/ogv-decoder-audio-vorbis-wasm.js,
copyparty/web/deps/ogv-decoder-audio-vorbis-wasm.wasm,
copyparty/web/deps/ogv-demuxer-ogg-wasm.js,
copyparty/web/deps/ogv-demuxer-ogg-wasm.wasm,
copyparty/web/deps/ogv-worker-audio.js,
copyparty/web/deps/ogv.js,
copyparty/web/deps/prism.js,
copyparty/web/deps/prism.css,
copyparty/web/deps/prismd.css,