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
* ☑ textfile browser with syntax hilighting
* ☑ [thumbnails](#thumbnails)
* ☑ ...of images using Pillow
* ☑ ...of images using Pillow, pyvips, or FFmpeg
* ☑ ...of videos using FFmpeg
* ☑ ...of audio (spectrograms) using FFmpeg
* ☑ 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)
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`)
@@ -1096,10 +1098,13 @@ enable music tags:
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
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`
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler)
* **AVIF pictures:** `pillow-avif-plugin`
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
* **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

View File

@@ -14,7 +14,6 @@ save one of these as `.epilogue.html` inside a folder to customize it:
## example browser-css
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

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
# following line to enable buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x
#
# keep ExecStartPre before ExecStart, at least on rhel8
[Unit]
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-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-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-webp", action="store_true", help="disable webp output")
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-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")
# 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.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 = 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("--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")

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 2, 2)
VERSION = (1, 2, 4)
CODENAME = "ftp btw"
BUILD_DT = (2022, 3, 20)
BUILD_DT = (2022, 4, 14)
S_VERSION = ".".join(map(str, VERSION))
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())
umap = {x: [] for x 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)
if usr in axs or "*" in axs:
umap[usr].append(mp)
umap[usr].append(vp)
umap[usr].sort()
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])
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():
fk = vol.flags.get("fk")
if fk:

View File

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

View File

@@ -17,7 +17,7 @@ from .util import Unrecv
from .httpcli import HttpCli
from .u2idx import U2idx
from .th_cli import ThumbCli
from .th_srv import HAVE_PIL
from .th_srv import HAVE_PIL, HAVE_VIPS
from .ico import Ico
@@ -38,7 +38,7 @@ class HttpConn(object):
self.cert_path = hsrv.cert_path
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.ico = Ico(self.args)

View File

@@ -70,6 +70,12 @@ class HttpSrv(object):
self.cb_ts = 0
self.cb_v = 0
try:
x = self.broker.put(True, "thumbsrv.getcfg")
self.th_cfg = x.get()
except:
pass
env = jinja2.Environment()
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
self.j2 = {

View File

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

View File

@@ -5,7 +5,7 @@ import tarfile
import threading
from .sutil import errdesc
from .util import Queue, fsenc
from .util import Queue, fsenc, min_ex
from .bos import bos
@@ -88,8 +88,9 @@ class StreamTar(object):
try:
self.ser(f)
except Exception as ex:
errors.append([f["vp"], repr(ex)])
except Exception:
ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if 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 .tcpsrv import TcpSrv
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
@@ -70,6 +70,10 @@ class SvcHub(object):
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
self.asrv = AuthSrv(self.args, self.log)
if args.ls:
@@ -78,20 +82,30 @@ class SvcHub(object):
self.tcpsrv = TcpSrv(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
if not args.no_thumb:
if HAVE_PIL:
if not HAVE_WEBP:
args.th_no_webp = True
msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
self.log("thumb", msg, c=3)
m = "decoder preference: {}".format(", ".join(self.args.th_dec))
self.log("thumb", m)
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)
if self.args.th_dec:
self.thumbsrv = ThumbSrv(self)
else:
msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n"
self.log(
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3
)
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"
msg = msg.format(" " * 37, os.path.basename(sys.executable))
self.log("thumb", msg, c=3)
if not args.no_acode and args.no_thumb:
msg = "setting --no-acode because --no-thumb (sorry)"

View File

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

View File

@@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
import os
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
@@ -18,30 +18,53 @@ class ThumbCli(object):
# cache on both sides for less broker spam
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):
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()
if ext not in THUMBABLE:
if ext not in self.thumbable or "dthumb" in dbv.flags:
return None
is_vid = ext in FMT_FFV
if is_vid and self.args.no_vthumb:
is_vid = ext in self.fmt_ffv
if is_vid and "dvthumb" in dbv.flags:
return None
want_opus = fmt in ("opus", "caf")
is_au = ext in FMT_FFA
is_au = ext in self.fmt_ffa
if is_au:
if want_opus:
if self.args.no_acode:
return None
else:
if self.args.no_athumb:
if "dathumb" in dbv.flags:
return None
elif want_opus:
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"]:
return os.path.join(ptop, rem)
@@ -49,7 +72,11 @@ class ThumbCli(object):
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"
histpath = self.asrv.vfs.histtab.get(ptop)
@@ -58,15 +85,23 @@ class ThumbCli(object):
return None
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
try:
st = bos.stat(tpath)
if st.st_size:
ret = tpath
else:
return None
except:
pass
abort = False
for tp in tpaths:
try:
st = bos.stat(tp)
if st.st_size:
ret = tpath = tp
fmt = ret.rsplit(".")[1]
else:
abort = True
except:
pass
if ret:
tdir = os.path.dirname(tpath)
@@ -80,5 +115,8 @@ class ThumbCli(object):
return ret
if abort:
return None
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
return x.get()

View File

@@ -47,31 +47,12 @@ try:
except:
pass
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# ffmpeg -formats
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"
try:
import pyvips
if HAVE_HEIF:
FMT_PIL += " heif heifs heic heics"
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)
HAVE_VIPS = True
except:
HAVE_VIPS = False
def thumb_path(histpath, rem, mtime, fmt):
@@ -141,6 +122,37 @@ class ThumbSrv(object):
t.daemon = True
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):
self.log_func("thumb", msg, c)
@@ -201,6 +213,16 @@ class ThumbSrv(object):
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):
while not self.stopping:
task = self.q.get()
@@ -211,15 +233,20 @@ class ThumbSrv(object):
ext = abspath.split(".")[-1].lower()
fun = None
if not bos.path.exists(tpath):
if ext in FMT_PIL:
fun = self.conv_pil
elif ext in FMT_FFV:
fun = self.conv_ffmpeg
elif ext in FMT_FFA:
if tpath.endswith(".opus") or tpath.endswith(".caf"):
fun = self.conv_opus
else:
fun = self.conv_spec
for lib in self.args.th_dec:
if fun:
break
elif lib == "pil" and ext in self.fmt_pil:
fun = self.conv_pil
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
elif lib == "ff" and ext in self.fmt_ffa:
if tpath.endswith(".opus") or tpath.endswith(".caf"):
fun = self.conv_opus
else:
fun = self.conv_spec
if fun:
try:
@@ -296,11 +323,29 @@ class ThumbSrv(object):
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):
ret, _ = ffprobe(abspath)
ext = abspath.rsplit(".")[-1]
if ext in ["h264", "h265"]:
ext = abspath.rsplit(".")[-1].lower()
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
seek = []
else:
dur = ret[".dur"][1] if ".dur" in ret else 4
@@ -350,11 +395,31 @@ class ThumbSrv(object):
def _run_ff(self, cmd):
# self.log((b" ".join(cmd)).decode("utf-8"))
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
if ret != 0:
m = "FFmpeg failed (probably a corrupt video file):\n"
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
self.log(m, c="1;30")
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
if not ret:
return
c = "1;30"
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")])
self.log(m, c=c)
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
def conv_spec(self, abspath, tpath):
ret, _ = ffprobe(abspath)

View File

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

View File

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

View File

@@ -17,12 +17,11 @@ window.baguetteBox = (function () {
titleTag: false,
async: false,
preload: 2,
animation: 'slideIn',
afterShow: null,
afterHide: 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 = [],
currentIndex = 0,
isOverlayVisible = false,
@@ -30,6 +29,7 @@ window.baguetteBox = (function () {
touchFlag = false, // busy
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
re_v = /.+\.(webm|mp4)(\?|$)/i,
anims = ['slideIn', 'fadeIn', 'none'],
data = {}, // all galleries
imagesElements = [],
documentLastFocus = null,
@@ -178,6 +178,7 @@ window.baguetteBox = (function () {
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' +
'<div id="bbox-btns">' +
'<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-rotr" type="button">↷</button>' +
'<button id="bbox-tsel" type="button">sel</button>' +
@@ -193,6 +194,7 @@ window.baguetteBox = (function () {
btnPrev = ebi('bbox-prev');
btnNext = ebi('bbox-next');
btnHelp = ebi('bbox-help');
btnAnim = ebi('bbox-anim');
btnRotL = ebi('bbox-rotl');
btnRotR = ebi('bbox-rotr');
btnSel = ebi('bbox-tsel');
@@ -284,6 +286,16 @@ window.baguetteBox = (function () {
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() {
var v = vid();
ebi('bbox-vmode').style.display = v ? '' : 'none';
@@ -397,6 +409,7 @@ window.baguetteBox = (function () {
bind(btnClose, 'click', hideOverlay);
bind(btnVmode, 'click', tglVmode);
bind(btnHelp, 'click', halp);
bind(btnAnim, 'click', anim);
bind(btnRotL, 'click', rotl);
bind(btnRotR, 'click', rotr);
bind(btnSel, 'click', tglsel);
@@ -414,6 +427,7 @@ window.baguetteBox = (function () {
unbind(btnClose, 'click', hideOverlay);
unbind(btnVmode, 'click', tglVmode);
unbind(btnHelp, 'click', halp);
unbind(btnAnim, 'click', anim);
unbind(btnRotL, 'click', rotl);
unbind(btnRotR, 'click', rotr);
unbind(btnSel, 'click', tglsel);
@@ -459,7 +473,12 @@ window.baguetteBox = (function () {
if (typeof newOptions[item] !== 'undefined')
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');
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
@@ -520,6 +539,7 @@ window.baguetteBox = (function () {
if (overlay.style.display === 'none')
return;
sethash('');
unbind(document, 'keydown', keyDownHandler);
unbind(document, 'keyup', keyUpHandler);
unbind(document, 'fullscreenchange', onFSC);
@@ -806,7 +826,7 @@ window.baguetteBox = (function () {
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
slider.style.left = offset;
slider.style.opacity = 1;
}, 400);
}, 100);
} else {
xform ?
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="submit" value="start upload">
</form>
<a id="bbsw" href="?b=u"><br />switch to basic browser</a>
</div>
<div id="op_mkdir" class="opview opbox act">
@@ -134,6 +135,9 @@
<script>
var acct = "{{ acct }}",
perms = {{ perms }},
themes = {{ themes }},
dtheme = "{{ dtheme }}",
srvinf = "{{ srv_info }}",
def_hcols = {{ def_hcols|tojson }},
have_up2k_idx = {{ have_up2k_idx|tojson }},
have_tags_idx = {{ have_tags_idx|tojson }},
@@ -147,9 +151,10 @@
readme = {{ readme|tojson }},
ls0 = {{ ls0|tojson }};
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
document.documentElement.setAttribute("class", localStorage.theme || dtheme);
</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/up2k.js?_={{ ts }}"></script>
{%- if js %}

View File

@@ -7,16 +7,16 @@ function dbg(msg) {
// toolbar
ebi('ops').innerHTML = (
'<a href="#" data-dest="" tt="close submenu">--</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>\n' +
(have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>\n' : '') +
'<a href="#" data-dest="up2k">🚀</a>\n' +
'<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="mkdir" tt="mkdir: create a new directory">📂</a>\n' +
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>\n' +
'<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>\n' +
'<a href="#" data-dest="player" tt="media player options">🎺</a>\n' +
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</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>' +
(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>' +
'<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>' +
'<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>' +
'<a href="#" data-dest="player" tt="media player options">🎺</a>' +
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>' +
'<div id="opdesc"></div>'
);
@@ -58,7 +58,7 @@ ebi('op_up2k').innerHTML = (
' <td class="c"><br />parallel uploads:</td>\n' +
' <td class="c" rowspan="2">\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 class="c" rowspan="2">\n' +
' <input type="checkbox" id="ask_up" />\n' +
@@ -74,7 +74,7 @@ ebi('op_up2k').innerHTML = (
' <tr>\n' +
' <td class="c">\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' +
' </td>\n' +
' </tr>\n' +
@@ -84,10 +84,9 @@ ebi('op_up2k').innerHTML = (
'<div id="u2btn_ct">\n' +
' <div id="u2btn">\n' +
' <span id="u2bm"></span><br />\n' +
' drag/drop files<br />\n' +
' and folders here<br />\n' +
' (or click me)\n' +
' <span id="u2bm"></span>\n' +
' drop files / folders<br />\n' +
' here (or click me)\n' +
' </div>\n' +
'</div>\n' +
@@ -121,8 +120,7 @@ ebi('op_up2k').innerHTML = (
'</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="u2foot"></p>\n' +
'<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>'
'<p id="u2foot"></p>'
);
@@ -146,7 +144,6 @@ ebi('op_cfg').innerHTML = (
' <h3>switches</h3>\n' +
' <div>\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="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' +
@@ -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' +
' </div>\n' +
'</div>\n' +
'<div>\n' +
' <h3>themes</h3>\n' +
' <div id="themes">\n' +
' </div>\n' +
'</div>\n' +
(have_zip ? (
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
) : '') +
@@ -262,6 +264,8 @@ function goto(dest) {
fn();
}
clmod(document.documentElement, 'op_open', dest);
if (window['treectl'])
treectl.onscroll();
}
@@ -427,7 +431,7 @@ var mpl = (function () {
};
function announce() {
if (!r.os_ctl)
if (!r.os_ctl || !mp.au)
return;
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 :
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
@@ -703,8 +707,12 @@ var widget = (function () {
return false;
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);
if (window.vbar) {
pbar.onresize();
vbar.onresize();
}
return true;
};
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.value = m;
document.body.appendChild(o);
o.focus();
o.select();
document.execCommand("copy");
o.value = 'copied to clipboard ';
setTimeout(function () {
document.body.removeChild(o);
}, 500);
var cln = function () {
o.value = 'copied to clipboard ';
setTimeout(function () {
document.body.removeChild(o);
}, 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);
setTimeout(function () {
clmod(ebi('widget'), 'anim', 1);
clmod(widget, 'anim', 1);
}, 10);
return r;
})();
@@ -794,6 +814,9 @@ var pbar = (function () {
grad;
r.onresize = function () {
if (!widget.is_open && r.buf)
return;
r.buf = canvas_cfg(ebi('barbuf'));
r.pos = canvas_cfg(ebi('barpos'));
r.drawbuf();
@@ -895,6 +918,9 @@ var vbar = (function () {
can, ctx, w, h, grad1, grad2;
r.onresize = function () {
if (!widget.is_open && r.can)
return;
r.can = canvas_cfg(ebi('pvol'));
can = r.can.can;
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() {
var v = hash0;
hash0 = null;
if (!v)
return;
if (v.indexOf('#af-') === 0) {
var id = v.slice(2).split('&');
if (id[0].length < 10)
return;
var media = scan_hash(v);
if (media) {
var mtype = media[0],
id = media[1],
ts = media[2];
if (id.length == 1)
return play(id[0]);
if (mtype == 'a') {
if (!ts)
return play(id);
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]);
if (!m)
return play(id[0]);
return play(id, false, ts);
}
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
if (mtype == 'g') {
if (!thegrid.en)
ebi('griden').click();
var t = setInterval(function () {
if (!thegrid.bbox)
return;
clearInterval(t);
var im = QS('#ggrid a[ref="' + id + '"]');
im.click();
im.scrollIntoView();
}, 50);
}
}
if (v.indexOf('#q=') === 0) {
@@ -2314,10 +2377,12 @@ var showfile = (function () {
'.bas': 'basic',
'.bat': 'batch',
'.cxx': 'cpp',
'.diz': 'ans',
'.h': 'c',
'.hpp': 'cpp',
'.htm': 'html',
'.hxx': 'cpp',
'.log': 'ans',
'.patch': 'diff',
'.ps1': 'powershell',
'.psm1': 'powershell',
@@ -2334,7 +2399,7 @@ var showfile = (function () {
'cmakelists.txt': 'cmake',
'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);
for (var a = 0; a < x.length; a++)
r.map["." + x[a]] = x[a];
@@ -2441,7 +2506,10 @@ var showfile = (function () {
if (lnh.slice(0, 5) == '#doc.')
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) { }
}
@@ -2489,6 +2557,65 @@ var showfile = (function () {
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 () {
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
for (var a = 0; a < r.files.length; a++) {
@@ -2860,6 +2987,9 @@ var thegrid = (function () {
return '<a download href="' + h +
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
esc(uricom_dec(h.split('/').pop())[0]) + '</a>';
},
onChange: function (i) {
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
}
})[0];
};
@@ -2873,10 +3003,6 @@ var thegrid = (function () {
});
ebi('wtgrid').onclick = ebi('griden').onclick;
setTimeout(function () {
import_js('/.cpr/baguettebox.js', r.bagit);
}, 1);
return r;
})();
@@ -3612,7 +3738,7 @@ var treectl = (function () {
treeh = winh - atop;
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);
@@ -3880,7 +4006,8 @@ var treectl = (function () {
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())
hist_push(this.top);
@@ -3954,21 +4081,42 @@ var treectl = (function () {
html = html.join('\n');
set_files_html(html);
filecols.set_style();
showfile.mktree();
mukey.render();
reload_tree();
reload_browser();
tree_scrollto();
if (res.acct) {
acct = res.acct;
have_up2k_idx = res.idx;
apply_perms(res.perms);
fileman.render();
function asdf() {
filecols.set_style();
showfile.mktree();
mukey.render();
reload_tree();
reload_browser();
tree_scrollto();
if (res.acct) {
acct = res.acct;
have_up2k_idx = res.idx;
apply_perms(res.perms);
fileman.render();
}
}
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 () {
qsr('#bbsw');
if (ls0 === null) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/?am_js', true);
@@ -3978,12 +4126,9 @@ var treectl = (function () {
}
r.gentab(get_evpath(), ls0);
reload_browser();
pbar.onresize();
vbar.onresize();
mukey.render();
showfile.addlinks();
thegrid.setdirty();
setTimeout(eval_hash, 1);
};
@@ -4082,14 +4227,16 @@ function apply_perms(newperms) {
perms = newperms || [];
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) {
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 {
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'));
var axs = [],
@@ -4106,10 +4253,11 @@ function apply_perms(newperms) {
axs += '-Only';
}
ebi('acc_info').innerHTML = '<span' + aclass + axs + ' access</span>' + (acct != '*' ?
'<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>');
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>');
var o = QSA('#ops>a[data-perm], #u2footfoot');
var o = QSA('#ops>a[data-perm]');
for (var a = 0; a < o.length; a++) {
var display = '';
var needed = o[a].getAttribute('data-perm').split(' ');
@@ -4464,20 +4612,51 @@ var mukey = (function () {
})();
var light;
(function () {
var light, theme;
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() {
clmod(document.documentElement, "light", light);
clmod(document.documentElement, "dark", !light);
var cl = document.documentElement.getAttribute('class');
cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' ');
document.documentElement.setAttribute('class', cl + ' ' + theme + ' ');
pbar.drawbuf();
pbar.drawpos();
vbar.draw();
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();
return settheme;
})();

View File

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

View File

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

View File

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

View File

@@ -644,12 +644,6 @@ function up2k_init(subtle) {
return false;
}
ebi('u2nope').onclick = function (e) {
ev(e);
setmsg(suggest_up2k, 'msg');
goto('bup');
};
setmsg(suggest_up2k, 'msg');
if (!String.prototype.format) {
@@ -1173,7 +1167,7 @@ function up2k_init(subtle) {
var t = st.todo.handshake[0],
cd = t.cooldown;
if (cd && cd - Date.now() > 0)
if (cd && cd > Date.now())
return false;
// keepalive or verify
@@ -1370,6 +1364,14 @@ function up2k_init(subtle) {
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
@@ -1468,7 +1470,6 @@ function up2k_init(subtle) {
min_filebuf = 1;
var td = Date.now() - t0;
if (td > 50) {
ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
min_filebuf = 32 * 1024 * 1024;
}
}
@@ -1756,8 +1757,12 @@ function up2k_init(subtle) {
pvis.move(t.n, 'ok');
}
else t.t_uploaded = undefined;
else {
if (t.t_uploaded)
chill(t);
t.t_uploaded = undefined;
}
tasker();
}
else {
@@ -1869,7 +1874,8 @@ function up2k_init(subtle) {
else {
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + (txt || "no further information"));
return;
chill(t);
}
orz2(xhr);
}
@@ -1920,6 +1926,7 @@ function up2k_init(subtle) {
//
function onresize(e) {
// 10x faster than matchMedia('(min-width
var bar = ebi('ops'),
wpx = window.innerWidth,
fpx = parseInt(getComputedStyle(bar)['font-size']),
@@ -1929,7 +1936,6 @@ function up2k_init(subtle) {
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
btn = ebi('u2btn');
//console.log([wpx, fpx, wem]);
if (btn.parentNode !== parent) {
parent.appendChild(btn);
ebi('u2conf').setAttribute('class', wide);
@@ -2057,10 +2063,10 @@ function up2k_init(subtle) {
try {
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);
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
ebi('u2bm').innerHTML = ico + '&nbsp; <sup>' + desc + '</sup>';
}
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) {
var v, vn, v1, v2, th = tab.tHead,
sopts = jread('fsort', [["href", 1, ""]]);
@@ -872,7 +882,7 @@ var tt = (function () {
};
r.getmsg = function (el) {
if (QS('body.bbox-open'))
if (IPHONE && QS('body.bbox-open'))
return;
var cfg = sread('tooltips');

View File

@@ -342,14 +342,15 @@ def get_payload():
def utime(top):
# avoid cleaners
i = 0
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())
if i:
msg("utime {}, {}".format(i, t))
for f in files:
for f in [top] + files:
os.utime(f, (t, t))
i += 1
@@ -374,16 +375,6 @@ def run(tmp, j2, ftp):
msg("sfxdir:", tmp)
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.daemon = True
t.start()

View File

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