mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
77.6KiB changeset nice
This commit is contained in:
parent
06c6ddffb6
commit
fda98730ac
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
@ -12,8 +12,7 @@
|
||||||
//"-nw",
|
//"-nw",
|
||||||
"-ed",
|
"-ed",
|
||||||
"-emp",
|
"-emp",
|
||||||
"-e2d",
|
"-e2dsa",
|
||||||
"-e2s",
|
|
||||||
"-a",
|
"-a",
|
||||||
"ed:wark",
|
"ed:wark",
|
||||||
"-v",
|
"-v",
|
||||||
|
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
@ -8,7 +8,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "no_dbg",
|
"label": "no_dbg",
|
||||||
"command": "${config:python.pythonPath} -m copyparty -ed -emp -e2d -e2s -a ed:wark -v srv::r:aed:cnodupe ;exit 1",
|
"command": "${config:python.pythonPath} -m copyparty -ed -emp -e2dsa -a ed:wark -v srv::r:aed:cnodupe ;exit 1",
|
||||||
"type": "shell"
|
"type": "shell"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -174,6 +174,18 @@ def main():
|
||||||
if HAVE_SSL:
|
if HAVE_SSL:
|
||||||
ensure_cert()
|
ensure_cert()
|
||||||
|
|
||||||
|
deprecated = [["-e2s", "-e2ds"]]
|
||||||
|
for dk, nk in deprecated:
|
||||||
|
try:
|
||||||
|
idx = sys.argv.index(dk)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
msg = "\033[1;31mWARNING:\033[0;1m\n {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
|
||||||
|
print(msg.format(dk, nk))
|
||||||
|
sys.argv[idx] = nk
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
ap = argparse.ArgumentParser(
|
ap = argparse.ArgumentParser(
|
||||||
formatter_class=RiceFormatter,
|
formatter_class=RiceFormatter,
|
||||||
prog="copyparty",
|
prog="copyparty",
|
||||||
|
@ -228,13 +240,15 @@ def main():
|
||||||
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("-e2d", action="store_true", help="enable up2k database")
|
ap.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||||
ap.add_argument("-e2s", action="store_true", help="enable up2k db-scanner")
|
ap.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
||||||
|
ap.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
||||||
ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
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")
|
||||||
ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
||||||
ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms")
|
ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms")
|
||||||
|
ap.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
||||||
|
@ -246,6 +260,12 @@ def main():
|
||||||
al = ap.parse_args()
|
al = ap.parse_args()
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
if al.e2dsa:
|
||||||
|
al.e2ds = True
|
||||||
|
|
||||||
|
if al.e2ds:
|
||||||
|
al.e2d = True
|
||||||
|
|
||||||
al.i = al.i.split(",")
|
al.i = al.i.split(",")
|
||||||
try:
|
try:
|
||||||
if "-" in al.p:
|
if "-" in al.p:
|
||||||
|
|
|
@ -19,6 +19,11 @@ class VFS(object):
|
||||||
self.uwrite = uwrite # users who can write this
|
self.uwrite = uwrite # users who can write this
|
||||||
self.flags = flags # config switches
|
self.flags = flags # config switches
|
||||||
self.nodes = {} # child nodes
|
self.nodes = {} # child nodes
|
||||||
|
self.all_vols = {vpath: self} # flattened recursive
|
||||||
|
|
||||||
|
def _trk(self, vol):
|
||||||
|
self.all_vols[vol.vpath] = vol
|
||||||
|
return vol
|
||||||
|
|
||||||
def add(self, src, dst):
|
def add(self, src, dst):
|
||||||
"""get existing, or add new path to the vfs"""
|
"""get existing, or add new path to the vfs"""
|
||||||
|
@ -30,7 +35,7 @@ class VFS(object):
|
||||||
name, dst = dst.split("/", 1)
|
name, dst = dst.split("/", 1)
|
||||||
if name in self.nodes:
|
if name in self.nodes:
|
||||||
# exists; do not manipulate permissions
|
# exists; do not manipulate permissions
|
||||||
return self.nodes[name].add(src, dst)
|
return self._trk(self.nodes[name].add(src, dst))
|
||||||
|
|
||||||
vn = VFS(
|
vn = VFS(
|
||||||
"{}/{}".format(self.realpath, name),
|
"{}/{}".format(self.realpath, name),
|
||||||
|
@ -40,7 +45,7 @@ class VFS(object):
|
||||||
self.flags,
|
self.flags,
|
||||||
)
|
)
|
||||||
self.nodes[name] = vn
|
self.nodes[name] = vn
|
||||||
return vn.add(src, dst)
|
return self._trk(vn.add(src, dst))
|
||||||
|
|
||||||
if dst in self.nodes:
|
if dst in self.nodes:
|
||||||
# leaf exists; return as-is
|
# leaf exists; return as-is
|
||||||
|
@ -50,7 +55,7 @@ class VFS(object):
|
||||||
vp = "{}/{}".format(self.vpath, dst).lstrip("/")
|
vp = "{}/{}".format(self.vpath, dst).lstrip("/")
|
||||||
vn = VFS(src, vp)
|
vn = VFS(src, vp)
|
||||||
self.nodes[dst] = vn
|
self.nodes[dst] = vn
|
||||||
return vn
|
return self._trk(vn)
|
||||||
|
|
||||||
def _find(self, vpath):
|
def _find(self, vpath):
|
||||||
"""return [vfs,remainder]"""
|
"""return [vfs,remainder]"""
|
||||||
|
@ -257,7 +262,6 @@ class AuthSrv(object):
|
||||||
with open(cfg_fn, "rb") as f:
|
with open(cfg_fn, "rb") as f:
|
||||||
self._parse_config_file(f, user, mread, mwrite, mflags, mount)
|
self._parse_config_file(f, user, mread, mwrite, mflags, mount)
|
||||||
|
|
||||||
self.all_writable = []
|
|
||||||
if not mount:
|
if not mount:
|
||||||
# -h says our defaults are CWD at root and read/write for everyone
|
# -h says our defaults are CWD at root and read/write for everyone
|
||||||
vfs = VFS(os.path.abspath("."), "", ["*"], ["*"])
|
vfs = VFS(os.path.abspath("."), "", ["*"], ["*"])
|
||||||
|
@ -280,11 +284,6 @@ class AuthSrv(object):
|
||||||
v.uread = mread[dst]
|
v.uread = mread[dst]
|
||||||
v.uwrite = mwrite[dst]
|
v.uwrite = mwrite[dst]
|
||||||
v.flags = mflags[dst]
|
v.flags = mflags[dst]
|
||||||
if v.uwrite:
|
|
||||||
self.all_writable.append(v)
|
|
||||||
|
|
||||||
if vfs.uwrite and vfs not in self.all_writable:
|
|
||||||
self.all_writable.append(vfs)
|
|
||||||
|
|
||||||
missing_users = {}
|
missing_users = {}
|
||||||
for d in [mread, mwrite]:
|
for d in [mread, mwrite]:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import os
|
||||||
import stat
|
import stat
|
||||||
import gzip
|
import gzip
|
||||||
import time
|
import time
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
import ctypes
|
import ctypes
|
||||||
|
@ -125,15 +126,15 @@ class HttpCli(object):
|
||||||
k, v = k.split("=", 1)
|
k, v = k.split("=", 1)
|
||||||
uparam[k.lower()] = v.strip()
|
uparam[k.lower()] = v.strip()
|
||||||
else:
|
else:
|
||||||
uparam[k.lower()] = True
|
uparam[k.lower()] = False
|
||||||
|
|
||||||
self.uparam = uparam
|
self.uparam = uparam
|
||||||
self.vpath = unquotep(vpath)
|
self.vpath = unquotep(vpath)
|
||||||
|
|
||||||
ua = self.headers.get("user-agent", "")
|
ua = self.headers.get("user-agent", "")
|
||||||
if ua.startswith("rclone/"):
|
if ua.startswith("rclone/"):
|
||||||
uparam["raw"] = True
|
uparam["raw"] = False
|
||||||
uparam["dots"] = True
|
uparam["dots"] = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.mode in ["GET", "HEAD"]:
|
if self.mode in ["GET", "HEAD"]:
|
||||||
|
@ -237,12 +238,15 @@ class HttpCli(object):
|
||||||
)
|
)
|
||||||
if not self.readable and not self.writable:
|
if not self.readable and not self.writable:
|
||||||
self.log("inaccessible: [{}]".format(self.vpath))
|
self.log("inaccessible: [{}]".format(self.vpath))
|
||||||
self.uparam = {"h": True}
|
self.uparam = {"h": False}
|
||||||
|
|
||||||
if "h" in self.uparam:
|
if "h" in self.uparam:
|
||||||
self.vpath = None
|
self.vpath = None
|
||||||
return self.tx_mounts()
|
return self.tx_mounts()
|
||||||
|
|
||||||
|
if "tree" in self.uparam:
|
||||||
|
return self.tx_tree()
|
||||||
|
|
||||||
return self.tx_browser()
|
return self.tx_browser()
|
||||||
|
|
||||||
def handle_options(self):
|
def handle_options(self):
|
||||||
|
@ -401,6 +405,9 @@ class HttpCli(object):
|
||||||
except:
|
except:
|
||||||
raise Pebkac(422, "you POSTed invalid json")
|
raise Pebkac(422, "you POSTed invalid json")
|
||||||
|
|
||||||
|
if "srch" in self.uparam or "srch" in body:
|
||||||
|
return self.handle_search(body)
|
||||||
|
|
||||||
# prefer this over undot; no reason to allow traversion
|
# prefer this over undot; no reason to allow traversion
|
||||||
if "/" in body["name"]:
|
if "/" in body["name"]:
|
||||||
raise Pebkac(400, "folders verboten")
|
raise Pebkac(400, "folders verboten")
|
||||||
|
@ -426,6 +433,30 @@ class HttpCli(object):
|
||||||
self.reply(response.encode("utf-8"), mime="application/json")
|
self.reply(response.encode("utf-8"), mime="application/json")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def handle_search(self, body):
|
||||||
|
vols = []
|
||||||
|
for vtop in self.rvol:
|
||||||
|
vfs, _ = self.conn.auth.vfs.get(vtop, self.uname, True, False)
|
||||||
|
vols.append([vfs.vpath, vfs.realpath, vfs.flags])
|
||||||
|
|
||||||
|
idx = self.conn.get_u2idx()
|
||||||
|
if "srch" in body:
|
||||||
|
# search by up2k hashlist
|
||||||
|
vbody = copy.deepcopy(body)
|
||||||
|
vbody["hash"] = len(vbody["hash"])
|
||||||
|
self.log("qj: " + repr(vbody))
|
||||||
|
hits = idx.fsearch(vols, body)
|
||||||
|
self.log("qh: " + repr(hits))
|
||||||
|
else:
|
||||||
|
# search by query params
|
||||||
|
self.log("qj: " + repr(body))
|
||||||
|
hits = idx.search(vols, body)
|
||||||
|
self.log("qh: " + str(len(hits)))
|
||||||
|
|
||||||
|
r = json.dumps(hits).encode("utf-8")
|
||||||
|
self.reply(r, mime="application/json")
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_post_binary(self):
|
def handle_post_binary(self):
|
||||||
try:
|
try:
|
||||||
remains = int(self.headers["content-length"])
|
remains = int(self.headers["content-length"])
|
||||||
|
@ -1037,6 +1068,60 @@ class HttpCli(object):
|
||||||
self.reply(html.encode("utf-8"))
|
self.reply(html.encode("utf-8"))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def tx_tree(self):
|
||||||
|
top = self.uparam["tree"] or ""
|
||||||
|
dst = self.vpath
|
||||||
|
if top in [".", ".."]:
|
||||||
|
top = undot(self.vpath + "/" + top)
|
||||||
|
|
||||||
|
if top == dst:
|
||||||
|
dst = ""
|
||||||
|
elif top:
|
||||||
|
if not dst.startswith(top + "/"):
|
||||||
|
raise Pebkac(400, "arg funk")
|
||||||
|
|
||||||
|
dst = dst[len(top) + 1 :]
|
||||||
|
|
||||||
|
ret = self.gen_tree(top, dst)
|
||||||
|
ret = json.dumps(ret)
|
||||||
|
self.reply(ret.encode("utf-8"))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def gen_tree(self, top, target):
|
||||||
|
ret = {}
|
||||||
|
excl = None
|
||||||
|
if target:
|
||||||
|
excl, target = (target.split("/", 1) + [""])[:2]
|
||||||
|
ret["k" + excl] = self.gen_tree("/".join([top, excl]).strip("/"), target)
|
||||||
|
|
||||||
|
try:
|
||||||
|
vn, rem = self.auth.vfs.get(top, self.uname, self.readable, self.writable)
|
||||||
|
fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname)
|
||||||
|
except:
|
||||||
|
vfs_ls = []
|
||||||
|
vfs_virt = {}
|
||||||
|
for v in self.rvol:
|
||||||
|
d1, d2 = v.rsplit("/", 1) if "/" in v else ["", v]
|
||||||
|
if d1 == top:
|
||||||
|
vfs_virt[d2] = 0
|
||||||
|
|
||||||
|
dirs = []
|
||||||
|
|
||||||
|
if not self.args.ed or "dots" not in self.uparam:
|
||||||
|
vfs_ls = exclude_dotfiles(vfs_ls)
|
||||||
|
|
||||||
|
for fn in [x for x in vfs_ls if x != excl]:
|
||||||
|
abspath = os.path.join(fsroot, fn)
|
||||||
|
if os.path.isdir(abspath):
|
||||||
|
dirs.append(fn)
|
||||||
|
|
||||||
|
for x in vfs_virt.keys():
|
||||||
|
if x != excl:
|
||||||
|
dirs.append(x)
|
||||||
|
|
||||||
|
ret["a"] = dirs
|
||||||
|
return ret
|
||||||
|
|
||||||
def tx_browser(self):
|
def tx_browser(self):
|
||||||
vpath = ""
|
vpath = ""
|
||||||
vpnodes = [["", "/"]]
|
vpnodes = [["", "/"]]
|
||||||
|
@ -1062,8 +1147,7 @@ class HttpCli(object):
|
||||||
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)
|
||||||
|
|
||||||
bad = "{0}.hist{0}up2k.".format(os.sep)
|
if rem.startswith(".hist/up2k."):
|
||||||
if abspath.endswith(bad + "db") or abspath.endswith(bad + "snap"):
|
|
||||||
raise Pebkac(403)
|
raise Pebkac(403)
|
||||||
|
|
||||||
return self.tx_file(abspath)
|
return self.tx_file(abspath)
|
||||||
|
@ -1092,8 +1176,8 @@ class HttpCli(object):
|
||||||
vfs_ls = exclude_dotfiles(vfs_ls)
|
vfs_ls = exclude_dotfiles(vfs_ls)
|
||||||
|
|
||||||
hidden = []
|
hidden = []
|
||||||
if fsroot.endswith(str(os.sep) + ".hist"):
|
if rem == ".hist":
|
||||||
hidden = ["up2k.db", "up2k.snap"]
|
hidden = ["up2k."]
|
||||||
|
|
||||||
dirs = []
|
dirs = []
|
||||||
files = []
|
files = []
|
||||||
|
@ -1106,7 +1190,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
if fn in vfs_virt:
|
if fn in vfs_virt:
|
||||||
fspath = vfs_virt[fn].realpath
|
fspath = vfs_virt[fn].realpath
|
||||||
elif fn in hidden:
|
elif hidden and any(fn.startswith(x) for x in hidden):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
fspath = fsroot + "/" + fn
|
fspath = fsroot + "/" + fn
|
||||||
|
@ -1193,12 +1277,19 @@ class HttpCli(object):
|
||||||
# ts = "?{}".format(time.time())
|
# ts = "?{}".format(time.time())
|
||||||
|
|
||||||
dirs.extend(files)
|
dirs.extend(files)
|
||||||
|
|
||||||
|
if "ls" in self.uparam:
|
||||||
|
ret = json.dumps(dirs)
|
||||||
|
self.reply(ret.encode("utf-8", "replace"))
|
||||||
|
return True
|
||||||
|
|
||||||
html = self.conn.tpl_browser.render(
|
html = self.conn.tpl_browser.render(
|
||||||
vdir=quotep(self.vpath),
|
vdir=quotep(self.vpath),
|
||||||
vpnodes=vpnodes,
|
vpnodes=vpnodes,
|
||||||
files=dirs,
|
files=dirs,
|
||||||
can_upload=self.writable,
|
can_upload=self.writable,
|
||||||
can_read=self.readable,
|
can_read=self.readable,
|
||||||
|
have_up2k_idx=self.args.e2d,
|
||||||
ts=ts,
|
ts=ts,
|
||||||
prologue=logues[0],
|
prologue=logues[0],
|
||||||
epilogue=logues[1],
|
epilogue=logues[1],
|
||||||
|
|
|
@ -30,6 +30,7 @@ except ImportError:
|
||||||
from .__init__ import E
|
from .__init__ import E
|
||||||
from .util import Unrecv
|
from .util import Unrecv
|
||||||
from .httpcli import HttpCli
|
from .httpcli import HttpCli
|
||||||
|
from .u2idx import U2idx
|
||||||
|
|
||||||
|
|
||||||
class HttpConn(object):
|
class HttpConn(object):
|
||||||
|
@ -50,6 +51,7 @@ class HttpConn(object):
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
self.nbyte = 0
|
self.nbyte = 0
|
||||||
self.workload = 0
|
self.workload = 0
|
||||||
|
self.u2idx = None
|
||||||
self.log_func = hsrv.log
|
self.log_func = hsrv.log
|
||||||
self.set_rproxy()
|
self.set_rproxy()
|
||||||
|
|
||||||
|
@ -80,6 +82,12 @@ class HttpConn(object):
|
||||||
def log(self, msg):
|
def log(self, msg):
|
||||||
self.log_func(self.log_src, msg)
|
self.log_func(self.log_src, msg)
|
||||||
|
|
||||||
|
def get_u2idx(self):
|
||||||
|
if not self.u2idx:
|
||||||
|
self.u2idx = U2idx(self.args, self.log_func)
|
||||||
|
|
||||||
|
return self.u2idx
|
||||||
|
|
||||||
def _detect_https(self):
|
def _detect_https(self):
|
||||||
method = None
|
method = None
|
||||||
if self.cert_path:
|
if self.cert_path:
|
||||||
|
|
|
@ -39,9 +39,13 @@ class SvcHub(object):
|
||||||
self.tcpsrv = TcpSrv(self)
|
self.tcpsrv = TcpSrv(self)
|
||||||
self.up2k = Up2k(self)
|
self.up2k = Up2k(self)
|
||||||
|
|
||||||
if self.args.e2d and self.args.e2s:
|
if self.args.e2ds:
|
||||||
auth = AuthSrv(self.args, self.log, False)
|
auth = AuthSrv(self.args, self.log, False)
|
||||||
self.up2k.build_indexes(auth.all_writable)
|
vols = auth.vfs.all_vols.values()
|
||||||
|
if not self.args.e2dsa:
|
||||||
|
vols = [x for x in vols if x.uwrite]
|
||||||
|
|
||||||
|
self.up2k.build_indexes(vols)
|
||||||
|
|
||||||
# decide which worker impl to use
|
# decide which worker impl to use
|
||||||
if self.check_mp_enable():
|
if self.check_mp_enable():
|
||||||
|
@ -79,7 +83,7 @@ class SvcHub(object):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now >= self.next_day:
|
if now >= self.next_day:
|
||||||
dt = datetime.utcfromtimestamp(now)
|
dt = datetime.utcfromtimestamp(now)
|
||||||
print("\033[36m{}\033[0m".format(dt.strftime("%Y-%m-%d")))
|
print("\033[36m{}\033[0m\n".format(dt.strftime("%Y-%m-%d")), end="")
|
||||||
|
|
||||||
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
||||||
day_now = dt.day
|
day_now = dt.day
|
||||||
|
@ -89,7 +93,7 @@ class SvcHub(object):
|
||||||
dt = dt.replace(hour=0, minute=0, second=0)
|
dt = dt.replace(hour=0, minute=0, second=0)
|
||||||
self.next_day = calendar.timegm(dt.utctimetuple())
|
self.next_day = calendar.timegm(dt.utctimetuple())
|
||||||
|
|
||||||
fmt = "\033[36m{} \033[33m{:21} \033[0m{}"
|
fmt = "\033[36m{} \033[33m{:21} \033[0m{}\n"
|
||||||
if not VT100:
|
if not VT100:
|
||||||
fmt = "{} {:21} {}"
|
fmt = "{} {:21} {}"
|
||||||
if "\033" in msg:
|
if "\033" in msg:
|
||||||
|
@ -100,12 +104,12 @@ class SvcHub(object):
|
||||||
ts = datetime.utcfromtimestamp(now).strftime("%H:%M:%S.%f")[:-3]
|
ts = datetime.utcfromtimestamp(now).strftime("%H:%M:%S.%f")[:-3]
|
||||||
msg = fmt.format(ts, src, msg)
|
msg = fmt.format(ts, src, msg)
|
||||||
try:
|
try:
|
||||||
print(msg)
|
print(msg, end="")
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
try:
|
try:
|
||||||
print(msg.encode("utf-8", "replace").decode())
|
print(msg.encode("utf-8", "replace").decode(), end="")
|
||||||
except:
|
except:
|
||||||
print(msg.encode("ascii", "replace").decode())
|
print(msg.encode("ascii", "replace").decode(), end="")
|
||||||
|
|
||||||
def check_mp_support(self):
|
def check_mp_support(self):
|
||||||
vmin = sys.version_info[1]
|
vmin = sys.version_info[1]
|
||||||
|
|
146
copyparty/u2idx.py
Normal file
146
copyparty/u2idx.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from .util import u8safe
|
||||||
|
from .up2k import up2k_wark_from_hashlist
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
HAVE_SQLITE3 = True
|
||||||
|
import sqlite3
|
||||||
|
except:
|
||||||
|
HAVE_SQLITE3 = False
|
||||||
|
|
||||||
|
|
||||||
|
class U2idx(object):
|
||||||
|
def __init__(self, args, log_func):
|
||||||
|
self.args = args
|
||||||
|
self.log_func = log_func
|
||||||
|
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
self.log("could not load sqlite3; searchign wqill be disabled")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.dbs = {}
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
self.log_func("u2idx", msg)
|
||||||
|
|
||||||
|
def fsearch(self, vols, body):
|
||||||
|
"""search by up2k hashlist"""
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
return []
|
||||||
|
|
||||||
|
fsize = body["size"]
|
||||||
|
fhash = body["hash"]
|
||||||
|
wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash)
|
||||||
|
return self.run_query(vols, "select * from up where w = ?", [wark])
|
||||||
|
|
||||||
|
def search(self, vols, body):
|
||||||
|
"""search by query params"""
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
return []
|
||||||
|
|
||||||
|
qobj = {}
|
||||||
|
_conv_sz(qobj, body, "sz_min", "sz >= ?")
|
||||||
|
_conv_sz(qobj, body, "sz_max", "sz <= ?")
|
||||||
|
_conv_dt(qobj, body, "dt_min", "mt >= ?")
|
||||||
|
_conv_dt(qobj, body, "dt_max", "mt <= ?")
|
||||||
|
for seg, dk in [["path", "rd"], ["name", "fn"]]:
|
||||||
|
for inv in ["no", "yes"]:
|
||||||
|
jk = "{}_{}".format(seg, inv)
|
||||||
|
if jk in body:
|
||||||
|
_conv_txt(qobj, body, jk, dk)
|
||||||
|
|
||||||
|
qstr = "select * from up"
|
||||||
|
qv = []
|
||||||
|
if qobj:
|
||||||
|
qk = []
|
||||||
|
for k, v in sorted(qobj.items()):
|
||||||
|
qk.append(k)
|
||||||
|
qv.append(v)
|
||||||
|
|
||||||
|
qstr = " and ".join(qk)
|
||||||
|
qstr = "select * from up where " + qstr
|
||||||
|
|
||||||
|
return self.run_query(vols, qstr, qv)
|
||||||
|
|
||||||
|
def run_query(self, vols, qstr, qv):
|
||||||
|
qv = tuple(qv)
|
||||||
|
self.log("qs: " + qstr)
|
||||||
|
self.log("qv: " + repr(qv))
|
||||||
|
|
||||||
|
ret = []
|
||||||
|
lim = 100
|
||||||
|
for (vtop, ptop, flags) in vols:
|
||||||
|
db = self.dbs.get(ptop)
|
||||||
|
if not db:
|
||||||
|
db = _open(ptop)
|
||||||
|
if not db:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.dbs[ptop] = db
|
||||||
|
self.log("idx /{} @ {} {}".format(vtop, ptop, flags))
|
||||||
|
|
||||||
|
c = db.execute(qstr, qv)
|
||||||
|
for _, ts, sz, rd, fn in c:
|
||||||
|
lim -= 1
|
||||||
|
if lim <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
rp = os.path.join(vtop, rd, fn).replace("\\", "/")
|
||||||
|
ret.append({"ts": int(ts), "sz": sz, "rp": rp})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def _open(ptop):
|
||||||
|
db_path = os.path.join(ptop, ".hist", "up2k.db")
|
||||||
|
if os.path.exists(db_path):
|
||||||
|
return sqlite3.connect(db_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _conv_sz(q, body, k, sql):
|
||||||
|
if k in body:
|
||||||
|
q[sql] = int(float(body[k]) * 1024 * 1024)
|
||||||
|
|
||||||
|
|
||||||
|
def _conv_dt(q, body, k, sql):
|
||||||
|
if k not in body:
|
||||||
|
return
|
||||||
|
|
||||||
|
v = body[k].upper().rstrip("Z").replace(",", " ").replace("T", " ")
|
||||||
|
while " " in v:
|
||||||
|
v = v.replace(" ", " ")
|
||||||
|
|
||||||
|
for fmt in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H", "%Y-%m-%d"]:
|
||||||
|
try:
|
||||||
|
ts = datetime.strptime(v, fmt).timestamp()
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
ts = None
|
||||||
|
|
||||||
|
if ts:
|
||||||
|
q[sql] = ts
|
||||||
|
|
||||||
|
|
||||||
|
def _conv_txt(q, body, k, sql):
|
||||||
|
v = body[k]
|
||||||
|
print("[" + v + "]")
|
||||||
|
|
||||||
|
head = "'%'||"
|
||||||
|
if v.startswith("^"):
|
||||||
|
head = ""
|
||||||
|
v = v[1:]
|
||||||
|
|
||||||
|
tail = "||'%'"
|
||||||
|
if v.endswith("$"):
|
||||||
|
tail = ""
|
||||||
|
v = v[:-1]
|
||||||
|
|
||||||
|
inv = "not" if k.endswith("_no") else ""
|
||||||
|
qk = "{} {} like {}?{}".format(sql, inv, head, tail)
|
||||||
|
q[qk] = u8safe(v)
|
|
@ -1,7 +1,6 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -17,15 +16,23 @@ import threading
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from .__init__ import WINDOWS
|
from .__init__ import WINDOWS
|
||||||
from .util import Pebkac, Queue, fsdec, fsenc, sanitize_fn, ren_open, atomic_move
|
from .util import (
|
||||||
|
Pebkac,
|
||||||
|
Queue,
|
||||||
|
ProgressPrinter,
|
||||||
|
fsdec,
|
||||||
|
fsenc,
|
||||||
|
sanitize_fn,
|
||||||
|
ren_open,
|
||||||
|
atomic_move,
|
||||||
|
u8safe,
|
||||||
|
)
|
||||||
|
|
||||||
HAVE_SQLITE3 = False
|
|
||||||
try:
|
try:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
HAVE_SQLITE3 = True
|
HAVE_SQLITE3 = True
|
||||||
|
import sqlite3
|
||||||
except:
|
except:
|
||||||
pass
|
HAVE_SQLITE3 = False
|
||||||
|
|
||||||
|
|
||||||
class Up2k(object):
|
class Up2k(object):
|
||||||
|
@ -39,11 +46,11 @@ class Up2k(object):
|
||||||
def __init__(self, broker):
|
def __init__(self, broker):
|
||||||
self.broker = broker
|
self.broker = broker
|
||||||
self.args = broker.args
|
self.args = broker.args
|
||||||
self.log = broker.log
|
self.log_func = broker.log
|
||||||
self.persist = self.args.e2d
|
self.persist = self.args.e2d
|
||||||
|
|
||||||
# config
|
# config
|
||||||
self.salt = "hunter2" # TODO: config
|
self.salt = broker.args.salt
|
||||||
|
|
||||||
# state
|
# state
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
@ -66,8 +73,16 @@ class Up2k(object):
|
||||||
self.r_hash = re.compile("^[0-9a-zA-Z_-]{43}$")
|
self.r_hash = re.compile("^[0-9a-zA-Z_-]{43}$")
|
||||||
|
|
||||||
if self.persist and not HAVE_SQLITE3:
|
if self.persist and not HAVE_SQLITE3:
|
||||||
m = "could not initialize sqlite3, will use in-memory registry only"
|
self.log("could not initialize sqlite3, will use in-memory registry only")
|
||||||
self.log("up2k", m)
|
|
||||||
|
def log(self, msg):
|
||||||
|
self.log_func("up2k", msg + "\033[K")
|
||||||
|
|
||||||
|
def _u8(self, rd, fn):
|
||||||
|
s_rd = u8safe(rd)
|
||||||
|
s_fn = u8safe(fn)
|
||||||
|
self.log("u8safe retry:\n [{}] [{}]\n [{}] [{}]".format(rd, fn, s_rd, s_fn))
|
||||||
|
return (s_rd, s_fn)
|
||||||
|
|
||||||
def _vis_job_progress(self, job):
|
def _vis_job_progress(self, job):
|
||||||
perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
|
perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
|
||||||
|
@ -98,7 +113,7 @@ class Up2k(object):
|
||||||
|
|
||||||
m = "loaded snap {} |{}|".format(path, len(reg.keys()))
|
m = "loaded snap {} |{}|".format(path, len(reg.keys()))
|
||||||
m = [m] + self._vis_reg_progress(reg)
|
m = [m] + self._vis_reg_progress(reg)
|
||||||
self.log("up2k", "\n".join(m))
|
self.log("\n".join(m))
|
||||||
|
|
||||||
self.registry[ptop] = reg
|
self.registry[ptop] = reg
|
||||||
if not self.persist or not HAVE_SQLITE3:
|
if not self.persist or not HAVE_SQLITE3:
|
||||||
|
@ -119,57 +134,86 @@ class Up2k(object):
|
||||||
self.db[ptop] = db
|
self.db[ptop] = db
|
||||||
return db
|
return db
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
m = "failed to open [{}]: {}".format(ptop, repr(ex))
|
self.log("cannot use database at [{}]: {}".format(ptop, repr(ex)))
|
||||||
self.log("up2k", m)
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def build_indexes(self, writeables):
|
def build_indexes(self, writeables):
|
||||||
tops = [d.realpath for d in writeables]
|
tops = [d.realpath for d in writeables]
|
||||||
|
self.pp = ProgressPrinter()
|
||||||
|
t0 = time.time()
|
||||||
for top in tops:
|
for top in tops:
|
||||||
db = self.register_vpath(top)
|
db = self.register_vpath(top)
|
||||||
if db:
|
if not db:
|
||||||
# can be symlink so don't `and d.startswith(top)``
|
continue
|
||||||
excl = set([d for d in tops if d != top])
|
|
||||||
dbw = [db, 0, time.time()]
|
|
||||||
self._build_dir(dbw, top, excl, top)
|
|
||||||
self._drop_lost(db, top)
|
|
||||||
if dbw[1]:
|
|
||||||
self.log("up2k", "commit {} new files".format(dbw[1]))
|
|
||||||
|
|
||||||
db.commit()
|
self.pp.n = next(db.execute("select count(w) from up"))[0]
|
||||||
|
db_path = os.path.join(top, ".hist", "up2k.db")
|
||||||
|
sz0 = os.path.getsize(db_path) // 1024
|
||||||
|
|
||||||
|
# can be symlink so don't `and d.startswith(top)``
|
||||||
|
excl = set([d for d in tops if d != top])
|
||||||
|
dbw = [db, 0, time.time()]
|
||||||
|
|
||||||
|
n_add = self._build_dir(dbw, top, excl, top)
|
||||||
|
n_rm = self._drop_lost(db, top)
|
||||||
|
if dbw[1]:
|
||||||
|
self.log("commit {} new files".format(dbw[1]))
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
if n_add or n_rm:
|
||||||
|
db_path = os.path.join(top, ".hist", "up2k.db")
|
||||||
|
sz1 = os.path.getsize(db_path) // 1024
|
||||||
|
db.execute("vacuum")
|
||||||
|
sz2 = os.path.getsize(db_path) // 1024
|
||||||
|
msg = "{} new, {} del, {} kB vacced, {} kB gain, {} kB now".format(
|
||||||
|
n_add, n_rm, sz1 - sz2, sz2 - sz0, sz2
|
||||||
|
)
|
||||||
|
self.log(msg)
|
||||||
|
|
||||||
|
self.pp.end = True
|
||||||
|
self.log("{} volumes in {:.2f} sec".format(len(tops), time.time() - t0))
|
||||||
|
|
||||||
def _build_dir(self, dbw, top, excl, cdir):
|
def _build_dir(self, dbw, top, excl, cdir):
|
||||||
try:
|
try:
|
||||||
inodes = [fsdec(x) for x in os.listdir(fsenc(cdir))]
|
inodes = [fsdec(x) for x in os.listdir(fsenc(cdir))]
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("up2k", "listdir: {} @ [{}]".format(repr(ex), cdir))
|
self.log("listdir: {} @ [{}]".format(repr(ex), cdir))
|
||||||
return
|
return 0
|
||||||
|
|
||||||
|
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
||||||
histdir = os.path.join(top, ".hist")
|
histdir = os.path.join(top, ".hist")
|
||||||
|
ret = 0
|
||||||
for inode in inodes:
|
for inode in inodes:
|
||||||
abspath = os.path.join(cdir, inode)
|
abspath = os.path.join(cdir, inode)
|
||||||
try:
|
try:
|
||||||
inf = os.stat(fsenc(abspath))
|
inf = os.stat(fsenc(abspath))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("up2k", "stat: {} @ [{}]".format(repr(ex), abspath))
|
self.log("stat: {} @ [{}]".format(repr(ex), abspath))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if stat.S_ISDIR(inf.st_mode):
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
if abspath in excl or abspath == histdir:
|
if abspath in excl or abspath == histdir:
|
||||||
continue
|
continue
|
||||||
# self.log("up2k", " dir: {}".format(abspath))
|
# self.log(" dir: {}".format(abspath))
|
||||||
self._build_dir(dbw, top, excl, abspath)
|
ret += self._build_dir(dbw, top, excl, abspath)
|
||||||
else:
|
else:
|
||||||
# self.log("up2k", "file: {}".format(abspath))
|
# self.log("file: {}".format(abspath))
|
||||||
rp = abspath[len(top) :].replace("\\", "/").strip("/")
|
rp = abspath[len(top) :].replace("\\", "/").strip("/")
|
||||||
c = dbw[0].execute("select * from up where rp = ?", (rp,))
|
rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
|
||||||
|
sql = "select * from up where rd = ? and fn = ?"
|
||||||
|
try:
|
||||||
|
c = dbw[0].execute(sql, (rd, fn))
|
||||||
|
except:
|
||||||
|
c = dbw[0].execute(sql, self._u8(rd, fn))
|
||||||
|
|
||||||
in_db = list(c.fetchall())
|
in_db = list(c.fetchall())
|
||||||
if in_db:
|
if in_db:
|
||||||
_, dts, dsz, _ = in_db[0]
|
self.pp.n -= 1
|
||||||
|
_, dts, dsz, _, _ = in_db[0]
|
||||||
if len(in_db) > 1:
|
if len(in_db) > 1:
|
||||||
m = "WARN: multiple entries: [{}] => [{}] ({})"
|
m = "WARN: multiple entries: [{}] => [{}] ({})"
|
||||||
self.log("up2k", m.format(top, rp, len(in_db)))
|
self.log(m.format(top, rp, len(in_db)))
|
||||||
dts = -1
|
dts = -1
|
||||||
|
|
||||||
if dts == inf.st_mtime and dsz == inf.st_size:
|
if dts == inf.st_mtime and dsz == inf.st_size:
|
||||||
|
@ -178,68 +222,80 @@ class Up2k(object):
|
||||||
m = "reindex [{}] => [{}] ({}/{}) ({}/{})".format(
|
m = "reindex [{}] => [{}] ({}/{}) ({}/{})".format(
|
||||||
top, rp, dts, inf.st_mtime, dsz, inf.st_size
|
top, rp, dts, inf.st_mtime, dsz, inf.st_size
|
||||||
)
|
)
|
||||||
self.log("up2k", m)
|
self.log(m)
|
||||||
self.db_rm(dbw[0], rp)
|
self.db_rm(dbw[0], rd, fn)
|
||||||
|
ret += 1
|
||||||
dbw[1] += 1
|
dbw[1] += 1
|
||||||
in_db = None
|
in_db = None
|
||||||
|
|
||||||
self.log("up2k", "file: {}".format(abspath))
|
self.pp.msg = "a{} {}".format(self.pp.n, abspath)
|
||||||
|
if inf.st_size > 1024 * 1024:
|
||||||
|
self.log("file: {}".format(abspath))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hashes = self._hashlist_from_file(abspath)
|
hashes = self._hashlist_from_file(abspath)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("up2k", "hash: {} @ [{}]".format(repr(ex), abspath))
|
self.log("hash: {} @ [{}]".format(repr(ex), abspath))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
wark = self._wark_from_hashlist(inf.st_size, hashes)
|
wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes)
|
||||||
self.db_add(dbw[0], wark, rp, inf.st_mtime, inf.st_size)
|
self.db_add(dbw[0], wark, rd, fn, inf.st_mtime, inf.st_size)
|
||||||
dbw[1] += 1
|
dbw[1] += 1
|
||||||
|
ret += 1
|
||||||
td = time.time() - dbw[2]
|
td = time.time() - dbw[2]
|
||||||
if dbw[1] > 1024 or td > 60:
|
if dbw[1] >= 4096 or td >= 60:
|
||||||
self.log("up2k", "commit {} new files".format(dbw[1]))
|
self.log("commit {} new files".format(dbw[1]))
|
||||||
dbw[0].commit()
|
dbw[0].commit()
|
||||||
dbw[1] = 0
|
dbw[1] = 0
|
||||||
dbw[2] = time.time()
|
dbw[2] = time.time()
|
||||||
|
return ret
|
||||||
|
|
||||||
def _drop_lost(self, db, top):
|
def _drop_lost(self, db, top):
|
||||||
rm = []
|
rm = []
|
||||||
|
nchecked = 0
|
||||||
|
nfiles = next(db.execute("select count(w) from up"))[0]
|
||||||
c = db.execute("select * from up")
|
c = db.execute("select * from up")
|
||||||
for dwark, dts, dsz, drp in c:
|
for dwark, dts, dsz, drd, dfn in c:
|
||||||
abspath = os.path.join(top, drp)
|
nchecked += 1
|
||||||
|
abspath = os.path.join(top, drd, dfn)
|
||||||
|
# almost zero overhead dw
|
||||||
|
self.pp.msg = "b{} {}".format(nfiles - nchecked, abspath)
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(fsenc(abspath)):
|
if not os.path.exists(fsenc(abspath)):
|
||||||
rm.append(drp)
|
rm.append([drd, dfn])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("up2k", "stat-rm: {} @ [{}]".format(repr(ex), abspath))
|
self.log("stat-rm: {} @ [{}]".format(repr(ex), abspath))
|
||||||
|
|
||||||
if not rm:
|
if rm:
|
||||||
return
|
self.log("forgetting {} deleted files".format(len(rm)))
|
||||||
|
for rd, fn in rm:
|
||||||
|
self.db_rm(db, rd, fn)
|
||||||
|
|
||||||
self.log("up2k", "forgetting {} deleted files".format(len(rm)))
|
return len(rm)
|
||||||
for rp in rm:
|
|
||||||
self.db_rm(db, rp)
|
|
||||||
|
|
||||||
def _open_db(self, db_path):
|
def _open_db(self, db_path):
|
||||||
|
existed = os.path.exists(db_path)
|
||||||
conn = sqlite3.connect(db_path, check_same_thread=False)
|
conn = sqlite3.connect(db_path, check_same_thread=False)
|
||||||
try:
|
try:
|
||||||
c = conn.execute(r"select * from kv where k = 'sver'")
|
ver = self._read_ver(conn)
|
||||||
rows = c.fetchall()
|
|
||||||
if rows:
|
|
||||||
ver = rows[0][1]
|
|
||||||
else:
|
|
||||||
self.log("up2k", "WARN: no sver in kv, DB corrupt?")
|
|
||||||
ver = "unknown"
|
|
||||||
|
|
||||||
if ver == "1":
|
if ver == 1:
|
||||||
|
conn = self._upgrade_v1(conn, db_path)
|
||||||
|
ver = self._read_ver(conn)
|
||||||
|
|
||||||
|
if ver == 2:
|
||||||
try:
|
try:
|
||||||
nfiles = next(conn.execute("select count(w) from up"))[0]
|
nfiles = next(conn.execute("select count(w) from up"))[0]
|
||||||
self.log("up2k", "found DB at {} |{}|".format(db_path, nfiles))
|
self.log("found DB at {} |{}|".format(db_path, nfiles))
|
||||||
return conn
|
return conn
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
m = "WARN: could not list files, DB corrupt?\n " + repr(ex)
|
self.log("WARN: could not list files, DB corrupt?\n " + repr(ex))
|
||||||
self.log("up2k", m)
|
|
||||||
|
if ver is not None:
|
||||||
|
self.log("REPLACING unsupported DB (v.{}) at {}".format(ver, db_path))
|
||||||
|
elif not existed:
|
||||||
|
raise Exception("whatever")
|
||||||
|
|
||||||
m = "REPLACING unsupported DB (v.{}) at {}".format(ver, db_path)
|
|
||||||
self.log("up2k", m)
|
|
||||||
conn.close()
|
conn.close()
|
||||||
os.unlink(db_path)
|
os.unlink(db_path)
|
||||||
conn = sqlite3.connect(db_path, check_same_thread=False)
|
conn = sqlite3.connect(db_path, check_same_thread=False)
|
||||||
|
@ -247,17 +303,58 @@ class Up2k(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# sqlite is variable-width only, no point in using char/nchar/varchar
|
# sqlite is variable-width only, no point in using char/nchar/varchar
|
||||||
|
self._create_v2(conn)
|
||||||
|
conn.commit()
|
||||||
|
self.log("created DB at {}".format(db_path))
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def _read_ver(self, conn):
|
||||||
|
for tab in ["ki", "kv"]:
|
||||||
|
try:
|
||||||
|
c = conn.execute(r"select v from {} where k = 'sver'".format(tab))
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rows = c.fetchall()
|
||||||
|
if rows:
|
||||||
|
return int(rows[0][0])
|
||||||
|
|
||||||
|
def _create_v2(self, conn):
|
||||||
for cmd in [
|
for cmd in [
|
||||||
r"create table kv (k text, v text)",
|
r"create table ks (k text, v text)",
|
||||||
r"create table up (w text, mt int, sz int, rp text)",
|
r"create table ki (k text, v int)",
|
||||||
r"insert into kv values ('sver', '1')",
|
r"create table up (w text, mt int, sz int, rd text, fn text)",
|
||||||
|
r"insert into ki values ('sver', 2)",
|
||||||
r"create index up_w on up(w)",
|
r"create index up_w on up(w)",
|
||||||
|
r"create index up_rd on up(rd)",
|
||||||
|
r"create index up_fn on up(fn)",
|
||||||
]:
|
]:
|
||||||
conn.execute(cmd)
|
conn.execute(cmd)
|
||||||
|
|
||||||
conn.commit()
|
def _upgrade_v1(self, odb, db_path):
|
||||||
self.log("up2k", "created DB at {}".format(db_path))
|
self.log("\033[33mupgrading v1 to v2:\033[0m {}".format(db_path))
|
||||||
return conn
|
|
||||||
|
npath = db_path + ".next"
|
||||||
|
if os.path.exists(npath):
|
||||||
|
os.unlink(npath)
|
||||||
|
|
||||||
|
ndb = sqlite3.connect(npath, check_same_thread=False)
|
||||||
|
self._create_v2(ndb)
|
||||||
|
|
||||||
|
c = odb.execute("select * from up")
|
||||||
|
for wark, ts, sz, rp in c:
|
||||||
|
rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
|
||||||
|
v = (wark, ts, sz, rd, fn)
|
||||||
|
ndb.execute("insert into up values (?,?,?,?,?)", v)
|
||||||
|
|
||||||
|
ndb.commit()
|
||||||
|
ndb.close()
|
||||||
|
odb.close()
|
||||||
|
bpath = db_path + ".bak.v1"
|
||||||
|
self.log("success; backup at: " + bpath)
|
||||||
|
atomic_move(db_path, bpath)
|
||||||
|
atomic_move(npath, db_path)
|
||||||
|
return sqlite3.connect(db_path, check_same_thread=False)
|
||||||
|
|
||||||
def handle_json(self, cj):
|
def handle_json(self, cj):
|
||||||
self.register_vpath(cj["ptop"])
|
self.register_vpath(cj["ptop"])
|
||||||
|
@ -271,19 +368,13 @@ class Up2k(object):
|
||||||
reg = self.registry[cj["ptop"]]
|
reg = self.registry[cj["ptop"]]
|
||||||
if db:
|
if db:
|
||||||
cur = db.execute(r"select * from up where w = ?", (wark,))
|
cur = db.execute(r"select * from up where w = ?", (wark,))
|
||||||
for _, dtime, dsize, dp_rel in cur:
|
for _, dtime, dsize, dp_dir, dp_fn in cur:
|
||||||
dp_abs = os.path.join(cj["ptop"], dp_rel).replace("\\", "/")
|
dp_abs = os.path.join(cj["ptop"], dp_dir, dp_fn).replace("\\", "/")
|
||||||
# relying on path.exists to return false on broken symlinks
|
# relying on path.exists to return false on broken symlinks
|
||||||
if os.path.exists(fsenc(dp_abs)):
|
if os.path.exists(fsenc(dp_abs)):
|
||||||
try:
|
|
||||||
prel, name = dp_rel.rsplit("/", 1)
|
|
||||||
except:
|
|
||||||
prel = ""
|
|
||||||
name = dp_rel
|
|
||||||
|
|
||||||
job = {
|
job = {
|
||||||
"name": name,
|
"name": dp_fn,
|
||||||
"prel": prel,
|
"prel": dp_dir,
|
||||||
"vtop": cj["vtop"],
|
"vtop": cj["vtop"],
|
||||||
"ptop": cj["ptop"],
|
"ptop": cj["ptop"],
|
||||||
"flag": cj["flag"],
|
"flag": cj["flag"],
|
||||||
|
@ -319,12 +410,12 @@ class Up2k(object):
|
||||||
vsrc = os.path.join(job["vtop"], job["prel"], job["name"])
|
vsrc = os.path.join(job["vtop"], job["prel"], job["name"])
|
||||||
vsrc = vsrc.replace("\\", "/") # just for prints anyways
|
vsrc = vsrc.replace("\\", "/") # just for prints anyways
|
||||||
if job["need"]:
|
if job["need"]:
|
||||||
self.log("up2k", "unfinished:\n {0}\n {1}".format(src, dst))
|
self.log("unfinished:\n {0}\n {1}".format(src, dst))
|
||||||
err = "partial upload exists at a different location; please resume uploading here instead:\n"
|
err = "partial upload exists at a different location; please resume uploading here instead:\n"
|
||||||
err += vsrc + " "
|
err += vsrc + " "
|
||||||
raise Pebkac(400, err)
|
raise Pebkac(400, err)
|
||||||
elif "nodupe" in job["flag"]:
|
elif "nodupe" in job["flag"]:
|
||||||
self.log("up2k", "dupe-reject:\n {0}\n {1}".format(src, dst))
|
self.log("dupe-reject:\n {0}\n {1}".format(src, dst))
|
||||||
err = "upload rejected, file already exists:\n " + vsrc + " "
|
err = "upload rejected, file already exists:\n " + vsrc + " "
|
||||||
raise Pebkac(400, err)
|
raise Pebkac(400, err)
|
||||||
else:
|
else:
|
||||||
|
@ -389,7 +480,7 @@ class Up2k(object):
|
||||||
|
|
||||||
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
|
||||||
self.log("up2k", "linking dupe:\n {0}\n {1}".format(src, dst))
|
self.log("linking dupe:\n {0}\n {1}".format(src, dst))
|
||||||
try:
|
try:
|
||||||
lsrc = src
|
lsrc = src
|
||||||
ldst = dst
|
ldst = dst
|
||||||
|
@ -412,7 +503,7 @@ class Up2k(object):
|
||||||
lsrc = "../" * (len(lsrc) - 1) + "/".join(lsrc)
|
lsrc = "../" * (len(lsrc) - 1) + "/".join(lsrc)
|
||||||
os.symlink(fsenc(lsrc), fsenc(ldst))
|
os.symlink(fsenc(lsrc), fsenc(ldst))
|
||||||
except (AttributeError, OSError) as ex:
|
except (AttributeError, OSError) as ex:
|
||||||
self.log("up2k", "cannot symlink; creating copy: " + repr(ex))
|
self.log("cannot symlink; creating copy: " + repr(ex))
|
||||||
shutil.copy2(fsenc(src), fsenc(dst))
|
shutil.copy2(fsenc(src), fsenc(dst))
|
||||||
|
|
||||||
def handle_chunk(self, ptop, wark, chash):
|
def handle_chunk(self, ptop, wark, chash):
|
||||||
|
@ -430,7 +521,7 @@ class Up2k(object):
|
||||||
|
|
||||||
job["poke"] = time.time()
|
job["poke"] = time.time()
|
||||||
|
|
||||||
chunksize = self._get_chunksize(job["size"])
|
chunksize = up2k_chunksize(job["size"])
|
||||||
ofs = [chunksize * x for x in nchunk]
|
ofs = [chunksize * x for x in nchunk]
|
||||||
|
|
||||||
path = os.path.join(job["ptop"], job["prel"], job["tnam"])
|
path = os.path.join(job["ptop"], job["prel"], job["tnam"])
|
||||||
|
@ -463,33 +554,31 @@ class Up2k(object):
|
||||||
|
|
||||||
db = self.db.get(job["ptop"], None)
|
db = self.db.get(job["ptop"], None)
|
||||||
if db:
|
if db:
|
||||||
rp = os.path.join(job["prel"], job["name"]).replace("\\", "/")
|
j = job
|
||||||
self.db_rm(db, rp)
|
self.db_rm(db, j["prel"], j["name"])
|
||||||
self.db_add(db, job["wark"], rp, job["lmod"], job["size"])
|
self.db_add(db, j["wark"], j["prel"], j["name"], j["lmod"], j["size"])
|
||||||
db.commit()
|
db.commit()
|
||||||
del self.registry[ptop][wark]
|
del self.registry[ptop][wark]
|
||||||
# in-memory registry is reserved for unfinished uploads
|
# in-memory registry is reserved for unfinished uploads
|
||||||
|
|
||||||
return ret, dst
|
return ret, dst
|
||||||
|
|
||||||
def _get_chunksize(self, filesize):
|
def db_rm(self, db, rd, fn):
|
||||||
chunksize = 1024 * 1024
|
sql = "delete from up where rd = ? and fn = ?"
|
||||||
stepsize = 512 * 1024
|
try:
|
||||||
while True:
|
db.execute(sql, (rd, fn))
|
||||||
for mul in [1, 2]:
|
except:
|
||||||
nchunks = math.ceil(filesize * 1.0 / chunksize)
|
db.execute(sql, self._u8(rd, fn))
|
||||||
if nchunks <= 256 or chunksize >= 32 * 1024 * 1024:
|
|
||||||
return chunksize
|
|
||||||
|
|
||||||
chunksize += stepsize
|
def db_add(self, db, wark, rd, fn, ts, sz):
|
||||||
stepsize *= mul
|
sql = "insert into up values (?,?,?,?,?)"
|
||||||
|
v = (wark, ts, sz, rd, fn)
|
||||||
def db_rm(self, db, rp):
|
try:
|
||||||
db.execute("delete from up where rp = ?", (rp,))
|
db.execute(sql, v)
|
||||||
|
except:
|
||||||
def db_add(self, db, wark, rp, ts, sz):
|
rd, fn = self._u8(rd, fn)
|
||||||
v = (wark, ts, sz, rp)
|
v = (wark, ts, sz, rd, fn)
|
||||||
db.execute("insert into up values (?,?,?,?)", v)
|
db.execute(sql, v)
|
||||||
|
|
||||||
def _get_wark(self, cj):
|
def _get_wark(self, cj):
|
||||||
if len(cj["name"]) > 1024 or len(cj["hash"]) > 512 * 1024: # 16TiB
|
if len(cj["name"]) > 1024 or len(cj["hash"]) > 512 * 1024: # 16TiB
|
||||||
|
@ -507,36 +596,17 @@ class Up2k(object):
|
||||||
except:
|
except:
|
||||||
cj["lmod"] = int(time.time())
|
cj["lmod"] = int(time.time())
|
||||||
|
|
||||||
wark = self._wark_from_hashlist(cj["size"], cj["hash"])
|
wark = up2k_wark_from_hashlist(self.salt, cj["size"], cj["hash"])
|
||||||
return wark
|
return wark
|
||||||
|
|
||||||
def _wark_from_hashlist(self, filesize, hashes):
|
|
||||||
""" server-reproducible file identifier, independent of name or location """
|
|
||||||
ident = [self.salt, str(filesize)]
|
|
||||||
ident.extend(hashes)
|
|
||||||
ident = "\n".join(ident)
|
|
||||||
|
|
||||||
hasher = hashlib.sha512()
|
|
||||||
hasher.update(ident.encode("utf-8"))
|
|
||||||
digest = hasher.digest()[:32]
|
|
||||||
|
|
||||||
wark = base64.urlsafe_b64encode(digest)
|
|
||||||
return wark.decode("utf-8").rstrip("=")
|
|
||||||
|
|
||||||
def _hashlist_from_file(self, path):
|
def _hashlist_from_file(self, path):
|
||||||
fsz = os.path.getsize(path)
|
fsz = os.path.getsize(path)
|
||||||
csz = self._get_chunksize(fsz)
|
csz = up2k_chunksize(fsz)
|
||||||
ret = []
|
ret = []
|
||||||
last_print = time.time()
|
last_print = time.time()
|
||||||
with open(path, "rb", 512 * 1024) as f:
|
with open(path, "rb", 512 * 1024) as f:
|
||||||
while fsz > 0:
|
while fsz > 0:
|
||||||
now = time.time()
|
self.pp.msg = msg = "{} MB".format(int(fsz / 1024 / 1024))
|
||||||
td = now - last_print
|
|
||||||
if td >= 0.1:
|
|
||||||
last_print = now
|
|
||||||
msg = " {} MB \r".format(int(fsz / 1024 / 1024))
|
|
||||||
print(msg, end="", file=sys.stderr)
|
|
||||||
|
|
||||||
hashobj = hashlib.sha512()
|
hashobj = hashlib.sha512()
|
||||||
rem = min(csz, fsz)
|
rem = min(csz, fsz)
|
||||||
fsz -= rem
|
fsz -= rem
|
||||||
|
@ -599,7 +669,7 @@ class Up2k(object):
|
||||||
if rm:
|
if rm:
|
||||||
m = "dropping {} abandoned uploads in {}".format(len(rm), k)
|
m = "dropping {} abandoned uploads in {}".format(len(rm), k)
|
||||||
vis = [self._vis_job_progress(x) for x in rm]
|
vis = [self._vis_job_progress(x) for x in rm]
|
||||||
self.log("up2k", "\n".join([m] + vis))
|
self.log("\n".join([m] + vis))
|
||||||
for job in rm:
|
for job in rm:
|
||||||
del reg[job["wark"]]
|
del reg[job["wark"]]
|
||||||
try:
|
try:
|
||||||
|
@ -635,5 +705,32 @@ class Up2k(object):
|
||||||
|
|
||||||
atomic_move(path2, path)
|
atomic_move(path2, path)
|
||||||
|
|
||||||
self.log("up2k", "snap: {} |{}|".format(path, len(reg.keys())))
|
self.log("snap: {} |{}|".format(path, len(reg.keys())))
|
||||||
prev[k] = etag
|
prev[k] = etag
|
||||||
|
|
||||||
|
|
||||||
|
def up2k_chunksize(filesize):
|
||||||
|
chunksize = 1024 * 1024
|
||||||
|
stepsize = 512 * 1024
|
||||||
|
while True:
|
||||||
|
for mul in [1, 2]:
|
||||||
|
nchunks = math.ceil(filesize * 1.0 / chunksize)
|
||||||
|
if nchunks <= 256 or chunksize >= 32 * 1024 * 1024:
|
||||||
|
return chunksize
|
||||||
|
|
||||||
|
chunksize += stepsize
|
||||||
|
stepsize *= mul
|
||||||
|
|
||||||
|
|
||||||
|
def up2k_wark_from_hashlist(salt, filesize, hashes):
|
||||||
|
""" server-reproducible file identifier, independent of name or location """
|
||||||
|
ident = [salt, str(filesize)]
|
||||||
|
ident.extend(hashes)
|
||||||
|
ident = "\n".join(ident)
|
||||||
|
|
||||||
|
hasher = hashlib.sha512()
|
||||||
|
hasher.update(ident.encode("utf-8"))
|
||||||
|
digest = hasher.digest()[:32]
|
||||||
|
|
||||||
|
wark = base64.urlsafe_b64encode(digest)
|
||||||
|
return wark.decode("utf-8").rstrip("=")
|
||||||
|
|
|
@ -99,6 +99,32 @@ class Unrecv(object):
|
||||||
self.buf = buf + self.buf
|
self.buf = buf + self.buf
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressPrinter(threading.Thread):
|
||||||
|
"""
|
||||||
|
periodically print progress info without linefeeds
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.msg = None
|
||||||
|
self.end = False
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
msg = None
|
||||||
|
while not self.end:
|
||||||
|
time.sleep(0.05)
|
||||||
|
if msg == self.msg or self.end:
|
||||||
|
continue
|
||||||
|
|
||||||
|
msg = self.msg
|
||||||
|
print(" {}\033[K\r".format(msg), end="")
|
||||||
|
|
||||||
|
print("\033[K", end="")
|
||||||
|
sys.stdout.flush() # necessary on win10 even w/ stderr btw
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def ren_open(fname, *args, **kwargs):
|
def ren_open(fname, *args, **kwargs):
|
||||||
fdir = kwargs.pop("fdir", None)
|
fdir = kwargs.pop("fdir", None)
|
||||||
|
@ -146,7 +172,7 @@ def ren_open(fname, *args, **kwargs):
|
||||||
|
|
||||||
except OSError as ex_:
|
except OSError as ex_:
|
||||||
ex = ex_
|
ex = ex_
|
||||||
if ex.errno != 36:
|
if ex.errno not in [36, 63] and (not WINDOWS or ex.errno != 22):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if not b64:
|
if not b64:
|
||||||
|
@ -480,6 +506,13 @@ def sanitize_fn(fn):
|
||||||
return fn.strip()
|
return fn.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def u8safe(txt):
|
||||||
|
try:
|
||||||
|
return txt.encode("utf-8", "xmlcharrefreplace").decode("utf-8", "replace")
|
||||||
|
except:
|
||||||
|
return txt.encode("utf-8", "replace").decode("utf-8", "replace")
|
||||||
|
|
||||||
|
|
||||||
def exclude_dotfiles(filepaths):
|
def exclude_dotfiles(filepaths):
|
||||||
for fpath in filepaths:
|
for fpath in filepaths:
|
||||||
if not fpath.split("/")[-1].startswith("."):
|
if not fpath.split("/")[-1].startswith("."):
|
||||||
|
|
|
@ -39,15 +39,27 @@ body {
|
||||||
margin: 1.3em 0 0 0;
|
margin: 1.3em 0 0 0;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
}
|
}
|
||||||
|
#path #entree {
|
||||||
|
margin-left: -.7em;
|
||||||
|
}
|
||||||
|
#treetab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#files {
|
#files {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
#files tbody a {
|
#files tbody a {
|
||||||
display: block;
|
display: block;
|
||||||
padding: .3em 0;
|
padding: .3em 0;
|
||||||
}
|
}
|
||||||
a {
|
#files[ts] tbody div a {
|
||||||
|
color: #f5a;
|
||||||
|
}
|
||||||
|
a,
|
||||||
|
#files[ts] tbody div a:last-child {
|
||||||
color: #fc5;
|
color: #fc5;
|
||||||
padding: .2em;
|
padding: .2em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -156,7 +168,7 @@ a.play.act {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #333;
|
background: #333;
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
z-index:99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
#blk_play,
|
#blk_play,
|
||||||
#blk_abrt {
|
#blk_abrt {
|
||||||
|
@ -190,6 +202,7 @@ a.play.act {
|
||||||
bottom: -6em;
|
bottom: -6em;
|
||||||
height: 6em;
|
height: 6em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
z-index: 3;
|
||||||
transition: bottom 0.15s;
|
transition: bottom 0.15s;
|
||||||
}
|
}
|
||||||
#widget.open {
|
#widget.open {
|
||||||
|
@ -214,6 +227,9 @@ a.play.act {
|
||||||
75% {cursor: url(/.cpr/dd/5.png), pointer}
|
75% {cursor: url(/.cpr/dd/5.png), pointer}
|
||||||
85% {cursor: url(/.cpr/dd/1.png), pointer}
|
85% {cursor: url(/.cpr/dd/1.png), pointer}
|
||||||
}
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
100% {transform: rotate(360deg)}
|
||||||
|
}
|
||||||
#wtoggle {
|
#wtoggle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1.2em;
|
top: -1.2em;
|
||||||
|
@ -273,3 +289,207 @@ a.play.act {
|
||||||
width: calc(100% - 10.5em);
|
width: calc(100% - 10.5em);
|
||||||
background: rgba(0,0,0,0.2);
|
background: rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.opview {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.opview.act {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#ops a {
|
||||||
|
color: #fc5;
|
||||||
|
font-size: 1.5em;
|
||||||
|
padding: .25em .3em;
|
||||||
|
margin: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#ops a.act {
|
||||||
|
background: #281838;
|
||||||
|
border-radius: 0 0 .2em .2em;
|
||||||
|
border-bottom: .3em solid #d90;
|
||||||
|
box-shadow: 0 -.15em .2em #000 inset;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
}
|
||||||
|
#ops i {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
#ops i:before {
|
||||||
|
content: 'x';
|
||||||
|
color: #282828;
|
||||||
|
text-shadow: 0 0 .08em #01a7e1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#ops i:after {
|
||||||
|
content: 'x';
|
||||||
|
color: #282828;
|
||||||
|
text-shadow: 0 0 .08em #ff3f1a;
|
||||||
|
margin-left: -.35em;
|
||||||
|
font-size: 1.05em;
|
||||||
|
}
|
||||||
|
#ops,
|
||||||
|
.opbox {
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
box-shadow: 0 0 1em #222 inset;
|
||||||
|
}
|
||||||
|
#ops {
|
||||||
|
display: none;
|
||||||
|
background: #333;
|
||||||
|
margin: 1.7em 1.5em 0 1.5em;
|
||||||
|
padding: .3em .6em;
|
||||||
|
border-radius: .3em;
|
||||||
|
border-width: .15em 0;
|
||||||
|
}
|
||||||
|
.opbox {
|
||||||
|
background: #2d2d2d;
|
||||||
|
margin: 1.5em 0 0 0;
|
||||||
|
padding: .5em;
|
||||||
|
border-radius: 0 1em 1em 0;
|
||||||
|
border-width: .15em .3em .3em 0;
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
.opbox input {
|
||||||
|
margin: .5em;
|
||||||
|
}
|
||||||
|
.opview input[type=text] {
|
||||||
|
color: #fff;
|
||||||
|
background: #383838;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 0 .3em #222;
|
||||||
|
border-bottom: 1px solid #fc5;
|
||||||
|
border-radius: .2em;
|
||||||
|
padding: .2em .3em;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]+label {
|
||||||
|
color: #f5a;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked+label {
|
||||||
|
color: #fc5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#op_search table {
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
box-shadow: 0 0 1em #222 inset;
|
||||||
|
background: #2d2d2d;
|
||||||
|
border-radius: .4em;
|
||||||
|
margin: 1.4em;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 0 .5em .5em 0;
|
||||||
|
}
|
||||||
|
#srch_form td {
|
||||||
|
padding: .6em .6em;
|
||||||
|
}
|
||||||
|
#op_search input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#srch_q {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
#files td div span {
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 .4em;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#files td div a:hover {
|
||||||
|
background: #444;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#files td div a {
|
||||||
|
display: table-cell;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#files td div a:last-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#files td div {
|
||||||
|
display: table;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#files td div a:last-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#tree,
|
||||||
|
#treefiles {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
#tree {
|
||||||
|
padding-top: 2em;
|
||||||
|
}
|
||||||
|
#detree {
|
||||||
|
padding: .3em .5em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
#treefiles #files tbody {
|
||||||
|
border-radius: 0 .7em 0 .7em;
|
||||||
|
}
|
||||||
|
#treefiles #files thead th:nth-child(1) {
|
||||||
|
border-radius: .7em 0 0 0;
|
||||||
|
}
|
||||||
|
#tree li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
#tree ul,
|
||||||
|
#tree li {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#tree ul {
|
||||||
|
border-left: .2em solid #444;
|
||||||
|
}
|
||||||
|
#tree li {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
#tree a.hl {
|
||||||
|
color: #400;
|
||||||
|
background: #fc4;
|
||||||
|
border-radius: .3em;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
#tree li {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#tree a {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#tree a+a {
|
||||||
|
width: calc(100% - 2em);
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
#tree a+a:hover {
|
||||||
|
background: #222;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#treeul {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
left: -1.7em;
|
||||||
|
}
|
||||||
|
#treeul:hover {
|
||||||
|
z-index: 2;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
#treeul:hover a+a {
|
||||||
|
width: auto;
|
||||||
|
min-width: calc(100% - 2em);
|
||||||
|
}
|
||||||
|
#treeul a:first-child {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
}
|
||||||
|
#treefiles {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
#tree:hover+#treefiles {
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
.dumb_loader_thing {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1em;
|
||||||
|
font-size: 3em;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
|
@ -13,11 +13,33 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="ops">
|
||||||
|
<a href="#" data-dest="">---</a>
|
||||||
|
{%- if can_read %}
|
||||||
|
<a href="#" data-dest="search">🔎</a>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if can_upload %}
|
||||||
|
<a href="#" data-dest="up2k">🚀</a>
|
||||||
|
<a href="#" data-dest="bup">🎈</a>
|
||||||
|
<a href="#" data-dest="mkdir">📂</a>
|
||||||
|
<a href="#" data-dest="new_md">📝</a>
|
||||||
|
<a href="#" data-dest="msg">📟</a>
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{%- if can_read %}
|
||||||
|
<div id="op_search" class="opview">
|
||||||
|
<table id="srch_form"></table>
|
||||||
|
<div id="srch_q"></div>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
{%- if can_upload %}
|
{%- if can_upload %}
|
||||||
{%- include 'upload.html' %}
|
{%- include 'upload.html' %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h1 id="path">
|
<h1 id="path">
|
||||||
|
<a href="#" id="entree">🌲</a>
|
||||||
{%- for n in vpnodes %}
|
{%- for n in vpnodes %}
|
||||||
<a href="/{{ n[0] }}">{{ n[1] }}</a>
|
<a href="/{{ n[0] }}">{{ n[1] }}</a>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
@ -28,6 +50,18 @@
|
||||||
<div id="pro" class="logue">{{ prologue }}</div>
|
<div id="pro" class="logue">{{ prologue }}</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
<table id="treetab">
|
||||||
|
<tr>
|
||||||
|
<td id="tree">
|
||||||
|
<a href="#" id="detree">🍞...</a>
|
||||||
|
<ul id="treeul">
|
||||||
|
<div class="dumb_loader_thing">🌲</div>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td id="treefiles"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<table id="files">
|
<table id="files">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -8,6 +8,8 @@ function dbg(msg) {
|
||||||
|
|
||||||
function ev(e) {
|
function ev(e) {
|
||||||
e = e || window.event;
|
e = e || window.event;
|
||||||
|
if (!e)
|
||||||
|
return;
|
||||||
|
|
||||||
if (e.preventDefault)
|
if (e.preventDefault)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -23,7 +25,7 @@ makeSortable(ebi('files'));
|
||||||
|
|
||||||
|
|
||||||
// extract songs + add play column
|
// extract songs + add play column
|
||||||
var mp = (function () {
|
function init_mp() {
|
||||||
var tracks = [];
|
var tracks = [];
|
||||||
var ret = {
|
var ret = {
|
||||||
'au': null,
|
'au': null,
|
||||||
|
@ -37,7 +39,8 @@ var mp = (function () {
|
||||||
var trs = ebi('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');
|
||||||
|
link = link[link.length - 1];
|
||||||
var url = link.getAttribute('href');
|
var url = link.getAttribute('href');
|
||||||
|
|
||||||
var m = re_audio.exec(url);
|
var m = re_audio.exec(url);
|
||||||
|
@ -71,7 +74,8 @@ var mp = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
})();
|
}
|
||||||
|
var mp = init_mp();
|
||||||
|
|
||||||
|
|
||||||
// toggle player widget
|
// toggle player widget
|
||||||
|
@ -466,7 +470,13 @@ function play(tid, call_depth) {
|
||||||
|
|
||||||
var o = ebi(oid);
|
var o = ebi(oid);
|
||||||
o.setAttribute('id', 'thx_js');
|
o.setAttribute('id', 'thx_js');
|
||||||
location.hash = oid;
|
if (window.history && history.replaceState) {
|
||||||
|
var nurl = (document.location + '').split('#')[0] + '#' + oid;
|
||||||
|
history.replaceState(ebi('files').tBodies[0].innerHTML, nurl, nurl);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.location.hash = oid;
|
||||||
|
}
|
||||||
o.setAttribute('id', oid);
|
o.setAttribute('id', oid);
|
||||||
|
|
||||||
pbar.drawbuf();
|
pbar.drawbuf();
|
||||||
|
@ -561,3 +571,384 @@ function autoplay_blocked() {
|
||||||
|
|
||||||
|
|
||||||
//widget.open();
|
//widget.open();
|
||||||
|
|
||||||
|
|
||||||
|
// search
|
||||||
|
(function () {
|
||||||
|
var sconf = [
|
||||||
|
["size",
|
||||||
|
["szl", "sz_min", "minimum MiB", ""],
|
||||||
|
["szu", "sz_max", "maximum MiB", ""]
|
||||||
|
],
|
||||||
|
["date",
|
||||||
|
["dtl", "dt_min", "min. iso8601", ""],
|
||||||
|
["dtu", "dt_max", "max. iso8601", ""]
|
||||||
|
],
|
||||||
|
["path",
|
||||||
|
["pn", "path_no", "path NOT contains", "30"],
|
||||||
|
["py", "path_yes", "path contains", "30"]
|
||||||
|
],
|
||||||
|
["name",
|
||||||
|
["nn", "name_no", "name NOT contains", "30"],
|
||||||
|
["ny", "name_yes", "name contains", "30"]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
var html = [];
|
||||||
|
for (var a = 0; a < sconf.length; a++) {
|
||||||
|
html.push('<tr><td><br />' + sconf[a][0] + '</td>');
|
||||||
|
for (var b = 1; b < 3; b++) {
|
||||||
|
var hn = "srch_" + sconf[a][b][0];
|
||||||
|
html.push(
|
||||||
|
'<td><input id="' + hn + 'c" type="checkbox">\n' +
|
||||||
|
'<label for="' + hn + 'c">' + sconf[a][b][2] + '</label>\n' +
|
||||||
|
'<br /><input id="' + hn + 'v" type="text" size="' + sconf[a][b][3] +
|
||||||
|
'" name="' + sconf[a][b][1] + '" /></td>');
|
||||||
|
}
|
||||||
|
html.push('</tr>');
|
||||||
|
}
|
||||||
|
ebi('srch_form').innerHTML = html.join('\n');
|
||||||
|
|
||||||
|
var o = document.querySelectorAll('#op_search input[type="text"]');
|
||||||
|
for (var a = 0; a < o.length; a++) {
|
||||||
|
o[a].oninput = ev_search_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
var search_timeout;
|
||||||
|
|
||||||
|
function ev_search_input() {
|
||||||
|
var v = this.value;
|
||||||
|
var chk = ebi(this.getAttribute('id').slice(0, -1) + 'c');
|
||||||
|
chk.checked = ((v + '').length > 0);
|
||||||
|
clearTimeout(search_timeout);
|
||||||
|
search_timeout = setTimeout(do_search, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_search() {
|
||||||
|
clearTimeout(search_timeout);
|
||||||
|
var params = {};
|
||||||
|
var o = document.querySelectorAll('#op_search input[type="text"]');
|
||||||
|
for (var a = 0; a < o.length; a++) {
|
||||||
|
var chk = ebi(o[a].getAttribute('id').slice(0, -1) + 'c');
|
||||||
|
if (!chk.checked)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
params[o[a].getAttribute('name')] = o[a].value;
|
||||||
|
}
|
||||||
|
// ebi('srch_q').textContent = JSON.stringify(params, null, 4);
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/?srch', true);
|
||||||
|
xhr.onreadystatechange = xhr_search_results;
|
||||||
|
xhr.ts = new Date().getTime();
|
||||||
|
xhr.send(JSON.stringify(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
function xhr_search_results() {
|
||||||
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.status !== 200) {
|
||||||
|
alert('ah fug\n' + this.status + ": " + this.responseText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ofiles = ebi('files');
|
||||||
|
if (ofiles.getAttribute('ts') > this.ts)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ebi('path').style.display = 'none';
|
||||||
|
ebi('tree').style.display = 'none';
|
||||||
|
|
||||||
|
var html = [];
|
||||||
|
var res = JSON.parse(this.responseText);
|
||||||
|
for (var a = 0; a < res.length; a++) {
|
||||||
|
var r = res[a],
|
||||||
|
ts = parseInt(r.ts),
|
||||||
|
sz = esc(r.sz + ''),
|
||||||
|
rp = esc(r.rp + ''),
|
||||||
|
ext = rp.lastIndexOf('.') > 0 ? rp.split('.').slice(-1)[0] : '%',
|
||||||
|
links = linksplit(rp);
|
||||||
|
|
||||||
|
ts = new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
|
||||||
|
|
||||||
|
if (ext.length > 8)
|
||||||
|
ext = '%';
|
||||||
|
|
||||||
|
links = links.join('');
|
||||||
|
html.push('<tr><td>-</td><td><div>' + links + '</div></td><td>' + sz +
|
||||||
|
'</td><td>' + ext + '</td><td>' + ts + '</td></tr>');
|
||||||
|
}
|
||||||
|
|
||||||
|
ofiles.tBodies[0].innerHTML = html.join('\n');
|
||||||
|
ofiles.setAttribute("ts", this.ts);
|
||||||
|
reload_browser();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
// tree
|
||||||
|
(function () {
|
||||||
|
var treedata = null;
|
||||||
|
|
||||||
|
function entree(e) {
|
||||||
|
ev(e);
|
||||||
|
ebi('path').style.display = 'none';
|
||||||
|
|
||||||
|
var treetab = ebi('treetab');
|
||||||
|
var treefiles = ebi('treefiles');
|
||||||
|
|
||||||
|
treetab.style.display = 'table';
|
||||||
|
|
||||||
|
var pro = ebi('pro');
|
||||||
|
if (pro)
|
||||||
|
treefiles.appendChild(pro);
|
||||||
|
|
||||||
|
treefiles.appendChild(ebi('files'));
|
||||||
|
|
||||||
|
var epi = ebi('epi');
|
||||||
|
if (epi)
|
||||||
|
treefiles.appendChild(epi);
|
||||||
|
|
||||||
|
localStorage.setItem('entreed', 'tree');
|
||||||
|
get_tree("", get_vpath());
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_tree(top, dst) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.top = top;
|
||||||
|
xhr.dst = dst;
|
||||||
|
xhr.open('GET', dst + '?tree=' + top, true);
|
||||||
|
xhr.onreadystatechange = recvtree;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function recvtree() {
|
||||||
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.status !== 200) {
|
||||||
|
alert('ah fug\n' + this.status + ": " + this.responseText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var top = this.top == '.' ? this.dst : this.top,
|
||||||
|
name = top.split('/').slice(-2)[0],
|
||||||
|
rtop = top.replace(/^\/+/, "");
|
||||||
|
|
||||||
|
try {
|
||||||
|
var res = JSON.parse(this.responseText);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var html = parsetree(res, rtop);
|
||||||
|
if (!this.top) {
|
||||||
|
html = '<li><a href="#">-</a><a href="/">[root]</a>\n<ul>' + html;
|
||||||
|
if (!ebi('treeul').getElementsByTagName('li').length)
|
||||||
|
ebi('treeul').innerHTML = html + '</ul></li>';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
html = '<a href="#">-</a><a href="' +
|
||||||
|
esc(top) + '">' + esc(name) + "</a>" + html;
|
||||||
|
|
||||||
|
var links = document.querySelectorAll('#tree a+a');
|
||||||
|
for (var a = 0, aa = links.length; a < aa; a++) {
|
||||||
|
if (links[a].getAttribute('href') == top) {
|
||||||
|
var o = links[a].parentNode;
|
||||||
|
if (!o.getElementsByTagName('li').length)
|
||||||
|
o.innerHTML = html;
|
||||||
|
//else
|
||||||
|
// links[a].previousSibling.textContent = '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.querySelector('#treeul>li>a+a').textContent = '[root]';
|
||||||
|
reload_tree();
|
||||||
|
|
||||||
|
var q = '#tree';
|
||||||
|
var nq = 0;
|
||||||
|
while (true) {
|
||||||
|
nq++;
|
||||||
|
q += '>ul>li';
|
||||||
|
if (!document.querySelector(q))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ebi('treeul').style.width = (24 + nq) + 'em';
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload_tree() {
|
||||||
|
var cdir = get_vpath();
|
||||||
|
var links = document.querySelectorAll('#tree a+a');
|
||||||
|
for (var a = 0, aa = links.length; a < aa; a++) {
|
||||||
|
var href = links[a].getAttribute('href');
|
||||||
|
links[a].setAttribute('class', href == cdir ? 'hl' : '');
|
||||||
|
links[a].onclick = treego;
|
||||||
|
}
|
||||||
|
links = document.querySelectorAll('#tree li>a:first-child');
|
||||||
|
for (var a = 0, aa = links.length; a < aa; a++) {
|
||||||
|
links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href'));
|
||||||
|
links[a].onclick = treegrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function treego(e) {
|
||||||
|
ev(e);
|
||||||
|
if (this.getAttribute('class') == 'hl') {
|
||||||
|
treegrow.call(this.previousSibling, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.top = this.getAttribute('href');
|
||||||
|
xhr.open('GET', xhr.top + '?ls', true);
|
||||||
|
xhr.onreadystatechange = recvls;
|
||||||
|
xhr.send();
|
||||||
|
get_tree('.', xhr.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
function treegrow(e) {
|
||||||
|
ev(e);
|
||||||
|
if (this.textContent == '-') {
|
||||||
|
while (this.nextSibling.nextSibling) {
|
||||||
|
var rm = this.nextSibling.nextSibling;
|
||||||
|
rm.parentNode.removeChild(rm);
|
||||||
|
this.textContent = '+';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dst = this.getAttribute('dst');
|
||||||
|
get_tree('.', dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
function recvls() {
|
||||||
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.status !== 200) {
|
||||||
|
alert('ah fug\n' + this.status + ": " + this.responseText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var res = JSON.parse(this.responseText);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
window.location = this.top;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var top = this.top;
|
||||||
|
var html = [];
|
||||||
|
for (var a = 0; a < res.length; a++) {
|
||||||
|
var ln = '<tr><td>' + res[a][0] + '</td><td><a href="' +
|
||||||
|
top + res[a][1] + '">' + res[a][2] + '</a></td>';
|
||||||
|
|
||||||
|
for (var b = 3; b < res[a].length; b++) {
|
||||||
|
ln += '<td>' + res[a][b] + '</td>';
|
||||||
|
}
|
||||||
|
html.push(ln + '</tr>')
|
||||||
|
}
|
||||||
|
html = html.join('\n');
|
||||||
|
ebi('files').tBodies[0].innerHTML = html;
|
||||||
|
history.pushState(html, this.top, this.top);
|
||||||
|
|
||||||
|
var o = ebi('pro');
|
||||||
|
if (o) o.parentNode.removeChild(o);
|
||||||
|
|
||||||
|
o = ebi('epi');
|
||||||
|
if (o) o.parentNode.removeChild(o);
|
||||||
|
|
||||||
|
reload_tree();
|
||||||
|
reload_browser();
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsetree(res, top) {
|
||||||
|
var ret = '';
|
||||||
|
for (var a = 0; a < res.a.length; a++) {
|
||||||
|
res['k' + res.a[a]] = 0;
|
||||||
|
}
|
||||||
|
delete res['a'];
|
||||||
|
var keys = Object.keys(res);
|
||||||
|
keys.sort();
|
||||||
|
for (var a = 0; a < keys.length; a++) {
|
||||||
|
var kk = keys[a],
|
||||||
|
k = kk.slice(1),
|
||||||
|
url = '/' + (top ? top + k : k) + '/',
|
||||||
|
ek = esc(k),
|
||||||
|
sym = res[kk] ? '-' : '+',
|
||||||
|
link = '<a href="#">' + sym + '</a><a href="' +
|
||||||
|
esc(url) + '">' + ek + '</a>';
|
||||||
|
|
||||||
|
if (res[kk]) {
|
||||||
|
var subtree = parsetree(res[kk], url.slice(1));
|
||||||
|
ret += '<li>' + link + '\n<ul>\n' + subtree + '</ul></li>\n';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret += '<li>' + link + '</li>\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detree(e) {
|
||||||
|
ev(e);
|
||||||
|
var treetab = ebi('treetab');
|
||||||
|
|
||||||
|
var pro = ebi('pro');
|
||||||
|
if (pro)
|
||||||
|
treetab.parentNode.insertBefore(pro, treetab);
|
||||||
|
|
||||||
|
treetab.parentNode.insertBefore(ebi('files'), treetab.nextSibling);
|
||||||
|
|
||||||
|
var epi = ebi('epi');
|
||||||
|
if (epi)
|
||||||
|
treetab.parentNode.insertBefore(epi, ebi('files').nextSibling);
|
||||||
|
|
||||||
|
ebi('path').style.display = 'inline-block';
|
||||||
|
treetab.style.display = 'none';
|
||||||
|
|
||||||
|
localStorage.setItem('entreed', 'na');
|
||||||
|
}
|
||||||
|
|
||||||
|
ebi('entree').onclick = entree;
|
||||||
|
ebi('detree').onclick = detree;
|
||||||
|
if (window.localStorage && localStorage.getItem('entreed') == 'tree')
|
||||||
|
entree();
|
||||||
|
|
||||||
|
window.onpopstate = function (e) {
|
||||||
|
console.log(e.url + ' ,, ' + ((e.state + '').slice(0, 64)));
|
||||||
|
if (e.state) {
|
||||||
|
ebi('files').tBodies[0].innerHTML = e.state;
|
||||||
|
reload_tree();
|
||||||
|
reload_browser();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window.history && history.pushState) {
|
||||||
|
var u = get_vpath();
|
||||||
|
history.replaceState(ebi('files').tBodies[0].innerHTML, u, u);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function reload_browser() {
|
||||||
|
makeSortable(ebi('files'));
|
||||||
|
|
||||||
|
var parts = get_vpath().split('/');
|
||||||
|
var rm = document.querySelectorAll('#path>a+a+a');
|
||||||
|
for (a = rm.length - 1; a >= 0; a--)
|
||||||
|
rm[a].parentNode.removeChild(rm[a]);
|
||||||
|
|
||||||
|
var link = '/';
|
||||||
|
for (var a = 1; a < parts.length - 1; a++) {
|
||||||
|
link += parts[a] + '/';
|
||||||
|
var o = document.createElement('a');
|
||||||
|
o.setAttribute('href', link);
|
||||||
|
o.innerHTML = parts[a];
|
||||||
|
ebi('path').appendChild(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mp && mp.au) {
|
||||||
|
mp.au.pause();
|
||||||
|
mp.au = null;
|
||||||
|
}
|
||||||
|
widget.close();
|
||||||
|
mp = init_mp();
|
||||||
|
}
|
||||||
|
|
|
@ -124,5 +124,3 @@ html.dark #toast {
|
||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s ease-in-out;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
# mt {opacity: .5;top:1px}
|
|
||||||
|
|
|
@ -3,51 +3,6 @@
|
||||||
window.onerror = vis_exh;
|
window.onerror = vis_exh;
|
||||||
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
var ops = document.querySelectorAll('#ops>a');
|
|
||||||
for (var a = 0; a < ops.length; a++) {
|
|
||||||
ops[a].onclick = opclick;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
function opclick(ev) {
|
|
||||||
if (ev) //ie
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
var dest = this.getAttribute('data-dest');
|
|
||||||
goto(dest);
|
|
||||||
|
|
||||||
// writing a blank value makes ie8 segfault w
|
|
||||||
if (window.localStorage)
|
|
||||||
localStorage.setItem('opmode', dest || '.');
|
|
||||||
|
|
||||||
var input = document.querySelector('.opview.act input:not([type="hidden"])')
|
|
||||||
if (input)
|
|
||||||
input.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function goto(dest) {
|
|
||||||
var obj = document.querySelectorAll('.opview.act');
|
|
||||||
for (var a = obj.length - 1; a >= 0; a--)
|
|
||||||
obj[a].classList.remove('act');
|
|
||||||
|
|
||||||
obj = document.querySelectorAll('#ops>a');
|
|
||||||
for (var a = obj.length - 1; a >= 0; a--)
|
|
||||||
obj[a].classList.remove('act');
|
|
||||||
|
|
||||||
if (dest) {
|
|
||||||
ebi('op_' + dest).classList.add('act');
|
|
||||||
document.querySelector('#ops>a[data-dest=' + dest + ']').classList.add('act');
|
|
||||||
|
|
||||||
var fn = window['goto_' + dest];
|
|
||||||
if (fn)
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function goto_up2k() {
|
function goto_up2k() {
|
||||||
if (up2k === false)
|
if (up2k === false)
|
||||||
return goto('bup');
|
return goto('bup');
|
||||||
|
@ -59,17 +14,6 @@ function goto_up2k() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
goto();
|
|
||||||
if (window.localStorage) {
|
|
||||||
var op = localStorage.getItem('opmode');
|
|
||||||
if (op !== null && op !== '.')
|
|
||||||
goto(op);
|
|
||||||
}
|
|
||||||
ebi('ops').style.display = 'block';
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
// chrome requires https to use crypto.subtle,
|
// chrome requires https to use crypto.subtle,
|
||||||
// usually it's undefined but some chromes throw on invoke
|
// usually it's undefined but some chromes throw on invoke
|
||||||
var up2k = null;
|
var up2k = null;
|
||||||
|
@ -255,7 +199,7 @@ function up2k_init(have_crypto) {
|
||||||
// handle user intent to use the basic uploader instead
|
// handle user intent to use the basic uploader instead
|
||||||
ebi('u2nope').onclick = function (e) {
|
ebi('u2nope').onclick = function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setmsg('');
|
setmsg();
|
||||||
goto('bup');
|
goto('bup');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -279,13 +223,17 @@ function up2k_init(have_crypto) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bcfg_get(name, defval) {
|
function bcfg_get(name, defval) {
|
||||||
|
var o = ebi(name);
|
||||||
|
if (!o)
|
||||||
|
return defval;
|
||||||
|
|
||||||
var val = localStorage.getItem(name);
|
var val = localStorage.getItem(name);
|
||||||
if (val === null)
|
if (val === null)
|
||||||
val = defval;
|
val = defval;
|
||||||
else
|
else
|
||||||
val = (val == '1');
|
val = (val == '1');
|
||||||
|
|
||||||
ebi(name).checked = val;
|
o.checked = val;
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +241,10 @@ function up2k_init(have_crypto) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
name, val ? '1' : '0');
|
name, val ? '1' : '0');
|
||||||
|
|
||||||
ebi(name).checked = val;
|
var o = ebi(name);
|
||||||
|
if (o)
|
||||||
|
o.checked = val;
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,6 +252,7 @@ function up2k_init(have_crypto) {
|
||||||
var multitask = bcfg_get('multitask', true);
|
var multitask = bcfg_get('multitask', true);
|
||||||
var ask_up = bcfg_get('ask_up', true);
|
var ask_up = bcfg_get('ask_up', true);
|
||||||
var flag_en = bcfg_get('flag_en', false);
|
var flag_en = bcfg_get('flag_en', false);
|
||||||
|
var fsearch = bcfg_get('fsearch', false);
|
||||||
|
|
||||||
var col_hashing = '#00bbff';
|
var col_hashing = '#00bbff';
|
||||||
var col_hashed = '#004466';
|
var col_hashed = '#004466';
|
||||||
|
@ -334,6 +286,7 @@ function up2k_init(have_crypto) {
|
||||||
|
|
||||||
var flag = false;
|
var flag = false;
|
||||||
apply_flag_cfg();
|
apply_flag_cfg();
|
||||||
|
apply_fsearch_cfg();
|
||||||
|
|
||||||
function nav() {
|
function nav() {
|
||||||
ebi('file' + fdom_ctr).click();
|
ebi('file' + fdom_ctr).click();
|
||||||
|
@ -404,7 +357,7 @@ function up2k_init(have_crypto) {
|
||||||
for (var a = 0; a < good_files.length; a++)
|
for (var a = 0; a < good_files.length; a++)
|
||||||
msg.push(good_files[a].name);
|
msg.push(good_files[a].name);
|
||||||
|
|
||||||
if (ask_up && !confirm(msg.join('\n')))
|
if (ask_up && !fsearch && !confirm(msg.join('\n')))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (var a = 0; a < good_files.length; a++) {
|
for (var a = 0; a < good_files.length; a++) {
|
||||||
|
@ -795,6 +748,34 @@ function up2k_init(have_crypto) {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
var response = JSON.parse(xhr.responseText);
|
var response = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
|
if (!response.name) {
|
||||||
|
var msg = '';
|
||||||
|
var smsg = '';
|
||||||
|
if (!response || !response.length) {
|
||||||
|
msg = 'not found on server';
|
||||||
|
smsg = '404';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
smsg = 'found';
|
||||||
|
var hit = response[0],
|
||||||
|
links = linksplit(hit.rp),
|
||||||
|
msg = links.join(''),
|
||||||
|
tr = new Date(hit.ts * 1000).toISOString().replace("T", " ").slice(0, -5),
|
||||||
|
tu = new Date(t.lmod * 1000).toISOString().replace("T", " ").slice(0, -5),
|
||||||
|
diff = parseInt(t.lmod) - parseInt(hit.ts),
|
||||||
|
cdiff = (Math.abs(diff) <= 2) ? '3c0' : 'f0b',
|
||||||
|
sdiff = '<span style="color:#' + cdiff + '">diff ' + diff;
|
||||||
|
|
||||||
|
msg += '<br /><small>' + tr + ' (srv), ' + tu + ' (You), ' + sdiff + '</span></span>';
|
||||||
|
}
|
||||||
|
ebi('f{0}p'.format(t.n)).innerHTML = msg;
|
||||||
|
ebi('f{0}t'.format(t.n)).innerHTML = smsg;
|
||||||
|
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||||
|
st.bytes.uploaded += t.size;
|
||||||
|
tasker();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -867,14 +848,19 @@ function up2k_init(have_crypto) {
|
||||||
"no further information"));
|
"no further information"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.open('POST', post_url + 'handshake.php', true);
|
|
||||||
xhr.responseType = 'text';
|
var req = {
|
||||||
xhr.send(JSON.stringify({
|
|
||||||
"name": t.name,
|
"name": t.name,
|
||||||
"size": t.size,
|
"size": t.size,
|
||||||
"lmod": t.lmod,
|
"lmod": t.lmod,
|
||||||
"hash": t.hash
|
"hash": t.hash
|
||||||
}));
|
};
|
||||||
|
if (fsearch)
|
||||||
|
req.srch = 1;
|
||||||
|
|
||||||
|
xhr.open('POST', post_url + 'handshake.php', true);
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
xhr.send(JSON.stringify(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
/////
|
/////
|
||||||
|
@ -966,6 +952,46 @@ function up2k_init(have_crypto) {
|
||||||
/// config ui
|
/// config ui
|
||||||
//
|
//
|
||||||
|
|
||||||
|
function onresize(ev) {
|
||||||
|
var bar = ebi('ops'),
|
||||||
|
wpx = innerWidth,
|
||||||
|
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
||||||
|
wem = wpx * 1.0 / fpx,
|
||||||
|
wide = wem > 54,
|
||||||
|
parent = ebi(wide ? 'u2btn_cw' : 'u2btn_ct'),
|
||||||
|
btn = ebi('u2btn');
|
||||||
|
|
||||||
|
//console.log([wpx, fpx, wem]);
|
||||||
|
if (btn.parentNode !== parent) {
|
||||||
|
parent.appendChild(btn);
|
||||||
|
ebi('u2conf').setAttribute('class', wide ? 'has_btn' : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.onresize = onresize;
|
||||||
|
onresize();
|
||||||
|
|
||||||
|
function desc_show(ev) {
|
||||||
|
var msg = this.getAttribute('alt');
|
||||||
|
msg = msg.replace(/\$N/g, "<br />");
|
||||||
|
var cdesc = ebi('u2cdesc');
|
||||||
|
cdesc.innerHTML = msg;
|
||||||
|
cdesc.setAttribute('class', 'show');
|
||||||
|
}
|
||||||
|
function desc_hide(ev) {
|
||||||
|
ebi('u2cdesc').setAttribute('class', '');
|
||||||
|
}
|
||||||
|
var o = document.querySelectorAll('#u2conf *[alt]');
|
||||||
|
for (var a = o.length - 1; a >= 0; a--) {
|
||||||
|
o[a].parentNode.getElementsByTagName('input')[0].setAttribute('alt', o[a].getAttribute('alt'));
|
||||||
|
}
|
||||||
|
var o = document.querySelectorAll('#u2conf *[alt]');
|
||||||
|
for (var a = 0; a < o.length; a++) {
|
||||||
|
o[a].onfocus = desc_show;
|
||||||
|
o[a].onblur = desc_hide;
|
||||||
|
o[a].onmouseenter = desc_show;
|
||||||
|
o[a].onmouseleave = desc_hide;
|
||||||
|
}
|
||||||
|
|
||||||
function bumpthread(dir) {
|
function bumpthread(dir) {
|
||||||
try {
|
try {
|
||||||
dir.stopPropagation();
|
dir.stopPropagation();
|
||||||
|
@ -1007,6 +1033,21 @@ function up2k_init(have_crypto) {
|
||||||
bcfg_set('ask_up', ask_up);
|
bcfg_set('ask_up', ask_up);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tgl_fsearch() {
|
||||||
|
fsearch = !fsearch;
|
||||||
|
bcfg_set('fsearch', fsearch);
|
||||||
|
apply_fsearch_cfg();
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply_fsearch_cfg() {
|
||||||
|
var ks = ['multitask', 'ask_up'];
|
||||||
|
for (var a = 0; a < ks.length; a++) {
|
||||||
|
var lbl = document.querySelector('label[for="' + ks[a] + '"]');
|
||||||
|
lbl.setAttribute('class', fsearch ? 'gray' : '');
|
||||||
|
}
|
||||||
|
ebi('u2tab').setAttribute('class', fsearch ? 'srch' : '');
|
||||||
|
}
|
||||||
|
|
||||||
function tgl_flag_en() {
|
function tgl_flag_en() {
|
||||||
flag_en = !flag_en;
|
flag_en = !flag_en;
|
||||||
bcfg_set('flag_en', flag_en);
|
bcfg_set('flag_en', flag_en);
|
||||||
|
@ -1047,6 +1088,9 @@ function up2k_init(have_crypto) {
|
||||||
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
||||||
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
||||||
ebi('flag_en').addEventListener('click', tgl_flag_en, false);
|
ebi('flag_en').addEventListener('click', tgl_flag_en, false);
|
||||||
|
var o = ebi('fsearch');
|
||||||
|
if (o)
|
||||||
|
o.addEventListener('click', tgl_fsearch, false);
|
||||||
|
|
||||||
var nodes = ebi('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--)
|
||||||
|
|
|
@ -1,92 +1,4 @@
|
||||||
.opview {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.opview.act {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
#ops a {
|
|
||||||
color: #fc5;
|
|
||||||
font-size: 1.5em;
|
|
||||||
padding: 0 .3em;
|
|
||||||
margin: 0;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
#ops a.act {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
#ops a+a:after,
|
|
||||||
#ops a:first-child:after {
|
|
||||||
content: 'x';
|
|
||||||
color: #282828;
|
|
||||||
text-shadow: 0 0 .08em #01a7e1;
|
|
||||||
margin-left: .3em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
#ops a+a:before {
|
|
||||||
content: 'x';
|
|
||||||
color: #282828;
|
|
||||||
text-shadow: 0 0 .08em #ff3f1a;
|
|
||||||
margin-right: .3em;
|
|
||||||
margin-left: -.3em;
|
|
||||||
}
|
|
||||||
#ops a:last-child:after {
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
#ops a.act:before,
|
|
||||||
#ops a.act:after {
|
|
||||||
text-decoration: none !important;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
#ops i {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
#ops i:before {
|
|
||||||
content: 'x';
|
|
||||||
color: #282828;
|
|
||||||
text-shadow: 0 0 .08em #01a7e1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
#ops i:after {
|
|
||||||
content: 'x';
|
|
||||||
color: #282828;
|
|
||||||
text-shadow: 0 0 .08em #ff3f1a;
|
|
||||||
margin-left: -.35em;
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
#ops,
|
|
||||||
.opbox {
|
|
||||||
border: 1px solid #3a3a3a;
|
|
||||||
box-shadow: 0 0 1em #222 inset;
|
|
||||||
}
|
|
||||||
#ops {
|
|
||||||
display: none;
|
|
||||||
background: #333;
|
|
||||||
margin: 1.7em 1.5em 0 1.5em;
|
|
||||||
padding: .3em .6em;
|
|
||||||
border-radius: .3em;
|
|
||||||
border-width: .15em 0;
|
|
||||||
}
|
|
||||||
.opbox {
|
|
||||||
background: #2d2d2d;
|
|
||||||
margin: 1.5em 0 0 0;
|
|
||||||
padding: .5em;
|
|
||||||
border-radius: 0 1em 1em 0;
|
|
||||||
border-width: .15em .3em .3em 0;
|
|
||||||
max-width: 40em;
|
|
||||||
}
|
|
||||||
.opbox input {
|
|
||||||
margin: .5em;
|
|
||||||
}
|
|
||||||
.opbox input[type=text] {
|
|
||||||
color: #fff;
|
|
||||||
background: #383838;
|
|
||||||
border: none;
|
|
||||||
box-shadow: 0 0 .3em #222;
|
|
||||||
border-bottom: 1px solid #fc5;
|
|
||||||
border-radius: .2em;
|
|
||||||
padding: .2em .3em;
|
|
||||||
}
|
|
||||||
#op_up2k {
|
#op_up2k {
|
||||||
padding: 0 1em 1em 1em;
|
padding: 0 1em 1em 1em;
|
||||||
}
|
}
|
||||||
|
@ -117,17 +29,22 @@
|
||||||
background: linear-gradient(to bottom, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
background: linear-gradient(to bottom, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#489', endColorstr='#38788a', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#489', endColorstr='#38788a', GradientType=0);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
line-height: 1.5em;
|
line-height: 1.3em;
|
||||||
border: 1px solid #222;
|
border: 1px solid #222;
|
||||||
border-radius: .4em;
|
border-radius: .4em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 2em;
|
font-size: 1.5em;
|
||||||
margin: 1em auto;
|
margin: .5em auto;
|
||||||
padding: 1em 0;
|
padding: .6em 0;
|
||||||
width: 12em;
|
width: 16em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: .4em .4em 0 #111;
|
box-shadow: .4em .4em 0 #111;
|
||||||
}
|
}
|
||||||
|
#u2conf #u2btn {
|
||||||
|
margin: -1em 0;
|
||||||
|
padding: .7em 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
#u2notbtn {
|
#u2notbtn {
|
||||||
display: none;
|
display: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -142,6 +59,9 @@
|
||||||
width: calc(100% - 2em);
|
width: calc(100% - 2em);
|
||||||
max-width: 100em;
|
max-width: 100em;
|
||||||
}
|
}
|
||||||
|
#u2tab.srch {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
#u2tab td {
|
#u2tab td {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-width: 0 0px 1px 0;
|
border-width: 0 0px 1px 0;
|
||||||
|
@ -153,12 +73,19 @@
|
||||||
#u2tab td:nth-child(3) {
|
#u2tab td:nth-child(3) {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
}
|
}
|
||||||
|
#u2tab.srch td:nth-child(3) {
|
||||||
|
font-family: sans-serif;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
#u2tab tr+tr:hover td {
|
#u2tab tr+tr:hover td {
|
||||||
background: #222;
|
background: #222;
|
||||||
}
|
}
|
||||||
#u2conf {
|
#u2conf {
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
width: 26em;
|
width: 30em;
|
||||||
|
}
|
||||||
|
#u2conf.has_btn {
|
||||||
|
width: 46em;
|
||||||
}
|
}
|
||||||
#u2conf * {
|
#u2conf * {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -194,16 +121,70 @@
|
||||||
#u2conf input+a {
|
#u2conf input+a {
|
||||||
background: #d80;
|
background: #d80;
|
||||||
}
|
}
|
||||||
|
#u2conf label {
|
||||||
|
font-size: 1.6em;
|
||||||
|
width: 2em;
|
||||||
|
height: 1em;
|
||||||
|
padding: .4em 0;
|
||||||
|
display: block;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: .25em;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"] {
|
||||||
|
position: relative;
|
||||||
|
opacity: .02;
|
||||||
|
top: 2em;
|
||||||
|
}
|
||||||
#u2conf input[type="checkbox"]+label {
|
#u2conf input[type="checkbox"]+label {
|
||||||
color: #f5a;
|
position: relative;
|
||||||
|
background: #603;
|
||||||
|
border-bottom: .2em solid #a16;
|
||||||
|
box-shadow: 0 .1em .3em #a00 inset;
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]:checked+label {
|
#u2conf input[type="checkbox"]:checked+label {
|
||||||
color: #fc5;
|
background: #6a1;
|
||||||
|
border-bottom: .2em solid #efa;
|
||||||
|
box-shadow: 0 .1em .5em #0c0;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"]+label:hover {
|
||||||
|
box-shadow: 0 .1em .3em #fb0;
|
||||||
|
border-color: #fb0;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"]+label.gray {
|
||||||
|
background: #777;
|
||||||
|
border-color: #ccc;
|
||||||
|
box-shadow: none;
|
||||||
|
opacity: .25;
|
||||||
|
}
|
||||||
|
#u2cdesc {
|
||||||
|
position: absolute;
|
||||||
|
width: 34em;
|
||||||
|
left: calc(50% - 15em);
|
||||||
|
background: #222;
|
||||||
|
border: 0 solid #555;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 -2em;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
opacity: .1;
|
||||||
|
transition: all 0.14s ease-in-out;
|
||||||
|
border-radius: .4em;
|
||||||
|
box-shadow: 0 .2em .5em #222;
|
||||||
|
}
|
||||||
|
#u2cdesc.show {
|
||||||
|
padding: 1em;
|
||||||
|
height: auto;
|
||||||
|
border-width: .2em 0;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#u2foot {
|
#u2foot {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
#u2footfoot {
|
||||||
|
margin-bottom: -1em;
|
||||||
|
}
|
||||||
.prog {
|
.prog {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
@ -225,3 +206,9 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: #0a0;
|
background: #0a0;
|
||||||
}
|
}
|
||||||
|
.prog>a>span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
color: #fff;
|
||||||
|
padding-left: .2em;
|
||||||
|
}
|
|
@ -1,10 +1,3 @@
|
||||||
<div id="ops"><a
|
|
||||||
href="#" data-dest="">---</a><i></i><a
|
|
||||||
href="#" data-dest="up2k">up2k</a><i></i><a
|
|
||||||
href="#" data-dest="bup">bup</a><i></i><a
|
|
||||||
href="#" data-dest="mkdir">mkdir</a><i></i><a
|
|
||||||
href="#" data-dest="new_md">new.md</a><i></i><a
|
|
||||||
href="#" data-dest="msg">msg</a></div>
|
|
||||||
|
|
||||||
<div id="op_bup" class="opview opbox act">
|
<div id="op_bup" class="opview opbox act">
|
||||||
<div id="u2err"></div>
|
<div id="u2err"></div>
|
||||||
|
@ -34,7 +27,7 @@
|
||||||
<div id="op_msg" class="opview opbox">
|
<div id="op_msg" class="opview opbox">
|
||||||
<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="/{{ vdir }}">
|
<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="/{{ vdir }}">
|
||||||
<input type="text" name="msg" size="30">
|
<input type="text" name="msg" size="30">
|
||||||
<input type="submit" value="send">
|
<input type="submit" value="send msg">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -44,6 +37,25 @@
|
||||||
<table id="u2conf">
|
<table id="u2conf">
|
||||||
<tr>
|
<tr>
|
||||||
<td>parallel uploads</td>
|
<td>parallel uploads</td>
|
||||||
|
<td rowspan="2">
|
||||||
|
<input type="checkbox" id="multitask" />
|
||||||
|
<label for="multitask" alt="continue hashing other files while uploading">🏃</label>
|
||||||
|
</td>
|
||||||
|
<td rowspan="2">
|
||||||
|
<input type="checkbox" id="ask_up" />
|
||||||
|
<label for="ask_up" alt="ask for confirmation befofre upload starts">💭</label>
|
||||||
|
</td>
|
||||||
|
<td rowspan="2">
|
||||||
|
<input type="checkbox" id="flag_en" />
|
||||||
|
<label for="flag_en" alt="ensure only one tab is uploading at a time $N (other tabs must have this enabled too)">💤</label>
|
||||||
|
</td>
|
||||||
|
{%- if have_up2k_idx %}
|
||||||
|
<td rowspan="2">
|
||||||
|
<input type="checkbox" id="fsearch" />
|
||||||
|
<label for="fsearch" alt="don't actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)">🔎</label>
|
||||||
|
</td>
|
||||||
|
{%- endif %}
|
||||||
|
<td rowspan="2" id="u2btn_cw"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
@ -51,26 +63,18 @@
|
||||||
<input class="txtbox" id="nthread" value="2" />
|
<input class="txtbox" id="nthread" value="2" />
|
||||||
<a href="#" id="nthread_add">+</a>
|
<a href="#" id="nthread_add">+</a>
|
||||||
</td>
|
</td>
|
||||||
<td rowspan="2" style="padding-left:1.5em">
|
|
||||||
<input type="checkbox" id="multitask" />
|
|
||||||
<label for="multitask">hash<br />while<br />upping</label>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<input type="checkbox" id="ask_up" />
|
|
||||||
<label for="ask_up">ask<br />before<br />start</label>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<input type="checkbox" id="flag_en" />
|
|
||||||
<label for="flag_en">only<br />one tab<br />at once</label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div id="u2cdesc"></div>
|
||||||
|
|
||||||
<div id="u2notbtn"></div>
|
<div id="u2notbtn"></div>
|
||||||
|
|
||||||
<div id="u2btn">
|
<div id="u2btn_ct">
|
||||||
drop files here<br />
|
<div id="u2btn">
|
||||||
(or click me)
|
drop files here<br />
|
||||||
|
(or click me)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table id="u2tab">
|
<table id="u2tab">
|
||||||
|
@ -82,5 +86,5 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p id="u2foot"></p>
|
<p id="u2foot"></p>
|
||||||
<p>( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope">basic uploader</a>)</p>
|
<p id="u2footfoot">( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope">basic uploader</a>)</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -106,4 +106,108 @@ function makeSortable(table) {
|
||||||
sortTable(table, i);
|
sortTable(table, i);
|
||||||
};
|
};
|
||||||
}(i));
|
}(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var ops = document.querySelectorAll('#ops>a');
|
||||||
|
for (var a = 0; a < ops.length; a++) {
|
||||||
|
ops[a].onclick = opclick;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function opclick(ev) {
|
||||||
|
if (ev) //ie
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
var dest = this.getAttribute('data-dest');
|
||||||
|
goto(dest);
|
||||||
|
|
||||||
|
// writing a blank value makes ie8 segfault w
|
||||||
|
if (window.localStorage)
|
||||||
|
localStorage.setItem('opmode', dest || '.');
|
||||||
|
|
||||||
|
var input = document.querySelector('.opview.act input:not([type="hidden"])')
|
||||||
|
if (input)
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function goto(dest) {
|
||||||
|
var obj = document.querySelectorAll('.opview.act');
|
||||||
|
for (var a = obj.length - 1; a >= 0; a--)
|
||||||
|
obj[a].classList.remove('act');
|
||||||
|
|
||||||
|
obj = document.querySelectorAll('#ops>a');
|
||||||
|
for (var a = obj.length - 1; a >= 0; a--)
|
||||||
|
obj[a].classList.remove('act');
|
||||||
|
|
||||||
|
var others = ['path', 'files', 'widget'];
|
||||||
|
for (var a = 0; a < others.length; a++)
|
||||||
|
ebi(others[a]).classList.remove('hidden');
|
||||||
|
|
||||||
|
if (dest) {
|
||||||
|
var ui = ebi('op_' + dest);
|
||||||
|
ui.classList.add('act');
|
||||||
|
document.querySelector('#ops>a[data-dest=' + dest + ']').classList.add('act');
|
||||||
|
|
||||||
|
var fn = window['goto_' + dest];
|
||||||
|
if (fn)
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
goto();
|
||||||
|
if (window.localStorage) {
|
||||||
|
var op = localStorage.getItem('opmode');
|
||||||
|
if (op !== null && op !== '.')
|
||||||
|
goto(op);
|
||||||
|
}
|
||||||
|
ebi('ops').style.display = 'block';
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function linksplit(rp) {
|
||||||
|
var ret = [];
|
||||||
|
var apath = '/';
|
||||||
|
while (rp) {
|
||||||
|
var link = rp;
|
||||||
|
var ofs = rp.indexOf('/');
|
||||||
|
if (ofs === -1) {
|
||||||
|
rp = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
link = rp.slice(0, ofs + 1);
|
||||||
|
rp = rp.slice(ofs + 1);
|
||||||
|
}
|
||||||
|
var vlink = link;
|
||||||
|
if (link.indexOf('/') !== -1)
|
||||||
|
vlink = link.slice(0, -1) + '<span>/</span>';
|
||||||
|
|
||||||
|
ret.push('<a href="' + apath + link + '">' + vlink + '</a>');
|
||||||
|
apath += link;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_evpath() {
|
||||||
|
var ret = document.location.pathname;
|
||||||
|
|
||||||
|
if (ret.indexOf('/') !== 0)
|
||||||
|
ret = '/' + ret;
|
||||||
|
|
||||||
|
if (ret.lastIndexOf('/') !== ret.length - 1)
|
||||||
|
ret += '/';
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_vpath() {
|
||||||
|
return decodeURIComponent(get_evpath());
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue