Compare commits

...

16 Commits

Author SHA1 Message Date
ed
7f2cb6764a v0.9.7 2021-03-08 03:51:26 +01:00
ed
96495a9bf1 v0.9.6 2021-03-07 21:44:25 +01:00
ed
b2fafec5fc handle key-normalization errors 2021-03-07 21:41:36 +01:00
ed
0850b8ae2b v0.9.5 2021-03-07 19:25:24 +01:00
ed
8a68a96c57 css tweaks 2021-03-07 19:15:19 +01:00
ed
d3aae8ed6a more mojibake fixes 2021-03-07 18:58:26 +01:00
ed
c62ebadda8 separate tree scrollbar 2021-03-07 18:26:57 +01:00
ed
ffcee6d390 add tooltips and more mojibake compat 2021-03-07 04:14:55 +01:00
ed
de32838346 key notation normalization (why tho) 2021-03-07 02:46:17 +01:00
ed
b9a4e47ea2 mojibake support for the spa stuff 2021-03-06 22:48:49 +01:00
ed
57d994422d logging cleanup 2021-03-06 17:38:56 +01:00
ed
6ecd745323 so much for sessionStorage 2021-03-06 16:34:55 +01:00
ed
bd769f5bdb fix py2 + encourage py3 2021-03-06 02:42:17 +01:00
ed
2381692aba js cfg 2021-03-06 02:30:36 +01:00
ed
24fdada0a0 did you know rhel 7 has an sqlite3 from 2015 2021-03-06 02:28:49 +01:00
ed
bb5169710a warn people when they're gonna have a bad time 2021-03-06 00:30:05 +01:00
26 changed files with 695 additions and 322 deletions

View File

@@ -69,7 +69,9 @@ summary: it works! you can use it! (but technically not even close to beta)
# bugs # bugs
* probably, pls let me know * Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
* Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
* probably more, pls let me know
# searching # searching

View File

@@ -12,7 +12,7 @@
Description=copyparty file server Description=copyparty file server
[Service] [Service]
ExecStart=/usr/bin/python /usr/local/bin/copyparty-sfx.py -q -v /mnt::a ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::a
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
[Install] [Install]

View File

@@ -18,7 +18,7 @@ import locale
import argparse import argparse
from textwrap import dedent from textwrap import dedent
from .__init__ import E, WINDOWS, VT100 from .__init__ import E, WINDOWS, VT100, PY2
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
from .svchub import SvcHub from .svchub import SvcHub
from .util import py_desc, align_tab from .util import py_desc, align_tab
@@ -53,6 +53,10 @@ class RiceFormatter(argparse.HelpFormatter):
return "".join(indent + line + "\n" for line in text.splitlines()) return "".join(indent + line + "\n" for line in text.splitlines())
def warn(msg):
print("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
def ensure_locale(): def ensure_locale():
for x in [ for x in [
"en_US.UTF-8", "en_US.UTF-8",
@@ -300,7 +304,13 @@ def main():
if al.ciphers: if al.ciphers:
configure_ssl_ciphers(al) configure_ssl_ciphers(al)
else: else:
print("\033[33m ssl module does not exist; cannot enable https\033[0m\n") warn("ssl module does not exist; cannot enable https")
if PY2 and WINDOWS and al.e2d:
warn(
"windows py2 cannot do unicode filenames with -e2d\n"
+ " (if you crash with codec errors then that is why)"
)
SvcHub(al).run() SvcHub(al).run()

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 9, 4) VERSION = (0, 9, 7)
CODENAME = "the strongest music server" CODENAME = "the strongest music server"
BUILD_DT = (2021, 3, 5) BUILD_DT = (2021, 3, 8)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -6,7 +6,7 @@ import re
import threading import threading
from .__init__ import PY2, WINDOWS from .__init__ import PY2, WINDOWS
from .util import undot, Pebkac, fsdec, fsenc, statdir from .util import undot, Pebkac, fsdec, fsenc, statdir, nuprint
class VFS(object): class VFS(object):
@@ -106,7 +106,7 @@ class VFS(object):
"""return user-readable [fsdir,real,virt] items at vpath""" """return user-readable [fsdir,real,virt] items at vpath"""
virt_vis = {} # nodes readable by user virt_vis = {} # nodes readable by user
abspath = self.canonical(rem) abspath = self.canonical(rem)
real = list(statdir(print, scandir, lstat, abspath)) real = list(statdir(nuprint, scandir, lstat, abspath))
real.sort() real.sort()
if not rem: if not rem:
for name, vn2 in sorted(self.nodes.items()): for name, vn2 in sorted(self.nodes.items()):
@@ -147,8 +147,8 @@ class AuthSrv(object):
self.mutex = threading.Lock() self.mutex = threading.Lock()
self.reload() self.reload()
def log(self, msg): def log(self, msg, c=0):
self.log_func("auth", msg) self.log_func("auth", msg, c)
def invert(self, orig): def invert(self, orig):
if PY2: if PY2:
@@ -304,9 +304,9 @@ class AuthSrv(object):
if missing_users: if missing_users:
self.log( self.log(
"\033[31myou must -a the following users: " "you must -a the following users: "
+ ", ".join(k for k in sorted(missing_users)) + ", ".join(k for k in sorted(missing_users)),
+ "\033[0m" c=1,
) )
raise Exception("invalid config") raise Exception("invalid config")
@@ -329,8 +329,8 @@ class AuthSrv(object):
v, _ = vfs.get("/", "*", False, True) v, _ = vfs.get("/", "*", False, True)
if self.warn_anonwrite and os.getcwd() == v.realpath: if self.warn_anonwrite and os.getcwd() == v.realpath:
self.warn_anonwrite = False self.warn_anonwrite = False
msg = "\033[31manyone can read/write the current directory: {}\033[0m" msg = "anyone can read/write the current directory: {}"
self.log(msg.format(v.realpath)) self.log(msg.format(v.realpath), c=1)
except Pebkac: except Pebkac:
self.warn_anonwrite = True self.warn_anonwrite = True

View File

@@ -49,11 +49,11 @@ class MpWorker(object):
# print('k') # print('k')
pass pass
def log(self, src, msg): def log(self, src, msg, c=0):
self.q_yield.put([0, "log", [src, msg]]) self.q_yield.put([0, "log", [src, msg, c]])
def logw(self, msg): def logw(self, msg, c=0):
self.log("mp{}".format(self.n), msg) self.log("mp{}".format(self.n), msg, c)
def httpdrop(self, addr): def httpdrop(self, addr):
self.q_yield.put([0, "httpdrop", [addr]]) self.q_yield.put([0, "httpdrop", [addr]])
@@ -73,7 +73,7 @@ class MpWorker(object):
if PY2: if PY2:
sck = pickle.loads(sck) # nosec sck = pickle.loads(sck) # nosec
self.log("%s %s" % addr, "\033[1;30m|%sC-qpop\033[0m" % ("-" * 4,)) self.log("%s %s" % addr, "|%sC-qpop" % ("-" * 4,), c="1;30")
self.httpsrv.accept(sck, addr) self.httpsrv.accept(sck, addr)
with self.mutex: with self.mutex:

View File

@@ -28,7 +28,7 @@ class BrokerThr(object):
def put(self, want_retval, dest, *args): def put(self, want_retval, dest, *args):
if dest == "httpconn": if dest == "httpconn":
sck, addr = args sck, addr = args
self.log("%s %s" % addr, "\033[1;30m|%sC-qpop\033[0m" % ("-" * 4,)) self.log("%s %s" % addr, "|%sC-qpop" % ("-" * 4,), c="1;30")
self.httpsrv.accept(sck, addr) self.httpsrv.accept(sck, addr)
else: else:

View File

@@ -41,8 +41,8 @@ class HttpCli(object):
self.absolute_urls = False self.absolute_urls = False
self.out_headers = {"Access-Control-Allow-Origin": "*"} self.out_headers = {"Access-Control-Allow-Origin": "*"}
def log(self, msg): def log(self, msg, c=0):
self.log_func(self.log_src, msg) self.log_func(self.log_src, msg, c)
def _check_nonfatal(self, ex): def _check_nonfatal(self, ex):
return ex.code < 400 or ex.code == 404 return ex.code < 400 or ex.code == 404
@@ -63,7 +63,7 @@ class HttpCli(object):
if not headerlines[0]: if not headerlines[0]:
# seen after login with IE6.0.2900.5512.xpsp.080413-2111 (xp-sp3) # seen after login with IE6.0.2900.5512.xpsp.080413-2111 (xp-sp3)
self.log("\033[1;31mBUG: trailing newline from previous request\033[0m") self.log("BUG: trailing newline from previous request", c="1;31")
headerlines.pop(0) headerlines.pop(0)
try: try:
@@ -74,7 +74,7 @@ class HttpCli(object):
except Pebkac as ex: except Pebkac as ex:
# self.log("pebkac at httpcli.run #1: " + repr(ex)) # self.log("pebkac at httpcli.run #1: " + repr(ex))
self.keepalive = self._check_nonfatal(ex) self.keepalive = self._check_nonfatal(ex)
self.loud_reply(str(ex), status=ex.code) self.loud_reply(unicode(ex), status=ex.code)
return self.keepalive return self.keepalive
# time.sleep(0.4) # time.sleep(0.4)
@@ -163,7 +163,7 @@ class HttpCli(object):
response = ["HTTP/1.1 {} {}".format(status, HTTPCODE[status])] response = ["HTTP/1.1 {} {}".format(status, HTTPCODE[status])]
if length is not None: if length is not None:
response.append("Content-Length: " + str(length)) response.append("Content-Length: " + unicode(length))
# close if unknown length, otherwise take client's preference # close if unknown length, otherwise take client's preference
response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close")) response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
@@ -521,7 +521,7 @@ class HttpCli(object):
if len(cstart) > 1 and path != os.devnull: if len(cstart) > 1 and path != os.devnull:
self.log( self.log(
"clone {} to {}".format( "clone {} to {}".format(
cstart[0], " & ".join(str(x) for x in cstart[1:]) cstart[0], " & ".join(unicode(x) for x in cstart[1:])
) )
) )
ofs = 0 ofs = 0
@@ -697,7 +697,7 @@ class HttpCli(object):
raise raise
except Pebkac as ex: except Pebkac as ex:
errmsg = str(ex) errmsg = unicode(ex)
td = max(0.1, time.time() - t0) td = max(0.1, time.time() - t0)
sz_total = sum(x[0] for x in files) sz_total = sum(x[0] for x in files)
@@ -1006,7 +1006,7 @@ class HttpCli(object):
mime=guess_mime(req_path)[0] or "application/octet-stream", mime=guess_mime(req_path)[0] or "application/octet-stream",
) )
logmsg += str(status) + logtail logmsg += unicode(status) + logtail
if self.mode == "HEAD" or not do_send: if self.mode == "HEAD" or not do_send:
self.log(logmsg) self.log(logmsg)
@@ -1020,7 +1020,7 @@ class HttpCli(object):
remains = sendfile_py(lower, upper, f, self.s) remains = sendfile_py(lower, upper, f, self.s)
if remains > 0: if remains > 0:
logmsg += " \033[31m" + str(upper - remains) + "\033[0m" logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
spd = self._spd((upper - lower) - remains) spd = self._spd((upper - lower) - remains)
self.log("{}, {}".format(logmsg, spd)) self.log("{}, {}".format(logmsg, spd))
@@ -1067,7 +1067,7 @@ class HttpCli(object):
sz_html = len(template.render(**targs).encode("utf-8")) sz_html = len(template.render(**targs).encode("utf-8"))
self.send_headers(sz_html + sz_md, status) self.send_headers(sz_html + sz_md, status)
logmsg += str(status) logmsg += unicode(status)
if self.mode == "HEAD" or not do_send: if self.mode == "HEAD" or not do_send:
self.log(logmsg) self.log(logmsg)
return True return True
@@ -1081,7 +1081,7 @@ class HttpCli(object):
self.log(logmsg + " \033[31md/c\033[0m") self.log(logmsg + " \033[31md/c\033[0m")
return False return False
self.log(logmsg + " " + str(len(html))) self.log(logmsg + " " + unicode(len(html)))
return True return True
def tx_mounts(self): def tx_mounts(self):
@@ -1115,7 +1115,8 @@ class HttpCli(object):
excl = None excl = None
if target: if target:
excl, target = (target.split("/", 1) + [""])[:2] excl, target = (target.split("/", 1) + [""])[:2]
ret["k" + excl] = self.gen_tree("/".join([top, excl]).strip("/"), target) sub = self.gen_tree("/".join([top, excl]).strip("/"), target)
ret["k" + quotep(excl)] = sub
try: try:
vn, rem = self.auth.vfs.get(top, self.uname, True, False) vn, rem = self.auth.vfs.get(top, self.uname, True, False)
@@ -1136,7 +1137,7 @@ class HttpCli(object):
vfs_ls = exclude_dotfiles(vfs_ls) vfs_ls = exclude_dotfiles(vfs_ls)
for fn in [x for x in vfs_ls if x != excl]: for fn in [x for x in vfs_ls if x != excl]:
dirs.append(fn) dirs.append(quotep(fn))
for x in vfs_virt.keys(): for x in vfs_virt.keys():
if x != excl: if x != excl:
@@ -1275,19 +1276,24 @@ class HttpCli(object):
del f["rd"] del f["rd"]
if icur: if icur:
q = "select w from up where rd = ? and fn = ?" q = "select w from up where rd = ? and fn = ?"
try:
r = icur.execute(q, (rd, fn)).fetchone() r = icur.execute(q, (rd, fn)).fetchone()
except:
args = s3enc(idx.mem_cur, rd, fn)
r = icur.execute(q, args).fetchone()
tags = {}
f["tags"] = tags
if not r: if not r:
continue continue
w = r[0][:16] w = r[0][:16]
tags = {}
q = "select k, v from mt where w = ? and k != 'x'" q = "select k, v from mt where w = ? and k != 'x'"
for k, v in icur.execute(q, (w,)): for k, v in icur.execute(q, (w,)):
taglist[k] = True taglist[k] = True
tags[k] = v tags[k] = v
f["tags"] = tags
if icur: if icur:
taglist = [k for k in self.args.mte.split(",") if k in taglist] taglist = [k for k in self.args.mte.split(",") if k in taglist]
for f in dirs: for f in dirs:
@@ -1297,7 +1303,7 @@ class HttpCli(object):
try: try:
if not self.args.nih: if not self.args.nih:
srv_info.append(str(socket.gethostname()).split(".")[0]) srv_info.append(unicode(socket.gethostname()).split(".")[0])
except: except:
self.log("#wow #whoa") self.log("#wow #whoa")
pass pass

View File

@@ -81,8 +81,8 @@ class HttpConn(object):
def respath(self, res_name): def respath(self, res_name):
return os.path.join(E.mod, "web", res_name) return os.path.join(E.mod, "web", res_name)
def log(self, msg): def log(self, msg, c=0):
self.log_func(self.log_src, msg) self.log_func(self.log_src, msg, c)
def get_u2idx(self): def get_u2idx(self):
if not self.u2idx: if not self.u2idx:
@@ -129,7 +129,7 @@ class HttpConn(object):
if is_https: if is_https:
if self.sr: if self.sr:
self.log("\033[1;31mTODO: cannot do https in jython\033[0m") self.log("TODO: cannot do https in jython", c="1;31")
return return
self.log_src = self.log_src.replace("[36m", "[35m") self.log_src = self.log_src.replace("[36m", "[35m")
@@ -180,7 +180,7 @@ class HttpConn(object):
pass pass
else: else:
self.log("\033[35mhandshake\033[0m " + em) self.log("handshake\033[0m " + em, c=5)
return return

View File

@@ -38,7 +38,7 @@ class HttpSrv(object):
def accept(self, sck, addr): def accept(self, sck, addr):
"""takes an incoming tcp connection and creates a thread to handle it""" """takes an incoming tcp connection and creates a thread to handle it"""
self.log("%s %s" % addr, "\033[1;30m|%sC-cthr\033[0m" % ("-" * 5,)) self.log("%s %s" % addr, "|%sC-cthr" % ("-" * 5,), c="1;30")
thr = threading.Thread(target=self.thr_client, args=(sck, addr)) thr = threading.Thread(target=self.thr_client, args=(sck, addr))
thr.daemon = True thr.daemon = True
thr.start() thr.start()
@@ -66,11 +66,11 @@ class HttpSrv(object):
thr.start() thr.start()
try: try:
self.log("%s %s" % addr, "\033[1;30m|%sC-crun\033[0m" % ("-" * 6,)) self.log("%s %s" % addr, "|%sC-crun" % ("-" * 6,), c="1;30")
cli.run() cli.run()
finally: finally:
self.log("%s %s" % addr, "\033[1;30m|%sC-cdone\033[0m" % ("-" * 7,)) self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 7,), c="1;30")
try: try:
sck.shutdown(socket.SHUT_RDWR) sck.shutdown(socket.SHUT_RDWR)
sck.close() sck.close()
@@ -78,7 +78,8 @@ class HttpSrv(object):
if not MACOS: if not MACOS:
self.log( self.log(
"%s %s" % addr, "%s %s" % addr,
"\033[1;30mshut({}): {}\033[0m".format(sck.fileno(), ex), "shut({}): {}".format(sck.fileno(), ex),
c="1;30",
) )
if ex.errno not in [10038, 10054, 107, 57, 9]: if ex.errno not in [10038, 10054, 107, 57, 9]:
# 10038 No longer considered a socket # 10038 No longer considered a socket

View File

@@ -10,6 +10,9 @@ import subprocess as sp
from .__init__ import PY2, WINDOWS from .__init__ import PY2, WINDOWS
from .util import fsenc, fsdec from .util import fsenc, fsdec
if not PY2:
unicode = str
class MTag(object): class MTag(object):
def __init__(self, log_func, args): def __init__(self, log_func, args):
@@ -18,13 +21,14 @@ class MTag(object):
self.prefer_mt = False self.prefer_mt = False
mappings = args.mtm mappings = args.mtm
self.backend = "ffprobe" if args.no_mutagen else "mutagen" self.backend = "ffprobe" if args.no_mutagen else "mutagen"
or_ffprobe = " or ffprobe"
if self.backend == "mutagen": if self.backend == "mutagen":
self.get = self.get_mutagen self.get = self.get_mutagen
try: try:
import mutagen import mutagen
except: except:
self.log("\033[33mcould not load mutagen, trying ffprobe instead") self.log("could not load mutagen, trying ffprobe instead", c=3)
self.backend = "ffprobe" self.backend = "ffprobe"
if self.backend == "ffprobe": if self.backend == "ffprobe":
@@ -32,7 +36,7 @@ class MTag(object):
self.prefer_mt = True self.prefer_mt = True
# about 20x slower # about 20x slower
if PY2: if PY2:
cmd = ["ffprobe", "-version"] cmd = [b"ffprobe", b"-version"]
try: try:
sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
except: except:
@@ -41,9 +45,15 @@ class MTag(object):
if not shutil.which("ffprobe"): if not shutil.which("ffprobe"):
self.usable = False self.usable = False
if self.usable and WINDOWS and sys.version_info < (3, 8):
self.usable = False
or_ffprobe = " or python >= 3.8"
msg = "found ffprobe but your python is too old; need 3.8 or newer"
self.log(msg, c=1)
if not self.usable: if not self.usable:
msg = "\033[31mneed mutagen or ffprobe to read media tags so please run this:\n {} -m pip install --user mutagen \033[0m" msg = "need mutagen{} to read media tags so please run this:\n {} -m pip install --user mutagen"
self.log(msg.format(os.path.basename(sys.executable))) self.log(msg.format(or_ffprobe, os.path.basename(sys.executable)), c=1)
return return
# https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html # https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
@@ -115,8 +125,8 @@ class MTag(object):
} }
# self.get = self.compare # self.get = self.compare
def log(self, msg): def log(self, msg, c=0):
self.log_func("mtag", msg) self.log_func("mtag", msg, c)
def normalize_tags(self, ret, md): def normalize_tags(self, ret, md):
for k, v in dict(md).items(): for k, v in dict(md).items():
@@ -133,7 +143,7 @@ class MTag(object):
ret[mk] = [pref, v[0]] ret[mk] = [pref, v[0]]
# take first value # take first value
ret = {k: str(v[1]).strip() for k, v in ret.items()} ret = {k: unicode(v[1]).strip() for k, v in ret.items()}
# track 3/7 => track 3 # track 3/7 => track 3
for k, v in ret.items(): for k, v in ret.items():
@@ -206,7 +216,7 @@ class MTag(object):
return self.normalize_tags(ret, md) return self.normalize_tags(ret, md)
def get_ffprobe(self, abspath): def get_ffprobe(self, abspath):
cmd = ["ffprobe", "-hide_banner", "--", fsenc(abspath)] cmd = [b"ffprobe", b"-hide_banner", b"--", fsenc(abspath)]
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
r = p.communicate() r = p.communicate()
txt = r[1].decode("utf-8", "replace") txt = r[1].decode("utf-8", "replace")
@@ -285,9 +295,7 @@ class MTag(object):
sec *= 60 sec *= 60
sec += int(f) sec += int(f)
except: except:
self.log( self.log("invalid timestr from ffmpeg: [{}]".format(tstr), c=3)
"\033[33minvalid timestr from ffmpeg: [{}]".format(tstr)
)
ret[".dur"] = sec ret[".dur"] = sec
m = ptn_br1.search(ln) m = ptn_br1.search(ln)

View File

@@ -9,7 +9,6 @@ from datetime import datetime, timedelta
import calendar import calendar
from .__init__ import PY2, WINDOWS, MACOS, VT100 from .__init__ import PY2, WINDOWS, MACOS, VT100
from .authsrv import AuthSrv
from .tcpsrv import TcpSrv from .tcpsrv import TcpSrv
from .up2k import Up2k from .up2k import Up2k
from .util import mp from .util import mp
@@ -66,10 +65,10 @@ class SvcHub(object):
self.broker.shutdown() self.broker.shutdown()
print("nailed it") print("nailed it")
def _log_disabled(self, src, msg): def _log_disabled(self, src, msg, c=0):
pass pass
def _log_enabled(self, src, msg): def _log_enabled(self, src, msg, c=0):
"""handles logging from all components""" """handles logging from all components"""
with self.log_mutex: with self.log_mutex:
now = time.time() now = time.time()
@@ -92,6 +91,13 @@ class SvcHub(object):
msg = self.ansi_re.sub("", msg) msg = self.ansi_re.sub("", msg)
if "\033" in src: if "\033" in src:
src = self.ansi_re.sub("", src) src = self.ansi_re.sub("", src)
elif c:
if isinstance(c, int):
msg = "\033[3{}m{}".format(c, msg)
elif "\033" not in c:
msg = "\033[{}m{}\033[0m".format(c, msg)
else:
msg = "{}{}\033[0m".format(c, msg)
ts = datetime.utcfromtimestamp(now).strftime("%H:%M:%S.%f")[:-3] ts = datetime.utcfromtimestamp(now).strftime("%H:%M:%S.%f")[:-3]
msg = fmt.format(ts, src, msg) msg = fmt.format(ts, src, msg)

View File

@@ -68,21 +68,22 @@ class TcpSrv(object):
self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port)) self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port))
while True: while True:
self.log("tcpsrv", "\033[1;30m|%sC-ncli\033[0m" % ("-" * 1,)) self.log("tcpsrv", "|%sC-ncli" % ("-" * 1,), c="1;30")
if self.num_clients.v >= self.args.nc: if self.num_clients.v >= self.args.nc:
time.sleep(0.1) time.sleep(0.1)
continue continue
self.log("tcpsrv", "\033[1;30m|%sC-acc1\033[0m" % ("-" * 2,)) self.log("tcpsrv", "|%sC-acc1" % ("-" * 2,), c="1;30")
ready, _, _ = select.select(self.srv, [], []) ready, _, _ = select.select(self.srv, [], [])
for srv in ready: for srv in ready:
sck, addr = srv.accept() sck, addr = srv.accept()
sip, sport = srv.getsockname() sip, sport = srv.getsockname()
self.log( self.log(
"%s %s" % addr, "%s %s" % addr,
"\033[1;30m|{}C-acc2 \033[0;36m{} \033[3{}m{}".format( "|{}C-acc2 \033[0;36m{} \033[3{}m{}".format(
"-" * 3, sip, sport % 8, sport "-" * 3, sip, sport % 8, sport
), ),
c="1;30",
) )
self.num_clients.add() self.num_clients.add()
self.hub.broker.put(False, "httpconn", sck, addr) self.hub.broker.put(False, "httpconn", sck, addr)

View File

@@ -25,9 +25,11 @@ class U2idx(object):
return return
self.cur = {} self.cur = {}
self.mem_cur = sqlite3.connect(":memory:")
self.mem_cur.execute(r"create table a (b text)")
def log(self, msg): def log(self, msg, c=0):
self.log_func("u2idx", msg) self.log_func("u2idx", msg, c)
def fsearch(self, vols, body): def fsearch(self, vols, body):
"""search by up2k hashlist""" """search by up2k hashlist"""
@@ -37,7 +39,11 @@ class U2idx(object):
fsize = body["size"] fsize = body["size"]
fhash = body["hash"] fhash = body["hash"]
wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash) wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash)
return self.run_query(vols, "w = ?", [wark], "", [])[0]
uq = "substr(w,1,16) = ? and w = ?"
uv = [wark[:16], wark]
return self.run_query(vols, uq, uv, "", [])[0]
def get_cur(self, ptop): def get_cur(self, ptop):
cur = self.cur.get(ptop) cur = self.cur.get(ptop)
@@ -108,6 +114,9 @@ class U2idx(object):
if lim <= 0: if lim <= 0:
break break
if rd.startswith("//") or fn.startswith("//"):
rd, fn = s3dec(rd, fn)
rp = os.path.join(vtop, rd, fn).replace("\\", "/") rp = os.path.join(vtop, rd, fn).replace("\\", "/")
sret.append({"ts": int(ts), "sz": sz, "rp": rp, "w": w[:16]}) sret.append({"ts": int(ts), "sz": sz, "rp": rp, "w": w[:16]})

View File

@@ -25,8 +25,8 @@ from .util import (
sanitize_fn, sanitize_fn,
ren_open, ren_open,
atomic_move, atomic_move,
w8b64enc, s3enc,
w8b64dec, s3dec,
statdir, statdir,
) )
from .mtag import MTag from .mtag import MTag
@@ -64,14 +64,18 @@ class Up2k(object):
self.flags = {} self.flags = {}
self.cur = {} self.cur = {}
self.mtag = None self.mtag = None
self.n_mtag_thr_alive = 0 self.n_mtag_tags_added = -1
self.n_mtag_tags_added = 0
self.mem_cur = None self.mem_cur = None
self.sqlite_ver = None
self.no_expr_idx = False
if HAVE_SQLITE3: if HAVE_SQLITE3:
# mojibake detector # mojibake detector
self.mem_cur = self._orz(":memory:") self.mem_cur = self._orz(":memory:")
self.mem_cur.execute(r"create table a (b text)") self.mem_cur.execute(r"create table a (b text)")
self.sqlite_ver = tuple([int(x) for x in sqlite3.sqlite_version.split(".")])
if self.sqlite_ver < (3, 9):
self.no_expr_idx = True
if WINDOWS: if WINDOWS:
# usually fails to set lastmod too quickly # usually fails to set lastmod too quickly
@@ -87,7 +91,7 @@ class Up2k(object):
self.log("could not initialize sqlite3, will use in-memory registry only") self.log("could not initialize sqlite3, will use in-memory registry only")
# this is kinda jank # this is kinda jank
auth = AuthSrv(self.args, self.log, False) auth = AuthSrv(self.args, self.log_func, False)
have_e2d = self.init_indexes(auth) have_e2d = self.init_indexes(auth)
if have_e2d: if have_e2d:
@@ -103,31 +107,8 @@ class Up2k(object):
thr.daemon = True thr.daemon = True
thr.start() thr.start()
def log(self, msg): def log(self, msg, c=0):
self.log_func("up2k", msg + "\033[K") self.log_func("up2k", msg + "\033[K", c)
def w8enc(self, rd, fn):
ret = []
for v in [rd, fn]:
try:
self.mem_cur.execute("select * from a where b = ?", (v,))
ret.append(v)
except:
ret.append("//" + w8b64enc(v))
# self.log("mojien/{} [{}] {}".format(k, v, ret[-1][2:]))
return tuple(ret)
def w8dec(self, rd, fn):
ret = []
for k, v in [["d", rd], ["f", fn]]:
if v.startswith("//"):
ret.append(w8b64dec(v[2:]))
# self.log("mojide/{} [{}] {}".format(k, ret[-1], v[2:]))
else:
ret.append(v)
return tuple(ret)
def _vis_job_progress(self, job): def _vis_job_progress(self, job):
perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"])) perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
@@ -141,24 +122,46 @@ class Up2k(object):
return ret return ret
def _expr_idx_filter(self, flags):
if not self.no_expr_idx:
return False, flags
ret = {k: v for k, v in flags.items() if not k.startswith("e2t")}
if ret.keys() == flags.keys():
return False, flags
return True, ret
def init_indexes(self, auth): def init_indexes(self, auth):
self.pp = ProgressPrinter() self.pp = ProgressPrinter()
vols = auth.vfs.all_vols.values() vols = auth.vfs.all_vols.values()
t0 = time.time() t0 = time.time()
have_e2d = False have_e2d = False
if self.no_expr_idx:
modified = False
for vol in vols:
m, f = self._expr_idx_filter(vol.flags)
if m:
vol.flags = f
modified = True
if modified:
msg = "disabling -e2t because your sqlite belongs in a museum"
self.log(msg, c=3)
live_vols = [] live_vols = []
for vol in vols: for vol in vols:
try: try:
os.listdir(vol.realpath) os.listdir(vol.realpath)
live_vols.append(vol) live_vols.append(vol)
except: except:
self.log("\033[31mcannot access " + vol.realpath) self.log("cannot access " + vol.realpath, c=1)
vols = live_vols vols = live_vols
need_mtag = False need_mtag = False
for vol in auth.vfs.all_vols.values(): for vol in vols:
if "e2t" in vol.flags: if "e2t" in vol.flags:
need_mtag = True need_mtag = True
@@ -204,8 +207,8 @@ class Up2k(object):
self.log(msg.format(len(vols), time.time() - t0)) self.log(msg.format(len(vols), time.time() - t0))
if needed_mutagen: if needed_mutagen:
msg = "\033[31mcould not read tags because no backends are available (mutagen or ffprobe)\033[0m" msg = "could not read tags because no backends are available (mutagen or ffprobe)"
self.log(msg) self.log(msg, c=1)
return have_e2d return have_e2d
@@ -214,6 +217,8 @@ class Up2k(object):
if ptop in self.registry: if ptop in self.registry:
return None return None
_, flags = self._expr_idx_filter(flags)
reg = {} reg = {}
path = os.path.join(ptop, ".hist", "up2k.snap") path = os.path.join(ptop, ".hist", "up2k.snap")
if "e2d" in flags and os.path.exists(path): if "e2d" in flags and os.path.exists(path):
@@ -312,7 +317,7 @@ class Up2k(object):
try: try:
c = dbw[0].execute(sql, (rd, fn)) c = dbw[0].execute(sql, (rd, fn))
except: except:
c = dbw[0].execute(sql, self.w8enc(rd, fn)) c = dbw[0].execute(sql, s3enc(self.mem_cur, rd, fn))
in_db = list(c.fetchall()) in_db = list(c.fetchall())
if in_db: if in_db:
@@ -366,7 +371,7 @@ class Up2k(object):
for dwark, dts, dsz, drd, dfn in c: for dwark, dts, dsz, drd, dfn in c:
nchecked += 1 nchecked += 1
if drd.startswith("//") or dfn.startswith("//"): if drd.startswith("//") or dfn.startswith("//"):
drd, dfn = self.w8dec(drd, dfn) drd, dfn = s3dec(drd, dfn)
abspath = os.path.join(top, drd, dfn) abspath = os.path.join(top, drd, dfn)
# almost zero overhead dw # almost zero overhead dw
@@ -432,12 +437,11 @@ class Up2k(object):
if self.mtag.prefer_mt and not self.args.no_mtag_mt: if self.mtag.prefer_mt and not self.args.no_mtag_mt:
# mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor
# both do crazy runahead so lets reinvent another wheel # both do crazy runahead so lets reinvent another wheel
nw = os.cpu_count() nw = os.cpu_count() if hasattr(os, "cpu_count") else 4
if not self.n_mtag_thr_alive: if self.n_mtag_tags_added == -1:
msg = 'using {} cores for tag reader "{}"' self.log("using {}x {}".format(nw, self.mtag.backend))
self.log(msg.format(nw, self.mtag.backend)) self.n_mtag_tags_added = 0
self.n_mtag_thr_alive = nw
mpool = Queue(nw) mpool = Queue(nw)
for _ in range(nw): for _ in range(nw):
thr = threading.Thread(target=self._tag_thr, args=(mpool,)) thr = threading.Thread(target=self._tag_thr, args=(mpool,))
@@ -453,6 +457,9 @@ class Up2k(object):
if c2.execute(q, (w[:16],)).fetchone(): if c2.execute(q, (w[:16],)).fetchone():
continue continue
if rd.startswith("//") or fn.startswith("//"):
rd, fn = s3dec(rd, fn)
abspath = os.path.join(ptop, rd, fn) abspath = os.path.join(ptop, rd, fn)
self.pp.msg = "c{} {}".format(n_left, abspath) self.pp.msg = "c{} {}".format(n_left, abspath)
args = c3, entags, w, abspath args = c3, entags, w, abspath
@@ -474,11 +481,12 @@ class Up2k(object):
last_write = time.time() last_write = time.time()
n_buf = 0 n_buf = 0
if self.n_mtag_thr_alive: if mpool:
mpool.join() for _ in range(mpool.maxsize):
for _ in range(self.n_mtag_thr_alive):
mpool.put(None) mpool.put(None)
mpool.join()
c3.close() c3.close()
c2.close() c2.close()
@@ -488,7 +496,8 @@ class Up2k(object):
while True: while True:
task = q.get() task = q.get()
if not task: if not task:
break q.task_done()
return
try: try:
write_cur, entags, wark, abspath = task write_cur, entags, wark, abspath = task
@@ -497,10 +506,10 @@ class Up2k(object):
n = self._tag_file(write_cur, entags, wark, abspath, tags) n = self._tag_file(write_cur, entags, wark, abspath, tags)
self.n_mtag_tags_added += n self.n_mtag_tags_added += n
except: except:
with self.mutex: ex = traceback.format_exc()
self.n_mtag_thr_alive -= 1 msg = "{} failed to read tags from {}:\n{}"
raise self.log(msg.format(self.mtag.backend, abspath, ex), c=3)
finally:
q.task_done() q.task_done()
def _tag_file(self, write_cur, entags, wark, abspath, tags=None): def _tag_file(self, write_cur, entags, wark, abspath, tags=None):
@@ -615,8 +624,12 @@ class Up2k(object):
except: except:
pass pass
idx = r"create index up_w on up(substr(w,1,16))"
if self.no_expr_idx:
idx = r"create index up_w on up(w)"
for cmd in [ for cmd in [
r"create index up_w on up(substr(w,1,16))", idx,
r"create table mt (w text, k text, v int)", r"create table mt (w text, k text, v int)",
r"create index mt_w on mt(w)", r"create index mt_w on mt(w)",
r"create index mt_k on mt(k)", r"create index mt_k on mt(k)",
@@ -661,12 +674,17 @@ class Up2k(object):
cur = self.cur.get(cj["ptop"], None) cur = self.cur.get(cj["ptop"], None)
reg = self.registry[cj["ptop"]] reg = self.registry[cj["ptop"]]
if cur: if cur:
if self.no_expr_idx:
q = r"select * from up where w = ?"
argv = (wark,)
else:
q = r"select * from up where substr(w,1,16) = ? and w = ?" q = r"select * from up where substr(w,1,16) = ? and w = ?"
argv = (wark[:16], wark) argv = (wark[:16], wark)
cur = cur.execute(q, argv) cur = cur.execute(q, argv)
for _, dtime, dsize, dp_dir, dp_fn in cur: for _, dtime, dsize, dp_dir, dp_fn in cur:
if dp_dir.startswith("//") or dp_fn.startswith("//"): if dp_dir.startswith("//") or dp_fn.startswith("//"):
dp_dir, dp_fn = self.w8dec(dp_dir, dp_fn) dp_dir, dp_fn = s3dec(dp_dir, dp_fn)
dp_abs = os.path.join(cj["ptop"], dp_dir, dp_fn).replace("\\", "/") dp_abs = os.path.join(cj["ptop"], dp_dir, dp_fn).replace("\\", "/")
# relying on path.exists to return false on broken symlinks # relying on path.exists to return false on broken symlinks
@@ -788,8 +806,13 @@ class Up2k(object):
raise OSError() raise OSError()
elif fs1 == fs2: elif fs1 == fs2:
# same fs; make symlink as relative as possible # same fs; make symlink as relative as possible
nsrc = src.replace("\\", "/").split("/") v = []
ndst = dst.replace("\\", "/").split("/") for p in [src, dst]:
if WINDOWS:
p = p.replace("\\", "/")
v.append(p.split("/"))
nsrc, ndst = v
nc = 0 nc = 0
for a, b in zip(nsrc, ndst): for a, b in zip(nsrc, ndst):
if a != b: if a != b:
@@ -797,7 +820,8 @@ class Up2k(object):
nc += 1 nc += 1
if nc > 1: if nc > 1:
lsrc = nsrc[nc:] lsrc = nsrc[nc:]
lsrc = "../" * (len(lsrc) - 1) + "/".join(lsrc) hops = len(ndst[nc:]) - 1
lsrc = "../" * hops + "/".join(lsrc)
os.symlink(fsenc(lsrc), fsenc(ldst)) os.symlink(fsenc(lsrc), fsenc(ldst))
except (AttributeError, OSError) as ex: except (AttributeError, OSError) as ex:
self.log("cannot symlink; creating copy: " + repr(ex)) self.log("cannot symlink; creating copy: " + repr(ex))
@@ -882,7 +906,7 @@ class Up2k(object):
try: try:
db.execute(sql, (rd, fn)) db.execute(sql, (rd, fn))
except: except:
db.execute(sql, self.w8enc(rd, fn)) db.execute(sql, s3enc(self.mem_cur, rd, fn))
def db_add(self, db, wark, rd, fn, ts, sz): def db_add(self, db, wark, rd, fn, ts, sz):
sql = "insert into up values (?,?,?,?,?)" sql = "insert into up values (?,?,?,?,?)"
@@ -890,7 +914,7 @@ class Up2k(object):
try: try:
db.execute(sql, v) db.execute(sql, v)
except: except:
rd, fn = self.w8enc(rd, fn) rd, fn = s3enc(self.mem_cur, rd, fn)
v = (wark, ts, sz, rd, fn) v = (wark, ts, sz, rd, fn)
db.execute(sql, v) db.execute(sql, v)
@@ -919,7 +943,7 @@ class Up2k(object):
ret = [] ret = []
with open(path, "rb", 512 * 1024) as f: with open(path, "rb", 512 * 1024) as f:
while fsz > 0: while fsz > 0:
self.pp.msg = "{} MB".format(int(fsz / 1024 / 1024)) self.pp.msg = "{} MB, {}".format(int(fsz / 1024 / 1024), path)
hashobj = hashlib.sha512() hashobj = hashlib.sha512()
rem = min(csz, fsz) rem = min(csz, fsz)
fsz -= rem fsz -= rem
@@ -1034,12 +1058,12 @@ class Up2k(object):
with self.mutex: with self.mutex:
cur = self.cur[ptop] cur = self.cur[ptop]
if not cur: if not cur:
self.log("\033[31mno cursor to write tags with??") self.log("no cursor to write tags with??", c=1)
continue continue
entags = self.entags[ptop] entags = self.entags[ptop]
if not entags: if not entags:
self.log("\033[33mno entags okay.jpg") self.log("no entags okay.jpg", c=3)
continue continue
if "e2t" in self.flags[ptop]: if "e2t" in self.flags[ptop]:

View File

@@ -119,19 +119,26 @@ class ProgressPrinter(threading.Thread):
continue continue
msg = self.msg msg = self.msg
m = " {}\033[K\r".format(msg) uprint(" {}\033[K\r".format(msg))
try:
print(m, end="")
except UnicodeEncodeError:
try:
print(m.encode("utf-8", "replace").decode(), end="")
except:
print(m.encode("ascii", "replace").decode(), end="")
print("\033[K", end="") print("\033[K", end="")
sys.stdout.flush() # necessary on win10 even w/ stderr btw sys.stdout.flush() # necessary on win10 even w/ stderr btw
def uprint(msg):
try:
print(msg, end="")
except UnicodeEncodeError:
try:
print(msg.encode("utf-8", "replace").decode(), end="")
except:
print(msg.encode("ascii", "replace").decode(), end="")
def nuprint(msg):
uprint("{}\n".format(msg))
@contextlib.contextmanager @contextlib.contextmanager
def ren_open(fname, *args, **kwargs): def ren_open(fname, *args, **kwargs):
fdir = kwargs.pop("fdir", None) fdir = kwargs.pop("fdir", None)
@@ -597,6 +604,31 @@ else:
fsdec = w8dec fsdec = w8dec
def s3enc(mem_cur, rd, fn):
ret = []
for v in [rd, fn]:
try:
mem_cur.execute("select * from a where b = ?", (v,))
ret.append(v)
except:
ret.append("//" + w8b64enc(v))
# self.log("mojien/{} [{}] {}".format(k, v, ret[-1][2:]))
return tuple(ret)
def s3dec(rd, fn):
ret = []
for k, v in [["d", rd], ["f", fn]]:
if v.startswith("//"):
ret.append(w8b64dec(v[2:]))
# self.log("mojide/{} [{}] {}".format(k, ret[-1], v[2:]))
else:
ret.append(v)
return tuple(ret)
def atomic_move(src, dst): def atomic_move(src, dst):
if not PY2: if not PY2:
os.replace(src, dst) os.replace(src, dst)
@@ -734,7 +766,8 @@ def statdir(logger, scandir, lstat, top):
try: try:
yield [fsdec(fh.name), fh.stat(follow_symlinks=not lstat)] yield [fsdec(fh.name), fh.stat(follow_symlinks=not lstat)]
except Exception as ex: except Exception as ex:
logger("scan-stat: {} @ {}".format(repr(ex), fsdec(fh.path))) msg = "scan-stat: \033[36m{} @ {}"
logger(msg.format(repr(ex), fsdec(fh.path)))
else: else:
src = "listdir" src = "listdir"
fun = os.lstat if lstat else os.stat fun = os.lstat if lstat else os.stat
@@ -743,9 +776,11 @@ def statdir(logger, scandir, lstat, top):
try: try:
yield [fsdec(name), fun(abspath)] yield [fsdec(name), fun(abspath)]
except Exception as ex: except Exception as ex:
logger("list-stat: {} @ {}".format(repr(ex), fsdec(abspath))) msg = "list-stat: \033[36m{} @ {}"
logger(msg.format(repr(ex), fsdec(abspath)))
except Exception as ex: except Exception as ex:
logger("{}: {} @ {}".format(src, repr(ex), top)) logger("{}: \033[31m{} @ {}".format(src, repr(ex), top))
def unescape_cookie(orig): def unescape_cookie(orig):

View File

@@ -42,12 +42,8 @@ body {
#path #entree { #path #entree {
margin-left: -.7em; margin-left: -.7em;
} }
#treetab {
display: none;
}
#files { #files {
border-spacing: 0; border-spacing: 0;
margin-top: 2em;
z-index: 1; z-index: 1;
position: relative; position: relative;
} }
@@ -55,11 +51,10 @@ body {
display: block; display: block;
padding: .3em 0; padding: .3em 0;
} }
#files[ts] tbody div a { #files tbody div a {
color: #f5a; color: #f5a;
} }
a, a, #files tbody div a:last-child {
#files[ts] tbody div a:last-child {
color: #fc5; color: #fc5;
padding: .2em; padding: .2em;
text-decoration: none; text-decoration: none;
@@ -158,6 +153,15 @@ a,
.logue { .logue {
padding: .2em 1.5em; padding: .2em 1.5em;
} }
.logue:empty {
display: none;
}
#pro.logue {
margin-bottom: .8em;
}
#epi.logue {
margin: .8em 0;
}
#srv_info { #srv_info {
opacity: .5; opacity: .5;
font-size: .8em; font-size: .8em;
@@ -443,12 +447,40 @@ input[type="checkbox"]:checked+label {
#files td div a:last-child { #files td div a:last-child {
width: 100%; width: 100%;
} }
#tree, #wrap {
#treefiles { margin-top: 2em;
vertical-align: top;
} }
#tree { #tree {
padding-top: 2em; display: none;
position: fixed;
left: 0;
bottom: 0;
top: 7em;
padding-top: .2em;
overflow-y: auto;
-ms-scroll-chaining: none;
overscroll-behavior-y: none;
scrollbar-color: #eb0 #333;
}
#thx_ff {
padding: 5em 0;
}
#tree::-webkit-scrollbar-track {
background: #333;
}
#tree::-webkit-scrollbar {
background: #333;
}
#tree::-webkit-scrollbar-thumb {
background: #eb0;
}
#tree:hover {
z-index: 2;
}
#treeul {
position: relative;
left: -1.7em;
width: calc(100% + 1.3em);
} }
#tree>a+a { #tree>a+a {
padding: .2em .4em; padding: .2em .4em;
@@ -472,24 +504,22 @@ input[type="checkbox"]:checked+label {
padding: .3em .5em; padding: .3em .5em;
font-size: 1.5em; font-size: 1.5em;
} }
#treefiles #files tbody {
border-radius: 0 .7em 0 .7em;
}
#treefiles #files thead th:nth-child(1) {
border-radius: .7em 0 0 0;
}
#tree ul, #tree ul,
#tree li { #tree li {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
#tree ul { #tree ul {
border-left: .2em solid #444; border-left: .2em solid #555;
} }
#tree li { #tree li {
margin-left: 1em; margin-left: 1em;
list-style: none; list-style: none;
white-space: nowrap; border-top: 1px solid #4c4c4c;
border-bottom: 1px solid #222;
}
#tree li:last-child {
border-bottom: none;
} }
#treeul a.hl { #treeul a.hl {
color: #400; color: #400;
@@ -503,24 +533,12 @@ input[type="checkbox"]:checked+label {
#treeul a+a { #treeul a+a {
width: calc(100% - 2em); width: calc(100% - 2em);
background: #333; background: #333;
line-height: 1em;
} }
#treeul a+a:hover { #treeul a+a:hover {
background: #222; background: #222;
color: #fff; color: #fff;
} }
#treeul {
position: relative;
overflow: hidden;
left: -1.7em;
}
#treeul:hover {
z-index: 2;
overflow: visible;
}
#treeul:hover a+a {
width: auto;
min-width: calc(100% - 2em);
}
#treeul a:first-child { #treeul a:first-child {
font-family: monospace, monospace; font-family: monospace, monospace;
} }
@@ -579,3 +597,38 @@ input[type="checkbox"]:checked+label {
color: #300; color: #300;
background: #fea; background: #fea;
} }
#op_cfg {
max-width: none;
margin-right: 1.5em;
}
#key_notation>span {
display: inline-block;
padding: .2em .4em;
}
#op_cfg h3 {
margin: .8em 0 0 .6em;
padding: 0;
border-bottom: 1px solid #555;
}
#opdesc {
display: none;
}
#ops:hover #opdesc {
display: block;
background: linear-gradient(0deg,#555, #4c4c4c 80%, #444);
box-shadow: 0 .3em 1em #222;
padding: 1em;
border-radius: .3em;
position: absolute;
z-index: 3;
top: 6em;
right: 1.5em;
}
#opdesc code {
background: #3c3c3c;
padding: .2em .3em;
border-top: 1px solid #777;
border-radius: .3em;
font-family: monospace, monospace;
line-height: 2em;
}

View File

@@ -12,17 +12,19 @@
<body> <body>
<div id="ops"> <div id="ops">
<a href="#" data-dest="">---</a> <a href="#" data-dest="" data-desc="close submenu">---</a>
<a href="#" data-perm="read" data-dest="search">🔎</a> <a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,&lt;br /&gt;&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,&lt;br /&gt;&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a>
{%- if have_up2k_idx %} {%- if have_up2k_idx %}
<a href="#" data-dest="up2k">🚀</a> <a href="#" data-dest="up2k" data-desc="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>
{%- else %} {%- else %}
<a href="#" data-perm="write" data-dest="up2k">🚀</a> <a href="#" data-perm="write" data-dest="up2k" data-desc="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>
{%- endif %} {%- endif %}
<a href="#" data-perm="write" data-dest="bup">🎈</a> <a href="#" data-perm="write" data-dest="bup" data-desc="bup: basic uploader, even supports netscape 4.0">🎈</a>
<a href="#" data-perm="write" data-dest="mkdir">📂</a> <a href="#" data-perm="write" data-dest="mkdir" data-desc="mkdir: create a new directory">📂</a>
<a href="#" data-perm="write" data-dest="new_md">📝</a> <a href="#" data-perm="write" data-dest="new_md" data-desc="new-md: create a new markdown document">📝</a>
<a href="#" data-perm="write" data-dest="msg">📟</a> <a href="#" data-perm="write" data-dest="msg" data-desc="msg: send a message to the server log">📟</a>
<a href="#" data-dest="cfg" data-desc="configuration options">⚙️</a>
<div id="opdesc"></div>
</div> </div>
<div id="op_search" class="opview"> <div id="op_search" class="opview">
@@ -33,8 +35,14 @@
{%- endif %} {%- endif %}
<div id="srch_q"></div> <div id="srch_q"></div>
</div> </div>
{%- include 'upload.html' %} {%- include 'upload.html' %}
<div id="op_cfg" class="opview opbox">
<h3>key notation</h3>
<div id="key_notation"></div>
</div>
<h1 id="path"> <h1 id="path">
<a href="#" id="entree">🌲</a> <a href="#" id="entree">🌲</a>
{%- for n in vpnodes %} {%- for n in vpnodes %}
@@ -42,20 +50,18 @@
{%- endfor %} {%- endfor %}
</h1> </h1>
<div id="pro" class="logue">{{ logues[0] }}</div> <div id="tree">
<table id="treetab">
<tr>
<td id="tree">
<a href="#" id="detree">🍞...</a> <a href="#" id="detree">🍞...</a>
<a href="#" step="2" id="twobytwo">+</a> <a href="#" step="2" id="twobytwo">+</a>
<a href="#" step="-2" id="twig">&ndash;</a> <a href="#" step="-2" id="twig">&ndash;</a>
<a href="#" id="dyntree">a</a> <a href="#" id="dyntree">a</a>
<ul id="treeul"></ul> <ul id="treeul"></ul>
</td> <div id="thx_ff">&nbsp;</div>
<td id="treefiles"></td> </div>
</tr>
</table> <div id="wrap">
<div id="pro" class="logue">{{ logues[0] }}</div>
<table id="files"> <table id="files">
<thead> <thead>
@@ -93,6 +99,8 @@
<h2><a href="?h">control-panel</a></h2> <h2><a href="?h">control-panel</a></h2>
</div>
{%- if srv_info %} {%- if srv_info %}
<div id="srv_info"><span>{{ srv_info }}</span></div> <div id="srv_info"><span>{{ srv_info }}</span></div>
{%- endif %} {%- endif %}

View File

@@ -467,8 +467,7 @@ function play(tid, call_depth) {
var o = ebi(oid); var o = ebi(oid);
o.setAttribute('id', 'thx_js'); o.setAttribute('id', 'thx_js');
if (window.history && history.replaceState) { if (window.history && history.replaceState) {
var nurl = (document.location + '').split('#')[0] + '#' + oid; hist_replace(document.location.pathname + '#' + oid);
hist_replace(ebi('files').innerHTML, nurl);
} }
else { else {
document.location.hash = oid; document.location.hash = oid;
@@ -511,7 +510,7 @@ function evau_error(e) {
if (eplaya.error.message) if (eplaya.error.message)
err += '\n\n' + eplaya.error.message; err += '\n\n' + eplaya.error.message;
err += '\n\nFile: «' + decodeURIComponent(eplaya.src.split('/').slice(-1)[0]) + '»'; err += '\n\nFile: «' + uricom_dec(eplaya.src.split('/').slice(-1)[0])[0] + '»';
alert(err); alert(err);
} }
@@ -546,7 +545,7 @@ function autoplay_blocked() {
var na = ebi('blk_na'); var na = ebi('blk_na');
var fn = mp.tracks[mp.au.tid].split(/\//).pop(); var fn = mp.tracks[mp.au.tid].split(/\//).pop();
fn = decodeURIComponent(fn.replace(/\+/g, ' ')); fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
go.textContent = 'Play "' + fn + '"'; go.textContent = 'Play "' + fn + '"';
go.onclick = function (e) { go.onclick = function (e) {
@@ -587,10 +586,11 @@ function autoplay_blocked() {
["name", "name", "name contains &nbsp; (negate with -nope)", "46"] ["name", "name", "name contains &nbsp; (negate with -nope)", "46"]
] ]
]; ];
var oldcfg = [];
if (document.querySelector('#srch_form.tags')) if (document.querySelector('#srch_form.tags'))
sconf.push(["tags", sconf.push(["tags",
["tags", "tags", "tags contains", "46"] ["tags", "tags", "tags contains &nbsp; (^=start, end=$)", "46"]
]); ]);
var html = []; var html = [];
@@ -654,7 +654,7 @@ function autoplay_blocked() {
return; return;
if (this.status !== 200) { if (this.status !== 200) {
alert('ah fug\n' + this.status + ": " + this.responseText); alert("http " + this.status + ": " + this.responseText);
return; return;
} }
@@ -665,8 +665,16 @@ function autoplay_blocked() {
if (ofiles.getAttribute('ts') > this.ts) if (ofiles.getAttribute('ts') > this.ts)
return; return;
if (!oldcfg.length) {
oldcfg = [
ebi('path').style.display,
ebi('tree').style.display,
ebi('wrap').style.marginLeft
];
ebi('path').style.display = 'none'; ebi('path').style.display = 'none';
ebi('tree').style.display = 'none'; ebi('tree').style.display = 'none';
ebi('wrap').style.marginLeft = '0';
}
var html = mk_files_header(tagord); var html = mk_files_header(tagord);
html.push('<tbody>'); html.push('<tbody>');
@@ -715,8 +723,10 @@ function autoplay_blocked() {
function unsearch(e) { function unsearch(e) {
ev(e); ev(e);
ebi('path').style.display = 'inline-block'; ebi('path').style.display = oldcfg[0];
ebi('tree').style.display = 'block'; ebi('tree').style.display = oldcfg[1];
ebi('wrap').style.marginLeft = oldcfg[2];
oldcfg = [];
ebi('files').innerHTML = orig_html; ebi('files').innerHTML = orig_html;
orig_html = null; orig_html = null;
reload_browser(); reload_browser();
@@ -724,35 +734,66 @@ function autoplay_blocked() {
})(); })();
// tree var treectl = (function () {
(function () {
var treedata = null;
var dyn = bcfg_get('dyntree', true); var dyn = bcfg_get('dyntree', true);
var treesz = icfg_get('treesz', 16); var treesz = icfg_get('treesz', 16);
treesz = isNaN(treesz) ? 16 : Math.min(Math.max(treesz, 4), 50); treesz = Math.min(Math.max(treesz, 4), 50);
console.log('treesz [' + treesz + ']'); console.log('treesz [' + treesz + ']');
function entree(e) { function entree(e) {
ev(e); ev(e);
ebi('path').style.display = 'none'; ebi('path').style.display = 'none';
var treetab = ebi('treetab'); var tree = ebi('tree');
var treefiles = ebi('treefiles'); tree.style.display = 'block';
treetab.style.display = 'table';
treefiles.appendChild(ebi('pro'));
treefiles.appendChild(ebi('files'));
treefiles.appendChild(ebi('epi'));
swrite('entreed', 'tree'); swrite('entreed', 'tree');
get_tree("", get_vpath()); get_tree("", get_evpath(), true);
onresize();
} }
function get_tree(top, dst) { function detree(e) {
ev(e);
ebi('tree').style.display = 'none';
ebi('path').style.display = 'inline-block';
ebi('wrap').style.marginLeft = '0';
swrite('entreed', 'na');
}
function onscroll() {
var top = ebi('wrap').getBoundingClientRect().top;
ebi('tree').style.top = Math.max(0, parseInt(top)) + 'px';
}
window.addEventListener('scroll', onscroll);
function periodic() {
onscroll();
setTimeout(periodic, document.visibilityState ? 200 : 5000);
}
periodic();
function onresize(e) {
var q = '#tree';
var nq = 0;
while (dyn) {
nq++;
q += '>ul>li';
if (!document.querySelector(q))
break;
}
var w = treesz + nq;
ebi('tree').style.width = w + 'em';
ebi('wrap').style.marginLeft = w + 'em';
onscroll();
}
window.addEventListener('resize', onresize);
function get_tree(top, dst, rst) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.top = top; xhr.top = top;
xhr.dst = dst; xhr.dst = dst;
xhr.rst = rst;
xhr.ts = new Date().getTime();
xhr.open('GET', dst + '?tree=' + top, true); xhr.open('GET', dst + '?tree=' + top, true);
xhr.onreadystatechange = recvtree; xhr.onreadystatechange = recvtree;
xhr.send(); xhr.send();
@@ -764,12 +805,19 @@ function autoplay_blocked() {
return; return;
if (this.status !== 200) { if (this.status !== 200) {
alert('ah fug\n' + this.status + ": " + this.responseText); alert("http " + this.status + ": " + this.responseText);
return; return;
} }
var cur = ebi('treeul').getAttribute('ts');
if (cur && parseInt(cur) > this.ts) {
console.log("reject tree");
return;
}
ebi('treeul').setAttribute('ts', this.ts);
var top = this.top == '.' ? this.dst : this.top, var top = this.top == '.' ? this.dst : this.top,
name = top.split('/').slice(-2)[0], name = uricom_dec(top.split('/').slice(-2)[0])[0],
rtop = top.replace(/^\/+/, ""); rtop = top.replace(/^\/+/, "");
try { try {
@@ -781,7 +829,7 @@ function autoplay_blocked() {
var html = parsetree(res, rtop); var html = parsetree(res, rtop);
if (!this.top) { if (!this.top) {
html = '<li><a href="#">-</a><a href="/">[root]</a>\n<ul>' + html; html = '<li><a href="#">-</a><a href="/">[root]</a>\n<ul>' + html;
if (!ebi('treeul').getElementsByTagName('li').length) if (this.rst || !ebi('treeul').getElementsByTagName('li').length)
ebi('treeul').innerHTML = html + '</ul></li>'; ebi('treeul').innerHTML = html + '</ul></li>';
} }
else { else {
@@ -803,24 +851,11 @@ function autoplay_blocked() {
document.querySelector('#treeul>li>a+a').textContent = '[root]'; document.querySelector('#treeul>li>a+a').textContent = '[root]';
despin('#tree'); despin('#tree');
reload_tree(); reload_tree();
rescale_tree(); onresize();
}
function rescale_tree() {
var q = '#tree';
var nq = 0;
while (true) {
nq++;
q += '>ul>li';
if (!document.querySelector(q))
break;
}
var w = treesz + (dyn ? nq : 0);
ebi('treeul').style.width = w + 'em';
} }
function reload_tree() { function reload_tree() {
var cdir = get_vpath(); var cdir = get_evpath();
var links = document.querySelectorAll('#treeul a+a'); var links = document.querySelectorAll('#treeul a+a');
for (var a = 0, aa = links.length; a < aa; a++) { for (var a = 0, aa = links.length; a < aa; a++) {
var href = links[a].getAttribute('href'); var href = links[a].getAttribute('href');
@@ -841,12 +876,20 @@ function autoplay_blocked() {
treegrow.call(this.previousSibling, e); treegrow.call(this.previousSibling, e);
return; return;
} }
reqls(this.getAttribute('href'), true);
}
function reqls(url, hpush) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.top = this.getAttribute('href'); xhr.top = url;
xhr.hpush = hpush;
xhr.ts = new Date().getTime();
xhr.open('GET', xhr.top + '?ls', true); xhr.open('GET', xhr.top + '?ls', true);
xhr.onreadystatechange = recvls; xhr.onreadystatechange = recvls;
xhr.send(); xhr.send();
if (hpush)
get_tree('.', xhr.top); get_tree('.', xhr.top);
enspin('#files'); enspin('#files');
} }
@@ -858,7 +901,7 @@ function autoplay_blocked() {
rm.parentNode.removeChild(rm); rm.parentNode.removeChild(rm);
} }
this.textContent = '+'; this.textContent = '+';
rescale_tree(); onresize();
return; return;
} }
var dst = this.getAttribute('dst'); var dst = this.getAttribute('dst');
@@ -870,10 +913,17 @@ function autoplay_blocked() {
return; return;
if (this.status !== 200) { if (this.status !== 200) {
alert('ah fug\n' + this.status + ": " + this.responseText); alert("http " + this.status + ": " + this.responseText);
return; return;
} }
var cur = ebi('files').getAttribute('ts');
if (cur && parseInt(cur) > this.ts) {
console.log("reject ls");
return;
}
ebi('files').setAttribute('ts', this.ts);
try { try {
var res = JSON.parse(this.responseText); var res = JSON.parse(this.responseText);
} }
@@ -890,7 +940,7 @@ function autoplay_blocked() {
for (var a = 0; a < nodes.length; a++) { for (var a = 0; a < nodes.length; a++) {
var r = nodes[a], var r = nodes[a],
ln = ['<tr><td>' + r.lead + '</td><td><a href="' + ln = ['<tr><td>' + r.lead + '</td><td><a href="' +
top + r.href + '">' + esc(decodeURIComponent(r.href)) + '</a>', r.sz]; top + r.href + '">' + esc(uricom_dec(r.href)[0]) + '</a>', r.sz];
for (var b = 0; b < res.taglist.length; b++) { for (var b = 0; b < res.taglist.length; b++) {
var k = res.taglist[b], var k = res.taglist[b],
@@ -913,7 +963,9 @@ function autoplay_blocked() {
html = html.join('\n'); html = html.join('\n');
ebi('files').innerHTML = html; ebi('files').innerHTML = html;
hist_push(html, this.top); if (this.hpush)
hist_push(this.top);
apply_perms(res.perms); apply_perms(res.perms);
despin('#files'); despin('#files');
@@ -921,6 +973,7 @@ function autoplay_blocked() {
ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : ""; ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : "";
filecols.set_style(); filecols.set_style();
mukey.render();
reload_tree(); reload_tree();
reload_browser(); reload_browser();
} }
@@ -936,12 +989,14 @@ function autoplay_blocked() {
keys.sort(); keys.sort();
for (var a = 0; a < keys.length; a++) { for (var a = 0; a < keys.length; a++) {
var kk = keys[a], var kk = keys[a],
k = kk.slice(1), ks = kk.slice(1),
url = '/' + (top ? top + k : k) + '/', k = uricom_dec(ks),
ek = esc(k), hek = esc(k[0]),
uek = k[1] ? uricom_enc(k[0], true) : k[0],
url = '/' + (top ? top + uek : uek) + '/',
sym = res[kk] ? '-' : '+', sym = res[kk] ? '-' : '+',
link = '<a href="#">' + sym + '</a><a href="' + link = '<a href="#">' + sym + '</a><a href="' +
esc(url) + '">' + ek + '</a>'; url + '">' + hek + '</a>';
if (res[kk]) { if (res[kk]) {
var subtree = parsetree(res[kk], url.slice(1)); var subtree = parsetree(res[kk], url.slice(1));
@@ -954,25 +1009,11 @@ function autoplay_blocked() {
return ret; return ret;
} }
function detree(e) {
ev(e);
var treetab = ebi('treetab');
treetab.parentNode.insertBefore(ebi('pro'), treetab);
treetab.parentNode.insertBefore(ebi('files'), treetab.nextSibling);
treetab.parentNode.insertBefore(ebi('epi'), ebi('files').nextSibling);
ebi('path').style.display = 'inline-block';
treetab.style.display = 'none';
swrite('entreed', 'na');
}
function dyntree(e) { function dyntree(e) {
ev(e); ev(e);
dyn = !dyn; dyn = !dyn;
bcfg_set('dyntree', dyn); bcfg_set('dyntree', dyn);
rescale_tree(); onresize();
} }
function scaletree(e) { function scaletree(e) {
@@ -982,7 +1023,7 @@ function autoplay_blocked() {
treesz = 16; treesz = 16;
swrite('treesz', treesz); swrite('treesz', treesz);
rescale_tree(); onresize();
} }
ebi('entree').onclick = entree; ebi('entree').onclick = entree;
@@ -994,19 +1035,22 @@ function autoplay_blocked() {
entree(); entree();
window.onpopstate = function (e) { window.onpopstate = function (e) {
console.log(e.url + ' ,, ' + ((e.state + '').slice(0, 64))); console.log("h-pop " + e.state);
var html = sessionStorage.getItem(e.state || 1); if (!e.state)
if (!html)
return; return;
ebi('files').innerHTML = html; var url = new URL(e.state, "https://" + document.location.host);
reload_tree(); url = url.pathname;
reload_browser(); get_tree("", url, true);
reqls(url);
}; };
if (window.history && history.pushState) { if (window.history && history.pushState) {
var u = get_vpath() + window.location.hash; hist_replace(get_evpath() + window.location.hash);
hist_replace(ebi('files').innerHTML, u); }
return {
"onscroll": onscroll
} }
})(); })();
@@ -1168,6 +1212,137 @@ var filecols = (function () {
})(); })();
var mukey = (function () {
var maps = {
"rekobo_alnum": [
"1B ", "2B ", "3B ", "4B ", "5B ", "6B ", "7B ", "8B ", "9B ", "10B", "11B", "12B",
"1A ", "2A ", "3A ", "4A ", "5A ", "6A ", "7A ", "8A ", "9A ", "10A", "11A", "12A"
],
"rekobo_classic": [
"B ", "F# ", "Db ", "Ab ", "Eb ", "Bb ", "F ", "C ", "G ", "D ", "A ", "E ",
"Abm", "Ebm", "Bbm", "Fm ", "Cm ", "Gm ", "Dm ", "Am ", "Em ", "Bm ", "F#m", "Dbm"
],
"traktor_musical": [
"B ", "Gb ", "Db ", "Ab ", "Eb ", "Bb ", "F ", "C ", "G ", "D ", "A ", "E ",
"Abm", "Ebm", "Bbm", "Fm ", "Cm ", "Gm ", "Dm ", "Am ", "Em ", "Bm ", "Gbm", "Dbm"
],
"traktor_sharps": [
"B ", "F# ", "C# ", "G# ", "D# ", "A# ", "F ", "C ", "G ", "D ", "A ", "E ",
"G#m", "D#m", "A#m", "Fm ", "Cm ", "Gm ", "Dm ", "Am ", "Em ", "Bm ", "F#m", "C#m"
],
"traktor_open": [
"6d ", "7d ", "8d ", "9d ", "10d", "11d", "12d", "1d ", "2d ", "3d ", "4d ", "5d ",
"6m ", "7m ", "8m ", "9m ", "10m", "11m", "12m", "1m ", "2m ", "3m ", "4m ", "5m "
]
};
var map = {};
var html = [];
for (var k in maps) {
if (!maps.hasOwnProperty(k))
continue;
html.push(
'<span><input type="radio" name="keytype" value="' + k + '" id="key_' + k + '">' +
'<label for="key_' + k + '">' + k + '</label></span>');
for (var a = 0; a < 24; a++)
maps[k][a] = maps[k][a].trim();
}
ebi('key_notation').innerHTML = html.join('\n');
function set_key_notation(e) {
ev(e);
var notation = this.getAttribute('value');
load_notation(notation);
render();
}
function load_notation(notation) {
swrite("key_notation", notation);
map = {};
var dst = maps[notation];
for (var k in maps)
if (k != notation && maps.hasOwnProperty(k))
for (var a = 0; a < 24; a++)
if (maps[k][a] != dst[a])
map[maps[k][a]] = dst[a];
}
function render() {
var tds = ebi('files').tHead.getElementsByTagName('th');
var i = -1;
var min = false;
for (var a = 0; a < tds.length; a++) {
var spans = tds[a].getElementsByTagName('span');
if (spans.length && spans[0].textContent == 'Key') {
min = tds[a].getAttribute('class').indexOf('min') !== -1;
i = a;
break;
}
}
if (i == -1)
return;
var rows = ebi('files').tBodies[0].rows;
if (min)
for (var a = 0, aa = rows.length; a < aa; a++) {
var c = rows[a].cells[i];
if (!c)
continue;
var v = c.getAttribute('html');
c.setAttribute('html', map[v] || v);
}
else
for (var a = 0, aa = rows.length; a < aa; a++) {
var c = rows[a].cells[i];
if (!c)
continue;
var v = c.textContent;
c.textContent = map[v] || v;
}
}
function try_render() {
try {
render();
}
catch (ex) {
console.log("key notation failed: " + ex);
}
}
var notation = sread("key_notation") || "rekobo_alnum";
ebi('key_' + notation).checked = true;
load_notation(notation);
var o = document.querySelectorAll('#key_notation input');
for (var a = 0; a < o.length; a++) {
o[a].onchange = set_key_notation;
}
return {
"render": try_render
};
})();
(function () {
function set_tooltip(e) {
ev(e);
ebi('opdesc').innerHTML = this.getAttribute('data-desc');
}
var btns = document.querySelectorAll('#ops, #ops>a');
for (var a = 0; a < btns.length; a++) {
btns[a].onmouseenter = set_tooltip;
}
})();
function ev_row_tgl(e) { function ev_row_tgl(e) {
ev(e); ev(e);
filecols.toggle(this.parentElement.parentElement.getElementsByTagName('span')[0].textContent); filecols.toggle(this.parentElement.parentElement.getElementsByTagName('span')[0].textContent);
@@ -1178,7 +1353,7 @@ function reload_browser(not_mp) {
filecols.set_style(); filecols.set_style();
makeSortable(ebi('files')); makeSortable(ebi('files'));
var parts = get_vpath().split('/'); var parts = get_evpath().split('/');
var rm = document.querySelectorAll('#path>a+a+a'); var rm = document.querySelectorAll('#path>a+a+a');
for (a = rm.length - 1; a >= 0; a--) for (a = rm.length - 1; a >= 0; a--)
rm[a].parentNode.removeChild(rm[a]); rm[a].parentNode.removeChild(rm[a]);
@@ -1213,3 +1388,4 @@ function reload_browser(not_mp) {
up2k.set_fsearch(); up2k.set_fsearch();
} }
reload_browser(true); reload_browser(true);
mukey.render();

View File

@@ -65,7 +65,7 @@ function statify(obj) {
if (a > 0) if (a > 0)
loc.push(n[a]); loc.push(n[a]);
var dec = hesc(decodeURIComponent(n[a])); var dec = hesc(uricom_dec(n[a])[0]);
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>'); nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
} }

View File

@@ -15,7 +15,7 @@ var dom_md = ebi('mt');
if (a > 0) if (a > 0)
loc.push(n[a]); loc.push(n[a]);
var dec = decodeURIComponent(n[a]).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); var dec = uricom_dec(n[a])[0].replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>'); nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
} }

View File

@@ -46,9 +46,9 @@ function up2k_flagbus() {
var dbg = function (who, msg) { var dbg = function (who, msg) {
console.log('flagbus(' + flag.id + '): [' + who + '] ' + msg); console.log('flagbus(' + flag.id + '): [' + who + '] ' + msg);
}; };
flag.ch.onmessage = function (ev) { flag.ch.onmessage = function (e) {
var who = ev.data[0], var who = e.data[0],
what = ev.data[1]; what = e.data[1];
if (who == flag.id) { if (who == flag.id) {
dbg(who, 'hi me (??)'); dbg(who, 'hi me (??)');
@@ -83,7 +83,7 @@ function up2k_flagbus() {
flag.ch.postMessage([flag.id, "hey"]); flag.ch.postMessage([flag.id, "hey"]);
} }
else { else {
dbg('?', ev.data); dbg('?', e.data);
} }
}; };
var tx = function (now, msg) { var tx = function (now, msg) {
@@ -194,7 +194,7 @@ function up2k_init(have_crypto) {
// handle user intent to use the basic uploader instead // handle user intent to use the basic uploader instead
ebi('u2nope').onclick = function (e) { ebi('u2nope').onclick = function (e) {
e.preventDefault(); ev(e);
setmsg(); setmsg();
goto('bup'); goto('bup');
}; };
@@ -254,29 +254,29 @@ function up2k_init(have_crypto) {
} }
ebi('u2btn').addEventListener('click', nav, false); ebi('u2btn').addEventListener('click', nav, false);
function ondrag(ev) { function ondrag(e) {
ev.stopPropagation(); e.stopPropagation();
ev.preventDefault(); e.preventDefault();
ev.dataTransfer.dropEffect = 'copy'; e.dataTransfer.dropEffect = 'copy';
ev.dataTransfer.effectAllowed = 'copy'; e.dataTransfer.effectAllowed = 'copy';
} }
ebi('u2btn').addEventListener('dragover', ondrag, false); ebi('u2btn').addEventListener('dragover', ondrag, false);
ebi('u2btn').addEventListener('dragenter', ondrag, false); ebi('u2btn').addEventListener('dragenter', ondrag, false);
function gotfile(ev) { function gotfile(e) {
ev.stopPropagation(); e.stopPropagation();
ev.preventDefault(); e.preventDefault();
var files; var files;
var is_itemlist = false; var is_itemlist = false;
if (ev.dataTransfer) { if (e.dataTransfer) {
if (ev.dataTransfer.items) { if (e.dataTransfer.items) {
files = ev.dataTransfer.items; // DataTransferItemList files = e.dataTransfer.items; // DataTransferItemList
is_itemlist = true; is_itemlist = true;
} }
else files = ev.dataTransfer.files; // FileList else files = e.dataTransfer.files; // FileList
} }
else files = ev.target.files; else files = e.target.files;
if (files.length == 0) if (files.length == 0)
return alert('no files selected??'); return alert('no files selected??');
@@ -332,7 +332,7 @@ function up2k_init(have_crypto) {
"name": fobj.name, "name": fobj.name,
"size": fobj.size, "size": fobj.size,
"lmod": lmod / 1000, "lmod": lmod / 1000,
"purl": get_vpath(), "purl": get_evpath(),
"done": false, "done": false,
"hash": [] "hash": []
}; };
@@ -655,8 +655,8 @@ function up2k_init(have_crypto) {
prog(t.n, nchunk, col_hashing); prog(t.n, nchunk, col_hashing);
}; };
var segm_load = function (ev) { var segm_load = function (e) {
cache_buf = ev.target.result; cache_buf = e.target.result;
cache_ofs = 0; cache_ofs = 0;
hash_calc(); hash_calc();
}; };
@@ -730,7 +730,7 @@ function up2k_init(have_crypto) {
st.busy.handshake.push(t); st.busy.handshake.push(t);
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onload = function (ev) { xhr.onload = function (e) {
if (xhr.status == 200) { if (xhr.status == 200) {
var response = JSON.parse(xhr.responseText); var response = JSON.parse(xhr.responseText);
@@ -881,7 +881,7 @@ function up2k_init(have_crypto) {
alert('y o u b r o k e i t\n\n(was that a folder? just files please)'); alert('y o u b r o k e i t\n\n(was that a folder? just files please)');
}; };
reader.onload = function (ev) { reader.onload = function (e) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (xev) { xhr.upload.onprogress = function (xev) {
var perc = xev.loaded / (cdr - car) * 100; var perc = xev.loaded / (cdr - car) * 100;
@@ -915,7 +915,7 @@ function up2k_init(have_crypto) {
xhr.setRequestHeader('Content-Type', 'application/octet-stream'); xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.overrideMimeType('Content-Type', 'application/octet-stream'); xhr.overrideMimeType('Content-Type', 'application/octet-stream');
xhr.responseType = 'text'; xhr.responseType = 'text';
xhr.send(ev.target.result); xhr.send(e.target.result);
}; };
reader.readAsArrayBuffer(bobslice.call(t.fobj, car, cdr)); reader.readAsArrayBuffer(bobslice.call(t.fobj, car, cdr));
@@ -944,7 +944,7 @@ function up2k_init(have_crypto) {
/// config ui /// config ui
// //
function onresize(ev) { function onresize(e) {
var bar = ebi('ops'), var bar = ebi('ops'),
wpx = innerWidth, wpx = innerWidth,
fpx = parseInt(getComputedStyle(bar)['font-size']), fpx = parseInt(getComputedStyle(bar)['font-size']),
@@ -959,17 +959,17 @@ function up2k_init(have_crypto) {
ebi('u2conf').setAttribute('class', wide ? 'has_btn' : ''); ebi('u2conf').setAttribute('class', wide ? 'has_btn' : '');
} }
} }
window.onresize = onresize; window.addEventListener('resize', onresize);
onresize(); onresize();
function desc_show(ev) { function desc_show(e) {
var msg = this.getAttribute('alt'); var msg = this.getAttribute('alt');
msg = msg.replace(/\$N/g, "<br />"); msg = msg.replace(/\$N/g, "<br />");
var cdesc = ebi('u2cdesc'); var cdesc = ebi('u2cdesc');
cdesc.innerHTML = msg; cdesc.innerHTML = msg;
cdesc.setAttribute('class', 'show'); cdesc.setAttribute('class', 'show');
} }
function desc_hide(ev) { function desc_hide(e) {
ebi('u2cdesc').setAttribute('class', ''); ebi('u2cdesc').setAttribute('class', '');
} }
var o = document.querySelectorAll('#u2conf *[alt]'); var o = document.querySelectorAll('#u2conf *[alt]');
@@ -1084,17 +1084,17 @@ function up2k_init(have_crypto) {
} }
} }
function nop(ev) { function nop(e) {
ev.preventDefault(); ev(e);
this.click(); this.click();
} }
ebi('nthread_add').onclick = function (ev) { ebi('nthread_add').onclick = function (e) {
ev.preventDefault(); ev(e);
bumpthread(1); bumpthread(1);
}; };
ebi('nthread_sub').onclick = function (ev) { ebi('nthread_sub').onclick = function (e) {
ev.preventDefault(); ev(e);
bumpthread(-1); bumpthread(-1);
}; };

View File

@@ -62,7 +62,7 @@
width: calc(100% - 2em); width: calc(100% - 2em);
max-width: 100em; max-width: 100em;
} }
#u2form.srch #u2tab { #op_up2k.srch #u2tab {
max-width: none; max-width: none;
} }
#u2tab td { #u2tab td {
@@ -76,7 +76,7 @@
#u2tab td:nth-child(3) { #u2tab td:nth-child(3) {
width: 40%; width: 40%;
} }
#u2form.srch #u2tab td:nth-child(3) { #op_up2k.srch #u2tab td:nth-child(3) {
font-family: sans-serif; font-family: sans-serif;
width: auto; width: auto;
} }

View File

@@ -23,6 +23,7 @@ function esc(txt) {
} }
function vis_exh(msg, url, lineNo, columnNo, error) { function vis_exh(msg, url, lineNo, columnNo, error) {
window.onerror = undefined; 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 screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>']; esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
@@ -156,7 +157,7 @@ function opclick(e) {
var dest = this.getAttribute('data-dest'); var dest = this.getAttribute('data-dest');
goto(dest); goto(dest);
swrite('opmode', dest || undefined); swrite('opmode', dest || null);
var input = document.querySelector('.opview.act input:not([type="hidden"])') var input = document.querySelector('.opview.act input:not([type="hidden"])')
if (input) if (input)
@@ -182,6 +183,9 @@ function goto(dest) {
if (fn) if (fn)
fn(); fn();
} }
if (window['treectl'])
treectl.onscroll();
} }
@@ -220,6 +224,31 @@ function linksplit(rp) {
} }
function uricom_enc(txt, do_fb_enc) {
try {
return encodeURIComponent(txt);
}
catch (ex) {
console.log("uce-err [" + txt + "]");
if (do_fb_enc)
return esc(txt);
return txt;
}
}
function uricom_dec(txt) {
try {
return [decodeURIComponent(txt), true];
}
catch (ex) {
console.log("ucd-err [" + txt + "]");
return [txt, false];
}
}
function get_evpath() { function get_evpath() {
var ret = document.location.pathname; var ret = document.location.pathname;
@@ -234,7 +263,7 @@ function get_evpath() {
function get_vpath() { function get_vpath() {
return decodeURIComponent(get_evpath()); return uricom_dec(get_evpath())[0];
} }
@@ -262,12 +291,12 @@ function sread(key) {
if (window.localStorage) if (window.localStorage)
return localStorage.getItem(key); return localStorage.getItem(key);
return ''; return null;
} }
function swrite(key, val) { function swrite(key, val) {
if (window.localStorage) { if (window.localStorage) {
if (val === undefined) if (val === undefined || val === null)
localStorage.removeItem(key); localStorage.removeItem(key);
else else
localStorage.setItem(key, val); localStorage.setItem(key, val);
@@ -293,7 +322,7 @@ function icfg_get(name, defval) {
var o = ebi(name); var o = ebi(name);
var val = parseInt(sread(name)); var val = parseInt(sread(name));
if (val === null) if (isNaN(val))
return parseInt(o ? o.value : defval); return parseInt(o ? o.value : defval);
if (o) if (o)
@@ -335,14 +364,12 @@ function bcfg_upd_ui(name, val) {
} }
function hist_push(html, url) { function hist_push(url) {
var key = new Date().getTime(); console.log("h-push " + url);
sessionStorage.setItem(key, html); history.pushState(url, url, url);
history.pushState(key, url, url);
} }
function hist_replace(html, url) { function hist_replace(url) {
var key = new Date().getTime(); console.log("h-repl " + url);
sessionStorage.setItem(key, html); history.replaceState(url, url, url);
history.replaceState(key, url, url);
} }

View File

@@ -11,6 +11,13 @@ gzip -d < .hist/up2k.snap | jq -r '.[].tnam' | while IFS= read -r f; do rm -f --
gzip -d < .hist/up2k.snap | jq -r '.[].name' | while IFS= read -r f; do wc -c -- "$f" | grep -qiE '^[^0-9a-z]*0' && rm -f -- "$f"; done gzip -d < .hist/up2k.snap | jq -r '.[].name' | while IFS= read -r f; do wc -c -- "$f" | grep -qiE '^[^0-9a-z]*0' && rm -f -- "$f"; done
##
## detect partial uploads based on file contents
## (in case of context loss or old copyparties)
echo; find -type f | while IFS= read -r x; do printf '\033[A\033[36m%s\033[K\033[0m\n' "$x"; tail -c$((1024*1024)) <"$x" | xxd -a | awk 'NR==1&&/^[0: ]+.{16}$/{next} NR==2&&/^\*$/{next} NR==3&&/^[0f]+: [0 ]+65 +.{16}$/{next} {e=1} END {exit e}' || continue; printf '\033[A\033[31msus:\033[33m %s \033[0m\n\n' "$x"; done
## ##
## create a test payload ## create a test payload

View File

@@ -90,7 +90,7 @@ class TestVFS(unittest.TestCase):
finally: finally:
return ret return ret
def log(self, src, msg): def log(self, src, msg, c=0):
pass pass
def test(self): def test(self):