Compare commits

..

33 Commits

Author SHA1 Message Date
ed
7e8daf650e v0.10.14 2021-04-21 22:04:21 +02:00
ed
0cf737b4ce 404 rather than redirect home if 404 or 403 2021-04-21 21:51:27 +02:00
ed
74635e0113 phew 2021-04-21 21:42:37 +02:00
ed
e5c4f49901 ok ok 2021-04-21 21:26:55 +02:00
ed
e4654ee7f1 uhh 2021-04-21 21:13:16 +02:00
ed
e5d05c05ed up2k ui tweaks 2021-04-21 20:50:10 +02:00
ed
73c4f99687 add markdown streaming 2021-04-21 20:28:50 +02:00
ed
28c12ef3bf cleanup 2021-04-21 18:48:23 +02:00
ed
eed82dbb54 remove dead code 2021-04-21 18:44:47 +02:00
ed
2c4b4ab928 up2k-cli: cond. readahead 2021-04-21 18:39:55 +02:00
ed
505a8fc6f6 up2k: sparse alloc on windows 2021-04-21 18:32:21 +02:00
ed
e4801d9b06 support msys2-python 2021-04-21 18:28:44 +02:00
ed
04f1b2cf3a v0.10.13 2021-04-21 01:19:22 +02:00
ed
c06d928bb5 sorry android 2021-04-21 01:10:18 +02:00
ed
ab09927e7b v0.10.12 2021-04-19 21:58:49 +02:00
ed
779437db67 up2k: more runahead 2021-04-19 21:58:30 +02:00
ed
28cbdb652e v0.10.11 2021-04-19 21:43:08 +02:00
ed
2b2415a7d8 up2k: gotta go faster 2021-04-19 21:29:43 +02:00
ed
746a8208aa v0.10.10 2021-04-19 17:17:07 +02:00
ed
a2a041a98a optimize 2021-04-19 16:54:38 +02:00
ed
10b436e449 browser: add media fragment uris 2021-04-19 16:41:06 +02:00
ed
4d62b34786 browser: add light mode 2021-04-19 15:40:32 +02:00
ed
0546210687 fix up2k progressbars 2021-04-19 13:18:29 +02:00
ed
f8c11faada don't start 2t stuff if there's no backend avail 2021-04-19 13:17:34 +02:00
ed
16d6e9be1f tweaks 2021-04-17 09:24:25 +02:00
ed
aff8185f2e v0.10.9 2021-04-17 01:29:27 +02:00
ed
217d15fe81 up2k: cheap progress bars 2021-04-17 00:57:35 +02:00
ed
171e93c201 up2k: show realtime speeds 2021-04-17 00:01:03 +02:00
ed
acc1d2e9e3 up2k: show some context in the busy-tab 2021-04-16 23:49:57 +02:00
ed
49c2f37154 up2k: replace progressbars with text 2021-04-16 21:23:53 +02:00
ed
69e54497aa yes good 2021-04-14 16:03:15 +02:00
ed
9aa1885669 hide search tab when d2d 2021-04-14 15:23:25 +02:00
ed
4418508513 dodge cpython bug 2021-04-14 14:37:44 +02:00
20 changed files with 992 additions and 452 deletions

10
.vscode/launch.py vendored
View File

@@ -5,6 +5,7 @@
import os import os
import sys import sys
import shlex
sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd())
@@ -16,9 +17,16 @@ with open(".vscode/launch.json", "r") as f:
oj = jstyleson.loads(tj) oj = jstyleson.loads(tj)
argv = oj["configurations"][0]["args"] argv = oj["configurations"][0]["args"]
try:
sargv = " ".join([shlex.quote(x) for x in argv])
print(sys.executable + " -m copyparty " + sargv + "\n")
except:
pass
argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv] argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
try: try:
copyparty(argv) copyparty(["a"] + argv)
except SystemExit as ex: except SystemExit as ex:
if ex.code: if ex.code:
raise raise

View File

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

View File

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

View File

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

View File

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

View File

@@ -111,7 +111,27 @@ class VFS(object):
if rem: if rem:
rp += "/" + rem rp += "/" + rem
try:
return fsdec(os.path.realpath(fsenc(rp))) return fsdec(os.path.realpath(fsenc(rp)))
except:
if not WINDOWS:
raise
# cpython bug introduced in 3.8, still exists in 3.9.1;
# some win7sp1 and win10:20H2 boxes cannot realpath a
# networked drive letter such as b"n:" or b"n:\\"
#
# requirements to trigger:
# * bytestring (not unicode str)
# * just the drive letter (subfolders are ok)
# * networked drive (regular disks and vmhgfs are ok)
# * on an enterprise network (idk, cannot repro with samba)
#
# hits the following exceptions in succession:
# * access denied at L601: "path = _getfinalpathname(path)"
# * "cant concat str to bytes" at L621: "return path + tail"
#
return os.path.realpath(rp)
def ls(self, rem, uname, scandir, lstat=False): def ls(self, rem, uname, scandir, lstat=False):
"""return user-readable [fsdir,real,virt] items at vpath""" """return user-readable [fsdir,real,virt] items at vpath"""

View File

@@ -13,7 +13,7 @@ import ctypes
from datetime import datetime from datetime import datetime
import calendar import calendar
from .__init__ import E, PY2, WINDOWS from .__init__ import E, PY2, WINDOWS, ANYWIN
from .util import * # noqa # pylint: disable=unused-wildcard-import from .util import * # noqa # pylint: disable=unused-wildcard-import
from .szip import StreamZip from .szip import StreamZip
from .star import StreamTar from .star import StreamTar
@@ -261,12 +261,14 @@ class HttpCli(object):
self.absolute_urls = True self.absolute_urls = True
# go home if verboten
self.readable, self.writable = self.conn.auth.vfs.can_access( self.readable, self.writable = self.conn.auth.vfs.can_access(
self.vpath, self.uname self.vpath, self.uname
) )
if not self.readable and not self.writable: if not self.readable and not self.writable:
if self.vpath:
self.log("inaccessible: [{}]".format(self.vpath)) self.log("inaccessible: [{}]".format(self.vpath))
raise Pebkac(404)
self.uparam = {"h": False} self.uparam = {"h": False}
if "h" in self.uparam: if "h" in self.uparam:
@@ -626,7 +628,7 @@ class HttpCli(object):
self.loud_reply(x, status=500) self.loud_reply(x, status=500)
return False return False
if not WINDOWS and num_left == 0: if not ANYWIN and num_left == 0:
times = (int(time.time()), int(lastmod)) times = (int(time.time()), int(lastmod))
self.log("no more chunks, setting times {}".format(times)) self.log("no more chunks, setting times {}".format(times))
try: try:
@@ -680,7 +682,7 @@ class HttpCli(object):
raise Pebkac(500, "mkdir failed, check the logs") raise Pebkac(500, "mkdir failed, check the logs")
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/") vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
esc_paths = [quotep(vpath), html_escape(vpath)] esc_paths = [quotep(vpath), html_escape(vpath, crlf=True)]
html = self.j2( html = self.j2(
"msg", "msg",
h2='<a href="/{}">go to /{}</a>'.format(*esc_paths), h2='<a href="/{}">go to /{}</a>'.format(*esc_paths),
@@ -1181,17 +1183,16 @@ class HttpCli(object):
template = self.j2(tpl) template = self.j2(tpl)
st = os.stat(fsenc(fs_path)) st = os.stat(fsenc(fs_path))
# sz_md = st.st_size
ts_md = st.st_mtime ts_md = st.st_mtime
st = os.stat(fsenc(html_path)) st = os.stat(fsenc(html_path))
ts_html = st.st_mtime ts_html = st.st_mtime
# TODO dont load into memory ;_; sz_md = 0
# (trivial fix, count the &'s) for buf in yieldfile(fs_path):
with open(fsenc(fs_path), "rb") as f: sz_md += len(buf)
md = f.read().replace(b"&", b"&amp;") for c, v in [[b"&", 4], [b"<", 3], [b">", 3]]:
sz_md = len(md) sz_md += (len(buf) - len(buf.replace(c, b""))) * v
file_ts = max(ts_md, ts_html) file_ts = max(ts_md, ts_html)
file_lastmod, do_send = self._chk_lastmod(file_ts) file_lastmod, do_send = self._chk_lastmod(file_ts)
@@ -1199,27 +1200,34 @@ class HttpCli(object):
self.out_headers["Cache-Control"] = "no-cache" self.out_headers["Cache-Control"] = "no-cache"
status = 200 if do_send else 304 status = 200 if do_send else 304
boundary = "\roll\tide"
targs = { targs = {
"edit": "edit" in self.uparam, "edit": "edit" in self.uparam,
"title": html_escape(self.vpath), "title": html_escape(self.vpath, crlf=True),
"lastmod": int(ts_md * 1000), "lastmod": int(ts_md * 1000),
"md_plug": "true" if self.args.emp else "false", "md_plug": "true" if self.args.emp else "false",
"md_chk_rate": self.args.mcr, "md_chk_rate": self.args.mcr,
"md": "", "md": boundary,
} }
sz_html = len(template.render(**targs).encode("utf-8")) html = template.render(**targs).encode("utf-8")
self.send_headers(sz_html + sz_md, status) html = html.split(boundary.encode("utf-8"))
if len(html) != 2:
raise Exception("boundary appears in " + html_path)
self.send_headers(sz_md + len(html[0]) + len(html[1]), status)
logmsg += unicode(status) logmsg += unicode(status)
if self.mode == "HEAD" or not do_send: if self.mode == "HEAD" or not do_send:
self.log(logmsg) self.log(logmsg)
return True return True
# TODO jinja2 can stream this right?
targs["md"] = md.decode("utf-8", "replace")
html = template.render(**targs).encode("utf-8")
try: try:
self.s.sendall(html) self.s.sendall(html[0])
for buf in yieldfile(fs_path):
self.s.sendall(html_bescape(buf))
self.s.sendall(html[1])
except: except:
self.log(logmsg + " \033[31md/c\033[0m") self.log(logmsg + " \033[31md/c\033[0m")
return False return False
@@ -1300,7 +1308,7 @@ class HttpCli(object):
else: else:
vpath += "/" + node vpath += "/" + node
vpnodes.append([quotep(vpath) + "/", html_escape(node)]) vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
vn, rem = self.auth.vfs.get( vn, rem = self.auth.vfs.get(
self.vpath, self.uname, self.readable, self.writable self.vpath, self.uname, self.readable, self.writable
@@ -1394,7 +1402,7 @@ class HttpCli(object):
margin = '<a href="{}?zip">zip</a>'.format(quotep(href)) margin = '<a href="{}?zip">zip</a>'.format(quotep(href))
elif fn in hist: elif fn in hist:
margin = '<a href="{}.hist/{}">#{}</a>'.format( margin = '<a href="{}.hist/{}">#{}</a>'.format(
base, html_escape(hist[fn][2], quote=True), hist[fn][0] base, html_escape(hist[fn][2], quote=True, crlf=True), hist[fn][0]
) )
else: else:
margin = "-" margin = "-"
@@ -1536,7 +1544,7 @@ class HttpCli(object):
have_b_u=(self.writable and self.uparam.get("b") == "u"), have_b_u=(self.writable and self.uparam.get("b") == "u"),
url_suf=url_suf, url_suf=url_suf,
logues=logues, logues=logues,
title=html_escape(self.vpath), title=html_escape(self.vpath, crlf=True),
srv_info=srv_info, srv_info=srv_info,
) )
self.reply(html.encode("utf-8", "replace")) self.reply(html.encode("utf-8", "replace"))

View File

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

View File

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

View File

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

View File

@@ -13,8 +13,8 @@
<body> <body>
<div id="ops"> <div id="ops">
<a href="#" data-dest="" data-desc="close submenu">---</a> <a href="#" data-dest="" data-desc="close submenu">---</a>
<a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,&lt;br /&gt;&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,&lt;br /&gt;&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a>
{%- if have_up2k_idx %} {%- if have_up2k_idx %}
<a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,&lt;br /&gt;&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,&lt;br /&gt;&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a>
<a href="#" data-dest="up2k" data-desc="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a> <a href="#" data-dest="up2k" data-desc="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>
{%- else %} {%- else %}
<a href="#" data-perm="write" data-dest="up2k" data-desc="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a> <a href="#" data-perm="write" data-dest="up2k" data-desc="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>
@@ -39,14 +39,17 @@
{%- include 'upload.html' %} {%- include 'upload.html' %}
<div id="op_cfg" class="opview opbox"> <div id="op_cfg" class="opview opbox">
<h3>key notation</h3> <h3>switches</h3>
<div id="key_notation"></div> <div>
<a id="tooltips" class="tglbtn" href="#">tooltips</a>
<a id="lightmode" class="tglbtn" href="#">lightmode</a>
</div>
{%- if have_zip %} {%- if have_zip %}
<h3>folder download</h3> <h3>folder download</h3>
<div id="arc_fmt"></div> <div id="arc_fmt"></div>
{%- endif %} {%- endif %}
<h3>tooltips</h3> <h3>key notation</h3>
<div><a id="tooltips" class="tglbtn" href="#">enable</a></div> <div id="key_notation"></div>
</div> </div>
<h1 id="path"> <h1 id="path">
@@ -117,8 +120,8 @@
<a href="#" id="selall">sel.<br />all</a> <a href="#" id="selall">sel.<br />all</a>
<a href="#" id="selinv">sel.<br />inv.</a> <a href="#" id="selinv">sel.<br />inv.</a>
<a href="#" id="selzip">zip</a> <a href="#" id="selzip">zip</a>
</span> </span><a
<a href="#" id="wtico"></a> href="#" id="wtico"></a>
</div> </div>
<div id="widgeti"> <div id="widgeti">
<div id="pctl"><a href="#" id="bprev"></a><a href="#" id="bplay"></a><a href="#" id="bnext"></a></div> <div id="pctl"><a href="#" id="bprev"></a><a href="#" id="bplay"></a><a href="#" id="bnext"></a></div>

View File

@@ -314,8 +314,8 @@ function seek_au_sec(seek) {
mp.au.currentTime = seek; mp.au.currentTime = seek;
// ogv.js breaks on .play() during playback
if (mp.au === mp.au_native) if (mp.au === mp.au_native)
// hack: ogv.js breaks on .play() during playback
mp.au.play(); mp.au.play();
} }
@@ -427,7 +427,7 @@ catch (ex) { }
// plays the tid'th audio file on the page // plays the tid'th audio file on the page
function play(tid, call_depth) { function play(tid, seek, call_depth) {
if (mp.order.length == 0) if (mp.order.length == 0)
return alert('no audio found wait what'); return alert('no audio found wait what');
@@ -449,7 +449,7 @@ function play(tid, call_depth) {
} }
// ogv.js breaks on .play() unless directly user-triggered // ogv.js breaks on .play() unless directly user-triggered
var hack_attempt_play = true; var attempt_play = true;
var url = mp.tracks[tid]; var url = mp.tracks[tid];
if (need_ogv && /\.(ogg|opus)$/i.test(url)) { if (need_ogv && /\.(ogg|opus)$/i.test(url)) {
@@ -458,7 +458,7 @@ function play(tid, call_depth) {
} }
else if (window['OGVPlayer']) { else if (window['OGVPlayer']) {
mp.au = mp.au_ogvjs = new OGVPlayer(); mp.au = mp.au_ogvjs = new OGVPlayer();
hack_attempt_play = false; attempt_play = false;
mp.au.addEventListener('error', evau_error, true); mp.au.addEventListener('error', evau_error, true);
mp.au.addEventListener('progress', pbar.drawpos, false); mp.au.addEventListener('progress', pbar.drawpos, false);
widget.open(); widget.open();
@@ -470,7 +470,7 @@ function play(tid, call_depth) {
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>'); show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
import_js('/.cpr/deps/ogv.js', function () { import_js('/.cpr/deps/ogv.js', function () {
play(tid, 1); play(tid, seek, 1);
}); });
return; return;
@@ -498,12 +498,16 @@ function play(tid, call_depth) {
ebi(oid).parentElement.parentElement.className += ' play'; ebi(oid).parentElement.parentElement.className += ' play';
try { try {
if (hack_attempt_play) if (attempt_play)
mp.au.play(); mp.au.play();
if (mp.au.paused) if (mp.au.paused)
autoplay_blocked(); autoplay_blocked(seek);
else if (seek) {
seek_au_sec(seek);
}
if (!seek) {
var o = ebi(oid); var o = ebi(oid);
o.setAttribute('id', 'thx_js'); o.setAttribute('id', 'thx_js');
if (window.history && history.replaceState) { if (window.history && history.replaceState) {
@@ -513,6 +517,7 @@ function play(tid, call_depth) {
document.location.hash = oid; document.location.hash = oid;
} }
o.setAttribute('id', oid); o.setAttribute('id', oid);
}
pbar.drawbuf(); pbar.drawbuf();
return true; return true;
@@ -576,7 +581,7 @@ function unblocked() {
// show ui to manually start playback of a linked song // show ui to manually start playback of a linked song
function autoplay_blocked() { function autoplay_blocked(seek) {
show_modal( show_modal(
'<div id="blk_play"><a href="#" id="blk_go"></a></div>' + '<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>'); '<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
@@ -592,6 +597,8 @@ function autoplay_blocked() {
if (e) e.preventDefault(); if (e) e.preventDefault();
unblocked(); unblocked();
mp.au.play(); mp.au.play();
if (seek)
seek_au_sec(seek);
}; };
na.onclick = unblocked; na.onclick = unblocked;
} }
@@ -600,8 +607,20 @@ function autoplay_blocked() {
// autoplay linked track // autoplay linked track
(function () { (function () {
var v = location.hash; var v = location.hash;
if (v && v.length == 12 && v.indexOf('#af-') === 0) if (v && v.indexOf('#af-') === 0) {
play(v.slice(2)); var id = v.slice(2).split('&');
if (id[0].length != 10)
return;
if (id.length == 1)
return play(id[0]);
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]);
if (!m)
return play(id[0]);
return play(id[0], parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
}
})(); })();
@@ -1549,8 +1568,11 @@ function addcrc() {
ebi('unsearch') ? 'div>a:last-child' : 'a')); ebi('unsearch') ? 'div>a:last-child' : 'a'));
for (var a = 0, aa = links.length; a < aa; a++) for (var a = 0, aa = links.length; a < aa; a++)
if (!links[a].getAttribute('id')) if (!links[a].getAttribute('id')) {
links[a].setAttribute('id', 'f-' + crc32(links[a].textContent || links[a].innerText)); var crc = crc32(links[a].textContent || links[a].innerText);
crc = ('00000000' + crc).slice(-8);
links[a].setAttribute('id', 'f-' + crc);
}
} }
@@ -1577,6 +1599,24 @@ function addcrc() {
})(); })();
(function () {
var light = bcfg_get('lightmode', false);
function freshen() {
document.documentElement.setAttribute("class", light ? "light" : "");
}
ebi('lightmode').onclick = function (e) {
ev(e);
light = !light;
bcfg_set('lightmode', light);
freshen();
};
freshen();
})();
var arcfmt = (function () { var arcfmt = (function () {
if (!ebi('arc_fmt')) if (!ebi('arc_fmt'))
return { "render": function () { } }; return { "render": function () { } };

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,11 @@
margin: -1.5em 0; margin: -1.5em 0;
padding: .8em 0; padding: .8em 0;
width: 100%; width: 100%;
max-width: 12em;
display: inline-block;
}
#u2conf #u2btn_cw {
text-align: right;
} }
#u2notbtn { #u2notbtn {
display: none; display: none;
@@ -72,6 +77,7 @@
} }
#u2tab td:nth-child(2) { #u2tab td:nth-child(2) {
width: 5em; width: 5em;
white-space: nowrap;
} }
#u2tab td:nth-child(3) { #u2tab td:nth-child(3) {
width: 40%; width: 40%;
@@ -83,6 +89,42 @@
#u2tab tr+tr:hover td { #u2tab tr+tr:hover td {
background: #222; background: #222;
} }
#u2cards {
padding: 1em 0 .3em 0;
margin: 1.5em auto -2.5em auto;
text-align: center;
overflow: hidden;
}
#u2cards.w {
width: 45em;
text-align: left;
}
#u2cards a {
padding: .2em 1em;
border: 1px solid #777;
border-width: 0 0 1px 0;
background: linear-gradient(to bottom, #333, #222);
}
#u2cards a:first-child {
border-radius: .4em 0 0 0;
}
#u2cards a:last-child {
border-radius: 0 .4em 0 0;
}
#u2cards a.act {
padding-bottom: .5em;
border-width: 1px 1px .1em 1px;
border-radius: .3em .3em 0 0;
margin-left: -1px;
background: linear-gradient(to bottom, #464, #333 80%);
box-shadow: 0 -.17em .67em #280;
border-color: #7c5 #583 #333 #583;
position: relative;
color: #fd7;
}
#u2cards span {
color: #fff;
}
#u2conf { #u2conf {
margin: 1em auto; margin: 1em auto;
width: 30em; width: 30em;
@@ -99,12 +141,16 @@
outline: none; outline: none;
} }
#u2conf .txtbox { #u2conf .txtbox {
width: 4em; width: 3em;
color: #fff; color: #fff;
background: #444; background: #444;
border: 1px solid #777; border: 1px solid #777;
font-size: 1.2em; font-size: 1.2em;
padding: .15em 0; padding: .15em 0;
height: 1.05em;
}
#u2conf .txtbox.err {
background: #922;
} }
#u2conf a { #u2conf a {
color: #fff; color: #fff;
@@ -113,13 +159,12 @@
border-radius: .1em; border-radius: .1em;
font-size: 1.5em; font-size: 1.5em;
padding: .1em 0; padding: .1em 0;
margin: 0 -.25em; margin: 0 -1px;
width: 1.5em; width: 1.5em;
height: 1em; height: 1em;
display: inline-block; display: inline-block;
position: relative; position: relative;
line-height: 1em; bottom: -0.08em;
bottom: -.08em;
} }
#u2conf input+a { #u2conf input+a {
background: #d80; background: #d80;
@@ -170,12 +215,13 @@
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
margin: 0 -2em; margin: 0 -2em;
height: 0;
padding: 0 1em; padding: 0 1em;
height: 0;
opacity: .1; opacity: .1;
transition: all 0.14s ease-in-out; transition: all 0.14s ease-in-out;
border-radius: .4em;
box-shadow: 0 .2em .5em #222; box-shadow: 0 .2em .5em #222;
border-radius: .4em;
z-index: 1;
} }
#u2cdesc.show { #u2cdesc.show {
padding: 1em; padding: 1em;
@@ -193,24 +239,6 @@
.prog { .prog {
font-family: monospace; font-family: monospace;
} }
.prog>div {
display: inline-block;
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
height: 1.1em;
margin-bottom: -.15em;
box-shadow: -1px -1px 0 inset rgba(255,255,255,0.1);
}
.prog>div>div {
width: 0%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: #0a0;
}
#u2tab a>span { #u2tab a>span {
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
@@ -221,3 +249,38 @@
float: right; float: right;
margin-bottom: -.3em; margin-bottom: -.3em;
} }
html.light #u2btn {
box-shadow: .4em .4em 0 #ccc;
}
html.light #u2cards span {
color: #000;
}
html.light #u2cards a {
background: linear-gradient(to bottom, #eee, #fff);
}
html.light #u2cards a.act {
color: #037;
background: inherit;
box-shadow: 0 -.17em .67em #0ad;
border-color: #09c #05a #eee #05a;
}
html.light #u2conf .txtbox {
background: #fff;
color: #444;
}
html.light #u2conf .txtbox.err {
background: #f96;
color: #300;
}
html.light #u2cdesc {
background: #fff;
border: none;
}
html.light #op_up2k.srch #u2btn {
border-color: #a80;
}

View File

@@ -59,9 +59,9 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#" id="nthread_sub">&ndash;</a> <a href="#" id="nthread_sub">&ndash;</a><input
<input class="txtbox" id="nthread" value="2" /> class="txtbox" id="nthread" value="2"/><a
<a href="#" id="nthread_add">+</a> href="#" id="nthread_add">+</a>
</td> </td>
</tr> </tr>
</table> </table>
@@ -79,12 +79,23 @@
</div> </div>
</div> </div>
<div id="u2cards">
<a href="#" act="ok">ok <span>0</span></a><a
href="#" act="ng">ng <span>0</span></a><a
href="#" act="done">done <span>0</span></a><a
href="#" act="bz" class="act">busy <span>0</span></a><a
href="#" act="q">que <span>0</span></a>
</div>
<table id="u2tab"> <table id="u2tab">
<thead>
<tr> <tr>
<td>filename</td> <td>filename</td>
<td>status</td> <td>status</td>
<td>progress<a href="#" id="u2cleanup">cleanup</a></td> <td>progress<a href="#" id="u2cleanup">cleanup</a></td>
</tr> </tr>
</thead>
<tbody></tbody>
</table> </table>
<p id="u2foot"></p> <p id="u2foot"></p>

View File

@@ -6,7 +6,8 @@ if (!window['console'])
}; };
var clickev = window.Touch ? 'touchstart' : 'click'; var clickev = window.Touch ? 'touchstart' : 'click',
ANDROID = /(android)/i.test(navigator.userAgent);
// error handler for mobile devices // error handler for mobile devices

View File

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