Compare commits

...

31 Commits

Author SHA1 Message Date
ed
c110ccb9ae v1.2.10 2022-05-13 01:44:00 +02:00
ed
0143380306 help the query planner 2022-05-13 01:41:39 +02:00
ed
af9000d3c8 v1.2.9 2022-05-12 23:10:54 +02:00
ed
097d798e5e steal colors from monokai 2022-05-12 23:06:37 +02:00
ed
1d9f9f221a louder 2022-05-12 20:55:37 +02:00
ed
214a367f48 be loud about segfaults and such 2022-05-12 20:26:48 +02:00
ed
2fb46551a2 avoid pointless recursion + show scan summary 2022-05-09 23:43:59 +02:00
ed
6bcf330ae0 symlink-checker: print base vpath in nonverbose mode 2022-05-09 20:17:03 +00:00
ed
2075a8b18c skip nonregular files when indexing filesystem 2022-05-09 19:56:17 +00:00
ed
1275ac6c42 start up2k indexing even if no interfaces could bind 2022-05-09 20:38:06 +02:00
ed
708f20b7af remove option to disable spa 2022-05-08 14:29:05 +02:00
ed
a2c0c708e8 focus password field if not logged in 2022-05-07 22:16:12 +02:00
ed
2f2c65d91e improve up2k error messages 2022-05-07 22:15:09 +02:00
ed
cd5fcc7ca7 fix file sel/play background on focus 2022-05-06 21:15:18 +02:00
ed
aa29e7be48 minimal support for browsers without css-variables 2022-05-03 00:52:26 +02:00
ed
93febe34b0 truncate huge ffmpeg errors 2022-05-03 00:32:00 +02:00
ed
f086e6d3c1 best-effort recovery when chrome desyncs the mediaSession 2022-05-02 19:08:37 +02:00
ed
22e51e1c96 compensate for play/pause fades by rewinding a bit 2022-05-02 19:07:16 +02:00
ed
63a5336f31 change modal ok/cancel focus with left/right keys 2022-05-02 19:06:51 +02:00
ed
bfc6c53cc5 ux 2022-05-02 19:06:08 +02:00
ed
236017f310 better dropzones on small screens 2022-05-02 01:08:31 +02:00
ed
0a1d9b4dfd nevermind, not reliable when rproxied 2022-05-01 22:35:34 +02:00
ed
b50d090946 add logout on inactivity + related errorhandling 2022-05-01 22:12:25 +02:00
ed
00b5db52cf notes 2022-05-01 12:02:27 +02:00
ed
24cb30e2c5 support login from ie4 / win3.11 2022-05-01 11:42:19 +02:00
ed
4549145ab5 fix filekeys in basic-html browser 2022-05-01 11:29:51 +02:00
ed
67b0217754 cleanup + readme 2022-04-30 23:37:27 +02:00
ed
ccae9efdf0 safer systemd example (unprivileged user + NAT for port 80 / 443) 2022-04-30 23:28:51 +02:00
ed
59d596b222 add service to autogenerate TLS certificates 2022-04-30 22:54:35 +02:00
ed
4878eb2c45 support symlinks as volume root 2022-04-30 20:26:26 +02:00
ed
7755392f57 redirect to webroot after login 2022-04-30 18:15:09 +02:00
28 changed files with 454 additions and 188 deletions

View File

@@ -881,7 +881,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
| **w3m** (0.5.3/macports) | can browse, login, upload at 100kB/s, mkdir/msg |
| **netsurf** (3.10/arch) | is basically ie6 with much better css (javascript has almost no effect) |
| **opera** (11.60/winxp) | OK: thumbnails, image-viewer, zip-selection, rename/cut/paste. NG: up2k, navpane, markdown, audio |
| **ie4** and **netscape** 4.0 | can browse, upload with `?b=u` |
| **ie4** and **netscape** 4.0 | can browse, upload with `?b=u`, auth with `&pw=wark` |
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
@@ -1175,12 +1175,16 @@ for the `re`pack to work, first run one of the sfx'es once to unpack it
install [Termux](https://termux.com/) (see [ocv.me/termux](https://ocv.me/termux/)) and then copy-paste this into Termux (long-tap) all at once:
```sh
apt update && apt -y full-upgrade && termux-setup-storage && apt -y install python && python -m ensurepip && python -m pip install -U copyparty
apt update && apt -y full-upgrade && apt update && termux-setup-storage && apt -y install python && python -m ensurepip && python -m pip install --user -U copyparty
echo $?
```
after the initial setup, you can launch copyparty at any time by running `copyparty` anywhere in Termux
if you want thumbnails, `apt -y install ffmpeg`
* or if you want to use vips instead, `apt -y install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
# reporting bugs

View File

@@ -29,6 +29,7 @@ however if your copyparty is behind a reverse-proxy, you may want to use [`share
### [`cfssl.sh`](cfssl.sh)
* creates CA and server certificates using cfssl
* give a 3rd argument to install it to your copyparty config
* systemd service at [`systemd/cfssl.service`](systemd/cfssl.service)
# OS integration
init-scripts to start copyparty as a service

View File

@@ -7,7 +7,7 @@ srv_fqdn="$2"
[ -z "$srv_fqdn" ] && {
echo "need arg 1: ca name"
echo "need arg 2: server fqdn"
echo "need arg 2: server fqdn and/or IPs, comma-separated"
echo "optional arg 3: if set, write cert into copyparty cfg"
exit 1
}

View File

@@ -0,0 +1,23 @@
# systemd service which generates a new TLS certificate on each boot,
# that way the one-year expiry time won't cause any issues --
# just have everyone trust the ca.pem once every 10 years
#
# assumptions/placeholder values:
# * this script and copyparty runs as user "cpp"
# * copyparty repo is at ~cpp/dev/copyparty
# * CA is named partylan
# * server IPs = 10.1.2.3 and 192.168.123.1
# * server hostname = party.lan
[Unit]
Description=copyparty certificate generator
Before=copyparty.service
[Service]
User=cpp
Type=oneshot
SyslogIdentifier=cpp-cert
ExecStart=/bin/bash -c 'cd ~/dev/copyparty/contrib && ./cfssl.sh partylan 10.1.2.3,192.168.123.1,party.lan y'
[Install]
WantedBy=multi-user.target

View File

@@ -2,15 +2,19 @@
# and share '/mnt' with anonymous read+write
#
# installation:
# cp -pv copyparty.service /etc/systemd/system && systemctl enable --now copyparty
# cp -pv copyparty.service /etc/systemd/system
# restorecon -vr /etc/systemd/system/copyparty.service
# firewall-cmd --permanent --add-port={80,443,3923}/tcp
# firewall-cmd --reload
# systemctl daemon-reload && systemctl enable --now copyparty
#
# you may want to:
# change "User=cpp" and "/home/cpp/" to another user
# remove the nft lines to only listen on port 3923
# and in the ExecStart= line:
# change '/usr/bin/python3' to another interpreter
# change '/mnt::rw' to another location or permission-set
# remove '-p 80,443,3923' to only listen on port 3923
# add '-q' to disable logging on busy servers
# add '-i 127.0.0.1' to only allow local connections
# add '-e2dsa' to enable filesystem scanning + indexing
# add '-e2ts' to enable metadata indexing
@@ -21,8 +25,8 @@
# python disabling line-buffering, so messages are out-of-order:
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
#
# if you remove -q to enable logging, you may also want to remove the
# following line to enable buffering (slightly better performance):
# unless you add -q to disable logging, you may want to remove the
# following line to allow buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x
#
# keep ExecStartPre before ExecStart, at least on rhel8
@@ -35,8 +39,23 @@ Type=notify
SyslogIdentifier=copyparty
Environment=PYTHONUNBUFFERED=x
ExecReload=/bin/kill -s USR1 $MAINPID
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -e2d -v /mnt::rw
# user to run as + where the TLS certificate is (if any)
User=cpp
Environment=XDG_CONFIG_HOME=/home/cpp/.config
# setup forwarding from ports 80 and 443 to port 3923
ExecStartPre=+/bin/bash -c 'nft -n -a list table nat | awk "/ to :3923 /{print\$NF}" | xargs -rL1 nft delete rule nat prerouting handle; true'
ExecStartPre=+nft add table ip nat
ExecStartPre=+nft -- add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
ExecStartPre=+nft add rule ip nat prerouting tcp dport 80 redirect to :3923
ExecStartPre=+nft add rule ip nat prerouting tcp dport 443 redirect to :3923
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
# copyparty settings
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -e2d -v /mnt::rw
[Install]
WantedBy=multi-user.target

View File

@@ -487,6 +487,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore --no-robots")
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything")
ap2.add_argument("--logout", metavar="H", type=float, default="8086", help="logout clients after H hours of inactivity (0.0028=10sec, 0.1=6min, 24=day, 168=week, 720=month, 8760=year)")
ap2 = ap.add_argument_group('yolo options')
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 2, 8)
VERSION = (1, 2, 10)
CODENAME = "ftp btw"
BUILD_DT = (2022, 4, 30)
BUILD_DT = (2022, 5, 13)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -411,7 +411,7 @@ class VFS(object):
return [abspath, real, virt_vis]
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat):
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat, subvols=True):
"""
recursively yields from ./rem;
rel is a unix-style user-defined vpath (not vfs-related)
@@ -444,9 +444,14 @@ class VFS(object):
wrel = (rel + "/" + rdir).lstrip("/")
wrem = (rem + "/" + rdir).lstrip("/")
for x in self.walk(wrel, wrem, seen, uname, permsets, dots, scandir, lstat):
for x in self.walk(
wrel, wrem, seen, uname, permsets, dots, scandir, lstat, subvols
):
yield x
if not subvols:
return
for n, vfs in sorted(vfs_virt.items()):
if not dots and n.startswith("."):
continue
@@ -1107,7 +1112,7 @@ class AuthSrv(object):
flag_p = "p" in flags
flag_r = "r" in flags
n_bads = 0
bads = []
for v in vols:
v = v[1:]
vtop = "/{}/".format(v) if v else "/"
@@ -1119,10 +1124,19 @@ class AuthSrv(object):
continue
atop = vn.realpath
safeabs = atop + os.sep
g = vn.walk(
vn.vpath, "", [], u, [[True]], True, not self.args.no_scandir, False
vn.vpath,
"",
[],
u,
[[True]],
True,
not self.args.no_scandir,
False,
False,
)
for _, _, vpath, apath, files, _, _ in g:
for _, _, vpath, apath, files, dirs, _ in g:
fnames = [n[0] for n in files]
vpaths = [vpath + "/" + n for n in fnames] if vpath else fnames
vpaths = [vtop + x for x in vpaths]
@@ -1130,21 +1144,28 @@ class AuthSrv(object):
files = [[vpath + "/", apath + os.sep]] + list(zip(vpaths, apaths))
if flag_ln:
files = [x for x in files if not x[1].startswith(atop + os.sep)]
n_bads += len(files)
files = [x for x in files if not x[1].startswith(safeabs)]
if files:
dirs[:] = [] # stop recursion
bads.append(files[0][0])
if flag_v:
msg = [
if not files:
continue
elif flag_v:
msg = [""] + [
'# user "{}", vpath "{}"\n{}'.format(u, vp, ap)
for vp, ap in files
]
else:
msg = [x[1] for x in files]
msg = ["user {}, vol {}: {} =>".format(u, vtop, files[0][0])]
msg += [x[1] for x in files]
if msg:
self.log("\n" + "\n".join(msg))
self.log("\n".join(msg))
if n_bads and flag_p:
if bads:
self.log("\n ".join(["found symlinks leaving volume:"] + bads))
if bads and flag_p:
raise Exception("found symlink leaving volume, and strict is set")
if not flag_r:

View File

@@ -143,6 +143,10 @@ class HttpCli(object):
if self.args.rsp_slp:
time.sleep(self.args.rsp_slp)
self.ua = self.headers.get("user-agent", "")
self.is_rclone = self.ua.startswith("rclone/")
self.is_ancient = self.ua.startswith("Mozilla/4.")
v = self.headers.get("connection", "").lower()
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
self.is_https = (self.headers.get("x-forwarded-proto", "").lower() == "https" or self.tls)
@@ -241,11 +245,9 @@ class HttpCli(object):
self.dvol = self.asrv.vfs.adel[self.uname]
self.gvol = self.asrv.vfs.aget[self.uname]
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
if pwd:
self.out_headerlist.append(("Set-Cookie", self.get_pwd_cookie(pwd)[0]))
self.ua = self.headers.get("user-agent", "")
self.is_rclone = self.ua.startswith("rclone/")
if self.is_rclone:
uparam["raw"] = False
uparam["dots"] = False
@@ -282,10 +284,11 @@ class HttpCli(object):
msg = str(ex) if pex == ex else min_ex()
self.log("{}\033[0m, {}".format(msg, self.vpath), 3)
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
msg = "{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
if self.hint:
msg += "hint: {}\r\n".format(self.hint)
msg = "<pre>" + html_escape(msg)
self.reply(msg.encode("utf-8", "replace"), status=pex.code, volsan=True)
return self.keepalive
except Pebkac:
@@ -1007,9 +1010,15 @@ class HttpCli(object):
pwd = self.parser.require("cppwd", 64)
self.parser.drop()
dst = "/?h"
self.out_headerlist = [
x
for x in self.out_headerlist
if x[0] != "Set-Cookie" or "cppwd=" not in x[1]
]
dst = "/"
if self.vpath:
dst = "/" + quotep(self.vpath)
dst += quotep(self.vpath)
ck, msg = self.get_pwd_cookie(pwd)
html = self.j2("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
@@ -1019,13 +1028,17 @@ class HttpCli(object):
def get_pwd_cookie(self, pwd):
if pwd in self.asrv.iacct:
msg = "login ok"
dur = 60 * 60 * 24 * 365
dur = int(60 * 60 * self.args.logout)
else:
msg = "naw dude"
pwd = "x" # nosec
dur = None
return [gencookie("cppwd", pwd, dur), msg]
r = gencookie("cppwd", pwd, dur)
if self.is_ancient:
r = r.rsplit(" ", 1)[0]
return [r, msg]
def handle_mkdir(self):
new_dir = self.parser.require("name", 512)
@@ -1814,15 +1827,17 @@ class HttpCli(object):
self.redirect("", "?h#cc")
def tx_404(self, is_403=False):
rc = 404
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>'
elif is_403:
m = '<h1>403 forbiddena &nbsp;~┻━┻</h1><p>you\'ll have to log in or <a href="/?h">go home</a></p>'
rc = 403
else:
m = '<h1>404 not found &nbsp;┐( ´ -`)┌</h1><p><a href="/?h">go home</a></p>'
html = self.j2("splash", this=self, qvpath=quotep(self.vpath), msg=m)
self.reply(html.encode("utf-8"), status=404)
self.reply(html.encode("utf-8"), status=rc)
return True
def scanvol(self):
@@ -1865,7 +1880,7 @@ class HttpCli(object):
if self.args.no_stack:
raise Pebkac(403, "the stackdump feature is disabled in server config")
ret = "<pre>{}\n{}".format(time.time(), alltrace())
ret = "<pre>{}\n{}".format(time.time(), html_escape(alltrace()))
self.reply(ret.encode("utf-8"))
def tx_tree(self):
@@ -2178,7 +2193,7 @@ class HttpCli(object):
if self.can_get:
perms.append("get")
url_suf = self.urlq({}, [])
url_suf = self.urlq({}, ["k"])
is_ls = "ls" in self.uparam
is_js = self.args.force_js or self.cookies.get("js") == "y"
@@ -2398,7 +2413,7 @@ class HttpCli(object):
continue
w = r[0][:16]
q = "select k, v from mt where w = ? and k != 'x'"
q = "select k, v from mt where w = ? and +k != 'x'"
try:
for k, v in icur.execute(q, (w,)):
taglist[k] = True

View File

@@ -18,6 +18,7 @@ from .httpcli import HttpCli
from .u2idx import U2idx
from .th_cli import ThumbCli
from .th_srv import HAVE_PIL, HAVE_VIPS
from .mtag import HAVE_FFMPEG
from .ico import Ico
@@ -38,7 +39,7 @@ class HttpConn(object):
self.cert_path = hsrv.cert_path
self.u2fh = hsrv.u2fh
enth = (HAVE_PIL or HAVE_VIPS) and not self.args.no_thumb
enth = (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb
self.thumbcli = ThumbCli(hsrv) if enth else None
self.ico = Ico(self.args)

View File

@@ -8,7 +8,7 @@ import shutil
import subprocess as sp
from .__init__ import PY2, WINDOWS, unicode
from .util import fsenc, fsdec, uncyg, runcmd, REKOBO_LKEY
from .util import fsenc, fsdec, uncyg, runcmd, retchk, REKOBO_LKEY
from .bos import bos
@@ -82,8 +82,9 @@ def ffprobe(abspath, timeout=10):
b"--",
fsenc(abspath),
]
rc = runcmd(cmd, timeout=timeout)
return parse_ffprobe(rc[1])
rc, so, se = runcmd(cmd, timeout=timeout)
retchk(rc, cmd, se)
return parse_ffprobe(so)
def parse_ffprobe(txt):
@@ -491,12 +492,14 @@ class MTag(object):
cmd = ["nice"] + cmd
cmd = [fsenc(x) for x in cmd]
v = sp.check_output(cmd, **args).strip()
rc, v, err = runcmd(cmd, **args)
retchk(rc, cmd, err, self.log, 5)
v = v.strip()
if not v:
continue
if "," not in tagname:
ret[tagname] = v.decode("utf-8")
ret[tagname] = v
else:
v = json.loads(v)
for tag in tagname.split(","):

View File

@@ -136,13 +136,16 @@ class SvcHub(object):
self.broker = Broker(self)
def thr_httpsrv_up(self):
time.sleep(5)
time.sleep(1 if self.args.ign_ebind_all else 5)
expected = self.broker.num_workers * self.tcpsrv.nsrv
failed = expected - self.httpsrv_up
if not failed:
return
if self.args.ign_ebind_all:
if not self.tcpsrv.srv:
for _ in range(self.broker.num_workers):
self.broker.put(False, "cb_httpsrv_up")
return
if self.args.ign_ebind and self.tcpsrv.srv:

View File

@@ -253,7 +253,9 @@ class ThumbSrv(object):
fun(abspath, tpath)
except:
msg = "{} could not create thumbnail of {}\n{}"
self.log(msg.format(fun.__name__, abspath, min_ex()), "1;30")
msg = msg.format(fun.__name__, abspath, min_ex())
c = 1 if "<Signals.SIG" in msg else "1;30"
self.log(msg, c)
with open(tpath, "wb") as _:
pass
@@ -343,6 +345,8 @@ class ThumbSrv(object):
def conv_ffmpeg(self, abspath, tpath):
ret, _ = ffprobe(abspath)
if not ret:
return
ext = abspath.rsplit(".")[-1].lower()
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
@@ -417,8 +421,15 @@ class ThumbSrv(object):
m = "FFmpeg failed because it was compiled without libsox; you must set --th-ff-swr to force swr resampling:\n"
c = 1
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
self.log(m, c=c)
lines = serr.strip("\n").split("\n")
if len(lines) > 50:
lines = lines[:25] + ["[...]"] + lines[-25:]
txt = "\n".join(["ff: " + str(x) for x in lines])
if len(txt) > 5000:
txt = txt[:2500] + "...\nff: [...]\nff: ..." + txt[-2500:]
self.log(m + txt, c=c)
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
def conv_spec(self, abspath, tpath):

View File

@@ -328,7 +328,7 @@ class U2idx(object):
w = hit["w"]
del hit["w"]
tags = {}
q2 = "select k, v from mt where w = ? and k != 'x'"
q2 = "select k, v from mt where w = ? and +k != 'x'"
for k, v2 in cur.execute(q2, (w,)):
taglist[k] = True
tags[k] = v2

View File

@@ -554,12 +554,16 @@ class Up2k(object):
for d in all_vols
if d != vol and (d.vpath.startswith(vol.vpath + "/") or not vol.vpath)
]
excl += [absreal(x) for x in excl]
excl += list(self.asrv.vfs.histtab.values())
if WINDOWS:
excl = [x.replace("/", "\\") for x in excl]
excl = set(excl)
rtop = absreal(top)
n_add = n_rm = 0
try:
n_add = self._build_dir(dbw, top, set(excl), top, rei, reh, [])
n_add = self._build_dir(dbw, top, excl, top, rtop, rei, reh, [])
n_rm = self._drop_lost(dbw[0], top)
except:
m = "failed to index volume [{}]:\n{}"
@@ -572,8 +576,7 @@ class Up2k(object):
return True, n_add or n_rm or do_vac
def _build_dir(self, dbw, top, excl, cdir, rei, reh, seen):
rcdir = absreal(cdir) # a bit expensive but worth
def _build_dir(self, dbw, top, excl, cdir, rcdir, rei, reh, seen):
if rcdir in seen:
m = "bailing from symlink loop,\n prev: {}\n curr: {}\n from: {}"
self.log(m.format(seen[-1], rcdir, cdir), 3)
@@ -581,7 +584,6 @@ class Up2k(object):
seen = seen + [rcdir]
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
histpath = self.asrv.vfs.histtab[top]
ret = 0
seen_files = {} # != inames; files-only for dropcheck
g = statdir(self.log_func, not self.args.no_scandir, False, cdir)
@@ -596,17 +598,20 @@ class Up2k(object):
lmod = int(inf.st_mtime)
sz = inf.st_size
if stat.S_ISDIR(inf.st_mode):
if abspath in excl or abspath == histpath:
rap = absreal(abspath)
if abspath in excl or rap in excl:
continue
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
# abandoned or foreign, skip
continue
# self.log(" dir: {}".format(abspath))
try:
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)
ret += self._build_dir(dbw, top, excl, abspath, rap, rei, reh, seen)
except:
m = "failed to index subdir [{}]:\n{}"
self.log(m.format(abspath, min_ex()), c=1)
elif not stat.S_ISREG(inf.st_mode):
self.log("skip type-{:x} file [{}]".format(inf.st_mode, abspath))
else:
# self.log("file: {}".format(abspath))
seen_files[iname] = 1
@@ -944,7 +949,7 @@ class Up2k(object):
n_done += 1
for w in to_delete.keys():
q = "delete from mt where w = ? and k = 't:mtp'"
q = "delete from mt where w = ? and +k = 't:mtp'"
cur.execute(q, (w,))
to_delete = {}
@@ -982,7 +987,7 @@ class Up2k(object):
with self.mutex:
done = self._flush_mpool(wcur)
for w in done:
q = "delete from mt where w = ? and k = 't:mtp'"
q = "delete from mt where w = ? and +k = 't:mtp'"
cur.execute(q, (w,))
cur.connection.commit()
@@ -1080,18 +1085,20 @@ class Up2k(object):
if parser == "mtag":
parser = self.mtag.backend
msg = "{} failed to read tags from {}:\n{}"
self.log(msg.format(parser, abspath, ex), c=3)
self._log_tag_err(parser, abspath, ex)
q.task_done()
def _log_tag_err(self, parser, abspath, ex):
msg = "{} failed to read tags from {}:\n{}".format(parser, abspath, ex)
self.log(msg.lstrip(), c=1 if "<Signals.SIG" in msg else 3)
def _tag_file(self, write_cur, entags, wark, abspath, tags=None):
if tags is None:
try:
tags = self.mtag.get(abspath)
except Exception as ex:
msg = "failed to read tags from {}:\n{}"
self.log(msg.format(abspath, ex), c=3)
self._log_tag_err("", abspath, ex)
return 0
if not bos.path.isfile(abspath):
@@ -1108,7 +1115,7 @@ class Up2k(object):
for k in tags.keys():
q = "delete from mt where w = ? and ({})".format(
" or ".join(["k = ?"] * len(tags))
" or ".join(["+k = ?"] * len(tags))
)
args = [wark[:16]] + list(tags.keys())
write_cur.execute(q, tuple(args))
@@ -2188,8 +2195,7 @@ class Up2k(object):
if parsers:
tags.update(self.mtag.get_bin(parsers, abspath))
except Exception as ex:
msg = "failed to read tags from {}:\n{}"
self.log(msg.format(abspath, ex), c=3)
self._log_tag_err("", abspath, ex)
continue
with self.mutex:

View File

@@ -9,6 +9,7 @@ import time
import base64
import select
import struct
import signal
import hashlib
import platform
import traceback
@@ -1350,8 +1351,8 @@ def guess_mime(url, fallback="application/octet-stream"):
return ret
def runcmd(argv, timeout=None):
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
def runcmd(argv, timeout=None, **ka):
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE, **ka)
if not timeout or PY2:
stdout, stderr = p.communicate()
else:
@@ -1366,9 +1367,10 @@ def runcmd(argv, timeout=None):
return [p.returncode, stdout, stderr]
def chkcmd(argv):
ok, sout, serr = runcmd(argv)
def chkcmd(argv, **ka):
ok, sout, serr = runcmd(argv, **ka)
if ok != 0:
retchk(ok, argv, serr)
raise Exception(serr)
return sout, serr
@@ -1385,6 +1387,46 @@ def mchkcmd(argv, timeout=10):
raise sp.CalledProcessError(rv, (argv[0], b"...", argv[-1]))
def retchk(rc, cmd, serr, logger=None, color=None):
if rc < 0:
rc = 128 - rc
if rc < 126:
return
s = None
if rc > 128:
try:
s = str(signal.Signals(rc - 128))
except:
pass
elif rc == 126:
s = "invalid program"
elif rc == 127:
s = "program not found"
else:
s = "invalid retcode"
if s:
m = "{} <{}>".format(rc, s)
else:
m = str(rc)
try:
c = " ".join([fsdec(x) for x in cmd])
except:
c = str(cmd)
m = "error {} from [{}]".format(m, c)
if serr:
m += "\n" + serr
if logger:
logger(m, color)
else:
raise Exception(m)
def gzip_orig_sz(fn):
with open(fsenc(fn), "rb") as f:
f.seek(-4, 2)

View File

@@ -41,6 +41,7 @@
--btn-h-bg: #805;
--btn-1-fg: #400;
--btn-1-bg: var(--a);
--btn-1h-fg: var(--btn-1-fg);
--btn-1h-bg: #fe8;
--chk-fg: var(--tab-alt);
--txt-sh: var(--bg-d2);
@@ -66,6 +67,7 @@
--u2-ok-b1: #8e4;
--u2-err-bg: #900;
--u2-err-b1: #d06;
--ud-b1: #888;
--sort-1: #fb0;
--sort-2: #d09;
@@ -100,8 +102,10 @@
--g-f-fg: var(--a-hil);
--g-sh: rgba(0,0,0,0.3);
--f-sh1: 1px 0 0 0 rgba(128,128,128,0.4) inset, 0 1px 0 rgba(255,255,255,0.02) inset, 0 -1px 0 rgba(255,255,255,0.02) inset;
--f-sh2: 1px 0 0 0 rgba(128,128,128,0.4) inset, 0 1px 0 rgba(0,0,0,0.2) inset, 0 -1px 0 rgba(0,0,0,0.2) inset;
--f-sh1: 0.33;
--f-sh2: 0.02;
--f-sh3: 0.2;
--f-h-b1: rgba(128,128,128,0.7);
--f-play-bg: #fc5;
--f-play-fg: #000;
@@ -162,6 +166,7 @@ html.y {
--u2-tab-1-b2: #05a;
--u2-tab-1-fg: var(--fg-max);
--u2-tab-1-bg: inherit;
--ud-b1: #bbb;
--sort-1: #059;
--sort-2: #f5d;
@@ -183,8 +188,9 @@ html.y {
--g-f-b1: var(--bg-u5);
--g-sh: rgba(0,0,0,0.07);
--f-sh1: 1px 0 0 0 rgba(128,128,128,0.3) inset, 0 1px 0 rgba(255,255,255,0.5) inset, 0 -1px 0 rgba(255,255,255,0.5) inset;
--f-sh2: 1px 0 0 0 rgba(128,128,128,0.3) inset, 0 1px 0 rgba(0,0,0,0.02) inset, 0 -1px 0 rgba(0,0,0,0.02) inset;
--f-sh1: 0.3;
--f-sh2: 0.5;
--f-sh3: 0.02;
--f-sel-sh: #e80;
@@ -232,11 +238,39 @@ html.b {
--u2-o-h-bg: var(--btn-h-bg);
--u2-o-1-bg: var(--a);
--u2-o-1h-bg: var(--a-hil);
--f-sh1: 0.1;
}
html.bz {
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u2));
--fg: #bbd;
--bg-u5: #3b3f58;
--bg-u4: #1e2130;
--bg-u3: #1e2130;
--bg-u1: #1e2130;
--bg: #11121d;
--bgg: #11121d;
--bg-d1: #232536;
--bg-d2: #34384e;
--bg-d3: #34384e;
--row-alt: rgba(139, 150, 205, 0.06);
--btn-bg: #202231;
--btn-h-bg: #2d2f45;
--btn-1-bg: #ba2959;
--btn-1-fg: #fff;
--btn-1h-fg: #000;
--u2-tab-1-fg: var(--fg-max);
--u2-tab-1-bg: var(--bg);
--srv-1: #79b;
--g-sel-bg: #ba2959;
--g-fsel-bg: #e6336e;
--f-h-b1: #34384e;
--mp-sh: #11121d;
}
html.by {
--scroll: var(--a);
@@ -425,11 +459,11 @@ a:hover {
#files thead th {
padding: .3em;
background: var(--bg);
border-bottom: 1px solid var(--bg-u5);
border-bottom: 1px solid var(--f-h-b1);
cursor: pointer;
}
html.y #files thead th {
box-shadow: 0 -1px 0 rgba(0,0,0,0.33) inset;
box-shadow: 0 1px 0 rgba(0,0,0,0.12);
}
#files td {
margin: 0;
@@ -443,10 +477,10 @@ html.y #files thead th {
overflow: hidden;
}
#files td+td {
box-shadow: var(--f-sh1);
box-shadow: 1px 0 0 0 rgba(128,128,128,var(--f-sh1)) inset, 0 1px 0 rgba(255,255,255,var(--f-sh2)) inset, 0 -1px 0 rgba(255,255,255,var(--f-sh2)) inset;
}
#files tr:nth-child(2n+1) td+td {
box-shadow: var(--f-sh2);
box-shadow: 1px 0 0 0 rgba(128,128,128,var(--f-sh1)) inset, 0 1px 0 rgba(0,0,0,var(--f-sh3)) inset, 0 -1px 0 rgba(0,0,0,var(--f-sh3)) inset;
}
#files td:first-child {
border-radius: .25em 0 0 .25em;
@@ -591,6 +625,21 @@ html.y #path a:hover {
padding: .3em;
margin: -.3em;
}
#files tbody tr.play td,
#files tbody tr.play td+td,
#files tbody tr.play div a {
background: #fc0;
background: var(--f-play-bg);
color: var(--f-play-fg);
text-shadow: none;
}
#files tbody tr.play a {
color: inherit;
}
#files tbody tr.play a:hover {
color: var(--btn-1h-fg);
background: var(--btn-1h-bg);
}
#ggrid {
margin: -.2em -.5em;
}
@@ -668,6 +717,7 @@ html.np_open #ggrid>a.au:before {
#ggrid>a.play,
#ggrid>a[tt].play {
color: var(--g-sel-fg);
background: #fc0;
background: var(--g-play-bg);
border-color: var(--g-play-b1);
border-top: 1px solid var(--g-play-b2);
@@ -677,6 +727,7 @@ html.np_open #ggrid>a.au:before {
#ggrid>a.sel,
#ggrid>a[tt].sel {
color: var(--g-sel-fg);
background: #f3c;
background: var(--g-sel-bg);
border-color: var(--g-sel-b1);
}
@@ -729,7 +780,7 @@ html.np_open #ggrid>a.au:before {
background: var(--bg-d3);
box-shadow: 0 .2em 0 var(--f-sel-sh), 0 -.2em 0 var(--f-sel-sh);
}
#files tr:focus td:first-child {
#files tr:focus:not(.play):not(.sel) td:first-child {
background: var(--bg-d3);
box-shadow: -.2em .2em 0 var(--f-sel-sh), -.2em -.2em 0 var(--f-sel-sh);
}
@@ -806,6 +857,7 @@ html.y #widget.open {
}
#wtoggle,
#widgeti {
background: #fff;
background: var(--bg-u3);
}
#wfm, #wzip, #wnp {
@@ -931,7 +983,9 @@ html.y #widget.open {
outline: none;
}
#ops a.act {
color: #fff;
color: var(--op-aa-fg);
background: #000;
background: var(--op-aa-bg);
border-radius: 0 0 .2em .2em;
border-bottom: .3em solid var(--a-b);
@@ -964,7 +1018,8 @@ html.y #ops svg circle {
background: var(--btn-bg);
border: none;
box-shadow: 0 0 2px var(--txt-sh);
border-bottom: 1px solid var(--a);
border-bottom: 1px solid #999;
border-color: var(--a);
border-radius: .2em;
padding: .2em .3em;
}
@@ -981,6 +1036,7 @@ input[type="checkbox"]+label {
}
input[type="radio"]:checked+label,
input[type="checkbox"]:checked+label {
color: #0e0;
color: var(--a);
}
.opwide div>span>input+label {
@@ -1141,6 +1197,7 @@ html {
}
.btn {
color: var(--btn-fg);
background: #eee;
background: var(--btn-bg);
border-radius: .3em;
padding: .2em .4em;
@@ -1162,7 +1219,9 @@ html.ca .btn {
background: var(--btn-h-bg);
}
.tgl.btn.on {
background: #000;
background: var(--btn-1-bg);
color: #fff;
color: var(--btn-1-fg);
text-shadow: none;
}
@@ -1172,7 +1231,7 @@ html.ca .tgl.btn.on {
}
.tgl.btn.on:hover {
background: var(--btn-1h-bg);
color: var(--btn-1-fg);
color: var(--btn-1h-fg);
}
#detree {
padding: .3em .5em;
@@ -1193,8 +1252,10 @@ html.ca .tgl.btn.on {
border-top: 1px solid var(--bg-u5);
}
#tree ul a.sel {
background: #000;
background: var(--bg-d3);
box-shadow: -.8em 0 0 var(--g-sel-b1) inset;
color: #fff;
color: var(--fg-max);
}
#tree ul a.hl {
@@ -1267,20 +1328,6 @@ html.y #tree.nowrap .ntree a+a:hover {
#files td:nth-child(2n) {
color: var(--tab-alt);
}
#files tbody tr.play td,
#files tbody tr.play td+td,
#files tbody tr.play div a {
background: var(--f-play-bg);
color: var(--f-play-fg);
text-shadow: none;
}
#files tbody tr.play a {
color: inherit;
}
#files tbody tr.play a:hover {
color: var(--btn-1-fg);
background: var(--btn-1h-bg);
}
.opwide,
#op_unpost,
#srch_form {
@@ -1742,7 +1789,7 @@ html.y #bbox-overlay figcaption a {
background: rgba(48, 48, 48, 0.7);
}
#drops.vis,
#drops .dropzone {
.dropzone {
display: block;
position: fixed;
top: 0;
@@ -1750,61 +1797,76 @@ html.y #bbox-overlay figcaption a {
right: 0;
bottom: 0;
}
#drops .dropdesc {
.dropdesc {
position: fixed;
display: table;
left: 10%;
width: 78%;
height: 30%;
height: 26%;
margin: 0;
font-size: 3em;
font-weight: bold;
text-shadow: .05em .05em .1em var(--bg-max);
text-shadow: .05em .05em .1em #333;
background: rgba(224, 224, 224, 0.2);
box-shadow: 0 0 0 #999;
border: .5em solid #999;
border: .5em solid var(--ud-b1);
border-radius: .5em;
border-width: 1vw;
color: #fff;
transition: all 0.12s;
}
#drops .dropdesc.hl.ok {
.dropdesc.hl.ok {
border-color: #fff;
box-shadow: 0 0 1em #cf5;
box-shadow: 0 0 1em .4em #cf5, 0 0 1em #000 inset;
background: rgba(24, 24, 24, 0.7);
left: 8%;
width: 82%;
height: 32%;
margin: -1vh 0;
margin: -3vh 0;
}
#drops .dropdesc.hl.err {
.dropdesc.hl.err {
background: rgba(224, 32, 65, 0.2);
box-shadow: 0 0 1em #f26;
box-shadow: 0 0 1em .4em #f26;
border-color: #fab;
}
#drops .dropdesc>div {
.dropdesc>div {
display: table-cell;
vertical-align: middle;
text-align: center;
}
#drops .dropdesc>div>div {
.dropdesc>div>div {
position: absolute;
top: 40%;
top: calc(50% - .5em);
left: -.8em;
}
#drops .dropdesc>div>div+div {
.dropdesc>div>div+div {
left: auto;
right: -.8em;
}
#drops .dropzone {
.dropdesc b {
position: relative;
font-size: .5em;
font-size: 2vw;
top: -.25em;
top: -.2vw;
margin: 0 .8em;
margin: 0 1.25vw;
transition: font-size 0.12s;
}
.dropdesc.hl b {
border-bottom: .1em solid #fff;
font-size: .6em;
font-size: 2.5vw;
}
.dropzone {
z-index: 80386;
height: 50%;
}
#drops #up_dz {
#up_dz {
bottom: 50%;
}
#drops #srch_dz {
#srch_dz {
top: 50%;
}
#up_zd {
@@ -1817,7 +1879,7 @@ html.y #bbox-overlay figcaption a {
color: #fff;
background: #490;
border-bottom: .3em solid #6d2;
text-shadow: 1px 1px 1px var(--bg-max);
text-shadow: 1px 1px 1px #000;
border-radius: .3em;
padding: .4em .8em;
font-size: .4em;
@@ -2145,7 +2207,7 @@ html.b #u2conf a.b:hover {
#u2tab span.inf,
#u2tab span.ok,
#u2tab span.err {
color: var(--fg-max);
color: #fff;
padding: .22em;
border-radius: .2em;
border: 2px solid #f0f;
@@ -2393,6 +2455,9 @@ html.b #op_up2k {
html.b #tree {
box-shadow: 0 -1px 0 rgba(128,128,128,0.4);
}
html.bz #tree {
box-shadow: 0 -1px 0 var(--bg-d3);
}
html.b #treeh,
html.b #tree li {
border: none;
@@ -2401,7 +2466,7 @@ html.b .ntree a {
padding: .6em .2em;
}
html.b #treepar {
border-bottom: .2em solid #999;
border-bottom: .2em solid var(--f-h-b1);
}
html.b #wrap {
margin-top: 2em;

View File

@@ -3,6 +3,7 @@
function dbg(msg) {
ebi('path').innerHTML = msg;
}
var XHR = XMLHttpRequest;
// toolbar
@@ -128,8 +129,8 @@ ebi('op_up2k').innerHTML = (
var o = mknod('div');
o.innerHTML = (
'<div id="drops">\n' +
' <div class="dropdesc" id="up_zd"><div>🚀 Upload<br /><span></span><div>🚀</div><div>🚀</div></div></div>\n' +
' <div class="dropdesc" id="srch_zd"><div>🔎 Search<br /><span></span><div>🔎</div><div>🔎</div></div></div>\n' +
' <div class="dropdesc" id="up_zd"><div>🚀 Upload<br /><span></span><div>🚀<b>Upload</b></div><div><b>Upload</b>🚀</div></div></div>\n' +
' <div class="dropdesc" id="srch_zd"><div>🔎 Search<br /><span></span><div>🔎<b>Search</b></div><div><b>Search</b>🔎</div></div></div>\n' +
' <div class="dropzone" id="up_dz" v="up_zd"></div>\n' +
' <div class="dropzone" id="srch_dz" v="srch_zd"></div>\n' +
'</div>'
@@ -148,7 +149,6 @@ ebi('op_cfg').innerHTML = (
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
' <a id="ireadme" class="tgl btn" href="#" tt="show README.md in folder listings">📜 readme</a>\n' +
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
' </div>\n' +
'</div>\n' +
'<div>\n' +
@@ -164,7 +164,7 @@ ebi('op_cfg').innerHTML = (
' <div>\n' +
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="the yolo button, you probably DO NOT want to enable this:$N$Nuse this if you were uploading a huge amount of files and had to restart for some reason, and want to continue the upload ASAP$N$Nthis replaces the hash-check with a simple <em>&quot;does this have the same filesize on the server?&quot;</em> so if the file contents are different it will NOT be uploaded$N$Nyou should turn this off when the upload is done, and then &quot;upload&quot; the same files again to let the client verify them">turbo</a>\n' +
' <a id="u2tdate" class="tgl btn ttb" href="#" tt="has no effect unless the turbo button is enabled$N$Nreduces the yolo factor by a tiny amount; checks whether the file timestamps on the server matches yours$N$Nshould <em>theoretically</em> catch most unfinished/corrupted uploads, but is not a substitute for doing a verification pass with turbo disabled afterwards">date-chk</a>\n' +
' <a id="flag_en" class="tgl btn" href="#" tt="ensure only one tab is uploading at a time $N (other tabs must have this enabled too)">💤</a>\n' +
' <a id="flag_en" class="tgl btn" href="#" tt="ensure only one tab is uploading at a time $N -- other tabs must have this enabled too $N -- only affects tabs on the same domain">💤</a>\n' +
' </td>\n' +
' </div>\n' +
'</div>\n' +
@@ -319,8 +319,8 @@ var mpl = (function () {
ebi('op_player').innerHTML = (
'<div><h3>switches</h3><div>' +
'<a href="#" class="tgl btn" id="au_preload" tt="start loading the next song near the end for gapless playback">preload</a>' +
'<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_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</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_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>' +
@@ -612,6 +612,10 @@ function MPlayer() {
r.fvol = 0;
r.au.pause();
mpl.pp();
var t = mp.au.currentTime - 0.8;
if (isFinite(t))
mp.au.currentTime = Math.max(t, 0);
}
else if (r.fvol > r.vol)
r.fvol = r.vol;
@@ -1099,6 +1103,7 @@ var mpui = (function () {
fpreloaded = null;
r.progress_updater = function () {
//console.trace();
timer.add(updater_impl, true);
};
@@ -1462,6 +1467,7 @@ function play(tid, is_ev, seek) {
mp.au2 = new Audio();
mp.au.onerror = evau_error;
mp.au.onprogress = pbar.drawpos;
mp.au.onplaying = mpui.progress_updater;
mp.au.onended = next_song;
widget.open();
}
@@ -1478,6 +1484,7 @@ function play(tid, is_ev, seek) {
t.onerror = t.onprogress = t.onended = null;
mp.au.onerror = evau_error;
mp.au.onprogress = pbar.drawpos;
mp.au.onplaying = mpui.progress_updater;
mp.au.onended = next_song;
}
else
@@ -1562,12 +1569,37 @@ function evau_error(e) {
err = 'Unknown Errol';
break;
}
if (eplaya.error.message)
err += '\n\n' + eplaya.error.message;
var em = '' + eplaya.error.message,
mfile = '\n\nFile: «' + uricom_dec(eplaya.src.split('/').pop())[0] + '»',
e404 = 'Could not play audio; error 404: File not found.',
e403 = 'Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out';
err += '\n\nFile: «' + uricom_dec(eplaya.src.split('/').pop())[0] + '»';
if (em)
err += '\n\n' + em;
toast.warn(15, esc(basenames(err)));
if (em.startsWith('403: '))
err = e403;
if (em.startsWith('404: '))
err = e404;
toast.warn(15, esc(basenames(err + mfile)));
if (em.startsWith('MEDIA_ELEMENT_ERROR:')) {
// chromish for 40x
var xhr = new XHR();
xhr.open('HEAD', eplaya.src, true);
xhr.onreadystatechange = function () {
if (this.readyState != XHR.DONE || this.status < 400)
return;
err = this.status == 403 ? e403 : this.status == 404 ? e404 :
'Could not play audio; server error ' + this.status;
toast.warn(15, esc(basenames(err + mfile)));
};
xhr.send();
}
}
@@ -2147,7 +2179,7 @@ var fileman = (function () {
var dst = base + uricom_enc(f[0].inew.value, false);
function rename_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
@@ -2160,7 +2192,7 @@ var fileman = (function () {
return rn_apply();
}
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('GET', f[0].src + '?move=' + dst, true);
xhr.onreadystatechange = rename_cb;
xhr.send();
@@ -2182,7 +2214,7 @@ var fileman = (function () {
return toast.err(3, 'select at least 1 item to delete');
function deleter() {
var xhr = new XMLHttpRequest(),
var xhr = new XHR(),
vp = vps.shift();
if (!vp) {
@@ -2197,7 +2229,7 @@ var fileman = (function () {
xhr.send();
}
function delete_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
@@ -2290,7 +2322,7 @@ var fileman = (function () {
return;
function paster() {
var xhr = new XMLHttpRequest(),
var xhr = new XHR(),
vp = req.shift();
if (!vp) {
@@ -2308,7 +2340,7 @@ var fileman = (function () {
xhr.send();
}
function paste_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
@@ -2461,7 +2493,7 @@ var showfile = (function () {
};
r.show = function (url, no_push) {
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.url = url;
xhr.no_push = no_push;
xhr.ts = Date.now();
@@ -2471,13 +2503,11 @@ var showfile = (function () {
};
function load_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
toast.err(0, "recvtree, http " + this.status + ": " + this.responseText);
if (!xhrchk(this, "could not load textfile:\n\nerror ", "404, file not found"))
return;
}
render([this.url, '', this.responseText], this.no_push);
}
@@ -2824,7 +2854,7 @@ var thegrid = (function () {
else if (in_tree && !have_sel)
in_tree.click();
else if (is_dir && !have_sel && treectl.spa)
else if (is_dir && !have_sel)
treectl.reqls(href, true, true);
else if (!is_img && have_sel)
@@ -3464,7 +3494,7 @@ document.onkeydown = function (e) {
srch_msg(false, "searching...");
clearTimeout(search_timeout);
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('POST', '/?srch', true);
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.onreadystatechange = xhr_search_results;
@@ -3474,7 +3504,7 @@ document.onkeydown = function (e) {
}
function xhr_search_results() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
@@ -3594,7 +3624,6 @@ var treectl = (function () {
mentered = null,
treesz = clamp(icfg_get('treesz', 16), 10, 50);
bcfg_bind(r, 'spa', 'spafiles', true);
bcfg_bind(r, 'ireadme', 'ireadme', true);
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
@@ -3802,7 +3831,7 @@ var treectl = (function () {
};
function get_tree(top, dst, rst) {
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.top = top;
xhr.dst = dst;
xhr.rst = rst;
@@ -3814,13 +3843,11 @@ var treectl = (function () {
}
function recvtree() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
toast.err(0, "recvtree, http " + this.status + ": " + this.responseText);
if (!xhrchk(this, "could not list subfolders:\n\nerror ", "404, folder not found"))
return;
}
var cur = ebi('treeul').getAttribute('ts');
if (cur && parseInt(cur) > this.ts) {
@@ -3973,7 +4000,7 @@ var treectl = (function () {
}
r.reqls = function (url, hpush, no_tree) {
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.top = url;
xhr.hpush = hpush;
xhr.ts = Date.now();
@@ -4002,13 +4029,11 @@ var treectl = (function () {
}
function recvls() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
toast.err(0, "recvls, http " + this.status + ": " + this.responseText);
if (!xhrchk(this, "could not list files in folder:\n\nerror ", "404, folder not found"))
return;
}
var cur = ebi('files').getAttribute('ts');
if (cur && parseInt(cur) > this.ts) {
@@ -4137,7 +4162,7 @@ var treectl = (function () {
r.hydrate = function () {
qsr('#bbsw');
if (ls0 === null) {
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('GET', '/?am_js', true);
xhr.send();
@@ -4898,7 +4923,7 @@ var msel = (function () {
fd.append("act", "mkdir");
fd.append("name", tb.value);
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.vp = get_evpath();
xhr.dn = tb.value;
xhr.open('POST', xhr.vp, true);
@@ -4910,7 +4935,7 @@ var msel = (function () {
};
function cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.vp !== get_evpath()) {
@@ -4918,6 +4943,8 @@ var msel = (function () {
return;
}
xhrchk(this, "could not create subfolder:\n\nerror ", "404, parent folder not found");
if (this.status !== 200) {
sf.textContent = 'error: ' + this.responseText;
return;
@@ -4947,7 +4974,7 @@ var msel = (function () {
clmod(sf, 'vis', 1);
sf.textContent = 'sending...';
var xhr = new XMLHttpRequest(),
var xhr = new XHR(),
ct = 'application/x-www-form-urlencoded;charset=UTF-8';
xhr.msg = tb.value;
@@ -4963,9 +4990,11 @@ var msel = (function () {
};
function cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
xhrchk(this, "could not send message:\n\nerror ", "404, parent folder not found");
if (this.status !== 200) {
sf.textContent = 'error: ' + this.responseText;
return;
@@ -5080,15 +5109,11 @@ var unpost = (function () {
html = [];
function unpost_load_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
var msg = this.responseText;
toast.err(9, 'unpost-load failed:\n' + msg);
ebi('op_unpost').innerHTML = html.join('\n');
return;
}
if (!xhrchk(this, "unpost-load failed:\n\nerror ", "404, file not found??"))
return ebi('op_unpost').innerHTML = 'failed to load unpost list from server';
var res = JSON.parse(this.responseText);
if (res.length) {
@@ -5128,7 +5153,7 @@ var unpost = (function () {
if (filt.value)
q += '&filter=' + uricom_enc(filt.value, true);
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('GET', q, true);
xhr.onreadystatechange = unpost_load_cb;
xhr.send();
@@ -5137,7 +5162,7 @@ var unpost = (function () {
};
function unpost_delete_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
@@ -5188,7 +5213,7 @@ var unpost = (function () {
toast.inf(0, "deleting " + req.length + " files...");
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.n = n;
xhr.n2 = n2;
xhr.open('POST', '/?delete', true);
@@ -5229,7 +5254,7 @@ function wintitle(txt) {
ebi('path').onclick = function (e) {
var a = e.target.closest('a[href]');
if (!treectl.spa || !a || !(a = a.getAttribute('href') + '') || !a.endsWith('/'))
if (!a || !(a = a.getAttribute('href') + '') || !a.endsWith('/'))
return;
thegrid.setvis(true);
@@ -5249,11 +5274,8 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
el.click();
return ev(e);
}
if (treectl.spa) {
treectl.reqls(tgt.getAttribute('href'), true, true);
return ev(e);
}
return;
treectl.reqls(tgt.getAttribute('href'), true, true);
return ev(e);
}
tgt = e.target.closest('a[hl]');

View File

@@ -45,7 +45,9 @@
<tr><td></td><td><a href="../{{ url_suf }}">parent folder</a></td><td>-</td><td>-</td></tr>
{%- for f in files %}
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}{{ url_suf }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td><td>{{ f.dt }}</td></tr>
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}{{
'&' + url_suf[1:] if url_suf[:1] == '?' and '?' in f.href else url_suf
}}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td><td>{{ f.dt }}</td></tr>
{%- endfor %}
</tbody>

View File

@@ -255,7 +255,7 @@ function Modpoll() {
console.log('modpoll...');
var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('GET', url, true);
xhr.responseType = 'text';
xhr.onreadystatechange = r.cb;
@@ -268,7 +268,7 @@ function Modpoll() {
return;
}
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200) {
@@ -336,7 +336,7 @@ function save(e) {
fd.append("body", txt);
var url = (document.location + '').split('?')[0];
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('POST', url, true);
xhr.responseType = 'text';
xhr.onreadystatechange = save_cb;
@@ -356,7 +356,7 @@ function save(e) {
}
function save_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200)
@@ -397,7 +397,7 @@ function save_cb() {
function run_savechk(lastmod, txt, btn, ntry) {
// download the saved doc from the server and compare
var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('GET', url, true);
xhr.responseType = 'text';
xhr.onreadystatechange = savechk_cb;
@@ -409,7 +409,7 @@ function run_savechk(lastmod, txt, btn, ntry) {
}
function savechk_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200)

View File

@@ -114,7 +114,7 @@ function save(mde) {
fd.append("body", txt);
var url = (document.location + '').split('?')[0];
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('POST', url, true);
xhr.responseType = 'text';
xhr.onreadystatechange = save_cb;
@@ -133,7 +133,7 @@ function save(mde) {
}
function save_cb() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200)
@@ -170,7 +170,7 @@ function save_cb() {
// download the saved doc from the server and compare
var url = (document.location + '').split('?')[0] + '?raw';
var xhr = new XMLHttpRequest();
var xhr = new XHR();
xhr.open('GET', url, true);
xhr.responseType = 'text';
xhr.onreadystatechange = save_chk;
@@ -182,7 +182,7 @@ function save_cb() {
}
function save_chk() {
if (this.readyState != XMLHttpRequest.DONE)
if (this.readyState != XHR.DONE)
return;
if (this.status !== 200)

View File

@@ -101,6 +101,11 @@ document.documentElement.className = localStorage.light == 1 ? "y" : "z";
</script>
<script src="/.cpr/util.js?_={{ ts }}"></script>
<script>tt.init();</script>
<script>
tt.init();
{%- if this.uname == '*' %}
QS('input[name="cppwd"]').focus();
{%- endif %}
</script>
</body>
</html>

View File

@@ -11,6 +11,7 @@ html {
max-width: 34em;
max-width: min(34em, 90%);
max-width: min(34em, calc(100% - 7em));
color: #ddd;
background: #333;
border: 0 solid #777;
box-shadow: 0 .2em .5em #111;

View File

@@ -1811,11 +1811,8 @@ function up2k_init(subtle) {
tasker();
return;
}
toast.err(0, "server broke; hs-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + (
(xhr.response && xhr.response.err) ||
(xhr.responseText && xhr.responseText) ||
"no further information"));
err = t.t_uploading ? "finalize upload" : t.srch ? "perform search" : "initiate upload";
xhrchk(xhr, "server rejected the request to " + err + ";\n\nfile: " + t.name + "\n\nerror ", "404, target folder not found");
}
}
xhr.onload = function (e) {
@@ -1874,8 +1871,7 @@ function up2k_init(subtle) {
console.log("ignoring dupe-segment error", t);
}
else {
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + (txt || "no further information"));
xhrchk(xhr, "server rejected upload (chunk {0} of {1});\n\nfile: {2}\n\nerror ".format(npart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)");
chill(t);
}
@@ -1904,7 +1900,7 @@ function up2k_init(subtle) {
return;
if (!toast.visible)
toast.warn(9.98, "failed to upload a chunk;\nprobably harmless, continuing\n\n" + t.name);
toast.warn(9.98, "failed to upload chunk {0} of {1};\nprobably harmless, continuing\n\nfile: {2}".format(npart, Math.ceil(t.size / chunksize), t.name));
console.log('chunkpit onerror,', ++tries, t);
orz2(xhr);

View File

@@ -14,7 +14,8 @@ var is_touch = 'ontouchstart' in window,
var ebi = document.getElementById.bind(document),
QS = document.querySelector.bind(document),
QSA = document.querySelectorAll.bind(document),
mknod = document.createElement.bind(document);
mknod = document.createElement.bind(document),
XHR = XMLHttpRequest;
function qsr(sel) {
@@ -1185,6 +1186,9 @@ var modal = (function () {
return ok();
}
if ((k == 'ArrowLeft' || k == 'ArrowRight') && eng && (ae == eok || ae == eng))
return (ae == eok ? eng : eok).focus() || ev(e);
if (k == 'Escape')
return ng();
}
@@ -1386,3 +1390,18 @@ var favico = (function () {
r.to = setTimeout(r.init, 100);
return r;
})();
function xhrchk(xhr, prefix, e404) {
if (xhr.status < 400 && xhr.status >= 200)
return true;
if (xhr.status == 403)
return toast.err(0, prefix + "403, access denied\n\ntry pressing F5, maybe you got logged out");
if (xhr.status == 404)
return toast.err(0, prefix + e404);
return toast.err(0, prefix + xhr.status + ": " + (
(xhr.response && xhr.response.err) || xhr.responseText));
}

4
docs/notes.bat Normal file
View File

@@ -0,0 +1,4 @@
rem appending a static ip to a dhcp nic on windows 10-1703 or later
netsh interface ipv4 show interface
netsh interface ipv4 set interface interface="Ethernet 2" dhcpstaticipcoexistence=enabled
netsh interface ipv4 add address "Ethernet 2" 10.1.2.4 255.255.255.0

View File

@@ -59,6 +59,7 @@ class Cfg(Namespace):
theme=0,
themes=0,
turbo=0,
logout=573,
hist=None,
no_idx=None,
no_hash=None,

View File

@@ -39,6 +39,7 @@ class Cfg(Namespace):
"theme": 0,
"themes": 0,
"turbo": 0,
"logout": 573,
}
ex.update(ex2)
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)