Compare commits

...

30 Commits

Author SHA1 Message Date
ed
c3e4d65b80 v0.10.15 2021-04-24 04:05:57 +02:00
ed
27a03510c5 quick upload test too 2021-04-24 03:35:58 +02:00
ed
ed7727f7cb fix write-only volumes + add regression test 2021-04-24 02:48:41 +02:00
ed
127ec10c0d js cleanup + minor tweaks 2021-04-23 20:04:17 +02:00
ed
5a9c0ad225 ui tweaks 2021-04-22 09:10:32 +02:00
ed
7e8daf650e v0.10.14 2021-04-21 22:04:21 +02:00
ed
0cf737b4ce 404 rather than redirect home if 404 or 403 2021-04-21 21:51:27 +02:00
ed
74635e0113 phew 2021-04-21 21:42:37 +02:00
ed
e5c4f49901 ok ok 2021-04-21 21:26:55 +02:00
ed
e4654ee7f1 uhh 2021-04-21 21:13:16 +02:00
ed
e5d05c05ed up2k ui tweaks 2021-04-21 20:50:10 +02:00
ed
73c4f99687 add markdown streaming 2021-04-21 20:28:50 +02:00
ed
28c12ef3bf cleanup 2021-04-21 18:48:23 +02:00
ed
eed82dbb54 remove dead code 2021-04-21 18:44:47 +02:00
ed
2c4b4ab928 up2k-cli: cond. readahead 2021-04-21 18:39:55 +02:00
ed
505a8fc6f6 up2k: sparse alloc on windows 2021-04-21 18:32:21 +02:00
ed
e4801d9b06 support msys2-python 2021-04-21 18:28:44 +02:00
ed
04f1b2cf3a v0.10.13 2021-04-21 01:19:22 +02:00
ed
c06d928bb5 sorry android 2021-04-21 01:10:18 +02:00
ed
ab09927e7b v0.10.12 2021-04-19 21:58:49 +02:00
ed
779437db67 up2k: more runahead 2021-04-19 21:58:30 +02:00
ed
28cbdb652e v0.10.11 2021-04-19 21:43:08 +02:00
ed
2b2415a7d8 up2k: gotta go faster 2021-04-19 21:29:43 +02:00
ed
746a8208aa v0.10.10 2021-04-19 17:17:07 +02:00
ed
a2a041a98a optimize 2021-04-19 16:54:38 +02:00
ed
10b436e449 browser: add media fragment uris 2021-04-19 16:41:06 +02:00
ed
4d62b34786 browser: add light mode 2021-04-19 15:40:32 +02:00
ed
0546210687 fix up2k progressbars 2021-04-19 13:18:29 +02:00
ed
f8c11faada don't start 2t stuff if there's no backend avail 2021-04-19 13:17:34 +02:00
ed
16d6e9be1f tweaks 2021-04-17 09:24:25 +02:00
28 changed files with 1377 additions and 795 deletions

View File

@@ -101,6 +101,11 @@ summary: it works! you can use it! (but technically not even close to beta)
* hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2 * hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2
* probably more, pls let me know * probably more, pls let me know
## not my bugs
* Windows: msys2-python 3.8.6 occasionally throws "RuntimeError: release unlocked lock" when leaving a scoped mutex in up2k
* this is an msys2 bug, the regular windows edition of python is fine
# usage # usage
@@ -111,6 +116,8 @@ the browser has the following hotkeys
* `I/K` prev/next folder * `I/K` prev/next folder
* `P` parent folder * `P` parent folder
you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&1:20` after the `.../#af-c8960dab`
## zip downloads ## zip downloads
@@ -339,7 +346,6 @@ in the `scripts` folder:
roughly sorted by priority roughly sorted by priority
* audio link with timestamp
* separate sqlite table per tag * separate sqlite table per tag
* audio fingerprinting * audio fingerprinting
* readme.md as epilogue * readme.md as epilogue

View File

@@ -16,6 +16,8 @@ if platform.system() == "Windows":
VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393] VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
# introduced in anniversary update # introduced in anniversary update
ANYWIN = WINDOWS or sys.platform in ["msys"]
MACOS = platform.system() == "Darwin" MACOS = platform.system() == "Darwin"

View File

@@ -247,6 +247,7 @@ def run_argparse(argv, formatter):
ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar") ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile (for debugging)") ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile (for debugging)")
ap.add_argument("--no-scandir", action="store_true", help="disable scandir (for debugging)") ap.add_argument("--no-scandir", action="store_true", help="disable scandir (for debugging)")
ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms") ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt") ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 10, 9) VERSION = (0, 10, 15)
CODENAME = "zip it" CODENAME = "zip it"
BUILD_DT = (2021, 4, 17) BUILD_DT = (2021, 4, 24)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -13,7 +13,7 @@ import ctypes
from datetime import datetime from datetime import datetime
import calendar import calendar
from .__init__ import E, PY2, WINDOWS from .__init__ import E, PY2, WINDOWS, ANYWIN
from .util import * # noqa # pylint: disable=unused-wildcard-import from .util import * # noqa # pylint: disable=unused-wildcard-import
from .szip import StreamZip from .szip import StreamZip
from .star import StreamTar from .star import StreamTar
@@ -182,10 +182,8 @@ class HttpCli(object):
self.out_headers.update(headers) self.out_headers.update(headers)
# default to utf8 html if no content-type is set # default to utf8 html if no content-type is set
try: if not mime:
mime = mime or self.out_headers["Content-Type"] mime = self.out_headers.get("Content-Type", "text/html; charset=UTF-8")
except KeyError:
mime = "text/html; charset=UTF-8"
self.out_headers["Content-Type"] = mime self.out_headers["Content-Type"] = mime
@@ -261,12 +259,14 @@ class HttpCli(object):
self.absolute_urls = True self.absolute_urls = True
# go home if verboten
self.readable, self.writable = self.conn.auth.vfs.can_access( self.readable, self.writable = self.conn.auth.vfs.can_access(
self.vpath, self.uname self.vpath, self.uname
) )
if not self.readable and not self.writable: if not self.readable and not self.writable:
self.log("inaccessible: [{}]".format(self.vpath)) if self.vpath:
self.log("inaccessible: [{}]".format(self.vpath))
raise Pebkac(404)
self.uparam = {"h": False} self.uparam = {"h": False}
if "h" in self.uparam: if "h" in self.uparam:
@@ -534,7 +534,7 @@ class HttpCli(object):
self.log("qj: " + repr(vbody)) self.log("qj: " + repr(vbody))
hits = idx.fsearch(vols, body) hits = idx.fsearch(vols, body)
msg = repr(hits) msg = repr(hits)
taglist = [] taglist = {}
else: else:
# search by query params # search by query params
self.log("qj: " + repr(body)) self.log("qj: " + repr(body))
@@ -626,7 +626,7 @@ class HttpCli(object):
self.loud_reply(x, status=500) self.loud_reply(x, status=500)
return False return False
if not WINDOWS and num_left == 0: if not ANYWIN and num_left == 0:
times = (int(time.time()), int(lastmod)) times = (int(time.time()), int(lastmod))
self.log("no more chunks, setting times {}".format(times)) self.log("no more chunks, setting times {}".format(times))
try: try:
@@ -680,7 +680,7 @@ class HttpCli(object):
raise Pebkac(500, "mkdir failed, check the logs") raise Pebkac(500, "mkdir failed, check the logs")
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/") vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
esc_paths = [quotep(vpath), html_escape(vpath)] esc_paths = [quotep(vpath), html_escape(vpath, crlf=True)]
html = self.j2( html = self.j2(
"msg", "msg",
h2='<a href="/{}">go to /{}</a>'.format(*esc_paths), h2='<a href="/{}">go to /{}</a>'.format(*esc_paths),
@@ -1181,17 +1181,16 @@ class HttpCli(object):
template = self.j2(tpl) template = self.j2(tpl)
st = os.stat(fsenc(fs_path)) st = os.stat(fsenc(fs_path))
# sz_md = st.st_size
ts_md = st.st_mtime ts_md = st.st_mtime
st = os.stat(fsenc(html_path)) st = os.stat(fsenc(html_path))
ts_html = st.st_mtime ts_html = st.st_mtime
# TODO dont load into memory ;_; sz_md = 0
# (trivial fix, count the &'s) for buf in yieldfile(fs_path):
with open(fsenc(fs_path), "rb") as f: sz_md += len(buf)
md = f.read().replace(b"&", b"&amp;") for c, v in [[b"&", 4], [b"<", 3], [b">", 3]]:
sz_md = len(md) sz_md += (len(buf) - len(buf.replace(c, b""))) * v
file_ts = max(ts_md, ts_html) file_ts = max(ts_md, ts_html)
file_lastmod, do_send = self._chk_lastmod(file_ts) file_lastmod, do_send = self._chk_lastmod(file_ts)
@@ -1199,27 +1198,34 @@ class HttpCli(object):
self.out_headers["Cache-Control"] = "no-cache" self.out_headers["Cache-Control"] = "no-cache"
status = 200 if do_send else 304 status = 200 if do_send else 304
boundary = "\roll\tide"
targs = { targs = {
"edit": "edit" in self.uparam, "edit": "edit" in self.uparam,
"title": html_escape(self.vpath), "title": html_escape(self.vpath, crlf=True),
"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_chk_rate": self.args.mcr,
"md": "", "md": boundary,
} }
sz_html = len(template.render(**targs).encode("utf-8")) html = template.render(**targs).encode("utf-8")
self.send_headers(sz_html + sz_md, status) html = html.split(boundary.encode("utf-8"))
if len(html) != 2:
raise Exception("boundary appears in " + html_path)
self.send_headers(sz_md + len(html[0]) + len(html[1]), status)
logmsg += unicode(status) logmsg += unicode(status)
if self.mode == "HEAD" or not do_send: if self.mode == "HEAD" or not do_send:
self.log(logmsg) self.log(logmsg)
return True return True
# TODO jinja2 can stream this right?
targs["md"] = md.decode("utf-8", "replace")
html = template.render(**targs).encode("utf-8")
try: try:
self.s.sendall(html) self.s.sendall(html[0])
for buf in yieldfile(fs_path):
self.s.sendall(html_bescape(buf))
self.s.sendall(html[1])
except: except:
self.log(logmsg + " \033[31md/c\033[0m") self.log(logmsg + " \033[31md/c\033[0m")
return False return False
@@ -1300,7 +1306,7 @@ class HttpCli(object):
else: else:
vpath += "/" + node vpath += "/" + node
vpnodes.append([quotep(vpath) + "/", html_escape(node)]) vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
vn, rem = self.auth.vfs.get( vn, rem = self.auth.vfs.get(
self.vpath, self.uname, self.readable, self.writable self.vpath, self.uname, self.readable, self.writable
@@ -1311,6 +1317,77 @@ class HttpCli(object):
# print(abspath) # print(abspath)
raise Pebkac(404) raise Pebkac(404)
srv_info = []
try:
if not self.args.nih:
srv_info.append(unicode(socket.gethostname()).split(".")[0])
except:
self.log("#wow #whoa")
try:
# some fuses misbehave
if not self.args.nid:
if WINDOWS:
bfree = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(
ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
)
srv_info.append(humansize(bfree.value) + " free")
else:
sv = os.statvfs(abspath)
free = humansize(sv.f_frsize * sv.f_bfree, True)
total = humansize(sv.f_frsize * sv.f_blocks, True)
srv_info.append(free + " free")
srv_info.append(total)
except:
pass
srv_info = "</span> /// <span>".join(srv_info)
perms = []
if self.readable:
perms.append("read")
if self.writable:
perms.append("write")
url_suf = self.urlq()
is_ls = "ls" in self.uparam
ts = "" # "?{}".format(time.time())
tpl = "browser"
if "b" in self.uparam:
tpl = "browser2"
j2a = {
"vdir": quotep(self.vpath),
"vpnodes": vpnodes,
"files": [],
"ts": ts,
"perms": json.dumps(perms),
"taglist": [],
"tag_order": [],
"have_up2k_idx": ("e2d" in vn.flags),
"have_tags_idx": ("e2t" in vn.flags),
"have_zip": (not self.args.no_zip),
"have_b_u": (self.writable and self.uparam.get("b") == "u"),
"url_suf": url_suf,
"logues": ["", ""],
"title": html_escape(self.vpath, crlf=True),
"srv_info": srv_info,
}
if not self.readable:
if is_ls:
raise Pebkac(403)
if not os.path.isdir(fsenc(abspath)):
raise Pebkac(404)
html = self.j2(tpl, **j2a)
self.reply(html.encode("utf-8", "replace"))
return True
if not os.path.isdir(fsenc(abspath)): if not os.path.isdir(fsenc(abspath)):
if abspath.endswith(".md") and "raw" not in self.uparam: if abspath.endswith(".md") and "raw" not in self.uparam:
return self.tx_md(abspath) return self.tx_md(abspath)
@@ -1354,15 +1431,11 @@ class HttpCli(object):
if rem == ".hist": if rem == ".hist":
hidden = ["up2k."] hidden = ["up2k."]
is_ls = "ls" in self.uparam
icur = None icur = None
if "e2t" in vn.flags: if "e2t" in vn.flags:
idx = self.conn.get_u2idx() idx = self.conn.get_u2idx()
icur = idx.get_cur(vn.realpath) icur = idx.get_cur(vn.realpath)
url_suf = self.urlq()
dirs = [] dirs = []
files = [] files = []
for fn in vfs_ls: for fn in vfs_ls:
@@ -1394,7 +1467,7 @@ class HttpCli(object):
margin = '<a href="{}?zip">zip</a>'.format(quotep(href)) margin = '<a href="{}?zip">zip</a>'.format(quotep(href))
elif fn in hist: elif fn in hist:
margin = '<a href="{}.hist/{}">#{}</a>'.format( margin = '<a href="{}.hist/{}">#{}</a>'.format(
base, html_escape(hist[fn][2], quote=True), hist[fn][0] base, html_escape(hist[fn][2], quote=True, crlf=True), hist[fn][0]
) )
else: else:
margin = "-" margin = "-"
@@ -1453,42 +1526,6 @@ class HttpCli(object):
for f in dirs: for f in dirs:
f["tags"] = {} f["tags"] = {}
srv_info = []
try:
if not self.args.nih:
srv_info.append(unicode(socket.gethostname()).split(".")[0])
except:
self.log("#wow #whoa")
pass
try:
# some fuses misbehave
if not self.args.nid:
if WINDOWS:
bfree = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(
ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
)
srv_info.append(humansize(bfree.value) + " free")
else:
sv = os.statvfs(abspath)
free = humansize(sv.f_frsize * sv.f_bfree, True)
total = humansize(sv.f_frsize * sv.f_blocks, True)
srv_info.append(free + " free")
srv_info.append(total)
except:
pass
srv_info = "</span> /// <span>".join(srv_info)
perms = []
if self.readable:
perms.append("read")
if self.writable:
perms.append("write")
logues = ["", ""] logues = ["", ""]
for n, fn in enumerate([".prologue.html", ".epilogue.html"]): for n, fn in enumerate([".prologue.html", ".epilogue.html"]):
fn = os.path.join(abspath, fn) fn = os.path.join(abspath, fn)
@@ -1510,34 +1547,12 @@ class HttpCli(object):
self.reply(ret.encode("utf-8", "replace"), mime="application/json") self.reply(ret.encode("utf-8", "replace"), mime="application/json")
return True return True
ts = "" j2a["files"] = dirs + files
# ts = "?{}".format(time.time()) j2a["logues"] = logues
j2a["taglist"] = taglist
if "mte" in vn.flags:
j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
dirs.extend(files) html = self.j2(tpl, **j2a)
tpl = "browser"
if "b" in self.uparam:
tpl = "browser2"
html = self.j2(
tpl,
vdir=quotep(self.vpath),
vpnodes=vpnodes,
files=dirs,
ts=ts,
perms=json.dumps(perms),
taglist=taglist,
tag_order=json.dumps(
vn.flags["mte"].split(",") if "mte" in vn.flags else []
),
have_up2k_idx=("e2d" in vn.flags),
have_tags_idx=("e2t" in vn.flags),
have_zip=(not self.args.no_zip),
have_b_u=(self.writable and self.uparam.get("b") == "u"),
url_suf=url_suf,
logues=logues,
title=html_escape(self.vpath),
srv_info=srv_info,
)
self.reply(html.encode("utf-8", "replace")) self.reply(html.encode("utf-8", "replace"))
return True return True

View File

@@ -16,7 +16,7 @@ import traceback
import subprocess as sp import subprocess as sp
from copy import deepcopy from copy import deepcopy
from .__init__ import WINDOWS from .__init__ import WINDOWS, ANYWIN
from .util import ( from .util import (
Pebkac, Pebkac,
Queue, Queue,
@@ -79,7 +79,7 @@ class Up2k(object):
if self.sqlite_ver < (3, 9): if self.sqlite_ver < (3, 9):
self.no_expr_idx = True self.no_expr_idx = True
if WINDOWS: if ANYWIN:
# usually fails to set lastmod too quickly # usually fails to set lastmod too quickly
self.lastmod_q = Queue() self.lastmod_q = Queue()
thr = threading.Thread(target=self._lastmodder) thr = threading.Thread(target=self._lastmodder)
@@ -101,17 +101,18 @@ class Up2k(object):
thr.daemon = True thr.daemon = True
thr.start() thr.start()
thr = threading.Thread(target=self._tagger)
thr.daemon = True
thr.start()
thr = threading.Thread(target=self._hasher) thr = threading.Thread(target=self._hasher)
thr.daemon = True thr.daemon = True
thr.start() thr.start()
thr = threading.Thread(target=self._run_all_mtp) if self.mtag:
thr.daemon = True thr = threading.Thread(target=self._tagger)
thr.start() thr.daemon = True
thr.start()
thr = threading.Thread(target=self._run_all_mtp)
thr.daemon = True
thr.start()
def log(self, msg, c=0): def log(self, msg, c=0):
self.log_func("up2k", msg + "\033[K", c) self.log_func("up2k", msg + "\033[K", c)
@@ -667,12 +668,6 @@ class Up2k(object):
cur.close() cur.close()
def _start_mpool(self): def _start_mpool(self):
if WINDOWS and False:
nah = open(os.devnull, "wb")
wmic = "processid={}".format(os.getpid())
wmic = ["wmic", "process", "where", wmic, "call", "setpriority"]
sp.call(wmic + ["below normal"], stdout=nah, stderr=nah)
# mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor # mp.pool.ThreadPool and concurrent.futures.ThreadPoolExecutor
# both do crazy runahead so lets reinvent another wheel # both do crazy runahead so lets reinvent another wheel
nw = os.cpu_count() if hasattr(os, "cpu_count") else 4 nw = os.cpu_count() if hasattr(os, "cpu_count") else 4
@@ -697,12 +692,6 @@ class Up2k(object):
mpool.join() mpool.join()
done = self._flush_mpool(wcur) done = self._flush_mpool(wcur)
if WINDOWS and False:
nah = open(os.devnull, "wb")
wmic = "processid={}".format(os.getpid())
wmic = ["wmic", "process", "where", wmic, "call", "setpriority"]
sp.call(wmic + ["below normal"], stdout=nah, stderr=nah)
return done return done
def _tag_thr(self, q): def _tag_thr(self, q):
@@ -1109,8 +1098,9 @@ class Up2k(object):
atomic_move(src, dst) atomic_move(src, dst)
if WINDOWS: if ANYWIN:
self.lastmod_q.put([dst, (int(time.time()), int(job["lmod"]))]) a = [dst, job["size"], (int(time.time()), int(job["lmod"]))]
self.lastmod_q.put(a)
# legit api sware 2 me mum # legit api sware 2 me mum
if self.idx_wark( if self.idx_wark(
@@ -1211,6 +1201,17 @@ class Up2k(object):
suffix = ".{:.6f}-{}".format(job["t0"], job["addr"]) suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f: with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
f, job["tnam"] = f["orz"] f, job["tnam"] = f["orz"]
if (
ANYWIN
and self.args.sparse
and self.args.sparse * 1024 * 1024 <= job["size"]
):
fp = os.path.join(pdir, job["tnam"])
try:
sp.check_call(["fsutil", "sparse", "setflag", fp])
except:
self.log("could not sparse [{}]".format(fp), 3)
f.seek(job["size"] - 1) f.seek(job["size"] - 1)
f.write(b"e") f.write(b"e")
@@ -1222,13 +1223,19 @@ class Up2k(object):
# self.log("lmod: got {}".format(len(ready))) # self.log("lmod: got {}".format(len(ready)))
time.sleep(5) time.sleep(5)
for path, times in ready: for path, sz, times in ready:
self.log("lmod: setting times {} on {}".format(times, path)) self.log("lmod: setting times {} on {}".format(times, path))
try: try:
os.utime(fsenc(path), times) os.utime(fsenc(path), times)
except: except:
self.log("lmod: failed to utime ({}, {})".format(path, times)) self.log("lmod: failed to utime ({}, {})".format(path, times))
if self.args.sparse and self.args.sparse * 1024 * 1024 <= sz:
try:
sp.check_call(["fsutil", "sparse", "setflag", path, "0"])
except:
self.log("could not unsparse [{}]".format(path), 3)
def _snapshot(self): def _snapshot(self):
persist_interval = 30 # persist unfinished uploads index every 30 sec persist_interval = 30 # persist unfinished uploads index every 30 sec
discard_interval = 21600 # drop unfinished uploads after 6 hours inactivity discard_interval = 21600 # drop unfinished uploads after 6 hours inactivity

View File

@@ -16,7 +16,7 @@ import mimetypes
import contextlib import contextlib
import subprocess as sp # nosec import subprocess as sp # nosec
from .__init__ import PY2, WINDOWS from .__init__ import PY2, WINDOWS, ANYWIN
from .stolen import surrogateescape from .stolen import surrogateescape
FAKE_MP = False FAKE_MP = False
@@ -580,8 +580,8 @@ def sanitize_fn(fn, ok=""):
if "/" not in ok: if "/" not in ok:
fn = fn.replace("\\", "/").split("/")[-1] fn = fn.replace("\\", "/").split("/")[-1]
if WINDOWS: if ANYWIN:
for bad, good in [x for x in [ remap = [
["<", ""], ["<", ""],
[">", ""], [">", ""],
[":", ""], [":", ""],
@@ -591,7 +591,8 @@ def sanitize_fn(fn, ok=""):
["|", ""], ["|", ""],
["?", ""], ["?", ""],
["*", ""], ["*", ""],
] if x[0] not in ok]: ]
for bad, good in [x for x in remap if x[0] not in ok]:
fn = fn.replace(bad, good) fn = fn.replace(bad, good)
bad = ["con", "prn", "aux", "nul"] bad = ["con", "prn", "aux", "nul"]
@@ -615,17 +616,24 @@ def exclude_dotfiles(filepaths):
return [x for x in filepaths if not x.split("/")[-1].startswith(".")] return [x for x in filepaths if not x.split("/")[-1].startswith(".")]
def html_escape(s, quote=False): def html_escape(s, quote=False, crlf=False):
"""html.escape but also newlines""" """html.escape but also newlines"""
s = ( s = s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\r", "&#13;")
.replace("\n", "&#10;")
)
if quote: if quote:
s = s.replace('"', "&quot;").replace("'", "&#x27;") s = s.replace('"', "&quot;").replace("'", "&#x27;")
if crlf:
s = s.replace("\r", "&#13;").replace("\n", "&#10;")
return s
def html_bescape(s, quote=False, crlf=False):
"""html.escape but bytestrings"""
s = s.replace(b"&", b"&amp;").replace(b"<", b"&lt;").replace(b">", b"&gt;")
if quote:
s = s.replace(b'"', b"&quot;").replace(b"'", b"&#x27;")
if crlf:
s = s.replace(b"\r", b"&#13;").replace(b"\n", b"&#10;")
return s return s

View File

@@ -68,7 +68,7 @@ a, #files tbody div a:last-child {
color: #999; color: #999;
font-weight: normal; font-weight: normal;
} }
#files tr+tr:hover { #files tr:hover {
background: #1c1c1c; background: #1c1c1c;
} }
#files thead th { #files thead th {
@@ -98,7 +98,7 @@ a, #files tbody div a:last-child {
max-width: 30em; max-width: 30em;
overflow: hidden; overflow: hidden;
} }
#files tr+tr td { #files tr td {
border-top: 1px solid #383838; border-top: 1px solid #383838;
} }
#files tbody td:nth-child(3) { #files tbody td:nth-child(3) {
@@ -284,7 +284,7 @@ a, #files tbody div a:last-child {
line-height: 1em; line-height: 1em;
} }
#wtoggle.sel { #wtoggle.sel {
width: 6em; width: 6.4em;
} }
#wtoggle.sel #wzip { #wtoggle.sel #wzip {
display: inline-block; display: inline-block;
@@ -685,3 +685,186 @@ input[type="checkbox"]:checked+label {
font-family: monospace, monospace; font-family: monospace, monospace;
line-height: 2em; line-height: 2em;
} }
#pvol,
#barbuf,
#barpos,
#u2conf label {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
html.light {
color: #333;
background: #eee;
text-shadow: none;
}
html.light #ops,
html.light .opbox,
html.light #srch_form {
background: #f7f7f7;
box-shadow: 0 0 .3em #ddd;
border-color: #f7f7f7;
}
html.light #ops a.act {
box-shadow: 0 .2em .2em #ccc;
background: #fff;
border-color: #07a;
padding-top: .4em;
}
html.light #op_cfg h3 {
border-color: #ccc;
}
html.light .tglbtn,
html.light #tree > a + a {
color: #666;
background: #ddd;
box-shadow: none;
}
html.light .tglbtn:hover,
html.light #tree > a + a:hover {
background: #caf;
}
html.light .tglbtn.on,
html.light #tree > a + a.on {
background: #4a0;
color: #fff;
}
html.light #srv_info {
color: #c83;
text-shadow: 1px 1px 0 #fff;
}
html.light #srv_info span {
color: #000;
}
html.light #treeul a+a {
background: inherit;
color: #06a;
}
html.light #treeul a.hl {
background: #07a;
color: #fff;
}
html.light #tree li {
border-color: #ddd #fff #f7f7f7 #fff;
}
html.light #tree ul {
border-color: #ccc;
}
html.light a,
html.light #ops a,
html.light #files tbody div a:last-child {
color: #06a;
}
html.light #files tbody {
background: #f7f7f7;
}
html.light #files {
box-shadow: 0 0 .3em #ccc;
}
html.light #files thead th {
background: #eee;
}
html.light #files tr td {
border-top: 1px solid #ddd;
}
html.light #files td {
border-bottom: 1px solid #f7f7f7;
}
html.light #files tbody tr:last-child td {
border-bottom: .2em solid #ccc;
}
html.light #files td:nth-child(2n) {
color: #d38;
}
html.light #files tr:hover td {
background: #fff;
}
html.light #files tbody a.play {
color: #c0f;
}
html.light tr.play td {
background: #fc5;
}
html.light tr.play a {
color: #406;
}
html.light #files th:hover .cfg,
html.light #files th.min .cfg {
background: #ccc;
}
html.light #files > thead > tr > th.min span {
background: linear-gradient(90deg, rgba(204,204,204,0), rgba(204,204,204,0.5) 70%, #ccc);
}
html.light #blocked {
background: #eee;
}
html.light #blk_play a,
html.light #blk_abrt a {
background: #fff;
box-shadow: 0 .2em .4em #ddd;
}
html.light #widget a {
color: #fc5;
}
html.light #files tr.sel:hover td {
background: #c37;
}
html.light #files tr.sel td {
color: #fff;
}
html.light #files tr.sel a {
color: #fff;
}
html.light input[type="checkbox"] + label {
color: #333;
}
html.light .opview input[type="text"] {
background: #fff;
color: #333;
box-shadow: 0 0 2px #888;
border-color: #38d;
}
html.light #ops:hover #opdesc {
background: #fff;
box-shadow: 0 .3em 1em #ccc;
}
html.light #opdesc code {
background: #060;
color: #fff;
}
html.light #u2tab a>span,
html.light #files td div span {
color: #000;
}
html.light #path {
background: #f7f7f7;
text-shadow: none;
box-shadow: 0 0 .3em #bbb;
}
html.light #path a {
color: #333;
}
html.light #path a:not(:last-child)::after {
border-color: #ccc;
background: none;
border-width: .1em .1em 0 0;
margin: -.2em .3em -.2em -.3em;
}
html.light #path a:hover {
background: none;
color: #60a;
}
html.light #files tbody div a {
color: #d38;
}
html.light #files a:hover,
html.light #files tr.sel a:hover {
color: #000;
background: #fff;
}

View File

@@ -39,14 +39,17 @@
{%- include 'upload.html' %} {%- include 'upload.html' %}
<div id="op_cfg" class="opview opbox"> <div id="op_cfg" class="opview opbox">
<h3>key notation</h3> <h3>switches</h3>
<div id="key_notation"></div> <div>
<a id="tooltips" class="tglbtn" href="#">tooltips</a>
<a id="lightmode" class="tglbtn" href="#">lightmode</a>
</div>
{%- if have_zip %} {%- if have_zip %}
<h3>folder download</h3> <h3>folder download</h3>
<div id="arc_fmt"></div> <div id="arc_fmt"></div>
{%- endif %} {%- endif %}
<h3>tooltips</h3> <h3>key notation</h3>
<div><a id="tooltips" class="tglbtn" href="#">enable</a></div> <div id="key_notation"></div>
</div> </div>
<h1 id="path"> <h1 id="path">

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,9 @@ pre code:last-child {
pre code::before { pre code::before {
content: counter(precode); content: counter(precode);
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: inline-block; display: inline-block;
text-align: right; text-align: right;
font-size: .75em; font-size: .75em;

View File

@@ -138,10 +138,10 @@ var md_opt = {
document.documentElement.setAttribute("class", dark ? "dark" : ""); document.documentElement.setAttribute("class", dark ? "dark" : "");
btn.innerHTML = "go " + (dark ? "light" : "dark"); btn.innerHTML = "go " + (dark ? "light" : "dark");
if (window.localStorage) if (window.localStorage)
localStorage.setItem('darkmode', dark ? 1 : 0); localStorage.setItem('lightmode', dark ? 0 : 1);
}; };
btn.onclick = toggle; btn.onclick = toggle;
if (window.localStorage && localStorage.getItem('darkmode') == 1) if (window.localStorage && localStorage.getItem('lightmode') != 1)
toggle(); toggle();
})(); })();

View File

@@ -46,7 +46,7 @@ function statify(obj) {
var ua = navigator.userAgent; var ua = navigator.userAgent;
if (ua.indexOf(') Gecko/') !== -1 && /Linux| Mac /.exec(ua)) { if (ua.indexOf(') Gecko/') !== -1 && /Linux| Mac /.exec(ua)) {
// necessary on ff-68.7 at least // necessary on ff-68.7 at least
var s = document.createElement('style'); var s = mknod('style');
s.innerHTML = '@page { margin: .5in .6in .8in .6in; }'; s.innerHTML = '@page { margin: .5in .6in .8in .6in; }';
console.log(s.innerHTML); console.log(s.innerHTML);
document.head.appendChild(s); document.head.appendChild(s);
@@ -175,12 +175,12 @@ function md_plug_err(ex, js) {
msg = "Line " + ln + ", " + msg; msg = "Line " + ln + ", " + msg;
var lns = js.split('\n'); var lns = js.split('\n');
if (ln < lns.length) { if (ln < lns.length) {
o = document.createElement('span'); o = mknod('span');
o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block'; o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block';
o.textContent = lns[ln - 1]; o.textContent = lns[ln - 1];
} }
} }
errbox = document.createElement('div'); errbox = mknod('div');
errbox.setAttribute('id', 'md_errbox'); errbox.setAttribute('id', 'md_errbox');
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5' errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
errbox.textContent = msg; errbox.textContent = msg;

View File

@@ -16,7 +16,7 @@ var dom_sbs = ebi('sbs');
var dom_nsbs = ebi('nsbs'); var dom_nsbs = ebi('nsbs');
var dom_tbox = ebi('toolsbox'); var dom_tbox = ebi('toolsbox');
var dom_ref = (function () { var dom_ref = (function () {
var d = document.createElement('div'); var d = mknod('div');
d.setAttribute('id', 'mtr'); d.setAttribute('id', 'mtr');
dom_swrap.appendChild(d); dom_swrap.appendChild(d);
d = ebi('mtr'); d = ebi('mtr');
@@ -71,7 +71,7 @@ var map_src = [];
var map_pre = []; var map_pre = [];
function genmap(dom, oldmap) { function genmap(dom, oldmap) {
var find = nlines; var find = nlines;
while (oldmap && find --> 0) { while (oldmap && find-- > 0) {
var tmap = genmapq(dom, '*[data-ln="' + find + '"]'); var tmap = genmapq(dom, '*[data-ln="' + find + '"]');
if (!tmap || !tmap.length) if (!tmap || !tmap.length)
continue; continue;
@@ -94,7 +94,7 @@ var nlines = 0;
var draw_md = (function () { var draw_md = (function () {
var delay = 1; var delay = 1;
function draw_md() { function draw_md() {
var t0 = new Date().getTime(); var t0 = Date.now();
var src = dom_src.value; var src = dom_src.value;
convert_markdown(src, dom_pre); convert_markdown(src, dom_pre);
@@ -110,7 +110,7 @@ var draw_md = (function () {
cls(ebi('save'), 'disabled', src == server_md); cls(ebi('save'), 'disabled', src == server_md);
var t1 = new Date().getTime(); var t1 = Date.now();
delay = t1 - t0 > 100 ? 25 : 1; delay = t1 - t0 > 100 ? 25 : 1;
} }
@@ -252,7 +252,7 @@ function Modpoll() {
} }
console.log('modpoll...'); console.log('modpoll...');
var url = (document.location + '').split('?')[0] + '?raw&_=' + new Date().getTime(); var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.modpoll = this; xhr.modpoll = this;
xhr.open('GET', url, true); xhr.open('GET', url, true);
@@ -399,7 +399,7 @@ function save_cb() {
function run_savechk(lastmod, txt, btn, ntry) { function run_savechk(lastmod, txt, btn, ntry) {
// download the saved doc from the server and compare // download the saved doc from the server and compare
var url = (document.location + '').split('?')[0] + '?raw&_=' + new Date().getTime(); var url = (document.location + '').split('?')[0] + '?raw&_=' + Date.now();
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', url, true); xhr.open('GET', url, true);
xhr.responseType = 'text'; xhr.responseType = 'text';
@@ -455,7 +455,7 @@ function toast(autoclose, style, width, msg) {
ok.parentNode.removeChild(ok); ok.parentNode.removeChild(ok);
style = "width:" + width + "em;left:calc(50% - " + (width / 2) + "em);" + style; style = "width:" + width + "em;left:calc(50% - " + (width / 2) + "em);" + style;
ok = document.createElement('div'); ok = mknod('div');
ok.setAttribute('id', 'toast'); ok.setAttribute('id', 'toast');
ok.setAttribute('style', style); ok.setAttribute('style', style);
ok.innerHTML = msg; ok.innerHTML = msg;
@@ -1049,7 +1049,7 @@ action_stack = (function () {
var p1 = from.length, var p1 = from.length,
p2 = to.length; p2 = to.length;
while (p1 --> 0 && p2 --> 0) while (p1-- > 0 && p2-- > 0)
if (from[p1] != to[p2]) if (from[p1] != to[p2])
break; break;

View File

@@ -31,12 +31,12 @@ var md_opt = {
var lightswitch = (function () { var lightswitch = (function () {
var fun = function () { var fun = function () {
var dark = !!!document.documentElement.getAttribute("class"); var dark = !document.documentElement.getAttribute("class");
document.documentElement.setAttribute("class", dark ? "dark" : ""); document.documentElement.setAttribute("class", dark ? "dark" : "");
if (window.localStorage) if (window.localStorage)
localStorage.setItem('darkmode', dark ? 1 : 0); localStorage.setItem('lightmode', dark ? 0 : 1);
}; };
if (window.localStorage && localStorage.getItem('darkmode') == 1) if (window.localStorage && localStorage.getItem('lightmode') != 1)
fun(); fun();
return fun; return fun;

View File

@@ -71,7 +71,7 @@ var mde = (function () {
})(); })();
function set_jumpto() { function set_jumpto() {
document.querySelector('.editor-preview-side').onclick = jumpto; QS('.editor-preview-side').onclick = jumpto;
} }
function jumpto(ev) { function jumpto(ev) {
@@ -94,7 +94,7 @@ function md_changed(mde, on_srv) {
window.md_saved = mde.value(); window.md_saved = mde.value();
var md_now = mde.value(); var md_now = mde.value();
var save_btn = document.querySelector('.editor-toolbar button.save'); var save_btn = QS('.editor-toolbar button.save');
if (md_now == window.md_saved) if (md_now == window.md_saved)
save_btn.classList.add('disabled'); save_btn.classList.add('disabled');
@@ -105,7 +105,7 @@ function md_changed(mde, on_srv) {
} }
function save(mde) { function save(mde) {
var save_btn = document.querySelector('.editor-toolbar button.save'); var save_btn = QS('.editor-toolbar button.save');
if (save_btn.classList.contains('disabled')) { if (save_btn.classList.contains('disabled')) {
alert('there is nothing to save'); alert('there is nothing to save');
return; return;
@@ -212,7 +212,7 @@ function save_chk() {
last_modified = this.lastmod; last_modified = this.lastmod;
md_changed(this.mde, true); md_changed(this.mde, true);
var ok = document.createElement('div'); var ok = mknod('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 = ebi('m'); var parent = ebi('m');

View File

@@ -38,7 +38,7 @@
</div> </div>
<script> <script>
if (window.localStorage && localStorage.getItem('darkmode') == 1) if (window.localStorage && localStorage.getItem('lightmode') != 1)
document.documentElement.setAttribute("class", "dark"); document.documentElement.setAttribute("class", "dark");
</script> </script>

View File

@@ -55,7 +55,7 @@ function up2k_flagbus() {
dbg(who, 'hi me (??)'); dbg(who, 'hi me (??)');
return; return;
} }
flag.act = new Date().getTime(); flag.act = Date.now();
if (what == "want") { if (what == "want") {
// lowest id wins, don't care if that's us // lowest id wins, don't care if that's us
if (who < flag.id) { if (who < flag.id) {
@@ -141,7 +141,7 @@ function U2pvis(act, btns) {
this.tail = -1; this.tail = -1;
this.wsz = 3; this.wsz = 3;
this.addfile = function (entry) { this.addfile = function (entry, sz) {
this.tab.push({ this.tab.push({
"hn": entry[0], "hn": entry[0],
"ht": entry[1], "ht": entry[1],
@@ -149,8 +149,10 @@ function U2pvis(act, btns) {
"in": 'q', "in": 'q',
"nh": 0, //hashed "nh": 0, //hashed
"nd": 0, //done "nd": 0, //done
"pa": [], //percents "cb": [], // bytes done in chunk
"pb": [] //active-list "bt": sz, // bytes total
"bd": 0, // bytes done
"bd0": 0 // upload start
}); });
this.ctr["q"]++; this.ctr["q"]++;
this.drawcard("q"); this.drawcard("q");
@@ -172,6 +174,9 @@ function U2pvis(act, btns) {
this.seth = function (nfile, field, html) { this.seth = function (nfile, field, html) {
var fo = this.tab[nfile]; var fo = this.tab[nfile];
field = ['hn', 'ht', 'hp'][field]; field = ['hn', 'ht', 'hp'][field];
if (fo[field] === html)
return;
fo[field] = html; fo[field] = html;
if (!this.is_act(fo.in)) if (!this.is_act(fo.in))
return; return;
@@ -184,61 +189,62 @@ function U2pvis(act, btns) {
} }
}; };
this.setab = function (nfile, blocks) { this.setab = function (nfile, nblocks) {
var t = []; var t = [];
for (var a = 0; a < blocks; a++) for (var a = 0; a < nblocks; a++)
t.push(0); t.push(0);
this.tab[nfile].pa = t; this.tab[nfile].cb = t;
}; };
this.perc = function (n, t, e, sz, t0) { this.setat = function (nfile, blocktab) {
var p = (n + e) * 100.0 / t, this.tab[nfile].cb = blocktab;
td = new Date().getTime() - t0,
pp = (td / 1000) / p, var bd = 0;
spd = (sz / 100) / pp, for (var a = 0; a < blocktab.length; a++)
eta = pp * (100 - p); bd += blocktab[a];
this.tab[nfile].bd = bd;
this.tab[nfile].bd0 = bd;
};
this.perc = function (bd, bd0, sz, t0) {
var td = Date.now() - t0,
p = bd * 100.0 / sz,
nb = bd - bd0,
spd = nb / (td / 1000),
eta = (sz - bd) / spd;
return [p, s2ms(eta), spd / (1024 * 1024)]; return [p, s2ms(eta), spd / (1024 * 1024)];
}; };
this.hashed = function (fobj) { this.hashed = function (fobj) {
var fo = this.tab[fobj.n]; var fo = this.tab[fobj.n],
fo.nh++; nb = fo.bt * (++fo.nh / fo.cb.length),
var p = this.perc(fo.nh, fo.pa.length, 0, fobj.size, fobj.t1); p = this.perc(nb, 0, fobj.size, fobj.t1);
fo.hp = '{0}%, {1}, {2} MB/s'.format( fo.hp = '{0}%, {1}, {2} MB/s'.format(
p[0].toFixed(2), p[1], p[2].toFixed(2) p[0].toFixed(2), p[1], p[2].toFixed(2)
); );
if (!this.is_act(fo.in)) if (!this.is_act(fo.in))
return; return;
var obj = ebi('f{0}p'.format(fobj.n)); var obj = ebi('f{0}p'.format(fobj.n)),
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
obj.innerHTML = fo.hp; obj.innerHTML = fo.hp;
obj.style.color = '#fff'; obj.style.color = '#fff';
var o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0]; obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #08d ' + o2 + '%, #333 ' + o3 + '%)';
}; };
this.prog = function (fobj, nchunk, percent) { this.prog = function (fobj, nchunk, cbd) {
var fo = this.tab[fobj.n], pb = fo.pb; var fo = this.tab[fobj.n],
var i = pb.indexOf(nchunk); delta = cbd - fo.cb[nchunk];
fo.pa[nchunk] = percent;
if (percent == 101) {
fo.nd++;
if (i >= 0)
pb.splice(i);
}
else if (i == -1) {
pb.push(nchunk);
}
var extra = 0; fo.cb[nchunk] = cbd;
for (var a = 0; a < pb.length; a++) fo.bd += delta;
extra += fo.pa[a];
extra /= fo.pa.length; var p = this.perc(fo.bd, fo.bd0, fo.bt, fobj.t3);
var p = this.perc(fo.nd, fo.pa.length, extra, fobj.size, fobj.t3);
fo.hp = '{0}%, {1}, {2} MB/s'.format( fo.hp = '{0}%, {1}, {2} MB/s'.format(
p[0].toFixed(2), p[1], p[2].toFixed(2) p[0].toFixed(2), p[1], p[2].toFixed(2)
); );
@@ -246,11 +252,12 @@ function U2pvis(act, btns) {
if (!this.is_act(fo.in)) if (!this.is_act(fo.in))
return; return;
var obj = ebi('f{0}p'.format(fobj.n)); var obj = ebi('f{0}p'.format(fobj.n)),
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
obj.innerHTML = fo.hp; obj.innerHTML = fo.hp;
obj.style.color = '#fff'; obj.style.color = '#fff';
var o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0]; obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%)';
}; };
this.move = function (nfile, newcat) { this.move = function (nfile, newcat) {
@@ -284,24 +291,14 @@ function U2pvis(act, btns) {
} }
}; };
this.bzw_log = function (first, last) {
console.log("first %d head %d tail %d last %d", first, this.head, this.tail, last);
var trs = document.querySelectorAll('#u2tab>tbody>tr'), msg = [];
for (var a = 0; a < trs.length; a++)
msg.push(trs[a].getAttribute('id'));
console.log(msg.join(' '));
}
this.bzw = function () { this.bzw = function () {
var first = document.querySelector('#u2tab>tbody>tr:first-child'); var first = QS('#u2tab>tbody>tr:first-child');
if (!first) if (!first)
return; return;
var last = document.querySelector('#u2tab>tbody>tr:last-child'); var last = QS('#u2tab>tbody>tr:last-child');
first = parseInt(first.getAttribute('id').slice(1)); first = parseInt(first.getAttribute('id').slice(1));
last = parseInt(last.getAttribute('id').slice(1)); last = parseInt(last.getAttribute('id').slice(1));
//this.bzw_log(first, last);
while (this.head - first > this.wsz) { while (this.head - first > this.wsz) {
var obj = ebi('f' + (first++)); var obj = ebi('f' + (first++));
@@ -312,12 +309,10 @@ function U2pvis(act, btns) {
if (!obj) if (!obj)
this.addrow(last); this.addrow(last);
} }
//this.bzw_log(first, last);
//console.log('--');
}; };
this.drawcard = function (cat) { this.drawcard = function (cat) {
var cards = document.querySelectorAll('#u2cards>a>span'); var cards = QSA('#u2cards>a>span');
if (cat == "q") { if (cat == "q") {
cards[4].innerHTML = this.ctr[cat]; cards[4].innerHTML = this.ctr[cat];
@@ -340,9 +335,9 @@ function U2pvis(act, btns) {
this.changecard = function (card) { this.changecard = function (card) {
this.act = card; this.act = card;
var html = [];
this.head = -1; this.head = -1;
this.tail = -1; this.tail = -1;
var html = [];
for (var a = 0; a < this.tab.length; a++) { for (var a = 0; a < this.tab.length; a++) {
var rt = this.tab[a].in; var rt = this.tab[a].in;
if (this.is_act(rt)) { if (this.is_act(rt)) {
@@ -379,7 +374,7 @@ function U2pvis(act, btns) {
if (as_html) if (as_html)
return '<tr id="f' + nfile + '">' + ret + '</tr>'; return '<tr id="f' + nfile + '">' + ret + '</tr>';
var obj = document.createElement('tr'); var obj = mknod('tr');
obj.setAttribute('id', 'f' + nfile); obj.setAttribute('id', 'f' + nfile);
obj.innerHTML = ret; obj.innerHTML = ret;
return obj; return obj;
@@ -391,7 +386,7 @@ function U2pvis(act, btns) {
}; };
var that = this; var that = this;
btns = document.querySelectorAll(btns + '>a[act]'); btns = QSA(btns + '>a[act]');
for (var a = 0; a < btns.length; a++) { for (var a = 0; a < btns.length; a++) {
btns[a].onclick = function (e) { btns[a].onclick = function (e) {
ev(e); ev(e);
@@ -408,7 +403,6 @@ function U2pvis(act, btns) {
function up2k_init(have_crypto) { function up2k_init(have_crypto) {
//have_crypto = false; //have_crypto = false;
var need_filereader_cache = undefined;
// show modal message // show modal message
function showmodal(msg) { function showmodal(msg) {
@@ -426,8 +420,9 @@ function up2k_init(have_crypto) {
ebi('u2notbtn').innerHTML = ''; ebi('u2notbtn').innerHTML = '';
} }
var shame = 'your browser <a href="https://www.chromium.org/blink/webcrypto">disables sha512</a> unless you <a href="' + (window.location + '').replace(':', 's:') + '">use https</a>' var shame = 'your browser <a href="https://www.chromium.org/blink/webcrypto">disables sha512</a> unless you <a href="' + (window.location + '').replace(':', 's:') + '">use https</a>',
var is_https = (window.location + '').indexOf('https:') === 0; is_https = (window.location + '').indexOf('https:') === 0;
if (is_https) if (is_https)
// chrome<37 firefox<34 edge<12 ie<11 opera<24 safari<10.1 // chrome<37 firefox<34 edge<12 ie<11 opera<24 safari<10.1
shame = 'your browser is impressively ancient'; shame = 'your browser is impressively ancient';
@@ -484,13 +479,14 @@ function up2k_init(have_crypto) {
}; };
} }
var parallel_uploads = icfg_get('nthread'); var parallel_uploads = icfg_get('nthread'),
var multitask = bcfg_get('multitask', true); multitask = bcfg_get('multitask', true),
var ask_up = bcfg_get('ask_up', true); ask_up = bcfg_get('ask_up', true),
var flag_en = bcfg_get('flag_en', false); flag_en = bcfg_get('flag_en', false),
var fsearch = bcfg_get('fsearch', false); fsearch = bcfg_get('fsearch', false),
fdom_ctr = 0,
min_filebuf = 0;
var fdom_ctr = 0;
var st = { var st = {
"files": [], "files": [],
"todo": { "todo": {
@@ -540,8 +536,9 @@ function up2k_init(have_crypto) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
var files; var files,
var is_itemlist = false; is_itemlist = false;
if (e.dataTransfer) { if (e.dataTransfer) {
if (e.dataTransfer.items) { if (e.dataTransfer.items) {
files = e.dataTransfer.items; // DataTransferItemList files = e.dataTransfer.items; // DataTransferItemList
@@ -555,9 +552,10 @@ function up2k_init(have_crypto) {
return alert('no files selected??'); return alert('no files selected??');
more_one_file(); more_one_file();
var bad_files = []; var bad_files = [],
var good_files = []; good_files = [],
var dirs = []; dirs = [];
for (var a = 0; a < files.length; a++) { for (var a = 0; a < files.length; a++) {
var fobj = files[a]; var fobj = files[a];
if (is_itemlist) { if (is_itemlist) {
@@ -642,12 +640,13 @@ function up2k_init(have_crypto) {
function gotallfiles(good_files, bad_files) { function gotallfiles(good_files, bad_files) {
if (bad_files.length > 0) { if (bad_files.length > 0) {
var ntot = bad_files.length + good_files.length; var ntot = bad_files.length + good_files.length,
var msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, ntot); msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, ntot);
for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++) for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++)
msg += '-- ' + bad_files[a] + '\n'; msg += '-- ' + bad_files[a] + '\n';
if (good_files.length - bad_files.length <= 1 && /(android)/i.test(navigator.userAgent)) if (good_files.length - bad_files.length <= 1 && ANDROID)
msg += '\nFirefox-Android has a bug which prevents selecting multiple files. Try selecting one file at a time. For more info, see firefox bug 1456557'; msg += '\nFirefox-Android has a bug which prevents selecting multiple files. Try selecting one file at a time. For more info, see firefox bug 1456557';
alert(msg); alert(msg);
@@ -661,9 +660,10 @@ function up2k_init(have_crypto) {
return; return;
for (var a = 0; a < good_files.length; a++) { for (var a = 0; a < good_files.length; a++) {
var fobj = good_files[a][0]; var fobj = good_files[a][0],
var now = new Date().getTime(); now = Date.now(),
var lmod = fobj.lastModified || now; lmod = fobj.lastModified || now;
var entry = { var entry = {
"n": parseInt(st.files.length.toString()), "n": parseInt(st.files.length.toString()),
"t0": now, "t0": now,
@@ -690,7 +690,7 @@ function up2k_init(have_crypto) {
esc(uricom_dec(entry.purl)[0] + entry.name)).join(' '), esc(uricom_dec(entry.purl)[0] + entry.name)).join(' '),
'📐 hash', '📐 hash',
'' ''
]); ], fobj.size);
st.files.push(entry); st.files.push(entry);
st.todo.hash.push(entry); st.todo.hash.push(entry);
} }
@@ -699,7 +699,7 @@ function up2k_init(have_crypto) {
function more_one_file() { function more_one_file() {
fdom_ctr++; fdom_ctr++;
var elm = document.createElement('div') var elm = mknod('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);
ebi('u2form').appendChild(elm); ebi('u2form').appendChild(elm);
ebi('file' + fdom_ctr).addEventListener('change', gotfile, false); ebi('file' + fdom_ctr).addEventListener('change', gotfile, false);
@@ -737,7 +737,7 @@ function up2k_init(have_crypto) {
function hashing_permitted() { function hashing_permitted() {
if (multitask) { if (multitask) {
var ahead = st.bytes.hashed - st.bytes.uploaded; var ahead = st.bytes.hashed - st.bytes.uploaded;
return ahead < 1024 * 1024 * 128 && return ahead < 1024 * 1024 * 1024 * 4 &&
st.todo.handshake.length + st.busy.handshake.length < 16; st.todo.handshake.length + st.busy.handshake.length < 16;
} }
return handshakes_permitted() && 0 == return handshakes_permitted() && 0 ==
@@ -746,26 +746,23 @@ function up2k_init(have_crypto) {
} }
var tasker = (function () { var tasker = (function () {
var mutex = false; var tto = null,
var was_busy = false; running = false,
was_busy = false;
function defer() {
running = false;
clearTimeout(tto);
tto = setTimeout(taskerd, 100);
}
function taskerd() { function taskerd() {
if (mutex) if (running)
return; return;
mutex = true; clearTimeout(tto);
running = true;
while (true) { while (true) {
if (false) {
ebi('srv_info').innerHTML =
new Date().getTime() + ", " +
st.todo.hash.length + ", " +
st.todo.handshake.length + ", " +
st.todo.upload.length + ", " +
st.busy.hash.length + ", " +
st.busy.handshake.length + ", " +
st.busy.upload.length;
}
var is_busy = 0 != var is_busy = 0 !=
st.todo.hash.length + st.todo.hash.length +
st.todo.handshake.length + st.todo.handshake.length +
@@ -777,21 +774,16 @@ function up2k_init(have_crypto) {
if (was_busy != is_busy) { if (was_busy != is_busy) {
was_busy = is_busy; was_busy = is_busy;
if (is_busy) window[(is_busy ? "add" : "remove") +
window.addEventListener("beforeunload", warn_uploader_busy); "EventListener"]("beforeunload", warn_uploader_busy);
else
window.removeEventListener("beforeunload", warn_uploader_busy);
} }
if (flag) { if (flag) {
if (is_busy) { if (is_busy) {
var now = new Date().getTime(); var now = Date.now();
flag.take(now); flag.take(now);
if (!flag.ours) { if (!flag.ours)
setTimeout(taskerd, 100); return defer();
mutex = false;
return;
}
} }
else if (flag.ours) { else if (flag.ours) {
flag.give(); flag.give();
@@ -833,11 +825,8 @@ function up2k_init(have_crypto) {
mou_ikkai = true; mou_ikkai = true;
} }
if (!mou_ikkai) { if (!mou_ikkai)
setTimeout(taskerd, 100); return defer();
mutex = false;
return;
}
} }
} }
taskerd(); taskerd();
@@ -851,47 +840,47 @@ function up2k_init(have_crypto) {
// https://gist.github.com/jonleighton/958841 // https://gist.github.com/jonleighton/958841
function buf2b64(arrayBuffer) { function buf2b64(arrayBuffer) {
var base64 = ''; var base64 = '',
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; cset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
var bytes = new Uint8Array(arrayBuffer); src = new Uint8Array(arrayBuffer),
var byteLength = bytes.byteLength; nbytes = src.byteLength,
var byteRemainder = byteLength % 3; byteRem = nbytes % 3,
var mainLength = byteLength - byteRemainder; mainLen = nbytes - byteRem,
var a, b, c, d; a, b, c, d, chunk;
var chunk;
for (var i = 0; i < mainLength; i = i + 3) { for (var i = 0; i < mainLen; i = i + 3) {
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; chunk = (src[i] << 16) | (src[i + 1] << 8) | src[i + 2];
// create 8*3=24bit segment then split into 6bit segments // create 8*3=24bit segment then split into 6bit segments
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 a = (chunk & 16515072) >> 18; // (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 b = (chunk & 258048) >> 12; // (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 c = (chunk & 4032) >> 6; // (2^6 - 1) << 6
d = chunk & 63; // 63 = 2^6 - 1 d = chunk & 63; // 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding // Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; base64 += cset[a] + cset[b] + cset[c] + cset[d];
} }
if (byteRemainder == 1) { if (byteRem == 1) {
chunk = bytes[mainLength]; chunk = src[mainLen];
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 a = (chunk & 252) >> 2; // (2^6 - 1) << 2
b = (chunk & 3) << 4; // 3 = 2^2 - 1 (zero 4 LSB) b = (chunk & 3) << 4; // 2^2 - 1 (zero 4 LSB)
base64 += encodings[a] + encodings[b];//+ '=='; base64 += cset[a] + cset[b];//+ '==';
} }
else if (byteRemainder == 2) { else if (byteRem == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]; chunk = (src[mainLen] << 8) | src[mainLen + 1];
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 a = (chunk & 64512) >> 10; // (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 b = (chunk & 1008) >> 4; // (2^6 - 1) << 4
c = (chunk & 15) << 2; // 15 = 2^4 - 1 (zero 2 LSB) c = (chunk & 15) << 2; // 2^4 - 1 (zero 2 LSB)
base64 += encodings[a] + encodings[b] + encodings[c];//+ '='; base64 += cset[a] + cset[b] + cset[c];//+ '=';
} }
return base64; return base64;
} }
function get_chunksize(filesize) { function get_chunksize(filesize) {
var chunksize = 1024 * 1024; var chunksize = 1024 * 1024,
var stepsize = 512 * 1024; stepsize = 512 * 1024;
while (true) { while (true) {
for (var mul = 1; mul <= 2; mul++) { for (var mul = 1; mul <= 2; mul++) {
var nchunks = Math.ceil(filesize / chunksize); var nchunks = Math.ceil(filesize / chunksize);
@@ -904,156 +893,99 @@ function up2k_init(have_crypto) {
} }
} }
function test_filereader_speed(segm_err) {
var f = st.todo.hash[0].fobj,
sz = Math.min(2, f.size),
reader = new FileReader(),
t0, ctr = 0;
var segm_next = function () {
var t = new Date().getTime(),
td = t - t0;
if (++ctr > 2) {
need_filereader_cache = td > 50;
st.busy.hash.pop();
return;
}
t0 = t;
reader.onload = segm_next;
reader.onerror = segm_err;
reader.readAsArrayBuffer(
bobslice.call(f, 0, sz));
};
segm_next();
}
function ensure_rendered(func) {
var hidden = false;
var keys = ['hidden', 'msHidden', 'webkitHidden'];
for (var a = 0; a < keys.length; a++)
if (typeof document[keys[a]] !== "undefined")
hidden = document[keys[a]];
if (hidden)
return func();
window.requestAnimationFrame(func);
}
function exec_hash() { function exec_hash() {
if (need_filereader_cache === undefined) {
st.busy.hash.push(1);
return test_filereader_speed(segm_err);
}
var t = st.todo.hash.shift(); var t = st.todo.hash.shift();
st.busy.hash.push(t); st.busy.hash.push(t);
st.bytes.hashed += t.size; st.bytes.hashed += t.size;
t.bytes_uploaded = 0; t.bytes_uploaded = 0;
t.t1 = new Date().getTime();
var nchunk = 0; var bpend = 0,
var chunksize = get_chunksize(t.size); nchunk = 0,
var nchunks = Math.ceil(t.size / chunksize); chunksize = get_chunksize(t.size),
nchunks = Math.ceil(t.size / chunksize),
// android-chrome has 180ms latency on FileReader calls, hashtab = {};
// detect this and do 32MB at a time
var cache_buf = undefined,
cache_ofs = 0,
subchunks = 2;
while (subchunks * chunksize <= 32 * 1024 * 1024)
subchunks++;
subchunks--;
if (!need_filereader_cache)
subchunks = 1;
pvis.setab(t.n, nchunks); pvis.setab(t.n, nchunks);
pvis.move(t.n, 'bz'); pvis.move(t.n, 'bz');
var reader = new FileReader();
var segm_next = function () { var segm_next = function () {
if (cache_buf) { if (nchunk >= nchunks || (bpend > chunksize && bpend >= min_filebuf))
return hash_calc(); return false;
}
reader.onload = segm_load; var reader = new FileReader(),
reader.onerror = segm_err; nch = nchunk++,
car = nch * chunksize,
cdr = car + chunksize,
t0 = Date.now();
var car = nchunk * chunksize;
var cdr = car + chunksize * subchunks;
if (cdr >= t.size) if (cdr >= t.size)
cdr = t.size; cdr = t.size;
bpend += cdr - car;
reader.onload = function (e) {
if (!min_filebuf && nch == 1) {
min_filebuf = 1;
var td = Date.now() - t0;
if (td > 50) {
ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
min_filebuf = 32 * 1024 * 1024;
}
}
hash_calc(nch, e.target.result);
};
reader.onerror = function () {
alert('y o u b r o k e i t\nerror: ' + reader.error);
};
reader.readAsArrayBuffer( reader.readAsArrayBuffer(
bobslice.call(t.fobj, car, cdr)); bobslice.call(t.fobj, car, cdr));
return true;
}; };
var segm_load = function (e) { var hash_calc = function (nch, buf) {
cache_buf = e.target.result; while (segm_next());
cache_ofs = 0;
hash_calc();
};
var hash_calc = function () { var hash_done = function (hashbuf) {
var buf = cache_buf; var hslice = new Uint8Array(hashbuf).subarray(0, 32),
if (chunksize >= buf.byteLength) b64str = buf2b64(hslice).replace(/=$/, '');
cache_buf = undefined;
else {
var ofs = cache_ofs;
var ofs2 = ofs + Math.min(chunksize, cache_buf.byteLength - cache_ofs);
cache_ofs = ofs2;
buf = new Uint8Array(cache_buf).subarray(ofs, ofs2);
if (ofs2 >= cache_buf.byteLength)
cache_buf = undefined;
}
var func = function () { hashtab[nch] = b64str;
if (have_crypto) t.hash.push(nch);
crypto.subtle.digest('SHA-512', buf).then(hash_done); pvis.hashed(t);
else {
var hasher = new asmCrypto.Sha512(); bpend -= buf.byteLength;
hasher.process(new Uint8Array(buf)); if (t.hash.length < nchunks) {
hasher.finish(); return segm_next();
hash_done(hasher.result);
} }
t.hash = [];
for (var a = 0; a < nchunks; a++) {
t.hash.push(hashtab[a]);
}
t.t2 = Date.now();
if (t.n == 0 && window.location.hash == '#dbg') {
var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
}
pvis.seth(t.n, 2, 'hashing done');
pvis.seth(t.n, 1, '📦 wait');
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
st.todo.handshake.push(t);
}; };
if (cache_buf) if (have_crypto)
ensure_rendered(func); crypto.subtle.digest('SHA-512', buf).then(hash_done);
else else {
func(); var hasher = new asmCrypto.Sha512();
}; hasher.process(new Uint8Array(buf));
hasher.finish();
var hash_done = function (hashbuf) { hash_done(hasher.result);
var hslice = new Uint8Array(hashbuf).subarray(0, 32);
var b64str = buf2b64(hslice).replace(/=$/, '');
t.hash.push(b64str);
pvis.hashed(t);
if (++nchunk < nchunks) {
return segm_next();
} }
t.t2 = new Date().getTime();
if (t.n == 0 && window.location.hash == '#dbg') {
var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
}
pvis.seth(t.n, 2, 'hashing done');
pvis.seth(t.n, 1, '📦 wait');
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
st.todo.handshake.push(t);
};
var segm_err = function () {
alert('y o u b r o k e i t\n\n(was that a folder? just files please)');
}; };
t.t1 = Date.now();
segm_next(); segm_next();
} }
@@ -1072,8 +1004,9 @@ function up2k_init(have_crypto) {
var response = JSON.parse(xhr.responseText); var response = JSON.parse(xhr.responseText);
if (!response.name) { if (!response.name) {
var msg = ''; var msg = '',
var smsg = ''; smsg = '';
if (!response || !response.hits || !response.hits.length) { if (!response || !response.hits || !response.hits.length) {
msg = 'not found on server'; msg = 'not found on server';
smsg = '404'; smsg = '404';
@@ -1106,6 +1039,15 @@ function up2k_init(have_crypto) {
pvis.seth(t.n, 0, linksplit(esc(t.purl + t.name)).join(' ')); pvis.seth(t.n, 0, linksplit(esc(t.purl + t.name)).join(' '));
} }
var chunksize = get_chunksize(t.size),
cdr_idx = Math.ceil(t.size / chunksize) - 1,
cdr_sz = (t.size % chunksize) || chunksize,
cbd = [];
for (var a = 0; a <= cdr_idx; a++) {
cbd.push(a == cdr_idx ? cdr_sz : chunksize);
}
t.postlist = []; t.postlist = [];
t.wark = response.wark; t.wark = response.wark;
var missing = response.hash; var missing = response.hash;
@@ -1116,10 +1058,15 @@ function up2k_init(have_crypto) {
missing[a], JSON.stringify(t))); missing[a], JSON.stringify(t)));
t.postlist.push(idx); t.postlist.push(idx);
cbd[idx] = 0;
} }
var done = true; pvis.setat(t.n, cbd);
var msg = '&#x1f3b7;&#x1f41b;'; pvis.prog(t, 0, cbd[0]);
var done = true,
msg = '&#x1f3b7;&#x1f41b;';
if (t.postlist.length > 0) { if (t.postlist.length > 0) {
for (var a = 0; a < t.postlist.length; a++) for (var a = 0; a < t.postlist.length; a++)
st.todo.upload.push({ st.todo.upload.push({
@@ -1136,10 +1083,12 @@ function up2k_init(have_crypto) {
if (done) { if (done) {
t.done = true; t.done = true;
st.bytes.uploaded += t.size - t.bytes_uploaded; st.bytes.uploaded += t.size - t.bytes_uploaded;
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.t4 - t.t3) / 1000.)) / (1024 * 1024.); spd2 = (t.size / ((t.t4 - t.t3) / 1000.)) / (1024 * 1024.);
pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format( pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format(
spd1.toFixed(2), spd2.toFixed(2))); spd1.toFixed(2), spd2.toFixed(2)));
pvis.move(t.n, 'ok'); pvis.move(t.n, 'ok');
} }
else t.t4 = undefined; else t.t4 = undefined;
@@ -1206,66 +1155,55 @@ function up2k_init(have_crypto) {
var upt = st.todo.upload.shift(); var upt = st.todo.upload.shift();
st.busy.upload.push(upt); st.busy.upload.push(upt);
var npart = upt.npart; var npart = upt.npart,
var t = st.files[upt.nfile]; t = st.files[upt.nfile];
if (!t.t3)
t.t3 = Date.now();
pvis.seth(t.n, 1, "🚀 send"); pvis.seth(t.n, 1, "🚀 send");
var chunksize = get_chunksize(t.size); var chunksize = get_chunksize(t.size),
var car = npart * chunksize; car = npart * chunksize,
var cdr = car + chunksize; cdr = car + chunksize;
if (cdr >= t.size) if (cdr >= t.size)
cdr = t.size; cdr = t.size;
var reader = new FileReader(); var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (xev) {
reader.onerror = function () { pvis.prog(t, npart, xev.loaded);
alert('y o u b r o k e i t\n\n(was that a folder? just files please)');
}; };
xhr.onload = function (xev) {
reader.onload = function (e) { if (xhr.status == 200) {
var xhr = new XMLHttpRequest(); pvis.prog(t, npart, cdr - car);
xhr.upload.onprogress = function (xev) { st.bytes.uploaded += cdr - car;
var perc = xev.loaded / (cdr - car) * 100; t.bytes_uploaded += cdr - car;
pvis.prog(t, npart, perc, t); st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
}; t.postlist.splice(t.postlist.indexOf(npart), 1);
xhr.onload = function (xev) { if (t.postlist.length == 0) {
if (xhr.status == 200) { t.t4 = Date.now();
pvis.prog(t, npart, 101, t); pvis.seth(t.n, 1, 'verifying');
st.bytes.uploaded += cdr - car; st.todo.handshake.unshift(t);
t.bytes_uploaded += cdr - car;
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
t.postlist.splice(t.postlist.indexOf(npart), 1);
if (t.postlist.length == 0) {
t.t4 = new Date().getTime();
pvis.seth(t.n, 1, 'verifying');
st.todo.handshake.unshift(t);
}
tasker();
} }
else tasker();
alert("server broke; cu-err {0} on file [{1}]:\n".format( }
xhr.status, t.name) + ( else
(xhr.response && xhr.response.err) || alert("server broke; cu-err {0} on file [{1}]:\n".format(
(xhr.responseText && xhr.responseText) || xhr.status, t.name) + (
"no further information")); (xhr.response && xhr.response.err) ||
}; (xhr.responseText && xhr.responseText) ||
xhr.open('POST', t.purl + 'chunkpit.php', true); "no further information"));
//xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart].substr(1) + "x");
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
if (xhr.overrideMimeType)
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
xhr.responseType = 'text';
xhr.send(e.target.result);
if (!t.t3)
t.t3 = new Date().getTime();
}; };
xhr.open('POST', t.purl + 'chunkpit.php', true);
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
if (xhr.overrideMimeType)
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
reader.readAsArrayBuffer(bobslice.call(t.fobj, car, cdr)); xhr.responseType = 'text';
xhr.send(bobslice.call(t.fobj, car, cdr));
} }
///// /////
@@ -1293,20 +1231,20 @@ function up2k_init(have_crypto) {
onresize(); onresize();
function desc_show(e) { function desc_show(e) {
var msg = this.getAttribute('alt'); var msg = this.getAttribute('alt'),
msg = msg.replace(/\$N/g, "<br />"); cdesc = ebi('u2cdesc');
var cdesc = ebi('u2cdesc');
cdesc.innerHTML = msg; cdesc.innerHTML = msg.replace(/\$N/g, "<br />");
cdesc.setAttribute('class', 'show'); cdesc.setAttribute('class', 'show');
} }
function desc_hide(e) { function desc_hide(e) {
ebi('u2cdesc').setAttribute('class', ''); ebi('u2cdesc').setAttribute('class', '');
} }
var o = document.querySelectorAll('#u2conf *[alt]'); var o = QSA('#u2conf *[alt]');
for (var a = o.length - 1; a >= 0; a--) { for (var a = o.length - 1; a >= 0; a--) {
o[a].parentNode.getElementsByTagName('input')[0].setAttribute('alt', o[a].getAttribute('alt')); o[a].parentNode.getElementsByTagName('input')[0].setAttribute('alt', o[a].getAttribute('alt'));
} }
var o = document.querySelectorAll('#u2conf *[alt]'); var o = QSA('#u2conf *[alt]');
for (var a = 0; a < o.length; a++) { for (var a = 0; a < o.length; a++) {
o[a].onfocus = desc_show; o[a].onfocus = desc_show;
o[a].onblur = desc_hide; o[a].onblur = desc_hide;
@@ -1322,14 +1260,14 @@ function up2k_init(have_crypto) {
var obj = ebi('nthread'); var obj = ebi('nthread');
if (dir.target) { if (dir.target) {
obj.style.background = '#922'; clmod(obj, 'err', 1);
var v = Math.floor(parseInt(obj.value)); var v = Math.floor(parseInt(obj.value));
if (v < 1 || v > 8 || v !== v) if (v < 1 || v > 8 || v !== v)
return; return;
parallel_uploads = v; parallel_uploads = v;
swrite('nthread', v); swrite('nthread', v);
obj.style.background = '#444'; clmod(obj, 'err');
return; return;
} }
@@ -1360,8 +1298,8 @@ function up2k_init(have_crypto) {
} }
function set_fsearch(new_state) { function set_fsearch(new_state) {
var perms = document.body.getAttribute('perms'); var perms = document.body.getAttribute('perms'),
var read_only = false; read_only = false;
if (!ebi('fsearch')) { if (!ebi('fsearch')) {
new_state = false; new_state = false;
@@ -1377,16 +1315,16 @@ function up2k_init(have_crypto) {
} }
try { try {
document.querySelector('label[for="fsearch"]').style.opacity = read_only ? '0' : '1'; QS('label[for="fsearch"]').style.opacity = read_only ? '0' : '1';
} }
catch (ex) { } catch (ex) { }
try { try {
var fun = fsearch ? 'add' : 'remove'; var fun = fsearch ? 'add' : 'remove',
ebi('op_up2k').classList[fun]('srch'); ico = fsearch ? '🔎' : '🚀',
desc = fsearch ? 'Search' : 'Upload';
var ico = fsearch ? '🔎' : '🚀'; ebi('op_up2k').classList[fun]('srch');
var desc = fsearch ? 'Search' : 'Upload';
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>'; ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
} }
catch (ex) { } catch (ex) { }
@@ -1453,5 +1391,5 @@ function warn_uploader_busy(e) {
} }
if (document.querySelector('#op_up2k.act')) if (QS('#op_up2k.act'))
goto_up2k(); goto_up2k();

View File

@@ -90,8 +90,10 @@
background: #222; background: #222;
} }
#u2cards { #u2cards {
margin: 2.5em auto -2.5em auto; padding: 1em 0 .3em 1em;
margin: 1.5em auto -2.5em auto;
text-align: center; text-align: center;
overflow: hidden;
} }
#u2cards.w { #u2cards.w {
width: 45em; width: 45em;
@@ -110,10 +112,15 @@
border-radius: 0 .4em 0 0; border-radius: 0 .4em 0 0;
} }
#u2cards a.act { #u2cards a.act {
border-width: 1px 1px 0 1px; padding-bottom: .5em;
border-width: 1px 1px .1em 1px;
border-radius: .3em .3em 0 0; border-radius: .3em .3em 0 0;
margin-left: -1px; margin-left: -1px;
background: transparent; background: linear-gradient(to bottom, #464, #333 80%);
box-shadow: 0 -.17em .67em #280;
border-color: #7c5 #583 #333 #583;
position: relative;
color: #fd7;
} }
#u2cards span { #u2cards span {
color: #fff; color: #fff;
@@ -134,12 +141,16 @@
outline: none; outline: none;
} }
#u2conf .txtbox { #u2conf .txtbox {
width: 4em; width: 3em;
color: #fff; color: #fff;
background: #444; background: #444;
border: 1px solid #777; border: 1px solid #777;
font-size: 1.2em; font-size: 1.2em;
padding: .15em 0; padding: .15em 0;
height: 1.05em;
}
#u2conf .txtbox.err {
background: #922;
} }
#u2conf a { #u2conf a {
color: #fff; color: #fff;
@@ -148,13 +159,12 @@
border-radius: .1em; border-radius: .1em;
font-size: 1.5em; font-size: 1.5em;
padding: .1em 0; padding: .1em 0;
margin: 0 -.25em; margin: 0 -1px;
width: 1.5em; width: 1.5em;
height: 1em; height: 1em;
display: inline-block; display: inline-block;
position: relative; position: relative;
line-height: 1em; bottom: -0.08em;
bottom: -.08em;
} }
#u2conf input+a { #u2conf input+a {
background: #d80; background: #d80;
@@ -165,7 +175,6 @@
height: 1em; height: 1em;
padding: .4em 0; padding: .4em 0;
display: block; display: block;
user-select: none;
border-radius: .25em; border-radius: .25em;
} }
#u2conf input[type="checkbox"] { #u2conf input[type="checkbox"] {
@@ -205,12 +214,13 @@
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
margin: 0 -2em; margin: 0 -2em;
height: 0;
padding: 0 1em; padding: 0 1em;
height: 0;
opacity: .1; opacity: .1;
transition: all 0.14s ease-in-out; transition: all 0.14s ease-in-out;
border-radius: .4em;
box-shadow: 0 .2em .5em #222; box-shadow: 0 .2em .5em #222;
border-radius: .4em;
z-index: 1;
} }
#u2cdesc.show { #u2cdesc.show {
padding: 1em; padding: 1em;
@@ -238,3 +248,41 @@
float: right; float: right;
margin-bottom: -.3em; margin-bottom: -.3em;
} }
html.light #u2btn {
box-shadow: .4em .4em 0 #ccc;
}
html.light #u2cards span {
color: #000;
}
html.light #u2cards a {
background: linear-gradient(to bottom, #eee, #fff);
}
html.light #u2cards a.act {
color: #037;
background: inherit;
box-shadow: 0 -.17em .67em #0ad;
border-color: #09c #05a #eee #05a;
}
html.light #u2conf .txtbox {
background: #fff;
color: #444;
}
html.light #u2conf .txtbox.err {
background: #f96;
color: #300;
}
html.light #u2cdesc {
background: #fff;
border: none;
}
html.light #op_up2k.srch #u2btn {
border-color: #a80;
}
html.light #u2foot {
color: #000;
}

View File

@@ -59,9 +59,9 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#" id="nthread_sub">&ndash;</a> <a href="#" id="nthread_sub">&ndash;</a><input
<input class="txtbox" id="nthread" value="2" /> class="txtbox" id="nthread" value="2"/><a
<a href="#" id="nthread_add">+</a> href="#" id="nthread_add">+</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -6,7 +6,8 @@ if (!window['console'])
}; };
var clickev = window.Touch ? 'touchstart' : 'click'; var clickev = window.Touch ? 'touchstart' : 'click',
ANDROID = /(android)/i.test(navigator.userAgent);
// error handler for mobile devices // error handler for mobile devices
@@ -49,9 +50,11 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
} }
function ebi(id) { var ebi = document.getElementById.bind(document),
return document.getElementById(id); QS = document.querySelector.bind(document),
} QSA = document.querySelectorAll.bind(document),
mknod = document.createElement.bind(document);
function ev(e) { function ev(e) {
e = e || window.event; e = e || window.event;
@@ -89,7 +92,7 @@ if (!String.startsWith) {
// https://stackoverflow.com/a/950146 // https://stackoverflow.com/a/950146
function import_js(url, cb) { function import_js(url, cb) {
var head = document.head || document.getElementsByTagName('head')[0]; var head = document.head || document.getElementsByTagName('head')[0];
var script = document.createElement('script'); var script = mknod('script');
script.type = 'text/javascript'; script.type = 'text/javascript';
script.src = url; script.src = url;
@@ -274,7 +277,7 @@ function makeSortable(table, cb) {
(function () { (function () {
var ops = document.querySelectorAll('#ops>a'); var ops = QSA('#ops>a');
for (var a = 0; a < ops.length; a++) { for (var a = 0; a < ops.length; a++) {
ops[a].onclick = opclick; ops[a].onclick = opclick;
} }
@@ -289,25 +292,25 @@ function opclick(e) {
swrite('opmode', dest || null); swrite('opmode', dest || null);
var input = document.querySelector('.opview.act input:not([type="hidden"])') var input = QS('.opview.act input:not([type="hidden"])')
if (input) if (input)
input.focus(); input.focus();
} }
function goto(dest) { function goto(dest) {
var obj = document.querySelectorAll('.opview.act'); var obj = QSA('.opview.act');
for (var a = obj.length - 1; a >= 0; a--) for (var a = obj.length - 1; a >= 0; a--)
clmod(obj[a], 'act'); clmod(obj[a], 'act');
obj = document.querySelectorAll('#ops>a'); obj = QSA('#ops>a');
for (var a = obj.length - 1; a >= 0; a--) for (var a = obj.length - 1; a >= 0; a--)
clmod(obj[a], 'act'); clmod(obj[a], 'act');
if (dest) { if (dest) {
var ui = ebi('op_' + dest); var ui = ebi('op_' + dest);
clmod(ui, 'act', true); clmod(ui, 'act', true);
document.querySelector('#ops>a[data-dest=' + dest + ']').className += " act"; QS('#ops>a[data-dest=' + dest + ']').className += " act";
var fn = window['goto_' + dest]; var fn = window['goto_' + dest];
if (fn) if (fn)

View File

@@ -171,7 +171,7 @@ Range: bytes=26- Content-Range: bytes */26
var tsh = []; var tsh = [];
function convert_markdown(md_text, dest_dom) { function convert_markdown(md_text, dest_dom) {
tsh.push(new Date().getTime()); tsh.push(Date.now());
while (tsh.length > 10) while (tsh.length > 10)
tsh.shift(); tsh.shift();
if (tsh.length > 1) { if (tsh.length > 1) {

View File

@@ -26,7 +26,7 @@ CKSUM = None
STAMP = None STAMP = None
PY2 = sys.version_info[0] == 2 PY2 = sys.version_info[0] == 2
WINDOWS = sys.platform == "win32" WINDOWS = sys.platform in ["win32", "msys"]
sys.dont_write_bytecode = True sys.dont_write_bytecode = True
me = os.path.abspath(os.path.realpath(__file__)) me = os.path.abspath(os.path.realpath(__file__))
cpp = None cpp = None

0
tests/__init__.py Normal file
View File

33
tests/run.py Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
import sys
import runpy
host = sys.argv[1]
sys.argv = sys.argv[:1] + sys.argv[2:]
sys.path.insert(0, ".")
def rp():
runpy.run_module("unittest", run_name="__main__")
if host == "vmprof":
rp()
elif host == "cprofile":
import cProfile
import pstats
log_fn = "cprofile.log"
cProfile.run("rp()", log_fn)
p = pstats.Stats(log_fn)
p.sort_stats(pstats.SortKey.CUMULATIVE).print_stats(64)
"""
python3.9 tests/run.py cprofile -v tests/test_httpcli.py
python3.9 -m pip install --user vmprof
python3.9 -m vmprof --lines -o vmprof.log tests/run.py vmprof -v tests/test_httpcli.py
"""

202
tests/test_httpcli.py Normal file
View File

@@ -0,0 +1,202 @@
#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function, unicode_literals
import io
import os
import time
import shutil
import pprint
import tarfile
import unittest
from argparse import Namespace
from copyparty.authsrv import AuthSrv
from copyparty.httpcli import HttpCli
from tests import util as tu
def hdr(query):
h = "GET /{} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\n\r\n"
return h.format(query).encode("utf-8")
class Cfg(Namespace):
def __init__(self, a=[], v=[], c=None):
super(Cfg, self).__init__(
a=a,
v=v,
c=c,
ed=False,
no_zip=False,
no_scandir=False,
no_sendfile=True,
nih=True,
mtp=[],
mte="a",
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
)
class TestHttpCli(unittest.TestCase):
def test(self):
td = os.path.join(tu.get_ramdisk(), "vfs")
try:
shutil.rmtree(td)
except OSError:
pass
os.mkdir(td)
os.chdir(td)
self.dtypes = ["ra", "ro", "rx", "wa", "wo", "wx", "aa", "ao", "ax"]
self.can_read = ["ra", "ro", "aa", "ao"]
self.can_write = ["wa", "wo", "aa", "ao"]
self.fn = "g{:x}g".format(int(time.time() * 3))
allfiles = []
allvols = []
for top in self.dtypes:
allvols.append(top)
allfiles.append("/".join([top, self.fn]))
for s1 in self.dtypes:
p = "/".join([top, s1])
allvols.append(p)
allfiles.append(p + "/" + self.fn)
allfiles.append(p + "/n/" + self.fn)
for s2 in self.dtypes:
p = "/".join([top, s1, "n", s2])
os.makedirs(p)
allvols.append(p)
allfiles.append(p + "/" + self.fn)
for fp in allfiles:
with open(fp, "w") as f:
f.write("ok {}\n".format(fp))
for top in self.dtypes:
vcfg = []
for vol in allvols:
if not vol.startswith(top):
continue
mode = vol[-2]
usr = vol[-1]
if usr == "a":
usr = ""
if "/" not in vol:
vol += "/"
top, sub = vol.split("/", 1)
vcfg.append("{0}/{1}:{1}:{2}{3}".format(top, sub, mode, usr))
pprint.pprint(vcfg)
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
self.auth = AuthSrv(self.args, self.log)
vfiles = [x for x in allfiles if x.startswith(top)]
for fp in vfiles:
rok, wok = self.can_rw(fp)
furl = fp.split("/", 1)[1]
durl = furl.rsplit("/", 1)[0] if "/" in furl else ""
# file download
h, ret = self.curl(furl)
res = "ok " + fp in ret
print("[{}] {} {} = {}".format(fp, rok, wok, res))
if rok != res:
print("\033[33m{}\n# {}\033[0m".format(ret, furl))
self.fail()
# file browser: html
h, ret = self.curl(durl)
res = "'{}'".format(self.fn) in ret
print(res)
if rok != res:
print("\033[33m{}\n# {}\033[0m".format(ret, durl))
self.fail()
# file browser: json
url = durl + "?ls"
h, ret = self.curl(url)
res = '"{}"'.format(self.fn) in ret
print(res)
if rok != res:
print("\033[33m{}\n# {}\033[0m".format(ret, url))
self.fail()
# tar
url = durl + "?tar"
h, b = self.curl(url, True)
# with open(os.path.join(td, "tar"), "wb") as f:
# f.write(b)
try:
tar = tarfile.open(fileobj=io.BytesIO(b)).getnames()
except:
tar = []
tar = ["/".join([y for y in [top, durl, x] if y]) for x in tar]
tar = [[x] + self.can_rw(x) for x in tar]
tar_ok = [x[0] for x in tar if x[1]]
tar_ng = [x[0] for x in tar if not x[1]]
self.assertEqual([], tar_ng)
if durl.split("/")[-1] in self.can_read:
ref = [x for x in vfiles if self.in_dive(top + "/" + durl, x)]
for f in ref:
print("{}: {}".format("ok" if f in tar_ok else "NG", f))
ref.sort()
tar_ok.sort()
self.assertEqual(ref, tar_ok)
# stash
h, ret = self.put(url)
res = h.startswith("HTTP/1.1 200 ")
self.assertEqual(res, wok)
def can_rw(self, fp):
# lowest non-neutral folder declares permissions
expect = fp.split("/")[:-1]
for x in reversed(expect):
if x != "n":
expect = x
break
return [expect in self.can_read, expect in self.can_write]
def in_dive(self, top, fp):
# archiver bails at first inaccessible subvolume
top = top.strip("/").split("/")
fp = fp.split("/")
for f1, f2 in zip(top, fp):
if f1 != f2:
return False
for f in fp[len(top) :]:
if f == self.fn:
return True
if f not in self.can_read and f != "n":
return False
return True
def put(self, url):
buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
buf = buf.format(url, len(url) + 4).encode("utf-8")
conn = tu.VHttpConn(self.args, self.auth, self.log, buf)
HttpCli(conn).run()
return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
def curl(self, url, binary=False):
conn = tu.VHttpConn(self.args, self.auth, self.log, hdr(url))
HttpCli(conn).run()
if binary:
h, b = conn.s._reply.split(b"\r\n\r\n", 1)
return [h.decode("utf-8"), b]
return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
def log(self, src, msg, c=0):
# print(repr(msg))
pass

View File

@@ -3,18 +3,18 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os import os
import time
import json import json
import shutil import shutil
import tempfile import tempfile
import unittest import unittest
import subprocess as sp # nosec
from textwrap import dedent from textwrap import dedent
from argparse import Namespace from argparse import Namespace
from copyparty.authsrv import AuthSrv from copyparty.authsrv import AuthSrv
from copyparty import util from copyparty import util
from tests import util as tu
class Cfg(Namespace): class Cfg(Namespace):
def __init__(self, a=[], v=[], c=None): def __init__(self, a=[], v=[], c=None):
@@ -51,52 +51,11 @@ class TestVFS(unittest.TestCase):
real = [x[0] for x in real] real = [x[0] for x in real]
return fsdir, real, virt return fsdir, real, virt
def runcmd(self, *argv):
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
return [p.returncode, stdout, stderr]
def chkcmd(self, *argv):
ok, sout, serr = self.runcmd(*argv)
if ok != 0:
raise Exception(serr)
return sout, serr
def get_ramdisk(self):
for vol in ["/dev/shm", "/Volumes/cptd"]: # nosec (singleton test)
if os.path.exists(vol):
return vol
if os.path.exists("/Volumes"):
devname, _ = self.chkcmd("hdiutil", "attach", "-nomount", "ram://8192")
devname = devname.strip()
print("devname: [{}]".format(devname))
for _ in range(10):
try:
_, _ = self.chkcmd(
"diskutil", "eraseVolume", "HFS+", "cptd", devname
)
return "/Volumes/cptd"
except Exception as ex:
print(repr(ex))
time.sleep(0.25)
raise Exception("ramdisk creation failed")
ret = os.path.join(tempfile.gettempdir(), "copyparty-test")
try:
os.mkdir(ret)
finally:
return ret
def log(self, src, msg, c=0): def log(self, src, msg, c=0):
pass pass
def test(self): def test(self):
td = os.path.join(self.get_ramdisk(), "vfs") td = os.path.join(tu.get_ramdisk(), "vfs")
try: try:
shutil.rmtree(td) shutil.rmtree(td)
except OSError: except OSError:
@@ -268,7 +227,7 @@ class TestVFS(unittest.TestCase):
self.assertEqual(list(v1), list(v2)) self.assertEqual(list(v1), list(v2))
# config file parser # config file parser
cfg_path = os.path.join(self.get_ramdisk(), "test.cfg") cfg_path = os.path.join(tu.get_ramdisk(), "test.cfg")
with open(cfg_path, "wb") as f: with open(cfg_path, "wb") as f:
f.write( f.write(
dedent( dedent(

97
tests/util.py Normal file
View File

@@ -0,0 +1,97 @@
import os
import time
import jinja2
import tempfile
import subprocess as sp
from copyparty.util import Unrecv
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader)
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}")
def runcmd(*argv):
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE)
stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
return [p.returncode, stdout, stderr]
def chkcmd(*argv):
ok, sout, serr = runcmd(*argv)
if ok != 0:
raise Exception(serr)
return sout, serr
def get_ramdisk():
for vol in ["/dev/shm", "/Volumes/cptd"]: # nosec (singleton test)
if os.path.exists(vol):
return vol
if os.path.exists("/Volumes"):
devname, _ = chkcmd("hdiutil", "attach", "-nomount", "ram://32768")
devname = devname.strip()
print("devname: [{}]".format(devname))
for _ in range(10):
try:
_, _ = chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
return "/Volumes/cptd"
except Exception as ex:
print(repr(ex))
time.sleep(0.25)
raise Exception("ramdisk creation failed")
ret = os.path.join(tempfile.gettempdir(), "copyparty-test")
try:
os.mkdir(ret)
finally:
return ret
class NullBroker(object):
def put(*args):
pass
class VSock(object):
def __init__(self, buf):
self._query = buf
self._reply = b""
self.sendall = self.send
def recv(self, sz):
ret = self._query[:sz]
self._query = self._query[sz:]
return ret
def send(self, buf):
self._reply += buf
return len(buf)
class VHttpSrv(object):
def __init__(self):
self.broker = NullBroker()
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
self.j2 = {x: J2_FILES for x in aliases}
class VHttpConn(object):
def __init__(self, args, auth, log, buf):
self.s = VSock(buf)
self.sr = Unrecv(self.s)
self.addr = ("127.0.0.1", "42069")
self.args = args
self.auth = auth
self.log_func = log
self.log_src = "a"
self.hsrv = VHttpSrv()
self.nbyte = 0
self.workload = 0
self.t0 = time.time()