mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			21 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ba94cc5df7 | ||
| 
						 | 
					d08245c3df | ||
| 
						 | 
					5c18d12cbf | ||
| 
						 | 
					580a42dec7 | ||
| 
						 | 
					29286e159b | ||
| 
						 | 
					19bcf90e9f | ||
| 
						 | 
					dae9c00742 | ||
| 
						 | 
					35324ceb7c | ||
| 
						 | 
					5aadd47199 | ||
| 
						 | 
					7d9057cc62 | ||
| 
						 | 
					c4b322b883 | ||
| 
						 | 
					19b09c898a | ||
| 
						 | 
					eafe2098b6 | ||
| 
						 | 
					2bc6a20d71 | ||
| 
						 | 
					8b502a7235 | ||
| 
						 | 
					37567844af | ||
| 
						 | 
					2f6c4e0e34 | ||
| 
						 | 
					1c7cc4cb2b | ||
| 
						 | 
					f83db3648e | ||
| 
						 | 
					b164aa00d4 | ||
| 
						 | 
					a2d866d0c2 | 
							
								
								
									
										12
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "env": {
 | 
				
			||||||
 | 
					        "browser": true,
 | 
				
			||||||
 | 
					        "es2021": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "extends": "eslint:recommended",
 | 
				
			||||||
 | 
					    "parserOptions": {
 | 
				
			||||||
 | 
					        "ecmaVersion": 12
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "rules": {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -906,6 +906,7 @@ class TheArgparseFormatter(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    global info, log, dbg
 | 
					    global info, log, dbg
 | 
				
			||||||
 | 
					    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # filecache helps for reads that are ~64k or smaller;
 | 
					    # filecache helps for reads that are ~64k or smaller;
 | 
				
			||||||
    #   linux generally does 128k so the cache is a slowdown,
 | 
					    #   linux generally does 128k so the cache is a slowdown,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -567,6 +567,8 @@ class CPPF(Fuse):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
 | 
					    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    server = CPPF()
 | 
					    server = CPPF()
 | 
				
			||||||
    server.parser.add_option(mountopt="url", metavar="BASE_URL", default=None)
 | 
					    server.parser.add_option(mountopt="url", metavar="BASE_URL", default=None)
 | 
				
			||||||
    server.parse(values=server, errex=1)
 | 
					    server.parse(values=server, errex=1)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ __license__ = "MIT"
 | 
				
			|||||||
__url__ = "https://github.com/9001/copyparty/"
 | 
					__url__ = "https://github.com/9001/copyparty/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import filecmp
 | 
					import filecmp
 | 
				
			||||||
import locale
 | 
					import locale
 | 
				
			||||||
@@ -85,6 +86,7 @@ def ensure_cert():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
 | 
					    time.strptime("19970815", "%Y%m%d")  # python#7980
 | 
				
			||||||
    if WINDOWS:
 | 
					    if WINDOWS:
 | 
				
			||||||
        os.system("")  # enables colors
 | 
					        os.system("")  # enables colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,20 +125,17 @@ def main():
 | 
				
			|||||||
            """
 | 
					            """
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    ap.add_argument(
 | 
					    ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file")
 | 
				
			||||||
        "-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")
 | 
					    ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind")
 | 
				
			||||||
    ap.add_argument("-p", metavar="PORT", type=int, default=3923, help="port to bind")
 | 
					    ap.add_argument("-p", metavar="PORT", type=int, default=3923, help="port to bind")
 | 
				
			||||||
    ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
 | 
					    ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
 | 
				
			||||||
    ap.add_argument(
 | 
					    ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores")
 | 
				
			||||||
        "-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("-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("-v", metavar="VOL", type=str, action="append", help="add volume")
 | 
				
			||||||
    ap.add_argument("-q", action="store_true", help="quiet")
 | 
					    ap.add_argument("-q", action="store_true", help="quiet")
 | 
				
			||||||
    ap.add_argument("-ed", action="store_true", help="enable ?dots")
 | 
					    ap.add_argument("-ed", action="store_true", help="enable ?dots")
 | 
				
			||||||
    ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
 | 
					    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("-nw", action="store_true", help="disable writes (benchmark)")
 | 
				
			||||||
    ap.add_argument("-nih", action="store_true", help="no info hostname")
 | 
					    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("-nid", action="store_true", help="no info disk-usage")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
# coding: utf-8
 | 
					# coding: utf-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = (0, 5, 5)
 | 
					VERSION = (0, 6, 2)
 | 
				
			||||||
CODENAME = "fuse jelly"
 | 
					CODENAME = "CHRISTMAAAAAS"
 | 
				
			||||||
BUILD_DT = (2020, 11, 27)
 | 
					BUILD_DT = (2020, 12, 14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
S_VERSION = ".".join(map(str, VERSION))
 | 
					S_VERSION = ".".join(map(str, VERSION))
 | 
				
			||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
					S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,7 +104,7 @@ class VFS(object):
 | 
				
			|||||||
        real.sort()
 | 
					        real.sort()
 | 
				
			||||||
        if not rem:
 | 
					        if not rem:
 | 
				
			||||||
            for name, vn2 in sorted(self.nodes.items()):
 | 
					            for name, vn2 in sorted(self.nodes.items()):
 | 
				
			||||||
                if uname in vn2.uread:
 | 
					                if uname in vn2.uread or "*" in vn2.uread:
 | 
				
			||||||
                    virt_vis[name] = vn2
 | 
					                    virt_vis[name] = vn2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # no vfs nodes in the list of real inodes
 | 
					            # no vfs nodes in the list of real inodes
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -568,24 +568,24 @@ class HttpCli(object):
 | 
				
			|||||||
                    self.log("discarding incoming file without filename")
 | 
					                    self.log("discarding incoming file without filename")
 | 
				
			||||||
                    # fallthrough
 | 
					                    # fallthrough
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                fn = os.devnull
 | 
					 | 
				
			||||||
                if p_file and not nullwrite:
 | 
					                if p_file and not nullwrite:
 | 
				
			||||||
                    fdir = os.path.join(vfs.realpath, rem)
 | 
					                    fdir = os.path.join(vfs.realpath, rem)
 | 
				
			||||||
                    fn = os.path.join(fdir, sanitize_fn(p_file))
 | 
					                    fname = sanitize_fn(p_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if not os.path.isdir(fsenc(fdir)):
 | 
					                    if not os.path.isdir(fsenc(fdir)):
 | 
				
			||||||
                        raise Pebkac(404, "that folder does not exist")
 | 
					                        raise Pebkac(404, "that folder does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # TODO broker which avoid this race and
 | 
					                    suffix = ".{:.6f}-{}".format(time.time(), self.addr[0])
 | 
				
			||||||
                    # provides a new filename if taken (same as up2k)
 | 
					                    open_args = {"fdir": fdir, "suffix": suffix}
 | 
				
			||||||
                    if os.path.exists(fsenc(fn)):
 | 
					                else:
 | 
				
			||||||
                        fn += ".{:.6f}-{}".format(time.time(), self.addr[0])
 | 
					                    open_args = {}
 | 
				
			||||||
                        # using current-time instead of t0 cause clients
 | 
					                    fname = os.devnull
 | 
				
			||||||
                        # may reuse a name for multiple files in one post
 | 
					                    fdir = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    with open(fsenc(fn), "wb") as f:
 | 
					                    with ren_open(fname, "wb", 512 * 1024, **open_args) as f:
 | 
				
			||||||
                        self.log("writing to {0}".format(fn))
 | 
					                        f, fname = f["orz"]
 | 
				
			||||||
 | 
					                        self.log("writing to {}/{}".format(fdir, fname))
 | 
				
			||||||
                        sz, sha512_hex, _ = hashcopy(self.conn, p_data, f)
 | 
					                        sz, sha512_hex, _ = hashcopy(self.conn, p_data, f)
 | 
				
			||||||
                        if sz == 0:
 | 
					                        if sz == 0:
 | 
				
			||||||
                            raise Pebkac(400, "empty files in post")
 | 
					                            raise Pebkac(400, "empty files in post")
 | 
				
			||||||
@@ -594,8 +594,14 @@ class HttpCli(object):
 | 
				
			|||||||
                        self.conn.nbyte += sz
 | 
					                        self.conn.nbyte += sz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                except Pebkac:
 | 
					                except Pebkac:
 | 
				
			||||||
                    if fn != os.devnull:
 | 
					                    if fname != os.devnull:
 | 
				
			||||||
                        os.rename(fsenc(fn), fsenc(fn + ".PARTIAL"))
 | 
					                        fp = os.path.join(fdir, fname)
 | 
				
			||||||
 | 
					                        suffix = ".PARTIAL"
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            os.rename(fsenc(fp), fsenc(fp + suffix))
 | 
				
			||||||
 | 
					                        except:
 | 
				
			||||||
 | 
					                            fp = fp[: -len(suffix)]
 | 
				
			||||||
 | 
					                            os.rename(fsenc(fp), fsenc(fp + suffix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    raise
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -731,7 +737,7 @@ class HttpCli(object):
 | 
				
			|||||||
        if p_field != "body":
 | 
					        if p_field != "body":
 | 
				
			||||||
            raise Pebkac(400, "expected body, got {}".format(p_field))
 | 
					            raise Pebkac(400, "expected body, got {}".format(p_field))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with open(fp, "wb") as f:
 | 
					        with open(fp, "wb", 512 * 1024) as f:
 | 
				
			||||||
            sz, sha512, _ = hashcopy(self.conn, p_data, f)
 | 
					            sz, sha512, _ = hashcopy(self.conn, p_data, f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        new_lastmod = os.stat(fsenc(fp)).st_mtime
 | 
					        new_lastmod = os.stat(fsenc(fp)).st_mtime
 | 
				
			||||||
@@ -756,9 +762,12 @@ class HttpCli(object):
 | 
				
			|||||||
                cli_dt = time.strptime(cli_lastmod, "%a, %d %b %Y %H:%M:%S GMT")
 | 
					                cli_dt = time.strptime(cli_lastmod, "%a, %d %b %Y %H:%M:%S GMT")
 | 
				
			||||||
                cli_ts = calendar.timegm(cli_dt)
 | 
					                cli_ts = calendar.timegm(cli_dt)
 | 
				
			||||||
                return file_lastmod, int(file_ts) > int(cli_ts)
 | 
					                return file_lastmod, int(file_ts) > int(cli_ts)
 | 
				
			||||||
            except:
 | 
					            except Exception as ex:
 | 
				
			||||||
                self.log("bad lastmod format: {}".format(cli_lastmod))
 | 
					                self.log(
 | 
				
			||||||
                self.log("   expected format: {}".format(file_lastmod))
 | 
					                    "lastmod {}\nremote: [{}]\n local: [{}]".format(
 | 
				
			||||||
 | 
					                        repr(ex), cli_lastmod, file_lastmod
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                return file_lastmod, file_lastmod != cli_lastmod
 | 
					                return file_lastmod, file_lastmod != cli_lastmod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return file_lastmod, True
 | 
					        return file_lastmod, True
 | 
				
			||||||
@@ -964,6 +973,7 @@ class HttpCli(object):
 | 
				
			|||||||
            "title": html_escape(self.vpath),
 | 
					            "title": html_escape(self.vpath),
 | 
				
			||||||
            "lastmod": int(ts_md * 1000),
 | 
					            "lastmod": int(ts_md * 1000),
 | 
				
			||||||
            "md_plug": "true" if self.args.emp else "false",
 | 
					            "md_plug": "true" if self.args.emp else "false",
 | 
				
			||||||
 | 
					            "md_chk_rate": self.args.mcr,
 | 
				
			||||||
            "md": "",
 | 
					            "md": "",
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        sz_html = len(template.render(**targs).encode("utf-8"))
 | 
					        sz_html = len(template.render(**targs).encode("utf-8"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ import threading
 | 
				
			|||||||
from copy import deepcopy
 | 
					from copy import deepcopy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .__init__ import WINDOWS
 | 
					from .__init__ import WINDOWS
 | 
				
			||||||
from .util import Pebkac, Queue, fsenc, sanitize_fn
 | 
					from .util import Pebkac, Queue, fsenc, sanitize_fn, ren_open
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Up2k(object):
 | 
					class Up2k(object):
 | 
				
			||||||
@@ -68,9 +68,11 @@ class Up2k(object):
 | 
				
			|||||||
                        # symlink to the client-provided name,
 | 
					                        # symlink to the client-provided name,
 | 
				
			||||||
                        # returning the previous upload info
 | 
					                        # returning the previous upload info
 | 
				
			||||||
                        job = deepcopy(job)
 | 
					                        job = deepcopy(job)
 | 
				
			||||||
                        suffix = self._suffix(dst, now, job["addr"])
 | 
					                        job["rdir"] = cj["rdir"]
 | 
				
			||||||
                        job["name"] = cj["name"] + suffix
 | 
					                        job["name"] = self._untaken(cj["rdir"], cj["name"], now, cj["addr"])
 | 
				
			||||||
                        self._symlink(src, dst + suffix)
 | 
					                        dst = os.path.join(job["rdir"], job["name"])
 | 
				
			||||||
 | 
					                        os.unlink(fsenc(dst))  # TODO ed pls
 | 
				
			||||||
 | 
					                        self._symlink(src, dst)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                job = {
 | 
					                job = {
 | 
				
			||||||
                    "wark": wark,
 | 
					                    "wark": wark,
 | 
				
			||||||
@@ -85,9 +87,6 @@ class Up2k(object):
 | 
				
			|||||||
                    "hash": deepcopy(cj["hash"]),
 | 
					                    "hash": deepcopy(cj["hash"]),
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                path = os.path.join(job["rdir"], job["name"])
 | 
					 | 
				
			||||||
                job["name"] += self._suffix(path, now, cj["addr"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # one chunk may occur multiple times in a file;
 | 
					                # one chunk may occur multiple times in a file;
 | 
				
			||||||
                # filter to unique values for the list of missing chunks
 | 
					                # filter to unique values for the list of missing chunks
 | 
				
			||||||
                # (preserve order to reduce disk thrashing)
 | 
					                # (preserve order to reduce disk thrashing)
 | 
				
			||||||
@@ -108,13 +107,12 @@ class Up2k(object):
 | 
				
			|||||||
                "wark": wark,
 | 
					                "wark": wark,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _suffix(self, fpath, ts, ip):
 | 
					    def _untaken(self, fdir, fname, ts, ip):
 | 
				
			||||||
        # TODO broker which avoid this race and
 | 
					        # TODO broker which avoid this race and
 | 
				
			||||||
        # provides a new filename if taken (same as bup)
 | 
					        # provides a new filename if taken (same as bup)
 | 
				
			||||||
        if not os.path.exists(fsenc(fpath)):
 | 
					        suffix = ".{:.6f}-{}".format(ts, ip)
 | 
				
			||||||
            return ""
 | 
					        with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as f:
 | 
				
			||||||
 | 
					            return f["orz"][1]
 | 
				
			||||||
        return ".{:.6f}-{}".format(ts, ip)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _symlink(self, src, dst):
 | 
					    def _symlink(self, src, dst):
 | 
				
			||||||
        # TODO store this in linktab so we never delete src if there are links to it
 | 
					        # TODO store this in linktab so we never delete src if there are links to it
 | 
				
			||||||
@@ -218,8 +216,9 @@ class Up2k(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def _new_upload(self, job):
 | 
					    def _new_upload(self, job):
 | 
				
			||||||
        self.registry[job["wark"]] = job
 | 
					        self.registry[job["wark"]] = job
 | 
				
			||||||
        path = os.path.join(job["rdir"], job["name"])
 | 
					        suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
 | 
				
			||||||
        with open(fsenc(path), "wb") as f:
 | 
					        with ren_open(job["name"], "wb", fdir=job["rdir"], suffix=suffix) as f:
 | 
				
			||||||
 | 
					            f, job["name"] = f["orz"]
 | 
				
			||||||
            f.seek(job["size"] - 1)
 | 
					            f.seek(job["size"] - 1)
 | 
				
			||||||
            f.write(b"e")
 | 
					            f.write(b"e")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
from __future__ import print_function, unicode_literals
 | 
					from __future__ import print_function, unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
@@ -10,6 +11,7 @@ import hashlib
 | 
				
			|||||||
import platform
 | 
					import platform
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
import mimetypes
 | 
					import mimetypes
 | 
				
			||||||
 | 
					import contextlib
 | 
				
			||||||
import subprocess as sp  # nosec
 | 
					import subprocess as sp  # nosec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .__init__ import PY2, WINDOWS
 | 
					from .__init__ import PY2, WINDOWS
 | 
				
			||||||
@@ -96,6 +98,80 @@ class Unrecv(object):
 | 
				
			|||||||
        self.buf = buf + self.buf
 | 
					        self.buf = buf + self.buf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@contextlib.contextmanager
 | 
				
			||||||
 | 
					def ren_open(fname, *args, **kwargs):
 | 
				
			||||||
 | 
					    fdir = kwargs.pop("fdir", None)
 | 
				
			||||||
 | 
					    suffix = kwargs.pop("suffix", None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if fname == os.devnull:
 | 
				
			||||||
 | 
					        with open(fname, *args, **kwargs) as f:
 | 
				
			||||||
 | 
					            yield {"orz": [f, fname]}
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    orig_name = fname
 | 
				
			||||||
 | 
					    bname = fname
 | 
				
			||||||
 | 
					    ext = ""
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        ofs = bname.rfind(".")
 | 
				
			||||||
 | 
					        if ofs < 0 or ofs < len(bname) - 7:
 | 
				
			||||||
 | 
					            # doesn't look like an extension anymore
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ext = bname[ofs:] + ext
 | 
				
			||||||
 | 
					        bname = bname[:ofs]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    b64 = ""
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if fdir:
 | 
				
			||||||
 | 
					                fpath = os.path.join(fdir, fname)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fpath = fname
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if suffix and os.path.exists(fpath):
 | 
				
			||||||
 | 
					                fpath += suffix
 | 
				
			||||||
 | 
					                fname += suffix
 | 
				
			||||||
 | 
					                ext += suffix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            with open(fsenc(fpath), *args, **kwargs) as f:
 | 
				
			||||||
 | 
					                if b64:
 | 
				
			||||||
 | 
					                    fp2 = "fn-trunc.{}.txt".format(b64)
 | 
				
			||||||
 | 
					                    fp2 = os.path.join(fdir, fp2)
 | 
				
			||||||
 | 
					                    with open(fsenc(fp2), "wb") as f2:
 | 
				
			||||||
 | 
					                        f2.write(orig_name.encode("utf-8"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                yield {"orz": [f, fname]}
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except OSError as ex_:
 | 
				
			||||||
 | 
					            ex = ex_
 | 
				
			||||||
 | 
					            if ex.errno != 36:
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not b64:
 | 
				
			||||||
 | 
					            b64 = (bname + ext).encode("utf-8", "replace")
 | 
				
			||||||
 | 
					            b64 = hashlib.sha512(b64).digest()[:12]
 | 
				
			||||||
 | 
					            b64 = base64.urlsafe_b64encode(b64).decode("utf-8").rstrip("=")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        badlen = len(fname)
 | 
				
			||||||
 | 
					        while len(fname) >= badlen:
 | 
				
			||||||
 | 
					            if len(bname) < 8:
 | 
				
			||||||
 | 
					                raise ex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if len(bname) > len(ext):
 | 
				
			||||||
 | 
					                # drop the last letter of the filename
 | 
				
			||||||
 | 
					                bname = bname[:-1]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    # drop the leftmost sub-extension
 | 
				
			||||||
 | 
					                    _, ext = ext.split(".", 1)
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    # okay do the first letter then
 | 
				
			||||||
 | 
					                    ext = "." + ext[2:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fname = "{}~{}{}".format(bname, b64, ext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MultipartParser(object):
 | 
					class MultipartParser(object):
 | 
				
			||||||
    def __init__(self, log_func, sr, http_headers):
 | 
					    def __init__(self, log_func, sr, http_headers):
 | 
				
			||||||
        self.sr = sr
 | 
					        self.sr = sr
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								copyparty/web/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								copyparty/web/Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					# run me to zopfli all the static files
 | 
				
			||||||
 | 
					# which should help on really slow connections
 | 
				
			||||||
 | 
					# but then why are you using copyparty in the first place
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pk: $(addsuffix .gz, $(wildcard *.js *.css))
 | 
				
			||||||
 | 
					un: $(addsuffix .un, $(wildcard *.gz))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%.gz: %
 | 
				
			||||||
 | 
						pigz -11 -J 34 -I 5730 $<
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%.un: %
 | 
				
			||||||
 | 
						pigz -d $<
 | 
				
			||||||
@@ -68,6 +68,8 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    <script src="/.cpr/util.js{{ ts }}"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {%- if can_read %}
 | 
					    {%- if can_read %}
 | 
				
			||||||
    <script src="/.cpr/browser.js{{ ts }}"></script>
 | 
					    <script src="/.cpr/browser.js{{ ts }}"></script>
 | 
				
			||||||
    {%- endif %}
 | 
					    {%- endif %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,75 +1,9 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// error handler for mobile devices
 | 
					window.onerror = vis_exh;
 | 
				
			||||||
function hcroak(msg) {
 | 
					 | 
				
			||||||
	document.body.innerHTML = msg;
 | 
					 | 
				
			||||||
	window.onerror = undefined;
 | 
					 | 
				
			||||||
	throw 'fatal_err';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function croak(msg) {
 | 
					 | 
				
			||||||
	document.body.textContent = msg;
 | 
					 | 
				
			||||||
	window.onerror = undefined;
 | 
					 | 
				
			||||||
	throw msg;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function esc(txt) {
 | 
					 | 
				
			||||||
	return txt.replace(/[&"<>]/g, function (c) {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			'&': '&',
 | 
					 | 
				
			||||||
			'"': '"',
 | 
					 | 
				
			||||||
			'<': '<',
 | 
					 | 
				
			||||||
			'>': '>'
 | 
					 | 
				
			||||||
		}[c];
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
 | 
					 | 
				
			||||||
	window.onerror = undefined;
 | 
					 | 
				
			||||||
	var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
					 | 
				
			||||||
		esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (error) {
 | 
					 | 
				
			||||||
		var find = ['desc', 'stack', 'trace'];
 | 
					 | 
				
			||||||
		for (var a = 0; a < find.length; a++)
 | 
					 | 
				
			||||||
			if (String(error[find[a]]) !== 'undefined')
 | 
					 | 
				
			||||||
				html.push('<h2>' + find[a] + '</h2>' +
 | 
					 | 
				
			||||||
					esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	document.body.style.fontSize = '0.8em';
 | 
					 | 
				
			||||||
	document.body.style.padding = '0 1em 1em 1em';
 | 
					 | 
				
			||||||
	hcroak(html.join('\n'));
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
 | 
					 | 
				
			||||||
if (!String.prototype.endsWith) {
 | 
					 | 
				
			||||||
	String.prototype.endsWith = function (search, this_len) {
 | 
					 | 
				
			||||||
		if (this_len === undefined || this_len > this.length) {
 | 
					 | 
				
			||||||
			this_len = this.length;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return this.substring(this_len - search.length, this_len) === search;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://stackoverflow.com/a/950146
 | 
					 | 
				
			||||||
function import_js(url, cb) {
 | 
					 | 
				
			||||||
	var head = document.head || document.getElementsByTagName('head')[0];
 | 
					 | 
				
			||||||
	var script = document.createElement('script');
 | 
					 | 
				
			||||||
	script.type = 'text/javascript';
 | 
					 | 
				
			||||||
	script.src = url;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	script.onreadystatechange = cb;
 | 
					 | 
				
			||||||
	script.onload = cb;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	head.appendChild(script);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function o(id) {
 | 
					 | 
				
			||||||
	return document.getElementById(id);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function dbg(msg) {
 | 
					function dbg(msg) {
 | 
				
			||||||
	o('path').innerHTML = msg;
 | 
						ebi('path').innerHTML = msg;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ev(e) {
 | 
					function ev(e) {
 | 
				
			||||||
@@ -78,40 +12,7 @@ function ev(e) {
 | 
				
			|||||||
	return e;
 | 
						return e;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					makeSortable(ebi('files'));
 | 
				
			||||||
function sortTable(table, col) {
 | 
					 | 
				
			||||||
	var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
 | 
					 | 
				
			||||||
		th = table.tHead.rows[0].cells,
 | 
					 | 
				
			||||||
		tr = Array.prototype.slice.call(tb.rows, 0),
 | 
					 | 
				
			||||||
		i, reverse = th[col].className == 'sort1' ? -1 : 1;
 | 
					 | 
				
			||||||
	for (var a = 0, thl = th.length; a < thl; a++)
 | 
					 | 
				
			||||||
		th[a].className = '';
 | 
					 | 
				
			||||||
	th[col].className = 'sort' + reverse;
 | 
					 | 
				
			||||||
	var stype = th[col].getAttribute('sort');
 | 
					 | 
				
			||||||
	tr = tr.sort(function (a, b) {
 | 
					 | 
				
			||||||
		var v1 = a.cells[col].textContent.trim();
 | 
					 | 
				
			||||||
		var v2 = b.cells[col].textContent.trim();
 | 
					 | 
				
			||||||
		if (stype == 'int') {
 | 
					 | 
				
			||||||
			v1 = parseInt(v1.replace(/,/g, ''));
 | 
					 | 
				
			||||||
			v2 = parseInt(v2.replace(/,/g, ''));
 | 
					 | 
				
			||||||
			return reverse * (v1 - v2);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return reverse * (v1.localeCompare(v2));
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	for (i = 0; i < tr.length; ++i) tb.appendChild(tr[i]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function makeSortable(table) {
 | 
					 | 
				
			||||||
	var th = table.tHead, i;
 | 
					 | 
				
			||||||
	th && (th = th.rows[0]) && (th = th.cells);
 | 
					 | 
				
			||||||
	if (th) i = th.length;
 | 
					 | 
				
			||||||
	else return; // if no `<thead>` then do nothing
 | 
					 | 
				
			||||||
	while (--i >= 0) (function (i) {
 | 
					 | 
				
			||||||
		th[i].onclick = function () {
 | 
					 | 
				
			||||||
			sortTable(table, i);
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}(i));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
makeSortable(o('files'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// extract songs + add play column
 | 
					// extract songs + add play column
 | 
				
			||||||
@@ -124,9 +25,9 @@ var mp = (function () {
 | 
				
			|||||||
		'tracks': tracks,
 | 
							'tracks': tracks,
 | 
				
			||||||
		'cover_url': ''
 | 
							'cover_url': ''
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	var re_audio = new RegExp('\.(opus|ogg|m4a|aac|mp3|wav|flac)$', 'i');
 | 
						var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var trs = document.getElementById('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
 | 
						var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
 | 
				
			||||||
	for (var a = 0, aa = trs.length; a < aa; a++) {
 | 
						for (var a = 0, aa = trs.length; a < aa; a++) {
 | 
				
			||||||
		var tds = trs[a].getElementsByTagName('td');
 | 
							var tds = trs[a].getElementsByTagName('td');
 | 
				
			||||||
		var link = tds[1].getElementsByTagName('a')[0];
 | 
							var link = tds[1].getElementsByTagName('a')[0];
 | 
				
			||||||
@@ -142,7 +43,7 @@ var mp = (function () {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (var a = 0, aa = tracks.length; a < aa; a++)
 | 
						for (var a = 0, aa = tracks.length; a < aa; a++)
 | 
				
			||||||
		o('trk' + a).onclick = ev_play;
 | 
							ebi('trk' + a).onclick = ev_play;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret.vol = localStorage.getItem('vol');
 | 
						ret.vol = localStorage.getItem('vol');
 | 
				
			||||||
	if (ret.vol !== null)
 | 
						if (ret.vol !== null)
 | 
				
			||||||
@@ -169,8 +70,8 @@ var mp = (function () {
 | 
				
			|||||||
// toggle player widget
 | 
					// toggle player widget
 | 
				
			||||||
var widget = (function () {
 | 
					var widget = (function () {
 | 
				
			||||||
	var ret = {};
 | 
						var ret = {};
 | 
				
			||||||
	var widget = document.getElementById('widget');
 | 
						var widget = ebi('widget');
 | 
				
			||||||
	var wtoggle = document.getElementById('wtoggle');
 | 
						var wtoggle = ebi('wtoggle');
 | 
				
			||||||
	var touchmode = false;
 | 
						var touchmode = false;
 | 
				
			||||||
	var side_open = false;
 | 
						var side_open = false;
 | 
				
			||||||
	var was_paused = true;
 | 
						var was_paused = true;
 | 
				
			||||||
@@ -199,7 +100,7 @@ var widget = (function () {
 | 
				
			|||||||
	ret.paused = function (paused) {
 | 
						ret.paused = function (paused) {
 | 
				
			||||||
		if (was_paused != paused) {
 | 
							if (was_paused != paused) {
 | 
				
			||||||
			was_paused = paused;
 | 
								was_paused = paused;
 | 
				
			||||||
			o('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
								ebi('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	var click_handler = function (e) {
 | 
						var click_handler = function (e) {
 | 
				
			||||||
@@ -223,8 +124,8 @@ var widget = (function () {
 | 
				
			|||||||
// buffer/position bar
 | 
					// buffer/position bar
 | 
				
			||||||
var pbar = (function () {
 | 
					var pbar = (function () {
 | 
				
			||||||
	var r = {};
 | 
						var r = {};
 | 
				
			||||||
	r.bcan = o('barbuf');
 | 
						r.bcan = ebi('barbuf');
 | 
				
			||||||
	r.pcan = o('barpos');
 | 
						r.pcan = ebi('barpos');
 | 
				
			||||||
	r.bctx = r.bcan.getContext('2d');
 | 
						r.bctx = r.bcan.getContext('2d');
 | 
				
			||||||
	r.pctx = r.pcan.getContext('2d');
 | 
						r.pctx = r.pcan.getContext('2d');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -289,7 +190,7 @@ var pbar = (function () {
 | 
				
			|||||||
// volume bar
 | 
					// volume bar
 | 
				
			||||||
var vbar = (function () {
 | 
					var vbar = (function () {
 | 
				
			||||||
	var r = {};
 | 
						var r = {};
 | 
				
			||||||
	r.can = o('pvol');
 | 
						r.can = ebi('pvol');
 | 
				
			||||||
	r.ctx = r.can.getContext('2d');
 | 
						r.ctx = r.can.getContext('2d');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var bctx = r.ctx;
 | 
						var bctx = r.ctx;
 | 
				
			||||||
@@ -386,7 +287,7 @@ var vbar = (function () {
 | 
				
			|||||||
		else
 | 
							else
 | 
				
			||||||
			play(0);
 | 
								play(0);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	o('bplay').onclick = function (e) {
 | 
						ebi('bplay').onclick = function (e) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
		if (mp.au) {
 | 
							if (mp.au) {
 | 
				
			||||||
			if (mp.au.paused)
 | 
								if (mp.au.paused)
 | 
				
			||||||
@@ -397,15 +298,15 @@ var vbar = (function () {
 | 
				
			|||||||
		else
 | 
							else
 | 
				
			||||||
			play(0);
 | 
								play(0);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	o('bprev').onclick = function (e) {
 | 
						ebi('bprev').onclick = function (e) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
		bskip(-1);
 | 
							bskip(-1);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	o('bnext').onclick = function (e) {
 | 
						ebi('bnext').onclick = function (e) {
 | 
				
			||||||
		ev(e);
 | 
							ev(e);
 | 
				
			||||||
		bskip(1);
 | 
							bskip(1);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	o('barpos').onclick = function (e) {
 | 
						ebi('barpos').onclick = function (e) {
 | 
				
			||||||
		if (!mp.au) {
 | 
							if (!mp.au) {
 | 
				
			||||||
			//dbg((new Date()).getTime());
 | 
								//dbg((new Date()).getTime());
 | 
				
			||||||
			return play(0);
 | 
								return play(0);
 | 
				
			||||||
@@ -471,7 +372,7 @@ function ev_play(e) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setclass(id, clas) {
 | 
					function setclass(id, clas) {
 | 
				
			||||||
	o(id).setAttribute('class', clas);
 | 
						ebi(id).setAttribute('class', clas);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -567,7 +468,6 @@ function play(tid, call_depth) {
 | 
				
			|||||||
function evau_error(e) {
 | 
					function evau_error(e) {
 | 
				
			||||||
	var err = '';
 | 
						var err = '';
 | 
				
			||||||
	var eplaya = (e && e.target) || (window.event && window.event.srcElement);
 | 
						var eplaya = (e && e.target) || (window.event && window.event.srcElement);
 | 
				
			||||||
	var url = eplaya.src;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (eplaya.error.code) {
 | 
						switch (eplaya.error.code) {
 | 
				
			||||||
		case eplaya.error.MEDIA_ERR_ABORTED:
 | 
							case eplaya.error.MEDIA_ERR_ABORTED:
 | 
				
			||||||
@@ -608,20 +508,20 @@ function show_modal(html) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// hide fullscreen message
 | 
					// hide fullscreen message
 | 
				
			||||||
function unblocked() {
 | 
					function unblocked() {
 | 
				
			||||||
	var dom = o('blocked');
 | 
						var dom = ebi('blocked');
 | 
				
			||||||
	if (dom)
 | 
						if (dom)
 | 
				
			||||||
		dom.parentNode.removeChild(dom);
 | 
							dom.parentNode.removeChild(dom);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// show ui to manually start playback of a linked song
 | 
					// show ui to manually start playback of a linked song
 | 
				
			||||||
function autoplay_blocked(tid) {
 | 
					function autoplay_blocked() {
 | 
				
			||||||
	show_modal(
 | 
						show_modal(
 | 
				
			||||||
		'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
 | 
							'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
 | 
				
			||||||
		'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
 | 
							'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var go = o('blk_go');
 | 
						var go = ebi('blk_go');
 | 
				
			||||||
	var na = o('blk_na');
 | 
						var na = ebi('blk_na');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var fn = mp.tracks[mp.au.tid].split(/\//).pop();
 | 
						var fn = mp.tracks[mp.au.tid].split(/\//).pop();
 | 
				
			||||||
	fn = decodeURIComponent(fn.replace(/\+/g, ' '));
 | 
						fn = decodeURIComponent(fn.replace(/\+/g, ' '));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,7 +126,8 @@ write markdown (most html is 🙆 too)
 | 
				
			|||||||
var last_modified = {{ lastmod }};
 | 
					var last_modified = {{ lastmod }};
 | 
				
			||||||
var md_opt = {
 | 
					var md_opt = {
 | 
				
			||||||
	link_md_as_html: false,
 | 
						link_md_as_html: false,
 | 
				
			||||||
	allow_plugins: {{ md_plug }}
 | 
						allow_plugins: {{ md_plug }},
 | 
				
			||||||
 | 
						modpoll_freq: {{ md_chk_rate }}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
@@ -144,14 +145,8 @@ var md_opt = {
 | 
				
			|||||||
		toggle();
 | 
							toggle();
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (!String.startsWith) {
 | 
					 | 
				
			||||||
	String.prototype.startsWith = function(s, i) {
 | 
					 | 
				
			||||||
		i = i>0 ? i|0 : 0;
 | 
					 | 
				
			||||||
		return this.substring(i, i + s.length) === s;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	</script>
 | 
						</script>
 | 
				
			||||||
 | 
					    <script src="/.cpr/util.js"></script>
 | 
				
			||||||
	<script src="/.cpr/deps/marked.full.js"></script>
 | 
						<script src="/.cpr/deps/marked.full.js"></script>
 | 
				
			||||||
	<script src="/.cpr/md.js"></script>
 | 
						<script src="/.cpr/md.js"></script>
 | 
				
			||||||
	{%- if edit %}
 | 
						{%- if edit %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,12 @@
 | 
				
			|||||||
var dom_toc = document.getElementById('toc');
 | 
					"use strict";
 | 
				
			||||||
var dom_wrap = document.getElementById('mw');
 | 
					
 | 
				
			||||||
var dom_hbar = document.getElementById('mh');
 | 
					var dom_toc = ebi('toc');
 | 
				
			||||||
var dom_nav = document.getElementById('mn');
 | 
					var dom_wrap = ebi('mw');
 | 
				
			||||||
var dom_pre = document.getElementById('mp');
 | 
					var dom_hbar = ebi('mh');
 | 
				
			||||||
var dom_src = document.getElementById('mt');
 | 
					var dom_nav = ebi('mn');
 | 
				
			||||||
var dom_navtgl = document.getElementById('navtoggle');
 | 
					var dom_pre = ebi('mp');
 | 
				
			||||||
 | 
					var dom_src = ebi('mt');
 | 
				
			||||||
 | 
					var dom_navtgl = ebi('navtoggle');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// chrome 49 needs this
 | 
					// chrome 49 needs this
 | 
				
			||||||
@@ -34,7 +36,7 @@ function cls(dom, name, add) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function static(obj) {
 | 
					function statify(obj) {
 | 
				
			||||||
    return JSON.parse(JSON.stringify(obj));
 | 
					    return JSON.parse(JSON.stringify(obj));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -158,6 +160,46 @@ function copydom(src, dst, lv) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function md_plug_err(ex, js) {
 | 
				
			||||||
 | 
					    var errbox = ebi('md_errbox');
 | 
				
			||||||
 | 
					    if (errbox)
 | 
				
			||||||
 | 
					        errbox.parentNode.removeChild(errbox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!ex)
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var msg = (ex + '').split('\n')[0];
 | 
				
			||||||
 | 
					    var ln = ex.lineNumber;
 | 
				
			||||||
 | 
					    var o = null;
 | 
				
			||||||
 | 
					    if (ln) {
 | 
				
			||||||
 | 
					        msg = "Line " + ln + ", " + msg;
 | 
				
			||||||
 | 
					        var lns = js.split('\n');
 | 
				
			||||||
 | 
					        if (ln < lns.length) {
 | 
				
			||||||
 | 
					            o = document.createElement('span');
 | 
				
			||||||
 | 
					            o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block';
 | 
				
			||||||
 | 
					            o.textContent = lns[ln - 1];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    errbox = document.createElement('div');
 | 
				
			||||||
 | 
					    errbox.setAttribute('id', 'md_errbox');
 | 
				
			||||||
 | 
					    errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
 | 
				
			||||||
 | 
					    errbox.textContent = msg;
 | 
				
			||||||
 | 
					    errbox.onclick = function () {
 | 
				
			||||||
 | 
					        alert('' + ex.stack);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (o) {
 | 
				
			||||||
 | 
					        errbox.appendChild(o);
 | 
				
			||||||
 | 
					        errbox.style.padding = '.25em .5em';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    dom_nav.appendChild(errbox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        console.trace();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    catch (ex2) { }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function load_plug(md_text, plug_type) {
 | 
					function load_plug(md_text, plug_type) {
 | 
				
			||||||
    if (!md_opt.allow_plugins)
 | 
					    if (!md_opt.allow_plugins)
 | 
				
			||||||
        return md_text;
 | 
					        return md_text;
 | 
				
			||||||
@@ -177,7 +219,14 @@ function load_plug(md_text, plug_type) {
 | 
				
			|||||||
    var old_plug = md_plug[plug_type];
 | 
					    var old_plug = md_plug[plug_type];
 | 
				
			||||||
    if (!old_plug || old_plug[1] != js) {
 | 
					    if (!old_plug || old_plug[1] != js) {
 | 
				
			||||||
        js = 'const x = { ' + js + ' }; x;';
 | 
					        js = 'const x = { ' + js + ' }; x;';
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            var x = eval(js);
 | 
					            var x = eval(js);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (ex) {
 | 
				
			||||||
 | 
					            md_plug[plug_type] = null;
 | 
				
			||||||
 | 
					            md_plug_err(ex, js);
 | 
				
			||||||
 | 
					            return md;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (x['ctor']) {
 | 
					        if (x['ctor']) {
 | 
				
			||||||
            x['ctor']();
 | 
					            x['ctor']();
 | 
				
			||||||
            delete x['ctor'];
 | 
					            delete x['ctor'];
 | 
				
			||||||
@@ -191,20 +240,30 @@ function load_plug(md_text, plug_type) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function convert_markdown(md_text, dest_dom) {
 | 
					function convert_markdown(md_text, dest_dom) {
 | 
				
			||||||
    md_text = md_text.replace(/\r/g, '');
 | 
					    md_text = md_text.replace(/\r/g, '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    md_plug_err(null);
 | 
				
			||||||
    md_text = load_plug(md_text, 'pre');
 | 
					    md_text = load_plug(md_text, 'pre');
 | 
				
			||||||
    md_text = load_plug(md_text, 'post');
 | 
					    md_text = load_plug(md_text, 'post');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    marked.setOptions({
 | 
					    var marked_opts = {
 | 
				
			||||||
        //headerPrefix: 'h-',
 | 
					        //headerPrefix: 'h-',
 | 
				
			||||||
        breaks: true,
 | 
					        breaks: true,
 | 
				
			||||||
        gfm: true
 | 
					        gfm: true
 | 
				
			||||||
    });
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (md_plug['pre']) {
 | 
					    var ext = md_plug['pre'];
 | 
				
			||||||
        marked.use(md_plug['pre'][0]);
 | 
					    if (ext)
 | 
				
			||||||
 | 
					        Object.assign(marked_opts, ext[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        var md_html = marked(md_text, marked_opts);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    catch (ex) {
 | 
				
			||||||
 | 
					        if (ext)
 | 
				
			||||||
 | 
					            md_plug_err(ex, ext[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var md_html = marked(md_text);
 | 
					        throw ex;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var md_dom = new DOMParser().parseFromString(md_html, "text/html").body;
 | 
					    var md_dom = new DOMParser().parseFromString(md_html, "text/html").body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var nodes = md_dom.getElementsByTagName('a');
 | 
					    var nodes = md_dom.getElementsByTagName('a');
 | 
				
			||||||
@@ -240,7 +299,7 @@ function convert_markdown(md_text, dest_dom) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // separate <code> for each line in <pre>
 | 
					    // separate <code> for each line in <pre>
 | 
				
			||||||
    var nodes = md_dom.getElementsByTagName('pre');
 | 
					    nodes = md_dom.getElementsByTagName('pre');
 | 
				
			||||||
    for (var a = nodes.length - 1; a >= 0; a--) {
 | 
					    for (var a = nodes.length - 1; a >= 0; a--) {
 | 
				
			||||||
        var el = nodes[a];
 | 
					        var el = nodes[a];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -286,15 +345,29 @@ function convert_markdown(md_text, dest_dom) {
 | 
				
			|||||||
        el.innerHTML = '<a href="#' + id + '">' + el.innerHTML + '</a>';
 | 
					        el.innerHTML = '<a href="#' + id + '">' + el.innerHTML + '</a>';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (md_plug['post'])
 | 
					    ext = md_plug['post'];
 | 
				
			||||||
        md_plug['post'][0].render(md_dom);
 | 
					    if (ext && ext[0].render)
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            ext[0].render(md_dom);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (ex) {
 | 
				
			||||||
 | 
					            md_plug_err(ex, ext[1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    copydom(md_dom, dest_dom, 0);
 | 
					    copydom(md_dom, dest_dom, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ext && ext[0].render2)
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            ext[0].render2(dest_dom);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (ex) {
 | 
				
			||||||
 | 
					            md_plug_err(ex, ext[1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init_toc() {
 | 
					function init_toc() {
 | 
				
			||||||
    var loader = document.getElementById('ml');
 | 
					    var loader = ebi('ml');
 | 
				
			||||||
    loader.parentNode.removeChild(loader);
 | 
					    loader.parentNode.removeChild(loader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var anchors = [];  // list of toc entries, complex objects
 | 
					    var anchors = [];  // list of toc entries, complex objects
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -77,32 +77,52 @@ html.dark #mt {
 | 
				
			|||||||
    background: #f97;
 | 
					    background: #f97;
 | 
				
			||||||
    border-radius: .15em;
 | 
					    border-radius: .15em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					html.dark #save.force-save {
 | 
				
			||||||
 | 
					    color: #fca;
 | 
				
			||||||
 | 
					    background: #720;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#save.disabled {
 | 
					#save.disabled {
 | 
				
			||||||
    opacity: .4;
 | 
					    opacity: .4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#helpbox,
 | 
				
			||||||
 | 
					#toast {
 | 
				
			||||||
 | 
					    background: #f7f7f7;
 | 
				
			||||||
 | 
					    border-radius: .4em;
 | 
				
			||||||
 | 
					    z-index: 9001;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#helpbox {
 | 
					#helpbox {
 | 
				
			||||||
    display: none;
 | 
					    display: none;
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
    background: #f7f7f7;
 | 
					 | 
				
			||||||
    box-shadow: 0 .5em 2em #777;
 | 
					 | 
				
			||||||
    border-radius: .4em;
 | 
					 | 
				
			||||||
    padding: 2em;
 | 
					    padding: 2em;
 | 
				
			||||||
    top: 4em;
 | 
					    top: 4em;
 | 
				
			||||||
    overflow-y: auto;
 | 
					    overflow-y: auto;
 | 
				
			||||||
 | 
					    box-shadow: 0 .5em 2em #777;
 | 
				
			||||||
    height: calc(100% - 12em);
 | 
					    height: calc(100% - 12em);
 | 
				
			||||||
    left: calc(50% - 15em);
 | 
					    left: calc(50% - 15em);
 | 
				
			||||||
    right: 0;
 | 
					    right: 0;
 | 
				
			||||||
    width: 30em;
 | 
					    width: 30em;
 | 
				
			||||||
    z-index: 9001;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#helpclose {
 | 
					#helpclose {
 | 
				
			||||||
    display: block;
 | 
					    display: block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
html.dark #helpbox {
 | 
					html.dark #helpbox {
 | 
				
			||||||
    background: #222;
 | 
					 | 
				
			||||||
    box-shadow: 0 .5em 2em #444;
 | 
					    box-shadow: 0 .5em 2em #444;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					html.dark #helpbox,
 | 
				
			||||||
 | 
					html.dark #toast {
 | 
				
			||||||
 | 
					    background: #222;
 | 
				
			||||||
    border: 1px solid #079;
 | 
					    border: 1px solid #079;
 | 
				
			||||||
    border-width: 1px 0;
 | 
					    border-width: 1px 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#toast {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    padding: .6em 0;
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    z-index: 9001;
 | 
				
			||||||
 | 
					    top: 30%;
 | 
				
			||||||
 | 
					    transition: opacity 0.2s ease-in-out;
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# mt {opacity: .5;top:1px}
 | 
					# mt {opacity: .5;top:1px}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,6 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// server state
 | 
					// server state
 | 
				
			||||||
var server_md = dom_src.value;
 | 
					var server_md = dom_src.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,15 +11,15 @@ var js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\'');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// dom nodes
 | 
					// dom nodes
 | 
				
			||||||
var dom_swrap = document.getElementById('mtw');
 | 
					var dom_swrap = ebi('mtw');
 | 
				
			||||||
var dom_sbs = document.getElementById('sbs');
 | 
					var dom_sbs = ebi('sbs');
 | 
				
			||||||
var dom_nsbs = document.getElementById('nsbs');
 | 
					var dom_nsbs = ebi('nsbs');
 | 
				
			||||||
var dom_tbox = document.getElementById('toolsbox');
 | 
					var dom_tbox = ebi('toolsbox');
 | 
				
			||||||
var dom_ref = (function () {
 | 
					var dom_ref = (function () {
 | 
				
			||||||
    var d = document.createElement('div');
 | 
					    var d = document.createElement('div');
 | 
				
			||||||
    d.setAttribute('id', 'mtr');
 | 
					    d.setAttribute('id', 'mtr');
 | 
				
			||||||
    dom_swrap.appendChild(d);
 | 
					    dom_swrap.appendChild(d);
 | 
				
			||||||
    d = document.getElementById('mtr');
 | 
					    d = ebi('mtr');
 | 
				
			||||||
    // hide behind the textarea (offsetTop is not computed if display:none)
 | 
					    // hide behind the textarea (offsetTop is not computed if display:none)
 | 
				
			||||||
    dom_src.style.zIndex = '4';
 | 
					    dom_src.style.zIndex = '4';
 | 
				
			||||||
    d.style.zIndex = '3';
 | 
					    d.style.zIndex = '3';
 | 
				
			||||||
@@ -105,7 +108,7 @@ var draw_md = (function () {
 | 
				
			|||||||
        map_src = genmap(dom_ref, map_src);
 | 
					        map_src = genmap(dom_ref, map_src);
 | 
				
			||||||
        map_pre = genmap(dom_pre, map_pre);
 | 
					        map_pre = genmap(dom_pre, map_pre);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cls(document.getElementById('save'), 'disabled', src == server_md);
 | 
					        cls(ebi('save'), 'disabled', src == server_md);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var t1 = new Date().getTime();
 | 
					        var t1 = new Date().getTime();
 | 
				
			||||||
        delay = t1 - t0 > 100 ? 25 : 1;
 | 
					        delay = t1 - t0 > 100 ? 25 : 1;
 | 
				
			||||||
@@ -141,7 +144,7 @@ redraw = (function () {
 | 
				
			|||||||
        onresize();
 | 
					        onresize();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    function modetoggle() {
 | 
					    function modetoggle() {
 | 
				
			||||||
        mode = dom_nsbs.innerHTML;
 | 
					        var mode = dom_nsbs.innerHTML;
 | 
				
			||||||
        dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
 | 
					        dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
 | 
				
			||||||
        mode += ' single';
 | 
					        mode += ' single';
 | 
				
			||||||
        dom_wrap.setAttribute('class', mode);
 | 
					        dom_wrap.setAttribute('class', mode);
 | 
				
			||||||
@@ -177,7 +180,7 @@ redraw = (function () {
 | 
				
			|||||||
        y += src.clientHeight / 2;
 | 
					        y += src.clientHeight / 2;
 | 
				
			||||||
        var sy1 = -1, sy2 = -1, dy1 = -1, dy2 = -1;
 | 
					        var sy1 = -1, sy2 = -1, dy1 = -1, dy2 = -1;
 | 
				
			||||||
        for (var a = 1; a < nlines + 1; a++) {
 | 
					        for (var a = 1; a < nlines + 1; a++) {
 | 
				
			||||||
            if (srcmap[a] === null || dstmap[a] === null)
 | 
					            if (srcmap[a] == null || dstmap[a] == null)
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (srcmap[a] > y) {
 | 
					            if (srcmap[a] > y) {
 | 
				
			||||||
@@ -220,14 +223,108 @@ redraw = (function () {
 | 
				
			|||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// modification checker
 | 
				
			||||||
 | 
					function Modpoll() {
 | 
				
			||||||
 | 
					    this.skip_one = true;
 | 
				
			||||||
 | 
					    this.disabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.periodic = function () {
 | 
				
			||||||
 | 
					        var that = this;
 | 
				
			||||||
 | 
					        setTimeout(function () {
 | 
				
			||||||
 | 
					            that.periodic();
 | 
				
			||||||
 | 
					        }, 1000 * md_opt.modpoll_freq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var skip = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (ebi('toast'))
 | 
				
			||||||
 | 
					            skip = 'toast';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else if (this.skip_one)
 | 
				
			||||||
 | 
					            skip = 'saved';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else if (this.disabled)
 | 
				
			||||||
 | 
					            skip = 'disabled';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (skip) {
 | 
				
			||||||
 | 
					            console.log('modpoll skip, ' + skip);
 | 
				
			||||||
 | 
					            this.skip_one = false;
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log('modpoll...');
 | 
				
			||||||
 | 
					        var url = (document.location + '').split('?')[0] + '?raw&_=' + new Date().getTime();
 | 
				
			||||||
 | 
					        var xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					        xhr.modpoll = this;
 | 
				
			||||||
 | 
					        xhr.open('GET', url, true);
 | 
				
			||||||
 | 
					        xhr.responseType = 'text';
 | 
				
			||||||
 | 
					        xhr.onreadystatechange = this.cb;
 | 
				
			||||||
 | 
					        xhr.send();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.cb = function () {
 | 
				
			||||||
 | 
					        if (this.modpoll.disabled || this.modpoll.skip_one) {
 | 
				
			||||||
 | 
					            console.log('modpoll abort');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.readyState != XMLHttpRequest.DONE)
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.status !== 200) {
 | 
				
			||||||
 | 
					            console.log('modpoll err ' + this.status + ": " + this.responseText);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.responseText)
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var server_ref = server_md.replace(/\r/g, '');
 | 
				
			||||||
 | 
					        var server_now = this.responseText.replace(/\r/g, '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (server_ref != server_now) {
 | 
				
			||||||
 | 
					            console.log("modpoll diff |" + server_ref.length + "|, |" + server_now.length + "|");
 | 
				
			||||||
 | 
					            this.modpoll.disabled = true;
 | 
				
			||||||
 | 
					            var msg = [
 | 
				
			||||||
 | 
					                "The document has changed on the server.<br />" +
 | 
				
			||||||
 | 
					                "The changes will NOT be loaded into your editor automatically.",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                "Press F5 or CTRL-R to refresh the page,<br />" +
 | 
				
			||||||
 | 
					                "replacing your document with the server copy.",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                "You can click this message to ignore and contnue."
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					            return toast(false, "box-shadow:0 1em 2em rgba(64,64,64,0.8);font-weight:normal",
 | 
				
			||||||
 | 
					                36, "<p>" + msg.join('</p>\n<p>') + '</p>');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log('modpoll eq');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (md_opt.modpoll_freq > 0)
 | 
				
			||||||
 | 
					        this.periodic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					var modpoll = new Modpoll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.onbeforeunload = function (e) {
 | 
				
			||||||
 | 
					    if ((ebi("save").getAttribute('class') + '').indexOf('disabled') >= 0)
 | 
				
			||||||
 | 
					        return; //nice (todo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    e.preventDefault(); //ff
 | 
				
			||||||
 | 
					    e.returnValue = ''; //chrome
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// save handler
 | 
					// save handler
 | 
				
			||||||
function save(e) {
 | 
					function save(e) {
 | 
				
			||||||
    if (e) e.preventDefault();
 | 
					    if (e) e.preventDefault();
 | 
				
			||||||
    var save_btn = document.getElementById("save"),
 | 
					    var save_btn = ebi("save"),
 | 
				
			||||||
        save_cls = save_btn.getAttribute('class') + '';
 | 
					        save_cls = save_btn.getAttribute('class') + '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (save_cls.indexOf('disabled') >= 0) {
 | 
					    if (save_cls.indexOf('disabled') >= 0) {
 | 
				
			||||||
        toast('font-size:2em;color:#fc6;width:9em;', 'no changes');
 | 
					        toast(true, ";font-size:2em;color:#c90", 9, "no changes");
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -251,6 +348,8 @@ function save(e) {
 | 
				
			|||||||
    xhr.onreadystatechange = save_cb;
 | 
					    xhr.onreadystatechange = save_cb;
 | 
				
			||||||
    xhr.btn = save_btn;
 | 
					    xhr.btn = save_btn;
 | 
				
			||||||
    xhr.txt = txt;
 | 
					    xhr.txt = txt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    modpoll.skip_one = true;  // skip one iteration while we save
 | 
				
			||||||
    xhr.send(fd);
 | 
					    xhr.send(fd);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -344,23 +443,44 @@ function savechk_cb() {
 | 
				
			|||||||
    last_modified = this.lastmod;
 | 
					    last_modified = this.lastmod;
 | 
				
			||||||
    server_md = this.txt;
 | 
					    server_md = this.txt;
 | 
				
			||||||
    draw_md();
 | 
					    draw_md();
 | 
				
			||||||
    toast('font-size:6em;font-family:serif;color:#cf6;width:4em;',
 | 
					    toast(true, ";font-size:6em;font-family:serif;color:#9b4", 4,
 | 
				
			||||||
        'OK✔️<span style="font-size:.2em;color:#999;position:absolute">' + this.ntry + '</span>');
 | 
					        'OK✔️<span style="font-size:.2em;color:#999;position:absolute">' + this.ntry + '</span>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    modpoll.disabled = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toast(style, msg) {
 | 
					function toast(autoclose, style, width, msg) {
 | 
				
			||||||
    var ok = document.createElement('div');
 | 
					    var ok = ebi("toast");
 | 
				
			||||||
    style += 'font-weight:bold;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1';
 | 
					    if (ok)
 | 
				
			||||||
 | 
					        ok.parentNode.removeChild(ok);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    style = "width:" + width + "em;left:calc(50% - " + (width / 2) + "em);" + style;
 | 
				
			||||||
 | 
					    ok = document.createElement('div');
 | 
				
			||||||
 | 
					    ok.setAttribute('id', 'toast');
 | 
				
			||||||
    ok.setAttribute('style', style);
 | 
					    ok.setAttribute('style', style);
 | 
				
			||||||
    ok.innerHTML = msg;
 | 
					    ok.innerHTML = msg;
 | 
				
			||||||
    var parent = document.getElementById('m');
 | 
					    var parent = ebi('m');
 | 
				
			||||||
    document.documentElement.appendChild(ok);
 | 
					    document.documentElement.appendChild(ok);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var hide = function (delay) {
 | 
				
			||||||
 | 
					        delay = delay || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setTimeout(function () {
 | 
					        setTimeout(function () {
 | 
				
			||||||
            ok.style.opacity = 0;
 | 
					            ok.style.opacity = 0;
 | 
				
			||||||
    }, 500);
 | 
					        }, delay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setTimeout(function () {
 | 
					        setTimeout(function () {
 | 
				
			||||||
 | 
					            if (ok.parentNode)
 | 
				
			||||||
                ok.parentNode.removeChild(ok);
 | 
					                ok.parentNode.removeChild(ok);
 | 
				
			||||||
    }, 750);
 | 
					        }, delay + 250);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ok.onclick = function () {
 | 
				
			||||||
 | 
					        hide(0);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (autoclose)
 | 
				
			||||||
 | 
					        hide(500);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -540,6 +660,10 @@ function md_backspace() {
 | 
				
			|||||||
    if (/^\s*$/.test(left))
 | 
					    if (/^\s*$/.test(left))
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // same if selection
 | 
				
			||||||
 | 
					    if (o0 != dom_src.selectionEnd)
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // same if line is all-whitespace or non-markup
 | 
					    // same if line is all-whitespace or non-markup
 | 
				
			||||||
    var v = m[0].replace(/[^ ]/g, " ");
 | 
					    var v = m[0].replace(/[^ ]/g, " ");
 | 
				
			||||||
    if (v === m[0] || v.length !== left.length)
 | 
					    if (v === m[0] || v.length !== left.length)
 | 
				
			||||||
@@ -623,7 +747,8 @@ function fmt_table(e) {
 | 
				
			|||||||
        lpipe = tab[1].indexOf('|') < tab[1].indexOf('-'),
 | 
					        lpipe = tab[1].indexOf('|') < tab[1].indexOf('-'),
 | 
				
			||||||
        rpipe = tab[1].lastIndexOf('|') > tab[1].lastIndexOf('-'),
 | 
					        rpipe = tab[1].lastIndexOf('|') > tab[1].lastIndexOf('-'),
 | 
				
			||||||
        re_lpipe = lpipe ? /^\s*\|\s*/ : /^\s*/,
 | 
					        re_lpipe = lpipe ? /^\s*\|\s*/ : /^\s*/,
 | 
				
			||||||
        re_rpipe = rpipe ? /\s*\|\s*$/ : /\s*$/;
 | 
					        re_rpipe = rpipe ? /\s*\|\s*$/ : /\s*$/,
 | 
				
			||||||
 | 
					        ncols;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // the second row defines the table,
 | 
					    // the second row defines the table,
 | 
				
			||||||
    // need to process that first
 | 
					    // need to process that first
 | 
				
			||||||
@@ -680,6 +805,7 @@ function fmt_table(e) {
 | 
				
			|||||||
    for (var col = 0; col < ncols; col++) {
 | 
					    for (var col = 0; col < ncols; col++) {
 | 
				
			||||||
        var max = 0;
 | 
					        var max = 0;
 | 
				
			||||||
        for (var row = 0; row < tab.length; row++)
 | 
					        for (var row = 0; row < tab.length; row++)
 | 
				
			||||||
 | 
					            if (row != 1)
 | 
				
			||||||
                max = Math.max(max, tab[row][col].length);
 | 
					                max = Math.max(max, tab[row][col].length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var s = '';
 | 
					        var s = '';
 | 
				
			||||||
@@ -747,8 +873,7 @@ function mark_uni(e) {
 | 
				
			|||||||
    dom_tbox.setAttribute('class', '');
 | 
					    dom_tbox.setAttribute('class', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var txt = dom_src.value,
 | 
					    var txt = dom_src.value,
 | 
				
			||||||
        ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g');
 | 
					        ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g'),
 | 
				
			||||||
 | 
					 | 
				
			||||||
        mod = txt.replace(/\r/g, "").replace(ptn, "\u2588\u2770$1\u2771");
 | 
					        mod = txt.replace(/\r/g, "").replace(ptn, "\u2588\u2770$1\u2771");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (txt == mod) {
 | 
					    if (txt == mod) {
 | 
				
			||||||
@@ -785,7 +910,12 @@ function iter_uni(e) {
 | 
				
			|||||||
// configure whitelist
 | 
					// configure whitelist
 | 
				
			||||||
function cfg_uni(e) {
 | 
					function cfg_uni(e) {
 | 
				
			||||||
    if (e) e.preventDefault();
 | 
					    if (e) e.preventDefault();
 | 
				
			||||||
    esc_uni_whitelist = prompt("unicode whitelist", esc_uni_whitelist);
 | 
					
 | 
				
			||||||
 | 
					    var reply = prompt("unicode whitelist", esc_uni_whitelist);
 | 
				
			||||||
 | 
					    if (reply === null)
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    esc_uni_whitelist = reply;
 | 
				
			||||||
    js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\'');
 | 
					    js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\'');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -802,7 +932,7 @@ function cfg_uni(e) {
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (ev.code == "Escape" || kc == 27) {
 | 
					        if (ev.code == "Escape" || kc == 27) {
 | 
				
			||||||
            var d = document.getElementById('helpclose');
 | 
					            var d = ebi('helpclose');
 | 
				
			||||||
            if (d)
 | 
					            if (d)
 | 
				
			||||||
                d.click();
 | 
					                d.click();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -859,22 +989,22 @@ function cfg_uni(e) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    document.onkeydown = keydown;
 | 
					    document.onkeydown = keydown;
 | 
				
			||||||
    document.getElementById('save').onclick = save;
 | 
					    ebi('save').onclick = save;
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('tools').onclick = function (e) {
 | 
					ebi('tools').onclick = function (e) {
 | 
				
			||||||
    if (e) e.preventDefault();
 | 
					    if (e) e.preventDefault();
 | 
				
			||||||
    var is_open = dom_tbox.getAttribute('class') != 'open';
 | 
					    var is_open = dom_tbox.getAttribute('class') != 'open';
 | 
				
			||||||
    dom_tbox.setAttribute('class', is_open ? 'open' : '');
 | 
					    dom_tbox.setAttribute('class', is_open ? 'open' : '');
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('help').onclick = function (e) {
 | 
					ebi('help').onclick = function (e) {
 | 
				
			||||||
    if (e) e.preventDefault();
 | 
					    if (e) e.preventDefault();
 | 
				
			||||||
    dom_tbox.setAttribute('class', '');
 | 
					    dom_tbox.setAttribute('class', '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var dom = document.getElementById('helpbox');
 | 
					    var dom = ebi('helpbox');
 | 
				
			||||||
    var dtxt = dom.getElementsByTagName('textarea');
 | 
					    var dtxt = dom.getElementsByTagName('textarea');
 | 
				
			||||||
    if (dtxt.length > 0) {
 | 
					    if (dtxt.length > 0) {
 | 
				
			||||||
        convert_markdown(dtxt[0].value, dom);
 | 
					        convert_markdown(dtxt[0].value, dom);
 | 
				
			||||||
@@ -882,16 +1012,16 @@ document.getElementById('help').onclick = function (e) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dom.style.display = 'block';
 | 
					    dom.style.display = 'block';
 | 
				
			||||||
    document.getElementById('helpclose').onclick = function () {
 | 
					    ebi('helpclose').onclick = function () {
 | 
				
			||||||
        dom.style.display = 'none';
 | 
					        dom.style.display = 'none';
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.getElementById('fmt_table').onclick = fmt_table;
 | 
					ebi('fmt_table').onclick = fmt_table;
 | 
				
			||||||
document.getElementById('mark_uni').onclick = mark_uni;
 | 
					ebi('mark_uni').onclick = mark_uni;
 | 
				
			||||||
document.getElementById('iter_uni').onclick = iter_uni;
 | 
					ebi('iter_uni').onclick = iter_uni;
 | 
				
			||||||
document.getElementById('cfg_uni').onclick = cfg_uni;
 | 
					ebi('cfg_uni').onclick = cfg_uni;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// blame steen
 | 
					// blame steen
 | 
				
			||||||
@@ -999,13 +1129,12 @@ action_stack = (function () {
 | 
				
			|||||||
        ref = newtxt;
 | 
					        ref = newtxt;
 | 
				
			||||||
        dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
 | 
					        dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
 | 
				
			||||||
        if (hist.un.length > 0)
 | 
					        if (hist.un.length > 0)
 | 
				
			||||||
            dbg(static(hist.un.slice(-1)[0]));
 | 
					            dbg(statify(hist.un.slice(-1)[0]));
 | 
				
			||||||
        if (hist.re.length > 0)
 | 
					        if (hist.re.length > 0)
 | 
				
			||||||
            dbg(static(hist.re.slice(-1)[0]));
 | 
					            dbg(statify(hist.re.slice(-1)[0]));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        push: push,
 | 
					 | 
				
			||||||
        undo: undo,
 | 
					        undo: undo,
 | 
				
			||||||
        redo: redo,
 | 
					        redo: redo,
 | 
				
			||||||
        push: schedule_push,
 | 
					        push: schedule_push,
 | 
				
			||||||
@@ -1015,7 +1144,7 @@ action_stack = (function () {
 | 
				
			|||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
document.getElementById('help').onclick = function () {
 | 
					ebi('help').onclick = function () {
 | 
				
			||||||
    var c1 = getComputedStyle(dom_src).cssText.split(';');
 | 
					    var c1 = getComputedStyle(dom_src).cssText.split(';');
 | 
				
			||||||
    var c2 = getComputedStyle(dom_ref).cssText.split(';');
 | 
					    var c2 = getComputedStyle(dom_ref).cssText.split(';');
 | 
				
			||||||
    var max = Math.min(c1.length, c2.length);
 | 
					    var max = Math.min(c1.length, c2.length);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,8 @@
 | 
				
			|||||||
var last_modified = {{ lastmod }};
 | 
					var last_modified = {{ lastmod }};
 | 
				
			||||||
var md_opt = {
 | 
					var md_opt = {
 | 
				
			||||||
	link_md_as_html: false,
 | 
						link_md_as_html: false,
 | 
				
			||||||
	allow_plugins: {{ md_plug }}
 | 
						allow_plugins: {{ md_plug }},
 | 
				
			||||||
 | 
						modpoll_freq: {{ md_chk_rate }}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var lightswitch = (function () {
 | 
					var lightswitch = (function () {
 | 
				
			||||||
@@ -42,6 +43,7 @@ var lightswitch = (function () {
 | 
				
			|||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	</script>
 | 
						</script>
 | 
				
			||||||
 | 
					    <script src="/.cpr/util.js"></script>
 | 
				
			||||||
	<script src="/.cpr/deps/easymde.js"></script>
 | 
						<script src="/.cpr/deps/easymde.js"></script>
 | 
				
			||||||
	<script src="/.cpr/mde.js"></script>
 | 
						<script src="/.cpr/mde.js"></script>
 | 
				
			||||||
</body></html>
 | 
					</body></html>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
var dom_wrap = document.getElementById('mw');
 | 
					"use strict";
 | 
				
			||||||
var dom_nav = document.getElementById('mn');
 | 
					
 | 
				
			||||||
var dom_doc = document.getElementById('m');
 | 
					var dom_wrap = ebi('mw');
 | 
				
			||||||
var dom_md = document.getElementById('mt');
 | 
					var dom_nav = ebi('mn');
 | 
				
			||||||
 | 
					var dom_doc = ebi('m');
 | 
				
			||||||
 | 
					var dom_md = ebi('mt');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
    var n = document.location + '';
 | 
					    var n = document.location + '';
 | 
				
			||||||
@@ -63,7 +65,7 @@ var mde = (function () {
 | 
				
			|||||||
    mde.codemirror.on("change", function () {
 | 
					    mde.codemirror.on("change", function () {
 | 
				
			||||||
        md_changed(mde);
 | 
					        md_changed(mde);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    var loader = document.getElementById('ml');
 | 
					    var loader = ebi('ml');
 | 
				
			||||||
    loader.parentNode.removeChild(loader);
 | 
					    loader.parentNode.removeChild(loader);
 | 
				
			||||||
    return mde;
 | 
					    return mde;
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
@@ -213,7 +215,7 @@ function save_chk() {
 | 
				
			|||||||
    var ok = document.createElement('div');
 | 
					    var ok = document.createElement('div');
 | 
				
			||||||
    ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1');
 | 
					    ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1');
 | 
				
			||||||
    ok.innerHTML = 'OK✔️';
 | 
					    ok.innerHTML = 'OK✔️';
 | 
				
			||||||
    var parent = document.getElementById('m');
 | 
					    var parent = ebi('m');
 | 
				
			||||||
    document.documentElement.appendChild(ok);
 | 
					    document.documentElement.appendChild(ok);
 | 
				
			||||||
    setTimeout(function () {
 | 
					    setTimeout(function () {
 | 
				
			||||||
        ok.style.opacity = 0;
 | 
					        ok.style.opacity = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,61 +1,6 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// error handler for mobile devices
 | 
					window.onerror = vis_exh;
 | 
				
			||||||
function hcroak(msg) {
 | 
					 | 
				
			||||||
    document.body.innerHTML = msg;
 | 
					 | 
				
			||||||
    window.onerror = undefined;
 | 
					 | 
				
			||||||
    throw 'fatal_err';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function croak(msg) {
 | 
					 | 
				
			||||||
    document.body.textContent = msg;
 | 
					 | 
				
			||||||
    window.onerror = undefined;
 | 
					 | 
				
			||||||
    throw msg;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function esc(txt) {
 | 
					 | 
				
			||||||
    return txt.replace(/[&"<>]/g, function (c) {
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            '&': '&',
 | 
					 | 
				
			||||||
            '"': '"',
 | 
					 | 
				
			||||||
            '<': '<',
 | 
					 | 
				
			||||||
            '>': '>'
 | 
					 | 
				
			||||||
        }[c];
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
 | 
					 | 
				
			||||||
    window.onerror = undefined;
 | 
					 | 
				
			||||||
    var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
					 | 
				
			||||||
        esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (error) {
 | 
					 | 
				
			||||||
        var find = ['desc', 'stack', 'trace'];
 | 
					 | 
				
			||||||
        for (var a = 0; a < find.length; a++)
 | 
					 | 
				
			||||||
            if (String(error[find[a]]) !== 'undefined')
 | 
					 | 
				
			||||||
                html.push('<h2>' + find[a] + '</h2>' +
 | 
					 | 
				
			||||||
                    esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    document.body.style.fontSize = '0.8em';
 | 
					 | 
				
			||||||
    document.body.style.padding = '0 1em 1em 1em';
 | 
					 | 
				
			||||||
    hcroak(html.join('\n'));
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://stackoverflow.com/a/950146
 | 
					 | 
				
			||||||
function import_js(url, cb) {
 | 
					 | 
				
			||||||
    var head = document.head || document.getElementsByTagName('head')[0];
 | 
					 | 
				
			||||||
    var script = document.createElement('script');
 | 
					 | 
				
			||||||
    script.type = 'text/javascript';
 | 
					 | 
				
			||||||
    script.src = url;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    script.onreadystatechange = cb;
 | 
					 | 
				
			||||||
    script.onload = cb;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    head.appendChild(script);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function o(id) {
 | 
					 | 
				
			||||||
    return document.getElementById(id);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
@@ -88,12 +33,12 @@ function goto(dest) {
 | 
				
			|||||||
    for (var a = obj.length - 1; a >= 0; a--)
 | 
					    for (var a = obj.length - 1; a >= 0; a--)
 | 
				
			||||||
        obj[a].classList.remove('act');
 | 
					        obj[a].classList.remove('act');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var obj = document.querySelectorAll('#ops>a');
 | 
					    obj = document.querySelectorAll('#ops>a');
 | 
				
			||||||
    for (var a = obj.length - 1; a >= 0; a--)
 | 
					    for (var a = obj.length - 1; a >= 0; a--)
 | 
				
			||||||
        obj[a].classList.remove('act');
 | 
					        obj[a].classList.remove('act');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (dest) {
 | 
					    if (dest) {
 | 
				
			||||||
        document.getElementById('op_' + dest).classList.add('act');
 | 
					        ebi('op_' + dest).classList.add('act');
 | 
				
			||||||
        document.querySelector('#ops>a[data-dest=' + dest + ']').classList.add('act');
 | 
					        document.querySelector('#ops>a[data-dest=' + dest + ']').classList.add('act');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var fn = window['goto_' + dest];
 | 
					        var fn = window['goto_' + dest];
 | 
				
			||||||
@@ -121,7 +66,7 @@ function goto_up2k() {
 | 
				
			|||||||
        if (op !== null && op !== '.')
 | 
					        if (op !== null && op !== '.')
 | 
				
			||||||
            goto(op);
 | 
					            goto(op);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    document.getElementById('ops').style.display = 'block';
 | 
					    ebi('ops').style.display = 'block';
 | 
				
			||||||
})();
 | 
					})();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -150,21 +95,21 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // show modal message
 | 
					    // show modal message
 | 
				
			||||||
    function showmodal(msg) {
 | 
					    function showmodal(msg) {
 | 
				
			||||||
        o('u2notbtn').innerHTML = msg;
 | 
					        ebi('u2notbtn').innerHTML = msg;
 | 
				
			||||||
        o('u2btn').style.display = 'none';
 | 
					        ebi('u2btn').style.display = 'none';
 | 
				
			||||||
        o('u2notbtn').style.display = 'block';
 | 
					        ebi('u2notbtn').style.display = 'block';
 | 
				
			||||||
        o('u2conf').style.opacity = '0.5';
 | 
					        ebi('u2conf').style.opacity = '0.5';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // hide modal message
 | 
					    // hide modal message
 | 
				
			||||||
    function unmodal() {
 | 
					    function unmodal() {
 | 
				
			||||||
        o('u2notbtn').style.display = 'none';
 | 
					        ebi('u2notbtn').style.display = 'none';
 | 
				
			||||||
        o('u2btn').style.display = 'block';
 | 
					        ebi('u2btn').style.display = 'block';
 | 
				
			||||||
        o('u2conf').style.opacity = '1';
 | 
					        ebi('u2conf').style.opacity = '1';
 | 
				
			||||||
        o('u2notbtn').innerHTML = '';
 | 
					        ebi('u2notbtn').innerHTML = '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var post_url = o('op_bup').getElementsByTagName('form')[0].getAttribute('action');
 | 
					    var post_url = ebi('op_bup').getElementsByTagName('form')[0].getAttribute('action');
 | 
				
			||||||
    if (post_url && post_url.charAt(post_url.length - 1) !== '/')
 | 
					    if (post_url && post_url.charAt(post_url.length - 1) !== '/')
 | 
				
			||||||
        post_url += '/';
 | 
					        post_url += '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -181,25 +126,25 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            import_js('/.cpr/deps/sha512.js', unmodal);
 | 
					            import_js('/.cpr/deps/sha512.js', unmodal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (is_https)
 | 
					            if (is_https)
 | 
				
			||||||
                o('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500kB/s at best';
 | 
					                ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500kB/s at best';
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
                o('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance';
 | 
					                ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // show uploader if the user only has write-access
 | 
					    // show uploader if the user only has write-access
 | 
				
			||||||
    if (!o('files'))
 | 
					    if (!ebi('files'))
 | 
				
			||||||
        goto('up2k');
 | 
					        goto('up2k');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // shows or clears an error message in the basic uploader ui
 | 
					    // shows or clears an error message in the basic uploader ui
 | 
				
			||||||
    function setmsg(msg) {
 | 
					    function setmsg(msg) {
 | 
				
			||||||
        if (msg !== undefined) {
 | 
					        if (msg !== undefined) {
 | 
				
			||||||
            o('u2err').setAttribute('class', 'err');
 | 
					            ebi('u2err').setAttribute('class', 'err');
 | 
				
			||||||
            o('u2err').innerHTML = msg;
 | 
					            ebi('u2err').innerHTML = msg;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            o('u2err').setAttribute('class', '');
 | 
					            ebi('u2err').setAttribute('class', '');
 | 
				
			||||||
            o('u2err').innerHTML = '';
 | 
					            ebi('u2err').innerHTML = '';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -210,7 +155,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // handle user intent to use the basic uploader instead
 | 
					    // handle user intent to use the basic uploader instead
 | 
				
			||||||
    o('u2nope').onclick = function (e) {
 | 
					    ebi('u2nope').onclick = function (e) {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
        setmsg('');
 | 
					        setmsg('');
 | 
				
			||||||
        goto('bup');
 | 
					        goto('bup');
 | 
				
			||||||
@@ -229,9 +174,9 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    function cfg_get(name) {
 | 
					    function cfg_get(name) {
 | 
				
			||||||
        var val = localStorage.getItem(name);
 | 
					        var val = localStorage.getItem(name);
 | 
				
			||||||
        if (val === null)
 | 
					        if (val === null)
 | 
				
			||||||
            return parseInt(o(name).value);
 | 
					            return parseInt(ebi(name).value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        o(name).value = val;
 | 
					        ebi(name).value = val;
 | 
				
			||||||
        return val;
 | 
					        return val;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -242,7 +187,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        else
 | 
					        else
 | 
				
			||||||
            val = (val == '1');
 | 
					            val = (val == '1');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        o(name).checked = val;
 | 
					        ebi(name).checked = val;
 | 
				
			||||||
        return val;
 | 
					        return val;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -250,7 +195,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        localStorage.setItem(
 | 
					        localStorage.setItem(
 | 
				
			||||||
            name, val ? '1' : '0');
 | 
					            name, val ? '1' : '0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        o(name).checked = val;
 | 
					        ebi(name).checked = val;
 | 
				
			||||||
        return val;
 | 
					        return val;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -284,9 +229,9 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        return un2k("this is the basic uploader; up2k needs at least<br />chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1");
 | 
					        return un2k("this is the basic uploader; up2k needs at least<br />chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function nav() {
 | 
					    function nav() {
 | 
				
			||||||
        o('file' + fdom_ctr).click();
 | 
					        ebi('file' + fdom_ctr).click();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    o('u2btn').addEventListener('click', nav, false);
 | 
					    ebi('u2btn').addEventListener('click', nav, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function ondrag(ev) {
 | 
					    function ondrag(ev) {
 | 
				
			||||||
        ev.stopPropagation();
 | 
					        ev.stopPropagation();
 | 
				
			||||||
@@ -294,8 +239,8 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        ev.dataTransfer.dropEffect = 'copy';
 | 
					        ev.dataTransfer.dropEffect = 'copy';
 | 
				
			||||||
        ev.dataTransfer.effectAllowed = 'copy';
 | 
					        ev.dataTransfer.effectAllowed = 'copy';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    o('u2btn').addEventListener('dragover', ondrag, false);
 | 
					    ebi('u2btn').addEventListener('dragover', ondrag, false);
 | 
				
			||||||
    o('u2btn').addEventListener('dragenter', ondrag, false);
 | 
					    ebi('u2btn').addEventListener('dragenter', ondrag, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function gotfile(ev) {
 | 
					    function gotfile(ev) {
 | 
				
			||||||
        ev.stopPropagation();
 | 
					        ev.stopPropagation();
 | 
				
			||||||
@@ -357,7 +302,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            var tr = document.createElement('tr');
 | 
					            var tr = document.createElement('tr');
 | 
				
			||||||
            tr.innerHTML = '<td id="f{0}n"></td><td id="f{0}t">hashing</td><td id="f{0}p" class="prog"></td>'.format(st.files.length);
 | 
					            tr.innerHTML = '<td id="f{0}n"></td><td id="f{0}t">hashing</td><td id="f{0}p" class="prog"></td>'.format(st.files.length);
 | 
				
			||||||
            tr.getElementsByTagName('td')[0].textContent = entry.name;
 | 
					            tr.getElementsByTagName('td')[0].textContent = entry.name;
 | 
				
			||||||
            o('u2tab').appendChild(tr);
 | 
					            ebi('u2tab').appendChild(tr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            st.files.push(entry);
 | 
					            st.files.push(entry);
 | 
				
			||||||
            st.todo.hash.push(entry);
 | 
					            st.todo.hash.push(entry);
 | 
				
			||||||
@@ -374,14 +319,14 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            alert(msg);
 | 
					            alert(msg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    o('u2btn').addEventListener('drop', gotfile, false);
 | 
					    ebi('u2btn').addEventListener('drop', gotfile, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function more_one_file() {
 | 
					    function more_one_file() {
 | 
				
			||||||
        fdom_ctr++;
 | 
					        fdom_ctr++;
 | 
				
			||||||
        var elm = document.createElement('div')
 | 
					        var elm = document.createElement('div')
 | 
				
			||||||
        elm.innerHTML = '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" />'.format(fdom_ctr);
 | 
					        elm.innerHTML = '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" />'.format(fdom_ctr);
 | 
				
			||||||
        o('u2form').appendChild(elm);
 | 
					        ebi('u2form').appendChild(elm);
 | 
				
			||||||
        o('file' + fdom_ctr).addEventListener('change', gotfile, false);
 | 
					        ebi('file' + fdom_ctr).addEventListener('change', gotfile, false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    more_one_file();
 | 
					    more_one_file();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -451,17 +396,6 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    ///   hashing
 | 
					    ///   hashing
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // https://gist.github.com/jonleighton/958841
 | 
					 | 
				
			||||||
    function buf2b64_maybe_fucky(buffer) {
 | 
					 | 
				
			||||||
        var ret = '';
 | 
					 | 
				
			||||||
        var view = new DataView(buffer);
 | 
					 | 
				
			||||||
        for (var i = 0; i < view.byteLength; i++) {
 | 
					 | 
				
			||||||
            ret += String.fromCharCode(view.getUint8(i));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return window.btoa(ret).replace(
 | 
					 | 
				
			||||||
            /\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // https://gist.github.com/jonleighton/958841
 | 
					    // https://gist.github.com/jonleighton/958841
 | 
				
			||||||
    function buf2b64(arrayBuffer) {
 | 
					    function buf2b64(arrayBuffer) {
 | 
				
			||||||
        var base64 = '';
 | 
					        var base64 = '';
 | 
				
			||||||
@@ -502,20 +436,6 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        return base64;
 | 
					        return base64;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
 | 
					 | 
				
			||||||
    function buf2hex(buffer) {
 | 
					 | 
				
			||||||
        var hexCodes = [];
 | 
					 | 
				
			||||||
        var view = new DataView(buffer);
 | 
					 | 
				
			||||||
        for (var i = 0; i < view.byteLength; i += 4) {
 | 
					 | 
				
			||||||
            var value = view.getUint32(i) // 4 bytes per iter
 | 
					 | 
				
			||||||
            var stringValue = value.toString(16) // doesn't pad
 | 
					 | 
				
			||||||
            var padding = '00000000'
 | 
					 | 
				
			||||||
            var paddedValue = (padding + stringValue).slice(-padding.length)
 | 
					 | 
				
			||||||
            hexCodes.push(paddedValue);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return hexCodes.join("");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function get_chunksize(filesize) {
 | 
					    function get_chunksize(filesize) {
 | 
				
			||||||
        var chunksize = 1024 * 1024;
 | 
					        var chunksize = 1024 * 1024;
 | 
				
			||||||
        var stepsize = 512 * 1024;
 | 
					        var stepsize = 512 * 1024;
 | 
				
			||||||
@@ -602,7 +522,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            pb_html += '<div id="f{0}p{1}" style="width:{2}%"><div></div></div>'.format(
 | 
					            pb_html += '<div id="f{0}p{1}" style="width:{2}%"><div></div></div>'.format(
 | 
				
			||||||
                t.n, a, pb_perc);
 | 
					                t.n, a, pb_perc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        o('f{0}p'.format(t.n)).innerHTML = pb_html;
 | 
					        ebi('f{0}p'.format(t.n)).innerHTML = pb_html;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var reader = new FileReader();
 | 
					        var reader = new FileReader();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -677,7 +597,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
 | 
					                alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            o('f{0}t'.format(t.n)).innerHTML = 'connecting';
 | 
					            ebi('f{0}t'.format(t.n)).innerHTML = 'connecting';
 | 
				
			||||||
            st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
 | 
					            st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
 | 
				
			||||||
            st.todo.handshake.push(t);
 | 
					            st.todo.handshake.push(t);
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
@@ -706,7 +626,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                if (response.name !== t.name) {
 | 
					                if (response.name !== t.name) {
 | 
				
			||||||
                    // file exists; server renamed us
 | 
					                    // file exists; server renamed us
 | 
				
			||||||
                    t.name = response.name;
 | 
					                    t.name = response.name;
 | 
				
			||||||
                    o('f{0}n'.format(t.n)).textContent = t.name;
 | 
					                    ebi('f{0}n'.format(t.n)).textContent = t.name;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                t.postlist = [];
 | 
					                t.postlist = [];
 | 
				
			||||||
@@ -736,23 +656,37 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                    msg = 'uploading';
 | 
					                    msg = 'uploading';
 | 
				
			||||||
                    done = false;
 | 
					                    done = false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                o('f{0}t'.format(t.n)).innerHTML = msg;
 | 
					                ebi('f{0}t'.format(t.n)).innerHTML = msg;
 | 
				
			||||||
                st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
 | 
					                st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (done) {
 | 
					                if (done) {
 | 
				
			||||||
                    var spd1 = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
 | 
					                    var spd1 = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
 | 
				
			||||||
                    var spd2 = (t.size / ((t.t3 - t.t2) / 1000.)) / (1024 * 1024.);
 | 
					                    var spd2 = (t.size / ((t.t3 - t.t2) / 1000.)) / (1024 * 1024.);
 | 
				
			||||||
                    o('f{0}p'.format(t.n)).innerHTML = 'hash {0}, up {1} MB/s'.format(
 | 
					                    ebi('f{0}p'.format(t.n)).innerHTML = 'hash {0}, up {1} MB/s'.format(
 | 
				
			||||||
                        spd1.toFixed(2), spd2.toFixed(2));
 | 
					                        spd1.toFixed(2), spd2.toFixed(2));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                tasker();
 | 
					                tasker();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else {
 | 
				
			||||||
 | 
					                var err = "";
 | 
				
			||||||
 | 
					                var rsp = (xhr.responseText + '');
 | 
				
			||||||
 | 
					                if (rsp.indexOf('partial upload exists') !== -1) {
 | 
				
			||||||
 | 
					                    err = rsp.slice(5);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (err != "") {
 | 
				
			||||||
 | 
					                    ebi('f{0}t'.format(t.n)).innerHTML = "ERROR";
 | 
				
			||||||
 | 
					                    ebi('f{0}p'.format(t.n)).innerHTML = err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
 | 
				
			||||||
 | 
					                    tasker();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                alert("server broke (error {0}):\n\"{1}\"\n".format(
 | 
					                alert("server broke (error {0}):\n\"{1}\"\n".format(
 | 
				
			||||||
                    xhr.status,
 | 
					                    xhr.status,
 | 
				
			||||||
                    (xhr.response && xhr.response.err) ||
 | 
					                    (xhr.response && xhr.response.err) ||
 | 
				
			||||||
                    (xhr.responseText && xhr.responseText) ||
 | 
					                    (xhr.responseText && xhr.responseText) ||
 | 
				
			||||||
                    "no further information"));
 | 
					                    "no further information"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        xhr.open('POST', post_url + 'handshake.php', true);
 | 
					        xhr.open('POST', post_url + 'handshake.php', true);
 | 
				
			||||||
        xhr.responseType = 'text';
 | 
					        xhr.responseType = 'text';
 | 
				
			||||||
@@ -803,7 +737,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
                    t.postlist.splice(t.postlist.indexOf(npart), 1);
 | 
					                    t.postlist.splice(t.postlist.indexOf(npart), 1);
 | 
				
			||||||
                    if (t.postlist.length == 0) {
 | 
					                    if (t.postlist.length == 0) {
 | 
				
			||||||
                        t.t3 = new Date().getTime();
 | 
					                        t.t3 = new Date().getTime();
 | 
				
			||||||
                        o('f{0}t'.format(t.n)).innerHTML = 'verifying';
 | 
					                        ebi('f{0}t'.format(t.n)).innerHTML = 'verifying';
 | 
				
			||||||
                        st.todo.handshake.push(t);
 | 
					                        st.todo.handshake.push(t);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    tasker();
 | 
					                    tasker();
 | 
				
			||||||
@@ -834,7 +768,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
    //
 | 
					    //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function prog(nfile, nchunk, color, percent) {
 | 
					    function prog(nfile, nchunk, color, percent) {
 | 
				
			||||||
        var n1 = o('f{0}p{1}'.format(nfile, nchunk));
 | 
					        var n1 = ebi('f{0}p{1}'.format(nfile, nchunk));
 | 
				
			||||||
        var n2 = n1.getElementsByTagName('div')[0];
 | 
					        var n2 = n1.getElementsByTagName('div')[0];
 | 
				
			||||||
        if (percent === undefined) {
 | 
					        if (percent === undefined) {
 | 
				
			||||||
            n1.style.background = color;
 | 
					            n1.style.background = color;
 | 
				
			||||||
@@ -857,7 +791,7 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
            dir.preventDefault();
 | 
					            dir.preventDefault();
 | 
				
			||||||
        } catch (ex) { }
 | 
					        } catch (ex) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var obj = o('nthread');
 | 
					        var obj = ebi('nthread');
 | 
				
			||||||
        if (dir.target) {
 | 
					        if (dir.target) {
 | 
				
			||||||
            obj.style.background = '#922';
 | 
					            obj.style.background = '#922';
 | 
				
			||||||
            var v = Math.floor(parseInt(obj.value));
 | 
					            var v = Math.floor(parseInt(obj.value));
 | 
				
			||||||
@@ -892,19 +826,19 @@ function up2k_init(have_crypto) {
 | 
				
			|||||||
        this.click();
 | 
					        this.click();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    o('nthread_add').onclick = function (ev) {
 | 
					    ebi('nthread_add').onclick = function (ev) {
 | 
				
			||||||
        ev.preventDefault();
 | 
					        ev.preventDefault();
 | 
				
			||||||
        bumpthread(1);
 | 
					        bumpthread(1);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    o('nthread_sub').onclick = function (ev) {
 | 
					    ebi('nthread_sub').onclick = function (ev) {
 | 
				
			||||||
        ev.preventDefault();
 | 
					        ev.preventDefault();
 | 
				
			||||||
        bumpthread(-1);
 | 
					        bumpthread(-1);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    o('nthread').addEventListener('input', bumpthread, false);
 | 
					    ebi('nthread').addEventListener('input', bumpthread, false);
 | 
				
			||||||
    o('multitask').addEventListener('click', tgl_multitask, false);
 | 
					    ebi('multitask').addEventListener('click', tgl_multitask, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var nodes = o('u2conf').getElementsByTagName('a');
 | 
					    var nodes = ebi('u2conf').getElementsByTagName('a');
 | 
				
			||||||
    for (var a = nodes.length - 1; a >= 0; a--)
 | 
					    for (var a = nodes.length - 1; a >= 0; a--)
 | 
				
			||||||
        nodes[a].addEventListener('touchend', nop, false);
 | 
					        nodes[a].addEventListener('touchend', nop, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										109
									
								
								copyparty/web/util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								copyparty/web/util.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// error handler for mobile devices
 | 
				
			||||||
 | 
					function hcroak(msg) {
 | 
				
			||||||
 | 
					    document.body.innerHTML = msg;
 | 
				
			||||||
 | 
					    window.onerror = undefined;
 | 
				
			||||||
 | 
					    throw 'fatal_err';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function croak(msg) {
 | 
				
			||||||
 | 
					    document.body.textContent = msg;
 | 
				
			||||||
 | 
					    window.onerror = undefined;
 | 
				
			||||||
 | 
					    throw msg;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function esc(txt) {
 | 
				
			||||||
 | 
					    return txt.replace(/[&"<>]/g, function (c) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            '&': '&',
 | 
				
			||||||
 | 
					            '"': '"',
 | 
				
			||||||
 | 
					            '<': '<',
 | 
				
			||||||
 | 
					            '>': '>'
 | 
				
			||||||
 | 
					        }[c];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function vis_exh(msg, url, lineNo, columnNo, error) {
 | 
				
			||||||
 | 
					    window.onerror = undefined;
 | 
				
			||||||
 | 
					    var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
				
			||||||
 | 
					        esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (error) {
 | 
				
			||||||
 | 
					        var find = ['desc', 'stack', 'trace'];
 | 
				
			||||||
 | 
					        for (var a = 0; a < find.length; a++)
 | 
				
			||||||
 | 
					            if (String(error[find[a]]) !== 'undefined')
 | 
				
			||||||
 | 
					                html.push('<h2>' + find[a] + '</h2>' +
 | 
				
			||||||
 | 
					                    esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    document.body.style.fontSize = '0.8em';
 | 
				
			||||||
 | 
					    document.body.style.padding = '0 1em 1em 1em';
 | 
				
			||||||
 | 
					    hcroak(html.join('\n'));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ebi(id) {
 | 
				
			||||||
 | 
					    return document.getElementById(id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
 | 
				
			||||||
 | 
					if (!String.prototype.endsWith) {
 | 
				
			||||||
 | 
					    String.prototype.endsWith = function (search, this_len) {
 | 
				
			||||||
 | 
					        if (this_len === undefined || this_len > this.length) {
 | 
				
			||||||
 | 
					            this_len = this.length;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return this.substring(this_len - search.length, this_len) === search;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					if (!String.startsWith) {
 | 
				
			||||||
 | 
					    String.prototype.startsWith = function (s, i) {
 | 
				
			||||||
 | 
					        i = i > 0 ? i | 0 : 0;
 | 
				
			||||||
 | 
					        return this.substring(i, i + s.length) === s;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://stackoverflow.com/a/950146
 | 
				
			||||||
 | 
					function import_js(url, cb) {
 | 
				
			||||||
 | 
					    var head = document.head || document.getElementsByTagName('head')[0];
 | 
				
			||||||
 | 
					    var script = document.createElement('script');
 | 
				
			||||||
 | 
					    script.type = 'text/javascript';
 | 
				
			||||||
 | 
					    script.src = url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    script.onreadystatechange = cb;
 | 
				
			||||||
 | 
					    script.onload = cb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    head.appendChild(script);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function sortTable(table, col) {
 | 
				
			||||||
 | 
					    var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
 | 
				
			||||||
 | 
					        th = table.tHead.rows[0].cells,
 | 
				
			||||||
 | 
					        tr = Array.prototype.slice.call(tb.rows, 0),
 | 
				
			||||||
 | 
					        i, reverse = th[col].className == 'sort1' ? -1 : 1;
 | 
				
			||||||
 | 
					    for (var a = 0, thl = th.length; a < thl; a++)
 | 
				
			||||||
 | 
					        th[a].className = '';
 | 
				
			||||||
 | 
					    th[col].className = 'sort' + reverse;
 | 
				
			||||||
 | 
					    var stype = th[col].getAttribute('sort');
 | 
				
			||||||
 | 
					    tr = tr.sort(function (a, b) {
 | 
				
			||||||
 | 
					        var v1 = a.cells[col].textContent.trim();
 | 
				
			||||||
 | 
					        var v2 = b.cells[col].textContent.trim();
 | 
				
			||||||
 | 
					        if (stype == 'int') {
 | 
				
			||||||
 | 
					            v1 = parseInt(v1.replace(/,/g, ''));
 | 
				
			||||||
 | 
					            v2 = parseInt(v2.replace(/,/g, ''));
 | 
				
			||||||
 | 
					            return reverse * (v1 - v2);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return reverse * (v1.localeCompare(v2));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    for (i = 0; i < tr.length; ++i) tb.appendChild(tr[i]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function makeSortable(table) {
 | 
				
			||||||
 | 
					    var th = table.tHead, i;
 | 
				
			||||||
 | 
					    th && (th = th.rows[0]) && (th = th.cells);
 | 
				
			||||||
 | 
					    if (th) i = th.length;
 | 
				
			||||||
 | 
					    else return; // if no `<thead>` then do nothing
 | 
				
			||||||
 | 
					    while (--i >= 0) (function (i) {
 | 
				
			||||||
 | 
					        th[i].onclick = function () {
 | 
				
			||||||
 | 
					            sortTable(table, i);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }(i));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					repacker=1
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# -- download latest copyparty (source.tgz and sfx),
 | 
					# -- download latest copyparty (source.tgz and sfx),
 | 
				
			||||||
@@ -19,19 +20,32 @@ set -e
 | 
				
			|||||||
# -rwxr-xr-x  0 ed ed  183808 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.py
 | 
					# -rwxr-xr-x  0 ed ed  183808 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					command -v gtar && tar() { gtar "$@"; }
 | 
				
			||||||
 | 
					command -v gsed && sed() { gsed "$@"; }
 | 
				
			||||||
td="$(mktemp -d)"
 | 
					td="$(mktemp -d)"
 | 
				
			||||||
od="$(pwd)"
 | 
					od="$(pwd)"
 | 
				
			||||||
cd "$td"
 | 
					cd "$td"
 | 
				
			||||||
pwd
 | 
					pwd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# debug: if cache exists, use that instead of bothering github
 | 
					dl_text() {
 | 
				
			||||||
 | 
						command -v curl && exec curl "$@"
 | 
				
			||||||
 | 
						exec wget -O- "$@"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					dl_files() {
 | 
				
			||||||
 | 
						command -v curl && exec curl -L --remote-name-all "$@"
 | 
				
			||||||
 | 
						exec wget "$@"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export -f dl_files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# if cache exists, use that instead of bothering github
 | 
				
			||||||
cache="$od/.copyparty-repack.cache"
 | 
					cache="$od/.copyparty-repack.cache"
 | 
				
			||||||
[ -e "$cache" ] &&
 | 
					[ -e "$cache" ] &&
 | 
				
			||||||
	tar -xvf "$cache" ||
 | 
						tar -xf "$cache" ||
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	# get download links from github
 | 
						# get download links from github
 | 
				
			||||||
	curl https://api.github.com/repos/9001/copyparty/releases/latest |
 | 
						dl_text https://api.github.com/repos/9001/copyparty/releases/latest |
 | 
				
			||||||
	(
 | 
						(
 | 
				
			||||||
		# prefer jq if available
 | 
							# prefer jq if available
 | 
				
			||||||
		jq -r '.assets[]|select(.name|test("-sfx|tar.gz")).browser_download_url' ||
 | 
							jq -r '.assets[]|select(.name|test("-sfx|tar.gz")).browser_download_url' ||
 | 
				
			||||||
@@ -40,10 +54,10 @@ cache="$od/.copyparty-repack.cache"
 | 
				
			|||||||
		awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
 | 
							awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
 | 
				
			||||||
	) |
 | 
						) |
 | 
				
			||||||
	tee /dev/stderr |
 | 
						tee /dev/stderr |
 | 
				
			||||||
	tr -d '\r' | tr '\n' '\0' | xargs -0 curl -L --remote-name-all
 | 
						tr -d '\r' | tr '\n' '\0' |
 | 
				
			||||||
 | 
						xargs -0 bash -c 'dl_files "$@"' _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# debug: create cache
 | 
						tar -czf "$cache" *
 | 
				
			||||||
	#tar -czvf "$cache" *
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,10 +70,21 @@ mv copyparty-*.tar.gz copyparty-extras/
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# unpack the source code
 | 
					# unpack the source code
 | 
				
			||||||
( cd copyparty-extras/
 | 
					( cd copyparty-extras/
 | 
				
			||||||
tar -xvf *.tar.gz
 | 
					tar -xf *.tar.gz
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# use repacker from release if that is newer
 | 
				
			||||||
 | 
					p_other=copyparty-extras/copyparty-*/scripts/copyparty-repack.sh
 | 
				
			||||||
 | 
					other=$(awk -F= 'BEGIN{v=-1} NR<10&&/^repacker=/{v=$NF} END{print v}' <$p_other) 
 | 
				
			||||||
 | 
					[ $repacker -lt $other ] &&
 | 
				
			||||||
 | 
					  cat $p_other >"$od/$0" && cd "$od" && rm -rf "$td" && exec "$0" "$@"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# now drop the cache
 | 
				
			||||||
 | 
					rm -f "$cache"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# fix permissions
 | 
					# fix permissions
 | 
				
			||||||
chmod 755 \
 | 
					chmod 755 \
 | 
				
			||||||
  copyparty-extras/sfx-full/* \
 | 
					  copyparty-extras/sfx-full/* \
 | 
				
			||||||
@@ -86,8 +111,10 @@ rm -rf copyparty-{0..9}*.*.*{0..9}
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 # and include the repacker itself too
 | 
					# and include the repacker itself too
 | 
				
			||||||
cp -pv "$od/$0" copyparty-extras/ 
 | 
					cp -av "$od/$0" copyparty-extras/ ||
 | 
				
			||||||
 | 
					cp -av "$0" copyparty-extras/ ||
 | 
				
			||||||
 | 
					true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# create the bundle
 | 
					# create the bundle
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -94,8 +94,39 @@ cd sfx
 | 
				
			|||||||
	rm -f ../tar
 | 
						rm -f ../tar
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ver="$(awk '/^VERSION *= \(/ {
 | 
					ver=
 | 
				
			||||||
	gsub(/[^0-9,]/,""); gsub(/,/,"."); print; exit}' < ../copyparty/__version__.py)"
 | 
					git describe --tags >/dev/null 2>/dev/null && {
 | 
				
			||||||
 | 
						git_ver="$(git describe --tags)";  # v0.5.5-2-gb164aa0
 | 
				
			||||||
 | 
						ver="$(printf '%s\n' "$git_ver" | sed -r 's/^v//; s/-g?/./g')";
 | 
				
			||||||
 | 
						t_ver=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+$' && {
 | 
				
			||||||
 | 
							# short format (exact version number)
 | 
				
			||||||
 | 
							t_ver="$(printf '%s\n' "$ver" | sed -r 's/\./, /g')";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+-[0-9]+-g[0-9a-f]+$' && {
 | 
				
			||||||
 | 
							# long format (unreleased commit)
 | 
				
			||||||
 | 
							t_ver="$(printf '%s\n' "$ver" | sed -r 's/\./, /g; s/(.*) (.*)/\1 "\2"/')"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[ -z "$t_ver" ] && {
 | 
				
			||||||
 | 
							printf 'unexpected git version format: [%s]\n' "$git_ver"
 | 
				
			||||||
 | 
							exit 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dt="$(git log -1 --format=%cd --date=format:'%Y,%m,%d' | sed 's/,0?/, /g')"
 | 
				
			||||||
 | 
						printf 'git %3s: \033[36m%s\033[0m\n' ver "$ver" dt "$dt"
 | 
				
			||||||
 | 
						sed -ri '
 | 
				
			||||||
 | 
							s/^(VERSION =)(.*)/#\1\2\n\1 ('"$t_ver"')/;
 | 
				
			||||||
 | 
							s/^(S_VERSION =)(.*)/#\1\2\n\1 "'"$ver"'"/;
 | 
				
			||||||
 | 
							s/^(BUILD_DT =)(.*)/#\1\2\n\1 ('"$dt"')/;
 | 
				
			||||||
 | 
						' copyparty/__version__.py
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[ -z "$ver" ] && 
 | 
				
			||||||
 | 
						ver="$(awk '/^VERSION *= \(/ {
 | 
				
			||||||
 | 
							gsub(/[^0-9,]/,""); gsub(/,/,"."); print; exit}' < copyparty/__version__.py)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ts=$(date -u +%s)
 | 
					ts=$(date -u +%s)
 | 
				
			||||||
hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx)
 | 
					hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,8 @@ this one becomes a hyperlink to ./except/ thanks to
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
it is a passthrough to the markdown extension api, see https://marked.js.org/using_pro
 | 
					it is a passthrough to the markdown extension api, see https://marked.js.org/using_pro
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in addition to the markdown extension functions, `ctor` will be called on document init
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### these/
 | 
					### these/
 | 
				
			||||||
and this one becomes ./except/these/
 | 
					and this one becomes ./except/these/
 | 
				
			||||||
@@ -36,6 +38,13 @@ whic hshoud be ./except/also-this.md
 | 
				
			|||||||
# ok
 | 
					# ok
 | 
				
			||||||
now for another extension type, `copyparty_post` which is called to manipulate the generated dom instead
 | 
					now for another extension type, `copyparty_post` which is called to manipulate the generated dom instead
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`copyparty_post` can have the following functions, all optional
 | 
				
			||||||
 | 
					* `ctor` is called on document init
 | 
				
			||||||
 | 
					* `render` is called when the dom is done but still in-memory
 | 
				
			||||||
 | 
					* `render2` is called with the live browser dom as-displayed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## post example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the values in the `ex:` columns are linkified to `example.com/$value`
 | 
					the values in the `ex:` columns are linkified to `example.com/$value`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| ex:foo       | bar      | ex:baz |
 | 
					| ex:foo       | bar      | ex:baz |
 | 
				
			||||||
@@ -43,6 +52,8 @@ the values in the `ex:` columns are linkified to `example.com/$value`
 | 
				
			|||||||
| asdf         | nice     | fgsfds |
 | 
					| asdf         | nice     | fgsfds |
 | 
				
			||||||
| more one row | hi hello | aaa    |
 | 
					| more one row | hi hello | aaa    |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					and the table can be sorted by clicking the headers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
the difference is that with `copyparty_pre` you'll probably break various copyparty features but if you use `copyparty_post` then future copyparty versions will probably break you
 | 
					the difference is that with `copyparty_pre` you'll probably break various copyparty features but if you use `copyparty_post` then future copyparty versions will probably break you
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,5 +134,8 @@ render(dom) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					},
 | 
				
			||||||
 | 
					render2(dom) {
 | 
				
			||||||
 | 
					    window.makeSortable(dom.getElementsByTagName('table')[0]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import os
 | 
				
			|||||||
import time
 | 
					import time
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
import subprocess as sp  # nosec
 | 
					import subprocess as sp  # nosec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,9 +32,6 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        response = self.unfoo(response)
 | 
					        response = self.unfoo(response)
 | 
				
			||||||
        self.assertEqual(util.undot(query), response)
 | 
					        self.assertEqual(util.undot(query), response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def absify(self, root, names):
 | 
					 | 
				
			||||||
        return ["{}/{}".format(root, x).replace("//", "/") for x in names]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def ls(self, vfs, vpath, uname):
 | 
					    def ls(self, vfs, vpath, uname):
 | 
				
			||||||
        """helper for resolving and listing a folder"""
 | 
					        """helper for resolving and listing a folder"""
 | 
				
			||||||
        vn, rem = vfs.get(vpath, uname, True, False)
 | 
					        vn, rem = vfs.get(vpath, uname, True, False)
 | 
				
			||||||
@@ -60,23 +58,31 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if os.path.exists("/Volumes"):
 | 
					        if os.path.exists("/Volumes"):
 | 
				
			||||||
            devname, _ = self.chkcmd("hdiutil", "attach", "-nomount", "ram://8192")
 | 
					            devname, _ = self.chkcmd("hdiutil", "attach", "-nomount", "ram://8192")
 | 
				
			||||||
 | 
					            devname = devname.strip()
 | 
				
			||||||
 | 
					            print("devname: [{}]".format(devname))
 | 
				
			||||||
            for _ in range(10):
 | 
					            for _ in range(10):
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    _, _ = self.chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
 | 
					                    _, _ = self.chkcmd(
 | 
				
			||||||
 | 
					                        "diskutil", "eraseVolume", "HFS+", "cptd", devname
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    return "/Volumes/cptd"
 | 
					                    return "/Volumes/cptd"
 | 
				
			||||||
                except:
 | 
					                except Exception as ex:
 | 
				
			||||||
                    print('lol macos')
 | 
					                    print(repr(ex))
 | 
				
			||||||
                    time.sleep(0.25)
 | 
					                    time.sleep(0.25)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            raise Exception("ramdisk creation failed")
 | 
					            raise Exception("ramdisk creation failed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise Exception("TODO support windows")
 | 
					        ret = os.path.join(tempfile.gettempdir(), "copyparty-test")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.mkdir(ret)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def log(self, src, msg):
 | 
					    def log(self, src, msg):
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test(self):
 | 
					    def test(self):
 | 
				
			||||||
        td = self.get_ramdisk() + "/vfs"
 | 
					        td = os.path.join(self.get_ramdisk(), "vfs")
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            shutil.rmtree(td)
 | 
					            shutil.rmtree(td)
 | 
				
			||||||
        except OSError:
 | 
					        except OSError:
 | 
				
			||||||
@@ -107,7 +113,7 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        vfs = AuthSrv(Namespace(c=None, a=[], v=["a/ab/::r"]), self.log).vfs
 | 
					        vfs = AuthSrv(Namespace(c=None, a=[], v=["a/ab/::r"]), self.log).vfs
 | 
				
			||||||
        self.assertEqual(vfs.nodes, {})
 | 
					        self.assertEqual(vfs.nodes, {})
 | 
				
			||||||
        self.assertEqual(vfs.vpath, "")
 | 
					        self.assertEqual(vfs.vpath, "")
 | 
				
			||||||
        self.assertEqual(vfs.realpath, td + "/a/ab")
 | 
					        self.assertEqual(vfs.realpath, os.path.join(td, "a", "ab"))
 | 
				
			||||||
        self.assertEqual(vfs.uread, ["*"])
 | 
					        self.assertEqual(vfs.uread, ["*"])
 | 
				
			||||||
        self.assertEqual(vfs.uwrite, [])
 | 
					        self.assertEqual(vfs.uwrite, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -117,7 +123,7 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        ).vfs
 | 
					        ).vfs
 | 
				
			||||||
        self.assertEqual(vfs.nodes, {})
 | 
					        self.assertEqual(vfs.nodes, {})
 | 
				
			||||||
        self.assertEqual(vfs.vpath, "")
 | 
					        self.assertEqual(vfs.vpath, "")
 | 
				
			||||||
        self.assertEqual(vfs.realpath, td + "/a/aa")
 | 
					        self.assertEqual(vfs.realpath, os.path.join(td, "a", "aa"))
 | 
				
			||||||
        self.assertEqual(vfs.uread, ["*"])
 | 
					        self.assertEqual(vfs.uread, ["*"])
 | 
				
			||||||
        self.assertEqual(vfs.uwrite, [])
 | 
					        self.assertEqual(vfs.uwrite, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -146,42 +152,63 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        n = n.nodes["acb"]
 | 
					        n = n.nodes["acb"]
 | 
				
			||||||
        self.assertEqual(n.nodes, {})
 | 
					        self.assertEqual(n.nodes, {})
 | 
				
			||||||
        self.assertEqual(n.vpath, "a/ac/acb")
 | 
					        self.assertEqual(n.vpath, "a/ac/acb")
 | 
				
			||||||
        self.assertEqual(n.realpath, td + "/a/ac/acb")
 | 
					        self.assertEqual(n.realpath, os.path.join(td, "a", "ac", "acb"))
 | 
				
			||||||
        self.assertEqual(n.uread, ["k"])
 | 
					        self.assertEqual(n.uread, ["k"])
 | 
				
			||||||
        self.assertEqual(n.uwrite, ["*", "k"])
 | 
					        self.assertEqual(n.uwrite, ["*", "k"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # something funky about the windows path normalization,
 | 
				
			||||||
 | 
					        # doesn't really matter but makes the test messy, TODO?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsdir, real, virt = self.ls(vfs, "/", "*")
 | 
					        fsdir, real, virt = self.ls(vfs, "/", "*")
 | 
				
			||||||
        self.assertEqual(fsdir, td)
 | 
					        self.assertEqual(fsdir, td)
 | 
				
			||||||
        self.assertEqual(real, ["b", "c"])
 | 
					        self.assertEqual(real, ["b", "c"])
 | 
				
			||||||
        self.assertEqual(list(virt), ["a"])
 | 
					        self.assertEqual(list(virt), ["a"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsdir, real, virt = self.ls(vfs, "a", "*")
 | 
					        fsdir, real, virt = self.ls(vfs, "a", "*")
 | 
				
			||||||
        self.assertEqual(fsdir, td + "/a")
 | 
					        self.assertEqual(fsdir, os.path.join(td, "a"))
 | 
				
			||||||
        self.assertEqual(real, ["aa", "ab"])
 | 
					        self.assertEqual(real, ["aa", "ab"])
 | 
				
			||||||
        self.assertEqual(list(virt), ["ac"])
 | 
					        self.assertEqual(list(virt), ["ac"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsdir, real, virt = self.ls(vfs, "a/ab", "*")
 | 
					        fsdir, real, virt = self.ls(vfs, "a/ab", "*")
 | 
				
			||||||
        self.assertEqual(fsdir, td + "/a/ab")
 | 
					        self.assertEqual(fsdir, os.path.join(td, "a", "ab"))
 | 
				
			||||||
        self.assertEqual(real, ["aba", "abb", "abc"])
 | 
					        self.assertEqual(real, ["aba", "abb", "abc"])
 | 
				
			||||||
        self.assertEqual(list(virt), [])
 | 
					        self.assertEqual(list(virt), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsdir, real, virt = self.ls(vfs, "a/ac", "*")
 | 
					        fsdir, real, virt = self.ls(vfs, "a/ac", "*")
 | 
				
			||||||
        self.assertEqual(fsdir, td + "/a/ac")
 | 
					        self.assertEqual(fsdir, os.path.join(td, "a", "ac"))
 | 
				
			||||||
        self.assertEqual(real, ["aca", "acc"])
 | 
					        self.assertEqual(real, ["aca", "acc"])
 | 
				
			||||||
        self.assertEqual(list(virt), [])
 | 
					        self.assertEqual(list(virt), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsdir, real, virt = self.ls(vfs, "a/ac", "k")
 | 
					        fsdir, real, virt = self.ls(vfs, "a/ac", "k")
 | 
				
			||||||
        self.assertEqual(fsdir, td + "/a/ac")
 | 
					        self.assertEqual(fsdir, os.path.join(td, "a", "ac"))
 | 
				
			||||||
        self.assertEqual(real, ["aca", "acc"])
 | 
					        self.assertEqual(real, ["aca", "acc"])
 | 
				
			||||||
        self.assertEqual(list(virt), ["acb"])
 | 
					        self.assertEqual(list(virt), ["acb"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertRaises(util.Pebkac, vfs.get, "a/ac/acb", "*", True, False)
 | 
					        self.assertRaises(util.Pebkac, vfs.get, "a/ac/acb", "*", True, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsdir, real, virt = self.ls(vfs, "a/ac/acb", "k")
 | 
					        fsdir, real, virt = self.ls(vfs, "a/ac/acb", "k")
 | 
				
			||||||
        self.assertEqual(fsdir, td + "/a/ac/acb")
 | 
					        self.assertEqual(fsdir, os.path.join(td, "a", "ac", "acb"))
 | 
				
			||||||
        self.assertEqual(real, ["acba", "acbb", "acbc"])
 | 
					        self.assertEqual(real, ["acba", "acbb", "acbc"])
 | 
				
			||||||
        self.assertEqual(list(virt), [])
 | 
					        self.assertEqual(list(virt), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # admin-only rootfs with all-read-only subfolder
 | 
				
			||||||
 | 
					        vfs = AuthSrv(Namespace(c=None, a=["k:k"], v=[".::ak", "a:a:r"]), self.log,).vfs
 | 
				
			||||||
 | 
					        self.assertEqual(len(vfs.nodes), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.vpath, "")
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.realpath, td)
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.uread, ["k"])
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.uwrite, ["k"])
 | 
				
			||||||
 | 
					        n = vfs.nodes["a"]
 | 
				
			||||||
 | 
					        self.assertEqual(len(vfs.nodes), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(n.vpath, "a")
 | 
				
			||||||
 | 
					        self.assertEqual(n.realpath, os.path.join(td, "a"))
 | 
				
			||||||
 | 
					        self.assertEqual(n.uread, ["*"])
 | 
				
			||||||
 | 
					        self.assertEqual(n.uwrite, [])
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.can_access("/", "*"), [False, False])
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.can_access("/", "k"), [True, True])
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.can_access("/a", "*"), [True, False])
 | 
				
			||||||
 | 
					        self.assertEqual(vfs.can_access("/a", "k"), [True, False])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # breadth-first construction
 | 
					        # breadth-first construction
 | 
				
			||||||
        vfs = AuthSrv(
 | 
					        vfs = AuthSrv(
 | 
				
			||||||
            Namespace(
 | 
					            Namespace(
 | 
				
			||||||
@@ -215,20 +242,20 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(list(v1), ["a"])
 | 
					        self.assertEqual(list(v1), ["a"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsp, r1, v1 = self.ls(vfs, "a", "*")
 | 
					        fsp, r1, v1 = self.ls(vfs, "a", "*")
 | 
				
			||||||
        self.assertEqual(fsp, td + "/a")
 | 
					        self.assertEqual(fsp, os.path.join(td, "a"))
 | 
				
			||||||
        self.assertEqual(r1, ["aa", "ab"])
 | 
					        self.assertEqual(r1, ["aa", "ab"])
 | 
				
			||||||
        self.assertEqual(list(v1), ["ac"])
 | 
					        self.assertEqual(list(v1), ["ac"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsp1, r1, v1 = self.ls(vfs, "a/ac", "*")
 | 
					        fsp1, r1, v1 = self.ls(vfs, "a/ac", "*")
 | 
				
			||||||
        fsp2, r2, v2 = self.ls(vfs, "b", "*")
 | 
					        fsp2, r2, v2 = self.ls(vfs, "b", "*")
 | 
				
			||||||
        self.assertEqual(fsp1, td + "/b")
 | 
					        self.assertEqual(fsp1, os.path.join(td, "b"))
 | 
				
			||||||
        self.assertEqual(fsp2, td + "/b")
 | 
					        self.assertEqual(fsp2, os.path.join(td, "b"))
 | 
				
			||||||
        self.assertEqual(r1, ["ba", "bb", "bc"])
 | 
					        self.assertEqual(r1, ["ba", "bb", "bc"])
 | 
				
			||||||
        self.assertEqual(r1, r2)
 | 
					        self.assertEqual(r1, r2)
 | 
				
			||||||
        self.assertEqual(list(v1), list(v2))
 | 
					        self.assertEqual(list(v1), list(v2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # config file parser
 | 
					        # config file parser
 | 
				
			||||||
        cfg_path = self.get_ramdisk() + "/test.cfg"
 | 
					        cfg_path = os.path.join(self.get_ramdisk(), "test.cfg")
 | 
				
			||||||
        with open(cfg_path, "wb") as f:
 | 
					        with open(cfg_path, "wb") as f:
 | 
				
			||||||
            f.write(
 | 
					            f.write(
 | 
				
			||||||
                dedent(
 | 
					                dedent(
 | 
				
			||||||
@@ -256,10 +283,11 @@ class TestVFS(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(len(n.nodes), 1)
 | 
					        self.assertEqual(len(n.nodes), 1)
 | 
				
			||||||
        n = n.nodes["dst"]
 | 
					        n = n.nodes["dst"]
 | 
				
			||||||
        self.assertEqual(n.vpath, "dst")
 | 
					        self.assertEqual(n.vpath, "dst")
 | 
				
			||||||
        self.assertEqual(n.realpath, td + "/src")
 | 
					        self.assertEqual(n.realpath, os.path.join(td, "src"))
 | 
				
			||||||
        self.assertEqual(n.uread, ["a", "asd"])
 | 
					        self.assertEqual(n.uread, ["a", "asd"])
 | 
				
			||||||
        self.assertEqual(n.uwrite, ["asd"])
 | 
					        self.assertEqual(n.uwrite, ["asd"])
 | 
				
			||||||
        self.assertEqual(len(n.nodes), 0)
 | 
					        self.assertEqual(len(n.nodes), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        os.chdir(tempfile.gettempdir())
 | 
				
			||||||
        shutil.rmtree(td)
 | 
					        shutil.rmtree(td)
 | 
				
			||||||
        os.unlink(cfg_path)
 | 
					        os.unlink(cfg_path)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user