mirror of
https://github.com/9001/copyparty.git
synced 2025-11-02 13:03:18 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff8313d0fb | ||
|
|
765294c263 | ||
|
|
d6b5351207 | ||
|
|
a2009bcc6b | ||
|
|
12709a8a0a | ||
|
|
c055baefd2 | ||
|
|
56522599b5 | ||
|
|
664f53b75d | ||
|
|
87200d9f10 | ||
|
|
5c3d0b6520 | ||
|
|
bd49979f4a | ||
|
|
7e606cdd9f | ||
|
|
8b4b7fa794 | ||
|
|
05345ddf8b | ||
|
|
66adb470ad | ||
|
|
e15c8fd146 | ||
|
|
0f09b98a39 | ||
|
|
b4d6f4e24d | ||
|
|
3217fa625b | ||
|
|
e719ff8a47 | ||
|
|
9fcf528d45 | ||
|
|
1ddbf5a158 | ||
|
|
64bf4574b0 | ||
|
|
5649d26077 | ||
|
|
92f923effe | ||
|
|
0d46d548b9 | ||
|
|
062df3f0c3 | ||
|
|
789fb53b8e | ||
|
|
351db5a18f | ||
|
|
aabbd271c8 | ||
|
|
aae8e0171e | ||
|
|
45827a2458 | ||
|
|
726030296f | ||
|
|
6659ab3881 | ||
|
|
c6a103609e | ||
|
|
c6b3f035e5 | ||
|
|
2b0a7e378e | ||
|
|
b75ce909c8 | ||
|
|
229c3f5dab | ||
|
|
ec73094506 |
26
README.md
26
README.md
@@ -20,6 +20,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
||||
|
||||
* top
|
||||
* [quickstart](#quickstart)
|
||||
* [on debian](#on-debian)
|
||||
* [notes](#notes)
|
||||
* [status](#status)
|
||||
* [bugs](#bugs)
|
||||
@@ -68,6 +69,7 @@ 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`
|
||||
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
|
||||
* 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
|
||||
@@ -77,6 +79,19 @@ you may also want these, especially on servers:
|
||||
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
|
||||
|
||||
|
||||
### on debian
|
||||
|
||||
recommended steps to enable audio metadata and thumbnails (from images and videos):
|
||||
|
||||
* as root, run the following:
|
||||
`apt install python3 python3-pip python3-dev ffmpeg`
|
||||
|
||||
* then, as the user which will be running copyparty (so hopefully not root), run this:
|
||||
`python3 -m pip install --user -U Pillow pillow-avif-plugin`
|
||||
|
||||
(skipped `pyheif-pillow-opener` because apparently debian is too old to build it)
|
||||
|
||||
|
||||
## notes
|
||||
|
||||
general:
|
||||
@@ -139,6 +154,8 @@ summary: all planned features work! now please enjoy the bloatening
|
||||
|
||||
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
|
||||
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
|
||||
* dupe files will not have metadata (audio tags etc) displayed in the file listing
|
||||
* because they don't get `up` entries in the db (probably best fix) and `tx_browser` does not `lstat`
|
||||
* probably more, pls let me know
|
||||
|
||||
## not my bugs
|
||||
@@ -178,9 +195,11 @@ the browser has the following hotkeys
|
||||
* `U/O` skip 10sec back/forward
|
||||
* `J/L` prev/next song
|
||||
* `P` play/pause (also starts playing the folder)
|
||||
* when tree-sidebar is open:
|
||||
* `A/D` adjust tree width
|
||||
* in the grid view:
|
||||
* `S` toggle multiselect
|
||||
* `A/D` zoom
|
||||
* shift+`A/D` zoom
|
||||
|
||||
|
||||
## tree-mode
|
||||
@@ -198,6 +217,8 @@ it does static images with Pillow and uses FFmpeg for video files, so you may wa
|
||||
|
||||
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
|
||||
|
||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
||||
|
||||
|
||||
## zip downloads
|
||||
|
||||
@@ -594,13 +615,14 @@ in the `scripts` folder:
|
||||
roughly sorted by priority
|
||||
|
||||
* readme.md as epilogue
|
||||
* single sha512 across all up2k chunks? maybe
|
||||
* reduce up2k roundtrips
|
||||
* start from a chunk index and just go
|
||||
* terminate client on bad data
|
||||
|
||||
discarded ideas
|
||||
|
||||
* single sha512 across all up2k chunks?
|
||||
* crypto.subtle cannot into streaming, would have to use hashwasm, expensive
|
||||
* separate sqlite table per tag
|
||||
* performance fixed by skipping some indexes (`+mt.k`)
|
||||
* audio fingerprinting
|
||||
|
||||
@@ -305,6 +305,7 @@ def run_argparse(argv, formatter):
|
||||
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
|
||||
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
|
||||
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||
ap2.add_argument("--th-covers", metavar="N,N", type=str, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
|
||||
|
||||
ap2 = ap.add_argument_group('database options')
|
||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||
@@ -323,6 +324,9 @@ 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("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
||||
|
||||
ap2 = ap.add_argument_group('video streaming options')
|
||||
ap2.add_argument("--vcr", action="store_true", help="enable video streaming")
|
||||
|
||||
ap2 = ap.add_argument_group('appearance options')
|
||||
ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (0, 11, 25)
|
||||
VERSION = (0, 11, 29)
|
||||
CODENAME = "the grid"
|
||||
BUILD_DT = (2021, 6, 25)
|
||||
BUILD_DT = (2021, 6, 30)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -693,8 +693,10 @@ class AuthSrv(object):
|
||||
self.user = user
|
||||
self.iuser = {v: k for k, v in user.items()}
|
||||
|
||||
self.re_pwd = None
|
||||
pwds = [re.escape(x) for x in self.iuser.keys()]
|
||||
self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
|
||||
if pwds:
|
||||
self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
|
||||
|
||||
# import pprint
|
||||
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
||||
|
||||
@@ -18,6 +18,8 @@ from .util import * # noqa # pylint: disable=unused-wildcard-import
|
||||
from .authsrv import AuthSrv
|
||||
from .szip import StreamZip
|
||||
from .star import StreamTar
|
||||
from .vcr import VCR_Direct
|
||||
from .th_srv import FMT_FF
|
||||
|
||||
if not PY2:
|
||||
unicode = str
|
||||
@@ -55,7 +57,7 @@ class HttpCli(object):
|
||||
|
||||
def log(self, msg, c=0):
|
||||
ptn = self.asrv.re_pwd
|
||||
if ptn.search(msg):
|
||||
if ptn and ptn.search(msg):
|
||||
msg = ptn.sub(self.unpwd, msg)
|
||||
|
||||
self.log_func(self.log_src, msg, c)
|
||||
@@ -72,9 +74,13 @@ class HttpCli(object):
|
||||
if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
|
||||
raise Exception("that was close")
|
||||
|
||||
def j2(self, name, **kwargs):
|
||||
def j2(self, name, **ka):
|
||||
tpl = self.conn.hsrv.j2[name]
|
||||
return tpl.render(**kwargs) if kwargs else tpl
|
||||
if ka:
|
||||
ka["ts"] = self.conn.hsrv.cachebuster()
|
||||
return tpl.render(**ka)
|
||||
|
||||
return tpl
|
||||
|
||||
def run(self):
|
||||
"""returns true if connection can be reused"""
|
||||
@@ -181,6 +187,9 @@ class HttpCli(object):
|
||||
self.rvol, self.wvol, self.avol = [[], [], []]
|
||||
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
|
||||
|
||||
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/")
|
||||
if self.is_rclone:
|
||||
@@ -222,7 +231,9 @@ class HttpCli(object):
|
||||
def send_headers(self, length, status=200, mime=None, headers={}):
|
||||
response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
|
||||
|
||||
if length is not None:
|
||||
if length is None:
|
||||
self.keepalive = False
|
||||
else:
|
||||
response.append("Content-Length: " + unicode(length))
|
||||
|
||||
# close if unknown length, otherwise take client's preference
|
||||
@@ -496,7 +507,7 @@ class HttpCli(object):
|
||||
|
||||
spd1 = get_spd(nbytes, self.t0)
|
||||
spd2 = get_spd(self.conn.nbyte, self.conn.t0)
|
||||
return spd1 + " " + spd2
|
||||
return "{} {} n{}".format(spd1, spd2, self.conn.nreq)
|
||||
|
||||
def handle_post_multipart(self):
|
||||
self.parser = MultipartParser(self.log, self.sr, self.headers)
|
||||
@@ -758,6 +769,12 @@ class HttpCli(object):
|
||||
pwd = self.parser.require("cppwd", 64)
|
||||
self.parser.drop()
|
||||
|
||||
ck, msg = self.get_pwd_cookie(pwd)
|
||||
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
|
||||
return True
|
||||
|
||||
def get_pwd_cookie(self, pwd):
|
||||
if pwd in self.asrv.iuser:
|
||||
msg = "login ok"
|
||||
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
|
||||
@@ -768,9 +785,7 @@ class HttpCli(object):
|
||||
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
||||
|
||||
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
|
||||
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
|
||||
return True
|
||||
return [ck, msg]
|
||||
|
||||
def handle_mkdir(self):
|
||||
new_dir = self.parser.require("name", 512)
|
||||
@@ -1376,6 +1391,7 @@ class HttpCli(object):
|
||||
"md_plug": "true" if self.args.emp else "false",
|
||||
"md_chk_rate": self.args.mcr,
|
||||
"md": boundary,
|
||||
"ts": self.conn.hsrv.cachebuster(),
|
||||
}
|
||||
html = template.render(**targs).encode("utf-8", "replace")
|
||||
html = html.split(boundary.encode("utf-8"))
|
||||
@@ -1551,11 +1567,20 @@ class HttpCli(object):
|
||||
if rem.startswith(".hist/up2k."):
|
||||
raise Pebkac(403)
|
||||
|
||||
if "vcr" in self.uparam:
|
||||
ext = abspath.rsplit(".")[-1]
|
||||
if not self.args.vcr or ext not in FMT_FF:
|
||||
raise Pebkac(403)
|
||||
|
||||
vcr = VCR_Direct(self, abspath)
|
||||
vcr.run()
|
||||
return False
|
||||
|
||||
is_dir = stat.S_ISDIR(st.st_mode)
|
||||
th_fmt = self.uparam.get("th")
|
||||
if th_fmt is not None:
|
||||
if is_dir:
|
||||
for fn in ["folder.png", "folder.jpg"]:
|
||||
for fn in self.args.th_covers.split(","):
|
||||
fp = os.path.join(abspath, fn)
|
||||
if os.path.exists(fp):
|
||||
vrem = "{}/{}".format(vrem.rstrip("/"), fn)
|
||||
@@ -1619,7 +1644,6 @@ class HttpCli(object):
|
||||
|
||||
url_suf = self.urlq()
|
||||
is_ls = "ls" in self.uparam
|
||||
ts = "" # "?{}".format(time.time())
|
||||
|
||||
tpl = "browser"
|
||||
if "b" in self.uparam:
|
||||
@@ -1644,7 +1668,6 @@ class HttpCli(object):
|
||||
"vdir": quotep(self.vpath),
|
||||
"vpnodes": vpnodes,
|
||||
"files": [],
|
||||
"ts": ts,
|
||||
"perms": json.dumps(perms),
|
||||
"taglist": [],
|
||||
"tag_order": [],
|
||||
|
||||
@@ -43,6 +43,7 @@ class HttpConn(object):
|
||||
|
||||
self.t0 = time.time()
|
||||
self.stopping = False
|
||||
self.nreq = 0
|
||||
self.nbyte = 0
|
||||
self.workload = 0
|
||||
self.u2idx = None
|
||||
@@ -188,6 +189,7 @@ class HttpConn(object):
|
||||
if self.workload >= 2 ** 31:
|
||||
self.workload = 100
|
||||
|
||||
self.nreq += 1
|
||||
cli = HttpCli(self)
|
||||
if not cli.run():
|
||||
return
|
||||
|
||||
@@ -4,6 +4,8 @@ from __future__ import print_function, unicode_literals
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import base64
|
||||
import struct
|
||||
import socket
|
||||
import threading
|
||||
|
||||
@@ -25,7 +27,6 @@ except ImportError:
|
||||
sys.exit(1)
|
||||
|
||||
from .__init__ import E, MACOS
|
||||
from .authsrv import AuthSrv
|
||||
from .httpconn import HttpConn
|
||||
|
||||
|
||||
@@ -48,6 +49,8 @@ class HttpSrv(object):
|
||||
self.clients = {}
|
||||
self.workload = 0
|
||||
self.workload_thr_alive = False
|
||||
self.cb_ts = 0
|
||||
self.cb_v = 0
|
||||
|
||||
env = jinja2.Environment()
|
||||
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
||||
@@ -138,11 +141,12 @@ class HttpSrv(object):
|
||||
"shut({}): {}".format(fno, ex),
|
||||
c="1;30",
|
||||
)
|
||||
if ex.errno not in [10038, 10054, 107, 57, 9]:
|
||||
if ex.errno not in [10038, 10054, 107, 57, 49, 9]:
|
||||
# 10038 No longer considered a socket
|
||||
# 10054 Foribly closed by remote
|
||||
# 107 Transport endpoint not connected
|
||||
# 57 Socket is not connected
|
||||
# 49 Can't assign requested address (wifi down)
|
||||
# 9 Bad file descriptor
|
||||
raise
|
||||
finally:
|
||||
@@ -177,3 +181,25 @@ class HttpSrv(object):
|
||||
self.clients[cli] = now
|
||||
|
||||
self.workload = total
|
||||
|
||||
def cachebuster(self):
|
||||
if time.time() - self.cb_ts < 1:
|
||||
return self.cb_v
|
||||
|
||||
with self.mutex:
|
||||
if time.time() - self.cb_ts < 1:
|
||||
return self.cb_v
|
||||
|
||||
v = E.t0
|
||||
try:
|
||||
with os.scandir(os.path.join(E.mod, "web")) as dh:
|
||||
for fh in dh:
|
||||
inf = fh.stat(follow_symlinks=False)
|
||||
v = max(v, inf.st_mtime)
|
||||
except:
|
||||
pass
|
||||
|
||||
v = base64.urlsafe_b64encode(struct.pack(">xxL", int(v)))
|
||||
self.cb_v = v.decode("ascii")[-4:]
|
||||
self.cb_ts = time.time()
|
||||
return self.cb_v
|
||||
|
||||
@@ -1019,7 +1019,8 @@ class Up2k(object):
|
||||
break
|
||||
except:
|
||||
# missing; restart
|
||||
job = None
|
||||
if not self.args.nw:
|
||||
job = None
|
||||
break
|
||||
else:
|
||||
# file contents match, but not the path
|
||||
@@ -1089,6 +1090,9 @@ class Up2k(object):
|
||||
}
|
||||
|
||||
def _untaken(self, fdir, fname, ts, ip):
|
||||
if self.args.nw:
|
||||
return fname
|
||||
|
||||
# TODO broker which avoid this race and
|
||||
# provides a new filename if taken (same as bup)
|
||||
suffix = ".{:.6f}-{}".format(ts, ip)
|
||||
@@ -1098,6 +1102,9 @@ class Up2k(object):
|
||||
def _symlink(self, src, dst):
|
||||
# TODO store this in linktab so we never delete src if there are links to it
|
||||
self.log("linking dupe:\n {0}\n {1}".format(src, dst))
|
||||
if self.args.nw:
|
||||
return
|
||||
|
||||
try:
|
||||
lsrc = src
|
||||
ldst = dst
|
||||
@@ -1175,6 +1182,10 @@ class Up2k(object):
|
||||
if ret > 0:
|
||||
return ret, src
|
||||
|
||||
if self.args.nw:
|
||||
# del self.registry[ptop][wark]
|
||||
return ret, dst
|
||||
|
||||
atomic_move(src, dst)
|
||||
|
||||
if ANYWIN:
|
||||
@@ -1284,6 +1295,10 @@ class Up2k(object):
|
||||
if self.args.dotpart:
|
||||
tnam = "." + tnam
|
||||
|
||||
if self.args.nw:
|
||||
job["tnam"] = tnam
|
||||
return
|
||||
|
||||
suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
|
||||
with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
|
||||
f, job["tnam"] = f["orz"]
|
||||
|
||||
80
copyparty/vcr.py
Normal file
80
copyparty/vcr.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# coding: utf-8
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import time
|
||||
import shlex
|
||||
import subprocess as sp
|
||||
|
||||
from .__init__ import PY2
|
||||
from .util import fsenc
|
||||
|
||||
|
||||
class VCR_Direct(object):
|
||||
def __init__(self, cli, fpath):
|
||||
self.cli = cli
|
||||
self.fpath = fpath
|
||||
|
||||
self.log_func = cli.log_func
|
||||
self.log_src = cli.log_src
|
||||
|
||||
def log(self, msg, c=0):
|
||||
self.log_func(self.log_src, "vcr: {}".format(msg), c)
|
||||
|
||||
def run(self):
|
||||
opts = self.cli.uparam
|
||||
|
||||
# fmt: off
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-nostdin",
|
||||
"-hide_banner",
|
||||
"-v", "warning",
|
||||
"-i", fsenc(self.fpath),
|
||||
"-vf", "scale=640:-4",
|
||||
"-c:a", "libopus",
|
||||
"-b:a", "128k",
|
||||
"-c:v", "libvpx",
|
||||
"-deadline", "realtime",
|
||||
"-row-mt", "1"
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
if "ss" in opts:
|
||||
cmd.extend(["-ss", opts["ss"]])
|
||||
|
||||
if "crf" in opts:
|
||||
cmd.extend(["-b:v", "0", "-crf", opts["crf"]])
|
||||
else:
|
||||
cmd.extend(["-b:v", "{}M".format(opts.get("mbps", 1.2))])
|
||||
|
||||
cmd.extend(["-f", "webm", "-"])
|
||||
|
||||
comp = str if not PY2 else unicode
|
||||
cmd = [x.encode("utf-8") if isinstance(x, comp) else x for x in cmd]
|
||||
|
||||
self.log(" ".join([shlex.quote(x.decode("utf-8", "replace")) for x in cmd]))
|
||||
|
||||
p = sp.Popen(cmd, stdout=sp.PIPE)
|
||||
|
||||
self.cli.send_headers(None, mime="video/webm")
|
||||
|
||||
fails = 0
|
||||
while True:
|
||||
self.log("read")
|
||||
buf = p.stdout.read(1024 * 4)
|
||||
if not buf:
|
||||
fails += 1
|
||||
if p.poll() is not None or fails > 30:
|
||||
self.log("ffmpeg exited")
|
||||
return
|
||||
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
fails = 0
|
||||
try:
|
||||
self.cli.s.sendall(buf)
|
||||
except:
|
||||
self.log("client disconnected")
|
||||
p.kill()
|
||||
return
|
||||
@@ -119,7 +119,7 @@ window.baguetteBox = (function () {
|
||||
var gallery = [];
|
||||
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
|
||||
var imageElementClickHandler = function (event) {
|
||||
if (event && event.ctrlKey)
|
||||
if (event && (event.ctrlKey || event.metaKey))
|
||||
return true;
|
||||
|
||||
event.preventDefault ? event.preventDefault() : event.returnValue = false;
|
||||
|
||||
@@ -607,7 +607,7 @@ input.eq_gain {
|
||||
#srch_q {
|
||||
white-space: pre;
|
||||
color: #f80;
|
||||
height: 1em;
|
||||
min-height: 1em;
|
||||
margin: .2em 0 -1em 1.6em;
|
||||
}
|
||||
#tq_raw {
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<title>⇆🎉 {{ title }}</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css{{ ts }}">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css{{ ts }}">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css?_={{ ts }}">
|
||||
{%- if css %}
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="{{ css }}{{ ts }}">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="{{ css }}?_={{ ts }}">
|
||||
{%- endif %}
|
||||
</head>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
<div id="epi" class="logue">{{ logues[1] }}</div>
|
||||
|
||||
<h2><a href="?h">control-panel</a></h2>
|
||||
<h2><a href="/?h">control-panel</a></h2>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -127,9 +127,9 @@
|
||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||
have_zip = {{ have_zip|tojson }};
|
||||
</script>
|
||||
<script src="/.cpr/util.js{{ ts }}"></script>
|
||||
<script src="/.cpr/browser.js{{ ts }}"></script>
|
||||
<script src="/.cpr/up2k.js{{ ts }}"></script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/browser.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/up2k.js?_={{ ts }}"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -78,7 +78,7 @@ ebi('op_up2k').innerHTML = (
|
||||
' <tr>\n' +
|
||||
' <td>\n' +
|
||||
' <a href="#" id="nthread_sub">–</a><input\n' +
|
||||
' class="txtbox" id="nthread" value="2"/><a\n' +
|
||||
' class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' +
|
||||
' href="#" id="nthread_add">+</a><br /> \n' +
|
||||
' </td>\n' +
|
||||
' </tr>\n' +
|
||||
@@ -237,13 +237,17 @@ var mpl = (function () {
|
||||
'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
|
||||
'</div></div>' +
|
||||
|
||||
'<div><h3>tint</h3><div>' +
|
||||
'<input type="text" id="pb_tint" size="3" value="0" tt="background level (0-100) on the seekbar$Nto make buffering less distracting" />' +
|
||||
'</div></div>' +
|
||||
|
||||
'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
|
||||
|
||||
var r = {
|
||||
"pb_mode": sread('pb_mode') || 'loop-folder',
|
||||
"preload": bcfg_get('au_preload', true),
|
||||
"clip": bcfg_get('au_npclip', false),
|
||||
"os_ctl": bcfg_get('au_os_ctl', true) && have_mctl,
|
||||
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
||||
"osd_cv": bcfg_get('au_osd_cv', true),
|
||||
};
|
||||
|
||||
@@ -290,6 +294,19 @@ var mpl = (function () {
|
||||
draw_pb_mode();
|
||||
}
|
||||
|
||||
function set_tint() {
|
||||
var tint = icfg_get('pb_tint', 0);
|
||||
if (!tint)
|
||||
ebi('barbuf').style.removeProperty('background');
|
||||
else
|
||||
ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')';
|
||||
}
|
||||
ebi('pb_tint').oninput = function (e) {
|
||||
swrite('pb_tint', this.value);
|
||||
set_tint();
|
||||
};
|
||||
set_tint();
|
||||
|
||||
r.pp = function () {
|
||||
if (!r.os_ctl)
|
||||
return;
|
||||
@@ -522,37 +539,38 @@ function get_np() {
|
||||
|
||||
// toggle player widget
|
||||
var widget = (function () {
|
||||
var ret = {},
|
||||
var r = {},
|
||||
widget = ebi('widget'),
|
||||
wtico = ebi('wtico'),
|
||||
nptxt = ebi('nptxt'),
|
||||
npirc = ebi('npirc'),
|
||||
touchmode = false,
|
||||
side_open = false,
|
||||
was_paused = true;
|
||||
|
||||
ret.open = function () {
|
||||
if (side_open)
|
||||
r.is_open = false;
|
||||
|
||||
r.open = function () {
|
||||
if (r.is_open)
|
||||
return false;
|
||||
|
||||
widget.className = 'open';
|
||||
side_open = true;
|
||||
r.is_open = true;
|
||||
return true;
|
||||
};
|
||||
ret.close = function () {
|
||||
if (!side_open)
|
||||
r.close = function () {
|
||||
if (!r.is_open)
|
||||
return false;
|
||||
|
||||
widget.className = '';
|
||||
side_open = false;
|
||||
r.is_open = false;
|
||||
return true;
|
||||
};
|
||||
ret.toggle = function (e) {
|
||||
ret.open() || ret.close();
|
||||
r.toggle = function (e) {
|
||||
r.open() || r.close();
|
||||
ev(e);
|
||||
return false;
|
||||
};
|
||||
ret.paused = function (paused) {
|
||||
r.paused = function (paused) {
|
||||
if (was_paused != paused) {
|
||||
was_paused = paused;
|
||||
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
||||
@@ -560,7 +578,7 @@ var widget = (function () {
|
||||
};
|
||||
wtico.onclick = function (e) {
|
||||
if (!touchmode)
|
||||
ret.toggle(e);
|
||||
r.toggle(e);
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -591,7 +609,7 @@ var widget = (function () {
|
||||
document.body.removeChild(o);
|
||||
}, 500);
|
||||
};
|
||||
return ret;
|
||||
return r;
|
||||
})();
|
||||
|
||||
|
||||
@@ -857,7 +875,10 @@ function playpause(e) {
|
||||
ebi('bplay').onclick = playpause;
|
||||
ebi('bprev').onclick = prev_song;
|
||||
ebi('bnext').onclick = next_song;
|
||||
ebi('barpos').onclick = function (e) {
|
||||
|
||||
var bar = ebi('barpos');
|
||||
|
||||
bar.onclick = function (e) {
|
||||
if (!mp.au) {
|
||||
play(0, true);
|
||||
return mp.fade_in();
|
||||
@@ -868,6 +889,19 @@ function playpause(e) {
|
||||
|
||||
seek_au_mul(x * 1.0 / rect.width);
|
||||
};
|
||||
|
||||
if (!is_touch)
|
||||
bar.onwheel = function (e) {
|
||||
var dist = Math.sign(e.deltaY) * 10;
|
||||
if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
|
||||
dist = e.deltaY;
|
||||
|
||||
if (!dist || !mp.au)
|
||||
return true;
|
||||
|
||||
seek_au_rel(dist);
|
||||
ev(e);
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -1186,8 +1220,11 @@ function play(tid, is_ev, seek, call_depth) {
|
||||
mp.stopfade(true);
|
||||
|
||||
var tn = tid;
|
||||
if ((tn + '').indexOf('f-') === 0)
|
||||
if ((tn + '').indexOf('f-') === 0) {
|
||||
tn = mp.order.indexOf(tn);
|
||||
if (tn < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
if (tn >= mp.order.length) {
|
||||
if (mpl.pb_mode == 'loop-folder') {
|
||||
@@ -1385,8 +1422,7 @@ function autoplay_blocked(seek) {
|
||||
}
|
||||
|
||||
|
||||
// autoplay linked track
|
||||
(function () {
|
||||
function play_linked() {
|
||||
var v = location.hash;
|
||||
if (v && v.indexOf('#af-') === 0) {
|
||||
var id = v.slice(2).split('&');
|
||||
@@ -1402,7 +1438,7 @@ function autoplay_blocked(seek) {
|
||||
|
||||
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
|
||||
var thegrid = (function () {
|
||||
@@ -1508,35 +1544,61 @@ var thegrid = (function () {
|
||||
}
|
||||
setsz();
|
||||
|
||||
function seltgl(e) {
|
||||
if (e && e.ctrlKey)
|
||||
function gclick(e) {
|
||||
if (e && (e.ctrlKey || e.metaKey))
|
||||
return true;
|
||||
|
||||
ev(e);
|
||||
var oth = ebi(this.getAttribute('ref')),
|
||||
td = oth.parentNode.nextSibling,
|
||||
href = this.getAttribute('href'),
|
||||
aplay = ebi('a' + oth.getAttribute('id')),
|
||||
is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
|
||||
is_vid = /\.(av1|asf|avi|flv|m4v|mkv|mjpeg|mjpg|mpg|mpeg|mpg2|mpeg2|h264|avc|h265|hevc|mov|3gp|mp4|ts|mpegts|nut|ogv|ogm|rm|vob|webm|wmv)(\?|$)/i.test(href),
|
||||
in_tree = null,
|
||||
have_sel = QS('#files tr.sel'),
|
||||
td = oth.closest('td').nextSibling,
|
||||
tr = td.parentNode;
|
||||
|
||||
td.click();
|
||||
this.setAttribute('class', tr.getAttribute('class'));
|
||||
}
|
||||
if (/\/(\?|$)/.test(href)) {
|
||||
var ta = QSA('#treeul a.hl+ul>li>a+a'),
|
||||
txt = oth.textContent.slice(0, -1);
|
||||
|
||||
function bgopen(e) {
|
||||
for (var a = 0, aa = ta.length; a < aa; a++) {
|
||||
if (ta[a].textContent == txt) {
|
||||
in_tree = ta[a];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (r.sel) {
|
||||
td.click();
|
||||
this.setAttribute('class', tr.getAttribute('class'));
|
||||
}
|
||||
else if (widget.is_open && aplay)
|
||||
aplay.click();
|
||||
|
||||
else if (in_tree && !have_sel)
|
||||
in_tree.click();
|
||||
|
||||
else if (is_vid)
|
||||
window.open(href + (href.indexOf('?') === -1 ? '?' : '&') + 'vcr', '_blank');
|
||||
|
||||
else if (!is_img && have_sel)
|
||||
window.open(href, '_blank');
|
||||
|
||||
else return true;
|
||||
ev(e);
|
||||
var url = this.getAttribute('href');
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
r.loadsel = function () {
|
||||
if (r.dirty)
|
||||
return;
|
||||
|
||||
var ths = QSA('#ggrid>a'),
|
||||
have_sel = !!QS('#files tr.sel');
|
||||
var ths = QSA('#ggrid>a');
|
||||
|
||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||
ths[a].onclick = r.sel ? seltgl : have_sel ? bgopen : null;
|
||||
ths[a].setAttribute('class', ebi(ths[a].getAttribute('ref')).parentNode.parentNode.getAttribute('class'));
|
||||
var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
|
||||
ths[a].setAttribute('class', tr.getAttribute('class'));
|
||||
}
|
||||
var uns = QS('#ggrid a[ref="unsearch"]');
|
||||
if (uns)
|
||||
@@ -1567,6 +1629,8 @@ var thegrid = (function () {
|
||||
|
||||
if (r.thumbs) {
|
||||
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
|
||||
if (href == "#")
|
||||
ihref = '/.cpr/ico/⏏️';
|
||||
}
|
||||
else if (isdir) {
|
||||
ihref = '/.cpr/ico/folder';
|
||||
@@ -1594,6 +1658,11 @@ var thegrid = (function () {
|
||||
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
||||
}
|
||||
ebi('ggrid').innerHTML = html.join('\n');
|
||||
|
||||
var ths = QSA('#ggrid>a');
|
||||
for (var a = 0, aa = ths.length; a < aa; a++)
|
||||
ths[a].onclick = gclick;
|
||||
|
||||
r.dirty = false;
|
||||
r.bagit();
|
||||
r.loadsel();
|
||||
@@ -1681,10 +1750,14 @@ document.onkeydown = function (e) {
|
||||
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
||||
return;
|
||||
|
||||
if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing)
|
||||
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||
return;
|
||||
|
||||
var k = (e.code + ''), pos = -1, n;
|
||||
|
||||
if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
|
||||
return;
|
||||
|
||||
if (k.indexOf('Digit') === 0)
|
||||
pos = parseInt(k.slice(-1)) * 0.1;
|
||||
|
||||
@@ -1720,6 +1793,14 @@ document.onkeydown = function (e) {
|
||||
if (k == 'KeyT')
|
||||
return ebi('thumbs').click();
|
||||
|
||||
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
|
||||
if (k == 'KeyA')
|
||||
return QS('#twig').click();
|
||||
|
||||
if (k == 'KeyD')
|
||||
return QS('#twobytwo').click();
|
||||
}
|
||||
|
||||
if (thegrid.en) {
|
||||
if (k == 'KeyS')
|
||||
return ebi('gridsel').click();
|
||||
@@ -1802,6 +1883,7 @@ document.onkeydown = function (e) {
|
||||
}
|
||||
|
||||
var search_timeout,
|
||||
defer_timeout,
|
||||
search_in_progress = 0;
|
||||
|
||||
function ev_search_input() {
|
||||
@@ -1816,9 +1898,29 @@ document.onkeydown = function (e) {
|
||||
if (id != "q_raw")
|
||||
encode_query();
|
||||
|
||||
clearTimeout(search_timeout);
|
||||
if (Date.now() - search_in_progress > 30 * 1000)
|
||||
set_vq();
|
||||
|
||||
clearTimeout(defer_timeout);
|
||||
defer_timeout = setTimeout(try_search, 2000);
|
||||
try_search();
|
||||
}
|
||||
|
||||
function try_search() {
|
||||
if (Date.now() - search_in_progress > 30 * 1000) {
|
||||
clearTimeout(defer_timeout);
|
||||
clearTimeout(search_timeout);
|
||||
search_timeout = setTimeout(do_search, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function set_vq() {
|
||||
if (search_in_progress)
|
||||
return;
|
||||
|
||||
var q = ebi('q_raw').value,
|
||||
vq = ebi('files').getAttribute('q_raw');
|
||||
|
||||
srch_msg(false, (q == vq) ? '' : 'search results below are from a previous query:\n ' + (vq ? vq : '(*)'));
|
||||
}
|
||||
|
||||
function encode_query() {
|
||||
@@ -1888,7 +1990,8 @@ document.onkeydown = function (e) {
|
||||
xhr.setRequestHeader('Content-Type', 'text/plain');
|
||||
xhr.onreadystatechange = xhr_search_results;
|
||||
xhr.ts = Date.now();
|
||||
xhr.send(JSON.stringify({ "q": ebi('q_raw').value }));
|
||||
xhr.q_raw = ebi('q_raw').value;
|
||||
xhr.send(JSON.stringify({ "q": xhr.q_raw }));
|
||||
}
|
||||
|
||||
function xhr_search_results() {
|
||||
@@ -1959,6 +2062,8 @@ document.onkeydown = function (e) {
|
||||
|
||||
ofiles.innerHTML = html.join('\n');
|
||||
ofiles.setAttribute("ts", this.ts);
|
||||
ofiles.setAttribute("q_raw", this.q_raw);
|
||||
set_vq();
|
||||
mukey.render();
|
||||
reload_browser();
|
||||
filecols.set_style(['File Name']);
|
||||
@@ -1970,6 +2075,7 @@ document.onkeydown = function (e) {
|
||||
ev(e);
|
||||
treectl.show();
|
||||
ebi('files').innerHTML = orig_html;
|
||||
ebi('files').removeAttribute('q_raw');
|
||||
orig_html = null;
|
||||
msel.render();
|
||||
reload_browser();
|
||||
@@ -2188,6 +2294,9 @@ var treectl = (function () {
|
||||
}
|
||||
|
||||
function treego(e) {
|
||||
if (e && (e.ctrlKey || e.metaKey))
|
||||
return true;
|
||||
|
||||
ev(e);
|
||||
if (this.getAttribute('class') == 'hl' &&
|
||||
this.previousSibling.textContent == '-') {
|
||||
@@ -2965,3 +3074,4 @@ function reload_browser(not_mp) {
|
||||
reload_browser(true);
|
||||
mukey.render();
|
||||
msel.render();
|
||||
play_linked();
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<div>{{ logues[1] }}</div><br />
|
||||
{%- endif %}
|
||||
|
||||
<h2><a href="{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||
<h2><a href="/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<title>📝🎉 {{ title }}</title> <!-- 📜 -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||
<link href="/.cpr/md.css" rel="stylesheet">
|
||||
<link href="/.cpr/md.css?_={{ ts }}" rel="stylesheet">
|
||||
{%- if edit %}
|
||||
<link href="/.cpr/md2.css" rel="stylesheet">
|
||||
<link href="/.cpr/md2.css?_={{ ts }}" rel="stylesheet">
|
||||
{%- endif %}
|
||||
</head>
|
||||
<body>
|
||||
@@ -146,10 +146,10 @@ var md_opt = {
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js"></script>
|
||||
<script src="/.cpr/deps/marked.js"></script>
|
||||
<script src="/.cpr/md.js"></script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/md.js?_={{ ts }}"></script>
|
||||
{%- if edit %}
|
||||
<script src="/.cpr/md2.js"></script>
|
||||
<script src="/.cpr/md2.js?_={{ ts }}"></script>
|
||||
{%- endif %}
|
||||
</body></html>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<title>📝🎉 {{ title }}</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||
<link href="/.cpr/mde.css" rel="stylesheet">
|
||||
<link href="/.cpr/deps/mini-fa.css" rel="stylesheet">
|
||||
<link href="/.cpr/deps/easymde.css" rel="stylesheet">
|
||||
<link href="/.cpr/mde.css?_={{ ts }}" rel="stylesheet">
|
||||
<link href="/.cpr/deps/mini-fa.css?_={{ ts }}" rel="stylesheet">
|
||||
<link href="/.cpr/deps/easymde.css?_={{ ts }}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mw">
|
||||
@@ -43,7 +43,7 @@ var lightswitch = (function () {
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js"></script>
|
||||
<script src="/.cpr/deps/easymde.js"></script>
|
||||
<script src="/.cpr/mde.js"></script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/mde.js?_={{ ts }}"></script>
|
||||
</body></html>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>copyparty</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>copyparty</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
window.onerror = vis_exh;
|
||||
|
||||
|
||||
function goto_up2k() {
|
||||
if (up2k === false)
|
||||
@@ -16,17 +14,19 @@ function goto_up2k() {
|
||||
|
||||
// chrome requires https to use crypto.subtle,
|
||||
// usually it's undefined but some chromes throw on invoke
|
||||
var up2k = null;
|
||||
var sha_js = window.WebAssembly ? 'hw' : 'ac'; // ff53,c57,sa11
|
||||
var up2k = null,
|
||||
sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
|
||||
m = 'will use ' + sha_js + ' instead of native sha512 due to';
|
||||
|
||||
try {
|
||||
var cf = crypto.subtle || crypto.webkitSubtle;
|
||||
cf.digest('SHA-512', new Uint8Array(1)).then(
|
||||
function (x) { console.log('sha-ok'); up2k = up2k_init(cf); },
|
||||
function (x) { console.log('sha-ng:', x); up2k = up2k_init(false); }
|
||||
function (x) { console.log(m, x); up2k = up2k_init(false); }
|
||||
);
|
||||
}
|
||||
catch (ex) {
|
||||
console.log('sha-na:', ex);
|
||||
console.log(m, ex);
|
||||
try {
|
||||
up2k = up2k_init(false);
|
||||
}
|
||||
@@ -142,7 +142,7 @@ function U2pvis(act, btns) {
|
||||
this.tail = -1;
|
||||
this.wsz = 3;
|
||||
|
||||
this.addfile = function (entry, sz) {
|
||||
this.addfile = function (entry, sz, draw) {
|
||||
this.tab.push({
|
||||
"hn": entry[0],
|
||||
"ht": entry[1],
|
||||
@@ -156,6 +156,9 @@ function U2pvis(act, btns) {
|
||||
"bd0": 0 // upload start
|
||||
});
|
||||
this.ctr["q"]++;
|
||||
if (!draw)
|
||||
return;
|
||||
|
||||
this.drawcard("q");
|
||||
if (this.act == "q") {
|
||||
this.addrow(this.tab.length - 1);
|
||||
@@ -256,6 +259,41 @@ function U2pvis(act, btns) {
|
||||
var obj = ebi('f{0}p'.format(fobj.n)),
|
||||
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
||||
|
||||
if (!obj) { //} || true) {
|
||||
var msg = [
|
||||
"act", this.act,
|
||||
"in", fo.in,
|
||||
"is_act", this.is_act(fo.in),
|
||||
"head", this.head,
|
||||
"tail", this.tail,
|
||||
"nfile", fobj.n,
|
||||
"name", fobj.name,
|
||||
"sz", fobj.size,
|
||||
"bytesDelta", delta,
|
||||
"bytesDone", fo.bd,
|
||||
],
|
||||
m2 = '',
|
||||
ds = QSA("#u2tab>tbody>tr>td:first-child>a:last-child");
|
||||
|
||||
for (var a = 0; a < msg.length; a += 2)
|
||||
m2 += msg[a] + '=' + msg[a + 1] + ', ';
|
||||
|
||||
console.log(m2);
|
||||
|
||||
for (var a = 0, aa = ds.length; a < aa; a++) {
|
||||
var id = ds[a].parentNode.getAttribute('id').slice(1, -1);
|
||||
console.log("dom %d/%d = [%s] in(%s) is_act(%s) %s",
|
||||
a, aa, id, this.tab[id].in, this.is_act(fo.in), ds[a].textContent);
|
||||
}
|
||||
|
||||
for (var a = 0, aa = this.tab.length; a < aa; a++)
|
||||
if (this.is_act(this.tab[a].in))
|
||||
console.log("tab %d/%d = sz %s", a, aa, this.tab[a].bt);
|
||||
|
||||
console.log("a");
|
||||
throw 42;
|
||||
}
|
||||
|
||||
obj.innerHTML = fo.hp;
|
||||
obj.style.color = '#fff';
|
||||
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
||||
@@ -276,12 +314,14 @@ function U2pvis(act, btns) {
|
||||
this.drawcard(oldcat);
|
||||
this.drawcard(newcat);
|
||||
if (this.is_act(newcat)) {
|
||||
this.tail++;
|
||||
this.tail = Math.max(this.tail, nfile + 1);
|
||||
if (!ebi('f' + nfile))
|
||||
this.addrow(nfile);
|
||||
}
|
||||
else if (this.is_act(oldcat)) {
|
||||
this.head++;
|
||||
while (this.head < Math.min(this.tab.length, this.tail) && (this.head == nfile || !this.is_act(this.tab[this.head].in)))
|
||||
this.head++;
|
||||
|
||||
if (!bz_act) {
|
||||
var tr = ebi("f" + nfile);
|
||||
tr.parentNode.removeChild(tr);
|
||||
@@ -350,8 +390,21 @@ function U2pvis(act, btns) {
|
||||
}
|
||||
}
|
||||
if (this.head == -1) {
|
||||
this.head = this.tab.length;
|
||||
this.tail = this.head - 1;
|
||||
var precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 },
|
||||
postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {};
|
||||
|
||||
for (var a = 0; a < this.tab.length; a++) {
|
||||
var rt = this.tab[a].in;
|
||||
if (precard[rt]) {
|
||||
this.head = a + 1;
|
||||
this.tail = a;
|
||||
}
|
||||
else if (postcard[rt]) {
|
||||
this.head = a;
|
||||
this.tail = a - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (card == "bz") {
|
||||
for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) {
|
||||
@@ -598,14 +651,50 @@ function up2k_init(subtle) {
|
||||
}
|
||||
}
|
||||
|
||||
function read_dirs(rd, pf, dirs, good, bad) {
|
||||
function rd_flatten(pf, dirs) {
|
||||
var ret = jcp(pf);
|
||||
for (var a = 0; a < dirs.length; a++)
|
||||
ret.push(dirs.fullPath || '');
|
||||
|
||||
ret.sort();
|
||||
return ret;
|
||||
}
|
||||
|
||||
var rd_missing_ref = [];
|
||||
function read_dirs(rd, pf, dirs, good, bad, spins) {
|
||||
spins = spins || 0;
|
||||
if (++spins == 5)
|
||||
rd_missing_ref = rd_flatten(pf, dirs);
|
||||
|
||||
if (spins == 200) {
|
||||
var missing = rd_flatten(pf, dirs),
|
||||
match = rd_missing_ref.length == missing.length,
|
||||
aa = match ? missing.length : 0;
|
||||
|
||||
missing.sort();
|
||||
for (var a = 0; a < aa; a++)
|
||||
if (rd_missing_ref[a] != missing[a])
|
||||
match = false;
|
||||
|
||||
if (match) {
|
||||
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:'.format(missing.length)];
|
||||
for (var a = 0; a < Math.min(20, missing.length); a++)
|
||||
msg.push(missing[a]);
|
||||
|
||||
alert(msg.join('\n-- '));
|
||||
dirs = [];
|
||||
pf = [];
|
||||
}
|
||||
spins = 0;
|
||||
}
|
||||
|
||||
if (!dirs.length) {
|
||||
if (!pf.length)
|
||||
return gotallfiles(good, bad);
|
||||
|
||||
console.log("retry pf, " + pf.length);
|
||||
setTimeout(function () {
|
||||
read_dirs(rd, pf, dirs, good, bad);
|
||||
read_dirs(rd, pf, dirs, good, bad, spins);
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
@@ -645,7 +734,7 @@ function up2k_init(subtle) {
|
||||
dirs.shift();
|
||||
rd = null;
|
||||
}
|
||||
return read_dirs(rd, pf, dirs, good, bad);
|
||||
return read_dirs(rd, pf, dirs, good, bad, spins);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -670,41 +759,50 @@ function up2k_init(subtle) {
|
||||
if (ask_up && !fsearch && !confirm(msg.join('\n')))
|
||||
return;
|
||||
|
||||
var seen = {},
|
||||
evpath = get_evpath(),
|
||||
draw_each = good_files.length < 50;
|
||||
|
||||
for (var a = 0; a < st.files.length; a++)
|
||||
seen[st.files[a].name + '\n' + st.files[a].size] = 1;
|
||||
|
||||
for (var a = 0; a < good_files.length; a++) {
|
||||
var fobj = good_files[a][0],
|
||||
now = Date.now(),
|
||||
lmod = fobj.lastModified || now;
|
||||
|
||||
var entry = {
|
||||
"n": parseInt(st.files.length.toString()),
|
||||
"n": st.files.length,
|
||||
"t0": now,
|
||||
"fobj": fobj,
|
||||
"name": good_files[a][1],
|
||||
"size": fobj.size,
|
||||
"lmod": lmod / 1000,
|
||||
"purl": get_evpath(),
|
||||
"purl": evpath,
|
||||
"done": false,
|
||||
"hash": []
|
||||
};
|
||||
},
|
||||
key = entry.name + '\n' + entry.size;
|
||||
|
||||
var skip = false;
|
||||
for (var b = 0; b < st.files.length; b++)
|
||||
if (entry.name == st.files[b].name &&
|
||||
entry.size == st.files[b].size)
|
||||
skip = true;
|
||||
|
||||
if (skip)
|
||||
if (seen[key])
|
||||
continue;
|
||||
|
||||
seen[key] = 1;
|
||||
|
||||
pvis.addfile([
|
||||
fsearch ? esc(entry.name) : linksplit(
|
||||
uricom_dec(entry.purl)[0] + entry.name).join(' '),
|
||||
'📐 hash',
|
||||
''
|
||||
], fobj.size);
|
||||
], fobj.size, draw_each);
|
||||
|
||||
st.files.push(entry);
|
||||
st.todo.hash.push(entry);
|
||||
}
|
||||
if (!draw_each) {
|
||||
pvis.drawcard("q");
|
||||
pvis.changecard(pvis.act);
|
||||
}
|
||||
}
|
||||
ebi('u2btn').addEventListener('drop', gotfile, false);
|
||||
|
||||
@@ -781,7 +879,7 @@ function up2k_init(subtle) {
|
||||
|
||||
clearTimeout(tto);
|
||||
running = true;
|
||||
while (true) {
|
||||
while (window['vis_exh']) {
|
||||
var is_busy = 0 !=
|
||||
st.todo.hash.length +
|
||||
st.todo.handshake.length +
|
||||
@@ -954,7 +1052,7 @@ function up2k_init(subtle) {
|
||||
|
||||
bpend += cdr - car;
|
||||
|
||||
reader.onload = function (e) {
|
||||
function orz(e) {
|
||||
if (!min_filebuf && nch == 1) {
|
||||
min_filebuf = 1;
|
||||
var td = Date.now() - t0;
|
||||
@@ -964,9 +1062,30 @@ function up2k_init(subtle) {
|
||||
}
|
||||
}
|
||||
hash_calc(nch, e.target.result);
|
||||
}
|
||||
reader.onload = function (e) {
|
||||
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||
};
|
||||
reader.onerror = function () {
|
||||
alert('y o u b r o k e i t\nerror: ' + reader.error);
|
||||
var err = reader.error + '';
|
||||
var handled = false;
|
||||
|
||||
if (err.indexOf('NotReadableError') !== -1 || // win10-chrome defender
|
||||
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions
|
||||
) {
|
||||
pvis.seth(t.n, 1, 'OS-error');
|
||||
pvis.seth(t.n, 2, err);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
pvis.move(t.n, 'ng');
|
||||
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
|
||||
st.bytes.uploaded += t.size;
|
||||
return tasker();
|
||||
}
|
||||
|
||||
alert('y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err);
|
||||
};
|
||||
reader.readAsArrayBuffer(
|
||||
bobslice.call(t.fobj, car, cdr));
|
||||
@@ -1047,12 +1166,12 @@ function up2k_init(subtle) {
|
||||
console.log('zombie handshake onerror,', t);
|
||||
return;
|
||||
}
|
||||
console.log('handshake onerror, retrying');
|
||||
console.log('handshake onerror, retrying', t);
|
||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||
st.todo.handshake.unshift(t);
|
||||
tasker();
|
||||
};
|
||||
xhr.onload = function (e) {
|
||||
function orz(e) {
|
||||
if (t.busied != me) {
|
||||
console.log('zombie handshake onload,', t);
|
||||
return;
|
||||
@@ -1092,6 +1211,7 @@ function up2k_init(subtle) {
|
||||
|
||||
if (response.name !== t.name) {
|
||||
// file exists; server renamed us
|
||||
console.log("server-rename [" + t.name + "] to [" + response.name + "]");
|
||||
t.name = response.name;
|
||||
pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' '));
|
||||
}
|
||||
@@ -1196,6 +1316,9 @@ function up2k_init(subtle) {
|
||||
(xhr.responseText && xhr.responseText) ||
|
||||
"no further information"));
|
||||
}
|
||||
}
|
||||
xhr.onload = function (e) {
|
||||
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||
};
|
||||
|
||||
var req = {
|
||||
@@ -1236,40 +1359,56 @@ function up2k_init(subtle) {
|
||||
if (cdr >= t.size)
|
||||
cdr = t.size;
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.upload.onprogress = function (xev) {
|
||||
pvis.prog(t, npart, xev.loaded);
|
||||
};
|
||||
xhr.onload = function (xev) {
|
||||
function orz(xhr) {
|
||||
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
||||
if (xhr.status == 200) {
|
||||
pvis.prog(t, npart, cdr - car);
|
||||
st.bytes.uploaded += cdr - car;
|
||||
t.bytes_uploaded += cdr - car;
|
||||
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
|
||||
t.postlist.splice(t.postlist.indexOf(npart), 1);
|
||||
if (t.postlist.length == 0) {
|
||||
t.t4 = Date.now();
|
||||
pvis.seth(t.n, 1, 'verifying');
|
||||
st.todo.handshake.unshift(t);
|
||||
}
|
||||
tasker();
|
||||
}
|
||||
else
|
||||
else if (txt.indexOf('already got that') !== -1) {
|
||||
console.log("ignoring dupe-segment error");
|
||||
}
|
||||
else {
|
||||
alert("server broke; cu-err {0} on file [{1}]:\n".format(
|
||||
xhr.status, t.name) + (
|
||||
(xhr.response && xhr.response.err) ||
|
||||
(xhr.responseText && xhr.responseText) ||
|
||||
"no further information"));
|
||||
};
|
||||
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||
if (xhr.overrideMimeType)
|
||||
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
||||
xhr.status, t.name) + (txt || "no further information"));
|
||||
return;
|
||||
}
|
||||
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
|
||||
t.postlist.splice(t.postlist.indexOf(npart), 1);
|
||||
if (t.postlist.length == 0) {
|
||||
t.t4 = Date.now();
|
||||
pvis.seth(t.n, 1, 'verifying');
|
||||
st.todo.handshake.unshift(t);
|
||||
}
|
||||
tasker();
|
||||
}
|
||||
function do_send() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.upload.onprogress = function (xev) {
|
||||
pvis.prog(t, npart, xev.loaded);
|
||||
};
|
||||
xhr.onload = function (xev) {
|
||||
try { orz(xhr); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||
};
|
||||
xhr.onerror = function (xev) {
|
||||
if (!window['vis_exh'])
|
||||
return;
|
||||
|
||||
xhr.responseType = 'text';
|
||||
xhr.send(bobslice.call(t.fobj, car, cdr));
|
||||
console.log('chunkpit onerror, retrying', t);
|
||||
do_send();
|
||||
};
|
||||
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||
if (xhr.overrideMimeType)
|
||||
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
||||
|
||||
xhr.responseType = 'text';
|
||||
xhr.send(bobslice.call(t.fobj, car, cdr));
|
||||
}
|
||||
do_send();
|
||||
}
|
||||
|
||||
/////
|
||||
@@ -1309,6 +1448,17 @@ function up2k_init(subtle) {
|
||||
}
|
||||
tt.init();
|
||||
|
||||
function bumpthread2(e) {
|
||||
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||
return;
|
||||
|
||||
if (e.code == 'ArrowUp')
|
||||
bumpthread(1);
|
||||
|
||||
if (e.code == 'ArrowDown')
|
||||
bumpthread(-1);
|
||||
}
|
||||
|
||||
function bumpthread(dir) {
|
||||
try {
|
||||
dir.stopPropagation();
|
||||
@@ -1319,7 +1469,7 @@ function up2k_init(subtle) {
|
||||
if (dir.target) {
|
||||
clmod(obj, 'err', 1);
|
||||
var v = Math.floor(parseInt(obj.value));
|
||||
if (v < 1 || v > 8 || v !== v)
|
||||
if (v < 0 || v > 64 || v !== v)
|
||||
return;
|
||||
|
||||
parallel_uploads = v;
|
||||
@@ -1330,11 +1480,11 @@ function up2k_init(subtle) {
|
||||
|
||||
parallel_uploads += dir;
|
||||
|
||||
if (parallel_uploads < 1)
|
||||
parallel_uploads = 1;
|
||||
if (parallel_uploads < 0)
|
||||
parallel_uploads = 0;
|
||||
|
||||
if (parallel_uploads > 8)
|
||||
parallel_uploads = 8;
|
||||
if (parallel_uploads > 16)
|
||||
parallel_uploads = 16;
|
||||
|
||||
obj.value = parallel_uploads;
|
||||
bumpthread({ "target": 1 })
|
||||
@@ -1430,6 +1580,7 @@ function up2k_init(subtle) {
|
||||
bumpthread(-1);
|
||||
};
|
||||
|
||||
ebi('nthread').onkeydown = bumpthread2;
|
||||
ebi('nthread').addEventListener('input', bumpthread, false);
|
||||
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
||||
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
||||
@@ -1443,7 +1594,10 @@ function up2k_init(subtle) {
|
||||
nodes[a].addEventListener('touchend', nop, false);
|
||||
|
||||
set_fsearch();
|
||||
bumpthread({ "target": 1 })
|
||||
bumpthread({ "target": 1 });
|
||||
if (parallel_uploads < 1)
|
||||
bumpthread(1);
|
||||
|
||||
return { "init_deps": init_deps, "set_fsearch": set_fsearch }
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,6 @@ var is_touch = 'ontouchstart' in window,
|
||||
|
||||
|
||||
// error handler for mobile devices
|
||||
function hcroak(msg) {
|
||||
document.body.innerHTML = msg;
|
||||
window.onerror = undefined;
|
||||
throw 'fatal_err';
|
||||
}
|
||||
function croak(msg) {
|
||||
document.body.textContent = msg;
|
||||
window.onerror = undefined;
|
||||
throw msg;
|
||||
}
|
||||
function esc(txt) {
|
||||
return txt.replace(/[&"<>]/g, function (c) {
|
||||
return {
|
||||
@@ -32,9 +22,12 @@ function esc(txt) {
|
||||
});
|
||||
}
|
||||
function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||
if (!window.onerror)
|
||||
return;
|
||||
|
||||
window.onerror = undefined;
|
||||
window['vis_exh'] = null;
|
||||
var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
|
||||
var html = ['<h1>you hit a bug!</h1><p>please send me a screenshot arigathanks gozaimuch: <code>ed/irc.rizon.net</code> or <code>ed#2644</code><br /> (and if you can, press F12 and include the "Console" tab in the screenshot too)</p><p>',
|
||||
esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
|
||||
|
||||
if (error) {
|
||||
@@ -44,9 +37,13 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||
html.push('<h2>' + find[a] + '</h2>' +
|
||||
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
|
||||
}
|
||||
document.body.style.fontSize = '0.8em';
|
||||
document.body.style.padding = '0 1em 1em 1em';
|
||||
hcroak(html.join('\n'));
|
||||
document.body.innerHTML = html.join('\n');
|
||||
|
||||
var s = mknod('style');
|
||||
s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
|
||||
document.head.appendChild(s);
|
||||
|
||||
throw 'fatal_err';
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +64,9 @@ function ev(e) {
|
||||
if (e.stopPropagation)
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.stopImmediatePropagation)
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
e.returnValue = false;
|
||||
return e;
|
||||
}
|
||||
@@ -360,11 +360,11 @@ function get_vpath() {
|
||||
|
||||
|
||||
function get_pwd() {
|
||||
var pwd = ('; ' + document.cookie).split('; cppwd=');
|
||||
if (pwd.length < 2)
|
||||
return null;
|
||||
var pwd = ('; ' + document.cookie).split('; cppwd=');
|
||||
if (pwd.length < 2)
|
||||
return null;
|
||||
|
||||
return pwd[1].split(';')[0];
|
||||
return pwd[1].split(';')[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -389,6 +389,11 @@ function has(haystack, needle) {
|
||||
}
|
||||
|
||||
|
||||
function jcp(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
|
||||
function sread(key) {
|
||||
if (window.localStorage)
|
||||
return localStorage.getItem(key);
|
||||
|
||||
@@ -6,10 +6,10 @@ import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform,
|
||||
import subprocess as sp
|
||||
|
||||
"""
|
||||
run me with any version of python, i will unpack and run copyparty
|
||||
pls don't edit this file with a text editor,
|
||||
it breaks the compressed stuff at the end
|
||||
|
||||
(but please don't edit this file with a text editor
|
||||
since that would probably corrupt the binary stuff at the end)
|
||||
run me with any version of python, i will unpack and run copyparty
|
||||
|
||||
there's zero binaries! just plaintext python scripts all the way down
|
||||
so you can easily unpack the archive and inspect it for shady stuff
|
||||
|
||||
@@ -108,6 +108,9 @@ class VHttpSrv(object):
|
||||
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
||||
self.j2 = {x: J2_FILES for x in aliases}
|
||||
|
||||
def cachebuster(self):
|
||||
return "a"
|
||||
|
||||
|
||||
class VHttpConn(object):
|
||||
def __init__(self, args, asrv, log, buf):
|
||||
@@ -121,6 +124,7 @@ class VHttpConn(object):
|
||||
self.log_src = "a"
|
||||
self.lf_url = None
|
||||
self.hsrv = VHttpSrv()
|
||||
self.nreq = 0
|
||||
self.nbyte = 0
|
||||
self.workload = 0
|
||||
self.ico = None
|
||||
|
||||
Reference in New Issue
Block a user