Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eca90cc21 | ||
|
|
6ecf4fdceb | ||
|
|
8cae7a715b | ||
|
|
c75b0c25a6 | ||
|
|
9dd5dec093 | ||
|
|
ec05f8ccd5 | ||
|
|
a1c7a095ee | ||
|
|
77df17d191 | ||
|
|
fa5845ff5f | ||
|
|
17fa490687 | ||
|
|
1eff87c3bd | ||
|
|
d123d2bff0 | ||
|
|
5ac3864874 | ||
|
|
c599e2aaa3 | ||
|
|
2e53f7979a | ||
|
|
f61511d8c8 | ||
|
|
47415a7120 |
32
README.md
32
README.md
@@ -8,7 +8,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
||||
* 🔌 protocols: [http](#the-browser) // [webdav](#webdav-server) // [ftp](#ftp-server) // [tftp](#tftp-server) // [smb/cifs](#smb-server)
|
||||
* 📱 [android app](#android-app) // [iPhone shortcuts](#ios-shortcuts)
|
||||
|
||||
👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running from a basement in finland
|
||||
👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running on a nuc in my basement
|
||||
|
||||
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer)
|
||||
|
||||
@@ -56,6 +56,7 @@ made in Norway 🇳🇴
|
||||
* [creating a playlist](#creating-a-playlist) - with a standalone mediaplayer or copyparty
|
||||
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
||||
* [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
|
||||
* [textfile viewer](#textfile-viewer) - with realtime streaming of logfiles and such ([demo](https://a.ocv.me/pub/demo/logtail/))
|
||||
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
||||
* [markdown vars](#markdown-vars) - dynamic docs with serverside variable expansion
|
||||
* [other tricks](#other-tricks)
|
||||
@@ -257,7 +258,8 @@ also see [comparison to similar software](./docs/versus.md)
|
||||
* ☑ play video files as audio (converted on server)
|
||||
* ☑ create and play [m3u8 playlists](#playlists)
|
||||
* ☑ image gallery with webm player
|
||||
* ☑ textfile browser with syntax hilighting
|
||||
* ☑ [textfile browser](#textfile-viewer) with syntax hilighting
|
||||
* ☑ realtime streaming of growing files (logfiles and such)
|
||||
* ☑ [thumbnails](#thumbnails)
|
||||
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
||||
* ☑ ...of videos using FFmpeg
|
||||
@@ -561,6 +563,8 @@ a client can request to see dotfiles in directory listings if global option `-ed
|
||||
|
||||
dotfiles do not appear in search results unless one of the above is true, **and** the global option / volflag `dotsrch` is set
|
||||
|
||||
> even if user has permission to see dotfiles, they are default-hidden unless `--see-dots` is set, and/or user has enabled the `dotfiles` option in the settings tab
|
||||
|
||||
config file example, where the same permission to see dotfiles is given in two different ways just for reference:
|
||||
|
||||
```yaml
|
||||
@@ -697,7 +701,10 @@ enabling `multiselect` lets you click files to select them, and then shift-click
|
||||
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
|
||||
* the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
|
||||
|
||||
to show `/icons/exe.png` as the thumbnail for all .exe files, `--ext-th=exe=/icons/exe.png` (optionally as a volflag)
|
||||
to show `/icons/exe.png` and `/icons/elf.gif` as the thumbnail for all `.exe` and `.elf` files respectively, do this: `--ext-th=exe=/icons/exe.png --ext-th=elf=/icons/elf.gif`
|
||||
* optionally as separate volflags for each mapping; see config file example below
|
||||
* the supported image formats are [jpg, png, gif, webp, ico](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types)
|
||||
* be careful with svg; chrome will crash if you have too many unique svg files showing on the same page (the limit is 250 or so) -- showing the same handful of svg files thousands of times is ok however
|
||||
|
||||
config file example:
|
||||
|
||||
@@ -714,6 +721,7 @@ config file example:
|
||||
dthumb # disable ALL thumbnails and audio transcoding
|
||||
dvthumb # only disable video thumbnails
|
||||
ext-th: exe=/ico/exe.png # /ico/exe.png is the thumbnail of *.exe
|
||||
ext-th: elf=/ico/elf.gif # ...and /ico/elf.gif is used for *.elf
|
||||
th-covers: folder.png,folder.jpg,cover.png,cover.jpg # the default
|
||||
```
|
||||
|
||||
@@ -1121,6 +1129,18 @@ not available on iPhones / iPads because AudioContext currently breaks backgroun
|
||||
due to phone / app settings, android phones may randomly stop playing music when the power saver kicks in, especially at the end of an album -- you can fix it by [disabling power saving](https://user-images.githubusercontent.com/241032/235262123-c328cca9-3930-4948-bd18-3949b9fd3fcf.png) in the [app settings](https://user-images.githubusercontent.com/241032/235262121-2ffc51ae-7821-4310-a322-c3b7a507890c.png) of the browser you use for music streaming (preferably a dedicated one)
|
||||
|
||||
|
||||
## textfile viewer
|
||||
|
||||
with realtime streaming of logfiles and such ([demo](https://a.ocv.me/pub/demo/logtail/)) , and terminal colors work too
|
||||
|
||||
click `-txt-` next to a textfile to open the viewer, which has the following toolbar buttons:
|
||||
|
||||
* `✏️ edit` opens the textfile editor
|
||||
* `📡 follow` starts monitoring the file for changes, streaming new lines in realtime
|
||||
* similar to `tail -f`
|
||||
* [link directly](https://a.ocv.me/pub/demo/logtail/?doc=lipsum.txt&tail) to a file with tailing enabled by adding `&tail` to the textviewer URL
|
||||
|
||||
|
||||
## markdown viewer
|
||||
|
||||
and there are *two* editors
|
||||
@@ -2419,6 +2439,9 @@ interact with copyparty using non-browser clients
|
||||
* and for screenshots on macos, see [./contrib/ishare.iscu](./contrib/#ishareiscu)
|
||||
* and for screenshots on linux, see [./contrib/flameshot.sh](./contrib/flameshot.sh)
|
||||
|
||||
* [Custom Uploader](https://f-droid.org/en/packages/com.nyx.custom_uploader/) (an Android app) as an alternative to copyparty's own [PartyUP!](#android-app)
|
||||
* works if you set UploadURL to `https://your.com/foo/?want=url&pw=hunter2` and FormDataName `f`
|
||||
|
||||
* contextlet (web browser integration); see [contrib contextlet](contrib/#send-to-cppcontextletjson)
|
||||
|
||||
* [igloo irc](https://iglooirc.com/): Method: `post` Host: `https://you.com/up/?want=url&pw=hunter2` Multipart: `yes` File parameter: `f`
|
||||
@@ -2732,6 +2755,7 @@ set any of the following environment variables to disable its associated optiona
|
||||
| `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
|
||||
| `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
|
||||
| `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
|
||||
| `PRTY_NO_MAGIC` | do not use [magic](https://pypi.org/project/python-magic/) for filetype detection |
|
||||
| `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
|
||||
| `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
|
||||
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
||||
@@ -2832,5 +2856,7 @@ if there's a wall of base64 in the log (thread stacks) then please include that,
|
||||
|
||||
for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md)
|
||||
|
||||
specifically you may want to [build the sfx](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#just-the-sfx) or [build from scratch](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#build-from-scratch)
|
||||
|
||||
see [./docs/TODO.md](./docs/TODO.md) for planned features / fixes / changes
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: icxes <dev.null@need.moe>
|
||||
pkgname=copyparty
|
||||
pkgver="1.17.1"
|
||||
pkgver="1.17.2"
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
@@ -22,7 +22,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
||||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("etc/${pkgname}.d/init" )
|
||||
sha256sums=("2ecbe68dea1de8612fa8beee42e07c62dc1f7c0068e527a8d63961a47e376207")
|
||||
sha256sums=("20af4a9b3188fee235c505af4a09190088d0094ab594e37ca1eabbda41c8912d")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.17.1/copyparty-sfx.py",
|
||||
"version": "1.17.1",
|
||||
"hash": "sha256-dvfsqPqnT1HNbxG/sqDqz+6PgAKB6+LVKx2sJ2zKYZc="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.17.2/copyparty-sfx.py",
|
||||
"version": "1.17.2",
|
||||
"hash": "sha256-qY3QTLthJLpvO9yo2kiwM2cT3eqg5/cGvGzp+JHy4Ko="
|
||||
}
|
||||
@@ -964,6 +964,7 @@ def add_general(ap, nc, srvname):
|
||||
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
|
||||
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
|
||||
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
|
||||
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
|
||||
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
||||
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
||||
|
||||
@@ -1270,6 +1271,7 @@ def add_optouts(ap):
|
||||
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
|
||||
ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
|
||||
ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
|
||||
ap2.add_argument("--no-tail", action="store_true", help="disable streaming a growing files with ?tail (volflag=notail)")
|
||||
ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader-IP into the database; will also disable unpost, you may want \033[32m--forget-ip\033[0m instead (volflag=no_db_ip)")
|
||||
|
||||
|
||||
@@ -1399,6 +1401,16 @@ def add_transcoding(ap):
|
||||
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
|
||||
|
||||
|
||||
def add_tail(ap):
|
||||
ap2 = ap.add_argument_group('tailing options (realtime streaming of a growing file)')
|
||||
ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)")
|
||||
ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads")
|
||||
ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)")
|
||||
ap2.add_argument("--tail-rate", metavar="SEC", type=float, default=0.2, help="check for new data every \033[33mSEC\033[0m seconds (volflag=tail_rate)")
|
||||
ap2.add_argument("--tail-ka", metavar="SEC", type=float, default=3.0, help="send a zerobyte if connection is idle for \033[33mSEC\033[0m seconds to prevent disconnect")
|
||||
ap2.add_argument("--tail-fd", metavar="SEC", type=float, default=1.0, help="check if file was replaced (new fd) if idle for \033[33mSEC\033[0m seconds (volflag=tail_fd)")
|
||||
|
||||
|
||||
def add_rss(ap):
|
||||
ap2 = ap.add_argument_group('RSS options')
|
||||
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
|
||||
@@ -1494,6 +1506,7 @@ def add_ui(ap, retry):
|
||||
ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
|
||||
ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
|
||||
ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
|
||||
ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
|
||||
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
||||
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
||||
ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
|
||||
@@ -1603,6 +1616,7 @@ def run_argparse(
|
||||
add_hooks(ap)
|
||||
add_stats(ap)
|
||||
add_txt(ap)
|
||||
add_tail(ap)
|
||||
add_og(ap)
|
||||
add_ui(ap, retry)
|
||||
add_admin(ap)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 17, 2)
|
||||
CODENAME = "mixtape.m3u"
|
||||
BUILD_DT = (2025, 5, 27)
|
||||
VERSION = (1, 18, 0)
|
||||
CODENAME = "logtail"
|
||||
BUILD_DT = (2025, 6, 22)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -72,7 +72,9 @@ SSEELOG = " ({})".format(SEE_LOG)
|
||||
BAD_CFG = "invalid config; {}".format(SEE_LOG)
|
||||
SBADCFG = " ({})".format(BAD_CFG)
|
||||
|
||||
PTN_U_GRP = re.compile(r"\$\{u%([+-])([^}]+)\}")
|
||||
PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
|
||||
PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
|
||||
PTN_SIGIL = re.compile(r"(\${[ug][}%])")
|
||||
|
||||
|
||||
class CfgEx(Exception):
|
||||
@@ -965,15 +967,27 @@ class AuthSrv(object):
|
||||
un_gn = [("", "")]
|
||||
|
||||
for un, gn in un_gn:
|
||||
m = PTN_U_GRP.search(dst0)
|
||||
if m:
|
||||
req, gnc = m.groups()
|
||||
hit = gnc in (un_gns.get(un) or [])
|
||||
if req == "+":
|
||||
if not hit:
|
||||
continue
|
||||
elif hit:
|
||||
rejected = False
|
||||
for ptn in [PTN_U_GRP, PTN_G_GRP]:
|
||||
m = ptn.search(dst0)
|
||||
if not m:
|
||||
continue
|
||||
zs = m.group(1)
|
||||
zs = zs.replace(",%+", "\n%+")
|
||||
zs = zs.replace(",%-", "\n%-")
|
||||
for rule in zs.split("\n"):
|
||||
gnc = rule[2:]
|
||||
if ptn == PTN_U_GRP:
|
||||
# is user member of group?
|
||||
hit = gnc in (un_gns.get(un) or [])
|
||||
else:
|
||||
# is it this specific group?
|
||||
hit = gn == gnc
|
||||
|
||||
if rule.startswith("%+") != hit:
|
||||
rejected = True
|
||||
if rejected:
|
||||
continue
|
||||
|
||||
# if ap/vp has a user/group placeholder, make sure to keep
|
||||
# track so the same user/group is mapped when setting perms;
|
||||
@@ -988,6 +1002,8 @@ class AuthSrv(object):
|
||||
|
||||
src = src1.replace("${g}", gn or "\n")
|
||||
dst = dst1.replace("${g}", gn or "\n")
|
||||
src = PTN_G_GRP.sub(gn or "\n", src)
|
||||
dst = PTN_G_GRP.sub(gn or "\n", dst)
|
||||
if src == src1 and dst == dst1:
|
||||
gn = ""
|
||||
|
||||
@@ -1862,7 +1878,7 @@ class AuthSrv(object):
|
||||
is_shr = shr and zv.vpath.split("/")[0] == shr
|
||||
if histp and not is_shr and histp in rhisttab:
|
||||
zv2 = rhisttab[histp]
|
||||
t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
|
||||
t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: /%s [%s]"
|
||||
t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
||||
self.log(t, 1)
|
||||
raise Exception(t)
|
||||
@@ -1876,7 +1892,7 @@ class AuthSrv(object):
|
||||
is_shr = shr and zv.vpath.split("/")[0] == shr
|
||||
if dbp and not is_shr and dbp in rdbpaths:
|
||||
zv2 = rdbpaths[dbp]
|
||||
t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
|
||||
t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: /%s [%s]"
|
||||
t = t % (dbp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
||||
self.log(t, 1)
|
||||
raise Exception(t)
|
||||
@@ -2059,12 +2075,13 @@ class AuthSrv(object):
|
||||
if vf not in vol.flags:
|
||||
vol.flags[vf] = getattr(self.args, ga)
|
||||
|
||||
zs = "forget_ip nrand u2abort u2ow ups_who zip_who"
|
||||
zs = "forget_ip nrand tail_who u2abort u2ow ups_who zip_who"
|
||||
for k in zs.split():
|
||||
if k in vol.flags:
|
||||
vol.flags[k] = int(vol.flags[k])
|
||||
|
||||
for k in ("convt",):
|
||||
zs = "convt tail_fd tail_rate tail_tmax"
|
||||
for k in zs.split():
|
||||
if k in vol.flags:
|
||||
vol.flags[k] = float(vol.flags[k])
|
||||
|
||||
@@ -2393,7 +2410,7 @@ class AuthSrv(object):
|
||||
idp_vn, _ = vfs.get(idp_vp, "*", False, False)
|
||||
idp_vp0 = idp_vn.vpath0
|
||||
|
||||
sigils = set(re.findall(r"(\${[ug][}%])", idp_vp0))
|
||||
sigils = set(PTN_SIGIL.findall(idp_vp0))
|
||||
if len(sigils) > 1:
|
||||
t = '\nWARNING: IdP-volume "/%s" created by "/%s" has multiple IdP placeholders: %s'
|
||||
self.idp_warn.append(t % (idp_vp, idp_vp0, list(sigils)))
|
||||
@@ -2563,6 +2580,7 @@ class AuthSrv(object):
|
||||
"txt_ext": self.args.textfiles.replace(",", " "),
|
||||
"def_hcols": list(vf.get("mth") or []),
|
||||
"unlist0": vf.get("unlist") or "",
|
||||
"see_dots": self.args.see_dots,
|
||||
"dgrid": "grid" in vf,
|
||||
"dgsel": "gsel" in vf,
|
||||
"dnsort": "nsort" in vf,
|
||||
|
||||
@@ -22,6 +22,7 @@ def vf_bmap() -> dict[str, str]:
|
||||
"no_forget": "noforget",
|
||||
"no_pipe": "nopipe",
|
||||
"no_robots": "norobots",
|
||||
"no_tail": "notail",
|
||||
"no_thumb": "dthumb",
|
||||
"no_vthumb": "dvthumb",
|
||||
"no_athumb": "dathumb",
|
||||
@@ -51,6 +52,7 @@ def vf_bmap() -> dict[str, str]:
|
||||
"og_no_head",
|
||||
"og_s_title",
|
||||
"rand",
|
||||
"rmagic",
|
||||
"rss",
|
||||
"wo_up_readme",
|
||||
"xdev",
|
||||
@@ -101,6 +103,10 @@ def vf_vmap() -> dict[str, str]:
|
||||
"mv_retry",
|
||||
"rm_retry",
|
||||
"sort",
|
||||
"tail_fd",
|
||||
"tail_rate",
|
||||
"tail_tmax",
|
||||
"tail_who",
|
||||
"tcolor",
|
||||
"unlist",
|
||||
"u2abort",
|
||||
@@ -304,6 +310,13 @@ flagcats = {
|
||||
"exp_md": "placeholders to expand in markdown files; see --help",
|
||||
"exp_lg": "placeholders to expand in prologue/epilogue; see --help",
|
||||
},
|
||||
"tailing": {
|
||||
"notail": "disable ?tail (download a growing file continuously)",
|
||||
"tail_fd=1": "check if file was replaced (new fd) every 1 sec",
|
||||
"tail_rate=0.2": "check for new data every 0.2 sec",
|
||||
"tail_tmax=30": "kill connection after 30 sec",
|
||||
"tail_who=2": "restrict ?tail access (1=admins,2=authed,3=everyone)",
|
||||
},
|
||||
"others": {
|
||||
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
|
||||
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
|
||||
@@ -312,6 +325,7 @@ flagcats = {
|
||||
"dks": "per-directory accesskeys allow browsing into subdirs",
|
||||
"dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so',
|
||||
"rss": "allow '?rss' URL suffix (experimental)",
|
||||
"rmagic": "expensive analysis for mimetype accuracy",
|
||||
"ups_who=2": "restrict viewing the list of recent uploads",
|
||||
"zip_who=2": "restrict access to download-as-zip/tar",
|
||||
"zipmaxn=9k": "reject download-as-zip if more than 9000 files",
|
||||
|
||||
@@ -1412,7 +1412,13 @@ class HttpCli(object):
|
||||
except:
|
||||
pass
|
||||
|
||||
ap = ""
|
||||
use_magic = "rmagic" in self.vn.flags
|
||||
|
||||
for i in hits:
|
||||
if use_magic:
|
||||
ap = os.path.join(self.vn.realpath, i["rp"])
|
||||
|
||||
iurl = html_escape("%s%s" % (baseurl, i["rp"]), True, True)
|
||||
title = unquotep(i["rp"].split("?")[0].split("/")[-1])
|
||||
title = html_escape(title, True, True)
|
||||
@@ -1420,7 +1426,7 @@ class HttpCli(object):
|
||||
tag_a = str(i["tags"].get("artist") or "")
|
||||
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
|
||||
desc = html_escape(desc, True, True) if desc else title
|
||||
mime = html_escape(guess_mime(title))
|
||||
mime = html_escape(guess_mime(title, ap))
|
||||
lmod = formatdate(max(0, i["ts"]))
|
||||
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
|
||||
zs = (
|
||||
@@ -1573,6 +1579,9 @@ class HttpCli(object):
|
||||
None, 207, "text/xml; charset=" + enc, {"Transfer-Encoding": "chunked"}
|
||||
)
|
||||
|
||||
ap = ""
|
||||
use_magic = "rmagic" in vn.flags
|
||||
|
||||
ret = '<?xml version="1.0" encoding="{}"?>\n<D:multistatus xmlns:D="DAV:">'
|
||||
ret = ret.format(uenc)
|
||||
for x in fgen:
|
||||
@@ -1599,7 +1608,9 @@ class HttpCli(object):
|
||||
"supportedlock": '<D:lockentry xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>',
|
||||
}
|
||||
if not isdir:
|
||||
pvs["getcontenttype"] = html_escape(guess_mime(rp))
|
||||
if use_magic:
|
||||
ap = os.path.join(tap, x["vp"])
|
||||
pvs["getcontenttype"] = html_escape(guess_mime(rp, ap))
|
||||
pvs["getcontentlength"] = str(st.st_size)
|
||||
|
||||
for k, v in pvs.items():
|
||||
@@ -2716,6 +2727,7 @@ class HttpCli(object):
|
||||
locked = chashes # remaining chunks to be received in this request
|
||||
written = [] # chunks written to disk, but not yet released by up2k
|
||||
num_left = -1 # num chunks left according to most recent up2k release
|
||||
bail1 = False # used in sad path to avoid contradicting error-text
|
||||
treport = time.time() # ratelimit up2k reporting to reduce overhead
|
||||
|
||||
if "x-up2k-subc" in self.headers:
|
||||
@@ -2854,7 +2866,6 @@ class HttpCli(object):
|
||||
except:
|
||||
# maybe busted handle (eg. disk went full)
|
||||
f.close()
|
||||
chashes = [] # exception flag
|
||||
raise
|
||||
finally:
|
||||
if locked:
|
||||
@@ -2863,13 +2874,14 @@ class HttpCli(object):
|
||||
num_left, t = x.get()
|
||||
if num_left < 0:
|
||||
self.loud_reply(t, status=500)
|
||||
if chashes: # kills exception bubbling otherwise
|
||||
return False
|
||||
bail1 = True
|
||||
else:
|
||||
t = "got %d more chunks, %d left"
|
||||
self.log(t % (len(written), num_left), 6)
|
||||
|
||||
if num_left < 0:
|
||||
if bail1:
|
||||
return False
|
||||
raise Pebkac(500, "unconfirmed; see serverlog")
|
||||
|
||||
if not num_left and fpool:
|
||||
@@ -3813,6 +3825,20 @@ class HttpCli(object):
|
||||
|
||||
return txt
|
||||
|
||||
def _can_tail(self, volflags: dict[str, Any]) -> bool:
|
||||
zp = self.args.ua_nodoc
|
||||
if zp and zp.search(self.ua):
|
||||
t = "this URL contains no valuable information for bots/crawlers"
|
||||
raise Pebkac(403, t)
|
||||
lvl = volflags["tail_who"]
|
||||
if "notail" in volflags or not lvl:
|
||||
raise Pebkac(400, "tail is disabled in server config")
|
||||
elif lvl <= 1 and not self.can_admin:
|
||||
raise Pebkac(400, "tail is admin-only on this server")
|
||||
elif lvl <= 2 and self.uname in ("", "*"):
|
||||
raise Pebkac(400, "you must be authenticated to use ?tail on this server")
|
||||
return True
|
||||
|
||||
def _can_zip(self, volflags: dict[str, Any]) -> str:
|
||||
lvl = volflags["zip_who"]
|
||||
if self.args.no_zip or not lvl:
|
||||
@@ -3957,6 +3983,8 @@ class HttpCli(object):
|
||||
logmsg = "{:4} {} ".format("", self.req)
|
||||
logtail = ""
|
||||
|
||||
is_tail = "tail" in self.uparam and self._can_tail(self.vn.flags)
|
||||
|
||||
if ptop is not None:
|
||||
ap_data = "<%s>" % (req_path,)
|
||||
try:
|
||||
@@ -4070,6 +4098,7 @@ class HttpCli(object):
|
||||
and can_range
|
||||
and file_sz
|
||||
and "," not in hrange
|
||||
and not is_tail
|
||||
):
|
||||
try:
|
||||
if not hrange.lower().startswith("bytes"):
|
||||
@@ -4138,6 +4167,8 @@ class HttpCli(object):
|
||||
mime = "text/plain; charset={}".format(self.uparam["txt"] or "utf-8")
|
||||
elif "mime" in self.uparam:
|
||||
mime = str(self.uparam.get("mime"))
|
||||
elif "rmagic" in self.vn.flags:
|
||||
mime = guess_mime(req_path, fs_path)
|
||||
else:
|
||||
mime = guess_mime(req_path)
|
||||
|
||||
@@ -4155,13 +4186,18 @@ class HttpCli(object):
|
||||
return True
|
||||
|
||||
dls = self.conn.hsrv.dls
|
||||
if is_tail:
|
||||
upper = 1 << 30
|
||||
if len(dls) > self.args.tail_cmax:
|
||||
raise Pebkac(400, "too many active downloads to start a new tail")
|
||||
|
||||
if upper - lower > 0x400000: # 4m
|
||||
now = time.time()
|
||||
self.dl_id = "%s:%s" % (self.ip, self.addr[1])
|
||||
dls[self.dl_id] = (now, 0)
|
||||
self.conn.hsrv.dli[self.dl_id] = (
|
||||
now,
|
||||
upper - lower,
|
||||
0 if is_tail else upper - lower,
|
||||
self.vn,
|
||||
self.vpath,
|
||||
self.uname,
|
||||
@@ -4172,6 +4208,9 @@ class HttpCli(object):
|
||||
return self.tx_pipe(
|
||||
ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
|
||||
)
|
||||
elif is_tail:
|
||||
self.tx_tail(open_args, status, mime)
|
||||
return False
|
||||
|
||||
ret = True
|
||||
with open_func(*open_args) as f:
|
||||
@@ -4201,6 +4240,133 @@ class HttpCli(object):
|
||||
|
||||
return ret
|
||||
|
||||
def tx_tail(
|
||||
self,
|
||||
open_args: list[Any],
|
||||
status: int,
|
||||
mime: str,
|
||||
) -> None:
|
||||
vf = self.vn.flags
|
||||
self.send_headers(length=None, status=status, mime=mime)
|
||||
abspath: bytes = open_args[0]
|
||||
sec_rate = vf["tail_rate"]
|
||||
sec_max = vf["tail_tmax"]
|
||||
sec_fd = vf["tail_fd"]
|
||||
sec_ka = self.args.tail_ka
|
||||
wr_slp = self.args.s_wr_slp
|
||||
wr_sz = self.args.s_wr_sz
|
||||
dls = self.conn.hsrv.dls
|
||||
dl_id = self.dl_id
|
||||
|
||||
# non-numeric = full file from start
|
||||
# positive = absolute offset from start
|
||||
# negative = start that many bytes from eof
|
||||
try:
|
||||
ofs = int(self.uparam["tail"])
|
||||
except:
|
||||
ofs = 0
|
||||
|
||||
t0 = time.time()
|
||||
ofs0 = ofs
|
||||
f = None
|
||||
try:
|
||||
st = os.stat(abspath)
|
||||
f = open(*open_args)
|
||||
f.seek(0, os.SEEK_END)
|
||||
eof = f.tell()
|
||||
f.seek(0)
|
||||
if ofs < 0:
|
||||
ofs = max(0, ofs + eof)
|
||||
|
||||
self.log("tailing from byte %d: %r" % (ofs, abspath), 6)
|
||||
|
||||
# send initial data asap
|
||||
remains = sendfile_py(
|
||||
self.log, # d/c
|
||||
ofs,
|
||||
eof,
|
||||
f,
|
||||
self.s,
|
||||
wr_sz,
|
||||
wr_slp,
|
||||
False, # d/c
|
||||
dls,
|
||||
dl_id,
|
||||
)
|
||||
sent = (eof - ofs) - remains
|
||||
ofs = eof - remains
|
||||
f.seek(ofs)
|
||||
|
||||
try:
|
||||
st2 = os.stat(open_args[0])
|
||||
if st.st_ino == st2.st_ino:
|
||||
st = st2 # for filesize
|
||||
except:
|
||||
pass
|
||||
|
||||
gone = 0
|
||||
t_fd = t_ka = time.time()
|
||||
while True:
|
||||
assert f # !rm
|
||||
buf = f.read(4096)
|
||||
now = time.time()
|
||||
|
||||
if sec_max and now - t0 >= sec_max:
|
||||
self.log("max duration exceeded; kicking client", 6)
|
||||
zb = b"\n\n*** max duration exceeded; disconnecting ***\n"
|
||||
self.s.sendall(zb)
|
||||
break
|
||||
|
||||
if buf:
|
||||
t_fd = t_ka = now
|
||||
self.s.sendall(buf)
|
||||
sent += len(buf)
|
||||
dls[dl_id] = (time.time(), sent)
|
||||
continue
|
||||
|
||||
time.sleep(sec_rate)
|
||||
if t_ka < now - sec_ka:
|
||||
t_ka = now
|
||||
self.s.send(b"\x00")
|
||||
if t_fd < now - sec_fd:
|
||||
try:
|
||||
st2 = os.stat(open_args[0])
|
||||
if (
|
||||
st2.st_ino != st.st_ino
|
||||
or st2.st_size < sent
|
||||
or st2.st_size < st.st_size
|
||||
):
|
||||
assert f # !rm
|
||||
# open new file before closing previous to avoid toctous (open may fail; cannot null f before)
|
||||
f2 = open(*open_args)
|
||||
f.close()
|
||||
f = f2
|
||||
f.seek(0, os.SEEK_END)
|
||||
eof = f.tell()
|
||||
if eof < sent:
|
||||
ofs = sent = 0 # shrunk; send from start
|
||||
zb = b"\n\n*** file size decreased -- rewinding to the start of the file ***\n\n"
|
||||
self.s.sendall(zb)
|
||||
if ofs0 < 0 and eof > -ofs0:
|
||||
ofs = eof + ofs0
|
||||
else:
|
||||
ofs = sent # just new fd? resume from same ofs
|
||||
f.seek(ofs)
|
||||
self.log("reopened at byte %d: %r" % (ofs, abspath), 6)
|
||||
gone = 0
|
||||
st = st2
|
||||
except:
|
||||
gone += 1
|
||||
if gone > 3:
|
||||
self.log("file deleted; disconnecting")
|
||||
break
|
||||
except IOError as ex:
|
||||
if ex.errno not in (errno.EPIPE, errno.ESHUTDOWN, errno.EBADFD):
|
||||
raise
|
||||
finally:
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
def tx_pipe(
|
||||
self,
|
||||
ptop: str,
|
||||
@@ -4761,7 +4927,6 @@ class HttpCli(object):
|
||||
if zi == 2 or (zi == 1 and self.avol):
|
||||
dl_list = self.get_dls()
|
||||
for t0, t1, sent, sz, vp, dl_id, uname in dl_list:
|
||||
rem = sz - sent
|
||||
td = max(0.1, now - t0)
|
||||
rd, fn = vsplit(vp)
|
||||
if not rd:
|
||||
|
||||
@@ -153,6 +153,14 @@ try:
|
||||
except:
|
||||
HAVE_PSUTIL = False
|
||||
|
||||
try:
|
||||
if os.environ.get("PRTY_NO_MAGIC"):
|
||||
raise Exception()
|
||||
|
||||
import magic
|
||||
except:
|
||||
pass
|
||||
|
||||
if True: # pylint: disable=using-constant-test
|
||||
import types
|
||||
from collections.abc import Callable, Iterable
|
||||
@@ -175,8 +183,6 @@ if True: # pylint: disable=using-constant-test
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import magic
|
||||
|
||||
from .authsrv import VFS
|
||||
from .broker_util import BrokerCli
|
||||
from .up2k import Up2k
|
||||
@@ -1256,8 +1262,6 @@ class Magician(object):
|
||||
self.magic: Optional["magic.Magic"] = None
|
||||
|
||||
def ext(self, fpath: str) -> str:
|
||||
import magic
|
||||
|
||||
try:
|
||||
if self.bad_magic:
|
||||
raise Exception()
|
||||
@@ -3152,11 +3156,13 @@ def unescape_cookie(orig: str) -> str:
|
||||
return "".join(ret)
|
||||
|
||||
|
||||
def guess_mime(url: str, fallback: str = "application/octet-stream") -> str:
|
||||
def guess_mime(
|
||||
url: str, path: str = "", fallback: str = "application/octet-stream"
|
||||
) -> str:
|
||||
try:
|
||||
ext = url.rsplit(".", 1)[1].lower()
|
||||
except:
|
||||
return fallback
|
||||
ext = ""
|
||||
|
||||
ret = MIMES.get(ext)
|
||||
|
||||
@@ -3164,6 +3170,16 @@ def guess_mime(url: str, fallback: str = "application/octet-stream") -> str:
|
||||
x = mimetypes.guess_type(url)
|
||||
ret = "application/{}".format(x[1]) if x[1] else x[0]
|
||||
|
||||
if not ret and path:
|
||||
try:
|
||||
with open(fsenc(path), "rb", 0) as f:
|
||||
ret = magic.from_buffer(f.read(4096), mime=True)
|
||||
if ret.startswith("text/htm"):
|
||||
# avoid serving up HTML content unless there was actually a .html extension
|
||||
ret = "text/plain"
|
||||
except Exception as ex:
|
||||
pass
|
||||
|
||||
if not ret:
|
||||
ret = fallback
|
||||
|
||||
|
||||
@@ -1825,10 +1825,11 @@ html.y #tree.nowrap .ntree a+a:hover {
|
||||
line-height: 2.3em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
#hdoc,
|
||||
#ghead {
|
||||
position: sticky;
|
||||
top: -.3em;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
.ghead .btn {
|
||||
position: relative;
|
||||
@@ -1838,6 +1839,13 @@ html.y #tree.nowrap .ntree a+a:hover {
|
||||
white-space: pre;
|
||||
padding-left: .3em;
|
||||
}
|
||||
#tailbtns {
|
||||
display: none;
|
||||
}
|
||||
#taildoc.on+#tailbtns {
|
||||
display: inherit;
|
||||
display: unset;
|
||||
}
|
||||
#op_unpost {
|
||||
padding: 1em;
|
||||
}
|
||||
@@ -1934,6 +1942,9 @@ html.y #tree.nowrap .ntree a+a:hover {
|
||||
padding: 1em 0 1em 0;
|
||||
border-radius: .3em;
|
||||
}
|
||||
#doc.wrap {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
html.y #doc {
|
||||
box-shadow: 0 0 .3em var(--bg-u5);
|
||||
background: #f7f7f7;
|
||||
@@ -3227,7 +3238,7 @@ html.d #treepar {
|
||||
|
||||
#ggrid>a>span {
|
||||
text-align: center;
|
||||
padding: 0.2em;
|
||||
padding: .2em .2em .15em .2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@ var Ls = {
|
||||
"f_empty": 'this folder is empty',
|
||||
"f_chide": 'this will hide the column «{0}»\n\nyou can unhide columns in the settings tab',
|
||||
"f_bigtxt": "this file is {0} MiB large -- really view as text?",
|
||||
"f_bigtxt2": "view just the end of the file instead? this will also enable following/tailing, showing newly added lines of text in real time",
|
||||
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
||||
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
||||
"f_anota": "only {0} of the {1} items were selected;\nto select the full folder, first scroll to the bottom",
|
||||
@@ -441,6 +442,11 @@ var Ls = {
|
||||
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
||||
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||
"tvt_tail": "monitor file for changes; show new lines in real time\">📡 follow",
|
||||
"tvt_wrap": "word-wrap\">↵",
|
||||
"tvt_atail": "lock scroll to bottom of page\">⚓",
|
||||
"tvt_ctail": "decode terminal colors (ansi escape codes)\">🌈",
|
||||
"tvt_ntail": "scrollback limit (how many bytes of text to keep loaded)",
|
||||
|
||||
"m3u_add1": "song added to m3u playlist",
|
||||
"m3u_addn": "{0} songs added to m3u playlist",
|
||||
@@ -540,6 +546,7 @@ var Ls = {
|
||||
"u_https3": "for better performance",
|
||||
"u_ancient": 'your browser is impressively ancient -- maybe you should <a href="#" onclick="goto(\'bup\')">use bup instead</a>',
|
||||
"u_nowork": "need firefox 53+ or chrome 57+ or iOS 11+",
|
||||
"tail_2old": "need firefox 105+ or chrome 71+ or iOS 14.5+",
|
||||
"u_nodrop": 'your browser is too old for drag-and-drop uploading',
|
||||
"u_notdir": "that's not a folder!\n\nyour browser is too old,\nplease try dragdrop instead",
|
||||
"u_uri": "to dragdrop images from other browser windows,\nplease drop it onto the big upload button",
|
||||
@@ -954,6 +961,7 @@ var Ls = {
|
||||
"f_empty": 'denne mappen er tom',
|
||||
"f_chide": 'dette vil skjule kolonnen «{0}»\n\nfanen for "andre innstillinger" lar deg vise kolonnen igjen',
|
||||
"f_bigtxt": "denne filen er hele {0} MiB -- vis som tekst?",
|
||||
"f_bigtxt2": "vil du se bunnen av filen istedenfor? du vil da også se nye linjer som blir lagt til på slutten av filen i sanntid",
|
||||
"fbd_more": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_more">vis {2}</a> eller <a href="#" id="bd_all">vis alle</a></div>',
|
||||
"fbd_all": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_all">vis alle</a></div>',
|
||||
"f_anota": "kun {0} av totalt {1} elementer ble markert;\nfor å velge alt må du bla til bunnen av mappen først",
|
||||
@@ -1058,6 +1066,11 @@ var Ls = {
|
||||
"tvt_next": "vis neste dokument$NSnarvei: K\">⬇ neste",
|
||||
"tvt_sel": "markér filen ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
|
||||
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
||||
"tvt_tail": "overvåk filen for endringer og vis nye linjer i sanntid\">📡 følg",
|
||||
"tvt_wrap": "tekstbryting\">↵",
|
||||
"tvt_atail": "hold de nyeste linjene synlig (lås til bunnen av siden)\">⚓",
|
||||
"tvt_ctail": "forstå og vis terminalfarger (ansi-sekvenser)\">🌈",
|
||||
"tvt_ntail": "maks-grense for antall bokstaver som skal vises i vinduet",
|
||||
|
||||
"m3u_add1": "sangen ble lagt til i m3u-spillelisten",
|
||||
"m3u_addn": "{0} sanger ble lagt til i m3u-spillelisten",
|
||||
@@ -1157,6 +1170,7 @@ var Ls = {
|
||||
"u_https3": "for høyere hastighet",
|
||||
"u_ancient": 'nettleseren din er prehistorisk -- mulig du burde <a href="#" onclick="goto(\'bup\')">bruke bup istedenfor</a>',
|
||||
"u_nowork": "krever firefox 53+, chrome 57+, eller iOS 11+",
|
||||
"tail_2old": "krever firefox 105+, chrome 71+, eller iOS 14.5+",
|
||||
"u_nodrop": 'nettleseren din er for gammel til å laste opp filer ved å dra dem inn i vinduet',
|
||||
"u_notdir": "mottok ikke mappen!\n\nnettleseren din er for gammel,\nprøv å dra mappen inn i vinduet istedenfor",
|
||||
"u_uri": "for å laste opp bilder ifra andre nettleservinduer,\nslipp bildet rett på den store last-opp-knappen",
|
||||
@@ -1571,6 +1585,7 @@ var Ls = {
|
||||
"f_empty": '该文件夹为空',
|
||||
"f_chide": '隐藏列 «{0}»\n\n你可以在设置选项卡中重新显示列',
|
||||
"f_bigtxt": "这个文件大小为 {0} MiB -- 真的以文本形式查看?",
|
||||
"f_bigtxt2": " 你想查看文件的结尾部分吗?这也将启用实时跟踪功能,能够实时显示新添加的文本行。", //m
|
||||
"fbd_more": '<div id="blazy">显示 <code>{0}</code> 个文件中的 <code>{1}</code> 个;<a href="#" id="bd_more">显示 {2}</a> 或 <a href="#" id="bd_all">显示全部</a></div>',
|
||||
"fbd_all": '<div id="blazy">显示 <code>{0}</code> 个文件中的 <code>{1}</code> 个;<a href="#" id="bd_all">显示全部</a></div>',
|
||||
"f_anota": "仅选择了 {0} 个项目,共 {1} 个;\n要选择整个文件夹,请先滚动到底部", //m
|
||||
@@ -1675,6 +1690,11 @@ var Ls = {
|
||||
"tvt_next": "显示下一个文档$N快捷键: K\">⬇ 下一个",
|
||||
"tvt_sel": "选择文件 (用于剪切/删除/...)$N快捷键: S\">选择",
|
||||
"tvt_edit": "在文本编辑器中打开文件$N快捷键: E\">✏️ 编辑",
|
||||
"tvt_tail": "监视文件更改,并实时显示新增的行\">📡 跟踪", //m
|
||||
"tvt_wrap": "自动换行\">↵", //m
|
||||
"tvt_atail": "锁定到底部,显示最新内容\">⚓", //m
|
||||
"tvt_ctail": "解析终端颜色(ANSI 转义码)\">🌈", //m
|
||||
"tvt_ntail": "滚动历史上限(保留多少字节的文本)", //m
|
||||
|
||||
"m3u_add1": "歌曲已添加到 m3u 播放列表", //m
|
||||
"m3u_addn": "已添加 {0} 首歌曲到 m3u 播放列表", //m
|
||||
@@ -1774,6 +1794,7 @@ var Ls = {
|
||||
"u_https3": "以获得更好的性能",
|
||||
"u_ancient": '你的浏览器非常古老 -- 也许你应该 <a href="#" onclick="goto(\'bup\')">改用 bup</a>',
|
||||
"u_nowork": "需要 Firefox 53+ 或 Chrome 57+ 或 iOS 11+",
|
||||
"tail_2old": "需要 Firefox 105+ 或 Chrome 71+ 或 iOS 14.5+",
|
||||
"u_nodrop": '浏览器版本低,不支持通过拖动文件到窗口来上传文件',
|
||||
"u_notdir": "不是文件夹!\n\n您的浏览器太旧;\n请尝试将文件夹拖入窗口",
|
||||
"u_uri": "要从其他浏览器窗口拖放图片,\n请将其拖放到大的上传按钮上",
|
||||
@@ -2966,6 +2987,9 @@ var widget = (function () {
|
||||
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
||||
}
|
||||
};
|
||||
r.setvis = function () {
|
||||
widget.style.display = !has(perms, "read") || showfile.abrt ? 'none' : '';
|
||||
};
|
||||
wtico.onclick = function (e) {
|
||||
if (!touchmode)
|
||||
r.toggle(e);
|
||||
@@ -5799,7 +5823,9 @@ var fileman = (function () {
|
||||
|
||||
|
||||
var showfile = (function () {
|
||||
var r = {};
|
||||
var r = {
|
||||
'nrend': 0,
|
||||
};
|
||||
r.map = {
|
||||
'.ahk': 'autohotkey',
|
||||
'.bas': 'basic',
|
||||
@@ -5912,16 +5938,77 @@ var showfile = (function () {
|
||||
}
|
||||
r.mktree();
|
||||
if (em) {
|
||||
render(em);
|
||||
if (r.taildoc)
|
||||
r.show(em[0], true);
|
||||
else
|
||||
render(em);
|
||||
em = null;
|
||||
}
|
||||
};
|
||||
|
||||
r.tail = function (url, no_push) {
|
||||
r.abrt = new AbortController();
|
||||
widget.setvis();
|
||||
render([url, '', ''], no_push);
|
||||
var me = r.tail_id = Date.now(),
|
||||
wfp = ebi('wfp'),
|
||||
edoc = ebi('doc'),
|
||||
txt = '';
|
||||
|
||||
url = addq(url, 'tail=-' + r.tailnb);
|
||||
fetch(url, {'signal': r.abrt.signal}).then(function(rsp) {
|
||||
var ro = rsp.body.pipeThrough(
|
||||
new TextDecoderStream('utf-8', {'fatal': false}),
|
||||
{'signal': r.abrt.signal}).getReader();
|
||||
|
||||
var rf = function() {
|
||||
ro.read().then(function(v) {
|
||||
if (r.tail_id != me)
|
||||
return;
|
||||
var vt = v.done ? '\n*** lost connection to copyparty ***' : v.value;
|
||||
if (vt == '\x00')
|
||||
return rf();
|
||||
txt += vt;
|
||||
var ofs = txt.length - r.tailnb;
|
||||
if (ofs > 0) {
|
||||
var ofs2 = txt.indexOf('\n', ofs);
|
||||
if (ofs2 >= ofs && ofs - ofs2 < 512)
|
||||
ofs = ofs2;
|
||||
txt = txt.slice(ofs);
|
||||
}
|
||||
var html = esc(txt);
|
||||
if (r.tailansi)
|
||||
html = r.ansify(html);
|
||||
edoc.innerHTML = html;
|
||||
if (r.tail2end)
|
||||
window.scrollTo(0, wfp.offsetTop - window.innerHeight);
|
||||
if (!v.done)
|
||||
rf();
|
||||
});
|
||||
};
|
||||
if (r.tail_id == me)
|
||||
rf();
|
||||
});
|
||||
};
|
||||
|
||||
r.untail = function () {
|
||||
if (!r.abrt)
|
||||
return;
|
||||
r.abrt.abort();
|
||||
r.abrt = null;
|
||||
r.tail_id = -1;
|
||||
widget.setvis();
|
||||
};
|
||||
|
||||
r.show = function (url, no_push) {
|
||||
r.untail();
|
||||
var xhr = new XHR(),
|
||||
m = /[?&](k=[^&#]+)/.exec(url);
|
||||
|
||||
url = url.split('?')[0] + (m ? '?' + m[1] : '');
|
||||
if (r.taildoc)
|
||||
return r.tail(url, no_push);
|
||||
|
||||
xhr.url = url;
|
||||
xhr.fname = uricom_dec(url.split('/').pop());
|
||||
xhr.no_push = no_push;
|
||||
@@ -5961,7 +6048,8 @@ var showfile = (function () {
|
||||
|
||||
function render(doc, no_push) {
|
||||
r.q = null;
|
||||
var url = doc[0],
|
||||
r.nrend++;
|
||||
var url = r.url = doc[0],
|
||||
lnh = doc[1],
|
||||
txt = doc[2],
|
||||
name = url.split('?')[0].split('/').pop(),
|
||||
@@ -5975,9 +6063,13 @@ var showfile = (function () {
|
||||
ebi('editdoc').style.display = (has(perms, 'write') && (is_md || has(perms, 'delete'))) ? '' : 'none';
|
||||
|
||||
var wr = ebi('bdoc'),
|
||||
nrend = r.nrend,
|
||||
defer = !Prism.highlightElement;
|
||||
|
||||
var fun = function (el) {
|
||||
if (r.nrend != nrend)
|
||||
return;
|
||||
|
||||
try {
|
||||
if (lnh.slice(0, 5) == '#doc.')
|
||||
sethash(lnh.slice(1));
|
||||
@@ -5985,13 +6077,16 @@ var showfile = (function () {
|
||||
el = el || QS('#doc>code');
|
||||
Prism.highlightElement(el);
|
||||
if (el.className == 'language-ans' || (!lang && /\x1b\[[0-9;]{0,16}m/.exec(txt.slice(0, 4096))))
|
||||
r.ansify(el);
|
||||
el.innerHTML = r.ansify(el.innerHTML);
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
|
||||
if (txt.length > 1024 * 256)
|
||||
var skip_prism = !txt || txt.length > 1024 * 256;
|
||||
if (skip_prism) {
|
||||
fun = function (el) { };
|
||||
is_md = false;
|
||||
}
|
||||
|
||||
qsr('#doc');
|
||||
var el = mknod('pre', 'doc');
|
||||
@@ -6003,7 +6098,7 @@ var showfile = (function () {
|
||||
else {
|
||||
el.textContent = txt;
|
||||
el.innerHTML = '<code>' + el.innerHTML + '</code>';
|
||||
if (!window.no_prism) {
|
||||
if (!window.no_prism && !skip_prism) {
|
||||
if ((lang == 'conf' || lang == 'cfg') && ('\n' + txt).indexOf('\n# -*- mode: yaml -*-') + 1)
|
||||
lang = 'yaml';
|
||||
|
||||
@@ -6013,6 +6108,8 @@ var showfile = (function () {
|
||||
else
|
||||
import_js(SR + '/.cpr/deps/prism.js', function () { fun(); });
|
||||
}
|
||||
if (!txt && r.wrap)
|
||||
el.className = 'wrap';
|
||||
}
|
||||
|
||||
wr.appendChild(el);
|
||||
@@ -6034,11 +6131,11 @@ var showfile = (function () {
|
||||
tree_scrollto();
|
||||
}
|
||||
|
||||
r.ansify = function (el) {
|
||||
r.ansify = function (html) {
|
||||
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),
|
||||
src = html.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++) {
|
||||
@@ -6091,7 +6188,7 @@ var showfile = (function () {
|
||||
|
||||
out.push(s + '">' + txt);
|
||||
}
|
||||
el.innerHTML = out.join('');
|
||||
return out.join('');
|
||||
};
|
||||
|
||||
r.mktree = function () {
|
||||
@@ -6138,6 +6235,18 @@ var showfile = (function () {
|
||||
msel.selui();
|
||||
};
|
||||
|
||||
r.tgltail = function () {
|
||||
if (!window.TextDecoderStream) {
|
||||
bcfg_set('taildoc', r.taildoc = false);
|
||||
return toast.err(10, L.tail_2old);
|
||||
}
|
||||
r.show(r.url, true);
|
||||
};
|
||||
|
||||
r.tglwrap = function () {
|
||||
r.show(r.url, true);
|
||||
};
|
||||
|
||||
var bdoc = ebi('bdoc');
|
||||
bdoc.className = 'line-numbers';
|
||||
bdoc.innerHTML = (
|
||||
@@ -6148,15 +6257,38 @@ var showfile = (function () {
|
||||
'<a href="#" class="btn" id="nextdoc" tt="' + L.tvt_next + '</a>\n' +
|
||||
'<a href="#" class="btn" id="seldoc" tt="' + L.tvt_sel + '</a>\n' +
|
||||
'<a href="#" class="btn" id="editdoc" tt="' + L.tvt_edit + '</a>\n' +
|
||||
'<a href="#" class="btn tgl" id="taildoc" tt="' + L.tvt_tail + '</a>\n' +
|
||||
'<div id="tailbtns">\n' +
|
||||
'<a href="#" class="btn tgl" id="wrapdoc" tt="' + L.tvt_wrap + '</a>\n' +
|
||||
'<a href="#" class="btn tgl" id="tail2end" tt="' + L.tvt_atail + '</a>\n' +
|
||||
'<a href="#" class="btn tgl" id="tailansi" tt="' + L.tvt_ctail + '</a>\n' +
|
||||
'<input type="text" id="tailnb" value="" ' + NOAC + ' style="width:4em" tt="' + L.tvt_ntail + '" />' +
|
||||
'</div>\n' +
|
||||
'</div>'
|
||||
);
|
||||
ebi('xdoc').onclick = function () {
|
||||
r.untail();
|
||||
thegrid.setvis(true);
|
||||
bcfg_bind(r, 'taildoc', 'taildoc', false, r.tgltail);
|
||||
};
|
||||
ebi('dldoc').setAttribute('download', '');
|
||||
ebi('prevdoc').onclick = function () { tree_neigh(-1); };
|
||||
ebi('nextdoc').onclick = function () { tree_neigh(1); };
|
||||
ebi('seldoc').onclick = r.tglsel;
|
||||
bcfg_bind(r, 'wrap', 'wrapdoc', true, r.tglwrap);
|
||||
bcfg_bind(r, 'taildoc', 'taildoc', false, r.tgltail);
|
||||
bcfg_bind(r, 'tail2end', 'tail2end', true);
|
||||
bcfg_bind(r, 'tailansi', 'tailansi', false, r.tgltail);
|
||||
|
||||
r.tailnb = ebi('tailnb').value = icfg_get('tailnb', 131072);
|
||||
ebi('tailnb').oninput = function (e) {
|
||||
swrite('tailnb', r.tailnb = this.value);
|
||||
};
|
||||
|
||||
if (/[?&]tail\b/.exec(sloc0)) {
|
||||
clmod(ebi('taildoc'), 'on', 1);
|
||||
r.taildoc = true;
|
||||
}
|
||||
|
||||
return r;
|
||||
})();
|
||||
@@ -6451,6 +6583,7 @@ var thegrid = (function () {
|
||||
ohref = esc(ao.getAttribute('href')),
|
||||
href = ohref.split('?')[0],
|
||||
ext = '',
|
||||
ext0 = '',
|
||||
name = uricom_dec(vsplit(href)[1]),
|
||||
ref = ao.getAttribute('id'),
|
||||
isdir = href.endsWith('/'),
|
||||
@@ -6463,17 +6596,19 @@ var thegrid = (function () {
|
||||
ar.shift();
|
||||
|
||||
ar.reverse();
|
||||
ext0 = ar[0];
|
||||
for (var b = 0; b < Math.min(2, ar.length); b++) {
|
||||
if (ar[b].length > 7)
|
||||
break;
|
||||
|
||||
ext = ar[b] + '.' + ext;
|
||||
ext = ext ? (ar[b] + '.' + ext) : ar[b];
|
||||
}
|
||||
ext = (ext || 'unk.').slice(0, -1);
|
||||
if (!ext)
|
||||
ext = 'unk';
|
||||
}
|
||||
|
||||
if (use_ext_th && ext_th[ext]) {
|
||||
ihref = ext_th[ext];
|
||||
if (use_ext_th && (ext_th[ext] || ext_th[ext0])) {
|
||||
ihref = ext_th[ext] || ext_th[ext0];
|
||||
}
|
||||
else if (r.thumbs) {
|
||||
ihref = addq(ihref, 'th=' + (have_webp ? 'w' : 'j'));
|
||||
@@ -7504,7 +7639,7 @@ var treectl = (function () {
|
||||
bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh);
|
||||
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
|
||||
bcfg_bind(r, 'csel', 'csel', dgsel);
|
||||
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
|
||||
bcfg_bind(r, 'dots', 'dotfiles', see_dots, function (v) {
|
||||
r.goto();
|
||||
var xhr = new XHR();
|
||||
xhr.open('GET', SR + '/?setck=dots=' + (v ? 'y' : ''), true);
|
||||
@@ -8580,7 +8715,7 @@ function apply_perms(res) {
|
||||
if (up2k)
|
||||
up2k.set_fsearch();
|
||||
|
||||
ebi('widget').style.display = have_read ? '' : 'none';
|
||||
widget.setvis();
|
||||
thegrid.setvis();
|
||||
if (!have_read && have_write)
|
||||
goto('up2k');
|
||||
@@ -10111,13 +10246,18 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
|
||||
fun = function () {
|
||||
showfile.show(href, tgt.getAttribute('lang'));
|
||||
},
|
||||
tfun = function () {
|
||||
bcfg_set('taildoc', showfile.taildoc = true);
|
||||
fun();
|
||||
},
|
||||
szs = ft2dict(a.closest('tr'))[0].sz,
|
||||
sz = parseInt(szs.replace(/[, ]/g, ''));
|
||||
|
||||
if (sz < 1024 * 1024)
|
||||
if (sz < 1024 * 1024 || showfile.taildoc)
|
||||
fun();
|
||||
else
|
||||
modal.confirm(L.f_bigtxt.format(f2f(sz / 1024 / 1024, 1)), fun, null);
|
||||
modal.confirm(L.f_bigtxt.format(f2f(sz / 1024 / 1024, 1)), fun, function() {
|
||||
modal.confirm(L.f_bigtxt2, tfun, null)});
|
||||
|
||||
return ev(e);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||
if (url.indexOf(' > eval') + 1 && !evalex_fatal)
|
||||
return; // md timer
|
||||
|
||||
if (IE && url.indexOf('prism.js') + 1)
|
||||
if (url.indexOf('prism.js') + 1)
|
||||
return;
|
||||
|
||||
if (url.indexOf('easymde.js') + 1)
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0527-1939 `v1.17.2` pushing chrome to the limits (and then some)
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* not this time
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* up2k: improve file-hashing speed on recent versions of google chrome e3e51fb8
|
||||
* speed increased from 319 to 513 MiB/s by default (but older chrome versions did 748...)
|
||||
* read the commit message for the full story, but basically chrome has gotten gradually slower over the past couple versions (starting from v133) and this makes it slightly less bad again
|
||||
* hashing speed can be further improved from `0.5` to `1.1` GiB/s by enabling the `[wasm]` option in the `[⚙️] settings` tab
|
||||
* this option can be made default-enabled with `--nosubtle 137` but beware that this increases the chances of running into browser-bugs (foreshadowing...)
|
||||
* up2k: fix errorhandler for browser-bugs (oom and such) 49c71247
|
||||
* because [chrome-bug 383568268](https://issues.chromium.org/issues/383568268) is about to make a [surprise return?!](https://issues.chromium.org/issues/383568268#comment14)
|
||||
* #168 fix uploading into shares if path-based proxying is used 9cb93ae1
|
||||
* #165 unconditionally heed `--rp-loc` 84f5f417
|
||||
* the config-option for [path-based proxying](https://github.com/9001/copyparty/#reverse-proxy) was ignored if the reverse-proxy was untrusted; this was confusing and not strictly necessary
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* #166 the nixos module was improved once more (thx @msfjarvis!) 48470f6b 60fb1207
|
||||
* added usage instructions to [minimal-up2k.js](https://github.com/9001/copyparty/tree/hovudstraum/contrib/plugins#example-browser-js), the up2k-ui [simplifier](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png) 1d308eeb
|
||||
* docker: improve feedback if config is bad or missing 28b63e58
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* this release was tested using an [unreliable rdp connection](https://a.ocv.me/pub/g/nerd-stuff/PXL_20250526_021207825.jpg) through two ssh-jumphosts to a qemu win10 vm back home from the bergen-oslo night train wifi
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0518-2234 `v1.17.1` as seen on archlinux
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* [dev env setup](#dev-env-setup)
|
||||
* [just the sfx](#just-the-sfx)
|
||||
* [build from release tarball](#build-from-release-tarball) - uses the included prebuilt webdeps
|
||||
* [build from scratch](#build-from-scratch) - how the sausage is made
|
||||
* [complete release](#complete-release)
|
||||
* [debugging](#debugging)
|
||||
* [music playback halting on phones](#music-playback-halting-on-phones) - mostly fine on android
|
||||
@@ -190,6 +191,9 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
||||
| GET | `?v` | open image/video/audio in mediaplayer |
|
||||
| GET | `?txt` | get file at URL as plaintext |
|
||||
| GET | `?txt=iso-8859-1` | ...with specific charset |
|
||||
| GET | `?tail` | continuously stream a growing file |
|
||||
| GET | `?tail=1024` | ...starting from byte 1024 |
|
||||
| GET | `?tail=-128` | ...starting 128 bytes from the end |
|
||||
| GET | `?th` | get image/video at URL as thumbnail |
|
||||
| GET | `?th=opus` | convert audio file to 128kbps opus |
|
||||
| GET | `?th=caf` | ...in the iOS-proprietary container |
|
||||
@@ -338,7 +342,7 @@ for the `re`pack to work, first run one of the sfx'es once to unpack it
|
||||
|
||||
you need python 3.9 or newer due to type hints
|
||||
|
||||
the rest is mostly optional; if you need a working env for vscode or similar
|
||||
setting up a venv with the below packages is only necessary if you want it for vscode or similar
|
||||
|
||||
```sh
|
||||
python3 -m venv .venv
|
||||
@@ -392,6 +396,39 @@ python3 setup.py install --skip-build --prefix=/usr --root=$HOME/pe/copyparty
|
||||
```
|
||||
|
||||
|
||||
## build from scratch
|
||||
|
||||
how the sausage is made:
|
||||
|
||||
to get started, first `cd` into the `scripts` folder
|
||||
|
||||
* the first step is the webdeps; they end up in `../copyparty/web/deps/` for example `../copyparty/web/deps/marked.js.gz` -- if you need to build the webdeps, run `make -C deps-docker`
|
||||
* this needs rootless podman and the `podman-docker` compat-layer to pretend it's docker, although it *should* be possible to use rootful/rootless docker too
|
||||
* if you don't have rootless podman/docker then `sudo make -C deps-docker` is fine too
|
||||
* alternatively, you can entirely skip building the webdeps and instead extract the compiled webdeps from the latest github release with `./make-sfx.sh fast dl-wd`
|
||||
|
||||
* next, build `copyparty-sfx.py` by running `./make-sfx.sh gz fast`
|
||||
* this is a dependency for most of the remaining steps, since they take the sfx as input
|
||||
* removing `fast` makes it compress better
|
||||
* removing `gz` too compresses even better, but startup gets slower
|
||||
|
||||
* if you want to build the `.pyz` standalone "binary", now run `./make-pyz.sh`
|
||||
|
||||
* if you want to build a pypi package, now run `./make-pypi-release.sh d`
|
||||
|
||||
* if you want to build a docker-image, you have two options:
|
||||
* if you want to use podman to build all docker-images for all supported architectures, now run `(cd docker; ./make.sh hclean; ./make.sh hclean pull img)`
|
||||
* if you want to use docker to build all docker-images for your native architecture, now run `sudo make -C docker`
|
||||
* if you want to do something else, please take a look at `docker/make.sh` or `docker/Makefile` for inspiration
|
||||
|
||||
* if you want to build the windows exe, first grab some snacks and a beer, [you'll need it](https://github.com/9001/copyparty/tree/hovudstraum/scripts/pyinstaller)
|
||||
|
||||
the complete list of buildtime dependencies to do a build from scratch is as follows:
|
||||
|
||||
* on ubuntu-server, install podman or [docker](https://get.docker.com/), and then `sudo apt install make zip bzip2`
|
||||
* because ubuntu is specifically what someone asked about :-p
|
||||
|
||||
|
||||
## complete release
|
||||
|
||||
also builds the sfx so skip the sfx section above
|
||||
|
||||
@@ -106,3 +106,10 @@
|
||||
/w/tank1
|
||||
[/m8s]
|
||||
/w/tank2
|
||||
|
||||
|
||||
# some other things you can do:
|
||||
# [/demo/${u%-su,%-fds}] # users which are NOT members of "su" or "fds"
|
||||
# [/demo/${u%+su,%+fds}] # users which ARE members of BOTH "su" and "fds"
|
||||
# [/demo/${g%-su}] # all groups except su
|
||||
# [/demo/${g%-su,%-fds}] # all groups except su and fds
|
||||
|
||||
@@ -168,6 +168,7 @@ symbol legend,
|
||||
| upload a 999 TiB file | █ | | | | █ | █ | • | | █ | | █ | ╱ | ╱ |
|
||||
| CTRL-V from device | █ | | | █ | | | | | | | | | |
|
||||
| race the beam ("p2p") | █ | | | | | | | | | | | | |
|
||||
| "tail -f" streaming | █ | | | | | | | | | | | | |
|
||||
| keep last-modified time | █ | | | █ | █ | █ | | | | | | █ | |
|
||||
| upload rules | ╱ | ╱ | ╱ | ╱ | ╱ | | | ╱ | ╱ | | ╱ | ╱ | ╱ |
|
||||
| ┗ max disk usage | █ | █ | █ | | █ | | | | █ | | | █ | █ |
|
||||
@@ -193,6 +194,8 @@ symbol legend,
|
||||
|
||||
* `race the beam` = files can be downloaded while they're still uploading; downloaders are slowed down such that the uploader is always ahead
|
||||
|
||||
* `tail -f` = when viewing or downloading a logfile, the connection can remain open to keep showing new lines as they are added in real time
|
||||
|
||||
* `upload routing` = depending on filetype / contents / uploader etc., the file can be redirected to another location or otherwise transformed; mitigates limitations such as [sharex#3992](https://github.com/ShareX/ShareX/issues/3992)
|
||||
* copyparty example: [reloc-by-ext](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks#before-upload)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ WORKDIR /z
|
||||
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
||||
ver_hashwasm=4.12.0 \
|
||||
ver_marked=4.3.0 \
|
||||
ver_dompf=3.2.5 \
|
||||
ver_dompf=3.2.6 \
|
||||
ver_mde=2.18.0 \
|
||||
ver_codemirror=5.65.18 \
|
||||
ver_fontawesome=5.13.0 \
|
||||
|
||||
@@ -537,6 +537,7 @@ find | grep -E '\.(js|html)$' | while IFS= read -r f; do
|
||||
done
|
||||
|
||||
gzres() {
|
||||
local pk=
|
||||
[ $zopf ] && command -v zopfli && pk="zopfli --i$zopf"
|
||||
[ $zopf ] && command -v pigz && pk="pigz -11 -I $zopf"
|
||||
[ -z "$pk" ] && pk='gzip'
|
||||
@@ -628,7 +629,6 @@ suf=
|
||||
[ $use_gz ] && {
|
||||
sed -r 's/"r:bz2"/"r:gz"/' <$py >$py.t
|
||||
py=$py.t
|
||||
suf=-gz
|
||||
}
|
||||
|
||||
"$pybin" $py --sfx-make tar.bz2 $ver $ts
|
||||
|
||||
@@ -16,7 +16,7 @@ uname -s | grep WOW64 && m=64 || m=32
|
||||
uname -s | grep NT-10 && w10=1 || w7=1
|
||||
[ $w7 ] && [ -e up2k.sh ] && [ ! "$1" ] && ./up2k.sh
|
||||
|
||||
[ $w7 ] && pyv=37 || pyv=312
|
||||
[ $w7 ] && pyv=37 || pyv=313
|
||||
esuf=
|
||||
[ $w7 ] && [ $m = 32 ] && esuf=32
|
||||
[ $w7 ] && [ $m = 64 ] && esuf=-winpe64
|
||||
@@ -89,14 +89,17 @@ excl=(
|
||||
urllib.request
|
||||
urllib.response
|
||||
urllib.robotparser
|
||||
zipfile
|
||||
)
|
||||
[ $w10 ] && excl+=(
|
||||
_pyrepl
|
||||
distutils
|
||||
setuptools
|
||||
PIL.ImageQt
|
||||
PIL.ImageShow
|
||||
PIL.ImageTk
|
||||
PIL.ImageWin
|
||||
PIL.PdfParser
|
||||
zipimport
|
||||
) || excl+=(
|
||||
inspect
|
||||
PIL
|
||||
@@ -104,6 +107,7 @@ excl=(
|
||||
PIL.Image
|
||||
PIL.ImageDraw
|
||||
PIL.ImageOps
|
||||
zipfile
|
||||
)
|
||||
excl=( "${excl[@]/#/--exclude-module }" )
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f8
|
||||
17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl
|
||||
b297ff66ec50cf5a1abcf07d6ac949644c5150ba094ffac974c5d27c81574c3e97ed814a47547f4b03a4c83ea0fb8f026433fca06a3f08e32742dc5c024f3d07 pywin32_ctypes-0.2.3-py3-none-any.whl
|
||||
085d39ef4426aa5f097fbc484595becc16e61ca23fc7da4d2a8bba540a3b82e789e390b176c7151bdc67d01735cce22b1562cdb2e31273225a2d3e275851a4ad setuptools-70.3.0-py3-none-any.whl
|
||||
360a141928f4a7ec18a994602cbb28bbf8b5cc7c077a06ac76b54b12fa769ed95ca0333a5cf728923a8e0baeb5cc4d5e73e5b3de2666beb05eb477d8ae719093 upx-4.2.4-win32.zip
|
||||
644931f8e1764e168c257c11c77b3d2ac5408397d97b0eef98168a058efe793d3ab6900dc2e9c54923a2bd906dd66bfbff8db6ff43418513e530a1bd501c6ccd upx-5.0.1-win32.zip
|
||||
# win7
|
||||
3253e86471e6f9fa85bfdb7684cd2f964ed6e35c6a4db87f81cca157c049bef43e66dfcae1e037b2fb904567b1e028aaeefe8983ba3255105df787406d2aa71e en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso
|
||||
ab0db0283f61a5bbe44797d74546786bf41685175764a448d2e3bd629f292f1e7d829757b26be346b5044d78c9c1891736d93237cee4b1b6f5996a902c86d15f en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso
|
||||
@@ -24,10 +24,11 @@ ac96786e5d35882e0c5b724794329c9125c2b86ae7847f17acfc49f0d294312c6afc1c3f248655de
|
||||
0a2cd4cadf0395f0374974cd2bc2407e5cc65c111275acdffb6ecc5a2026eee9e1bb3da528b35c7f0ff4b64563a74857d5c2149051e281cc09ebd0d1968be9aa en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso
|
||||
16cc0c58b5df6c7040893089f3eb29c074aed61d76dae6cd628d8a89a05f6223ac5d7f3f709a12417c147594a87a94cc808d1e04a6f1e407cc41f7c9f47790d1 virtio-win-0.1.248.iso
|
||||
9a7f40edc6f9209a2acd23793f3cbd6213c94f36064048cb8bf6eb04f1bdb2c2fe991cb09f77fe8b13e5cd85c618ef23573e79813b2fef899ab2f290cd129779 jinja2-3.1.6-py3-none-any.whl
|
||||
6df21f0da408a89f6504417c7cdf9aaafe4ed88cfa13e9b8fa8414f604c0401f885a04bbad0484dc51a29284af5d1548e33c6cc6bfb9896d9992c1b1074f332d MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl
|
||||
00731cfdd9d5c12efef04a7161c90c1e5ed1dc4677aa88a1d4054aff836f3430df4da5262ed4289c21637358a9e10e5df16f76743cbf5a29bb3a44b146c19cf3 MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl
|
||||
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
||||
0203ec2551c4836696cfab0b2c9fff603352f03fa36e7476e2e1ca7ec57a3a0c24bd791fcd92f342bf817f0887854d9f072e0271c643de4b313d8c9569ba8813 packaging-24.1-py3-none-any.whl
|
||||
c9051daaf34ec934962c743a5ac2dbe55a9b0cababb693a8cde0001d24d4a50b67bd534d714d935def6ca7b898ec0a352e58bd9ccdce01c54eaf2281b18e478d pillow-11.2.1-cp312-cp312-win_amd64.whl
|
||||
f0463895e9aee97f31a2003323de235fed1b26289766dc0837261e3f4a594a31162b69e9adbb0e9a31e2e2d4b5f25c762ed1669553df7dc89a8ba4f85d297873 pyinstaller-6.11.1-py3-none-win_amd64.whl
|
||||
d550a0a14428386945533de2220c4c2e37c0c890fc51a600f626c6ca90a32d39572c121ec04c157ba3a8d6601cb021f8433d871b5c562a3d342c804fffec90c1 pyinstaller_hooks_contrib-2024.11-py3-none-any.whl
|
||||
4f9a4d9f65c93e2d851e2674057343a9599f30f5dc582ffca485522237d4fcf43653b3d393ed5eb11e518c4ba93714a07134bbb13a97d421cce211e1da34682e python-3.12.10-amd64.exe
|
||||
a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac72f35a624401f3f3b442882ba1cc4cadaf9c88558b5b8bdae packaging-25.0-py3-none-any.whl
|
||||
9265164114db16c7f4286f188d6ebc7f3e2c9a9aca7144f92bc66c320d7d5db44e2c3a93e5e8f8cc12b8ffb30d0402a9510716ed7dbd10df39d19f5cf7ed7486 pillow-11.2.1-cp313-cp313-win_amd64.whl
|
||||
59fbbcae044f4ee73d203ac74b553b27bfad3e6b2f3fb290fd3f8774753c6b545176b6b3399c240b092d131d152290ce732750accd962dc1e48e930be85f5e53 pyinstaller-6.14.1-py3-none-win_amd64.whl
|
||||
fc6f3e144c5f5b662412de07cb8bf0c2eb3b3be21d19ec448aef3c4244d779b9ab8027fd67a4871e6e13823b248ea0f5a7a9241a53aef30f3b51a6d3cb5bdb3f pyinstaller_hooks_contrib-2025.5-py3-none-any.whl
|
||||
2c7a52e223b8186c21009d3fa5ed6a856d8eb4ef3b98f5d24c378c6a1afbfa1378bd7a51d6addc500e263d7989efb544c862bf920055e740f137c702dfd9d18b python-3.13.5-amd64.exe
|
||||
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl
|
||||
|
||||
@@ -29,19 +29,19 @@ uname -s | grep NT-10 && w10=1 || {
|
||||
fns=(
|
||||
altgraph-0.17.4-py2.py3-none-any.whl
|
||||
pefile-2023.2.7-py3-none-any.whl
|
||||
pywin32_ctypes-0.2.2-py3-none-any.whl
|
||||
setuptools-70.3.0-py3-none-any.whl
|
||||
upx-4.2.4-win32.zip
|
||||
pywin32_ctypes-0.2.3-py3-none-any.whl
|
||||
upx-5.0.1-win32.zip
|
||||
)
|
||||
[ $w10 ] && fns+=(
|
||||
jinja2-3.1.6-py3-none-any.whl
|
||||
MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl
|
||||
MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl
|
||||
mutagen-1.47.0-py3-none-any.whl
|
||||
packaging-24.1-py3-none-any.whl
|
||||
pillow-11.2.1-cp312-cp312-win_amd64.whl
|
||||
pyinstaller-6.10.0-py3-none-win_amd64.whl
|
||||
pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
||||
python-3.12.10-amd64.exe
|
||||
packaging-25.0-py3-none-any.whl
|
||||
pillow-11.2.1-cp313-cp313-win_amd64.whl
|
||||
pyinstaller-6.14.1-py3-none-win_amd64.whl
|
||||
pyinstaller_hooks_contrib-2025.5-py3-none-any.whl
|
||||
python-3.13.5-amd64.exe
|
||||
setuptools-80.9.0-py3-none-any.whl
|
||||
)
|
||||
[ $w7 ] && fns+=(
|
||||
future-1.0.0-py3-none-any.whl
|
||||
@@ -49,6 +49,7 @@ fns=(
|
||||
packaging-24.0-py3-none-any.whl
|
||||
pip-24.0-py3-none-any.whl
|
||||
pyinstaller_hooks_contrib-2023.8-py2.py3-none-any.whl
|
||||
setuptools-70.3.0-py3-none-any.whl
|
||||
typing_extensions-4.7.1-py3-none-any.whl
|
||||
zipp-3.15.0-py3-none-any.whl
|
||||
)
|
||||
@@ -80,7 +81,7 @@ close and reopen git-bash so python is in PATH
|
||||
|
||||
===[ copy-paste into git-bash ]================================
|
||||
uname -s | grep NT-10 && w10=1 || w7=1
|
||||
[ $w7 ] && pyv=37 || pyv=312
|
||||
[ $w7 ] && pyv=37 || pyv=313
|
||||
appd=$(cygpath.exe "$APPDATA")
|
||||
cd ~/Downloads &&
|
||||
yes | unzip upx-*-win32.zip &&
|
||||
|
||||
@@ -34,6 +34,7 @@ shift
|
||||
./make-sfx.sh "$@"
|
||||
f=../dist/copyparty-sfx
|
||||
[ -e $f.py ] && s= || s=-gz
|
||||
# TODO: the -gz suffix is gone, can drop all the $s stuff probably
|
||||
|
||||
$f$s.py --version >/dev/null
|
||||
|
||||
|
||||
@@ -423,6 +423,7 @@ var tl_browser = {
|
||||
"f_empty": 'this folder is empty',
|
||||
"f_chide": 'this will hide the column «{0}»\n\nyou can unhide columns in the settings tab',
|
||||
"f_bigtxt": "this file is {0} MiB large -- really view as text?",
|
||||
"f_bigtxt2": "view just the end of the file instead? this will also enable following/tailing, showing newly added lines of text in real time",
|
||||
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
||||
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
||||
"f_anota": "only {0} of the {1} items were selected;\nto select the full folder, first scroll to the bottom",
|
||||
@@ -527,6 +528,11 @@ var tl_browser = {
|
||||
"tvt_next": "show next document$NHotkey: K\">⬇ next",
|
||||
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||
"tvt_tail": "monitor file for changes; show new lines in real time\">📡 follow",
|
||||
"tvt_wrap": "word-wrap\">↵",
|
||||
"tvt_atail": "lock scroll to bottom of page\">⚓",
|
||||
"tvt_ctail": "decode terminal colors (ansi escape codes)\">🌈",
|
||||
"tvt_ntail": "scrollback limit (how many bytes of text to keep loaded)",
|
||||
|
||||
"m3u_add1": "song added to m3u playlist",
|
||||
"m3u_addn": "{0} songs added to m3u playlist",
|
||||
@@ -626,6 +632,7 @@ var tl_browser = {
|
||||
"u_https3": "for better performance",
|
||||
"u_ancient": 'your browser is impressively ancient -- maybe you should <a href="#" onclick="goto(\'bup\')">use bup instead</a>',
|
||||
"u_nowork": "need firefox 53+ or chrome 57+ or iOS 11+",
|
||||
"tail_2old": "need firefox 105+ or chrome 71+ or iOS 14.5+",
|
||||
"u_nodrop": 'your browser is too old for drag-and-drop uploading',
|
||||
"u_notdir": "that's not a folder!\n\nyour browser is too old,\nplease try dragdrop instead",
|
||||
"u_uri": "to dragdrop images from other browser windows,\nplease drop it onto the big upload button",
|
||||
|
||||
46
tests/res/idp/7.conf
Normal file
46
tests/res/idp/7.conf
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- mode: yaml -*-
|
||||
# vim: ft=yaml:
|
||||
|
||||
[global]
|
||||
idp-h-usr: x-idp-user
|
||||
idp-h-grp: x-idp-group
|
||||
|
||||
[/u/${u}]
|
||||
/u/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/uya/${u%+ga}]
|
||||
/uya/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/uyab/${u%+ga,%+gb}]
|
||||
/uyab/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/una/${u%-ga}]
|
||||
/una/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/unab/${u%-ga,%-gb}]
|
||||
/unab/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/gya/${g%+ga}]
|
||||
/gya/${g}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/gna/${g%-ga}]
|
||||
/gna/${g}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/gnab/${g%-ga,%-gb}]
|
||||
/gnab/${g}
|
||||
accs:
|
||||
r: *
|
||||
47
tests/res/idp/8.conf
Normal file
47
tests/res/idp/8.conf
Normal file
@@ -0,0 +1,47 @@
|
||||
# -*- mode: yaml -*-
|
||||
# vim: ft=yaml:
|
||||
|
||||
[groups]
|
||||
ga: iua, iuab, iuabc
|
||||
gb: iuab, iuabc, iub, iubc
|
||||
gc: iuabc, iubc, iuc
|
||||
|
||||
[/u/${u}]
|
||||
/u/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/uya/${u%+ga}]
|
||||
/uya/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/uyab/${u%+ga,%+gb}]
|
||||
/uyab/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/una/${u%-ga}]
|
||||
/una/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/unab/${u%-ga,%-gb}]
|
||||
/unab/${u}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/gya/${g%+ga}]
|
||||
/gya/${g}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/gna/${g%-ga}]
|
||||
/gna/${g}
|
||||
accs:
|
||||
r: *
|
||||
|
||||
[/gnab/${g%-ga,%-gb}]
|
||||
/gnab/${g}
|
||||
accs:
|
||||
r: *
|
||||
@@ -234,3 +234,74 @@ class TestVFS(unittest.TestCase):
|
||||
au.idp_checkin(None, "iud", "su")
|
||||
self.assertAxsAt(au, "team/su/iuc", [["iuc", "iud"]])
|
||||
self.assertAxsAt(au, "team/su/iud", [["iuc", "iud"]])
|
||||
|
||||
def test_7(self):
|
||||
"""
|
||||
conditional idp-vols
|
||||
"""
|
||||
_, cfgdir, xcfg = self.prep()
|
||||
au = AuthSrv(Cfg(c=[cfgdir + "/7.conf"], **xcfg), self.log)
|
||||
au.idp_checkin(None, "iua", "ga")
|
||||
au.idp_checkin(None, "iuab", "ga,gb")
|
||||
au.idp_checkin(None, "iuabc", "ga,gb,gc")
|
||||
au.idp_checkin(None, "iub", "gb")
|
||||
au.idp_checkin(None, "iubc", "gb,gc")
|
||||
au.idp_checkin(None, "iuc", "gc")
|
||||
zs = """
|
||||
u/iua
|
||||
u/iuab
|
||||
u/iuabc
|
||||
u/iub
|
||||
u/iubc
|
||||
u/iuc
|
||||
uya/iua
|
||||
uya/iuab
|
||||
uya/iuabc
|
||||
uyab/iuab
|
||||
uyab/iuabc
|
||||
una/iub
|
||||
una/iubc
|
||||
una/iuc
|
||||
unab/iuc
|
||||
gya/ga
|
||||
gna/gb
|
||||
gna/gc
|
||||
gnab/gc
|
||||
"""
|
||||
zl1 = sorted(zs.strip().split("\n"))[:]
|
||||
zl2 = sorted(list(au.vfs.all_vols))[:]
|
||||
# print(" ".join(zl1))
|
||||
# print(" ".join(zl2))
|
||||
self.assertListEqual(zl1, zl2)
|
||||
|
||||
def test_8(self):
|
||||
"""
|
||||
conditional non-idp vols
|
||||
"""
|
||||
_, cfgdir, xcfg = self.prep()
|
||||
xcfg = {"vc": True}
|
||||
au = AuthSrv(Cfg(c=[cfgdir + "/8.conf"], **xcfg), self.log)
|
||||
zs = """
|
||||
u/iua
|
||||
u/iuab
|
||||
u/iuabc
|
||||
u/iub
|
||||
u/iubc
|
||||
u/iuc
|
||||
uya/iua
|
||||
uya/iuab
|
||||
uya/iuabc
|
||||
uyab/iuab
|
||||
uyab/iuabc
|
||||
una/iub
|
||||
una/iubc
|
||||
una/iuc
|
||||
unab/iuc
|
||||
gya/ga
|
||||
gna/gb
|
||||
gna/gc
|
||||
gnab/gc
|
||||
"""
|
||||
zl1 = sorted(zs.strip().split("\n"))[:]
|
||||
zl2 = sorted(list(au.vfs.all_vols))[:]
|
||||
self.assertListEqual(zl1, zl2)
|
||||
|
||||
@@ -143,19 +143,19 @@ class Cfg(Namespace):
|
||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||
ka = {}
|
||||
|
||||
ex = "chpw daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||
ex = "chpw daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz rmagic rss smb srch_dbg srch_excl stats uqe vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||
ka.update(**{k: False for k in ex.split()})
|
||||
|
||||
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
|
||||
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip"
|
||||
ka.update(**{k: True for k in ex.split()})
|
||||
|
||||
ex = "ah_cli ah_gen css_browser dbpath hist ipu js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua ua_nodoc ua_nozip"
|
||||
ka.update(**{k: None for k in ex.split()})
|
||||
|
||||
ex = "hash_mt hsortn safe_dedup srch_time u2abort u2j u2sz"
|
||||
ex = "hash_mt hsortn safe_dedup srch_time tail_fd tail_rate u2abort u2j u2sz"
|
||||
ka.update(**{k: 1 for k in ex.split()})
|
||||
|
||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt ups_who zip_who"
|
||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who"
|
||||
ka.update(**{k: 9 for k in ex.split()})
|
||||
|
||||
ex = "db_act forget_ip k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
||||
|
||||
Reference in New Issue
Block a user