Compare commits

..

26 Commits

Author SHA1 Message Date
ed
799a5ffa47 v1.2.4 2022-04-14 21:45:22 +02:00
ed
b000707c10 detect poor ffmpeg builds 2022-04-14 18:20:48 +02:00
ed
feba4de1d6 make gallery linkable 2022-04-14 17:12:56 +02:00
ed
951fdb27ca dont scan orphaned volumes 2022-04-14 17:11:51 +02:00
ed
9697fb3d84 option to disable thumbnails per volume 2022-04-14 17:11:26 +02:00
ed
2dbed4500a add flat theme 2022-04-14 16:57:51 +02:00
ed
fd9d0e433d thumbnails: try FFmpeg for images too 2022-04-11 10:38:57 +02:00
ed
f096f3ef81 thumbnails: disable pdf because too scary 2022-04-10 23:02:09 +02:00
ed
cc4a063695 thumbnails: per-decoder filetype config 2022-04-10 22:59:45 +02:00
ed
b64cabc3c9 thumbnails: add pyvips as alt/supp. to pillow 2022-04-10 14:16:09 +02:00
ed
3dd460717c add flat theme 2022-04-09 23:05:54 +02:00
ed
bf658a522b naming 2022-04-09 20:41:08 +02:00
ed
e9be7e712d futureproof clipboard function 2022-04-09 19:38:05 +02:00
ed
e40cd2a809 optimize window resizing 2022-04-09 19:20:09 +02:00
ed
dbabeb9692 gallery: add animation preferences 2022-04-09 17:23:54 +02:00
ed
8dd37d76b0 fix drifting resize 2022-04-09 14:37:25 +02:00
ed
fd475aa358 textviewer: translate basic ansi/sgr colors 2022-04-09 00:50:54 +02:00
ed
f0988c0e32 filter some volflags from up2k dump 2022-04-08 21:56:24 +02:00
ed
0632f09bff rhel8 ignores flock and kills us anyways 2022-04-08 21:29:31 +02:00
ed
ba599aaca0 explain systemd jank 2022-04-08 20:39:22 +02:00
ed
ff05919e89 support mpc/musepack audio (streaming + thumbnailing) 2022-04-02 22:17:16 +02:00
ed
52e63fa101 dont crash when mediaplayer config is changed while music isnt playing 2022-03-28 23:17:02 +02:00
ed
96ceccd12a v1.2.3 2022-03-24 02:35:53 +01:00
ed
87994fe006 retry failed uploads with backoff 2022-03-24 02:29:59 +01:00
ed
fa12c81a03 zip-download files older than 1980-01-01 2022-03-24 01:31:50 +01:00
ed
344ce63455 basic-browser is implicitly not js 2022-03-21 01:20:47 +01:00
29 changed files with 1144 additions and 504 deletions

View File

@@ -174,7 +174,7 @@ feature summary
* ☑ image gallery with webm player * ☑ image gallery with webm player
* ☑ textfile browser with syntax hilighting * ☑ textfile browser with syntax hilighting
* ☑ [thumbnails](#thumbnails) * ☑ [thumbnails](#thumbnails)
* ☑ ...of images using Pillow * ☑ ...of images using Pillow, pyvips, or FFmpeg
* ☑ ...of videos using FFmpeg * ☑ ...of videos using FFmpeg
* ☑ ...of audio (spectrograms) using FFmpeg * ☑ ...of audio (spectrograms) using FFmpeg
* ☑ cache eviction (max-age; maybe max-size eventually) * ☑ cache eviction (max-age; maybe max-size eventually)
@@ -403,7 +403,9 @@ press `g` to toggle grid-view instead of the file listing, and `t` toggles icon
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png) ![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png)
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are it does static images with Pillow / pyvips / FFmpeg, and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
* pyvips is 3x faster than Pillow, Pillow is 3x faster than FFmpeg
* disable thumbnails for specific volumes with volflag `dthumb` for all, or `dvthumb` / `dathumb` / `dithumb` for video/audio/images only
audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`) audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`)
@@ -1096,10 +1098,13 @@ enable music tags:
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users) * or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
enable [thumbnails](#thumbnails) of... enable [thumbnails](#thumbnails) of...
* **images:** `Pillow` (requires py2.7 or py3.5+) * **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
* **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH` * **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler) * **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
* **AVIF pictures:** `pillow-avif-plugin` * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin`
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
## install recommended deps ## install recommended deps

View File

@@ -14,7 +14,6 @@ save one of these as `.epilogue.html` inside a folder to customize it:
## example browser-css ## example browser-css
point `--css-browser` to one of these by URL: point `--css-browser` to one of these by URL:
* [`browser.css`](browser.css) changes the background
* [`browser-icons.css`](browser-icons.css) adds filetype icons * [`browser-icons.css`](browser-icons.css) adds filetype icons

View File

@@ -1,30 +0,0 @@
html {
background: #222 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
}
#files th {
background: rgba(32, 32, 32, 0.9) !important;
}
#ops,
#tree,
#files td {
background: rgba(32, 32, 32, 0.3) !important;
}
html.light {
background: #eee url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
}
html.light #files th {
background: rgba(255, 255, 255, 0.9) !important;
}
html.light .logue,
html.light #ops,
html.light #tree,
html.light #files td {
background: rgba(248, 248, 248, 0.8) !important;
}
#files * {
background: transparent !important;
}

View File

@@ -22,6 +22,8 @@
# if you remove -q to enable logging, you may also want to remove the # if you remove -q to enable logging, you may also want to remove the
# following line to enable buffering (slightly better performance): # following line to enable buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x # Environment=PYTHONUNBUFFERED=x
#
# keep ExecStartPre before ExecStart, at least on rhel8
[Unit] [Unit]
Description=copyparty file server Description=copyparty file server

View File

@@ -500,6 +500,7 @@ def run_argparse(argv, formatter):
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails") ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails")
ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds") ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds")
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image") ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="decoders, in order of preference")
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output") ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output") ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs") ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
@@ -508,6 +509,14 @@ def run_argparse(argv, formatter):
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled") ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
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=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for") ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# https://github.com/libvips/libvips
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="bmp,dib,gif,icns,ico,jpg,jpeg,jp2,jpx,pcx,png,pbm,pgm,ppm,pnm,sgi,tga,tif,tiff,webp,xbm,dds,xpm,heif,heifs,heic,heics,avif,avifs", help="image formats to decode using pillow")
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="jpg,jpeg,jp2,jpx,jxl,tif,tiff,png,webp,heic,avif,fit,fits,fts,exr,svg,hdr,ppm,pgm,pfm,gif,nii", help="image formats to decode using pyvips")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="av1,asf,avi,flv,m4v,mkv,mjpeg,mjpg,mpg,mpeg,mpg2,mpeg2,h264,avc,mts,h265,hevc,mov,3gp,mp4,ts,mpegts,nut,ogv,ogm,rm,vob,webm,wmv", help="video formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,m4a,ogg,opus,flac,alac,mp3,mp2,ac3,dts,wma,ra,wav,aif,aiff,au,alaw,ulaw,mulaw,amr,gsm,ape,tak,tta,wv,mpc", help="audio formats to decode using ffmpeg")
ap2 = ap.add_argument_group('transcoding options') ap2 = ap.add_argument_group('transcoding options')
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding") ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
@@ -539,6 +548,8 @@ def run_argparse(argv, formatter):
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin") ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
ap2 = ap.add_argument_group('ui options') ap2 = ap.add_argument_group('ui options')
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
ap2.add_argument("--themes", metavar="NUM", type=int, default=4, help="number of themes installed")
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include") ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include") ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages") ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (1, 2, 2) VERSION = (1, 2, 4)
CODENAME = "ftp btw" CODENAME = "ftp btw"
BUILD_DT = (2022, 3, 20) BUILD_DT = (2022, 4, 14)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -741,10 +741,10 @@ class AuthSrv(object):
unames = ["*"] + list(acct.keys()) unames = ["*"] + list(acct.keys())
umap = {x: [] for x in unames} umap = {x: [] for x in unames}
for usr in unames: for usr in unames:
for mp, vol in vfs.all_vols.items(): for vp, vol in vfs.all_vols.items():
axs = getattr(vol.axs, axs_key) axs = getattr(vol.axs, axs_key)
if usr in axs or "*" in axs: if usr in axs or "*" in axs:
umap[usr].append(mp) umap[usr].append(vp)
umap[usr].sort() umap[usr].sort()
setattr(vfs, "a" + perm, umap) setattr(vfs, "a" + perm, umap)
@@ -875,6 +875,17 @@ class AuthSrv(object):
vol.flags["html_head"] = "\n".join([x for x in h if x]) vol.flags["html_head"] = "\n".join([x for x in h if x])
for vol in vfs.all_vols.values():
if self.args.no_vthumb:
vol.flags["dvthumb"] = True
if self.args.no_athumb:
vol.flags["dathumb"] = True
if self.args.no_thumb or vol.flags.get("dthumb", False):
vol.flags["dthumb"] = True
vol.flags["dvthumb"] = True
vol.flags["dathumb"] = True
vol.flags["dithumb"] = True
for vol in vfs.all_vols.values(): for vol in vfs.all_vols.values():
fk = vol.flags.get("fk") fk = vol.flags.get("fk")
if fk: if fk:

View File

@@ -2097,9 +2097,7 @@ class HttpCli(object):
thp = None thp = None
if self.thumbcli: if self.thumbcli:
thp = self.thumbcli.get( thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
dbv.realpath, vrem, int(st.st_mtime), th_fmt
)
if thp: if thp:
return self.tx_file(thp) return self.tx_file(thp)
@@ -2149,12 +2147,11 @@ class HttpCli(object):
free = humansize(sv.f_frsize * sv.f_bfree, True) free = humansize(sv.f_frsize * sv.f_bfree, True)
total = humansize(sv.f_frsize * sv.f_blocks, True) total = humansize(sv.f_frsize * sv.f_blocks, True)
srv_info.append(free + " free") srv_info.append("{} free of {}".format(free, total))
srv_info.append(total)
except: except:
pass pass
srv_info = "</span> /// <span>".join(srv_info) srv_info = "</span> // <span>".join(srv_info)
perms = [] perms = []
if self.can_read: if self.can_read:
@@ -2175,6 +2172,7 @@ class HttpCli(object):
tpl = "browser" tpl = "browser"
if "b" in self.uparam: if "b" in self.uparam:
tpl = "browser2" tpl = "browser2"
is_js = False
logues = ["", ""] logues = ["", ""]
if not self.args.no_logues: if not self.args.no_logues:
@@ -2226,6 +2224,8 @@ class HttpCli(object):
"readme": readme, "readme": readme,
"title": html_escape(self.vpath, crlf=True), "title": html_escape(self.vpath, crlf=True),
"srv_info": srv_info, "srv_info": srv_info,
"dtheme": self.args.theme,
"themes": self.args.themes,
} }
if not self.can_read: if not self.can_read:
if is_ls: if is_ls:

View File

@@ -17,7 +17,7 @@ from .util import Unrecv
from .httpcli import HttpCli from .httpcli import HttpCli
from .u2idx import U2idx from .u2idx import U2idx
from .th_cli import ThumbCli from .th_cli import ThumbCli
from .th_srv import HAVE_PIL from .th_srv import HAVE_PIL, HAVE_VIPS
from .ico import Ico from .ico import Ico
@@ -38,7 +38,7 @@ class HttpConn(object):
self.cert_path = hsrv.cert_path self.cert_path = hsrv.cert_path
self.u2fh = hsrv.u2fh self.u2fh = hsrv.u2fh
enth = HAVE_PIL and not self.args.no_thumb enth = (HAVE_PIL or HAVE_VIPS) and not self.args.no_thumb
self.thumbcli = ThumbCli(hsrv) if enth else None self.thumbcli = ThumbCli(hsrv) if enth else None
self.ico = Ico(self.args) self.ico = Ico(self.args)

View File

@@ -70,6 +70,12 @@ class HttpSrv(object):
self.cb_ts = 0 self.cb_ts = 0
self.cb_v = 0 self.cb_v = 0
try:
x = self.broker.put(True, "thumbsrv.getcfg")
self.th_cfg = x.get()
except:
pass
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"))
self.j2 = { self.j2 = {

View File

@@ -477,13 +477,13 @@ class MTag(object):
env["PYTHONPATH"] = pypath env["PYTHONPATH"] = pypath
ret = {} ret = {}
for tagname, mp in parsers.items(): for tagname, parser in parsers.items():
try: try:
cmd = [mp.bin, abspath] cmd = [parser.bin, abspath]
if mp.bin.endswith(".py"): if parser.bin.endswith(".py"):
cmd = [sys.executable] + cmd cmd = [sys.executable] + cmd
args = {"env": env, "timeout": mp.timeout} args = {"env": env, "timeout": parser.timeout}
if WINDOWS: if WINDOWS:
args["creationflags"] = 0x4000 args["creationflags"] = 0x4000

View File

@@ -5,7 +5,7 @@ import tarfile
import threading import threading
from .sutil import errdesc from .sutil import errdesc
from .util import Queue, fsenc from .util import Queue, fsenc, min_ex
from .bos import bos from .bos import bos
@@ -88,8 +88,9 @@ class StreamTar(object):
try: try:
self.ser(f) self.ser(f)
except Exception as ex: except Exception:
errors.append([f["vp"], repr(ex)]) ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if errors: if errors:
self.errf, txt = errdesc(errors) self.errf, txt = errdesc(errors)

View File

@@ -17,7 +17,7 @@ from .util import mp, start_log_thrs, start_stackmon, min_ex, ansi_re
from .authsrv import AuthSrv from .authsrv import AuthSrv
from .tcpsrv import TcpSrv from .tcpsrv import TcpSrv
from .up2k import Up2k from .up2k import Up2k
from .th_srv import ThumbSrv, HAVE_PIL, HAVE_WEBP from .th_srv import ThumbSrv, HAVE_PIL, HAVE_VIPS, HAVE_WEBP
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
@@ -70,6 +70,10 @@ class SvcHub(object):
self.log("root", m, c=3) self.log("root", m, c=3)
bri = "zy"[args.theme % 2 :][:1]
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
args.theme = "{0}{1} {0} {1}".format(ch, bri)
# initiate all services to manage # initiate all services to manage
self.asrv = AuthSrv(self.args, self.log) self.asrv = AuthSrv(self.args, self.log)
if args.ls: if args.ls:
@@ -78,20 +82,30 @@ class SvcHub(object):
self.tcpsrv = TcpSrv(self) self.tcpsrv = TcpSrv(self)
self.up2k = Up2k(self) self.up2k = Up2k(self)
decs = {k: 1 for k in self.args.th_dec.split(",")}
if not HAVE_VIPS:
decs.pop("vips", None)
if not HAVE_PIL:
decs.pop("pil", None)
if not HAVE_FFMPEG or not HAVE_FFPROBE:
decs.pop("ff", None)
self.args.th_dec = list(decs.keys())
self.thumbsrv = None self.thumbsrv = None
if not args.no_thumb: if not args.no_thumb:
if HAVE_PIL: m = "decoder preference: {}".format(", ".join(self.args.th_dec))
if not HAVE_WEBP: self.log("thumb", m)
args.th_no_webp = True
msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old" if "pil" in self.args.th_dec and not HAVE_WEBP:
msg = "disabling webp thumbnails because either libwebp is not available or your Pillow is too old"
self.log("thumb", msg, c=3) self.log("thumb", msg, c=3)
if self.args.th_dec:
self.thumbsrv = ThumbSrv(self) self.thumbsrv = ThumbSrv(self)
else: else:
msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n" msg = "need either Pillow, pyvips, or FFmpeg to create thumbnails; for example:\n{0}{1} -m pip install --user Pillow\n{0}{1} -m pip install --user pyvips\n{0}apt install ffmpeg"
self.log( msg = msg.format(" " * 37, os.path.basename(sys.executable))
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3 self.log("thumb", msg, c=3)
)
if not args.no_acode and args.no_thumb: if not args.no_acode and args.no_thumb:
msg = "setting --no-acode because --no-thumb (sorry)" msg = "setting --no-acode because --no-thumb (sorry)"

View File

@@ -1,13 +1,12 @@
# coding: utf-8 # coding: utf-8
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os
import time import time
import zlib import zlib
from datetime import datetime from datetime import datetime
from .sutil import errdesc from .sutil import errdesc
from .util import yieldfile, sanitize_fn, spack, sunpack from .util import yieldfile, sanitize_fn, spack, sunpack, min_ex
from .bos import bos from .bos import bos
@@ -36,7 +35,10 @@ def unixtime2dos(ts):
bd = ((dy - 1980) << 9) + (dm << 5) + dd bd = ((dy - 1980) << 9) + (dm << 5) + dd
bt = (th << 11) + (tm << 5) + ts // 2 bt = (th << 11) + (tm << 5) + ts // 2
try:
return spack(b"<HH", bt, bd) return spack(b"<HH", bt, bd)
except:
return b"\x00\x00\x21\x00"
def gen_fdesc(sz, crc32, z64): def gen_fdesc(sz, crc32, z64):
@@ -244,8 +246,9 @@ class StreamZip(object):
try: try:
for x in self.ser(f): for x in self.ser(f):
yield x yield x
except Exception as ex: except Exception:
errors.append([f["vp"], repr(ex)]) ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if errors: if errors:
errf, txt = errdesc(errors) errf, txt = errdesc(errors)

View File

@@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
import os import os
from .util import Cooldown from .util import Cooldown
from .th_srv import thumb_path, THUMBABLE, FMT_FFV, FMT_FFA from .th_srv import thumb_path, HAVE_WEBP
from .bos import bos from .bos import bos
@@ -18,30 +18,53 @@ class ThumbCli(object):
# cache on both sides for less broker spam # cache on both sides for less broker spam
self.cooldown = Cooldown(self.args.th_poke) self.cooldown = Cooldown(self.args.th_poke)
try:
c = hsrv.th_cfg
except:
c = {k: {} for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]}
self.thumbable = c["thumbable"]
self.fmt_pil = c["pil"]
self.fmt_vips = c["vips"]
self.fmt_ffi = c["ffi"]
self.fmt_ffv = c["ffv"]
self.fmt_ffa = c["ffa"]
# defer args.th_ff_jpg, can change at runtime
d = next((x for x in self.args.th_dec if x in ("vips", "pil")), None)
self.can_webp = HAVE_WEBP or d == "vips"
def log(self, msg, c=0): def log(self, msg, c=0):
self.log_func("thumbcli", msg, c) self.log_func("thumbcli", msg, c)
def get(self, ptop, rem, mtime, fmt): def get(self, dbv, rem, mtime, fmt):
ptop = dbv.realpath
ext = rem.rsplit(".")[-1].lower() ext = rem.rsplit(".")[-1].lower()
if ext not in THUMBABLE: if ext not in self.thumbable or "dthumb" in dbv.flags:
return None return None
is_vid = ext in FMT_FFV is_vid = ext in self.fmt_ffv
if is_vid and self.args.no_vthumb: if is_vid and "dvthumb" in dbv.flags:
return None return None
want_opus = fmt in ("opus", "caf") want_opus = fmt in ("opus", "caf")
is_au = ext in FMT_FFA is_au = ext in self.fmt_ffa
if is_au: if is_au:
if want_opus: if want_opus:
if self.args.no_acode: if self.args.no_acode:
return None return None
else: else:
if self.args.no_athumb: if "dathumb" in dbv.flags:
return None return None
elif want_opus: elif want_opus:
return None return None
is_img = not is_vid and not is_au
if is_img and "dithumb" in dbv.flags:
return None
preferred = self.args.th_dec[0] if self.args.th_dec else ""
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]: if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
return os.path.join(ptop, rem) return os.path.join(ptop, rem)
@@ -49,7 +72,11 @@ class ThumbCli(object):
fmt = "w" fmt = "w"
if fmt == "w": if fmt == "w":
if self.args.th_no_webp or ((is_vid or is_au) and self.args.th_ff_jpg): if (
self.args.th_no_webp
or (is_img and not self.can_webp)
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
):
fmt = "j" fmt = "j"
histpath = self.asrv.vfs.histtab.get(ptop) histpath = self.asrv.vfs.histtab.get(ptop)
@@ -58,13 +85,21 @@ class ThumbCli(object):
return None return None
tpath = thumb_path(histpath, rem, mtime, fmt) tpath = thumb_path(histpath, rem, mtime, fmt)
tpaths = [tpath]
if fmt == "w":
# also check for jpg (maybe webp is unavailable)
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
ret = None ret = None
abort = False
for tp in tpaths:
try: try:
st = bos.stat(tpath) st = bos.stat(tp)
if st.st_size: if st.st_size:
ret = tpath ret = tpath = tp
fmt = ret.rsplit(".")[1]
else: else:
return None abort = True
except: except:
pass pass
@@ -80,5 +115,8 @@ class ThumbCli(object):
return ret return ret
if abort:
return None
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt) x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
return x.get() return x.get()

View File

@@ -47,31 +47,12 @@ try:
except: except:
pass pass
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html try:
# ffmpeg -formats import pyvips
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
FMT_FFV = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
FMT_FFA = "aac m4a ogg opus flac alac mp3 mp2 ac3 dts wma ra wav aif aiff au alaw ulaw mulaw amr gsm ape tak tta wv"
if HAVE_HEIF: HAVE_VIPS = True
FMT_PIL += " heif heifs heic heics" except:
HAVE_VIPS = False
if HAVE_AVIF:
FMT_PIL += " avif avifs"
FMT_PIL, FMT_FFV, FMT_FFA = [
{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FFV, FMT_FFA]
]
THUMBABLE = {}
if HAVE_PIL:
THUMBABLE.update(FMT_PIL)
if HAVE_FFMPEG and HAVE_FFPROBE:
THUMBABLE.update(FMT_FFV)
THUMBABLE.update(FMT_FFA)
def thumb_path(histpath, rem, mtime, fmt): def thumb_path(histpath, rem, mtime, fmt):
@@ -141,6 +122,37 @@ class ThumbSrv(object):
t.daemon = True t.daemon = True
t.start() t.start()
self.fmt_pil, self.fmt_vips, self.fmt_ffi, self.fmt_ffv, self.fmt_ffa = [
{x: True for x in y.split(",")}
for y in [
self.args.th_r_pil,
self.args.th_r_vips,
self.args.th_r_ffi,
self.args.th_r_ffv,
self.args.th_r_ffa,
]
]
if not HAVE_HEIF:
for f in "heif heifs heic heics".split(" "):
self.fmt_pil.pop(f, None)
if not HAVE_AVIF:
for f in "avif avifs".split(" "):
self.fmt_pil.pop(f, None)
self.thumbable = {}
if "pil" in self.args.th_dec:
self.thumbable.update(self.fmt_pil)
if "vips" in self.args.th_dec:
self.thumbable.update(self.fmt_vips)
if "ff" in self.args.th_dec:
for t in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
self.thumbable.update(t)
def log(self, msg, c=0): def log(self, msg, c=0):
self.log_func("thumb", msg, c) self.log_func("thumb", msg, c)
@@ -201,6 +213,16 @@ class ThumbSrv(object):
return None return None
def getcfg(self):
return {
"thumbable": self.thumbable,
"pil": self.fmt_pil,
"vips": self.fmt_vips,
"ffi": self.fmt_ffi,
"ffv": self.fmt_ffv,
"ffa": self.fmt_ffa,
}
def worker(self): def worker(self):
while not self.stopping: while not self.stopping:
task = self.q.get() task = self.q.get()
@@ -211,11 +233,16 @@ class ThumbSrv(object):
ext = abspath.split(".")[-1].lower() ext = abspath.split(".")[-1].lower()
fun = None fun = None
if not bos.path.exists(tpath): if not bos.path.exists(tpath):
if ext in FMT_PIL: for lib in self.args.th_dec:
if fun:
break
elif lib == "pil" and ext in self.fmt_pil:
fun = self.conv_pil fun = self.conv_pil
elif ext in FMT_FFV: elif lib == "vips" and ext in self.fmt_vips:
fun = self.conv_vips
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
fun = self.conv_ffmpeg fun = self.conv_ffmpeg
elif ext in FMT_FFA: elif lib == "ff" and ext in self.fmt_ffa:
if tpath.endswith(".opus") or tpath.endswith(".caf"): if tpath.endswith(".opus") or tpath.endswith(".caf"):
fun = self.conv_opus fun = self.conv_opus
else: else:
@@ -296,11 +323,29 @@ class ThumbSrv(object):
im.save(tpath, **args) im.save(tpath, **args)
def conv_vips(self, abspath, tpath):
crops = ["centre", "none"]
if self.args.th_no_crop:
crops = ["none"]
w, h = self.res
kw = {"height": h, "size": "down", "intent": "relative"}
for c in crops:
try:
kw["crop"] = c
img = pyvips.Image.thumbnail(abspath, w, **kw)
break
except:
pass
img.write_to_file(tpath, Q=40)
def conv_ffmpeg(self, abspath, tpath): def conv_ffmpeg(self, abspath, tpath):
ret, _ = ffprobe(abspath) ret, _ = ffprobe(abspath)
ext = abspath.rsplit(".")[-1] ext = abspath.rsplit(".")[-1].lower()
if ext in ["h264", "h265"]: if ext in ["h264", "h265"] or ext in self.fmt_ffi:
seek = [] seek = []
else: else:
dur = ret[".dur"][1] if ".dur" in ret else 4 dur = ret[".dur"][1] if ".dur" in ret else 4
@@ -350,10 +395,30 @@ class ThumbSrv(object):
def _run_ff(self, cmd): def _run_ff(self, cmd):
# self.log((b" ".join(cmd)).decode("utf-8")) # self.log((b" ".join(cmd)).decode("utf-8"))
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt) ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
if ret != 0: if not ret:
return
c = "1;30"
m = "FFmpeg failed (probably a corrupt video file):\n" m = "FFmpeg failed (probably a corrupt video file):\n"
if cmd[-1].lower().endswith(b".webp") and (
"Error selecting an encoder" in serr
or "Automatic encoder selection failed" in serr
or "Default encoder for format webp" in serr
or "Please choose an encoder manually" in serr
):
self.args.th_ff_jpg = True
m = "FFmpeg failed because it was compiled without libwebp; enabling --th-ff-jpg to force jpeg output:\n"
c = 1
if (
"Requested resampling engine is unavailable" in serr
or "output pad on Parsed_aresample_" in serr
):
m = "FFmpeg failed because it was compiled without libsox; you must set --th-ff-swr to force swr resampling:\n"
c = 1
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")]) m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
self.log(m, c="1;30") self.log(m, c=c)
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1])) raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
def conv_spec(self, abspath, tpath): def conv_spec(self, abspath, tpath):

View File

@@ -470,9 +470,11 @@ class Up2k(object):
ft = "\033[0;32m{}{:.0}" ft = "\033[0;32m{}{:.0}"
ff = "\033[0;35m{}{:.0}" ff = "\033[0;35m{}{:.0}"
fv = "\033[0;36m{}:\033[1;30m{}" fv = "\033[0;36m{}:\033[1;30m{}"
fx = set(("html_head",))
a = [ a = [
(ft if v is True else ff if v is False else fv).format(k, str(v)) (ft if v is True else ff if v is False else fv).format(k, str(v))
for k, v in flags.items() for k, v in flags.items()
if k not in fx
] ]
if a: if a:
vpath = "?" vpath = "?"
@@ -594,6 +596,9 @@ class Up2k(object):
if stat.S_ISDIR(inf.st_mode): if stat.S_ISDIR(inf.st_mode):
if abspath in excl or abspath == histpath: if abspath in excl or abspath == histpath:
continue continue
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
# abandoned or foreign, skip
continue
# self.log(" dir: {}".format(abspath)) # self.log(" dir: {}".format(abspath))
try: try:
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen) ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)

View File

@@ -485,13 +485,13 @@ def vol_san(vols, txt):
return txt return txt
def min_ex(): def min_ex(max_lines=8, reverse=False):
et, ev, tb = sys.exc_info() et, ev, tb = sys.exc_info()
tb = traceback.extract_tb(tb) tb = traceback.extract_tb(tb)
fmt = "{} @ {} <{}>: {}" fmt = "{} @ {} <{}>: {}"
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb] ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
ex.append("[{}] {}".format(et.__name__, ev)) ex.append("[{}] {}".format(et.__name__, ev))
return "\n".join(ex[-8:]) return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
@contextlib.contextmanager @contextlib.contextmanager

View File

@@ -17,12 +17,11 @@ window.baguetteBox = (function () {
titleTag: false, titleTag: false,
async: false, async: false,
preload: 2, preload: 2,
animation: 'slideIn',
afterShow: null, afterShow: null,
afterHide: null, afterHide: null,
onChange: null, onChange: null,
}, },
overlay, slider, btnPrev, btnNext, btnHelp, btnRotL, btnRotR, btnSel, btnVmode, btnClose, overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnVmode, btnClose,
currentGallery = [], currentGallery = [],
currentIndex = 0, currentIndex = 0,
isOverlayVisible = false, isOverlayVisible = false,
@@ -30,6 +29,7 @@ window.baguetteBox = (function () {
touchFlag = false, // busy touchFlag = false, // busy
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i, re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
re_v = /.+\.(webm|mp4)(\?|$)/i, re_v = /.+\.(webm|mp4)(\?|$)/i,
anims = ['slideIn', 'fadeIn', 'none'],
data = {}, // all galleries data = {}, // all galleries
imagesElements = [], imagesElements = [],
documentLastFocus = null, documentLastFocus = null,
@@ -178,6 +178,7 @@ window.baguetteBox = (function () {
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' + '<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' +
'<div id="bbox-btns">' + '<div id="bbox-btns">' +
'<button id="bbox-help" type="button">?</button>' + '<button id="bbox-help" type="button">?</button>' +
'<button id="bbox-anim" type="button" tt="a">-</button>' +
'<button id="bbox-rotl" type="button">↶</button>' + '<button id="bbox-rotl" type="button">↶</button>' +
'<button id="bbox-rotr" type="button">↷</button>' + '<button id="bbox-rotr" type="button">↷</button>' +
'<button id="bbox-tsel" type="button">sel</button>' + '<button id="bbox-tsel" type="button">sel</button>' +
@@ -193,6 +194,7 @@ window.baguetteBox = (function () {
btnPrev = ebi('bbox-prev'); btnPrev = ebi('bbox-prev');
btnNext = ebi('bbox-next'); btnNext = ebi('bbox-next');
btnHelp = ebi('bbox-help'); btnHelp = ebi('bbox-help');
btnAnim = ebi('bbox-anim');
btnRotL = ebi('bbox-rotl'); btnRotL = ebi('bbox-rotl');
btnRotR = ebi('bbox-rotr'); btnRotR = ebi('bbox-rotr');
btnSel = ebi('bbox-tsel'); btnSel = ebi('bbox-tsel');
@@ -284,6 +286,16 @@ window.baguetteBox = (function () {
rotn(e.shiftKey ? -1 : 1); rotn(e.shiftKey ? -1 : 1);
} }
function anim() {
var i = (anims.indexOf(options.animation) + 1) % anims.length,
o = options;
swrite('ganim', anims[i]);
options = {};
setOptions(o);
if (tt.en)
tt.show.bind(this)();
}
function setVmode() { function setVmode() {
var v = vid(); var v = vid();
ebi('bbox-vmode').style.display = v ? '' : 'none'; ebi('bbox-vmode').style.display = v ? '' : 'none';
@@ -397,6 +409,7 @@ window.baguetteBox = (function () {
bind(btnClose, 'click', hideOverlay); bind(btnClose, 'click', hideOverlay);
bind(btnVmode, 'click', tglVmode); bind(btnVmode, 'click', tglVmode);
bind(btnHelp, 'click', halp); bind(btnHelp, 'click', halp);
bind(btnAnim, 'click', anim);
bind(btnRotL, 'click', rotl); bind(btnRotL, 'click', rotl);
bind(btnRotR, 'click', rotr); bind(btnRotR, 'click', rotr);
bind(btnSel, 'click', tglsel); bind(btnSel, 'click', tglsel);
@@ -414,6 +427,7 @@ window.baguetteBox = (function () {
unbind(btnClose, 'click', hideOverlay); unbind(btnClose, 'click', hideOverlay);
unbind(btnVmode, 'click', tglVmode); unbind(btnVmode, 'click', tglVmode);
unbind(btnHelp, 'click', halp); unbind(btnHelp, 'click', halp);
unbind(btnAnim, 'click', anim);
unbind(btnRotL, 'click', rotl); unbind(btnRotL, 'click', rotl);
unbind(btnRotR, 'click', rotr); unbind(btnRotR, 'click', rotr);
unbind(btnSel, 'click', tglsel); unbind(btnSel, 'click', tglsel);
@@ -459,7 +473,12 @@ window.baguetteBox = (function () {
if (typeof newOptions[item] !== 'undefined') if (typeof newOptions[item] !== 'undefined')
options[item] = newOptions[item]; options[item] = newOptions[item];
} }
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
var an = options.animation = sread('ganim') || anims[ANIM ? 0 : 2];
btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)];
btnAnim.setAttribute('tt', 'animation: ' + an);
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .3s ease' :
options.animation === 'slideIn' ? '' : 'none'); options.animation === 'slideIn' ? '' : 'none');
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
@@ -520,6 +539,7 @@ window.baguetteBox = (function () {
if (overlay.style.display === 'none') if (overlay.style.display === 'none')
return; return;
sethash('');
unbind(document, 'keydown', keyDownHandler); unbind(document, 'keydown', keyDownHandler);
unbind(document, 'keyup', keyUpHandler); unbind(document, 'keyup', keyUpHandler);
unbind(document, 'fullscreenchange', onFSC); unbind(document, 'fullscreenchange', onFSC);
@@ -806,7 +826,7 @@ window.baguetteBox = (function () {
slider.style.transform = 'translate3d(' + offset + ',0,0)' : slider.style.transform = 'translate3d(' + offset + ',0,0)' :
slider.style.left = offset; slider.style.left = offset;
slider.style.opacity = 1; slider.style.opacity = 1;
}, 400); }, 100);
} else { } else {
xform ? xform ?
slider.style.transform = 'translate3d(' + offset + ',0,0)' : slider.style.transform = 'translate3d(' + offset + ',0,0)' :

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@
<input type="file" name="f" multiple /><br /> <input type="file" name="f" multiple /><br />
<input type="submit" value="start upload"> <input type="submit" value="start upload">
</form> </form>
<a id="bbsw" href="?b=u"><br />switch to basic browser</a>
</div> </div>
<div id="op_mkdir" class="opview opbox act"> <div id="op_mkdir" class="opview opbox act">
@@ -134,6 +135,9 @@
<script> <script>
var acct = "{{ acct }}", var acct = "{{ acct }}",
perms = {{ perms }}, perms = {{ perms }},
themes = {{ themes }},
dtheme = "{{ dtheme }}",
srvinf = "{{ srv_info }}",
def_hcols = {{ def_hcols|tojson }}, def_hcols = {{ def_hcols|tojson }},
have_up2k_idx = {{ have_up2k_idx|tojson }}, have_up2k_idx = {{ have_up2k_idx|tojson }},
have_tags_idx = {{ have_tags_idx|tojson }}, have_tags_idx = {{ have_tags_idx|tojson }},
@@ -147,9 +151,10 @@
readme = {{ readme|tojson }}, readme = {{ readme|tojson }},
ls0 = {{ ls0|tojson }}; ls0 = {{ ls0|tojson }};
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark"); document.documentElement.setAttribute("class", localStorage.theme || dtheme);
</script> </script>
<script src="/.cpr/util.js?_={{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>
<script src="/.cpr/baguettebox.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>
{%- if js %} {%- if js %}

View File

@@ -7,16 +7,16 @@ function dbg(msg) {
// toolbar // toolbar
ebi('ops').innerHTML = ( ebi('ops').innerHTML = (
'<a href="#" data-dest="" tt="close submenu">--</a>\n' + '<a href="#" data-dest="" tt="close submenu">--</a>' +
'<a href="#" data-perm="read" data-dep="idx" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = start with yana and be an opus file$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = contain exactly «try unite»">🔎</a>\n' + '<a href="#" data-perm="read" data-dep="idx" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = start with yana and be an opus file$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = contain exactly «try unite»">🔎</a>' +
(have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>\n' : '') + (have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>' : '') +
'<a href="#" data-dest="up2k">🚀</a>\n' + '<a href="#" data-dest="up2k">🚀</a>' +
'<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>\n' + '<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>' +
'<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>\n' + '<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>' +
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>\n' + '<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>' +
'<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>\n' + '<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>' +
'<a href="#" data-dest="player" tt="media player options">🎺</a>\n' + '<a href="#" data-dest="player" tt="media player options">🎺</a>' +
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>\n' + '<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>' +
'<div id="opdesc"></div>' '<div id="opdesc"></div>'
); );
@@ -58,7 +58,7 @@ ebi('op_up2k').innerHTML = (
' <td class="c"><br />parallel uploads:</td>\n' + ' <td class="c"><br />parallel uploads:</td>\n' +
' <td class="c" rowspan="2">\n' + ' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="multitask" />\n' + ' <input type="checkbox" id="multitask" />\n' +
' <label for="multitask" tt="continue hashing other files while uploading">🏃</label>\n' + ' <label for="multitask" tt="continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck">🏃</label>\n' +
' </td>\n' + ' </td>\n' +
' <td class="c" rowspan="2">\n' + ' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="ask_up" />\n' + ' <input type="checkbox" id="ask_up" />\n' +
@@ -74,7 +74,7 @@ ebi('op_up2k').innerHTML = (
' <tr>\n' + ' <tr>\n' +
' <td class="c">\n' + ' <td class="c">\n' +
' <a href="#" class="b" id="nthread_sub">&ndash;</a><input\n' + ' <a href="#" class="b" id="nthread_sub">&ndash;</a><input\n' +
' class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' + ' class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0$N$Nincrease if your connection is slow / high latency$N$Nkeep it 1 on LAN or if the server HDD is a bottleneck"/><a\n' +
' href="#" class="b" id="nthread_add">+</a><br />&nbsp;\n' + ' href="#" class="b" id="nthread_add">+</a><br />&nbsp;\n' +
' </td>\n' + ' </td>\n' +
' </tr>\n' + ' </tr>\n' +
@@ -84,10 +84,9 @@ ebi('op_up2k').innerHTML = (
'<div id="u2btn_ct">\n' + '<div id="u2btn_ct">\n' +
' <div id="u2btn">\n' + ' <div id="u2btn">\n' +
' <span id="u2bm"></span><br />\n' + ' <span id="u2bm"></span>\n' +
' drag/drop files<br />\n' + ' drop files / folders<br />\n' +
' and folders here<br />\n' + ' here (or click me)\n' +
' (or click me)\n' +
' </div>\n' + ' </div>\n' +
'</div>\n' + '</div>\n' +
@@ -121,8 +120,7 @@ ebi('op_up2k').innerHTML = (
'</table></div>\n' + '</table></div>\n' +
'<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' + '<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' +
'<p id="u2foot"></p>\n' + '<p id="u2foot"></p>'
'<p id="u2footfoot" data-perm="write">( you can use the <a href="#" id="u2nope">basic uploader</a> if you don\'t need lastmod timestamps, resumable uploads, or progress bars )</p>'
); );
@@ -146,7 +144,6 @@ ebi('op_cfg').innerHTML = (
' <h3>switches</h3>\n' + ' <h3>switches</h3>\n' +
' <div>\n' + ' <div>\n' +
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' + ' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' +
' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' +
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' + ' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' + ' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' + ' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
@@ -154,6 +151,11 @@ ebi('op_cfg').innerHTML = (
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' + ' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
' </div>\n' + ' </div>\n' +
'</div>\n' + '</div>\n' +
'<div>\n' +
' <h3>themes</h3>\n' +
' <div id="themes">\n' +
' </div>\n' +
'</div>\n' +
(have_zip ? ( (have_zip ? (
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n' '<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
) : '') + ) : '') +
@@ -262,6 +264,8 @@ function goto(dest) {
fn(); fn();
} }
clmod(document.documentElement, 'op_open', dest);
if (window['treectl']) if (window['treectl'])
treectl.onscroll(); treectl.onscroll();
} }
@@ -427,7 +431,7 @@ var mpl = (function () {
}; };
function announce() { function announce() {
if (!r.os_ctl) if (!r.os_ctl || !mp.au)
return; return;
var np = get_np()[0], var np = get_np()[0],
@@ -509,7 +513,7 @@ catch (ex) { }
var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i : var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i :
have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i, have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i,
re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv)$/i; re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv|mpc)$/i;
// extract songs + add play column // extract songs + add play column
@@ -703,8 +707,12 @@ var widget = (function () {
return false; return false;
clmod(document.documentElement, 'np_open', is_open); clmod(document.documentElement, 'np_open', is_open);
widget.className = is_open ? 'open' : ''; clmod(widget, 'open', is_open);
bcfg_set('au_open', r.is_open = is_open); bcfg_set('au_open', r.is_open = is_open);
if (window.vbar) {
pbar.onresize();
vbar.onresize();
}
return true; return true;
}; };
r.toggle = function (e) { r.toggle = function (e) {
@@ -743,17 +751,29 @@ var widget = (function () {
o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9'; o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
o.value = m; o.value = m;
document.body.appendChild(o); document.body.appendChild(o);
o.focus();
o.select(); var cln = function () {
document.execCommand("copy");
o.value = 'copied to clipboard '; o.value = 'copied to clipboard ';
setTimeout(function () { setTimeout(function () {
document.body.removeChild(o); document.body.removeChild(o);
}, 500); }, 500);
}; };
var fb = function () {
console.log('fb');
o.focus();
o.select();
document.execCommand("copy");
cln();
};
try {
// https only
navigator.clipboard.writeText(m).then(cln, fb);
}
catch (ex) { fb(); }
};
r.set(sread('au_open') == 1); r.set(sread('au_open') == 1);
setTimeout(function () { setTimeout(function () {
clmod(ebi('widget'), 'anim', 1); clmod(widget, 'anim', 1);
}, 10); }, 10);
return r; return r;
})(); })();
@@ -794,6 +814,9 @@ var pbar = (function () {
grad; grad;
r.onresize = function () { r.onresize = function () {
if (!widget.is_open && r.buf)
return;
r.buf = canvas_cfg(ebi('barbuf')); r.buf = canvas_cfg(ebi('barbuf'));
r.pos = canvas_cfg(ebi('barpos')); r.pos = canvas_cfg(ebi('barpos'));
r.drawbuf(); r.drawbuf();
@@ -895,6 +918,9 @@ var vbar = (function () {
can, ctx, w, h, grad1, grad2; can, ctx, w, h, grad1, grad2;
r.onresize = function () { r.onresize = function () {
if (!widget.is_open && r.can)
return;
r.can = canvas_cfg(ebi('pvol')); r.can = canvas_cfg(ebi('pvol'));
can = r.can.can; can = r.can.can;
ctx = r.can.ctx; ctx = r.can.ctx;
@@ -1567,25 +1593,62 @@ function autoplay_blocked(seek) {
} }
function scan_hash(v) {
if (!v)
return null;
var m = /^#([ag])(f-[0-9a-f]{8,16})(&.+)?/.exec(v + '');
if (!m)
return null;
var mtype = m[1],
id = m[2],
ts = null;
if (m.length > 3) {
m = /^&[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(m[3]);
if (m) {
ts = parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0);
}
}
return [mtype, id, ts];
}
function eval_hash() { function eval_hash() {
var v = hash0; var v = hash0;
hash0 = null; hash0 = null;
if (!v) if (!v)
return; return;
if (v.indexOf('#af-') === 0) { var media = scan_hash(v);
var id = v.slice(2).split('&'); if (media) {
if (id[0].length < 10) var mtype = media[0],
id = media[1],
ts = media[2];
if (mtype == 'a') {
if (!ts)
return play(id);
return play(id, false, ts);
}
if (mtype == 'g') {
if (!thegrid.en)
ebi('griden').click();
var t = setInterval(function () {
if (!thegrid.bbox)
return; return;
if (id.length == 1) clearInterval(t);
return play(id[0]); var im = QS('#ggrid a[ref="' + id + '"]');
im.click();
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]); im.scrollIntoView();
if (!m) }, 50);
return play(id[0]); }
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
} }
if (v.indexOf('#q=') === 0) { if (v.indexOf('#q=') === 0) {
@@ -2314,10 +2377,12 @@ var showfile = (function () {
'.bas': 'basic', '.bas': 'basic',
'.bat': 'batch', '.bat': 'batch',
'.cxx': 'cpp', '.cxx': 'cpp',
'.diz': 'ans',
'.h': 'c', '.h': 'c',
'.hpp': 'cpp', '.hpp': 'cpp',
'.htm': 'html', '.htm': 'html',
'.hxx': 'cpp', '.hxx': 'cpp',
'.log': 'ans',
'.patch': 'diff', '.patch': 'diff',
'.ps1': 'powershell', '.ps1': 'powershell',
'.psm1': 'powershell', '.psm1': 'powershell',
@@ -2334,7 +2399,7 @@ var showfile = (function () {
'cmakelists.txt': 'cmake', 'cmakelists.txt': 'cmake',
'dockerfile': 'docker' 'dockerfile': 'docker'
}; };
var x = txt_ext + ' c cfg conf cpp cs css diff go html ini java js json jsx kt kts less latex lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml'; var x = txt_ext + ' ans c cfg conf cpp cs css diff go html ini java js json jsx kt kts latex less lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml';
x = x.split(/ +/g); x = x.split(/ +/g);
for (var a = 0; a < x.length; a++) for (var a = 0; a < x.length; a++)
r.map["." + x[a]] = x[a]; r.map["." + x[a]] = x[a];
@@ -2441,7 +2506,10 @@ var showfile = (function () {
if (lnh.slice(0, 5) == '#doc.') if (lnh.slice(0, 5) == '#doc.')
sethash(lnh.slice(1)); sethash(lnh.slice(1));
Prism.highlightElement(el || QS('#doc>code')); el = el || QS('#doc>code');
Prism.highlightElement(el);
if (el.getAttribute('class') == 'language-ans')
r.ansify(el);
} }
catch (ex) { } catch (ex) { }
} }
@@ -2489,6 +2557,65 @@ var showfile = (function () {
tree_scrollto(); tree_scrollto();
} }
r.ansify = function (el) {
var ctab = (light ?
'bfbfbf d30253 497600 b96900 006fbb a50097 288276 2d2d2d 9f9f9f 943b55 3a5600 7f4f00 00507d 683794 004343 000000' :
'404040 f03669 b8e346 ffa402 02a2ff f65be3 3da698 d2d2d2 606060 c75b79 c8e37e ffbe4a 71cbff b67fe3 9cf0ed ffffff').split(/ /g),
src = el.innerHTML.split(/\x1b\[/g),
out = ['<span>'], fg = 7, bg = null, bfg = 0, bbg = 0, inv = 0, bold = 0;
for (var a = 0; a < src.length; a++) {
var m = /^([0-9;]+)m/.exec(src[a]);
if (!m) {
if (a || src[a])
out.push('\x1b[' + src[a]);
continue;
}
var cs = m[1].split(/;/g),
txt = src[a].slice(m[1].length + 1);
for (var b = 0; b < cs.length; b++) {
var c = parseInt(cs[b]);
if (c == 0) {
fg = 7;
bg = null;
bfg = bbg = bold = inv = 0;
}
if (c == 1) bfg = bold = 1;
if (c == 7) inv = 1;
if (c == 22) bfg = bold = 0;
if (c == 27) inv = 0;
if (c >= 30 && c <= 37) fg = c - 30;
if (c >= 40 && c <= 47) bg = c - 40;
if (c >= 90 && c <= 97) {
fg = c - 90;
bfg = 1;
}
if (c >= 100 && c <= 107) {
bg = c - 100;
bbg = 1;
}
}
var cfg = fg, cbg = bg;
if (inv) {
cbg = fg;
cfg = bg || 0;
}
var s = '</span><span style="color:#' + ctab[cfg + bfg * 8];
if (cbg !== null)
s += ';background:#' + ctab[cbg + bbg * 8];
if (bold)
s += ';font-weight:bold';
out.push(s + '">' + txt);
}
el.innerHTML = out.join('');
};
r.mktree = function () { r.mktree = function () {
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>']; var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
for (var a = 0; a < r.files.length; a++) { for (var a = 0; a < r.files.length; a++) {
@@ -2860,6 +2987,9 @@ var thegrid = (function () {
return '<a download href="' + h + return '<a download href="' + h +
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' + '">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
esc(uricom_dec(h.split('/').pop())[0]) + '</a>'; esc(uricom_dec(h.split('/').pop())[0]) + '</a>';
},
onChange: function (i) {
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
} }
})[0]; })[0];
}; };
@@ -2873,10 +3003,6 @@ var thegrid = (function () {
}); });
ebi('wtgrid').onclick = ebi('griden').onclick; ebi('wtgrid').onclick = ebi('griden').onclick;
setTimeout(function () {
import_js('/.cpr/baguettebox.js', r.bagit);
}, 1);
return r; return r;
})(); })();
@@ -3612,7 +3738,7 @@ var treectl = (function () {
treeh = winh - atop; treeh = winh - atop;
tree.style.top = top + 'px'; tree.style.top = top + 'px';
tree.style.height = treeh < 10 ? '' : Math.floor(treeh - 2) + 'px'; tree.style.height = treeh < 10 ? '' : Math.floor(treeh) + 'px';
} }
} }
timer.add(onscroll2, true); timer.add(onscroll2, true);
@@ -3880,7 +4006,8 @@ var treectl = (function () {
return; return;
} }
ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>'; srvinf = res.srvinf;
ebi('srv_info').innerHTML = ebi('srv_info2').innerHTML = '<span>' + res.srvinf + '</span>';
if (this.hpush && !showfile.active()) if (this.hpush && !showfile.active())
hist_push(this.top); hist_push(this.top);
@@ -3954,6 +4081,7 @@ var treectl = (function () {
html = html.join('\n'); html = html.join('\n');
set_files_html(html); set_files_html(html);
function asdf() {
filecols.set_style(); filecols.set_style();
showfile.mktree(); showfile.mktree();
mukey.render(); mukey.render();
@@ -3968,7 +4096,27 @@ var treectl = (function () {
} }
} }
var m = scan_hash(hash0),
url = null;
if (m) {
url = ebi(m[1]);
if (url) {
url = url.href;
var mt = m[0] == 'a' ? 'audio' : /\.(webm|mkv)($|\?)/i.exec(url) ? 'video' : 'image'
if (mt == 'image') {
url += url.indexOf('?') < 0 ? '?cache' : '&cache';
console.log(url);
new Image().src = url;
}
}
}
if (url) setTimeout(asdf, 1); else asdf();
}
r.hydrate = function () { r.hydrate = function () {
qsr('#bbsw');
if (ls0 === null) { if (ls0 === null) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', '/?am_js', true); xhr.open('GET', '/?am_js', true);
@@ -3978,12 +4126,9 @@ var treectl = (function () {
} }
r.gentab(get_evpath(), ls0); r.gentab(get_evpath(), ls0);
reload_browser();
pbar.onresize(); pbar.onresize();
vbar.onresize(); vbar.onresize();
mukey.render();
showfile.addlinks(); showfile.addlinks();
thegrid.setdirty();
setTimeout(eval_hash, 1); setTimeout(eval_hash, 1);
}; };
@@ -4082,14 +4227,16 @@ function apply_perms(newperms) {
perms = newperms || []; perms = newperms || [];
var a = QS('#ops a[data-dest="up2k"]'); var a = QS('#ops a[data-dest="up2k"]');
var suf = 'multithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader';
if (have_up2k_idx) { if (have_up2k_idx) {
a.removeAttribute('data-perm'); a.removeAttribute('data-perm');
a.setAttribute('tt', 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server'); a.setAttribute('tt', 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server$N$Nuploads are resumable, ' + suf);
} }
else { else {
a.setAttribute('data-perm', 'write'); a.setAttribute('data-perm', 'write');
a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)'); a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$N' + suf);
} }
a.style.display = '';
tt.att(QS('#ops')); tt.att(QS('#ops'));
var axs = [], var axs = [],
@@ -4106,10 +4253,11 @@ function apply_perms(newperms) {
axs += '-Only'; axs += '-Only';
} }
ebi('acc_info').innerHTML = '<span' + aclass + axs + ' access</span>' + (acct != '*' ? ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
'</span></span><span' + aclass + axs + ' access</span>' + (acct != '*' ?
'<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>'); '<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>');
var o = QSA('#ops>a[data-perm], #u2footfoot'); var o = QSA('#ops>a[data-perm]');
for (var a = 0; a < o.length; a++) { for (var a = 0; a < o.length; a++) {
var display = ''; var display = '';
var needed = o[a].getAttribute('data-perm').split(' '); var needed = o[a].getAttribute('data-perm').split(' ');
@@ -4464,20 +4612,51 @@ var mukey = (function () {
})(); })();
var light; var light, theme;
(function () { var settheme = (function () {
var ax = 'abcdefghijklmnopqrstuvwx';
theme = sread('theme') || 'a';
if (!/^[a-x][yz]/.exec(theme))
theme = dtheme;
light = !!(theme.indexOf('y') + 1);
function freshen() { function freshen() {
clmod(document.documentElement, "light", light); var cl = document.documentElement.getAttribute('class');
clmod(document.documentElement, "dark", !light); cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' ');
document.documentElement.setAttribute('class', cl + ' ' + theme + ' ');
pbar.drawbuf(); pbar.drawbuf();
pbar.drawpos(); pbar.drawpos();
vbar.draw(); vbar.draw();
showfile.setstyle(); showfile.setstyle();
var html = [], itheme = ax.indexOf(theme.charAt(0)) * 2 + (light ? 1 : 0);
for (var a = 0; a < themes; a++)
html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') + '">' + a + '</a>');
ebi('themes').innerHTML = html.join('');
var btns = QSA('#themes a');
for (var a = 0; a < themes; a++)
btns[a].onclick = settheme;
bcfg_set('light', light);
} }
bcfg_bind(window, 'light', 'lightmode', false, freshen); function settheme(e) {
var i = e;
try { ev(e); i = e.target.textContent; } catch (ex) { }
light = i % 2 == 1;
var c = ax.charAt(Math.floor(i / 2)),
l = light ? 'y' : 'z';
theme = c + l + ' ' + c + ' ' + l + ' ' + (light ? 'light ' : 'dark ');
swrite('theme', theme);
freshen();
}
freshen(); freshen();
return settheme;
})(); })();

View File

@@ -136,13 +136,13 @@ var md_opt = {
(function () { (function () {
var l = localStorage, var l = localStorage,
drk = l.lightmode != 1, drk = l.light != 1,
btn = document.getElementById("lightswitch"), btn = document.getElementById("lightswitch"),
f = function (e) { f = function (e) {
if (e) { e.preventDefault(); drk = !drk; } if (e) { e.preventDefault(); drk = !drk; }
document.documentElement.setAttribute("class", drk? "dark":"light"); document.documentElement.setAttribute("class", drk? "dark":"light");
btn.innerHTML = "go " + (drk ? "light":"dark"); btn.innerHTML = "go " + (drk ? "light":"dark");
l.lightmode = drk? 0:1; l.light = drk? 0:1;
}; };
btn.onclick = f; btn.onclick = f;

View File

@@ -34,11 +34,11 @@ var md_opt = {
var lightswitch = (function () { var lightswitch = (function () {
var l = localStorage, var l = localStorage,
drk = l.lightmode != 1, drk = l.light != 1,
f = function (e) { f = function (e) {
if (e) drk = !drk; if (e) drk = !drk;
document.documentElement.setAttribute("class", drk? "dark":"light"); document.documentElement.setAttribute("class", drk? "dark":"light");
l.lightmode = drk? 0:1; l.light = drk? 0:1;
}; };
f(); f();
return f; return f;

View File

@@ -97,7 +97,7 @@
<a href="#" id="repl">π</a> <a href="#" id="repl">π</a>
<script> <script>
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark"); document.documentElement.setAttribute("class", localStorage.light == 1 ? "light" : "dark");
</script> </script>
<script src="/.cpr/util.js?_={{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>

View File

@@ -644,12 +644,6 @@ function up2k_init(subtle) {
return false; return false;
} }
ebi('u2nope').onclick = function (e) {
ev(e);
setmsg(suggest_up2k, 'msg');
goto('bup');
};
setmsg(suggest_up2k, 'msg'); setmsg(suggest_up2k, 'msg');
if (!String.prototype.format) { if (!String.prototype.format) {
@@ -1173,7 +1167,7 @@ function up2k_init(subtle) {
var t = st.todo.handshake[0], var t = st.todo.handshake[0],
cd = t.cooldown; cd = t.cooldown;
if (cd && cd - Date.now() > 0) if (cd && cd > Date.now())
return false; return false;
// keepalive or verify // keepalive or verify
@@ -1370,6 +1364,14 @@ function up2k_init(subtle) {
return taskerd; return taskerd;
})(); })();
function chill(t) {
var now = Date.now();
if ((t.coolmul || 0) < 2 || now - t.cooldown < t.coolmul * 700)
t.coolmul = Math.min((t.coolmul || 0.5) * 2, 32);
t.cooldown = Math.max(t.cooldown || 1, Date.now() + t.coolmul * 1000);
}
///// /////
//// ////
/// hashing /// hashing
@@ -1468,7 +1470,6 @@ function up2k_init(subtle) {
min_filebuf = 1; min_filebuf = 1;
var td = Date.now() - t0; var td = Date.now() - t0;
if (td > 50) { if (td > 50) {
ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
min_filebuf = 32 * 1024 * 1024; min_filebuf = 32 * 1024 * 1024;
} }
} }
@@ -1756,8 +1757,12 @@ function up2k_init(subtle) {
pvis.move(t.n, 'ok'); pvis.move(t.n, 'ok');
} }
else t.t_uploaded = undefined; else {
if (t.t_uploaded)
chill(t);
t.t_uploaded = undefined;
}
tasker(); tasker();
} }
else { else {
@@ -1869,7 +1874,8 @@ function up2k_init(subtle) {
else { else {
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format( toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + (txt || "no further information")); xhr.status, t.name) + (txt || "no further information"));
return;
chill(t);
} }
orz2(xhr); orz2(xhr);
} }
@@ -1920,6 +1926,7 @@ function up2k_init(subtle) {
// //
function onresize(e) { function onresize(e) {
// 10x faster than matchMedia('(min-width
var bar = ebi('ops'), var bar = ebi('ops'),
wpx = window.innerWidth, wpx = window.innerWidth,
fpx = parseInt(getComputedStyle(bar)['font-size']), fpx = parseInt(getComputedStyle(bar)['font-size']),
@@ -1929,7 +1936,6 @@ function up2k_init(subtle) {
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'), parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
btn = ebi('u2btn'); btn = ebi('u2btn');
//console.log([wpx, fpx, wem]);
if (btn.parentNode !== parent) { if (btn.parentNode !== parent) {
parent.appendChild(btn); parent.appendChild(btn);
ebi('u2conf').setAttribute('class', wide); ebi('u2conf').setAttribute('class', wide);
@@ -2057,10 +2063,10 @@ function up2k_init(subtle) {
try { try {
var ico = uc.fsearch ? '🔎' : '🚀', var ico = uc.fsearch ? '🔎' : '🚀',
desc = uc.fsearch ? 'Search' : 'Upload'; desc = uc.fsearch ? 'S E A R C H' : 'U P L O A D';
clmod(ebi('op_up2k'), 'srch', uc.fsearch); clmod(ebi('op_up2k'), 'srch', uc.fsearch);
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>'; ebi('u2bm').innerHTML = ico + '&nbsp; <sup>' + desc + '</sup>';
} }
catch (ex) { } catch (ex) { }

View File

@@ -332,6 +332,16 @@ function clgot(el, cls) {
} }
var ANIM = true;
if (window.matchMedia) {
var mq = window.matchMedia('(prefers-reduced-motion: reduce)');
mq.onchange = function () {
ANIM = !mq.matches;
};
ANIM = !mq.matches;
}
function showsort(tab) { function showsort(tab) {
var v, vn, v1, v2, th = tab.tHead, var v, vn, v1, v2, th = tab.tHead,
sopts = jread('fsort', [["href", 1, ""]]); sopts = jread('fsort', [["href", 1, ""]]);
@@ -872,7 +882,7 @@ var tt = (function () {
}; };
r.getmsg = function (el) { r.getmsg = function (el) {
if (QS('body.bbox-open')) if (IPHONE && QS('body.bbox-open'))
return; return;
var cfg = sread('tooltips'); var cfg = sread('tooltips');

View File

@@ -342,14 +342,15 @@ def get_payload():
def utime(top): def utime(top):
# avoid cleaners
i = 0 i = 0
files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df] files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
while WINDOWS: while WINDOWS or os.path.exists("/etc/systemd"):
t = int(time.time()) t = int(time.time())
if i: if i:
msg("utime {}, {}".format(i, t)) msg("utime {}, {}".format(i, t))
for f in files: for f in [top] + files:
os.utime(f, (t, t)) os.utime(f, (t, t))
i += 1 i += 1
@@ -374,16 +375,6 @@ def run(tmp, j2, ftp):
msg("sfxdir:", tmp) msg("sfxdir:", tmp)
msg() msg()
# block systemd-tmpfiles-clean.timer
try:
import fcntl
fd = os.open(tmp, os.O_RDONLY)
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except Exception as ex:
if not WINDOWS:
msg("\033[31mflock:{!r}\033[0m".format(ex))
t = threading.Thread(target=utime, args=(tmp,)) t = threading.Thread(target=utime, args=(tmp,))
t.daemon = True t.daemon = True
t.start() t.start()

View File

@@ -114,9 +114,10 @@ args = {
"install_requires": ["jinja2"], "install_requires": ["jinja2"],
"extras_require": { "extras_require": {
"thumbnails": ["Pillow"], "thumbnails": ["Pillow"],
"thumbnails2": ["pyvips"],
"audiotags": ["mutagen"], "audiotags": ["mutagen"],
"ftpd": ["pyftpdlib"], "ftpd": ["pyftpdlib"],
"ftps": ["pyopenssl"], "ftps": ["pyftpdlib", "pyopenssl"],
}, },
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]}, "entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
"scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"], "scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"],