Compare commits

..

40 Commits

Author SHA1 Message Date
ed
ff8313d0fb add mistake 2021-07-01 21:49:44 +02:00
ed
765294c263 ignore dupe-chunk warnings; handshake takes care of it 2021-07-01 20:22:12 +02:00
ed
d6b5351207 add cachebuster because chrome ignores no-cache 2021-07-01 20:10:02 +02:00
ed
a2009bcc6b up2k-cli: recover from tcp/dns issues on upload 2021-07-01 00:52:09 +02:00
ed
12709a8a0a up2k-cli: recover from antivirus yanking files mid-read 2021-07-01 00:11:40 +02:00
ed
c055baefd2 up2k-client: maybe fix busy-tab (assumed linear progress) 2021-06-30 23:17:07 +02:00
ed
56522599b5 up2k-client: way faster init on large filedrops 2021-06-30 21:26:13 +02:00
ed
664f53b75d chrome gets stuck iterating over aux.h on win10 2021-06-30 19:26:06 +02:00
ed
87200d9f10 make -nw apply to more stuff 2021-06-30 19:23:45 +02:00
ed
5c3d0b6520 catch errors in onloads 2021-06-30 17:09:37 +02:00
ed
bd49979f4a v0.11.29 2021-06-30 01:51:57 +02:00
ed
7e606cdd9f make search rate-control less visually confusing 2021-06-30 01:44:25 +02:00
ed
8b4b7fa794 allow opening tree nodes in a new tab 2021-06-30 01:08:20 +02:00
ed
05345ddf8b add per-connection request counting 2021-06-30 01:00:00 +02:00
ed
66adb470ad optional progressbar tint 2021-06-30 00:55:57 +02:00
ed
e15c8fd146 add upload pause 2021-06-30 00:34:33 +02:00
ed
0f09b98a39 scan for additional folder thumbnails 2021-06-30 00:19:39 +02:00
ed
b4d6f4e24d american-friendly upload limits (allow additional bypass using manual text entry) 2021-06-30 00:11:23 +02:00
ed
3217fa625b more todo 2021-06-29 23:59:15 +02:00
ed
e719ff8a47 make sfx kipu-proof 2021-06-29 23:53:57 +02:00
ed
9fcf528d45 update readme 2021-06-29 23:32:21 +02:00
ed
1ddbf5a158 update todo 2021-06-29 23:00:28 +02:00
ed
64bf4574b0 add todo maybe 2021-06-28 20:38:59 +02:00
ed
5649d26077 v0.11.28 2021-06-28 15:36:13 +02:00
ed
92f923effe hotkey for adjusting tree width 2021-06-28 15:34:10 +02:00
ed
0d46d548b9 fix panic when zero accounts 2021-06-28 15:20:40 +02:00
ed
062df3f0c3 point control-panel link to / 2021-06-27 00:52:15 +02:00
ed
789fb53b8e tweaks 2021-06-27 00:49:28 +02:00
ed
351db5a18f ah yes trailing whitespace as markup my good old friend we meet again 2021-06-27 00:20:42 +02:00
ed
aabbd271c8 add debian howto 2021-06-27 00:19:37 +02:00
ed
aae8e0171e v0.11.27 2021-06-25 22:23:21 +02:00
ed
45827a2458 fix exit-search button in gridview 2021-06-25 22:18:16 +02:00
ed
726030296f apparently the html dom-property is not normalized 2021-06-25 22:07:37 +02:00
ed
6659ab3881 ajax subfolders from gridview 2021-06-25 21:49:09 +02:00
ed
c6a103609e fix gridview selection/baguettebox order 2021-06-25 21:35:45 +02:00
ed
c6b3f035e5 gridview audio playback in search results too 2021-06-25 21:12:49 +02:00
ed
2b0a7e378e persist url-password as cookie 2021-06-25 20:39:55 +02:00
ed
b75ce909c8 audio seek with scrollbar on progressbar 2021-06-25 20:24:30 +02:00
ed
229c3f5dab play audio from grid when widget open 2021-06-25 20:04:19 +02:00
ed
ec73094506 v0.11.26 2021-06-25 03:10:43 +02:00
22 changed files with 612 additions and 165 deletions

View File

@@ -20,6 +20,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* top * top
* [quickstart](#quickstart) * [quickstart](#quickstart)
* [on debian](#on-debian)
* [notes](#notes) * [notes](#notes)
* [status](#status) * [status](#status)
* [bugs](#bugs) * [bugs](#bugs)
@@ -68,6 +69,7 @@ some recommended options:
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration) * `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies) * `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar` * `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else * replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
* in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it * in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access * `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
@@ -77,6 +79,19 @@ you may also want these, especially on servers:
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https) * [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
### on debian
recommended steps to enable audio metadata and thumbnails (from images and videos):
* as root, run the following:
`apt install python3 python3-pip python3-dev ffmpeg`
* then, as the user which will be running copyparty (so hopefully not root), run this:
`python3 -m pip install --user -U Pillow pillow-avif-plugin`
(skipped `pyheif-pillow-opener` because apparently debian is too old to build it)
## notes ## notes
general: general:
@@ -139,6 +154,8 @@ summary: all planned features work! now please enjoy the bloatening
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise * all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1` * cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
* dupe files will not have metadata (audio tags etc) displayed in the file listing
* because they don't get `up` entries in the db (probably best fix) and `tx_browser` does not `lstat`
* probably more, pls let me know * probably more, pls let me know
## not my bugs ## not my bugs
@@ -178,9 +195,11 @@ the browser has the following hotkeys
* `U/O` skip 10sec back/forward * `U/O` skip 10sec back/forward
* `J/L` prev/next song * `J/L` prev/next song
* `P` play/pause (also starts playing the folder) * `P` play/pause (also starts playing the folder)
* when tree-sidebar is open:
* `A/D` adjust tree width
* in the grid view: * in the grid view:
* `S` toggle multiselect * `S` toggle multiselect
* `A/D` zoom * shift+`A/D` zoom
## tree-mode ## tree-mode
@@ -198,6 +217,8 @@ it does static images with Pillow and uses FFmpeg for video files, so you may wa
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
## zip downloads ## zip downloads
@@ -594,13 +615,14 @@ in the `scripts` folder:
roughly sorted by priority roughly sorted by priority
* readme.md as epilogue * readme.md as epilogue
* single sha512 across all up2k chunks? maybe
* reduce up2k roundtrips * reduce up2k roundtrips
* start from a chunk index and just go * start from a chunk index and just go
* terminate client on bad data * terminate client on bad data
discarded ideas discarded ideas
* single sha512 across all up2k chunks?
* crypto.subtle cannot into streaming, would have to use hashwasm, expensive
* separate sqlite table per tag * separate sqlite table per tag
* performance fixed by skipping some indexes (`+mt.k`) * performance fixed by skipping some indexes (`+mt.k`)
* audio fingerprinting * audio fingerprinting

View File

@@ -305,6 +305,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown") ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval") ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age") ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
ap2.add_argument("--th-covers", metavar="N,N", type=str, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
ap2 = ap.add_argument_group('database options') ap2 = ap.add_argument_group('database options')
ap2.add_argument("-e2d", action="store_true", help="enable up2k database") ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
@@ -323,6 +324,9 @@ def run_argparse(argv, formatter):
ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin") ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline") ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
ap2 = ap.add_argument_group('video streaming options')
ap2.add_argument("--vcr", action="store_true", help="enable video streaming")
ap2 = ap.add_argument_group('appearance options') ap2 = ap.add_argument_group('appearance options')
ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include") ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 11, 25) VERSION = (0, 11, 29)
CODENAME = "the grid" CODENAME = "the grid"
BUILD_DT = (2021, 6, 25) BUILD_DT = (2021, 6, 30)
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

@@ -693,8 +693,10 @@ class AuthSrv(object):
self.user = user self.user = user
self.iuser = {v: k for k, v in user.items()} self.iuser = {v: k for k, v in user.items()}
self.re_pwd = None
pwds = [re.escape(x) for x in self.iuser.keys()] pwds = [re.escape(x) for x in self.iuser.keys()]
self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)") if pwds:
self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
# import pprint # import pprint
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount}) # pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})

View File

@@ -18,6 +18,8 @@ from .util import * # noqa # pylint: disable=unused-wildcard-import
from .authsrv import AuthSrv from .authsrv import AuthSrv
from .szip import StreamZip from .szip import StreamZip
from .star import StreamTar from .star import StreamTar
from .vcr import VCR_Direct
from .th_srv import FMT_FF
if not PY2: if not PY2:
unicode = str unicode = str
@@ -55,7 +57,7 @@ class HttpCli(object):
def log(self, msg, c=0): def log(self, msg, c=0):
ptn = self.asrv.re_pwd ptn = self.asrv.re_pwd
if ptn.search(msg): if ptn and ptn.search(msg):
msg = ptn.sub(self.unpwd, msg) msg = ptn.sub(self.unpwd, msg)
self.log_func(self.log_src, msg, c) self.log_func(self.log_src, msg, c)
@@ -72,9 +74,13 @@ class HttpCli(object):
if rem.startswith("/") or rem.startswith("../") or "/../" in rem: if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
raise Exception("that was close") raise Exception("that was close")
def j2(self, name, **kwargs): def j2(self, name, **ka):
tpl = self.conn.hsrv.j2[name] tpl = self.conn.hsrv.j2[name]
return tpl.render(**kwargs) if kwargs else tpl if ka:
ka["ts"] = self.conn.hsrv.cachebuster()
return tpl.render(**ka)
return tpl
def run(self): def run(self):
"""returns true if connection can be reused""" """returns true if connection can be reused"""
@@ -181,6 +187,9 @@ class HttpCli(object):
self.rvol, self.wvol, self.avol = [[], [], []] self.rvol, self.wvol, self.avol = [[], [], []]
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol) self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
ua = self.headers.get("user-agent", "") ua = self.headers.get("user-agent", "")
self.is_rclone = ua.startswith("rclone/") self.is_rclone = ua.startswith("rclone/")
if self.is_rclone: if self.is_rclone:
@@ -222,7 +231,9 @@ class HttpCli(object):
def send_headers(self, length, status=200, mime=None, headers={}): def send_headers(self, length, status=200, mime=None, headers={}):
response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])] response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
if length is not None: if length is None:
self.keepalive = False
else:
response.append("Content-Length: " + unicode(length)) response.append("Content-Length: " + unicode(length))
# close if unknown length, otherwise take client's preference # close if unknown length, otherwise take client's preference
@@ -496,7 +507,7 @@ class HttpCli(object):
spd1 = get_spd(nbytes, self.t0) spd1 = get_spd(nbytes, self.t0)
spd2 = get_spd(self.conn.nbyte, self.conn.t0) spd2 = get_spd(self.conn.nbyte, self.conn.t0)
return spd1 + " " + spd2 return "{} {} n{}".format(spd1, spd2, self.conn.nreq)
def handle_post_multipart(self): def handle_post_multipart(self):
self.parser = MultipartParser(self.log, self.sr, self.headers) self.parser = MultipartParser(self.log, self.sr, self.headers)
@@ -758,6 +769,12 @@ class HttpCli(object):
pwd = self.parser.require("cppwd", 64) pwd = self.parser.require("cppwd", 64)
self.parser.drop() self.parser.drop()
ck, msg = self.get_pwd_cookie(pwd)
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
return True
def get_pwd_cookie(self, pwd):
if pwd in self.asrv.iuser: if pwd in self.asrv.iuser:
msg = "login ok" msg = "login ok"
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365) dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
@@ -768,9 +785,7 @@ class HttpCli(object):
exp = "Fri, 15 Aug 1997 01:00:00 GMT" exp = "Fri, 15 Aug 1997 01:00:00 GMT"
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp) ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/") return [ck, msg]
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
return True
def handle_mkdir(self): def handle_mkdir(self):
new_dir = self.parser.require("name", 512) new_dir = self.parser.require("name", 512)
@@ -1376,6 +1391,7 @@ class HttpCli(object):
"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": boundary, "md": boundary,
"ts": self.conn.hsrv.cachebuster(),
} }
html = template.render(**targs).encode("utf-8", "replace") html = template.render(**targs).encode("utf-8", "replace")
html = html.split(boundary.encode("utf-8")) html = html.split(boundary.encode("utf-8"))
@@ -1551,11 +1567,20 @@ class HttpCli(object):
if rem.startswith(".hist/up2k."): if rem.startswith(".hist/up2k."):
raise Pebkac(403) raise Pebkac(403)
if "vcr" in self.uparam:
ext = abspath.rsplit(".")[-1]
if not self.args.vcr or ext not in FMT_FF:
raise Pebkac(403)
vcr = VCR_Direct(self, abspath)
vcr.run()
return False
is_dir = stat.S_ISDIR(st.st_mode) is_dir = stat.S_ISDIR(st.st_mode)
th_fmt = self.uparam.get("th") th_fmt = self.uparam.get("th")
if th_fmt is not None: if th_fmt is not None:
if is_dir: if is_dir:
for fn in ["folder.png", "folder.jpg"]: for fn in self.args.th_covers.split(","):
fp = os.path.join(abspath, fn) fp = os.path.join(abspath, fn)
if os.path.exists(fp): if os.path.exists(fp):
vrem = "{}/{}".format(vrem.rstrip("/"), fn) vrem = "{}/{}".format(vrem.rstrip("/"), fn)
@@ -1619,7 +1644,6 @@ class HttpCli(object):
url_suf = self.urlq() url_suf = self.urlq()
is_ls = "ls" in self.uparam is_ls = "ls" in self.uparam
ts = "" # "?{}".format(time.time())
tpl = "browser" tpl = "browser"
if "b" in self.uparam: if "b" in self.uparam:
@@ -1644,7 +1668,6 @@ class HttpCli(object):
"vdir": quotep(self.vpath), "vdir": quotep(self.vpath),
"vpnodes": vpnodes, "vpnodes": vpnodes,
"files": [], "files": [],
"ts": ts,
"perms": json.dumps(perms), "perms": json.dumps(perms),
"taglist": [], "taglist": [],
"tag_order": [], "tag_order": [],

View File

@@ -43,6 +43,7 @@ class HttpConn(object):
self.t0 = time.time() self.t0 = time.time()
self.stopping = False self.stopping = False
self.nreq = 0
self.nbyte = 0 self.nbyte = 0
self.workload = 0 self.workload = 0
self.u2idx = None self.u2idx = None
@@ -188,6 +189,7 @@ class HttpConn(object):
if self.workload >= 2 ** 31: if self.workload >= 2 ** 31:
self.workload = 100 self.workload = 100
self.nreq += 1
cli = HttpCli(self) cli = HttpCli(self)
if not cli.run(): if not cli.run():
return return

View File

@@ -4,6 +4,8 @@ from __future__ import print_function, unicode_literals
import os import os
import sys import sys
import time import time
import base64
import struct
import socket import socket
import threading import threading
@@ -25,7 +27,6 @@ except ImportError:
sys.exit(1) sys.exit(1)
from .__init__ import E, MACOS from .__init__ import E, MACOS
from .authsrv import AuthSrv
from .httpconn import HttpConn from .httpconn import HttpConn
@@ -48,6 +49,8 @@ class HttpSrv(object):
self.clients = {} self.clients = {}
self.workload = 0 self.workload = 0
self.workload_thr_alive = False self.workload_thr_alive = False
self.cb_ts = 0
self.cb_v = 0
env = jinja2.Environment() env = jinja2.Environment()
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web")) env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
@@ -138,11 +141,12 @@ class HttpSrv(object):
"shut({}): {}".format(fno, ex), "shut({}): {}".format(fno, ex),
c="1;30", c="1;30",
) )
if ex.errno not in [10038, 10054, 107, 57, 9]: if ex.errno not in [10038, 10054, 107, 57, 49, 9]:
# 10038 No longer considered a socket # 10038 No longer considered a socket
# 10054 Foribly closed by remote # 10054 Foribly closed by remote
# 107 Transport endpoint not connected # 107 Transport endpoint not connected
# 57 Socket is not connected # 57 Socket is not connected
# 49 Can't assign requested address (wifi down)
# 9 Bad file descriptor # 9 Bad file descriptor
raise raise
finally: finally:
@@ -177,3 +181,25 @@ class HttpSrv(object):
self.clients[cli] = now self.clients[cli] = now
self.workload = total self.workload = total
def cachebuster(self):
if time.time() - self.cb_ts < 1:
return self.cb_v
with self.mutex:
if time.time() - self.cb_ts < 1:
return self.cb_v
v = E.t0
try:
with os.scandir(os.path.join(E.mod, "web")) as dh:
for fh in dh:
inf = fh.stat(follow_symlinks=False)
v = max(v, inf.st_mtime)
except:
pass
v = base64.urlsafe_b64encode(struct.pack(">xxL", int(v)))
self.cb_v = v.decode("ascii")[-4:]
self.cb_ts = time.time()
return self.cb_v

View File

@@ -1019,7 +1019,8 @@ class Up2k(object):
break break
except: except:
# missing; restart # missing; restart
job = None if not self.args.nw:
job = None
break break
else: else:
# file contents match, but not the path # file contents match, but not the path
@@ -1089,6 +1090,9 @@ class Up2k(object):
} }
def _untaken(self, fdir, fname, ts, ip): def _untaken(self, fdir, fname, ts, ip):
if self.args.nw:
return fname
# TODO broker which avoid this race and # TODO broker which avoid this race and
# provides a new filename if taken (same as bup) # provides a new filename if taken (same as bup)
suffix = ".{:.6f}-{}".format(ts, ip) suffix = ".{:.6f}-{}".format(ts, ip)
@@ -1098,6 +1102,9 @@ 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("linking dupe:\n {0}\n {1}".format(src, dst)) self.log("linking dupe:\n {0}\n {1}".format(src, dst))
if self.args.nw:
return
try: try:
lsrc = src lsrc = src
ldst = dst ldst = dst
@@ -1175,6 +1182,10 @@ class Up2k(object):
if ret > 0: if ret > 0:
return ret, src return ret, src
if self.args.nw:
# del self.registry[ptop][wark]
return ret, dst
atomic_move(src, dst) atomic_move(src, dst)
if ANYWIN: if ANYWIN:
@@ -1284,6 +1295,10 @@ class Up2k(object):
if self.args.dotpart: if self.args.dotpart:
tnam = "." + tnam tnam = "." + tnam
if self.args.nw:
job["tnam"] = tnam
return
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"]

80
copyparty/vcr.py Normal file
View File

@@ -0,0 +1,80 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
import time
import shlex
import subprocess as sp
from .__init__ import PY2
from .util import fsenc
class VCR_Direct(object):
def __init__(self, cli, fpath):
self.cli = cli
self.fpath = fpath
self.log_func = cli.log_func
self.log_src = cli.log_src
def log(self, msg, c=0):
self.log_func(self.log_src, "vcr: {}".format(msg), c)
def run(self):
opts = self.cli.uparam
# fmt: off
cmd = [
"ffmpeg",
"-nostdin",
"-hide_banner",
"-v", "warning",
"-i", fsenc(self.fpath),
"-vf", "scale=640:-4",
"-c:a", "libopus",
"-b:a", "128k",
"-c:v", "libvpx",
"-deadline", "realtime",
"-row-mt", "1"
]
# fmt: on
if "ss" in opts:
cmd.extend(["-ss", opts["ss"]])
if "crf" in opts:
cmd.extend(["-b:v", "0", "-crf", opts["crf"]])
else:
cmd.extend(["-b:v", "{}M".format(opts.get("mbps", 1.2))])
cmd.extend(["-f", "webm", "-"])
comp = str if not PY2 else unicode
cmd = [x.encode("utf-8") if isinstance(x, comp) else x for x in cmd]
self.log(" ".join([shlex.quote(x.decode("utf-8", "replace")) for x in cmd]))
p = sp.Popen(cmd, stdout=sp.PIPE)
self.cli.send_headers(None, mime="video/webm")
fails = 0
while True:
self.log("read")
buf = p.stdout.read(1024 * 4)
if not buf:
fails += 1
if p.poll() is not None or fails > 30:
self.log("ffmpeg exited")
return
time.sleep(0.1)
continue
fails = 0
try:
self.cli.s.sendall(buf)
except:
self.log("client disconnected")
p.kill()
return

View File

@@ -119,7 +119,7 @@ window.baguetteBox = (function () {
var gallery = []; var gallery = [];
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) { [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
var imageElementClickHandler = function (event) { var imageElementClickHandler = function (event) {
if (event && event.ctrlKey) if (event && (event.ctrlKey || event.metaKey))
return true; return true;
event.preventDefault ? event.preventDefault() : event.returnValue = false; event.preventDefault ? event.preventDefault() : event.returnValue = false;

View File

@@ -607,7 +607,7 @@ input.eq_gain {
#srch_q { #srch_q {
white-space: pre; white-space: pre;
color: #f80; color: #f80;
height: 1em; min-height: 1em;
margin: .2em 0 -1em 1.6em; margin: .2em 0 -1em 1.6em;
} }
#tq_raw { #tq_raw {

View File

@@ -6,10 +6,10 @@
<title>⇆🎉 {{ title }}</title> <title>⇆🎉 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8"> <meta name="viewport" content="width=device-width, initial-scale=0.8">
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css{{ ts }}"> <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css?_={{ ts }}">
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css{{ ts }}"> <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css?_={{ ts }}">
{%- if css %} {%- if css %}
<link rel="stylesheet" type="text/css" media="screen" href="{{ css }}{{ ts }}"> <link rel="stylesheet" type="text/css" media="screen" href="{{ css }}?_={{ ts }}">
{%- endif %} {%- endif %}
</head> </head>
@@ -110,7 +110,7 @@
<div id="epi" class="logue">{{ logues[1] }}</div> <div id="epi" class="logue">{{ logues[1] }}</div>
<h2><a href="?h">control-panel</a></h2> <h2><a href="/?h">control-panel</a></h2>
</div> </div>
@@ -127,9 +127,9 @@
have_tags_idx = {{ have_tags_idx|tojson }}, have_tags_idx = {{ have_tags_idx|tojson }},
have_zip = {{ have_zip|tojson }}; have_zip = {{ have_zip|tojson }};
</script> </script>
<script src="/.cpr/util.js{{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>
<script src="/.cpr/browser.js{{ ts }}"></script> <script src="/.cpr/browser.js?_={{ ts }}"></script>
<script src="/.cpr/up2k.js{{ ts }}"></script> <script src="/.cpr/up2k.js?_={{ ts }}"></script>
</body> </body>
</html> </html>

View File

@@ -78,7 +78,7 @@ ebi('op_up2k').innerHTML = (
' <tr>\n' + ' <tr>\n' +
' <td>\n' + ' <td>\n' +
' <a href="#" id="nthread_sub">&ndash;</a><input\n' + ' <a href="#" id="nthread_sub">&ndash;</a><input\n' +
' class="txtbox" id="nthread" value="2"/><a\n' + ' class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' +
' href="#" id="nthread_add">+</a><br />&nbsp;\n' + ' href="#" id="nthread_add">+</a><br />&nbsp;\n' +
' </td>\n' + ' </td>\n' +
' </tr>\n' + ' </tr>\n' +
@@ -237,13 +237,17 @@ var mpl = (function () {
'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' + '<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
'</div></div>' + '</div></div>' +
'<div><h3>tint</h3><div>' +
'<input type="text" id="pb_tint" size="3" value="0" tt="background level (0-100) on the seekbar$Nto make buffering less distracting" />' +
'</div></div>' +
'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>'); '<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
var r = { var r = {
"pb_mode": sread('pb_mode') || 'loop-folder', "pb_mode": sread('pb_mode') || 'loop-folder',
"preload": bcfg_get('au_preload', true), "preload": bcfg_get('au_preload', true),
"clip": bcfg_get('au_npclip', false), "clip": bcfg_get('au_npclip', false),
"os_ctl": bcfg_get('au_os_ctl', true) && have_mctl, "os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
"osd_cv": bcfg_get('au_osd_cv', true), "osd_cv": bcfg_get('au_osd_cv', true),
}; };
@@ -290,6 +294,19 @@ var mpl = (function () {
draw_pb_mode(); draw_pb_mode();
} }
function set_tint() {
var tint = icfg_get('pb_tint', 0);
if (!tint)
ebi('barbuf').style.removeProperty('background');
else
ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')';
}
ebi('pb_tint').oninput = function (e) {
swrite('pb_tint', this.value);
set_tint();
};
set_tint();
r.pp = function () { r.pp = function () {
if (!r.os_ctl) if (!r.os_ctl)
return; return;
@@ -522,37 +539,38 @@ function get_np() {
// toggle player widget // toggle player widget
var widget = (function () { var widget = (function () {
var ret = {}, var r = {},
widget = ebi('widget'), widget = ebi('widget'),
wtico = ebi('wtico'), wtico = ebi('wtico'),
nptxt = ebi('nptxt'), nptxt = ebi('nptxt'),
npirc = ebi('npirc'), npirc = ebi('npirc'),
touchmode = false, touchmode = false,
side_open = false,
was_paused = true; was_paused = true;
ret.open = function () { r.is_open = false;
if (side_open)
r.open = function () {
if (r.is_open)
return false; return false;
widget.className = 'open'; widget.className = 'open';
side_open = true; r.is_open = true;
return true; return true;
}; };
ret.close = function () { r.close = function () {
if (!side_open) if (!r.is_open)
return false; return false;
widget.className = ''; widget.className = '';
side_open = false; r.is_open = false;
return true; return true;
}; };
ret.toggle = function (e) { r.toggle = function (e) {
ret.open() || ret.close(); r.open() || r.close();
ev(e); ev(e);
return false; return false;
}; };
ret.paused = function (paused) { r.paused = function (paused) {
if (was_paused != paused) { if (was_paused != paused) {
was_paused = paused; was_paused = paused;
ebi('bplay').innerHTML = paused ? '▶' : '⏸'; ebi('bplay').innerHTML = paused ? '▶' : '⏸';
@@ -560,7 +578,7 @@ var widget = (function () {
}; };
wtico.onclick = function (e) { wtico.onclick = function (e) {
if (!touchmode) if (!touchmode)
ret.toggle(e); r.toggle(e);
return false; return false;
}; };
@@ -591,7 +609,7 @@ var widget = (function () {
document.body.removeChild(o); document.body.removeChild(o);
}, 500); }, 500);
}; };
return ret; return r;
})(); })();
@@ -857,7 +875,10 @@ function playpause(e) {
ebi('bplay').onclick = playpause; ebi('bplay').onclick = playpause;
ebi('bprev').onclick = prev_song; ebi('bprev').onclick = prev_song;
ebi('bnext').onclick = next_song; ebi('bnext').onclick = next_song;
ebi('barpos').onclick = function (e) {
var bar = ebi('barpos');
bar.onclick = function (e) {
if (!mp.au) { if (!mp.au) {
play(0, true); play(0, true);
return mp.fade_in(); return mp.fade_in();
@@ -868,6 +889,19 @@ function playpause(e) {
seek_au_mul(x * 1.0 / rect.width); seek_au_mul(x * 1.0 / rect.width);
}; };
if (!is_touch)
bar.onwheel = function (e) {
var dist = Math.sign(e.deltaY) * 10;
if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
dist = e.deltaY;
if (!dist || !mp.au)
return true;
seek_au_rel(dist);
ev(e);
};
})(); })();
@@ -1186,8 +1220,11 @@ function play(tid, is_ev, seek, call_depth) {
mp.stopfade(true); mp.stopfade(true);
var tn = tid; var tn = tid;
if ((tn + '').indexOf('f-') === 0) if ((tn + '').indexOf('f-') === 0) {
tn = mp.order.indexOf(tn); tn = mp.order.indexOf(tn);
if (tn < 0)
return;
}
if (tn >= mp.order.length) { if (tn >= mp.order.length) {
if (mpl.pb_mode == 'loop-folder') { if (mpl.pb_mode == 'loop-folder') {
@@ -1385,8 +1422,7 @@ function autoplay_blocked(seek) {
} }
// autoplay linked track function play_linked() {
(function () {
var v = location.hash; var v = location.hash;
if (v && v.indexOf('#af-') === 0) { if (v && v.indexOf('#af-') === 0) {
var id = v.slice(2).split('&'); var id = v.slice(2).split('&');
@@ -1402,7 +1438,7 @@ function autoplay_blocked(seek) {
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0)); return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
} }
})(); };
var thegrid = (function () { var thegrid = (function () {
@@ -1508,35 +1544,61 @@ var thegrid = (function () {
} }
setsz(); setsz();
function seltgl(e) { function gclick(e) {
if (e && e.ctrlKey) if (e && (e.ctrlKey || e.metaKey))
return true; return true;
ev(e);
var oth = ebi(this.getAttribute('ref')), var oth = ebi(this.getAttribute('ref')),
td = oth.parentNode.nextSibling, href = this.getAttribute('href'),
aplay = ebi('a' + oth.getAttribute('id')),
is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
is_vid = /\.(av1|asf|avi|flv|m4v|mkv|mjpeg|mjpg|mpg|mpeg|mpg2|mpeg2|h264|avc|h265|hevc|mov|3gp|mp4|ts|mpegts|nut|ogv|ogm|rm|vob|webm|wmv)(\?|$)/i.test(href),
in_tree = null,
have_sel = QS('#files tr.sel'),
td = oth.closest('td').nextSibling,
tr = td.parentNode; tr = td.parentNode;
td.click(); if (/\/(\?|$)/.test(href)) {
this.setAttribute('class', tr.getAttribute('class')); var ta = QSA('#treeul a.hl+ul>li>a+a'),
} txt = oth.textContent.slice(0, -1);
function bgopen(e) { for (var a = 0, aa = ta.length; a < aa; a++) {
if (ta[a].textContent == txt) {
in_tree = ta[a];
break;
}
}
}
if (r.sel) {
td.click();
this.setAttribute('class', tr.getAttribute('class'));
}
else if (widget.is_open && aplay)
aplay.click();
else if (in_tree && !have_sel)
in_tree.click();
else if (is_vid)
window.open(href + (href.indexOf('?') === -1 ? '?' : '&') + 'vcr', '_blank');
else if (!is_img && have_sel)
window.open(href, '_blank');
else return true;
ev(e); ev(e);
var url = this.getAttribute('href');
window.open(url, '_blank');
} }
r.loadsel = function () { r.loadsel = function () {
if (r.dirty) if (r.dirty)
return; return;
var ths = QSA('#ggrid>a'), var ths = QSA('#ggrid>a');
have_sel = !!QS('#files tr.sel');
for (var a = 0, aa = ths.length; a < aa; a++) { for (var a = 0, aa = ths.length; a < aa; a++) {
ths[a].onclick = r.sel ? seltgl : have_sel ? bgopen : null; var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
ths[a].setAttribute('class', ebi(ths[a].getAttribute('ref')).parentNode.parentNode.getAttribute('class')); ths[a].setAttribute('class', tr.getAttribute('class'));
} }
var uns = QS('#ggrid a[ref="unsearch"]'); var uns = QS('#ggrid a[ref="unsearch"]');
if (uns) if (uns)
@@ -1567,6 +1629,8 @@ var thegrid = (function () {
if (r.thumbs) { if (r.thumbs) {
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j'); ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
if (href == "#")
ihref = '/.cpr/ico/⏏️';
} }
else if (isdir) { else if (isdir) {
ihref = '/.cpr/ico/folder'; ihref = '/.cpr/ico/folder';
@@ -1594,6 +1658,11 @@ var thegrid = (function () {
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>'); ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
} }
ebi('ggrid').innerHTML = html.join('\n'); ebi('ggrid').innerHTML = html.join('\n');
var ths = QSA('#ggrid>a');
for (var a = 0, aa = ths.length; a < aa; a++)
ths[a].onclick = gclick;
r.dirty = false; r.dirty = false;
r.bagit(); r.bagit();
r.loadsel(); r.loadsel();
@@ -1681,10 +1750,14 @@ document.onkeydown = function (e) {
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a') if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
return; return;
if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing) if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
return; return;
var k = (e.code + ''), pos = -1, n; var k = (e.code + ''), pos = -1, n;
if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
return;
if (k.indexOf('Digit') === 0) if (k.indexOf('Digit') === 0)
pos = parseInt(k.slice(-1)) * 0.1; pos = parseInt(k.slice(-1)) * 0.1;
@@ -1720,6 +1793,14 @@ document.onkeydown = function (e) {
if (k == 'KeyT') if (k == 'KeyT')
return ebi('thumbs').click(); return ebi('thumbs').click();
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
if (k == 'KeyA')
return QS('#twig').click();
if (k == 'KeyD')
return QS('#twobytwo').click();
}
if (thegrid.en) { if (thegrid.en) {
if (k == 'KeyS') if (k == 'KeyS')
return ebi('gridsel').click(); return ebi('gridsel').click();
@@ -1802,6 +1883,7 @@ document.onkeydown = function (e) {
} }
var search_timeout, var search_timeout,
defer_timeout,
search_in_progress = 0; search_in_progress = 0;
function ev_search_input() { function ev_search_input() {
@@ -1816,9 +1898,29 @@ document.onkeydown = function (e) {
if (id != "q_raw") if (id != "q_raw")
encode_query(); encode_query();
clearTimeout(search_timeout); set_vq();
if (Date.now() - search_in_progress > 30 * 1000)
clearTimeout(defer_timeout);
defer_timeout = setTimeout(try_search, 2000);
try_search();
}
function try_search() {
if (Date.now() - search_in_progress > 30 * 1000) {
clearTimeout(defer_timeout);
clearTimeout(search_timeout);
search_timeout = setTimeout(do_search, 200); search_timeout = setTimeout(do_search, 200);
}
}
function set_vq() {
if (search_in_progress)
return;
var q = ebi('q_raw').value,
vq = ebi('files').getAttribute('q_raw');
srch_msg(false, (q == vq) ? '' : 'search results below are from a previous query:\n ' + (vq ? vq : '(*)'));
} }
function encode_query() { function encode_query() {
@@ -1888,7 +1990,8 @@ document.onkeydown = function (e) {
xhr.setRequestHeader('Content-Type', 'text/plain'); xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.onreadystatechange = xhr_search_results; xhr.onreadystatechange = xhr_search_results;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.send(JSON.stringify({ "q": ebi('q_raw').value })); xhr.q_raw = ebi('q_raw').value;
xhr.send(JSON.stringify({ "q": xhr.q_raw }));
} }
function xhr_search_results() { function xhr_search_results() {
@@ -1959,6 +2062,8 @@ document.onkeydown = function (e) {
ofiles.innerHTML = html.join('\n'); ofiles.innerHTML = html.join('\n');
ofiles.setAttribute("ts", this.ts); ofiles.setAttribute("ts", this.ts);
ofiles.setAttribute("q_raw", this.q_raw);
set_vq();
mukey.render(); mukey.render();
reload_browser(); reload_browser();
filecols.set_style(['File Name']); filecols.set_style(['File Name']);
@@ -1970,6 +2075,7 @@ document.onkeydown = function (e) {
ev(e); ev(e);
treectl.show(); treectl.show();
ebi('files').innerHTML = orig_html; ebi('files').innerHTML = orig_html;
ebi('files').removeAttribute('q_raw');
orig_html = null; orig_html = null;
msel.render(); msel.render();
reload_browser(); reload_browser();
@@ -2188,6 +2294,9 @@ var treectl = (function () {
} }
function treego(e) { function treego(e) {
if (e && (e.ctrlKey || e.metaKey))
return true;
ev(e); ev(e);
if (this.getAttribute('class') == 'hl' && if (this.getAttribute('class') == 'hl' &&
this.previousSibling.textContent == '-') { this.previousSibling.textContent == '-') {
@@ -2965,3 +3074,4 @@ function reload_browser(not_mp) {
reload_browser(true); reload_browser(true);
mukey.render(); mukey.render();
msel.render(); msel.render();
play_linked();

View File

@@ -54,7 +54,7 @@
<div>{{ logues[1] }}</div><br /> <div>{{ logues[1] }}</div><br />
{%- endif %} {%- endif %}
<h2><a href="{{ url_suf }}{{ url_suf and '&amp;' or '?' }}h">control-panel</a></h2> <h2><a href="/{{ url_suf }}{{ url_suf and '&amp;' or '?' }}h">control-panel</a></h2>
</body> </body>
</html> </html>

View File

@@ -3,9 +3,9 @@
<title>📝🎉 {{ title }}</title> <!-- 📜 --> <title>📝🎉 {{ title }}</title> <!-- 📜 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7"> <meta name="viewport" content="width=device-width, initial-scale=0.7">
<link href="/.cpr/md.css" rel="stylesheet"> <link href="/.cpr/md.css?_={{ ts }}" rel="stylesheet">
{%- if edit %} {%- if edit %}
<link href="/.cpr/md2.css" rel="stylesheet"> <link href="/.cpr/md2.css?_={{ ts }}" rel="stylesheet">
{%- endif %} {%- endif %}
</head> </head>
<body> <body>
@@ -146,10 +146,10 @@ var md_opt = {
})(); })();
</script> </script>
<script src="/.cpr/util.js"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>
<script src="/.cpr/deps/marked.js"></script> <script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
<script src="/.cpr/md.js"></script> <script src="/.cpr/md.js?_={{ ts }}"></script>
{%- if edit %} {%- if edit %}
<script src="/.cpr/md2.js"></script> <script src="/.cpr/md2.js?_={{ ts }}"></script>
{%- endif %} {%- endif %}
</body></html> </body></html>

View File

@@ -3,9 +3,9 @@
<title>📝🎉 {{ title }}</title> <title>📝🎉 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7"> <meta name="viewport" content="width=device-width, initial-scale=0.7">
<link href="/.cpr/mde.css" rel="stylesheet"> <link href="/.cpr/mde.css?_={{ ts }}" rel="stylesheet">
<link href="/.cpr/deps/mini-fa.css" rel="stylesheet"> <link href="/.cpr/deps/mini-fa.css?_={{ ts }}" rel="stylesheet">
<link href="/.cpr/deps/easymde.css" rel="stylesheet"> <link href="/.cpr/deps/easymde.css?_={{ ts }}" rel="stylesheet">
</head> </head>
<body> <body>
<div id="mw"> <div id="mw">
@@ -43,7 +43,7 @@ var lightswitch = (function () {
})(); })();
</script> </script>
<script src="/.cpr/util.js"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>
<script src="/.cpr/deps/easymde.js"></script> <script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
<script src="/.cpr/mde.js"></script> <script src="/.cpr/mde.js?_={{ ts }}"></script>
</body></html> </body></html>

View File

@@ -6,7 +6,7 @@
<title>copyparty</title> <title>copyparty</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8"> <meta name="viewport" content="width=device-width, initial-scale=0.8">
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css"> <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css?_={{ ts }}">
</head> </head>
<body> <body>

View File

@@ -6,7 +6,7 @@
<title>copyparty</title> <title>copyparty</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8"> <meta name="viewport" content="width=device-width, initial-scale=0.8">
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css"> <link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css?_={{ ts }}">
</head> </head>
<body> <body>

View File

@@ -1,7 +1,5 @@
"use strict"; "use strict";
window.onerror = vis_exh;
function goto_up2k() { function goto_up2k() {
if (up2k === false) if (up2k === false)
@@ -16,17 +14,19 @@ function goto_up2k() {
// 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,
var sha_js = window.WebAssembly ? 'hw' : 'ac'; // ff53,c57,sa11 sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
m = 'will use ' + sha_js + ' instead of native sha512 due to';
try { try {
var cf = crypto.subtle || crypto.webkitSubtle; var cf = crypto.subtle || crypto.webkitSubtle;
cf.digest('SHA-512', new Uint8Array(1)).then( cf.digest('SHA-512', new Uint8Array(1)).then(
function (x) { console.log('sha-ok'); up2k = up2k_init(cf); }, function (x) { console.log('sha-ok'); up2k = up2k_init(cf); },
function (x) { console.log('sha-ng:', x); up2k = up2k_init(false); } function (x) { console.log(m, x); up2k = up2k_init(false); }
); );
} }
catch (ex) { catch (ex) {
console.log('sha-na:', ex); console.log(m, ex);
try { try {
up2k = up2k_init(false); up2k = up2k_init(false);
} }
@@ -142,7 +142,7 @@ function U2pvis(act, btns) {
this.tail = -1; this.tail = -1;
this.wsz = 3; this.wsz = 3;
this.addfile = function (entry, sz) { this.addfile = function (entry, sz, draw) {
this.tab.push({ this.tab.push({
"hn": entry[0], "hn": entry[0],
"ht": entry[1], "ht": entry[1],
@@ -156,6 +156,9 @@ function U2pvis(act, btns) {
"bd0": 0 // upload start "bd0": 0 // upload start
}); });
this.ctr["q"]++; this.ctr["q"]++;
if (!draw)
return;
this.drawcard("q"); this.drawcard("q");
if (this.act == "q") { if (this.act == "q") {
this.addrow(this.tab.length - 1); this.addrow(this.tab.length - 1);
@@ -256,6 +259,41 @@ function U2pvis(act, btns) {
var obj = ebi('f{0}p'.format(fobj.n)), var obj = ebi('f{0}p'.format(fobj.n)),
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0]; o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
if (!obj) { //} || true) {
var msg = [
"act", this.act,
"in", fo.in,
"is_act", this.is_act(fo.in),
"head", this.head,
"tail", this.tail,
"nfile", fobj.n,
"name", fobj.name,
"sz", fobj.size,
"bytesDelta", delta,
"bytesDone", fo.bd,
],
m2 = '',
ds = QSA("#u2tab>tbody>tr>td:first-child>a:last-child");
for (var a = 0; a < msg.length; a += 2)
m2 += msg[a] + '=' + msg[a + 1] + ', ';
console.log(m2);
for (var a = 0, aa = ds.length; a < aa; a++) {
var id = ds[a].parentNode.getAttribute('id').slice(1, -1);
console.log("dom %d/%d = [%s] in(%s) is_act(%s) %s",
a, aa, id, this.tab[id].in, this.is_act(fo.in), ds[a].textContent);
}
for (var a = 0, aa = this.tab.length; a < aa; a++)
if (this.is_act(this.tab[a].in))
console.log("tab %d/%d = sz %s", a, aa, this.tab[a].bt);
console.log("a");
throw 42;
}
obj.innerHTML = fo.hp; obj.innerHTML = fo.hp;
obj.style.color = '#fff'; obj.style.color = '#fff';
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)'; obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
@@ -276,12 +314,14 @@ function U2pvis(act, btns) {
this.drawcard(oldcat); this.drawcard(oldcat);
this.drawcard(newcat); this.drawcard(newcat);
if (this.is_act(newcat)) { if (this.is_act(newcat)) {
this.tail++; this.tail = Math.max(this.tail, nfile + 1);
if (!ebi('f' + nfile)) if (!ebi('f' + nfile))
this.addrow(nfile); this.addrow(nfile);
} }
else if (this.is_act(oldcat)) { else if (this.is_act(oldcat)) {
this.head++; while (this.head < Math.min(this.tab.length, this.tail) && (this.head == nfile || !this.is_act(this.tab[this.head].in)))
this.head++;
if (!bz_act) { if (!bz_act) {
var tr = ebi("f" + nfile); var tr = ebi("f" + nfile);
tr.parentNode.removeChild(tr); tr.parentNode.removeChild(tr);
@@ -350,8 +390,21 @@ function U2pvis(act, btns) {
} }
} }
if (this.head == -1) { if (this.head == -1) {
this.head = this.tab.length; var precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 },
this.tail = this.head - 1; postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {};
for (var a = 0; a < this.tab.length; a++) {
var rt = this.tab[a].in;
if (precard[rt]) {
this.head = a + 1;
this.tail = a;
}
else if (postcard[rt]) {
this.head = a;
this.tail = a - 1;
break;
}
}
} }
if (card == "bz") { if (card == "bz") {
for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) { for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) {
@@ -598,14 +651,50 @@ function up2k_init(subtle) {
} }
} }
function read_dirs(rd, pf, dirs, good, bad) { function rd_flatten(pf, dirs) {
var ret = jcp(pf);
for (var a = 0; a < dirs.length; a++)
ret.push(dirs.fullPath || '');
ret.sort();
return ret;
}
var rd_missing_ref = [];
function read_dirs(rd, pf, dirs, good, bad, spins) {
spins = spins || 0;
if (++spins == 5)
rd_missing_ref = rd_flatten(pf, dirs);
if (spins == 200) {
var missing = rd_flatten(pf, dirs),
match = rd_missing_ref.length == missing.length,
aa = match ? missing.length : 0;
missing.sort();
for (var a = 0; a < aa; a++)
if (rd_missing_ref[a] != missing[a])
match = false;
if (match) {
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:'.format(missing.length)];
for (var a = 0; a < Math.min(20, missing.length); a++)
msg.push(missing[a]);
alert(msg.join('\n-- '));
dirs = [];
pf = [];
}
spins = 0;
}
if (!dirs.length) { if (!dirs.length) {
if (!pf.length) if (!pf.length)
return gotallfiles(good, bad); return gotallfiles(good, bad);
console.log("retry pf, " + pf.length); console.log("retry pf, " + pf.length);
setTimeout(function () { setTimeout(function () {
read_dirs(rd, pf, dirs, good, bad); read_dirs(rd, pf, dirs, good, bad, spins);
}, 50); }, 50);
return; return;
} }
@@ -645,7 +734,7 @@ function up2k_init(subtle) {
dirs.shift(); dirs.shift();
rd = null; rd = null;
} }
return read_dirs(rd, pf, dirs, good, bad); return read_dirs(rd, pf, dirs, good, bad, spins);
}); });
} }
@@ -670,41 +759,50 @@ function up2k_init(subtle) {
if (ask_up && !fsearch && !confirm(msg.join('\n'))) if (ask_up && !fsearch && !confirm(msg.join('\n')))
return; return;
var seen = {},
evpath = get_evpath(),
draw_each = good_files.length < 50;
for (var a = 0; a < st.files.length; a++)
seen[st.files[a].name + '\n' + st.files[a].size] = 1;
for (var a = 0; a < good_files.length; a++) { for (var a = 0; a < good_files.length; a++) {
var fobj = good_files[a][0], var fobj = good_files[a][0],
now = Date.now(), now = Date.now(),
lmod = fobj.lastModified || now; lmod = fobj.lastModified || now;
var entry = { var entry = {
"n": parseInt(st.files.length.toString()), "n": st.files.length,
"t0": now, "t0": now,
"fobj": fobj, "fobj": fobj,
"name": good_files[a][1], "name": good_files[a][1],
"size": fobj.size, "size": fobj.size,
"lmod": lmod / 1000, "lmod": lmod / 1000,
"purl": get_evpath(), "purl": evpath,
"done": false, "done": false,
"hash": [] "hash": []
}; },
key = entry.name + '\n' + entry.size;
var skip = false; if (seen[key])
for (var b = 0; b < st.files.length; b++)
if (entry.name == st.files[b].name &&
entry.size == st.files[b].size)
skip = true;
if (skip)
continue; continue;
seen[key] = 1;
pvis.addfile([ pvis.addfile([
fsearch ? esc(entry.name) : linksplit( fsearch ? esc(entry.name) : linksplit(
uricom_dec(entry.purl)[0] + entry.name).join(' '), uricom_dec(entry.purl)[0] + entry.name).join(' '),
'📐 hash', '📐 hash',
'' ''
], fobj.size); ], fobj.size, draw_each);
st.files.push(entry); st.files.push(entry);
st.todo.hash.push(entry); st.todo.hash.push(entry);
} }
if (!draw_each) {
pvis.drawcard("q");
pvis.changecard(pvis.act);
}
} }
ebi('u2btn').addEventListener('drop', gotfile, false); ebi('u2btn').addEventListener('drop', gotfile, false);
@@ -781,7 +879,7 @@ function up2k_init(subtle) {
clearTimeout(tto); clearTimeout(tto);
running = true; running = true;
while (true) { while (window['vis_exh']) {
var is_busy = 0 != var is_busy = 0 !=
st.todo.hash.length + st.todo.hash.length +
st.todo.handshake.length + st.todo.handshake.length +
@@ -954,7 +1052,7 @@ function up2k_init(subtle) {
bpend += cdr - car; bpend += cdr - car;
reader.onload = function (e) { function orz(e) {
if (!min_filebuf && nch == 1) { if (!min_filebuf && nch == 1) {
min_filebuf = 1; min_filebuf = 1;
var td = Date.now() - t0; var td = Date.now() - t0;
@@ -964,9 +1062,30 @@ function up2k_init(subtle) {
} }
} }
hash_calc(nch, e.target.result); hash_calc(nch, e.target.result);
}
reader.onload = function (e) {
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
}; };
reader.onerror = function () { reader.onerror = function () {
alert('y o u b r o k e i t\nerror: ' + reader.error); var err = reader.error + '';
var handled = false;
if (err.indexOf('NotReadableError') !== -1 || // win10-chrome defender
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions
) {
pvis.seth(t.n, 1, 'OS-error');
pvis.seth(t.n, 2, err);
handled = true;
}
if (handled) {
pvis.move(t.n, 'ng');
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
st.bytes.uploaded += t.size;
return tasker();
}
alert('y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err);
}; };
reader.readAsArrayBuffer( reader.readAsArrayBuffer(
bobslice.call(t.fobj, car, cdr)); bobslice.call(t.fobj, car, cdr));
@@ -1047,12 +1166,12 @@ function up2k_init(subtle) {
console.log('zombie handshake onerror,', t); console.log('zombie handshake onerror,', t);
return; return;
} }
console.log('handshake onerror, retrying'); console.log('handshake onerror, retrying', t);
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1); st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
st.todo.handshake.unshift(t); st.todo.handshake.unshift(t);
tasker(); tasker();
}; };
xhr.onload = function (e) { function orz(e) {
if (t.busied != me) { if (t.busied != me) {
console.log('zombie handshake onload,', t); console.log('zombie handshake onload,', t);
return; return;
@@ -1092,6 +1211,7 @@ function up2k_init(subtle) {
if (response.name !== t.name) { if (response.name !== t.name) {
// file exists; server renamed us // file exists; server renamed us
console.log("server-rename [" + t.name + "] to [" + response.name + "]");
t.name = response.name; t.name = response.name;
pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' ')); pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' '));
} }
@@ -1196,6 +1316,9 @@ function up2k_init(subtle) {
(xhr.responseText && xhr.responseText) || (xhr.responseText && xhr.responseText) ||
"no further information")); "no further information"));
} }
}
xhr.onload = function (e) {
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
}; };
var req = { var req = {
@@ -1236,40 +1359,56 @@ function up2k_init(subtle) {
if (cdr >= t.size) if (cdr >= t.size)
cdr = t.size; cdr = t.size;
var xhr = new XMLHttpRequest(); function orz(xhr) {
xhr.upload.onprogress = function (xev) { var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
pvis.prog(t, npart, xev.loaded);
};
xhr.onload = function (xev) {
if (xhr.status == 200) { if (xhr.status == 200) {
pvis.prog(t, npart, cdr - car); pvis.prog(t, npart, cdr - car);
st.bytes.uploaded += cdr - car; st.bytes.uploaded += cdr - car;
t.bytes_uploaded += cdr - car; t.bytes_uploaded += cdr - car;
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
t.postlist.splice(t.postlist.indexOf(npart), 1);
if (t.postlist.length == 0) {
t.t4 = Date.now();
pvis.seth(t.n, 1, 'verifying');
st.todo.handshake.unshift(t);
}
tasker();
} }
else else if (txt.indexOf('already got that') !== -1) {
console.log("ignoring dupe-segment error");
}
else {
alert("server broke; cu-err {0} on file [{1}]:\n".format( alert("server broke; cu-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + ( xhr.status, t.name) + (txt || "no further information"));
(xhr.response && xhr.response.err) || return;
(xhr.responseText && xhr.responseText) || }
"no further information")); st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
}; t.postlist.splice(t.postlist.indexOf(npart), 1);
xhr.open('POST', t.purl + 'chunkpit.php', true); if (t.postlist.length == 0) {
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]); t.t4 = Date.now();
xhr.setRequestHeader("X-Up2k-Wark", t.wark); pvis.seth(t.n, 1, 'verifying');
xhr.setRequestHeader('Content-Type', 'application/octet-stream'); st.todo.handshake.unshift(t);
if (xhr.overrideMimeType) }
xhr.overrideMimeType('Content-Type', 'application/octet-stream'); tasker();
}
function do_send() {
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (xev) {
pvis.prog(t, npart, xev.loaded);
};
xhr.onload = function (xev) {
try { orz(xhr); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
};
xhr.onerror = function (xev) {
if (!window['vis_exh'])
return;
xhr.responseType = 'text'; console.log('chunkpit onerror, retrying', t);
xhr.send(bobslice.call(t.fobj, car, cdr)); do_send();
};
xhr.open('POST', t.purl + 'chunkpit.php', true);
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
if (xhr.overrideMimeType)
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
xhr.responseType = 'text';
xhr.send(bobslice.call(t.fobj, car, cdr));
}
do_send();
} }
///// /////
@@ -1309,6 +1448,17 @@ function up2k_init(subtle) {
} }
tt.init(); tt.init();
function bumpthread2(e) {
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
return;
if (e.code == 'ArrowUp')
bumpthread(1);
if (e.code == 'ArrowDown')
bumpthread(-1);
}
function bumpthread(dir) { function bumpthread(dir) {
try { try {
dir.stopPropagation(); dir.stopPropagation();
@@ -1319,7 +1469,7 @@ function up2k_init(subtle) {
if (dir.target) { if (dir.target) {
clmod(obj, 'err', 1); clmod(obj, 'err', 1);
var v = Math.floor(parseInt(obj.value)); var v = Math.floor(parseInt(obj.value));
if (v < 1 || v > 8 || v !== v) if (v < 0 || v > 64 || v !== v)
return; return;
parallel_uploads = v; parallel_uploads = v;
@@ -1330,11 +1480,11 @@ function up2k_init(subtle) {
parallel_uploads += dir; parallel_uploads += dir;
if (parallel_uploads < 1) if (parallel_uploads < 0)
parallel_uploads = 1; parallel_uploads = 0;
if (parallel_uploads > 8) if (parallel_uploads > 16)
parallel_uploads = 8; parallel_uploads = 16;
obj.value = parallel_uploads; obj.value = parallel_uploads;
bumpthread({ "target": 1 }) bumpthread({ "target": 1 })
@@ -1430,6 +1580,7 @@ function up2k_init(subtle) {
bumpthread(-1); bumpthread(-1);
}; };
ebi('nthread').onkeydown = bumpthread2;
ebi('nthread').addEventListener('input', bumpthread, false); ebi('nthread').addEventListener('input', bumpthread, false);
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);
@@ -1443,7 +1594,10 @@ function up2k_init(subtle) {
nodes[a].addEventListener('touchend', nop, false); nodes[a].addEventListener('touchend', nop, false);
set_fsearch(); set_fsearch();
bumpthread({ "target": 1 }) bumpthread({ "target": 1 });
if (parallel_uploads < 1)
bumpthread(1);
return { "init_deps": init_deps, "set_fsearch": set_fsearch } return { "init_deps": init_deps, "set_fsearch": set_fsearch }
} }

View File

@@ -11,16 +11,6 @@ var is_touch = 'ontouchstart' in window,
// error handler for mobile devices // error handler for mobile devices
function hcroak(msg) {
document.body.innerHTML = msg;
window.onerror = undefined;
throw 'fatal_err';
}
function croak(msg) {
document.body.textContent = msg;
window.onerror = undefined;
throw msg;
}
function esc(txt) { function esc(txt) {
return txt.replace(/[&"<>]/g, function (c) { return txt.replace(/[&"<>]/g, function (c) {
return { return {
@@ -32,9 +22,12 @@ function esc(txt) {
}); });
} }
function vis_exh(msg, url, lineNo, columnNo, error) { function vis_exh(msg, url, lineNo, columnNo, error) {
if (!window.onerror)
return;
window.onerror = undefined; window.onerror = undefined;
window['vis_exh'] = null; window['vis_exh'] = null;
var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>', var html = ['<h1>you hit a bug!</h1><p>please send me a screenshot arigathanks gozaimuch: <code>ed/irc.rizon.net</code> or <code>ed#2644</code><br />&nbsp; (and if you can, press F12 and include the "Console" tab in the screenshot too)</p><p>',
esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>']; esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
if (error) { if (error) {
@@ -44,9 +37,13 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
html.push('<h2>' + find[a] + '</h2>' + html.push('<h2>' + find[a] + '</h2>' +
esc(String(error[find[a]])).replace(/\n/g, '<br />\n')); esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
} }
document.body.style.fontSize = '0.8em'; document.body.innerHTML = html.join('\n');
document.body.style.padding = '0 1em 1em 1em';
hcroak(html.join('\n')); var s = mknod('style');
s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
document.head.appendChild(s);
throw 'fatal_err';
} }
@@ -67,6 +64,9 @@ function ev(e) {
if (e.stopPropagation) if (e.stopPropagation)
e.stopPropagation(); e.stopPropagation();
if (e.stopImmediatePropagation)
e.stopImmediatePropagation();
e.returnValue = false; e.returnValue = false;
return e; return e;
} }
@@ -360,11 +360,11 @@ function get_vpath() {
function get_pwd() { function get_pwd() {
var pwd = ('; ' + document.cookie).split('; cppwd='); var pwd = ('; ' + document.cookie).split('; cppwd=');
if (pwd.length < 2) if (pwd.length < 2)
return null; return null;
return pwd[1].split(';')[0]; return pwd[1].split(';')[0];
} }
@@ -389,6 +389,11 @@ function has(haystack, needle) {
} }
function jcp(obj) {
return JSON.parse(JSON.stringify(obj));
}
function sread(key) { function sread(key) {
if (window.localStorage) if (window.localStorage)
return localStorage.getItem(key); return localStorage.getItem(key);

View File

@@ -6,10 +6,10 @@ import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform,
import subprocess as sp import subprocess as sp
""" """
run me with any version of python, i will unpack and run copyparty pls don't edit this file with a text editor,
it breaks the compressed stuff at the end
(but please don't edit this file with a text editor run me with any version of python, i will unpack and run copyparty
since that would probably corrupt the binary stuff at the end)
there's zero binaries! just plaintext python scripts all the way down there's zero binaries! just plaintext python scripts all the way down
so you can easily unpack the archive and inspect it for shady stuff so you can easily unpack the archive and inspect it for shady stuff

View File

@@ -108,6 +108,9 @@ class VHttpSrv(object):
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"] aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
self.j2 = {x: J2_FILES for x in aliases} self.j2 = {x: J2_FILES for x in aliases}
def cachebuster(self):
return "a"
class VHttpConn(object): class VHttpConn(object):
def __init__(self, args, asrv, log, buf): def __init__(self, args, asrv, log, buf):
@@ -121,6 +124,7 @@ class VHttpConn(object):
self.log_src = "a" self.log_src = "a"
self.lf_url = None self.lf_url = None
self.hsrv = VHttpSrv() self.hsrv = VHttpSrv()
self.nreq = 0
self.nbyte = 0 self.nbyte = 0
self.workload = 0 self.workload = 0
self.ico = None self.ico = None