mirror of
				https://github.com/9001/copyparty.git
				synced 2025-10-31 20:13:34 +00:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e3f1d19756 | ||
|  | 93c2bd6ef6 | ||
|  | 4d0e5ff6db | ||
|  | 0893f06919 | ||
|  | 46b6abde3f | ||
|  | 0696610dee | ||
|  | edf0d3684c | ||
|  | 7af159f5f6 | ||
|  | 7f2cb6764a | ||
|  | 96495a9bf1 | ||
|  | b2fafec5fc | ||
|  | 0850b8ae2b | ||
|  | 8a68a96c57 | ||
|  | d3aae8ed6a | ||
|  | c62ebadda8 | ||
|  | ffcee6d390 | ||
|  | de32838346 | ||
|  | b9a4e47ea2 | ||
|  | 57d994422d | ||
|  | 6ecd745323 | ||
|  | bd769f5bdb | ||
|  | 2381692aba | ||
|  | 24fdada0a0 | ||
|  | bb5169710a | ||
|  | 9cde2352f3 | 
| @@ -69,7 +69,9 @@ summary: it works! you can use it! (but technically not even close to beta) | ||||
|  | ||||
| # 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 | ||||
| @@ -210,6 +212,7 @@ pip install black bandit pylint flake8  # vscode tooling | ||||
| in the `scripts` folder: | ||||
|  | ||||
| * run `make -C deps-docker` to build all dependencies | ||||
| * `git tag v1.2.3 && git push origin --tags` | ||||
| * create github release with `make-tgz-release.sh` | ||||
| * upload to pypi with `make-pypi-release.(sh|bat)` | ||||
| * create sfx with `make-sfx.sh` | ||||
|   | ||||
| @@ -1008,6 +1008,12 @@ def main(): | ||||
|         log = null_log | ||||
|         dbg = null_log | ||||
|  | ||||
|     if ar.a and ar.a.startswith("$"): | ||||
|         fn = ar.a[1:] | ||||
|         log("reading password from file [{}]".format(fn)) | ||||
|         with open(fn, "rb") as f: | ||||
|             ar.a = f.read().decode("utf-8").strip() | ||||
|  | ||||
|     if WINDOWS: | ||||
|         os.system("rem") | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| Description=copyparty file server | ||||
|  | ||||
| [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' | ||||
|  | ||||
| [Install] | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import locale | ||||
| import argparse | ||||
| 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 .svchub import SvcHub | ||||
| 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()) | ||||
|  | ||||
|  | ||||
| def warn(msg): | ||||
|     print("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg)) | ||||
|  | ||||
|  | ||||
| def ensure_locale(): | ||||
|     for x in [ | ||||
|         "en_US.UTF-8", | ||||
| @@ -300,7 +304,13 @@ def main(): | ||||
|         if al.ciphers: | ||||
|             configure_ssl_ciphers(al) | ||||
|     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() | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| # coding: utf-8 | ||||
|  | ||||
| VERSION = (0, 9, 3) | ||||
| VERSION = (0, 9, 8) | ||||
| CODENAME = "the strongest music server" | ||||
| BUILD_DT = (2021, 3, 4) | ||||
| BUILD_DT = (2021, 3, 15) | ||||
|  | ||||
| S_VERSION = ".".join(map(str, VERSION)) | ||||
| S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import re | ||||
| import threading | ||||
|  | ||||
| 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): | ||||
| @@ -106,7 +106,7 @@ class VFS(object): | ||||
|         """return user-readable [fsdir,real,virt] items at vpath""" | ||||
|         virt_vis = {}  # nodes readable by user | ||||
|         abspath = self.canonical(rem) | ||||
|         real = list(statdir(print, scandir, lstat, abspath)) | ||||
|         real = list(statdir(nuprint, scandir, lstat, abspath)) | ||||
|         real.sort() | ||||
|         if not rem: | ||||
|             for name, vn2 in sorted(self.nodes.items()): | ||||
| @@ -147,8 +147,8 @@ class AuthSrv(object): | ||||
|         self.mutex = threading.Lock() | ||||
|         self.reload() | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.log_func("auth", msg) | ||||
|     def log(self, msg, c=0): | ||||
|         self.log_func("auth", msg, c) | ||||
|  | ||||
|     def invert(self, orig): | ||||
|         if PY2: | ||||
| @@ -304,9 +304,9 @@ class AuthSrv(object): | ||||
|  | ||||
|         if missing_users: | ||||
|             self.log( | ||||
|                 "\033[31myou must -a the following users: " | ||||
|                 + ", ".join(k for k in sorted(missing_users)) | ||||
|                 + "\033[0m" | ||||
|                 "you must -a the following users: " | ||||
|                 + ", ".join(k for k in sorted(missing_users)), | ||||
|                 c=1, | ||||
|             ) | ||||
|             raise Exception("invalid config") | ||||
|  | ||||
| @@ -329,8 +329,8 @@ class AuthSrv(object): | ||||
|             v, _ = vfs.get("/", "*", False, True) | ||||
|             if self.warn_anonwrite and os.getcwd() == v.realpath: | ||||
|                 self.warn_anonwrite = False | ||||
|                 msg = "\033[31manyone can read/write the current directory: {}\033[0m" | ||||
|                 self.log(msg.format(v.realpath)) | ||||
|                 msg = "anyone can read/write the current directory: {}" | ||||
|                 self.log(msg.format(v.realpath), c=1) | ||||
|         except Pebkac: | ||||
|             self.warn_anonwrite = True | ||||
|  | ||||
|   | ||||
| @@ -49,11 +49,11 @@ class MpWorker(object): | ||||
|         # print('k') | ||||
|         pass | ||||
|  | ||||
|     def log(self, src, msg): | ||||
|         self.q_yield.put([0, "log", [src, msg]]) | ||||
|     def log(self, src, msg, c=0): | ||||
|         self.q_yield.put([0, "log", [src, msg, c]]) | ||||
|  | ||||
|     def logw(self, msg): | ||||
|         self.log("mp{}".format(self.n), msg) | ||||
|     def logw(self, msg, c=0): | ||||
|         self.log("mp{}".format(self.n), msg, c) | ||||
|  | ||||
|     def httpdrop(self, addr): | ||||
|         self.q_yield.put([0, "httpdrop", [addr]]) | ||||
| @@ -73,7 +73,7 @@ class MpWorker(object): | ||||
|                 if PY2: | ||||
|                     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) | ||||
|  | ||||
|                 with self.mutex: | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class BrokerThr(object): | ||||
|     def put(self, want_retval, dest, *args): | ||||
|         if dest == "httpconn": | ||||
|             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) | ||||
|  | ||||
|         else: | ||||
|   | ||||
| @@ -41,8 +41,8 @@ class HttpCli(object): | ||||
|         self.absolute_urls = False | ||||
|         self.out_headers = {"Access-Control-Allow-Origin": "*"} | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.log_func(self.log_src, msg) | ||||
|     def log(self, msg, c=0): | ||||
|         self.log_func(self.log_src, msg, c) | ||||
|  | ||||
|     def _check_nonfatal(self, ex): | ||||
|         return ex.code < 400 or ex.code == 404 | ||||
| @@ -63,7 +63,7 @@ class HttpCli(object): | ||||
|  | ||||
|             if not headerlines[0]: | ||||
|                 # 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) | ||||
|  | ||||
|             try: | ||||
| @@ -74,7 +74,7 @@ class HttpCli(object): | ||||
|         except Pebkac as ex: | ||||
|             # self.log("pebkac at httpcli.run #1: " + repr(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 | ||||
|  | ||||
|         # time.sleep(0.4) | ||||
| @@ -163,7 +163,7 @@ class HttpCli(object): | ||||
|         response = ["HTTP/1.1 {} {}".format(status, HTTPCODE[status])] | ||||
|  | ||||
|         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 | ||||
|         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: | ||||
|                 self.log( | ||||
|                     "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 | ||||
| @@ -697,7 +697,7 @@ class HttpCli(object): | ||||
|                     raise | ||||
|  | ||||
|         except Pebkac as ex: | ||||
|             errmsg = str(ex) | ||||
|             errmsg = unicode(ex) | ||||
|  | ||||
|         td = max(0.1, time.time() - t0) | ||||
|         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", | ||||
|         ) | ||||
|  | ||||
|         logmsg += str(status) + logtail | ||||
|         logmsg += unicode(status) + logtail | ||||
|  | ||||
|         if self.mode == "HEAD" or not do_send: | ||||
|             self.log(logmsg) | ||||
| @@ -1020,7 +1020,7 @@ class HttpCli(object): | ||||
|                 remains = sendfile_py(lower, upper, f, self.s) | ||||
|  | ||||
|         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) | ||||
|         self.log("{},  {}".format(logmsg, spd)) | ||||
| @@ -1067,7 +1067,7 @@ class HttpCli(object): | ||||
|         sz_html = len(template.render(**targs).encode("utf-8")) | ||||
|         self.send_headers(sz_html + sz_md, status) | ||||
|  | ||||
|         logmsg += str(status) | ||||
|         logmsg += unicode(status) | ||||
|         if self.mode == "HEAD" or not do_send: | ||||
|             self.log(logmsg) | ||||
|             return True | ||||
| @@ -1081,7 +1081,7 @@ class HttpCli(object): | ||||
|             self.log(logmsg + " \033[31md/c\033[0m") | ||||
|             return False | ||||
|  | ||||
|         self.log(logmsg + " " + str(len(html))) | ||||
|         self.log(logmsg + " " + unicode(len(html))) | ||||
|         return True | ||||
|  | ||||
|     def tx_mounts(self): | ||||
| @@ -1115,7 +1115,8 @@ class HttpCli(object): | ||||
|         excl = None | ||||
|         if target: | ||||
|             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: | ||||
|             vn, rem = self.auth.vfs.get(top, self.uname, True, False) | ||||
| @@ -1136,7 +1137,7 @@ class HttpCli(object): | ||||
|             vfs_ls = exclude_dotfiles(vfs_ls) | ||||
|  | ||||
|         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(): | ||||
|             if x != excl: | ||||
| @@ -1275,19 +1276,24 @@ class HttpCli(object): | ||||
|             del f["rd"] | ||||
|             if icur: | ||||
|                 q = "select w from up where rd = ? and fn = ?" | ||||
|                 try: | ||||
|                     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: | ||||
|                     continue | ||||
|  | ||||
|                 w = r[0][:16] | ||||
|                 tags = {} | ||||
|                 q = "select k, v from mt where w = ? and k != 'x'" | ||||
|                 for k, v in icur.execute(q, (w,)): | ||||
|                     taglist[k] = True | ||||
|                     tags[k] = v | ||||
|  | ||||
|                 f["tags"] = tags | ||||
|  | ||||
|         if icur: | ||||
|             taglist = [k for k in self.args.mte.split(",") if k in taglist] | ||||
|             for f in dirs: | ||||
| @@ -1297,7 +1303,7 @@ class HttpCli(object): | ||||
|  | ||||
|         try: | ||||
|             if not self.args.nih: | ||||
|                 srv_info.append(str(socket.gethostname()).split(".")[0]) | ||||
|                 srv_info.append(unicode(socket.gethostname()).split(".")[0]) | ||||
|         except: | ||||
|             self.log("#wow #whoa") | ||||
|             pass | ||||
|   | ||||
| @@ -81,8 +81,8 @@ class HttpConn(object): | ||||
|     def respath(self, res_name): | ||||
|         return os.path.join(E.mod, "web", res_name) | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.log_func(self.log_src, msg) | ||||
|     def log(self, msg, c=0): | ||||
|         self.log_func(self.log_src, msg, c) | ||||
|  | ||||
|     def get_u2idx(self): | ||||
|         if not self.u2idx: | ||||
| @@ -129,7 +129,7 @@ class HttpConn(object): | ||||
|  | ||||
|         if is_https: | ||||
|             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 | ||||
|  | ||||
|             self.log_src = self.log_src.replace("[36m", "[35m") | ||||
| @@ -180,7 +180,7 @@ class HttpConn(object): | ||||
|                     pass | ||||
|  | ||||
|                 else: | ||||
|                     self.log("\033[35mhandshake\033[0m " + em) | ||||
|                     self.log("handshake\033[0m " + em, c=5) | ||||
|  | ||||
|                 return | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class HttpSrv(object): | ||||
|  | ||||
|     def accept(self, sck, addr): | ||||
|         """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.daemon = True | ||||
|         thr.start() | ||||
| @@ -66,11 +66,11 @@ class HttpSrv(object): | ||||
|                 thr.start() | ||||
|  | ||||
|         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() | ||||
|  | ||||
|         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: | ||||
|                 sck.shutdown(socket.SHUT_RDWR) | ||||
|                 sck.close() | ||||
| @@ -78,7 +78,8 @@ class HttpSrv(object): | ||||
|                 if not MACOS: | ||||
|                     self.log( | ||||
|                         "%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]: | ||||
|                     # 10038 No longer considered a socket | ||||
|   | ||||
| @@ -10,6 +10,9 @@ import subprocess as sp | ||||
| from .__init__ import PY2, WINDOWS | ||||
| from .util import fsenc, fsdec | ||||
|  | ||||
| if not PY2: | ||||
|     unicode = str | ||||
|  | ||||
|  | ||||
| class MTag(object): | ||||
|     def __init__(self, log_func, args): | ||||
| @@ -18,13 +21,14 @@ class MTag(object): | ||||
|         self.prefer_mt = False | ||||
|         mappings = args.mtm | ||||
|         self.backend = "ffprobe" if args.no_mutagen else "mutagen" | ||||
|         or_ffprobe = " or ffprobe" | ||||
|  | ||||
|         if self.backend == "mutagen": | ||||
|             self.get = self.get_mutagen | ||||
|             try: | ||||
|                 import mutagen | ||||
|             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" | ||||
|  | ||||
|         if self.backend == "ffprobe": | ||||
| @@ -32,7 +36,7 @@ class MTag(object): | ||||
|             self.prefer_mt = True | ||||
|             # about 20x slower | ||||
|             if PY2: | ||||
|                 cmd = ["ffprobe", "-version"] | ||||
|                 cmd = [b"ffprobe", b"-version"] | ||||
|                 try: | ||||
|                     sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) | ||||
|                 except: | ||||
| @@ -41,9 +45,15 @@ class MTag(object): | ||||
|                 if not shutil.which("ffprobe"): | ||||
|                     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: | ||||
|             msg = "\033[31mneed mutagen or ffprobe to read media tags so please run this:\n  {} -m pip install --user mutagen \033[0m" | ||||
|             self.log(msg.format(os.path.basename(sys.executable))) | ||||
|             msg = "need mutagen{} to read media tags so please run this:\n  {} -m pip install --user mutagen" | ||||
|             self.log(msg.format(or_ffprobe, os.path.basename(sys.executable)), c=1) | ||||
|             return | ||||
|  | ||||
|         # https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html | ||||
| @@ -115,8 +125,8 @@ class MTag(object): | ||||
|         } | ||||
|         # self.get = self.compare | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.log_func("mtag", msg) | ||||
|     def log(self, msg, c=0): | ||||
|         self.log_func("mtag", msg, c) | ||||
|  | ||||
|     def normalize_tags(self, ret, md): | ||||
|         for k, v in dict(md).items(): | ||||
| @@ -133,7 +143,7 @@ class MTag(object): | ||||
|                 ret[mk] = [pref, v[0]] | ||||
|  | ||||
|         # 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 | ||||
|         for k, v in ret.items(): | ||||
| @@ -206,7 +216,7 @@ class MTag(object): | ||||
|         return self.normalize_tags(ret, md) | ||||
|  | ||||
|     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) | ||||
|         r = p.communicate() | ||||
|         txt = r[1].decode("utf-8", "replace") | ||||
| @@ -285,9 +295,7 @@ class MTag(object): | ||||
|                             sec *= 60 | ||||
|                             sec += int(f) | ||||
|                     except: | ||||
|                         self.log( | ||||
|                             "\033[33minvalid timestr from ffmpeg: [{}]".format(tstr) | ||||
|                         ) | ||||
|                         self.log("invalid timestr from ffmpeg: [{}]".format(tstr), c=3) | ||||
|  | ||||
|                 ret[".dur"] = sec | ||||
|                 m = ptn_br1.search(ln) | ||||
|   | ||||
| @@ -9,7 +9,6 @@ from datetime import datetime, timedelta | ||||
| import calendar | ||||
|  | ||||
| from .__init__ import PY2, WINDOWS, MACOS, VT100 | ||||
| from .authsrv import AuthSrv | ||||
| from .tcpsrv import TcpSrv | ||||
| from .up2k import Up2k | ||||
| from .util import mp | ||||
| @@ -66,10 +65,10 @@ class SvcHub(object): | ||||
|             self.broker.shutdown() | ||||
|             print("nailed it") | ||||
|  | ||||
|     def _log_disabled(self, src, msg): | ||||
|     def _log_disabled(self, src, msg, c=0): | ||||
|         pass | ||||
|  | ||||
|     def _log_enabled(self, src, msg): | ||||
|     def _log_enabled(self, src, msg, c=0): | ||||
|         """handles logging from all components""" | ||||
|         with self.log_mutex: | ||||
|             now = time.time() | ||||
| @@ -92,6 +91,13 @@ class SvcHub(object): | ||||
|                     msg = self.ansi_re.sub("", msg) | ||||
|                 if "\033" in 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] | ||||
|             msg = fmt.format(ts, src, msg) | ||||
|   | ||||
| @@ -68,21 +68,22 @@ class TcpSrv(object): | ||||
|             self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port)) | ||||
|  | ||||
|         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: | ||||
|                 time.sleep(0.1) | ||||
|                 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, [], []) | ||||
|             for srv in ready: | ||||
|                 sck, addr = srv.accept() | ||||
|                 sip, sport = srv.getsockname() | ||||
|                 self.log( | ||||
|                     "%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 | ||||
|                     ), | ||||
|                     c="1;30", | ||||
|                 ) | ||||
|                 self.num_clients.add() | ||||
|                 self.hub.broker.put(False, "httpconn", sck, addr) | ||||
|   | ||||
| @@ -25,9 +25,11 @@ class U2idx(object): | ||||
|             return | ||||
|  | ||||
|         self.cur = {} | ||||
|         self.mem_cur = sqlite3.connect(":memory:") | ||||
|         self.mem_cur.execute(r"create table a (b text)") | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.log_func("u2idx", msg) | ||||
|     def log(self, msg, c=0): | ||||
|         self.log_func("u2idx", msg, c) | ||||
|  | ||||
|     def fsearch(self, vols, body): | ||||
|         """search by up2k hashlist""" | ||||
| @@ -37,7 +39,11 @@ class U2idx(object): | ||||
|         fsize = body["size"] | ||||
|         fhash = body["hash"] | ||||
|         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): | ||||
|         cur = self.cur.get(ptop) | ||||
| @@ -108,6 +114,9 @@ class U2idx(object): | ||||
|                 if lim <= 0: | ||||
|                     break | ||||
|  | ||||
|                 if rd.startswith("//") or fn.startswith("//"): | ||||
|                     rd, fn = s3dec(rd, fn) | ||||
|  | ||||
|                 rp = os.path.join(vtop, rd, fn).replace("\\", "/") | ||||
|                 sret.append({"ts": int(ts), "sz": sz, "rp": rp, "w": w[:16]}) | ||||
|  | ||||
|   | ||||
| @@ -25,8 +25,8 @@ from .util import ( | ||||
|     sanitize_fn, | ||||
|     ren_open, | ||||
|     atomic_move, | ||||
|     w8b64enc, | ||||
|     w8b64dec, | ||||
|     s3enc, | ||||
|     s3dec, | ||||
|     statdir, | ||||
| ) | ||||
| from .mtag import MTag | ||||
| @@ -64,14 +64,18 @@ class Up2k(object): | ||||
|         self.flags = {} | ||||
|         self.cur = {} | ||||
|         self.mtag = None | ||||
|         self.n_mtag_thr_alive = 0 | ||||
|         self.n_mtag_tags_added = 0 | ||||
|         self.n_mtag_tags_added = -1 | ||||
|  | ||||
|         self.mem_cur = None | ||||
|         self.sqlite_ver = None | ||||
|         self.no_expr_idx = False | ||||
|         if HAVE_SQLITE3: | ||||
|             # mojibake detector | ||||
|             self.mem_cur = self._orz(":memory:") | ||||
|             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: | ||||
|             # 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") | ||||
|  | ||||
|         # 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) | ||||
|  | ||||
|         if have_e2d: | ||||
| @@ -103,31 +107,8 @@ class Up2k(object): | ||||
|             thr.daemon = True | ||||
|             thr.start() | ||||
|  | ||||
|     def log(self, msg): | ||||
|         self.log_func("up2k", msg + "\033[K") | ||||
|  | ||||
|     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 log(self, msg, c=0): | ||||
|         self.log_func("up2k", msg + "\033[K", c) | ||||
|  | ||||
|     def _vis_job_progress(self, job): | ||||
|         perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"])) | ||||
| @@ -141,24 +122,46 @@ class Up2k(object): | ||||
|  | ||||
|         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): | ||||
|         self.pp = ProgressPrinter() | ||||
|         vols = auth.vfs.all_vols.values() | ||||
|         t0 = time.time() | ||||
|         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 = [] | ||||
|         for vol in vols: | ||||
|             try: | ||||
|                 os.listdir(vol.realpath) | ||||
|                 live_vols.append(vol) | ||||
|             except: | ||||
|                 self.log("\033[31mcannot access " + vol.realpath) | ||||
|                 self.log("cannot access " + vol.realpath, c=1) | ||||
|  | ||||
|         vols = live_vols | ||||
|  | ||||
|         need_mtag = False | ||||
|         for vol in auth.vfs.all_vols.values(): | ||||
|         for vol in vols: | ||||
|             if "e2t" in vol.flags: | ||||
|                 need_mtag = True | ||||
|  | ||||
| @@ -204,8 +207,8 @@ class Up2k(object): | ||||
|         self.log(msg.format(len(vols), time.time() - t0)) | ||||
|  | ||||
|         if needed_mutagen: | ||||
|             msg = "\033[31mcould not read tags because no backends are available (mutagen or ffprobe)\033[0m" | ||||
|             self.log(msg) | ||||
|             msg = "could not read tags because no backends are available (mutagen or ffprobe)" | ||||
|             self.log(msg, c=1) | ||||
|  | ||||
|         return have_e2d | ||||
|  | ||||
| @@ -214,6 +217,8 @@ class Up2k(object): | ||||
|             if ptop in self.registry: | ||||
|                 return None | ||||
|  | ||||
|             _, flags = self._expr_idx_filter(flags) | ||||
|  | ||||
|             reg = {} | ||||
|             path = os.path.join(ptop, ".hist", "up2k.snap") | ||||
|             if "e2d" in flags and os.path.exists(path): | ||||
| @@ -312,7 +317,7 @@ class Up2k(object): | ||||
|                 try: | ||||
|                     c = dbw[0].execute(sql, (rd, fn)) | ||||
|                 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()) | ||||
|                 if in_db: | ||||
| @@ -366,7 +371,7 @@ class Up2k(object): | ||||
|         for dwark, dts, dsz, drd, dfn in c: | ||||
|             nchecked += 1 | ||||
|             if drd.startswith("//") or dfn.startswith("//"): | ||||
|                 drd, dfn = self.w8dec(drd, dfn) | ||||
|                 drd, dfn = s3dec(drd, dfn) | ||||
|  | ||||
|             abspath = os.path.join(top, drd, dfn) | ||||
|             # almost zero overhead dw | ||||
| @@ -432,12 +437,11 @@ class Up2k(object): | ||||
|             if self.mtag.prefer_mt and not self.args.no_mtag_mt: | ||||
|                 # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor | ||||
|                 # both do crazy runahead so lets reinvent another wheel | ||||
|                 nw = os.cpu_count() | ||||
|                 if not self.n_mtag_thr_alive: | ||||
|                     msg = 'using {} cores for tag reader "{}"' | ||||
|                     self.log(msg.format(nw, self.mtag.backend)) | ||||
|                 nw = os.cpu_count() if hasattr(os, "cpu_count") else 4 | ||||
|                 if self.n_mtag_tags_added == -1: | ||||
|                     self.log("using {}x {}".format(nw, self.mtag.backend)) | ||||
|                     self.n_mtag_tags_added = 0 | ||||
|  | ||||
|                 self.n_mtag_thr_alive = nw | ||||
|                 mpool = Queue(nw) | ||||
|                 for _ in range(nw): | ||||
|                     thr = threading.Thread(target=self._tag_thr, args=(mpool,)) | ||||
| @@ -453,6 +457,9 @@ class Up2k(object): | ||||
|                 if c2.execute(q, (w[:16],)).fetchone(): | ||||
|                     continue | ||||
|  | ||||
|                 if rd.startswith("//") or fn.startswith("//"): | ||||
|                     rd, fn = s3dec(rd, fn) | ||||
|  | ||||
|                 abspath = os.path.join(ptop, rd, fn) | ||||
|                 self.pp.msg = "c{} {}".format(n_left, abspath) | ||||
|                 args = c3, entags, w, abspath | ||||
| @@ -474,11 +481,12 @@ class Up2k(object): | ||||
|                     last_write = time.time() | ||||
|                     n_buf = 0 | ||||
|  | ||||
|             if self.n_mtag_thr_alive: | ||||
|                 mpool.join() | ||||
|                 for _ in range(self.n_mtag_thr_alive): | ||||
|             if mpool: | ||||
|                 for _ in range(mpool.maxsize): | ||||
|                     mpool.put(None) | ||||
|  | ||||
|                 mpool.join() | ||||
|  | ||||
|             c3.close() | ||||
|             c2.close() | ||||
|  | ||||
| @@ -488,7 +496,8 @@ class Up2k(object): | ||||
|         while True: | ||||
|             task = q.get() | ||||
|             if not task: | ||||
|                 break | ||||
|                 q.task_done() | ||||
|                 return | ||||
|  | ||||
|             try: | ||||
|                 write_cur, entags, wark, abspath = task | ||||
| @@ -497,10 +506,10 @@ class Up2k(object): | ||||
|                     n = self._tag_file(write_cur, entags, wark, abspath, tags) | ||||
|                     self.n_mtag_tags_added += n | ||||
|             except: | ||||
|                 with self.mutex: | ||||
|                     self.n_mtag_thr_alive -= 1 | ||||
|                 raise | ||||
|             finally: | ||||
|                 ex = traceback.format_exc() | ||||
|                 msg = "{} failed to read tags from {}:\n{}" | ||||
|                 self.log(msg.format(self.mtag.backend, abspath, ex), c=3) | ||||
|  | ||||
|             q.task_done() | ||||
|  | ||||
|     def _tag_file(self, write_cur, entags, wark, abspath, tags=None): | ||||
| @@ -615,8 +624,12 @@ class Up2k(object): | ||||
|                 except: | ||||
|                     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 [ | ||||
|             r"create index up_w on up(substr(w,1,16))", | ||||
|             idx, | ||||
|             r"create table mt (w text, k text, v int)", | ||||
|             r"create index mt_w on mt(w)", | ||||
|             r"create index mt_k on mt(k)", | ||||
| @@ -661,12 +674,17 @@ class Up2k(object): | ||||
|             cur = self.cur.get(cj["ptop"], None) | ||||
|             reg = self.registry[cj["ptop"]] | ||||
|             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 = ?" | ||||
|                     argv = (wark[:16], wark) | ||||
|  | ||||
|                 cur = cur.execute(q, argv) | ||||
|                 for _, dtime, dsize, dp_dir, dp_fn in cur: | ||||
|                     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("\\", "/") | ||||
|                     # relying on path.exists to return false on broken symlinks | ||||
| @@ -788,8 +806,13 @@ class Up2k(object): | ||||
|                 raise OSError() | ||||
|             elif fs1 == fs2: | ||||
|                 # same fs; make symlink as relative as possible | ||||
|                 nsrc = src.replace("\\", "/").split("/") | ||||
|                 ndst = dst.replace("\\", "/").split("/") | ||||
|                 v = [] | ||||
|                 for p in [src, dst]: | ||||
|                     if WINDOWS: | ||||
|                         p = p.replace("\\", "/") | ||||
|                     v.append(p.split("/")) | ||||
|  | ||||
|                 nsrc, ndst = v | ||||
|                 nc = 0 | ||||
|                 for a, b in zip(nsrc, ndst): | ||||
|                     if a != b: | ||||
| @@ -797,7 +820,8 @@ class Up2k(object): | ||||
|                     nc += 1 | ||||
|                 if nc > 1: | ||||
|                     lsrc = nsrc[nc:] | ||||
|                     lsrc = "../" * (len(lsrc) - 1) + "/".join(lsrc) | ||||
|                     hops = len(ndst[nc:]) - 1 | ||||
|                     lsrc = "../" * hops + "/".join(lsrc) | ||||
|             os.symlink(fsenc(lsrc), fsenc(ldst)) | ||||
|         except (AttributeError, OSError) as ex: | ||||
|             self.log("cannot symlink; creating copy: " + repr(ex)) | ||||
| @@ -882,7 +906,7 @@ class Up2k(object): | ||||
|         try: | ||||
|             db.execute(sql, (rd, fn)) | ||||
|         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): | ||||
|         sql = "insert into up values (?,?,?,?,?)" | ||||
| @@ -890,7 +914,7 @@ class Up2k(object): | ||||
|         try: | ||||
|             db.execute(sql, v) | ||||
|         except: | ||||
|             rd, fn = self.w8enc(rd, fn) | ||||
|             rd, fn = s3enc(self.mem_cur, rd, fn) | ||||
|             v = (wark, ts, sz, rd, fn) | ||||
|             db.execute(sql, v) | ||||
|  | ||||
| @@ -919,7 +943,7 @@ class Up2k(object): | ||||
|         ret = [] | ||||
|         with open(path, "rb", 512 * 1024) as f: | ||||
|             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() | ||||
|                 rem = min(csz, fsz) | ||||
|                 fsz -= rem | ||||
| @@ -1034,12 +1058,12 @@ class Up2k(object): | ||||
|             with self.mutex: | ||||
|                 cur = self.cur[ptop] | ||||
|                 if not cur: | ||||
|                     self.log("\033[31mno cursor to write tags with??") | ||||
|                     self.log("no cursor to write tags with??", c=1) | ||||
|                     continue | ||||
|  | ||||
|                 entags = self.entags[ptop] | ||||
|                 if not entags: | ||||
|                     self.log("\033[33mno entags okay.jpg") | ||||
|                     self.log("no entags okay.jpg", c=3) | ||||
|                     continue | ||||
|  | ||||
|                 if "e2t" in self.flags[ptop]: | ||||
|   | ||||
| @@ -119,19 +119,26 @@ class ProgressPrinter(threading.Thread): | ||||
|                 continue | ||||
|  | ||||
|             msg = self.msg | ||||
|             m = " {}\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="") | ||||
|             uprint(" {}\033[K\r".format(msg)) | ||||
|  | ||||
|         print("\033[K", end="") | ||||
|         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 | ||||
| def ren_open(fname, *args, **kwargs): | ||||
|     fdir = kwargs.pop("fdir", None) | ||||
| @@ -597,6 +604,31 @@ else: | ||||
|     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): | ||||
|     if not PY2: | ||||
|         os.replace(src, dst) | ||||
| @@ -734,7 +766,8 @@ def statdir(logger, scandir, lstat, top): | ||||
|                     try: | ||||
|                         yield [fsdec(fh.name), fh.stat(follow_symlinks=not lstat)] | ||||
|                     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: | ||||
|             src = "listdir" | ||||
|             fun = os.lstat if lstat else os.stat | ||||
| @@ -743,9 +776,11 @@ def statdir(logger, scandir, lstat, top): | ||||
|                 try: | ||||
|                     yield [fsdec(name), fun(abspath)] | ||||
|                 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: | ||||
|         logger("{}: {} @ {}".format(src, repr(ex), top)) | ||||
|         logger("{}: \033[31m{} @ {}".format(src, repr(ex), top)) | ||||
|  | ||||
|  | ||||
| def unescape_cookie(orig): | ||||
| @@ -802,7 +837,11 @@ def chkcmd(*argv): | ||||
| def gzip_orig_sz(fn): | ||||
|     with open(fsenc(fn), "rb") as f: | ||||
|         f.seek(-4, 2) | ||||
|         return struct.unpack(b"I", f.read(4))[0] | ||||
|         rv = f.read(4) | ||||
|         try: | ||||
|             return struct.unpack(b"I", rv)[0] | ||||
|         except: | ||||
|             return struct.unpack("I", rv)[0] | ||||
|  | ||||
|  | ||||
| def py_desc(): | ||||
| @@ -812,7 +851,11 @@ def py_desc(): | ||||
|     if ofs > 0: | ||||
|         py_ver = py_ver[:ofs] | ||||
|  | ||||
|     try: | ||||
|         bitness = struct.calcsize(b"P") * 8 | ||||
|     except: | ||||
|         bitness = struct.calcsize("P") * 8 | ||||
|  | ||||
|     host_os = platform.system() | ||||
|     compiler = platform.python_compiler() | ||||
|  | ||||
|   | ||||
| @@ -42,12 +42,8 @@ body { | ||||
| #path #entree { | ||||
| 	margin-left: -.7em; | ||||
| } | ||||
| #treetab { | ||||
| 	display: none; | ||||
| } | ||||
| #files { | ||||
| 	border-spacing: 0; | ||||
| 	margin-top: 2em; | ||||
| 	z-index: 1; | ||||
| 	position: relative; | ||||
| } | ||||
| @@ -55,11 +51,10 @@ body { | ||||
| 	display: block; | ||||
| 	padding: .3em 0; | ||||
| } | ||||
| #files[ts] tbody div a { | ||||
| #files tbody div a { | ||||
| 	color: #f5a; | ||||
| } | ||||
| a, | ||||
| #files[ts] tbody div a:last-child { | ||||
| a, #files tbody div a:last-child { | ||||
| 	color: #fc5; | ||||
| 	padding: .2em; | ||||
| 	text-decoration: none; | ||||
| @@ -158,6 +153,15 @@ a, | ||||
| .logue { | ||||
| 	padding: .2em 1.5em; | ||||
| } | ||||
| .logue:empty { | ||||
| 	display: none; | ||||
| } | ||||
| #pro.logue { | ||||
| 	margin-bottom: .8em; | ||||
| } | ||||
| #epi.logue { | ||||
| 	margin: .8em 0; | ||||
| } | ||||
| #srv_info { | ||||
| 	opacity: .5; | ||||
| 	font-size: .8em; | ||||
| @@ -443,12 +447,40 @@ input[type="checkbox"]:checked+label { | ||||
| #files td div a:last-child { | ||||
| 	width: 100%; | ||||
| } | ||||
| #tree, | ||||
| #treefiles { | ||||
| 	vertical-align: top; | ||||
| #wrap { | ||||
| 	margin-top: 2em; | ||||
| } | ||||
| #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 { | ||||
| 	padding: .2em .4em; | ||||
| @@ -472,24 +504,22 @@ input[type="checkbox"]:checked+label { | ||||
| 	padding: .3em .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 li { | ||||
| 	padding: 0; | ||||
| 	margin: 0; | ||||
| } | ||||
| #tree ul { | ||||
| 	border-left: .2em solid #444; | ||||
| 	border-left: .2em solid #555; | ||||
| } | ||||
| #tree li { | ||||
| 	margin-left: 1em; | ||||
| 	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 { | ||||
| 	color: #400; | ||||
| @@ -503,24 +533,12 @@ input[type="checkbox"]:checked+label { | ||||
| #treeul a+a { | ||||
| 	width: calc(100% - 2em); | ||||
| 	background: #333; | ||||
| 	line-height: 1em; | ||||
| } | ||||
| #treeul a+a:hover { | ||||
| 	background: #222; | ||||
| 	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 { | ||||
| 	font-family: monospace, monospace; | ||||
| } | ||||
| @@ -579,3 +597,38 @@ input[type="checkbox"]:checked+label { | ||||
| 	color: #300; | ||||
| 	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; | ||||
| } | ||||
| @@ -12,17 +12,19 @@ | ||||
|  | ||||
| <body> | ||||
|     <div id="ops"> | ||||
|         <a href="#" data-dest="">---</a> | ||||
|         <a href="#" data-perm="read" data-dest="search">🔎</a> | ||||
|         <a href="#" data-dest="" data-desc="close submenu">---</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.<br /><br /><code>foo bar</code> = must contain both foo and bar,<br /><code>foo -bar</code> = must contain foo but not bar,<br /><code>^yana .opus$</code> = must start with yana and have the opus extension">🔎</a> | ||||
|         {%- 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 %} | ||||
|         <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 %} | ||||
|         <a href="#" data-perm="write" data-dest="bup">🎈</a> | ||||
|         <a href="#" data-perm="write" data-dest="mkdir">📂</a> | ||||
|         <a href="#" data-perm="write" data-dest="new_md">📝</a> | ||||
|         <a href="#" data-perm="write" data-dest="msg">📟</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" data-desc="mkdir: create a new directory">📂</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" 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 id="op_search" class="opview"> | ||||
| @@ -33,8 +35,14 @@ | ||||
|         {%- endif %} | ||||
|         <div id="srch_q"></div> | ||||
|     </div> | ||||
|  | ||||
|     {%- include 'upload.html' %} | ||||
|  | ||||
|     <div id="op_cfg" class="opview opbox"> | ||||
|         <h3>key notation</h3> | ||||
|         <div id="key_notation"></div> | ||||
|     </div> | ||||
|      | ||||
|     <h1 id="path"> | ||||
|         <a href="#" id="entree">🌲</a> | ||||
|         {%- for n in vpnodes %} | ||||
| @@ -42,20 +50,18 @@ | ||||
|         {%- endfor %} | ||||
|     </h1> | ||||
|      | ||||
|     <div id="pro" class="logue">{{ logues[0] }}</div> | ||||
|  | ||||
|     <table id="treetab"> | ||||
|         <tr> | ||||
|             <td id="tree"> | ||||
|     <div id="tree"> | ||||
|         <a href="#" id="detree">🍞...</a> | ||||
|         <a href="#" step="2" id="twobytwo">+</a> | ||||
|         <a href="#" step="-2" id="twig">–</a> | ||||
|         <a href="#" id="dyntree">a</a> | ||||
|         <ul id="treeul"></ul> | ||||
|             </td> | ||||
|             <td id="treefiles"></td> | ||||
|         </tr> | ||||
|     </table> | ||||
|         <div id="thx_ff"> </div> | ||||
|     </div> | ||||
|  | ||||
| <div id="wrap"> | ||||
|  | ||||
|     <div id="pro" class="logue">{{ logues[0] }}</div> | ||||
|  | ||||
|     <table id="files"> | ||||
|         <thead> | ||||
| @@ -93,6 +99,8 @@ | ||||
|  | ||||
|     <h2><a href="?h">control-panel</a></h2> | ||||
|  | ||||
| </div> | ||||
|  | ||||
|     {%- if srv_info %} | ||||
|     <div id="srv_info"><span>{{ srv_info }}</span></div> | ||||
|     {%- endif %} | ||||
|   | ||||
| @@ -6,7 +6,7 @@ function dbg(msg) { | ||||
| 	ebi('path').innerHTML = msg; | ||||
| } | ||||
|  | ||||
| makeSortable(ebi('files')); | ||||
| makeSortable(ebi('files'), reload_mp); | ||||
|  | ||||
|  | ||||
| // extract songs + add play column | ||||
| @@ -467,8 +467,7 @@ function play(tid, call_depth) { | ||||
| 		var o = ebi(oid); | ||||
| 		o.setAttribute('id', 'thx_js'); | ||||
| 		if (window.history && history.replaceState) { | ||||
| 			var nurl = (document.location + '').split('#')[0] + '#' + oid; | ||||
| 			hist_replace(ebi('files').innerHTML, nurl); | ||||
| 			hist_replace(document.location.pathname + '#' + oid); | ||||
| 		} | ||||
| 		else { | ||||
| 			document.location.hash = oid; | ||||
| @@ -511,7 +510,7 @@ function evau_error(e) { | ||||
| 	if (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); | ||||
| } | ||||
| @@ -546,7 +545,7 @@ function autoplay_blocked() { | ||||
| 	var na = ebi('blk_na'); | ||||
|  | ||||
| 	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.onclick = function (e) { | ||||
| @@ -587,10 +586,11 @@ function autoplay_blocked() { | ||||
| 			["name", "name", "name contains   (negate with -nope)", "46"] | ||||
| 		] | ||||
| 	]; | ||||
| 	var oldcfg = []; | ||||
|  | ||||
| 	if (document.querySelector('#srch_form.tags')) | ||||
| 		sconf.push(["tags", | ||||
| 			["tags", "tags", "tags contains", "46"] | ||||
| 			["tags", "tags", "tags contains   (^=start, end=$)", "46"] | ||||
| 		]); | ||||
|  | ||||
| 	var html = []; | ||||
| @@ -654,7 +654,7 @@ function autoplay_blocked() { | ||||
| 			return; | ||||
|  | ||||
| 		if (this.status !== 200) { | ||||
| 			alert('ah fug\n' + this.status + ": " + this.responseText); | ||||
| 			alert("http " + this.status + ": " + this.responseText); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| @@ -665,8 +665,16 @@ function autoplay_blocked() { | ||||
| 		if (ofiles.getAttribute('ts') > this.ts) | ||||
| 			return; | ||||
|  | ||||
| 		if (!oldcfg.length) { | ||||
| 			oldcfg = [ | ||||
| 				ebi('path').style.display, | ||||
| 				ebi('tree').style.display, | ||||
| 				ebi('wrap').style.marginLeft | ||||
| 			]; | ||||
| 			ebi('path').style.display = 'none'; | ||||
| 			ebi('tree').style.display = 'none'; | ||||
| 			ebi('wrap').style.marginLeft = '0'; | ||||
| 		} | ||||
|  | ||||
| 		var html = mk_files_header(tagord); | ||||
| 		html.push('<tbody>'); | ||||
| @@ -715,8 +723,10 @@ function autoplay_blocked() { | ||||
|  | ||||
| 	function unsearch(e) { | ||||
| 		ev(e); | ||||
| 		ebi('path').style.display = 'inline-block'; | ||||
| 		ebi('tree').style.display = 'block'; | ||||
| 		ebi('path').style.display = oldcfg[0]; | ||||
| 		ebi('tree').style.display = oldcfg[1]; | ||||
| 		ebi('wrap').style.marginLeft = oldcfg[2]; | ||||
| 		oldcfg = []; | ||||
| 		ebi('files').innerHTML = orig_html; | ||||
| 		orig_html = null; | ||||
| 		reload_browser(); | ||||
| @@ -724,35 +734,77 @@ function autoplay_blocked() { | ||||
| })(); | ||||
|  | ||||
|  | ||||
| // tree | ||||
| (function () { | ||||
| 	var treedata = null; | ||||
| var treectl = (function () { | ||||
| 	var dyn = bcfg_get('dyntree', true); | ||||
| 	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 + ']'); | ||||
| 	var entreed = false; | ||||
|  | ||||
| 	function entree(e) { | ||||
| 		ev(e); | ||||
| 		entreed = true; | ||||
| 		ebi('path').style.display = 'none'; | ||||
|  | ||||
| 		var treetab = ebi('treetab'); | ||||
| 		var treefiles = ebi('treefiles'); | ||||
|  | ||||
| 		treetab.style.display = 'table'; | ||||
|  | ||||
| 		treefiles.appendChild(ebi('pro')); | ||||
| 		treefiles.appendChild(ebi('files')); | ||||
| 		treefiles.appendChild(ebi('epi')); | ||||
| 		var tree = ebi('tree'); | ||||
| 		tree.style.display = 'block'; | ||||
|  | ||||
| 		swrite('entreed', 'tree'); | ||||
| 		get_tree("", get_vpath()); | ||||
| 		get_tree("", get_evpath(), true); | ||||
| 		window.addEventListener('scroll', onscroll); | ||||
| 		window.addEventListener('resize', onresize); | ||||
| 		onresize(); | ||||
| 	} | ||||
|  | ||||
| 	function get_tree(top, dst) { | ||||
| 	function detree(e) { | ||||
| 		ev(e); | ||||
| 		entreed = false; | ||||
| 		ebi('tree').style.display = 'none'; | ||||
| 		ebi('path').style.display = 'inline-block'; | ||||
| 		ebi('wrap').style.marginLeft = '0'; | ||||
| 		swrite('entreed', 'na'); | ||||
| 		window.removeEventListener('resize', onresize); | ||||
| 		window.removeEventListener('scroll', onscroll); | ||||
| 	} | ||||
|  | ||||
| 	function onscroll() { | ||||
| 		if (!entreed) | ||||
| 			return; | ||||
|  | ||||
| 		var top = ebi('wrap').getBoundingClientRect().top; | ||||
| 		ebi('tree').style.top = Math.max(0, parseInt(top)) + 'px'; | ||||
| 	} | ||||
|  | ||||
| 	function periodic() { | ||||
| 		onscroll(); | ||||
| 		setTimeout(periodic, document.visibilityState ? 200 : 5000); | ||||
| 	} | ||||
| 	periodic(); | ||||
|  | ||||
| 	function onresize(e) { | ||||
| 		if (!entreed) | ||||
| 			return; | ||||
|  | ||||
| 		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(); | ||||
| 	} | ||||
|  | ||||
| 	function get_tree(top, dst, rst) { | ||||
| 		var xhr = new XMLHttpRequest(); | ||||
| 		xhr.top = top; | ||||
| 		xhr.dst = dst; | ||||
| 		xhr.rst = rst; | ||||
| 		xhr.ts = new Date().getTime(); | ||||
| 		xhr.open('GET', dst + '?tree=' + top, true); | ||||
| 		xhr.onreadystatechange = recvtree; | ||||
| 		xhr.send(); | ||||
| @@ -764,12 +816,19 @@ function autoplay_blocked() { | ||||
| 			return; | ||||
|  | ||||
| 		if (this.status !== 200) { | ||||
| 			alert('ah fug\n' + this.status + ": " + this.responseText); | ||||
| 			alert("http " + this.status + ": " + this.responseText); | ||||
| 			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, | ||||
| 			name = top.split('/').slice(-2)[0], | ||||
| 			name = uricom_dec(top.split('/').slice(-2)[0])[0], | ||||
| 			rtop = top.replace(/^\/+/, ""); | ||||
|  | ||||
| 		try { | ||||
| @@ -781,7 +840,7 @@ function autoplay_blocked() { | ||||
| 		var html = parsetree(res, rtop); | ||||
| 		if (!this.top) { | ||||
| 			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>'; | ||||
| 		} | ||||
| 		else { | ||||
| @@ -803,24 +862,11 @@ function autoplay_blocked() { | ||||
| 		document.querySelector('#treeul>li>a+a').textContent = '[root]'; | ||||
| 		despin('#tree'); | ||||
| 		reload_tree(); | ||||
| 		rescale_tree(); | ||||
| 	} | ||||
|  | ||||
| 	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'; | ||||
| 		onresize(); | ||||
| 	} | ||||
|  | ||||
| 	function reload_tree() { | ||||
| 		var cdir = get_vpath(); | ||||
| 		var cdir = get_evpath(); | ||||
| 		var links = document.querySelectorAll('#treeul a+a'); | ||||
| 		for (var a = 0, aa = links.length; a < aa; a++) { | ||||
| 			var href = links[a].getAttribute('href'); | ||||
| @@ -841,12 +887,20 @@ function autoplay_blocked() { | ||||
| 			treegrow.call(this.previousSibling, e); | ||||
| 			return; | ||||
| 		} | ||||
| 		reqls(this.getAttribute('href'), true); | ||||
| 	} | ||||
|  | ||||
| 	function reqls(url, hpush) { | ||||
| 		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.onreadystatechange = recvls; | ||||
| 		xhr.send(); | ||||
| 		if (hpush) | ||||
| 			get_tree('.', xhr.top); | ||||
|  | ||||
| 		enspin('#files'); | ||||
| 	} | ||||
|  | ||||
| @@ -858,7 +912,7 @@ function autoplay_blocked() { | ||||
| 				rm.parentNode.removeChild(rm); | ||||
| 			} | ||||
| 			this.textContent = '+'; | ||||
| 			rescale_tree(); | ||||
| 			onresize(); | ||||
| 			return; | ||||
| 		} | ||||
| 		var dst = this.getAttribute('dst'); | ||||
| @@ -870,10 +924,17 @@ function autoplay_blocked() { | ||||
| 			return; | ||||
|  | ||||
| 		if (this.status !== 200) { | ||||
| 			alert('ah fug\n' + this.status + ": " + this.responseText); | ||||
| 			alert("http " + this.status + ": " + this.responseText); | ||||
| 			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 { | ||||
| 			var res = JSON.parse(this.responseText); | ||||
| 		} | ||||
| @@ -890,7 +951,7 @@ function autoplay_blocked() { | ||||
| 		for (var a = 0; a < nodes.length; a++) { | ||||
| 			var r = nodes[a], | ||||
| 				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++) { | ||||
| 				var k = res.taglist[b], | ||||
| @@ -913,7 +974,9 @@ function autoplay_blocked() { | ||||
| 		html = html.join('\n'); | ||||
| 		ebi('files').innerHTML = html; | ||||
|  | ||||
| 		hist_push(html, this.top); | ||||
| 		if (this.hpush) | ||||
| 			hist_push(this.top); | ||||
|  | ||||
| 		apply_perms(res.perms); | ||||
| 		despin('#files'); | ||||
|  | ||||
| @@ -921,6 +984,7 @@ function autoplay_blocked() { | ||||
| 		ebi('epi').innerHTML = res.logues ? res.logues[1] || "" : ""; | ||||
|  | ||||
| 		filecols.set_style(); | ||||
| 		mukey.render(); | ||||
| 		reload_tree(); | ||||
| 		reload_browser(); | ||||
| 	} | ||||
| @@ -936,12 +1000,14 @@ function autoplay_blocked() { | ||||
| 		keys.sort(); | ||||
| 		for (var a = 0; a < keys.length; a++) { | ||||
| 			var kk = keys[a], | ||||
| 				k = kk.slice(1), | ||||
| 				url = '/' + (top ? top + k : k) + '/', | ||||
| 				ek = esc(k), | ||||
| 				ks = kk.slice(1), | ||||
| 				k = uricom_dec(ks), | ||||
| 				hek = esc(k[0]), | ||||
| 				uek = k[1] ? uricom_enc(k[0], true) : k[0], | ||||
| 				url = '/' + (top ? top + uek : uek) + '/', | ||||
| 				sym = res[kk] ? '-' : '+', | ||||
| 				link = '<a href="#">' + sym + '</a><a href="' + | ||||
| 					esc(url) + '">' + ek + '</a>'; | ||||
| 					url + '">' + hek + '</a>'; | ||||
|  | ||||
| 			if (res[kk]) { | ||||
| 				var subtree = parsetree(res[kk], url.slice(1)); | ||||
| @@ -954,25 +1020,11 @@ function autoplay_blocked() { | ||||
| 		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) { | ||||
| 		ev(e); | ||||
| 		dyn = !dyn; | ||||
| 		bcfg_set('dyntree', dyn); | ||||
| 		rescale_tree(); | ||||
| 		onresize(); | ||||
| 	} | ||||
|  | ||||
| 	function scaletree(e) { | ||||
| @@ -982,7 +1034,7 @@ function autoplay_blocked() { | ||||
| 			treesz = 16; | ||||
|  | ||||
| 		swrite('treesz', treesz); | ||||
| 		rescale_tree(); | ||||
| 		onresize(); | ||||
| 	} | ||||
|  | ||||
| 	ebi('entree').onclick = entree; | ||||
| @@ -994,19 +1046,22 @@ function autoplay_blocked() { | ||||
| 		entree(); | ||||
|  | ||||
| 	window.onpopstate = function (e) { | ||||
| 		console.log(e.url + ' ,, ' + ((e.state + '').slice(0, 64))); | ||||
| 		var html = sessionStorage.getItem(e.state || 1); | ||||
| 		if (!html) | ||||
| 		console.log("h-pop " + e.state); | ||||
| 		if (!e.state) | ||||
| 			return; | ||||
|  | ||||
| 		ebi('files').innerHTML = html; | ||||
| 		reload_tree(); | ||||
| 		reload_browser(); | ||||
| 		var url = new URL(e.state, "https://" + document.location.host); | ||||
| 		url = url.pathname; | ||||
| 		get_tree("", url, true); | ||||
| 		reqls(url); | ||||
| 	}; | ||||
|  | ||||
| 	if (window.history && history.pushState) { | ||||
| 		var u = get_vpath() + window.location.hash; | ||||
| 		hist_replace(ebi('files').innerHTML, u); | ||||
| 		hist_replace(get_evpath() + window.location.hash); | ||||
| 	} | ||||
|  | ||||
| 	return { | ||||
| 		"onscroll": onscroll | ||||
| 	} | ||||
| })(); | ||||
|  | ||||
| @@ -1168,17 +1223,158 @@ 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) { | ||||
| 	ev(e); | ||||
| 	filecols.toggle(this.parentElement.parentElement.getElementsByTagName('span')[0].textContent); | ||||
| } | ||||
|  | ||||
|  | ||||
| function reload_mp() { | ||||
| 	if (mp && mp.au) { | ||||
| 		mp.au.pause(); | ||||
| 		mp.au = null; | ||||
| 	} | ||||
| 	widget.close(); | ||||
| 	mp = init_mp(); | ||||
| } | ||||
|  | ||||
|  | ||||
| function reload_browser(not_mp) { | ||||
| 	filecols.set_style(); | ||||
| 	makeSortable(ebi('files')); | ||||
| 	makeSortable(ebi('files'), reload_mp); | ||||
|  | ||||
| 	var parts = get_vpath().split('/'); | ||||
| 	var parts = get_evpath().split('/'); | ||||
| 	var rm = document.querySelectorAll('#path>a+a+a'); | ||||
| 	for (a = rm.length - 1; a >= 0; a--) | ||||
| 		rm[a].parentNode.removeChild(rm[a]); | ||||
| @@ -1200,16 +1396,11 @@ function reload_browser(not_mp) { | ||||
| 		oo[a].textContent = hsz; | ||||
| 	} | ||||
|  | ||||
| 	if (!not_mp) { | ||||
| 		if (mp && mp.au) { | ||||
| 			mp.au.pause(); | ||||
| 			mp.au = null; | ||||
| 		} | ||||
| 		widget.close(); | ||||
| 		mp = init_mp(); | ||||
| 	} | ||||
| 	if (!not_mp) | ||||
| 		reload_mp(); | ||||
|  | ||||
| 	if (window['up2k']) | ||||
| 		up2k.set_fsearch(); | ||||
| } | ||||
| reload_browser(true); | ||||
| mukey.render(); | ||||
|   | ||||
| @@ -65,7 +65,7 @@ function statify(obj) { | ||||
|         if (a > 0) | ||||
|             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>'); | ||||
|     } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ var dom_md = ebi('mt'); | ||||
|         if (a > 0) | ||||
|             loc.push(n[a]); | ||||
|  | ||||
|         var dec = decodeURIComponent(n[a]).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); | ||||
|         var dec = uricom_dec(n[a])[0].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); | ||||
|  | ||||
|         nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>'); | ||||
|     } | ||||
|   | ||||
| @@ -46,9 +46,9 @@ function up2k_flagbus() { | ||||
|     var dbg = function (who, msg) { | ||||
|         console.log('flagbus(' + flag.id + '): [' + who + '] ' + msg); | ||||
|     }; | ||||
|     flag.ch.onmessage = function (ev) { | ||||
|         var who = ev.data[0], | ||||
|             what = ev.data[1]; | ||||
|     flag.ch.onmessage = function (e) { | ||||
|         var who = e.data[0], | ||||
|             what = e.data[1]; | ||||
|  | ||||
|         if (who == flag.id) { | ||||
|             dbg(who, 'hi me (??)'); | ||||
| @@ -83,7 +83,7 @@ function up2k_flagbus() { | ||||
|             flag.ch.postMessage([flag.id, "hey"]); | ||||
|         } | ||||
|         else { | ||||
|             dbg('?', ev.data); | ||||
|             dbg('?', e.data); | ||||
|         } | ||||
|     }; | ||||
|     var tx = function (now, msg) { | ||||
| @@ -194,7 +194,7 @@ function up2k_init(have_crypto) { | ||||
|  | ||||
|     // handle user intent to use the basic uploader instead | ||||
|     ebi('u2nope').onclick = function (e) { | ||||
|         e.preventDefault(); | ||||
|         ev(e); | ||||
|         setmsg(); | ||||
|         goto('bup'); | ||||
|     }; | ||||
| @@ -254,29 +254,29 @@ function up2k_init(have_crypto) { | ||||
|     } | ||||
|     ebi('u2btn').addEventListener('click', nav, false); | ||||
|  | ||||
|     function ondrag(ev) { | ||||
|         ev.stopPropagation(); | ||||
|         ev.preventDefault(); | ||||
|         ev.dataTransfer.dropEffect = 'copy'; | ||||
|         ev.dataTransfer.effectAllowed = 'copy'; | ||||
|     function ondrag(e) { | ||||
|         e.stopPropagation(); | ||||
|         e.preventDefault(); | ||||
|         e.dataTransfer.dropEffect = 'copy'; | ||||
|         e.dataTransfer.effectAllowed = 'copy'; | ||||
|     } | ||||
|     ebi('u2btn').addEventListener('dragover', ondrag, false); | ||||
|     ebi('u2btn').addEventListener('dragenter', ondrag, false); | ||||
|  | ||||
|     function gotfile(ev) { | ||||
|         ev.stopPropagation(); | ||||
|         ev.preventDefault(); | ||||
|     function gotfile(e) { | ||||
|         e.stopPropagation(); | ||||
|         e.preventDefault(); | ||||
|  | ||||
|         var files; | ||||
|         var is_itemlist = false; | ||||
|         if (ev.dataTransfer) { | ||||
|             if (ev.dataTransfer.items) { | ||||
|                 files = ev.dataTransfer.items; // DataTransferItemList | ||||
|         if (e.dataTransfer) { | ||||
|             if (e.dataTransfer.items) { | ||||
|                 files = e.dataTransfer.items; // DataTransferItemList | ||||
|                 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) | ||||
|             return alert('no files selected??'); | ||||
| @@ -332,7 +332,7 @@ function up2k_init(have_crypto) { | ||||
|                 "name": fobj.name, | ||||
|                 "size": fobj.size, | ||||
|                 "lmod": lmod / 1000, | ||||
|                 "purl": get_vpath(), | ||||
|                 "purl": get_evpath(), | ||||
|                 "done": false, | ||||
|                 "hash": [] | ||||
|             }; | ||||
| @@ -655,8 +655,8 @@ function up2k_init(have_crypto) { | ||||
|             prog(t.n, nchunk, col_hashing); | ||||
|         }; | ||||
|  | ||||
|         var segm_load = function (ev) { | ||||
|             cache_buf = ev.target.result; | ||||
|         var segm_load = function (e) { | ||||
|             cache_buf = e.target.result; | ||||
|             cache_ofs = 0; | ||||
|             hash_calc(); | ||||
|         }; | ||||
| @@ -730,7 +730,7 @@ function up2k_init(have_crypto) { | ||||
|         st.busy.handshake.push(t); | ||||
|  | ||||
|         var xhr = new XMLHttpRequest(); | ||||
|         xhr.onload = function (ev) { | ||||
|         xhr.onload = function (e) { | ||||
|             if (xhr.status == 200) { | ||||
|                 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)'); | ||||
|         }; | ||||
|  | ||||
|         reader.onload = function (ev) { | ||||
|         reader.onload = function (e) { | ||||
|             var xhr = new XMLHttpRequest(); | ||||
|             xhr.upload.onprogress = function (xev) { | ||||
|                 var perc = xev.loaded / (cdr - car) * 100; | ||||
| @@ -915,7 +915,7 @@ function up2k_init(have_crypto) { | ||||
|             xhr.setRequestHeader('Content-Type', 'application/octet-stream'); | ||||
|             xhr.overrideMimeType('Content-Type', 'application/octet-stream'); | ||||
|             xhr.responseType = 'text'; | ||||
|             xhr.send(ev.target.result); | ||||
|             xhr.send(e.target.result); | ||||
|         }; | ||||
|  | ||||
|         reader.readAsArrayBuffer(bobslice.call(t.fobj, car, cdr)); | ||||
| @@ -944,7 +944,7 @@ function up2k_init(have_crypto) { | ||||
|     ///   config ui | ||||
|     // | ||||
|  | ||||
|     function onresize(ev) { | ||||
|     function onresize(e) { | ||||
|         var bar = ebi('ops'), | ||||
|             wpx = innerWidth, | ||||
|             fpx = parseInt(getComputedStyle(bar)['font-size']), | ||||
| @@ -959,17 +959,17 @@ function up2k_init(have_crypto) { | ||||
|             ebi('u2conf').setAttribute('class', wide ? 'has_btn' : ''); | ||||
|         } | ||||
|     } | ||||
|     window.onresize = onresize; | ||||
|     window.addEventListener('resize', onresize); | ||||
|     onresize(); | ||||
|  | ||||
|     function desc_show(ev) { | ||||
|     function desc_show(e) { | ||||
|         var msg = this.getAttribute('alt'); | ||||
|         msg = msg.replace(/\$N/g, "<br />"); | ||||
|         var cdesc = ebi('u2cdesc'); | ||||
|         cdesc.innerHTML = msg; | ||||
|         cdesc.setAttribute('class', 'show'); | ||||
|     } | ||||
|     function desc_hide(ev) { | ||||
|     function desc_hide(e) { | ||||
|         ebi('u2cdesc').setAttribute('class', ''); | ||||
|     } | ||||
|     var o = document.querySelectorAll('#u2conf *[alt]'); | ||||
| @@ -1084,17 +1084,17 @@ function up2k_init(have_crypto) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function nop(ev) { | ||||
|         ev.preventDefault(); | ||||
|     function nop(e) { | ||||
|         ev(e); | ||||
|         this.click(); | ||||
|     } | ||||
|  | ||||
|     ebi('nthread_add').onclick = function (ev) { | ||||
|         ev.preventDefault(); | ||||
|     ebi('nthread_add').onclick = function (e) { | ||||
|         ev(e); | ||||
|         bumpthread(1); | ||||
|     }; | ||||
|     ebi('nthread_sub').onclick = function (ev) { | ||||
|         ev.preventDefault(); | ||||
|     ebi('nthread_sub').onclick = function (e) { | ||||
|         ev(e); | ||||
|         bumpthread(-1); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -62,7 +62,7 @@ | ||||
| 	width: calc(100% - 2em); | ||||
| 	max-width: 100em; | ||||
| } | ||||
| #u2form.srch #u2tab { | ||||
| #op_up2k.srch #u2tab { | ||||
| 	max-width: none; | ||||
| } | ||||
| #u2tab td { | ||||
| @@ -76,7 +76,7 @@ | ||||
| #u2tab td:nth-child(3) { | ||||
| 	width: 40%; | ||||
| } | ||||
| #u2form.srch #u2tab td:nth-child(3) { | ||||
| #op_up2k.srch #u2tab td:nth-child(3) { | ||||
| 	font-family: sans-serif; | ||||
| 	width: auto; | ||||
| } | ||||
|   | ||||
| @@ -23,6 +23,7 @@ function esc(txt) { | ||||
| } | ||||
| function vis_exh(msg, url, lineNo, columnNo, error) { | ||||
|     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>', | ||||
|         esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>']; | ||||
|  | ||||
| @@ -127,7 +128,7 @@ function sortTable(table, col) { | ||||
|     }); | ||||
|     for (i = 0; i < tr.length; ++i) tb.appendChild(tr[vl[i][1]]); | ||||
| } | ||||
| function makeSortable(table) { | ||||
| function makeSortable(table, cb) { | ||||
|     var th = table.tHead, i; | ||||
|     th && (th = th.rows[0]) && (th = th.cells); | ||||
|     if (th) i = th.length; | ||||
| @@ -136,6 +137,8 @@ function makeSortable(table) { | ||||
|         th[i].onclick = function (e) { | ||||
|             ev(e); | ||||
|             sortTable(table, i); | ||||
|             if (cb) | ||||
|                 cb(); | ||||
|         }; | ||||
|     }(i)); | ||||
| } | ||||
| @@ -156,7 +159,7 @@ function opclick(e) { | ||||
|     var dest = this.getAttribute('data-dest'); | ||||
|     goto(dest); | ||||
|  | ||||
|     swrite('opmode', dest || undefined); | ||||
|     swrite('opmode', dest || null); | ||||
|  | ||||
|     var input = document.querySelector('.opview.act input:not([type="hidden"])') | ||||
|     if (input) | ||||
| @@ -173,10 +176,6 @@ function goto(dest) { | ||||
|     for (var a = obj.length - 1; a >= 0; a--) | ||||
|         obj[a].classList.remove('act'); | ||||
|  | ||||
|     var others = ['path', 'files', 'widget']; | ||||
|     for (var a = 0; a < others.length; a++) | ||||
|         ebi(others[a]).classList.remove('hidden'); | ||||
|  | ||||
|     if (dest) { | ||||
|         var ui = ebi('op_' + dest); | ||||
|         ui.classList.add('act'); | ||||
| @@ -186,6 +185,9 @@ function goto(dest) { | ||||
|         if (fn) | ||||
|             fn(); | ||||
|     } | ||||
|  | ||||
|     if (window['treectl']) | ||||
|         treectl.onscroll(); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -224,6 +226,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() { | ||||
|     var ret = document.location.pathname; | ||||
|  | ||||
| @@ -238,7 +265,7 @@ function get_evpath() { | ||||
|  | ||||
|  | ||||
| function get_vpath() { | ||||
|     return decodeURIComponent(get_evpath()); | ||||
|     return uricom_dec(get_evpath())[0]; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -266,12 +293,12 @@ function sread(key) { | ||||
|     if (window.localStorage) | ||||
|         return localStorage.getItem(key); | ||||
|  | ||||
|     return ''; | ||||
|     return null; | ||||
| } | ||||
|  | ||||
| function swrite(key, val) { | ||||
|     if (window.localStorage) { | ||||
|         if (val === undefined) | ||||
|         if (val === undefined || val === null) | ||||
|             localStorage.removeItem(key); | ||||
|         else | ||||
|             localStorage.setItem(key, val); | ||||
| @@ -297,7 +324,7 @@ function icfg_get(name, defval) { | ||||
|     var o = ebi(name); | ||||
|  | ||||
|     var val = parseInt(sread(name)); | ||||
|     if (val === null) | ||||
|     if (isNaN(val)) | ||||
|         return parseInt(o ? o.value : defval); | ||||
|  | ||||
|     if (o) | ||||
| @@ -339,14 +366,12 @@ function bcfg_upd_ui(name, val) { | ||||
| } | ||||
|  | ||||
|  | ||||
| function hist_push(html, url) { | ||||
|     var key = new Date().getTime(); | ||||
|     sessionStorage.setItem(key, html); | ||||
|     history.pushState(key, url, url); | ||||
| function hist_push(url) { | ||||
|     console.log("h-push " + url); | ||||
|     history.pushState(url, url, url); | ||||
| } | ||||
|  | ||||
| function hist_replace(html, url) { | ||||
|     var key = new Date().getTime(); | ||||
|     sessionStorage.setItem(key, html); | ||||
|     history.replaceState(key, url, url); | ||||
| function hist_replace(url) { | ||||
|     console.log("h-repl " + url); | ||||
|     history.replaceState(url, url, url); | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|  | ||||
| ## | ||||
| ## 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 | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,7 @@ set -e | ||||
| # -rwxr-xr-x  0 ed ed  183808 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.py | ||||
|  | ||||
|  | ||||
| command -v gnutar && tar() { gnutar "$@"; } | ||||
| command -v gtar && tar() { gtar "$@"; } | ||||
| command -v gsed && sed() { gsed "$@"; } | ||||
| td="$(mktemp -d)" | ||||
| @@ -29,11 +30,11 @@ pwd | ||||
|  | ||||
|  | ||||
| dl_text() { | ||||
| 	command -v curl && exec curl "$@" | ||||
| 	command -v curl >/dev/null && exec curl "$@" | ||||
| 	exec wget -O- "$@" | ||||
| } | ||||
| dl_files() { | ||||
| 	command -v curl && exec curl -L --remote-name-all "$@" | ||||
| 	command -v curl >/dev/null && exec curl -L --remote-name-all "$@" | ||||
| 	exec wget "$@" | ||||
| } | ||||
| export -f dl_files | ||||
|   | ||||
| @@ -180,7 +180,7 @@ tmv "$f" | ||||
|  | ||||
| # up2k goes from 28k to 22k laff | ||||
| echo entabbening | ||||
| find | grep -E '\.(js|css|html|py)$' | while IFS= read -r f; do | ||||
| find | grep -E '\.(js|css|html)$' | while IFS= read -r f; do | ||||
| 	unexpand -t 4 --first-only <"$f" >t | ||||
| 	tmv "$f" | ||||
| done | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| #!/usr/bin/env python | ||||
| # coding: utf-8 | ||||
| # coding: latin-1 | ||||
| from __future__ import print_function, unicode_literals | ||||
|  | ||||
| import os, sys, time, shutil, signal, tarfile, hashlib, platform, tempfile | ||||
| import subprocess as sp | ||||
| import os, sys, time, shutil, runpy, tarfile, hashlib, platform, tempfile, traceback | ||||
|  | ||||
| """ | ||||
| run me with any version of python, i will unpack and run copyparty | ||||
| @@ -344,20 +343,24 @@ def get_payload(): | ||||
|                 break | ||||
|  | ||||
|  | ||||
| def confirm(): | ||||
| def confirm(rv): | ||||
|     msg() | ||||
|     msg(traceback.format_exc()) | ||||
|     msg("*** hit enter to exit ***") | ||||
|     try: | ||||
|         raw_input() if PY2 else input() | ||||
|     except: | ||||
|         pass | ||||
|  | ||||
|     sys.exit(rv) | ||||
|  | ||||
|  | ||||
| def run(tmp, j2ver): | ||||
|     global cpp | ||||
|  | ||||
|     msg("jinja2:", j2ver or "bundled") | ||||
|     msg("sfxdir:", tmp) | ||||
|     msg() | ||||
|  | ||||
|     # "systemd-tmpfiles-clean.timer"?? HOW do you even come up with this shit | ||||
|     try: | ||||
| @@ -373,30 +376,16 @@ def run(tmp, j2ver): | ||||
|     if j2ver: | ||||
|         del ld[-1] | ||||
|  | ||||
|     cmd = ( | ||||
|         "import sys, runpy; " | ||||
|         + "".join(['sys.path.insert(0, r"' + x + '"); ' for x in ld]) | ||||
|         + 'runpy.run_module("copyparty", run_name="__main__")' | ||||
|     ) | ||||
|     cmd = [sys.executable, "-c", cmd] + list(sys.argv[1:]) | ||||
|     for x in ld: | ||||
|         sys.path.insert(0, x) | ||||
|  | ||||
|     cmd = [str(x) for x in cmd] | ||||
|     msg("\n", cmd, "\n") | ||||
|     cpp = sp.Popen(cmd) | ||||
|     try: | ||||
|         cpp.wait() | ||||
|         runpy.run_module(str("copyparty"), run_name=str("__main__")) | ||||
|     except SystemExit as ex: | ||||
|         if ex.code: | ||||
|             confirm(ex.code) | ||||
|     except: | ||||
|         cpp.wait() | ||||
|  | ||||
|     if cpp.returncode != 0: | ||||
|         confirm() | ||||
|  | ||||
|     sys.exit(cpp.returncode) | ||||
|  | ||||
|  | ||||
| def bye(sig, frame): | ||||
|     if cpp is not None: | ||||
|         cpp.terminate() | ||||
|         confirm(1) | ||||
|  | ||||
|  | ||||
| def main(): | ||||
| @@ -430,8 +419,6 @@ def main(): | ||||
|  | ||||
|     # skip 0 | ||||
|  | ||||
|     signal.signal(signal.SIGTERM, bye) | ||||
|  | ||||
|     tmp = unpack() | ||||
|  | ||||
|     try: | ||||
| @@ -439,7 +426,7 @@ def main(): | ||||
|     except: | ||||
|         j2ver = None | ||||
|  | ||||
|     return run(tmp, j2ver) | ||||
|     run(tmp, j2ver) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   | ||||
| @@ -90,7 +90,7 @@ class TestVFS(unittest.TestCase): | ||||
|         finally: | ||||
|             return ret | ||||
|  | ||||
|     def log(self, src, msg): | ||||
|     def log(self, src, msg, c=0): | ||||
|         pass | ||||
|  | ||||
|     def test(self): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user