mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-03 21:43:12 +00:00 
			
		
		
		
	Compare commits
	
		
			21 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					32f9c6b5bb | ||
| 
						 | 
					6251584ef6 | ||
| 
						 | 
					f3e413bc28 | ||
| 
						 | 
					6f6cc8f3f8 | ||
| 
						 | 
					8b081e9e69 | ||
| 
						 | 
					c8a510d10e | ||
| 
						 | 
					6f834f6679 | ||
| 
						 | 
					cf2d6650ac | ||
| 
						 | 
					cd52dea488 | ||
| 
						 | 
					6ea75df05d | ||
| 
						 | 
					4846e1e8d6 | ||
| 
						 | 
					fc024f789d | ||
| 
						 | 
					473e773aea | ||
| 
						 | 
					48a2e1a353 | ||
| 
						 | 
					6da63fbd79 | ||
| 
						 | 
					5bec37fcee | ||
| 
						 | 
					3fd0ba0a31 | ||
| 
						 | 
					241a143366 | ||
| 
						 | 
					a537064da7 | ||
| 
						 | 
					f3dfd24c92 | ||
| 
						 | 
					fa0a7f50bb | 
@@ -106,6 +106,7 @@ summary: all planned features work! now please enjoy the bloatening
 | 
			
		||||
    * ☑ images using Pillow
 | 
			
		||||
    * ☑ videos using FFmpeg
 | 
			
		||||
    * ☑ cache eviction (max-age; maybe max-size eventually)
 | 
			
		||||
  * ☑ image gallery
 | 
			
		||||
  * ☑ SPA (browse while uploading)
 | 
			
		||||
    * if you use the file-tree on the left only, not folders in the file list
 | 
			
		||||
* server indexing
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,8 @@
 | 
			
		||||
# when running copyparty behind a reverse-proxy,
 | 
			
		||||
# make sure that copyparty allows at least as many clients as the proxy does,
 | 
			
		||||
# so run copyparty with -nc 512 if your nginx has the default limits
 | 
			
		||||
# (worker_processes 1, worker_connections 512)
 | 
			
		||||
 | 
			
		||||
upstream cpp {
 | 
			
		||||
	server 127.0.0.1:3923;
 | 
			
		||||
	keepalive 120;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ from textwrap import dedent
 | 
			
		||||
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, IMPLICATIONS
 | 
			
		||||
from .util import py_desc, align_tab, IMPLICATIONS, alltrace
 | 
			
		||||
 | 
			
		||||
HAVE_SSL = True
 | 
			
		||||
try:
 | 
			
		||||
@@ -182,6 +182,16 @@ def sighandler(sig=None, frame=None):
 | 
			
		||||
    print("\n".join(msg))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def stackmon(fp, ival):
 | 
			
		||||
    ctr = 0
 | 
			
		||||
    while True:
 | 
			
		||||
        ctr += 1
 | 
			
		||||
        time.sleep(ival)
 | 
			
		||||
        st = "{}, {}\n{}".format(ctr, time.time(), alltrace())
 | 
			
		||||
        with open(fp, "wb") as f:
 | 
			
		||||
            f.write(st.encode("utf-8", "replace"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_argparse(argv, formatter):
 | 
			
		||||
    ap = argparse.ArgumentParser(
 | 
			
		||||
        formatter_class=formatter,
 | 
			
		||||
@@ -222,10 +232,6 @@ def run_argparse(argv, formatter):
 | 
			
		||||
              "print,get" prints the data in the log and returns GET
 | 
			
		||||
              (leave out the ",get" to return an error instead)
 | 
			
		||||
 | 
			
		||||
            --ciphers help = available ssl/tls ciphers,
 | 
			
		||||
            --ssl-ver help = available ssl/tls versions,
 | 
			
		||||
              default is what python considers safe, usually >= TLS1
 | 
			
		||||
            
 | 
			
		||||
            values for --ls:
 | 
			
		||||
              "USR" is a user to browse as; * is anonymous, ** is all users
 | 
			
		||||
              "VOL" is a single volume to scan, default is * (all vols)
 | 
			
		||||
@@ -244,27 +250,45 @@ def run_argparse(argv, formatter):
 | 
			
		||||
    )
 | 
			
		||||
    # fmt: off
 | 
			
		||||
    ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file")
 | 
			
		||||
    ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
 | 
			
		||||
    ap.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
 | 
			
		||||
    ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
 | 
			
		||||
    ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores")
 | 
			
		||||
    ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account")
 | 
			
		||||
    ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume")
 | 
			
		||||
    ap.add_argument("-q", action="store_true", help="quiet")
 | 
			
		||||
    ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account, USER:PASS; example [ed:wark")
 | 
			
		||||
    ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed")
 | 
			
		||||
    ap.add_argument("-ed", action="store_true", help="enable ?dots")
 | 
			
		||||
    ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
 | 
			
		||||
    ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
 | 
			
		||||
    ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
 | 
			
		||||
    ap.add_argument("-nih", action="store_true", help="no info hostname")
 | 
			
		||||
    ap.add_argument("-nid", action="store_true", help="no info disk-usage")
 | 
			
		||||
    ap.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
 | 
			
		||||
    ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
 | 
			
		||||
    ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
 | 
			
		||||
    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
 | 
			
		||||
    ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
			
		||||
    ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('appearance options')
 | 
			
		||||
    ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
 | 
			
		||||
    ap2 = ap.add_argument_group('network options')
 | 
			
		||||
    ap2.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
 | 
			
		||||
    ap2.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
 | 
			
		||||
    ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
 | 
			
		||||
    
 | 
			
		||||
    ap2 = ap.add_argument_group('SSL/TLS options')
 | 
			
		||||
    ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
 | 
			
		||||
    ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
 | 
			
		||||
    ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
 | 
			
		||||
    ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ssl/tls ciphers; [help] shows available ciphers")
 | 
			
		||||
    ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
 | 
			
		||||
    ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('opt-outs')
 | 
			
		||||
    ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
 | 
			
		||||
    ap2.add_argument("-nih", action="store_true", help="no info hostname")
 | 
			
		||||
    ap2.add_argument("-nid", action="store_true", help="no info disk-usage")
 | 
			
		||||
    ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('safety options')
 | 
			
		||||
    ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
 | 
			
		||||
    ap2.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('logging options')
 | 
			
		||||
    ap2.add_argument("-q", action="store_true", help="quiet")
 | 
			
		||||
    ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
 | 
			
		||||
    ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
 | 
			
		||||
    ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('admin panel options')
 | 
			
		||||
    ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
 | 
			
		||||
@@ -299,22 +323,14 @@ def run_argparse(argv, formatter):
 | 
			
		||||
    ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin")
 | 
			
		||||
    ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('SSL/TLS options')
 | 
			
		||||
    ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
 | 
			
		||||
    ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
 | 
			
		||||
    ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="ssl/tls versions to allow")
 | 
			
		||||
    ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers")
 | 
			
		||||
    ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
 | 
			
		||||
    ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
 | 
			
		||||
    ap2 = ap.add_argument_group('appearance options')
 | 
			
		||||
    ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('debug options')
 | 
			
		||||
    ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes")
 | 
			
		||||
    ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
 | 
			
		||||
    ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
 | 
			
		||||
    ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
 | 
			
		||||
    ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
 | 
			
		||||
    ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
 | 
			
		||||
    ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
 | 
			
		||||
    ap2.add_argument("--stackmon", metavar="P,S", help="write stacktrace to Path every S second")
 | 
			
		||||
    
 | 
			
		||||
    return ap.parse_args(args=argv[1:])
 | 
			
		||||
    # fmt: on
 | 
			
		||||
@@ -354,6 +370,16 @@ def main(argv=None):
 | 
			
		||||
    except AssertionError:
 | 
			
		||||
        al = run_argparse(argv, Dodge11874)
 | 
			
		||||
 | 
			
		||||
    if al.stackmon:
 | 
			
		||||
        fp, f = al.stackmon.rsplit(",", 1)
 | 
			
		||||
        f = int(f)
 | 
			
		||||
        t = threading.Thread(
 | 
			
		||||
            target=stackmon,
 | 
			
		||||
            args=(fp, f),
 | 
			
		||||
        )
 | 
			
		||||
        t.daemon = True
 | 
			
		||||
        t.start()
 | 
			
		||||
 | 
			
		||||
    # propagate implications
 | 
			
		||||
    for k1, k2 in IMPLICATIONS:
 | 
			
		||||
        if getattr(al, k1):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
VERSION = (0, 11, 12)
 | 
			
		||||
VERSION = (0, 11, 16)
 | 
			
		||||
CODENAME = "the grid"
 | 
			
		||||
BUILD_DT = (2021, 6, 12)
 | 
			
		||||
BUILD_DT = (2021, 6, 16)
 | 
			
		||||
 | 
			
		||||
S_VERSION = ".".join(map(str, VERSION))
 | 
			
		||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
			
		||||
 
 | 
			
		||||
@@ -104,10 +104,21 @@ class HttpCli(object):
 | 
			
		||||
        v = self.headers.get("connection", "").lower()
 | 
			
		||||
        self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
 | 
			
		||||
 | 
			
		||||
        v = self.headers.get("x-forwarded-for", None)
 | 
			
		||||
        if v is not None and self.conn.addr[0] in ["127.0.0.1", "::1"]:
 | 
			
		||||
            self.ip = v.split(",")[0]
 | 
			
		||||
            self.log_src = self.conn.set_rproxy(self.ip)
 | 
			
		||||
        n = self.args.rproxy
 | 
			
		||||
        if n:
 | 
			
		||||
            v = self.headers.get("x-forwarded-for")
 | 
			
		||||
            if v and self.conn.addr[0] in ["127.0.0.1", "::1"]:
 | 
			
		||||
                if n > 0:
 | 
			
		||||
                    n -= 1
 | 
			
		||||
 | 
			
		||||
                vs = v.split(",")
 | 
			
		||||
                try:
 | 
			
		||||
                    self.ip = vs[n].strip()
 | 
			
		||||
                except:
 | 
			
		||||
                    self.ip = vs[-1].strip()
 | 
			
		||||
                    self.log("rproxy={} oob x-fwd {}".format(self.args.rproxy, v), c=3)
 | 
			
		||||
 | 
			
		||||
                self.log_src = self.conn.set_rproxy(self.ip)
 | 
			
		||||
 | 
			
		||||
        if self.args.ihead:
 | 
			
		||||
            keys = self.args.ihead
 | 
			
		||||
@@ -1423,33 +1434,8 @@ class HttpCli(object):
 | 
			
		||||
        if self.args.no_stack:
 | 
			
		||||
            raise Pebkac(403, "disabled by argv")
 | 
			
		||||
 | 
			
		||||
        threads = {}
 | 
			
		||||
        names = dict([(t.ident, t.name) for t in threading.enumerate()])
 | 
			
		||||
        for tid, stack in sys._current_frames().items():
 | 
			
		||||
            name = "{} ({:x})".format(names.get(tid), tid)
 | 
			
		||||
            threads[name] = stack
 | 
			
		||||
 | 
			
		||||
        rret = []
 | 
			
		||||
        bret = []
 | 
			
		||||
        for name, stack in sorted(threads.items()):
 | 
			
		||||
            ret = ["\n\n# {}".format(name)]
 | 
			
		||||
            pad = None
 | 
			
		||||
            for fn, lno, name, line in traceback.extract_stack(stack):
 | 
			
		||||
                fn = os.sep.join(fn.split(os.sep)[-3:])
 | 
			
		||||
                ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
 | 
			
		||||
                if line:
 | 
			
		||||
                    ret.append("  " + str(line.strip()))
 | 
			
		||||
                    if "self.not_empty.wait()" in line:
 | 
			
		||||
                        pad = " " * 4
 | 
			
		||||
 | 
			
		||||
            if pad:
 | 
			
		||||
                bret += [ret[0]] + [pad + x for x in ret[1:]]
 | 
			
		||||
            else:
 | 
			
		||||
                rret += ret
 | 
			
		||||
 | 
			
		||||
        ret = rret + bret
 | 
			
		||||
        ret = ("<pre>" + "\n".join(ret)).encode("utf-8")
 | 
			
		||||
        self.reply(ret)
 | 
			
		||||
        ret = "<pre>{}\n{}".format(time.time(), alltrace())
 | 
			
		||||
        self.reply(ret.encode("utf-8"))
 | 
			
		||||
 | 
			
		||||
    def tx_tree(self):
 | 
			
		||||
        top = self.uparam["tree"] or ""
 | 
			
		||||
 
 | 
			
		||||
@@ -154,7 +154,8 @@ class ThumbSrv(object):
 | 
			
		||||
        histpath = self.asrv.vfs.histtab[ptop]
 | 
			
		||||
        tpath = thumb_path(histpath, rem, mtime, fmt)
 | 
			
		||||
        abspath = os.path.join(ptop, rem)
 | 
			
		||||
        cond = threading.Condition()
 | 
			
		||||
        cond = threading.Condition(self.mutex)
 | 
			
		||||
        do_conv = False
 | 
			
		||||
        with self.mutex:
 | 
			
		||||
            try:
 | 
			
		||||
                self.busy[tpath].append(cond)
 | 
			
		||||
@@ -172,8 +173,11 @@ class ThumbSrv(object):
 | 
			
		||||
                        f.write(fsenc(os.path.dirname(abspath)))
 | 
			
		||||
 | 
			
		||||
                self.busy[tpath] = [cond]
 | 
			
		||||
                self.q.put([abspath, tpath])
 | 
			
		||||
                self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
 | 
			
		||||
                do_conv = True
 | 
			
		||||
 | 
			
		||||
        if do_conv:
 | 
			
		||||
            self.q.put([abspath, tpath])
 | 
			
		||||
            self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
 | 
			
		||||
 | 
			
		||||
        while not self.stopping:
 | 
			
		||||
            with self.mutex:
 | 
			
		||||
@@ -181,7 +185,7 @@ class ThumbSrv(object):
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            with cond:
 | 
			
		||||
                cond.wait()
 | 
			
		||||
                cond.wait(3)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            st = os.stat(tpath)
 | 
			
		||||
 
 | 
			
		||||
@@ -1022,7 +1022,7 @@ class Up2k(object):
 | 
			
		||||
        now = time.time()
 | 
			
		||||
        job = None
 | 
			
		||||
        with self.mutex:
 | 
			
		||||
            cur = self.cur.get(cj["ptop"], None)
 | 
			
		||||
            cur = self.cur.get(cj["ptop"])
 | 
			
		||||
            reg = self.registry[cj["ptop"]]
 | 
			
		||||
            if cur:
 | 
			
		||||
                if self.no_expr_idx:
 | 
			
		||||
@@ -1180,7 +1180,7 @@ class Up2k(object):
 | 
			
		||||
 | 
			
		||||
    def handle_chunk(self, ptop, wark, chash):
 | 
			
		||||
        with self.mutex:
 | 
			
		||||
            job = self.registry[ptop].get(wark, None)
 | 
			
		||||
            job = self.registry[ptop].get(wark)
 | 
			
		||||
            if not job:
 | 
			
		||||
                known = " ".join([x for x in self.registry[ptop].keys()])
 | 
			
		||||
                self.log("unknown wark [{}], known: {}".format(wark, known))
 | 
			
		||||
@@ -1245,7 +1245,7 @@ class Up2k(object):
 | 
			
		||||
        return ret, dst
 | 
			
		||||
 | 
			
		||||
    def idx_wark(self, ptop, wark, rd, fn, lmod, sz):
 | 
			
		||||
        cur = self.cur.get(ptop, None)
 | 
			
		||||
        cur = self.cur.get(ptop)
 | 
			
		||||
        if not cur:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
@@ -1414,7 +1414,7 @@ class Up2k(object):
 | 
			
		||||
 | 
			
		||||
        newest = max(x["poke"] for _, x in reg.items()) if reg else 0
 | 
			
		||||
        etag = [len(reg), newest]
 | 
			
		||||
        if etag == prev.get(ptop, None):
 | 
			
		||||
        if etag == prev.get(ptop):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
 
 | 
			
		||||
@@ -254,6 +254,34 @@ def trace(*args, **kwargs):
 | 
			
		||||
    nuprint(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def alltrace():
 | 
			
		||||
    threads = {}
 | 
			
		||||
    names = dict([(t.ident, t.name) for t in threading.enumerate()])
 | 
			
		||||
    for tid, stack in sys._current_frames().items():
 | 
			
		||||
        name = "{} ({:x})".format(names.get(tid), tid)
 | 
			
		||||
        threads[name] = stack
 | 
			
		||||
 | 
			
		||||
    rret = []
 | 
			
		||||
    bret = []
 | 
			
		||||
    for name, stack in sorted(threads.items()):
 | 
			
		||||
        ret = ["\n\n# {}".format(name)]
 | 
			
		||||
        pad = None
 | 
			
		||||
        for fn, lno, name, line in traceback.extract_stack(stack):
 | 
			
		||||
            fn = os.sep.join(fn.split(os.sep)[-3:])
 | 
			
		||||
            ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
 | 
			
		||||
            if line:
 | 
			
		||||
                ret.append("  " + str(line.strip()))
 | 
			
		||||
                if "self.not_empty.wait()" in line:
 | 
			
		||||
                    pad = " " * 4
 | 
			
		||||
 | 
			
		||||
        if pad:
 | 
			
		||||
            bret += [ret[0]] + [pad + x for x in ret[1:]]
 | 
			
		||||
        else:
 | 
			
		||||
            rret += ret
 | 
			
		||||
 | 
			
		||||
    return "\n".join(rret + bret)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def min_ex():
 | 
			
		||||
    et, ev, tb = sys.exc_info()
 | 
			
		||||
    tb = traceback.extract_tb(tb, 2)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										583
									
								
								copyparty/web/baguettebox.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										583
									
								
								copyparty/web/baguettebox.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,583 @@
 | 
			
		||||
/*!
 | 
			
		||||
 * baguetteBox.js
 | 
			
		||||
 * @author  feimosi
 | 
			
		||||
 * @version 1.11.1-mod
 | 
			
		||||
 * @url https://github.com/feimosi/baguetteBox.js
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
window.baguetteBox = (function () {
 | 
			
		||||
    'use strict';
 | 
			
		||||
 | 
			
		||||
    var options = {},
 | 
			
		||||
        defaults = {
 | 
			
		||||
            captions: true,
 | 
			
		||||
            buttons: 'auto',
 | 
			
		||||
            noScrollbars: false,
 | 
			
		||||
            bodyClass: 'baguetteBox-open',
 | 
			
		||||
            titleTag: false,
 | 
			
		||||
            async: false,
 | 
			
		||||
            preload: 2,
 | 
			
		||||
            animation: 'slideIn',
 | 
			
		||||
            afterShow: null,
 | 
			
		||||
            afterHide: null,
 | 
			
		||||
            onChange: null,
 | 
			
		||||
        },
 | 
			
		||||
        overlay, slider, previousButton, nextButton, closeButton,
 | 
			
		||||
        currentGallery = [],
 | 
			
		||||
        currentIndex = 0,
 | 
			
		||||
        isOverlayVisible = false,
 | 
			
		||||
        touch = {},  // start-pos
 | 
			
		||||
        touchFlag = false,  // busy
 | 
			
		||||
        regex = /.+\.(gif|jpe?g|png|webp)/i,
 | 
			
		||||
        data = {},  // all galleries
 | 
			
		||||
        imagesElements = [],
 | 
			
		||||
        documentLastFocus = null;
 | 
			
		||||
 | 
			
		||||
    var overlayClickHandler = function (event) {
 | 
			
		||||
        if (event.target.id.indexOf('baguette-img') !== -1) {
 | 
			
		||||
            hideOverlay();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    var touchstartHandler = function (event) {
 | 
			
		||||
        touch.count++;
 | 
			
		||||
        if (touch.count > 1) {
 | 
			
		||||
            touch.multitouch = true;
 | 
			
		||||
        }
 | 
			
		||||
        touch.startX = event.changedTouches[0].pageX;
 | 
			
		||||
        touch.startY = event.changedTouches[0].pageY;
 | 
			
		||||
    };
 | 
			
		||||
    var touchmoveHandler = function (event) {
 | 
			
		||||
        if (touchFlag || touch.multitouch) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
			
		||||
        var touchEvent = event.touches[0] || event.changedTouches[0];
 | 
			
		||||
        if (touchEvent.pageX - touch.startX > 40) {
 | 
			
		||||
            touchFlag = true;
 | 
			
		||||
            showPreviousImage();
 | 
			
		||||
        } else if (touchEvent.pageX - touch.startX < -40) {
 | 
			
		||||
            touchFlag = true;
 | 
			
		||||
            showNextImage();
 | 
			
		||||
        } else if (touch.startY - touchEvent.pageY > 100) {
 | 
			
		||||
            hideOverlay();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    var touchendHandler = function () {
 | 
			
		||||
        touch.count--;
 | 
			
		||||
        if (touch.count <= 0) {
 | 
			
		||||
            touch.multitouch = false;
 | 
			
		||||
        }
 | 
			
		||||
        touchFlag = false;
 | 
			
		||||
    };
 | 
			
		||||
    var contextmenuHandler = function () {
 | 
			
		||||
        touchendHandler();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    var trapFocusInsideOverlay = function (event) {
 | 
			
		||||
        if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(event.target))) {
 | 
			
		||||
            event.stopPropagation();
 | 
			
		||||
            initFocus();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function run(selector, userOptions) {
 | 
			
		||||
        buildOverlay();
 | 
			
		||||
        removeFromCache(selector);
 | 
			
		||||
        return bindImageClickListeners(selector, userOptions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function bindImageClickListeners(selector, userOptions) {
 | 
			
		||||
        var galleryNodeList = document.querySelectorAll(selector);
 | 
			
		||||
        var selectorData = {
 | 
			
		||||
            galleries: [],
 | 
			
		||||
            nodeList: galleryNodeList
 | 
			
		||||
        };
 | 
			
		||||
        data[selector] = selectorData;
 | 
			
		||||
 | 
			
		||||
        [].forEach.call(galleryNodeList, function (galleryElement) {
 | 
			
		||||
            if (userOptions && userOptions.filter) {
 | 
			
		||||
                regex = userOptions.filter;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var tagsNodeList = [];
 | 
			
		||||
            if (galleryElement.tagName === 'A') {
 | 
			
		||||
                tagsNodeList = [galleryElement];
 | 
			
		||||
            } else {
 | 
			
		||||
                tagsNodeList = galleryElement.getElementsByTagName('a');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            tagsNodeList = [].filter.call(tagsNodeList, function (element) {
 | 
			
		||||
                if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {
 | 
			
		||||
                    return regex.test(element.href);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (tagsNodeList.length === 0) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var gallery = [];
 | 
			
		||||
            [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
 | 
			
		||||
                var imageElementClickHandler = function (event) {
 | 
			
		||||
                    if (event && event.ctrlKey)
 | 
			
		||||
                        return true;
 | 
			
		||||
 | 
			
		||||
                    event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
			
		||||
                    prepareOverlay(gallery, userOptions);
 | 
			
		||||
                    showOverlay(imageIndex);
 | 
			
		||||
                };
 | 
			
		||||
                var imageItem = {
 | 
			
		||||
                    eventHandler: imageElementClickHandler,
 | 
			
		||||
                    imageElement: imageElement
 | 
			
		||||
                };
 | 
			
		||||
                bind(imageElement, 'click', imageElementClickHandler);
 | 
			
		||||
                gallery.push(imageItem);
 | 
			
		||||
            });
 | 
			
		||||
            selectorData.galleries.push(gallery);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return selectorData.galleries;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clearCachedData() {
 | 
			
		||||
        for (var selector in data) {
 | 
			
		||||
            if (data.hasOwnProperty(selector)) {
 | 
			
		||||
                removeFromCache(selector);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeFromCache(selector) {
 | 
			
		||||
        if (!data.hasOwnProperty(selector)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        var galleries = data[selector].galleries;
 | 
			
		||||
        [].forEach.call(galleries, function (gallery) {
 | 
			
		||||
            [].forEach.call(gallery, function (imageItem) {
 | 
			
		||||
                unbind(imageItem.imageElement, 'click', imageItem.eventHandler);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (currentGallery === gallery) {
 | 
			
		||||
                currentGallery = [];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        delete data[selector];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function buildOverlay() {
 | 
			
		||||
        overlay = ebi('baguetteBox-overlay');
 | 
			
		||||
        if (overlay) {
 | 
			
		||||
            slider = ebi('baguetteBox-slider');
 | 
			
		||||
            previousButton = ebi('previous-button');
 | 
			
		||||
            nextButton = ebi('next-button');
 | 
			
		||||
            closeButton = ebi('close-button');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        overlay = mknod('div');
 | 
			
		||||
        overlay.setAttribute('role', 'dialog');
 | 
			
		||||
        overlay.id = 'baguetteBox-overlay';
 | 
			
		||||
        document.getElementsByTagName('body')[0].appendChild(overlay);
 | 
			
		||||
 | 
			
		||||
        slider = mknod('div');
 | 
			
		||||
        slider.id = 'baguetteBox-slider';
 | 
			
		||||
        overlay.appendChild(slider);
 | 
			
		||||
 | 
			
		||||
        previousButton = mknod('button');
 | 
			
		||||
        previousButton.setAttribute('type', 'button');
 | 
			
		||||
        previousButton.id = 'previous-button';
 | 
			
		||||
        previousButton.setAttribute('aria-label', 'Previous');
 | 
			
		||||
        previousButton.innerHTML = '<';
 | 
			
		||||
        overlay.appendChild(previousButton);
 | 
			
		||||
 | 
			
		||||
        nextButton = mknod('button');
 | 
			
		||||
        nextButton.setAttribute('type', 'button');
 | 
			
		||||
        nextButton.id = 'next-button';
 | 
			
		||||
        nextButton.setAttribute('aria-label', 'Next');
 | 
			
		||||
        nextButton.innerHTML = '>';
 | 
			
		||||
        overlay.appendChild(nextButton);
 | 
			
		||||
 | 
			
		||||
        closeButton = mknod('button');
 | 
			
		||||
        closeButton.setAttribute('type', 'button');
 | 
			
		||||
        closeButton.id = 'close-button';
 | 
			
		||||
        closeButton.setAttribute('aria-label', 'Close');
 | 
			
		||||
        closeButton.innerHTML = '×';
 | 
			
		||||
        overlay.appendChild(closeButton);
 | 
			
		||||
 | 
			
		||||
        previousButton.className = nextButton.className = closeButton.className = 'baguetteBox-button';
 | 
			
		||||
 | 
			
		||||
        bindEvents();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function keyDownHandler(event) {
 | 
			
		||||
        switch (event.keyCode) {
 | 
			
		||||
            case 37: // Left
 | 
			
		||||
                showPreviousImage();
 | 
			
		||||
                break;
 | 
			
		||||
            case 39: // Right
 | 
			
		||||
                showNextImage();
 | 
			
		||||
                break;
 | 
			
		||||
            case 27: // Esc
 | 
			
		||||
                hideOverlay();
 | 
			
		||||
                break;
 | 
			
		||||
            case 36: // Home
 | 
			
		||||
                showFirstImage(event);
 | 
			
		||||
                break;
 | 
			
		||||
            case 35: // End
 | 
			
		||||
                showLastImage(event);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var passiveSupp = false;
 | 
			
		||||
    try {
 | 
			
		||||
        var opts = {
 | 
			
		||||
            get passive() {
 | 
			
		||||
                passiveSupp = true;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        window.addEventListener('test', null, opts);
 | 
			
		||||
        window.removeEventListener('test', null, opts);
 | 
			
		||||
    }
 | 
			
		||||
    catch (ex) {
 | 
			
		||||
        passiveSupp = false;
 | 
			
		||||
    }
 | 
			
		||||
    var passiveEvent = passiveSupp ? { passive: false } : null;
 | 
			
		||||
    var nonPassiveEvent = passiveSupp ? { passive: true } : null;
 | 
			
		||||
 | 
			
		||||
    function bindEvents() {
 | 
			
		||||
        bind(overlay, 'click', overlayClickHandler);
 | 
			
		||||
        bind(previousButton, 'click', showPreviousImage);
 | 
			
		||||
        bind(nextButton, 'click', showNextImage);
 | 
			
		||||
        bind(closeButton, 'click', hideOverlay);
 | 
			
		||||
        bind(slider, 'contextmenu', contextmenuHandler);
 | 
			
		||||
        bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
			
		||||
        bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
			
		||||
        bind(overlay, 'touchend', touchendHandler);
 | 
			
		||||
        bind(document, 'focus', trapFocusInsideOverlay, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function unbindEvents() {
 | 
			
		||||
        unbind(overlay, 'click', overlayClickHandler);
 | 
			
		||||
        unbind(previousButton, 'click', showPreviousImage);
 | 
			
		||||
        unbind(nextButton, 'click', showNextImage);
 | 
			
		||||
        unbind(closeButton, 'click', hideOverlay);
 | 
			
		||||
        unbind(slider, 'contextmenu', contextmenuHandler);
 | 
			
		||||
        unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
 | 
			
		||||
        unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
 | 
			
		||||
        unbind(overlay, 'touchend', touchendHandler);
 | 
			
		||||
        unbind(document, 'focus', trapFocusInsideOverlay, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function prepareOverlay(gallery, userOptions) {
 | 
			
		||||
        if (currentGallery === gallery) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        currentGallery = gallery;
 | 
			
		||||
        setOptions(userOptions);
 | 
			
		||||
        slider.innerHTML = '';
 | 
			
		||||
        imagesElements.length = 0;
 | 
			
		||||
 | 
			
		||||
        var imagesFiguresIds = [];
 | 
			
		||||
        var imagesCaptionsIds = [];
 | 
			
		||||
        for (var i = 0, fullImage; i < gallery.length; i++) {
 | 
			
		||||
            fullImage = mknod('div');
 | 
			
		||||
            fullImage.className = 'full-image';
 | 
			
		||||
            fullImage.id = 'baguette-img-' + i;
 | 
			
		||||
            imagesElements.push(fullImage);
 | 
			
		||||
 | 
			
		||||
            imagesFiguresIds.push('baguetteBox-figure-' + i);
 | 
			
		||||
            imagesCaptionsIds.push('baguetteBox-figcaption-' + i);
 | 
			
		||||
            slider.appendChild(imagesElements[i]);
 | 
			
		||||
        }
 | 
			
		||||
        overlay.setAttribute('aria-labelledby', imagesFiguresIds.join(' '));
 | 
			
		||||
        overlay.setAttribute('aria-describedby', imagesCaptionsIds.join(' '));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setOptions(newOptions) {
 | 
			
		||||
        if (!newOptions) {
 | 
			
		||||
            newOptions = {};
 | 
			
		||||
        }
 | 
			
		||||
        for (var item in defaults) {
 | 
			
		||||
            options[item] = defaults[item];
 | 
			
		||||
            if (typeof newOptions[item] !== 'undefined') {
 | 
			
		||||
                options[item] = newOptions[item];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
 | 
			
		||||
            options.animation === 'slideIn' ? '' : 'none');
 | 
			
		||||
 | 
			
		||||
        if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) {
 | 
			
		||||
            options.buttons = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        previousButton.style.display = nextButton.style.display = (options.buttons ? '' : 'none');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showOverlay(chosenImageIndex) {
 | 
			
		||||
        if (options.noScrollbars) {
 | 
			
		||||
            document.documentElement.style.overflowY = 'hidden';
 | 
			
		||||
            document.body.style.overflowY = 'scroll';
 | 
			
		||||
        }
 | 
			
		||||
        if (overlay.style.display === 'block') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bind(document, 'keydown', keyDownHandler);
 | 
			
		||||
        currentIndex = chosenImageIndex;
 | 
			
		||||
        touch = {
 | 
			
		||||
            count: 0,
 | 
			
		||||
            startX: null,
 | 
			
		||||
            startY: null
 | 
			
		||||
        };
 | 
			
		||||
        loadImage(currentIndex, function () {
 | 
			
		||||
            preloadNext(currentIndex);
 | 
			
		||||
            preloadPrev(currentIndex);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        updateOffset();
 | 
			
		||||
        overlay.style.display = 'block';
 | 
			
		||||
        // Fade in overlay
 | 
			
		||||
        setTimeout(function () {
 | 
			
		||||
            overlay.className = 'visible';
 | 
			
		||||
            if (options.bodyClass && document.body.classList) {
 | 
			
		||||
                document.body.classList.add(options.bodyClass);
 | 
			
		||||
            }
 | 
			
		||||
            if (options.afterShow) {
 | 
			
		||||
                options.afterShow();
 | 
			
		||||
            }
 | 
			
		||||
        }, 50);
 | 
			
		||||
        if (options.onChange) {
 | 
			
		||||
            options.onChange(currentIndex, imagesElements.length);
 | 
			
		||||
        }
 | 
			
		||||
        documentLastFocus = document.activeElement;
 | 
			
		||||
        initFocus();
 | 
			
		||||
        isOverlayVisible = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function initFocus() {
 | 
			
		||||
        if (options.buttons) {
 | 
			
		||||
            previousButton.focus();
 | 
			
		||||
        } else {
 | 
			
		||||
            closeButton.focus();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function hideOverlay(e) {
 | 
			
		||||
        ev(e);
 | 
			
		||||
        if (options.noScrollbars) {
 | 
			
		||||
            document.documentElement.style.overflowY = 'auto';
 | 
			
		||||
            document.body.style.overflowY = 'auto';
 | 
			
		||||
        }
 | 
			
		||||
        if (overlay.style.display === 'none') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        unbind(document, 'keydown', keyDownHandler);
 | 
			
		||||
        // Fade out and hide the overlay
 | 
			
		||||
        overlay.className = '';
 | 
			
		||||
        setTimeout(function () {
 | 
			
		||||
            overlay.style.display = 'none';
 | 
			
		||||
            if (options.bodyClass && document.body.classList) {
 | 
			
		||||
                document.body.classList.remove(options.bodyClass);
 | 
			
		||||
            }
 | 
			
		||||
            if (options.afterHide) {
 | 
			
		||||
                options.afterHide();
 | 
			
		||||
            }
 | 
			
		||||
            documentLastFocus && documentLastFocus.focus();
 | 
			
		||||
            isOverlayVisible = false;
 | 
			
		||||
        }, 500);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function loadImage(index, callback) {
 | 
			
		||||
        var imageContainer = imagesElements[index];
 | 
			
		||||
        var galleryItem = currentGallery[index];
 | 
			
		||||
 | 
			
		||||
        if (typeof imageContainer === 'undefined' || typeof galleryItem === 'undefined') {
 | 
			
		||||
            return;  // out-of-bounds or gallery dirty
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (imageContainer.getElementsByTagName('img')[0]) {
 | 
			
		||||
            // image is loaded, cb and bail
 | 
			
		||||
            if (callback) {
 | 
			
		||||
                callback();
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var imageElement = galleryItem.imageElement,
 | 
			
		||||
            imageSrc = imageElement.href,
 | 
			
		||||
            thumbnailElement = imageElement.getElementsByTagName('img')[0],
 | 
			
		||||
            imageCaption = typeof options.captions === 'function' ?
 | 
			
		||||
                options.captions.call(currentGallery, imageElement) :
 | 
			
		||||
                imageElement.getAttribute('data-caption') || imageElement.title;
 | 
			
		||||
 | 
			
		||||
        var figure = mknod('figure');
 | 
			
		||||
        figure.id = 'baguetteBox-figure-' + index;
 | 
			
		||||
        figure.innerHTML = '<div class="baguetteBox-spinner">' +
 | 
			
		||||
            '<div class="baguetteBox-double-bounce1"></div>' +
 | 
			
		||||
            '<div class="baguetteBox-double-bounce2"></div>' +
 | 
			
		||||
            '</div>';
 | 
			
		||||
 | 
			
		||||
        if (options.captions && imageCaption) {
 | 
			
		||||
            var figcaption = mknod('figcaption');
 | 
			
		||||
            figcaption.id = 'baguetteBox-figcaption-' + index;
 | 
			
		||||
            figcaption.innerHTML = imageCaption;
 | 
			
		||||
            figure.appendChild(figcaption);
 | 
			
		||||
        }
 | 
			
		||||
        imageContainer.appendChild(figure);
 | 
			
		||||
 | 
			
		||||
        var image = mknod('img');
 | 
			
		||||
        image.onload = function () {
 | 
			
		||||
            // Remove loader element
 | 
			
		||||
            var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');
 | 
			
		||||
            figure.removeChild(spinner);
 | 
			
		||||
            if (!options.async && callback) {
 | 
			
		||||
                callback();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        image.setAttribute('src', imageSrc);
 | 
			
		||||
        image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
 | 
			
		||||
        if (options.titleTag && imageCaption) {
 | 
			
		||||
            image.title = imageCaption;
 | 
			
		||||
        }
 | 
			
		||||
        figure.appendChild(image);
 | 
			
		||||
 | 
			
		||||
        if (options.async && callback) {
 | 
			
		||||
            callback();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showNextImage(e) {
 | 
			
		||||
        ev(e);
 | 
			
		||||
        return show(currentIndex + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showPreviousImage(e) {
 | 
			
		||||
        ev(e);
 | 
			
		||||
        return show(currentIndex - 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showFirstImage(event) {
 | 
			
		||||
        if (event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
        }
 | 
			
		||||
        return show(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showLastImage(event) {
 | 
			
		||||
        if (event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
        }
 | 
			
		||||
        return show(currentGallery.length - 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Move the gallery to a specific index
 | 
			
		||||
     * @param `index` {number} - the position of the image
 | 
			
		||||
     * @param `gallery` {array} - gallery which should be opened, if omitted assumes the currently opened one
 | 
			
		||||
     * @return {boolean} - true on success or false if the index is invalid
 | 
			
		||||
     */
 | 
			
		||||
    function show(index, gallery) {
 | 
			
		||||
        if (!isOverlayVisible && index >= 0 && index < gallery.length) {
 | 
			
		||||
            prepareOverlay(gallery, options);
 | 
			
		||||
            showOverlay(index);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (index < 0) {
 | 
			
		||||
            if (options.animation) {
 | 
			
		||||
                bounceAnimation('left');
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (index >= imagesElements.length) {
 | 
			
		||||
            if (options.animation) {
 | 
			
		||||
                bounceAnimation('right');
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        currentIndex = index;
 | 
			
		||||
        loadImage(currentIndex, function () {
 | 
			
		||||
            preloadNext(currentIndex);
 | 
			
		||||
            preloadPrev(currentIndex);
 | 
			
		||||
        });
 | 
			
		||||
        updateOffset();
 | 
			
		||||
 | 
			
		||||
        if (options.onChange) {
 | 
			
		||||
            options.onChange(currentIndex, imagesElements.length);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Triggers the bounce animation
 | 
			
		||||
     * @param {('left'|'right')} direction - Direction of the movement
 | 
			
		||||
     */
 | 
			
		||||
    function bounceAnimation(direction) {
 | 
			
		||||
        slider.className = 'bounce-from-' + direction;
 | 
			
		||||
        setTimeout(function () {
 | 
			
		||||
            slider.className = '';
 | 
			
		||||
        }, 400);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function updateOffset() {
 | 
			
		||||
        var offset = -currentIndex * 100 + '%';
 | 
			
		||||
        if (options.animation === 'fadeIn') {
 | 
			
		||||
            slider.style.opacity = 0;
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                slider.style.transform = 'translate3d(' + offset + ',0,0)';
 | 
			
		||||
                slider.style.opacity = 1;
 | 
			
		||||
            }, 400);
 | 
			
		||||
        } else {
 | 
			
		||||
            slider.style.transform = 'translate3d(' + offset + ',0,0)';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preloadNext(index) {
 | 
			
		||||
        if (index - currentIndex >= options.preload) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        loadImage(index + 1, function () {
 | 
			
		||||
            preloadNext(index + 1);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preloadPrev(index) {
 | 
			
		||||
        if (currentIndex - index >= options.preload) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        loadImage(index - 1, function () {
 | 
			
		||||
            preloadPrev(index - 1);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function bind(element, event, callback, options) {
 | 
			
		||||
        element.addEventListener(event, callback, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function unbind(element, event, callback, options) {
 | 
			
		||||
        element.removeEventListener(event, callback, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function destroyPlugin() {
 | 
			
		||||
        unbindEvents();
 | 
			
		||||
        clearCachedData();
 | 
			
		||||
        unbind(document, 'keydown', keyDownHandler);
 | 
			
		||||
        document.getElementsByTagName('body')[0].removeChild(ebi('baguetteBox-overlay'));
 | 
			
		||||
        data = {};
 | 
			
		||||
        currentGallery = [];
 | 
			
		||||
        currentIndex = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        run: run,
 | 
			
		||||
        show: show,
 | 
			
		||||
        showNext: showNextImage,
 | 
			
		||||
        showPrevious: showPreviousImage,
 | 
			
		||||
        hide: hideOverlay,
 | 
			
		||||
        destroy: destroyPlugin
 | 
			
		||||
    };
 | 
			
		||||
})();
 | 
			
		||||
@@ -497,6 +497,27 @@ input[type="checkbox"]+label {
 | 
			
		||||
input[type="checkbox"]:checked+label {
 | 
			
		||||
	color: #fc5;
 | 
			
		||||
}
 | 
			
		||||
input.eq_gain {
 | 
			
		||||
	width: 3em;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	margin: 0 .6em;
 | 
			
		||||
}
 | 
			
		||||
#audio_eq table {
 | 
			
		||||
	border-collapse: collapse;
 | 
			
		||||
}
 | 
			
		||||
#audio_eq td {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
#audio_eq a.eq_step {
 | 
			
		||||
	font-size: 1.5em;
 | 
			
		||||
	display: block;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
}
 | 
			
		||||
#au_eq {
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin-top: .5em;
 | 
			
		||||
	padding: 1.3em .3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -563,6 +584,7 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
}
 | 
			
		||||
#wrap {
 | 
			
		||||
	margin-top: 2em;
 | 
			
		||||
	min-height: 90vh;
 | 
			
		||||
}
 | 
			
		||||
#tree {
 | 
			
		||||
	display: none;
 | 
			
		||||
@@ -575,6 +597,12 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
	overscroll-behavior-y: none;
 | 
			
		||||
	scrollbar-color: #eb0 #333;
 | 
			
		||||
}
 | 
			
		||||
#treeh {
 | 
			
		||||
	background: #333;
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	z-index: 1;
 | 
			
		||||
	top: 0;
 | 
			
		||||
}
 | 
			
		||||
#thx_ff {
 | 
			
		||||
	padding: 5em 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -600,6 +628,7 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
	box-shadow: 0 .1em .2em #222 inset;
 | 
			
		||||
	border-radius: .3em;
 | 
			
		||||
	margin: .2em;
 | 
			
		||||
	white-space: pre;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	top: -.2em;
 | 
			
		||||
}
 | 
			
		||||
@@ -667,34 +696,20 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
	font-size: 2em;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
#files th:hover .cfg,
 | 
			
		||||
#files th.min .cfg {
 | 
			
		||||
#files th:hover .cfg {
 | 
			
		||||
	display: block;
 | 
			
		||||
	width: 1em;
 | 
			
		||||
	border-radius: .2em;
 | 
			
		||||
	margin: -1.3em auto 0 auto;
 | 
			
		||||
	background: #444;
 | 
			
		||||
}
 | 
			
		||||
#files th.min .cfg {
 | 
			
		||||
	margin: -.6em;
 | 
			
		||||
}
 | 
			
		||||
#files>thead>tr>th.min span {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	transform: rotate(270deg);
 | 
			
		||||
	background: linear-gradient(90deg, rgba(68,68,68,0), rgba(68,68,68,0.5) 70%, #444);
 | 
			
		||||
	margin-left: -4.6em;
 | 
			
		||||
	padding: .4em;
 | 
			
		||||
	top: 5.4em;
 | 
			
		||||
	width: 8em;
 | 
			
		||||
	text-align: right;
 | 
			
		||||
	letter-spacing: .04em;
 | 
			
		||||
#files>thead>tr>th.min,
 | 
			
		||||
#files td.min {
 | 
			
		||||
		display: none;
 | 
			
		||||
}
 | 
			
		||||
#files td:nth-child(2n) {
 | 
			
		||||
	color: #f5a;
 | 
			
		||||
}
 | 
			
		||||
#files td.min a {
 | 
			
		||||
	display: none;
 | 
			
		||||
}
 | 
			
		||||
#files tr.play td,
 | 
			
		||||
#files tr.play div a {
 | 
			
		||||
	background: #fc4;
 | 
			
		||||
@@ -709,18 +724,18 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
	color: #300;
 | 
			
		||||
	background: #fea;
 | 
			
		||||
}
 | 
			
		||||
#op_cfg {
 | 
			
		||||
.opwide {
 | 
			
		||||
	max-width: none;
 | 
			
		||||
	margin-right: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
#op_cfg>div>a {
 | 
			
		||||
.opwide>div>a {
 | 
			
		||||
	line-height: 2em;
 | 
			
		||||
}
 | 
			
		||||
#op_cfg>div>span {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	padding: .2em .4em;
 | 
			
		||||
}
 | 
			
		||||
#op_cfg h3 {
 | 
			
		||||
.opbox h3 {
 | 
			
		||||
	margin: .8em 0 0 .6em;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	border-bottom: 1px solid #555;
 | 
			
		||||
@@ -750,9 +765,12 @@ input[type="checkbox"]:checked+label {
 | 
			
		||||
	font-family: monospace, monospace;
 | 
			
		||||
	line-height: 2em;
 | 
			
		||||
}
 | 
			
		||||
#griden.on+#thumbs {
 | 
			
		||||
#thumbs {
 | 
			
		||||
	opacity: .3;
 | 
			
		||||
}
 | 
			
		||||
#griden.on+#thumbs {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
#ghead {
 | 
			
		||||
	background: #3c3c3c;
 | 
			
		||||
	border: 1px solid #444;
 | 
			
		||||
@@ -801,8 +819,7 @@ html.light #ghead {
 | 
			
		||||
	content: '📂';
 | 
			
		||||
	line-height: 0;
 | 
			
		||||
	font-size: 2em;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	margin: -.7em 0 -.5em -.3em;
 | 
			
		||||
	margin: -.7em .1em -.5em -.3em;
 | 
			
		||||
}
 | 
			
		||||
#ggrid a:hover {
 | 
			
		||||
	background: #444;
 | 
			
		||||
@@ -942,13 +959,9 @@ html.light tr.play td {
 | 
			
		||||
html.light tr.play a {
 | 
			
		||||
	color: #406;
 | 
			
		||||
}
 | 
			
		||||
html.light #files th:hover .cfg,
 | 
			
		||||
html.light #files th.min .cfg {
 | 
			
		||||
html.light #files th:hover .cfg {
 | 
			
		||||
	background: #ccc;
 | 
			
		||||
}
 | 
			
		||||
html.light #files > thead > tr > th.min span {
 | 
			
		||||
	background: linear-gradient(90deg, rgba(204,204,204,0), rgba(204,204,204,0.5) 70%, #ccc);
 | 
			
		||||
}
 | 
			
		||||
html.light #blocked {
 | 
			
		||||
	background: #eee;
 | 
			
		||||
}
 | 
			
		||||
@@ -1019,6 +1032,9 @@ html.light #files tr.sel a:hover {
 | 
			
		||||
	color: #000;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
}
 | 
			
		||||
html.light #treeh {
 | 
			
		||||
	background: #eee;
 | 
			
		||||
}
 | 
			
		||||
html.light #tree {
 | 
			
		||||
	scrollbar-color: #a70 #ddd;
 | 
			
		||||
}
 | 
			
		||||
@@ -1028,4 +1044,161 @@ html.light #tree::-webkit-scrollbar {
 | 
			
		||||
}
 | 
			
		||||
#tree::-webkit-scrollbar-thumb {
 | 
			
		||||
	background: #da0;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#baguetteBox-overlay {
 | 
			
		||||
	display: none;
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	position: fixed;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	z-index: 1000000;
 | 
			
		||||
	background: rgba(0, 0, 0, 0.8);
 | 
			
		||||
	transition: opacity .3s ease;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay.visible {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay .full-image {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay .full-image figure {
 | 
			
		||||
	display: inline;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay .full-image img {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	width: auto;
 | 
			
		||||
	height: auto;
 | 
			
		||||
	max-height: 100%;
 | 
			
		||||
	max-width: 100%;
 | 
			
		||||
	vertical-align: middle;
 | 
			
		||||
	box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay .full-image figcaption {
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	line-height: 1.8;
 | 
			
		||||
	white-space: normal;
 | 
			
		||||
	color: #ccc;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay figcaption a {
 | 
			
		||||
	background: rgba(0, 0, 0, 0.6);
 | 
			
		||||
	border-radius: .4em;
 | 
			
		||||
	padding: .3em .6em;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-overlay .full-image:before {
 | 
			
		||||
	content: "";
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	height: 50%;
 | 
			
		||||
	width: 1px;
 | 
			
		||||
	margin-right: -1px;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-slider {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	transition: left .2s ease, transform .2s ease;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-slider.bounce-from-right {
 | 
			
		||||
	animation: bounceFromRight .4s ease-out;
 | 
			
		||||
}
 | 
			
		||||
#baguetteBox-slider.bounce-from-left {
 | 
			
		||||
	animation: bounceFromLeft .4s ease-out;
 | 
			
		||||
}
 | 
			
		||||
@keyframes bounceFromRight {
 | 
			
		||||
	0% {margin-left: 0}
 | 
			
		||||
	50% {margin-left: -30px}
 | 
			
		||||
	100% {margin-left: 0}
 | 
			
		||||
}
 | 
			
		||||
@keyframes bounceFromLeft {
 | 
			
		||||
	0% {margin-left: 0}
 | 
			
		||||
	50% {margin-left: 30px}
 | 
			
		||||
	100% {margin-left: 0}
 | 
			
		||||
}
 | 
			
		||||
.baguetteBox-button#next-button,
 | 
			
		||||
.baguetteBox-button#previous-button {
 | 
			
		||||
	top: 50%;
 | 
			
		||||
	top: calc(50% - 30px);
 | 
			
		||||
	width: 44px;
 | 
			
		||||
	height: 60px;
 | 
			
		||||
} 
 | 
			
		||||
.baguetteBox-button {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	outline: none;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	border: 0;
 | 
			
		||||
	border-radius: 15%;
 | 
			
		||||
	background: rgba(50, 50, 50, 0.5);
 | 
			
		||||
	color: #ddd;
 | 
			
		||||
	font: 1.6em sans-serif;
 | 
			
		||||
	transition: background-color .3s ease;
 | 
			
		||||
}
 | 
			
		||||
.baguetteBox-button:focus,
 | 
			
		||||
.baguetteBox-button:hover {
 | 
			
		||||
	background: rgba(50, 50, 50, 0.9);
 | 
			
		||||
}
 | 
			
		||||
#next-button {
 | 
			
		||||
	right: 2%;
 | 
			
		||||
}
 | 
			
		||||
#previous-button {
 | 
			
		||||
	left: 2%;
 | 
			
		||||
}
 | 
			
		||||
#close-button {
 | 
			
		||||
	top: 20px;
 | 
			
		||||
	right: 2%;
 | 
			
		||||
	width: 30px;
 | 
			
		||||
	height: 30px;
 | 
			
		||||
}
 | 
			
		||||
.baguetteBox-button svg {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
}
 | 
			
		||||
.baguetteBox-spinner {
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 50%;
 | 
			
		||||
	left: 50%;
 | 
			
		||||
	margin-top: -20px;
 | 
			
		||||
	margin-left: -20px;
 | 
			
		||||
}
 | 
			
		||||
.baguetteBox-double-bounce1,
 | 
			
		||||
.baguetteBox-double-bounce2 {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	border-radius: 50%;
 | 
			
		||||
	background-color: #fff;
 | 
			
		||||
	opacity: .6;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	animation: bounce 2s infinite ease-in-out;
 | 
			
		||||
}
 | 
			
		||||
.baguetteBox-double-bounce2 {
 | 
			
		||||
	animation-delay: -1s;
 | 
			
		||||
}
 | 
			
		||||
@keyframes bounce {
 | 
			
		||||
	0%, 100% {transform: scale(0)}
 | 
			
		||||
	50% {transform: scale(1)}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
        <a href="#" data-perm="write" data-dest="mkdir" data-desc="mkdir: create a new directory">📂</a>
 | 
			
		||||
        <a href="#" data-perm="read 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="player" data-desc="media player options">🎺</a>
 | 
			
		||||
        <a href="#" data-dest="cfg" data-desc="configuration options">⚙️</a>
 | 
			
		||||
        <div id="opdesc"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -39,22 +40,23 @@
 | 
			
		||||
        <div id="srch_q"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div id="op_player" class="opview opbox opwide"></div>
 | 
			
		||||
 | 
			
		||||
    {%- include 'upload.html' %}
 | 
			
		||||
 | 
			
		||||
    <div id="op_cfg" class="opview opbox">
 | 
			
		||||
    <div id="op_cfg" class="opview opbox opwide">
 | 
			
		||||
        <h3>switches</h3>
 | 
			
		||||
        <div>
 | 
			
		||||
            <a id="tooltips" class="tgl btn" href="#">tooltips</a>
 | 
			
		||||
            <a id="lightmode" class="tgl btn" href="#">lightmode</a>
 | 
			
		||||
            <a id="griden" class="tgl btn" href="#">the grid</a>
 | 
			
		||||
            <a id="thumbs" class="tgl btn" href="#">thumbs</a>
 | 
			
		||||
            <a id="tooltips" class="tgl btn" href="#">ℹ️ tooltips</a>
 | 
			
		||||
            <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>
 | 
			
		||||
            <a id="griden" class="tgl btn" href="#">田 the grid</a>
 | 
			
		||||
            <a id="thumbs" class="tgl btn" href="#">🖼️ thumbs</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        {%- if have_zip %}
 | 
			
		||||
        <h3>folder download</h3>
 | 
			
		||||
        <div id="arc_fmt"></div>
 | 
			
		||||
        <h3>folder download</h3><div id="arc_fmt"></div>
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
        <h3>key notation</h3>
 | 
			
		||||
        <div id="key_notation"></div>
 | 
			
		||||
        <h3>key notation</h3><div id="key_notation"></div>
 | 
			
		||||
        <h3>hidden columns</h3><div id="hcols"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <h1 id="path">
 | 
			
		||||
@@ -65,10 +67,12 @@
 | 
			
		||||
    </h1>
 | 
			
		||||
    
 | 
			
		||||
    <div id="tree">
 | 
			
		||||
        <a href="#" id="detree">🍞...</a>
 | 
			
		||||
        <a href="#" class="btn" step="2" id="twobytwo">+</a>
 | 
			
		||||
        <a href="#" class="btn" step="-2" id="twig">–</a>
 | 
			
		||||
        <a href="#" class="tgl btn" id="dyntree">a</a>
 | 
			
		||||
        <div id="treeh">
 | 
			
		||||
            <a href="#" id="detree">🍞...</a>
 | 
			
		||||
            <a href="#" class="btn" step="2" id="twobytwo">+</a>
 | 
			
		||||
            <a href="#" class="btn" step="-2" id="twig">–</a>
 | 
			
		||||
            <a href="#" class="tgl btn" id="dyntree">a</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <ul id="treeul"></ul>
 | 
			
		||||
        <div id="thx_ff"> </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,39 @@ var have_webp = null;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var mpl = (function () {
 | 
			
		||||
	ebi('op_player').innerHTML = (
 | 
			
		||||
		'<h3>audio equalizer</h3><div id="audio_eq"></div>' +
 | 
			
		||||
 | 
			
		||||
		'<h3>playback mode</h3><div id="pb_mode">' +
 | 
			
		||||
		'<a href="#" class="tgl btn">🔁 loop-folder</a>' +
 | 
			
		||||
		'<a href="#" class="tgl btn">📂 next-folder</a>' +
 | 
			
		||||
		'</div>');
 | 
			
		||||
 | 
			
		||||
	var r = {
 | 
			
		||||
		"pb_mode": sread('pb_mode') || 'loop-folder'
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	function draw_pb_mode() {
 | 
			
		||||
		var btns = QSA('#pb_mode>a');
 | 
			
		||||
		for (var a = 0, aa = btns.length; a < aa; a++) {
 | 
			
		||||
			clmod(btns[a], 'on', btns[a].textContent.indexOf(r.pb_mode) != -1);
 | 
			
		||||
			btns[a].onclick = set_pb_mode;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	draw_pb_mode();
 | 
			
		||||
 | 
			
		||||
	function set_pb_mode(e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		r.pb_mode = this.textContent.split(' ').slice(-1)[0];
 | 
			
		||||
		swrite('pb_mode', r.pb_mode);
 | 
			
		||||
		draw_pb_mode();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// extract songs + add play column
 | 
			
		||||
function MPlayer() {
 | 
			
		||||
	this.id = Date.now();
 | 
			
		||||
@@ -162,8 +195,9 @@ var widget = (function () {
 | 
			
		||||
			m = ck + 'np: ';
 | 
			
		||||
 | 
			
		||||
		for (var a = 1, aa = th.length; a < aa; a++) {
 | 
			
		||||
			var tk = a == 1 ? '' : th[a].getAttribute('name').split('/').slice(-1)[0];
 | 
			
		||||
			var tv = tr[a].getAttribute('html') || tr[a].textContent;
 | 
			
		||||
			var tv = tr[a].textContent,
 | 
			
		||||
				tk = a == 1 ? '' : th[a].getAttribute('name').split('/').slice(-1)[0];
 | 
			
		||||
 | 
			
		||||
			m += tk + '(' + cv + tv + ck + ') // ';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -407,7 +441,7 @@ function song_skip(n) {
 | 
			
		||||
	if (tid !== null)
 | 
			
		||||
		play(mp.order.indexOf(tid) + n);
 | 
			
		||||
	else
 | 
			
		||||
		play(mp.order[0]);
 | 
			
		||||
		play(mp.order[n == -1 ? mp.order.length - 1 : 0]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -508,6 +542,194 @@ try {
 | 
			
		||||
catch (ex) { }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var audio_eq = (function () {
 | 
			
		||||
	var r = {
 | 
			
		||||
		"en": false,
 | 
			
		||||
		"bands": [31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
 | 
			
		||||
		"gains": [4, 3, 2, 1, 0, 0, 1, 2, 3, 4],
 | 
			
		||||
		"filters": [],
 | 
			
		||||
		"last_au": null
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	var cfg = [ // hz, q, g
 | 
			
		||||
		[31.25 * 0.88, 0, 1.4],  // shelf
 | 
			
		||||
		[31.25 * 1.04, 0.7, 0.96],  // peak
 | 
			
		||||
		[62.5, 0.7, 1],
 | 
			
		||||
		[125, 0.8, 1],
 | 
			
		||||
		[250, 0.9, 1.03],
 | 
			
		||||
		[500, 0.9, 1.1],
 | 
			
		||||
		[1000, 0.9, 1.1],
 | 
			
		||||
		[2000, 0.9, 1.105],
 | 
			
		||||
		[4000, 0.88, 1.05],
 | 
			
		||||
		[8000 * 1.006, 0.73, 1.24],
 | 
			
		||||
		[16000 * 0.89, 0.7, 1.26],  // peak
 | 
			
		||||
		[16000 * 1.13, 0.82, 1.09],  // peak
 | 
			
		||||
		[16000 * 1.205, 0, 1.9]  // shelf
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		var gains = jread('au_eq_gain', r.gains);
 | 
			
		||||
		if (r.gains.length == gains.length)
 | 
			
		||||
			r.gains = gains;
 | 
			
		||||
	}
 | 
			
		||||
	catch (ex) { }
 | 
			
		||||
 | 
			
		||||
	r.draw = function () {
 | 
			
		||||
		jwrite('au_eq_gain', r.gains);
 | 
			
		||||
 | 
			
		||||
		var txt = QSA('input.eq_gain');
 | 
			
		||||
		for (var a = 0; a < r.bands.length; a++)
 | 
			
		||||
			txt[a].value = r.gains[a];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	r.apply = function () {
 | 
			
		||||
		r.draw();
 | 
			
		||||
 | 
			
		||||
		var Ctx = window.AudioContext || window.webkitAudioContext;
 | 
			
		||||
		if (!Ctx)
 | 
			
		||||
			bcfg_set('au_eq', false);
 | 
			
		||||
 | 
			
		||||
		if (!Ctx || !mp.au)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (!r.en && !mp.ac)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (mp.ac) {
 | 
			
		||||
			for (var a = 0; a < r.filters.length; a++)
 | 
			
		||||
				r.filters[a].disconnect();
 | 
			
		||||
 | 
			
		||||
			mp.acs.disconnect();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!mp.ac || mp.au != r.last_au) {
 | 
			
		||||
			if (mp.ac)
 | 
			
		||||
				mp.ac.close();
 | 
			
		||||
 | 
			
		||||
			r.last_au = mp.au;
 | 
			
		||||
			mp.ac = new Ctx();
 | 
			
		||||
			mp.acs = mp.ac.createMediaElementSource(mp.au);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.filters = [];
 | 
			
		||||
 | 
			
		||||
		if (!r.en) {
 | 
			
		||||
			mp.acs.connect(mp.ac.destination);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var max = 0;
 | 
			
		||||
		for (var a = 0; a < r.gains.length; a++)
 | 
			
		||||
			if (max < r.gains[a])
 | 
			
		||||
				max = r.gains[a];
 | 
			
		||||
 | 
			
		||||
		var gains = []
 | 
			
		||||
		for (var a = 0; a < r.gains.length; a++)
 | 
			
		||||
			gains.push(r.gains[a] - max);
 | 
			
		||||
 | 
			
		||||
		var t = gains[gains.length - 1];
 | 
			
		||||
		gains.push(t);
 | 
			
		||||
		gains.push(t);
 | 
			
		||||
		gains.unshift(gains[0]);
 | 
			
		||||
 | 
			
		||||
		for (var a = 0; a < cfg.length; a++) {
 | 
			
		||||
			var fi = mp.ac.createBiquadFilter();
 | 
			
		||||
			fi.frequency.value = cfg[a][0];
 | 
			
		||||
			fi.gain.value = cfg[a][2] * gains[a];
 | 
			
		||||
			fi.Q.value = cfg[a][1];
 | 
			
		||||
			fi.type = a == 0 ? 'lowshelf' : a == cfg.length - 1 ? 'highshelf' : 'peaking';
 | 
			
		||||
			r.filters.push(fi);
 | 
			
		||||
		}
 | 
			
		||||
		for (var a = r.filters.length - 1; a >= 0; a--) {
 | 
			
		||||
			r.filters[a].connect(a > 0 ? r.filters[a - 1] : mp.ac.destination);
 | 
			
		||||
		}
 | 
			
		||||
		fi = mp.ac.createGain();
 | 
			
		||||
		fi.gain.value = '0.94';  // +.137 dB measured; now -.25 dB and almost bitperfect
 | 
			
		||||
		mp.acs.connect(fi);
 | 
			
		||||
		fi.connect(r.filters[r.filters.length - 1]);
 | 
			
		||||
		r.filters.push(fi);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function eq_step(e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var band = parseInt(this.getAttribute('band')),
 | 
			
		||||
			step = parseFloat(this.getAttribute('step'));
 | 
			
		||||
 | 
			
		||||
		r.gains[band] += step;
 | 
			
		||||
		r.apply();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function adj_band(that, step) {
 | 
			
		||||
		try {
 | 
			
		||||
			var band = parseInt(that.getAttribute('band')),
 | 
			
		||||
				v = parseFloat(that.value);
 | 
			
		||||
 | 
			
		||||
			if (isNaN(v))
 | 
			
		||||
				throw 42;
 | 
			
		||||
 | 
			
		||||
			r.gains[band] = v + step;
 | 
			
		||||
		}
 | 
			
		||||
		catch (ex) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		r.apply();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function eq_mod(e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		adj_band(this, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function eq_keydown(e) {
 | 
			
		||||
		var step = e.key == 'ArrowUp' ? 0.25 : e.key == 'ArrowDown' ? -0.25 : 0;
 | 
			
		||||
		if (step != 0)
 | 
			
		||||
			adj_band(this, step);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var html = ['<table><tr><td rowspan="4">',
 | 
			
		||||
		'<a id="au_eq" class="tgl btn" href="#">enable</a></td>'],
 | 
			
		||||
		h2 = [], h3 = [], h4 = [];
 | 
			
		||||
 | 
			
		||||
	for (var a = 0; a < r.bands.length; a++) {
 | 
			
		||||
		var hz = r.bands[a];
 | 
			
		||||
		if (hz >= 1000)
 | 
			
		||||
			hz = (hz / 1000) + 'k';
 | 
			
		||||
 | 
			
		||||
		hz = (hz + '').split('.')[0];
 | 
			
		||||
		html.push('<td><a href="#" class="eq_step" step="0.5" band="' + a + '">+</a></td>');
 | 
			
		||||
		h2.push('<td>' + hz + '</td>');
 | 
			
		||||
		h4.push('<td><a href="#" class="eq_step" step="-0.5" band="' + a + '">–</a></td>');
 | 
			
		||||
		h3.push('<td><input type="text" class="eq_gain" band="' + a + '" value="' + r.gains[a] + '" /></td>');
 | 
			
		||||
	}
 | 
			
		||||
	html = html.join('\n') + '</tr><tr>';
 | 
			
		||||
	html += h2.join('\n') + '</tr><tr>';
 | 
			
		||||
	html += h3.join('\n') + '</tr><tr>';
 | 
			
		||||
	html += h4.join('\n') + '</tr><table>';
 | 
			
		||||
	ebi('audio_eq').innerHTML = html;
 | 
			
		||||
 | 
			
		||||
	var stp = QSA('a.eq_step');
 | 
			
		||||
	for (var a = 0, aa = stp.length; a < aa; a++)
 | 
			
		||||
		stp[a].onclick = eq_step;
 | 
			
		||||
 | 
			
		||||
	var txt = QSA('input.eq_gain');
 | 
			
		||||
	for (var a = 0; a < r.gains.length; a++) {
 | 
			
		||||
		txt[a].oninput = eq_mod;
 | 
			
		||||
		txt[a].onkeydown = eq_keydown;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.en = bcfg_get('au_eq', false);
 | 
			
		||||
	ebi('au_eq').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		r.en = !r.en;
 | 
			
		||||
		bcfg_set('au_eq', r.en);
 | 
			
		||||
		r.apply();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	r.draw();
 | 
			
		||||
	return r;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// plays the tid'th audio file on the page
 | 
			
		||||
function play(tid, seek, call_depth) {
 | 
			
		||||
	if (mp.order.length == 0)
 | 
			
		||||
@@ -517,11 +739,25 @@ function play(tid, seek, call_depth) {
 | 
			
		||||
	if ((tn + '').indexOf('f-') === 0)
 | 
			
		||||
		tn = mp.order.indexOf(tn);
 | 
			
		||||
 | 
			
		||||
	while (tn >= mp.order.length)
 | 
			
		||||
		tn -= mp.order.length;
 | 
			
		||||
	if (tn >= mp.order.length) {
 | 
			
		||||
		if (mpl.pb_mode == 'loop-folder') {
 | 
			
		||||
			tn = 0;
 | 
			
		||||
		}
 | 
			
		||||
		else if (mpl.pb_mode == 'next-folder') {
 | 
			
		||||
			treectl.ls_cb = function () { song_skip(1); };
 | 
			
		||||
			return tree_neigh(1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	while (tn < 0)
 | 
			
		||||
		tn += mp.order.length;
 | 
			
		||||
	if (tn < 0) {
 | 
			
		||||
		if (mpl.pb_mode == 'loop-folder') {
 | 
			
		||||
			tn = mp.order.length - 1;
 | 
			
		||||
		}
 | 
			
		||||
		else if (mpl.pb_mode == 'next-folder') {
 | 
			
		||||
			treectl.ls_cb = function () { song_skip(-1); };
 | 
			
		||||
			return tree_neigh(-1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tid = mp.order[tn];
 | 
			
		||||
 | 
			
		||||
@@ -568,6 +804,8 @@ function play(tid, seek, call_depth) {
 | 
			
		||||
		mp.au = mp.au_native;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	audio_eq.apply();
 | 
			
		||||
 | 
			
		||||
	mp.au.tid = tid;
 | 
			
		||||
	mp.au.src = url;
 | 
			
		||||
	mp.au.volume = mp.expvol();
 | 
			
		||||
@@ -709,8 +947,9 @@ function autoplay_blocked(seek) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var thegrid = (function () {
 | 
			
		||||
	var lfiles = ebi('files');
 | 
			
		||||
	var gfiles = document.createElement('div');
 | 
			
		||||
	var lfiles = ebi('files'),
 | 
			
		||||
		gfiles = document.createElement('div');
 | 
			
		||||
 | 
			
		||||
	gfiles.setAttribute('id', 'gfiles');
 | 
			
		||||
	gfiles.style.display = 'none';
 | 
			
		||||
	gfiles.innerHTML = (
 | 
			
		||||
@@ -732,7 +971,8 @@ var thegrid = (function () {
 | 
			
		||||
		'en': bcfg_get('griden', false),
 | 
			
		||||
		'sel': bcfg_get('gridsel', false),
 | 
			
		||||
		'sz': fcfg_get('gridsz', 10),
 | 
			
		||||
		'isdirty': true
 | 
			
		||||
		'isdirty': true,
 | 
			
		||||
		'bbox': null
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ebi('thumbs').onclick = function (e) {
 | 
			
		||||
@@ -891,9 +1131,37 @@ var thegrid = (function () {
 | 
			
		||||
		lfiles.style.display = 'none';
 | 
			
		||||
		gfiles.style.display = 'block';
 | 
			
		||||
		ebi('ggrid').innerHTML = html.join('\n');
 | 
			
		||||
		r.bagit();
 | 
			
		||||
		r.loadsel();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.bagit = function () {
 | 
			
		||||
		if (!window.baguetteBox)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (r.bbox)
 | 
			
		||||
			baguetteBox.destroy();
 | 
			
		||||
 | 
			
		||||
		r.bbox = baguetteBox.run('#ggrid', {
 | 
			
		||||
			captions: function (g) {
 | 
			
		||||
				var idx = -1,
 | 
			
		||||
					h = '' + g;
 | 
			
		||||
 | 
			
		||||
				for (var a = 0; a < r.bbox.length; a++)
 | 
			
		||||
					if (r.bbox[a].imageElement == g)
 | 
			
		||||
						idx = a;
 | 
			
		||||
 | 
			
		||||
				return '<a download href="' + h +
 | 
			
		||||
					'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
 | 
			
		||||
					esc(uricom_dec(h.split('/').slice(-1)[0])[0]) + '</a>';
 | 
			
		||||
			}
 | 
			
		||||
		})[0];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	setTimeout(function () {
 | 
			
		||||
		import_js('/.cpr/baguettebox.js', r.bagit);
 | 
			
		||||
	}, 1);
 | 
			
		||||
 | 
			
		||||
	if (r.en) {
 | 
			
		||||
		loadgrid();
 | 
			
		||||
	}
 | 
			
		||||
@@ -1246,7 +1514,8 @@ document.onkeydown = function (e) {
 | 
			
		||||
 | 
			
		||||
var treectl = (function () {
 | 
			
		||||
	var treectl = {
 | 
			
		||||
		"hidden": false
 | 
			
		||||
		"hidden": false,
 | 
			
		||||
		"ls_cb": null
 | 
			
		||||
	},
 | 
			
		||||
		entreed = false,
 | 
			
		||||
		fixedpos = false,
 | 
			
		||||
@@ -1346,6 +1615,11 @@ var treectl = (function () {
 | 
			
		||||
		onscroll();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	treectl.goto = function (url, push) {
 | 
			
		||||
		get_tree("", url, true);
 | 
			
		||||
		reqls(url, push);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function get_tree(top, dst, rst) {
 | 
			
		||||
		var xhr = new XMLHttpRequest();
 | 
			
		||||
		xhr.top = top;
 | 
			
		||||
@@ -1540,6 +1814,12 @@ var treectl = (function () {
 | 
			
		||||
		msel.render();
 | 
			
		||||
		reload_tree();
 | 
			
		||||
		reload_browser();
 | 
			
		||||
 | 
			
		||||
		var fun = treectl.ls_cb;
 | 
			
		||||
		if (fun) {
 | 
			
		||||
			treectl.ls_cb = null;
 | 
			
		||||
			fun();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function parsetree(res, top) {
 | 
			
		||||
@@ -1604,9 +1884,7 @@ var treectl = (function () {
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var url = new URL(e.state, "https://" + document.location.host);
 | 
			
		||||
		url = url.pathname;
 | 
			
		||||
		get_tree("", url, true);
 | 
			
		||||
		reqls(url);
 | 
			
		||||
		treectl.goto(url.pathname);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (window.history && history.pushState) {
 | 
			
		||||
@@ -1731,17 +2009,34 @@ var filecols = (function () {
 | 
			
		||||
	var add_btns = function () {
 | 
			
		||||
		var ths = QSA('#files th>span');
 | 
			
		||||
		for (var a = 0, aa = ths.length; a < aa; a++) {
 | 
			
		||||
			var th = ths[a].parentElement,
 | 
			
		||||
				is_hidden = has(hidden, ths[a].textContent);
 | 
			
		||||
 | 
			
		||||
			th.innerHTML = '<div class="cfg"><a href="#">' +
 | 
			
		||||
				(is_hidden ? '+' : '-') + '</a></div>' + ths[a].outerHTML;
 | 
			
		||||
 | 
			
		||||
			var th = ths[a].parentElement;
 | 
			
		||||
			th.innerHTML = '<div class="cfg"><a href="#">-</a></div>' + ths[a].outerHTML;
 | 
			
		||||
			th.getElementsByTagName('a')[0].onclick = ev_row_tgl;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	function hcols_click(e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var t = e.target;
 | 
			
		||||
		if (t.tagName != 'A')
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		toggle(t.textContent);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var set_style = function () {
 | 
			
		||||
		hidden.sort();
 | 
			
		||||
 | 
			
		||||
		var html = [],
 | 
			
		||||
			hcols = ebi('hcols');
 | 
			
		||||
 | 
			
		||||
		for (var a = 0; a < hidden.length; a++) {
 | 
			
		||||
			html.push('<a href="#" class="btn">' + esc(hidden[a]) + '</a>');
 | 
			
		||||
		}
 | 
			
		||||
		hcols.previousSibling.style.display = html.length ? 'block' : 'none';
 | 
			
		||||
		hcols.innerHTML = html.join('\n');
 | 
			
		||||
		hcols.onclick = hcols_click;
 | 
			
		||||
 | 
			
		||||
		add_btns();
 | 
			
		||||
 | 
			
		||||
		var ohidden = [],
 | 
			
		||||
@@ -1766,22 +2061,8 @@ var filecols = (function () {
 | 
			
		||||
			var cls = has(ohidden, a) ? 'min' : '',
 | 
			
		||||
				tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')');
 | 
			
		||||
 | 
			
		||||
			for (var b = 0, bb = tds.length; b < bb; b++) {
 | 
			
		||||
			for (var b = 0, bb = tds.length; b < bb; b++)
 | 
			
		||||
				tds[b].setAttribute('class', cls);
 | 
			
		||||
				if (a < 2)
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				if (cls) {
 | 
			
		||||
					if (!tds[b].hasAttribute('html')) {
 | 
			
		||||
						tds[b].setAttribute('html', tds[b].innerHTML);
 | 
			
		||||
						tds[b].innerHTML = '...';
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				else if (tds[b].hasAttribute('html')) {
 | 
			
		||||
					tds[b].innerHTML = tds[b].getAttribute('html');
 | 
			
		||||
					tds[b].removeAttribute('html');
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	set_style();
 | 
			
		||||
@@ -1800,15 +2081,13 @@ var filecols = (function () {
 | 
			
		||||
	try {
 | 
			
		||||
		var ci = find_file_col('dur'),
 | 
			
		||||
			i = ci[0],
 | 
			
		||||
			min = ci[1],
 | 
			
		||||
			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 && c.textContent)
 | 
			
		||||
					c.textContent = s2ms(c.textContent);
 | 
			
		||||
			}
 | 
			
		||||
		for (var a = 0, aa = rows.length; a < aa; a++) {
 | 
			
		||||
			var c = rows[a].cells[i];
 | 
			
		||||
			if (c && c.textContent)
 | 
			
		||||
				c.textContent = s2ms(c.textContent);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	catch (ex) { }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										61
									
								
								copyparty/web/dbg-audio.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								copyparty/web/dbg-audio.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
var ofun = audio_eq.apply.bind(audio_eq);
 | 
			
		||||
audio_eq.apply = function () {
 | 
			
		||||
    var ac1 = mp.ac;
 | 
			
		||||
    ofun();
 | 
			
		||||
    var ac = mp.ac,
 | 
			
		||||
        w = 2048,
 | 
			
		||||
        h = 256;
 | 
			
		||||
 | 
			
		||||
    if (!audio_eq.filters.length) {
 | 
			
		||||
        audio_eq.ana = null;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var can = ebi('fft_can');
 | 
			
		||||
    if (!can) {
 | 
			
		||||
        can = mknod('canvas');
 | 
			
		||||
        can.setAttribute('id', 'fft_can');
 | 
			
		||||
        can.style.cssText = 'position:absolute;left:0;bottom:5em;width:' + w + 'px;height:' + h + 'px;z-index:9001';
 | 
			
		||||
        document.body.appendChild(can);
 | 
			
		||||
        can.width = w;
 | 
			
		||||
        can.height = h;
 | 
			
		||||
    }
 | 
			
		||||
    var cc = can.getContext('2d');
 | 
			
		||||
    if (!ac)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    var ana = ac.createAnalyser();
 | 
			
		||||
    ana.smoothingTimeConstant = 0;
 | 
			
		||||
    ana.fftSize = 8192;
 | 
			
		||||
 | 
			
		||||
    audio_eq.filters[0].connect(ana);
 | 
			
		||||
    audio_eq.ana = ana;
 | 
			
		||||
 | 
			
		||||
    var buf = new Uint8Array(ana.frequencyBinCount),
 | 
			
		||||
        colw = can.width / buf.length;
 | 
			
		||||
 | 
			
		||||
    cc.fillStyle = '#fc0';
 | 
			
		||||
    function draw() {
 | 
			
		||||
        if (ana == audio_eq.ana)
 | 
			
		||||
            requestAnimationFrame(draw);
 | 
			
		||||
 | 
			
		||||
        ana.getByteFrequencyData(buf);
 | 
			
		||||
 | 
			
		||||
        cc.clearRect(0, 0, can.width, can.height);
 | 
			
		||||
 | 
			
		||||
        /*var x = 0, w = 1;
 | 
			
		||||
        for (var a = 0; a < buf.length; a++) {
 | 
			
		||||
            cc.fillRect(x, h - buf[a], w, h);
 | 
			
		||||
            x += w;
 | 
			
		||||
        }*/
 | 
			
		||||
        var mul = Math.pow(w, 4) / buf.length;
 | 
			
		||||
        for (var x = 0; x < w; x++) {
 | 
			
		||||
            var a = Math.floor(Math.pow(x, 4) / mul),
 | 
			
		||||
                v = buf[a];
 | 
			
		||||
 | 
			
		||||
            cc.fillRect(x, h - v, 1, v);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    draw();
 | 
			
		||||
};
 | 
			
		||||
audio_eq.apply();
 | 
			
		||||
@@ -59,7 +59,7 @@
 | 
			
		||||
 | 
			
		||||
        <h1>login for more:</h1>
 | 
			
		||||
        <ul>
 | 
			
		||||
            <form method="post" enctype="multipart/form-data" action="/{{ url_suf }}">
 | 
			
		||||
            <form method="post" enctype="multipart/form-data" action="/">
 | 
			
		||||
                <input type="hidden" name="act" value="login" />
 | 
			
		||||
                <input type="password" name="cppwd" />
 | 
			
		||||
                <input type="submit" value="Login" />
 | 
			
		||||
 
 | 
			
		||||
@@ -804,6 +804,14 @@ function up2k_init(subtle) {
 | 
			
		||||
 | 
			
		||||
                var mou_ikkai = false;
 | 
			
		||||
 | 
			
		||||
                if (st.busy.handshake.length > 0 &&
 | 
			
		||||
                    st.busy.handshake[0].busied < Date.now() - 30 * 1000
 | 
			
		||||
                ) {
 | 
			
		||||
                    console.log("retrying stuck handshake");
 | 
			
		||||
                    var t = st.busy.handshake.shift();
 | 
			
		||||
                    st.todo.handshake.unshift(t);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (st.todo.handshake.length > 0 &&
 | 
			
		||||
                    st.busy.handshake.length == 0 && (
 | 
			
		||||
                        st.todo.handshake[0].t4 || (
 | 
			
		||||
@@ -1019,11 +1027,27 @@ function up2k_init(subtle) {
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    function exec_handshake() {
 | 
			
		||||
        var t = st.todo.handshake.shift();
 | 
			
		||||
        var t = st.todo.handshake.shift(),
 | 
			
		||||
            me = Date.now();
 | 
			
		||||
 | 
			
		||||
        st.busy.handshake.push(t);
 | 
			
		||||
        t.busied = me;
 | 
			
		||||
 | 
			
		||||
        var xhr = new XMLHttpRequest();
 | 
			
		||||
        xhr.onerror = function () {
 | 
			
		||||
            if (t.busied != me) {
 | 
			
		||||
                console.log('zombie handshake onerror,', t);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            console.log('handshake onerror, retrying');
 | 
			
		||||
            st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
 | 
			
		||||
            st.todo.handshake.unshift(t);
 | 
			
		||||
        };
 | 
			
		||||
        xhr.onload = function (e) {
 | 
			
		||||
            if (t.busied != me) {
 | 
			
		||||
                console.log('zombie handshake onload,', t);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (xhr.status == 200) {
 | 
			
		||||
                var response = JSON.parse(xhr.responseText);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,19 @@
 | 
			
		||||
## [`minimal-up2k.html`](minimal-up2k.html)
 | 
			
		||||
* save as `.epilogue.html` inside a folder to [simplify the ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
 | 
			
		||||
# example `.epilogue.html`
 | 
			
		||||
save one of these as `.epilogue.html` inside a folder to customize it:
 | 
			
		||||
 | 
			
		||||
## [`browser.css`](browser.css)
 | 
			
		||||
* example for `--css-browser`
 | 
			
		||||
* [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# example browser-css
 | 
			
		||||
point `--css-browser` to one of these by URL:
 | 
			
		||||
 | 
			
		||||
* [`browser.css`](browser.css) changes the background
 | 
			
		||||
* [`browser-icons.css`](browser-icons.css) adds filetype icons
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# other stuff
 | 
			
		||||
 | 
			
		||||
## [`rclone.md`](rclone.md)
 | 
			
		||||
* notes on using rclone as a fuse client/server
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										95
									
								
								docs/biquad.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								docs/biquad.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
<!DOCTYPE html><html><head></head><body><script>
 | 
			
		||||
 | 
			
		||||
setTimeout(location.reload.bind(location), 700);
 | 
			
		||||
document.documentElement.scrollLeft = 0;
 | 
			
		||||
 | 
			
		||||
var can = document.createElement('canvas'),
 | 
			
		||||
    cc = can.getContext('2d'),
 | 
			
		||||
    w = 2048,
 | 
			
		||||
    h = 1024;
 | 
			
		||||
 | 
			
		||||
w = 2048;
 | 
			
		||||
 | 
			
		||||
can.width = w;
 | 
			
		||||
can.height = h;
 | 
			
		||||
document.body.appendChild(can);
 | 
			
		||||
can.style.cssText = 'width:' + w + 'px;height:' + h + 'px';
 | 
			
		||||
 | 
			
		||||
cc.fillStyle = '#000';
 | 
			
		||||
cc.fillRect(0, 0, w, h);
 | 
			
		||||
 | 
			
		||||
var cfg = [ // hz, q, g
 | 
			
		||||
    [31.25 * 0.88, 0, 1.4],  // shelf
 | 
			
		||||
    [31.25 * 1.04, 0.7, 0.96],  // peak
 | 
			
		||||
    [62.5, 0.7, 1],
 | 
			
		||||
    [125, 0.8, 1],
 | 
			
		||||
    [250, 0.9, 1.03],
 | 
			
		||||
    [500, 0.9, 1.1],
 | 
			
		||||
    [1000, 0.9, 1.1],
 | 
			
		||||
    [2000, 0.9, 1.105],
 | 
			
		||||
    [4000, 0.88, 1.05],
 | 
			
		||||
    [8000 * 1.006, 0.73, 1.24],
 | 
			
		||||
    //[16000 * 1.00, 0.5, 1.75],  // peak.v1
 | 
			
		||||
    //[16000 * 1.19, 0, 1.8]  // shelf.v1
 | 
			
		||||
    [16000 * 0.89, 0.7, 1.26],  // peak
 | 
			
		||||
    [16000 * 1.13, 0.82, 1.09],  // peak
 | 
			
		||||
    [16000 * 1.205, 0, 1.9]  // shelf
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
var freqs = new Float32Array(22000),
 | 
			
		||||
    sum = new Float32Array(freqs.length),
 | 
			
		||||
    ac = new AudioContext(),
 | 
			
		||||
    step = w / freqs.length,
 | 
			
		||||
    colors = [
 | 
			
		||||
        'rgba(255, 0, 0, 0.7)',
 | 
			
		||||
        'rgba(0, 224, 0, 0.7)',
 | 
			
		||||
        'rgba(0, 64, 255, 0.7)'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
var order = [];
 | 
			
		||||
 | 
			
		||||
for (var a = 0; a < cfg.length; a += 2)
 | 
			
		||||
    order.push(a);
 | 
			
		||||
 | 
			
		||||
for (var a = 1; a < cfg.length; a += 2)
 | 
			
		||||
    order.push(a);
 | 
			
		||||
 | 
			
		||||
for (var ia = 0; ia < order.length; ia++) {
 | 
			
		||||
    var a = order[ia],
 | 
			
		||||
        fi = ac.createBiquadFilter(),
 | 
			
		||||
        mag = new Float32Array(freqs.length),
 | 
			
		||||
        phase = new Float32Array(freqs.length);
 | 
			
		||||
 | 
			
		||||
    for (var b = 0; b < freqs.length; b++)
 | 
			
		||||
        freqs[b] = b;
 | 
			
		||||
 | 
			
		||||
    fi.type = a == 0 ? 'lowshelf' : a == cfg.length - 1 ? 'highshelf' : 'peaking';
 | 
			
		||||
    fi.frequency.value = cfg[a][0];
 | 
			
		||||
    fi.Q.value = cfg[a][1];
 | 
			
		||||
    fi.gain.value = 1;
 | 
			
		||||
 | 
			
		||||
    fi.getFrequencyResponse(freqs, mag, phase);
 | 
			
		||||
    cc.fillStyle = colors[a % colors.length];
 | 
			
		||||
    for (var b = 0; b < sum.length; b++) {
 | 
			
		||||
        mag[b] -= 1;
 | 
			
		||||
        sum[b] += mag[b] * cfg[a][2];
 | 
			
		||||
        var y = h - (mag[b] * h * 3);
 | 
			
		||||
        cc.fillRect(b * step, y, step, h - y);
 | 
			
		||||
        cc.fillRect(b * step - 1, y - 1, 3, 3);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var min = 999999, max = 0;
 | 
			
		||||
for (var a = 0; a < sum.length; a++) {
 | 
			
		||||
    min = Math.min(min, sum[a]);
 | 
			
		||||
    max = Math.max(max, sum[a]);
 | 
			
		||||
}
 | 
			
		||||
cc.fillStyle = 'rgba(255,255,255,1)';
 | 
			
		||||
for (var a = 0; a < sum.length; a++) {
 | 
			
		||||
    var v = (sum[a] - min) / (max - min);
 | 
			
		||||
    cc.fillRect(a * step, 0, step, v * h / 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
cc.fillRect(0, 460, w, 1);
 | 
			
		||||
 | 
			
		||||
</script></body></html>
 | 
			
		||||
							
								
								
									
										68
									
								
								docs/browser-icons.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								docs/browser-icons.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
/* put filetype icons inline with text
 | 
			
		||||
#ggrid>a>span:before,
 | 
			
		||||
#ggrid>a>span.dir:before {
 | 
			
		||||
	display: inline;
 | 
			
		||||
	line-height: 0;
 | 
			
		||||
	font-size: 1.7em;
 | 
			
		||||
	margin: -.7em .1em -.5em -.6em;
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* move folder icons top-left */
 | 
			
		||||
#ggrid>a>span.dir:before {
 | 
			
		||||
	content: initial;
 | 
			
		||||
}
 | 
			
		||||
#ggrid>a[href$="/"]:before {
 | 
			
		||||
	content: '📂';
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    margin: -.1em -.4em;
 | 
			
		||||
    text-shadow: 0 0 .1em #000;
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* put filetype icons top-left */
 | 
			
		||||
#ggrid>a:before {
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    margin: -.1em -.4em;
 | 
			
		||||
    text-shadow: 0 0 .1em #000;
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* video */
 | 
			
		||||
#ggrid>a:is(
 | 
			
		||||
[href$=".mkv"i],
 | 
			
		||||
[href$=".mp4"i],
 | 
			
		||||
[href$=".webm"i],
 | 
			
		||||
):before {
 | 
			
		||||
    content: '📺';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* audio */
 | 
			
		||||
#ggrid>a:is(
 | 
			
		||||
[href$=".mp3"i],
 | 
			
		||||
[href$=".ogg"i],
 | 
			
		||||
[href$=".opus"i],
 | 
			
		||||
[href$=".flac"i],
 | 
			
		||||
[href$=".m4a"i],
 | 
			
		||||
[href$=".aac"i],
 | 
			
		||||
):before {
 | 
			
		||||
    content: '🎵';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* image */
 | 
			
		||||
#ggrid>a:is(
 | 
			
		||||
[href$=".jpg"i],
 | 
			
		||||
[href$=".jpeg"i],
 | 
			
		||||
[href$=".png"i],
 | 
			
		||||
[href$=".gif"i],
 | 
			
		||||
[href$=".webp"i],
 | 
			
		||||
):before {
 | 
			
		||||
    content: '🎨';
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
html {
 | 
			
		||||
    background: url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
 | 
			
		||||
    background: #333 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
 | 
			
		||||
}
 | 
			
		||||
#files th {
 | 
			
		||||
    background: rgba(32, 32, 32, 0.9) !important;
 | 
			
		||||
@@ -12,7 +12,7 @@ html {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
html.light {
 | 
			
		||||
    background: url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
 | 
			
		||||
    background: #eee url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
 | 
			
		||||
}
 | 
			
		||||
html.light #files th {
 | 
			
		||||
    background: rgba(255, 255, 255, 0.9) !important;
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,9 @@ var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.quer
 | 
			
		||||
# get the size and video-id of all youtube vids in folder, assuming filename ends with -id.ext, and create a copyparty search query
 | 
			
		||||
find -maxdepth 1 -printf '%s %p\n' | sort -n | awk '!/-([0-9a-zA-Z_-]{11})\.(mkv|mp4|webm)$/{next} {sub(/\.[^\.]+$/,"");n=length($0);v=substr($0,n-10);print $1, v}' | tee /dev/stderr | awk 'BEGIN {p="("} {printf("%s name like *-%s.* ",p,$2);p="or"} END {print ")\n"}' | cat >&2
 | 
			
		||||
 | 
			
		||||
# unique stacks in a stackdump
 | 
			
		||||
f=a; rm -rf stacks; mkdir stacks; grep -E '^#' $f | while IFS= read -r n; do awk -v n="$n" '!$0{o=0} o; $0==n{o=1}' <$f >stacks/f; h=$(sha1sum <stacks/f | cut -c-16); mv stacks/f stacks/$h-"$n"; done ; find stacks/ | sort | uniq -cw24
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##
 | 
			
		||||
## sqlite3 stuff
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								docs/tcp-debug.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								docs/tcp-debug.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
(cd ~/dev/copyparty && strace -Tttyyvfs 256 -o strace.strace python3 -um copyparty -i 127.0.0.1 --http-only --stackmon /dev/shm/cpps,10 ) 2>&1 | tee /dev/stderr > ~/log-copyparty-$(date +%Y-%m%d-%H%M%S).txt
 | 
			
		||||
 | 
			
		||||
14/Jun/2021:16:34:02 1623688447.212405 death
 | 
			
		||||
14/Jun/2021:16:35:02 1623688502.420860 back
 | 
			
		||||
 | 
			
		||||
tcpdump -nni lo -w /home/ed/lo.pcap
 | 
			
		||||
 | 
			
		||||
# 16:35:25.324662 IP 127.0.0.1.48632 > 127.0.0.1.3920: Flags [F.], seq 849, ack 544, win 359, options [nop,nop,TS val 809396796 ecr 809396796], length 0
 | 
			
		||||
 | 
			
		||||
tcpdump -nnr /home/ed/lo.pcap | awk '/ > 127.0.0.1.3920: /{sub(/ > .*/,"");sub(/.*\./,"");print}' | sort -n | uniq | while IFS= read -r port; do echo; tcpdump -nnr /home/ed/lo.pcap 2>/dev/null | grep -E "\.$port( > |: F)" | sed -r 's/ > .*, /, /'; done | grep -E '^16:35:0.*length [^0]' -C50
 | 
			
		||||
 | 
			
		||||
16:34:02.441732 IP 127.0.0.1.48638, length 0
 | 
			
		||||
16:34:02.441738 IP 127.0.0.1.3920, length 0
 | 
			
		||||
16:34:02.441744 IP 127.0.0.1.48638, length 0
 | 
			
		||||
16:34:02.441756 IP 127.0.0.1.48638, length 791
 | 
			
		||||
16:34:02.441759 IP 127.0.0.1.3920, length 0
 | 
			
		||||
16:35:02.445529 IP 127.0.0.1.48638, length 0
 | 
			
		||||
16:35:02.489194 IP 127.0.0.1.3920, length 0
 | 
			
		||||
16:35:02.515595 IP 127.0.0.1.3920, length 216
 | 
			
		||||
16:35:02.515600 IP 127.0.0.1.48638, length 0
 | 
			
		||||
 | 
			
		||||
grep 48638 "$(find ~ -maxdepth 1 -name log-copyparty-\*.txt | sort | tail -n 1)"
 | 
			
		||||
 | 
			
		||||
1623688502.510380 48638 rh
 | 
			
		||||
1623688502.511291 48638 Unrecv direct ...
 | 
			
		||||
1623688502.511827 48638 rh = 791
 | 
			
		||||
16:35:02.518 127.0.0.1 48638       shut(8): [Errno 107] Socket not connected
 | 
			
		||||
Exception in thread httpsrv-0.1-48638:
 | 
			
		||||
 | 
			
		||||
grep 48638 ~/dev/copyparty/strace.strace
 | 
			
		||||
14561 16:35:02.506310 <... accept4 resumed> {sa_family=AF_INET, sin_port=htons(48638), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC) = 8<TCP:[127.0.0.1:3920->127.0.0.1:48638]> <0.000012>
 | 
			
		||||
15230 16:35:02.510725 write(1<pipe:[256639555]>, "1623688502.510380 48638 rh\n", 27 <unfinished ...>
 | 
			
		||||
@@ -167,7 +167,7 @@ find .. -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
 | 
			
		||||
find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
 | 
			
		||||
 | 
			
		||||
echo use smol web deps
 | 
			
		||||
rm -f copyparty/web/deps/*.full.* copyparty/web/Makefile
 | 
			
		||||
rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
 | 
			
		||||
 | 
			
		||||
# it's fine dw
 | 
			
		||||
grep -lE '\.full\.(js|css)' copyparty/web/* |
 | 
			
		||||
@@ -208,7 +208,7 @@ find | grep -E '\.css$' | while IFS= read -r f; do
 | 
			
		||||
	}
 | 
			
		||||
	!/\}$/ {printf "%s",$0;next}
 | 
			
		||||
	1
 | 
			
		||||
	' <$f | gsed 's/;\}$/}/' >t
 | 
			
		||||
	' <$f | sed 's/;\}$/}/' >t
 | 
			
		||||
	tmv "$f"
 | 
			
		||||
done
 | 
			
		||||
find | grep -E '\.(js|html)$' | while IFS= read -r f; do
 | 
			
		||||
@@ -223,7 +223,7 @@ command -v pigz &&
 | 
			
		||||
	pk='gzip'
 | 
			
		||||
 | 
			
		||||
echo "$pk"
 | 
			
		||||
find | grep -E '\.(js|css)$' | while IFS= read -r f; do
 | 
			
		||||
find | grep -E '\.(js|css)$' | grep -vF /deps/ | while IFS= read -r f; do
 | 
			
		||||
	echo -n .
 | 
			
		||||
	$pk "$f"
 | 
			
		||||
done
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ class Cfg(Namespace):
 | 
			
		||||
            a=a,
 | 
			
		||||
            v=v,
 | 
			
		||||
            c=c,
 | 
			
		||||
            rproxy=0,
 | 
			
		||||
            ed=False,
 | 
			
		||||
            no_zip=False,
 | 
			
		||||
            no_scandir=False,
 | 
			
		||||
@@ -39,6 +40,7 @@ class Cfg(Namespace):
 | 
			
		||||
            mte="a",
 | 
			
		||||
            hist=None,
 | 
			
		||||
            no_hash=False,
 | 
			
		||||
            css_browser=None,
 | 
			
		||||
            **{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,14 @@ from copyparty import util
 | 
			
		||||
class Cfg(Namespace):
 | 
			
		||||
    def __init__(self, a=[], v=[], c=None):
 | 
			
		||||
        ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
 | 
			
		||||
        ex2 = {"mtp": [], "mte": "a", "hist": None, "no_hash": False}
 | 
			
		||||
        ex2 = {
 | 
			
		||||
            "mtp": [],
 | 
			
		||||
            "mte": "a",
 | 
			
		||||
            "hist": None,
 | 
			
		||||
            "no_hash": False,
 | 
			
		||||
            "css_browser": None,
 | 
			
		||||
            "rproxy": 0,
 | 
			
		||||
        }
 | 
			
		||||
        ex.update(ex2)
 | 
			
		||||
        super(Cfg, self).__init__(a=a, v=v, c=c, **ex)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user