mirror of
https://github.com/9001/copyparty.git
synced 2026-04-13 07:32:49 -06:00
Compare commits
No commits in common. "hovudstraum" and "v1.20.11" have entirely different histories.
hovudstrau
...
v1.20.11
113
README.md
113
README.md
|
|
@ -65,7 +65,6 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
|
||||||
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
||||||
* [server config](#server-config) - using arguments or config files, or a mix of both
|
* [server config](#server-config) - using arguments or config files, or a mix of both
|
||||||
* [version-checker](#version-checker) - sleep better at night
|
* [version-checker](#version-checker) - sleep better at night
|
||||||
* [logging](#logging) - serverlog is sent to stdout by default
|
|
||||||
* [zeroconf](#zeroconf) - announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png))
|
* [zeroconf](#zeroconf) - announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png))
|
||||||
* [mdns](#mdns) - LAN domain-name and feature announcer
|
* [mdns](#mdns) - LAN domain-name and feature announcer
|
||||||
* [ssdp](#ssdp) - windows-explorer announcer
|
* [ssdp](#ssdp) - windows-explorer announcer
|
||||||
|
|
@ -87,8 +86,6 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
|
||||||
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
||||||
* [chmod and chown](#chmod-and-chown) - per-volume filesystem-permissions and ownership
|
* [chmod and chown](#chmod-and-chown) - per-volume filesystem-permissions and ownership
|
||||||
* [other flags](#other-flags)
|
* [other flags](#other-flags)
|
||||||
* [descript.ion](#description) - add a description to each file in a folder
|
|
||||||
* [dothidden](#dothidden) - cosmetically hide specific files in a folder
|
|
||||||
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
|
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
|
||||||
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
||||||
* [metadata from xattrs](#metadata-from-xattrs) - unix extended file attributes
|
* [metadata from xattrs](#metadata-from-xattrs) - unix extended file attributes
|
||||||
|
|
@ -120,7 +117,6 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
|
||||||
* [packages](#packages) - the party might be closer than you think
|
* [packages](#packages) - the party might be closer than you think
|
||||||
* [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
|
* [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
|
||||||
* [fedora package](#fedora-package) - does not exist yet
|
* [fedora package](#fedora-package) - does not exist yet
|
||||||
* [gentoo ::guru package](#gentoo-guru-package) - `emerge www-servers/copyparty::guru` (in [::guru](https://wiki.gentoo.org/wiki/Project:GURU))
|
|
||||||
* [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg`
|
* [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg`
|
||||||
* [nix package](#nix-package) - `nix profile install github:9001/copyparty`
|
* [nix package](#nix-package) - `nix profile install github:9001/copyparty`
|
||||||
* [nixos module](#nixos-module)
|
* [nixos module](#nixos-module)
|
||||||
|
|
@ -608,12 +604,10 @@ and if you want to use config files instead of commandline args (good!) then her
|
||||||
|
|
||||||
hiding specific subfolders by mounting another volume on top of them
|
hiding specific subfolders by mounting another volume on top of them
|
||||||
|
|
||||||
for example `-v /mnt::r -v /var/empty:web/certs:` (note: no permissions) mounts the server folder `/mnt` as the webroot, but another volume is mounted at `/web/certs` -- so visitors can only see the contents of `/mnt` and `/mnt/web` (at URLs `/` and `/web`), but not `/mnt/web/certs` because URL `/web/certs` is mapped to `/var/empty`
|
for example `-v /mnt::r -v /var/empty:web/certs:r` mounts the server folder `/mnt` as the webroot, but another volume is mounted at `/web/certs` -- so visitors can only see the contents of `/mnt` and `/mnt/web` (at URLs `/` and `/web`), but not `/mnt/web/certs` because URL `/web/certs` is mapped to `/var/empty`
|
||||||
|
|
||||||
the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead
|
the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead
|
||||||
|
|
||||||
so, to shadow a file/folder, define a volume but leave out the `accs:` section
|
|
||||||
|
|
||||||
> ℹ️ this also works for single files, because files can also be volumes
|
> ℹ️ this also works for single files, because files can also be volumes
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -625,8 +619,6 @@ anyone can access these if they know the name, but they normally don't appear in
|
||||||
|
|
||||||
a client can request to see dotfiles in directory listings if global option `-ed` is specified, or the volume has volflag `dots`, or the user has permission `.`
|
a client can request to see dotfiles in directory listings if global option `-ed` is specified, or the volume has volflag `dots`, or the user has permission `.`
|
||||||
|
|
||||||
> for [shares](#shares), the `dots` volflag is ignored
|
|
||||||
|
|
||||||
dotfiles do not appear in search results unless one of the above is true, **and** the global option / volflag `dotsrch` is set
|
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
|
> 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
|
||||||
|
|
@ -774,7 +766,6 @@ to show `/icons/exe.png` and `/icons/elf.gif` as the thumbnail for all `.exe` an
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* heif/heifs/heic/heics images usually require the `libvips` [optional dependency](#optional-dependencies) but this is not possible with the docker-images due to [legal reasons](docs/bad-codecs.md)
|
* heif/heifs/heic/heics images usually require the `libvips` [optional dependency](#optional-dependencies) but this is not possible with the docker-images due to [legal reasons](docs/bad-codecs.md)
|
||||||
* if you do not want thumbnails to be generated on-the-fly, and instead wish to generate all of them on server startup, then see [thumbnail pregen](#thumbnail-pregen)
|
|
||||||
|
|
||||||
config file example:
|
config file example:
|
||||||
|
|
||||||
|
|
@ -833,7 +824,6 @@ cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3`
|
||||||
* and url-param `&nodot` skips dotfiles/dotfolders; they are included by default if your account has permission to see them
|
* and url-param `&nodot` skips dotfiles/dotfolders; they are included by default if your account has permission to see them
|
||||||
* and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
|
* and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
|
||||||
* can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
|
* can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
|
||||||
* but now there is also a real [thumbnail pregen](#thumbnail-pregen) so just use that
|
|
||||||
|
|
||||||
|
|
||||||
## uploading
|
## uploading
|
||||||
|
|
@ -1015,8 +1005,6 @@ specify `--shr /foobar` to enable this feature; a toplevel virtual folder named
|
||||||
|
|
||||||
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
|
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
|
||||||
|
|
||||||
the volflag `--shr-who` lets you control who can create a share from that volume, either `no` (nobody), `a` (people with admin permission), or `auth` (people who are logged in)
|
|
||||||
|
|
||||||
after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there
|
after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there
|
||||||
|
|
||||||
**security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
|
**security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
|
||||||
|
|
@ -1351,33 +1339,6 @@ config file example:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## logging
|
|
||||||
|
|
||||||
serverlog is sent to stdout by default (but logging to a file is also possible)
|
|
||||||
|
|
||||||
"stdout" usually means either the terminal, or journalctl, or whatever is collecting logs from your docker containers, so that depends on your setup
|
|
||||||
|
|
||||||
* [-q](https://copyparty.eu/cli/#g-q) disables logging to stdout, and may improve performance a little bit
|
|
||||||
* combine it with `-lo logfolder/cpp-%Y-%m-%d.txt` to log to a file instead
|
|
||||||
* the `%Y-%m-%d` makes it create a new logfile every day, with the date as filename
|
|
||||||
* `-lo whatever.txt` can be used without `-q` to log to both at the same time
|
|
||||||
* by default, the logfile will have colors if the terminal does (usually the case)
|
|
||||||
* use the [textfile-viewer](https://github.com/user-attachments/assets/8a828947-2fae-4df9-bd2a-3de46f42d478) or `less -R` in a terminal to see colors correctly
|
|
||||||
* if you want [no colors](https://youtu.be/biW5UVGkPMA?t=148):
|
|
||||||
* `--flo 2` disables colors for just the logfile
|
|
||||||
* `--no-ansi` disables colors for both the terminal and logfile
|
|
||||||
|
|
||||||
config file example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
[global]
|
|
||||||
log-date: %Y-%m-%d # show dates on stdout too
|
|
||||||
lo: /var/log/cpp/%Y-%m-%d.txt # logfile path
|
|
||||||
flo: 2 # just text (no colors) in logfile
|
|
||||||
q # disable stdout; use logfile only
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## zeroconf
|
## zeroconf
|
||||||
|
|
||||||
announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png)) -- `-z` enables both [mdns](#mdns) and [ssdp](#ssdp)
|
announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png)) -- `-z` enables both [mdns](#mdns) and [ssdp](#ssdp)
|
||||||
|
|
@ -1886,55 +1847,6 @@ notes:
|
||||||
* adding `?cache` to a link will override this with "fully cache this for 69 seconds"; `?cache=321` is 321 seconds, and `?cache=i` is 7 days
|
* adding `?cache` to a link will override this with "fully cache this for 69 seconds"; `?cache=321` is 321 seconds, and `?cache=i` is 7 days
|
||||||
|
|
||||||
|
|
||||||
## descript.ion
|
|
||||||
|
|
||||||
add a description to each file in a folder by adding them to a textfile named `descript.ion`
|
|
||||||
|
|
||||||
see https://copyparty.eu/beta/ for an example -- here's a basic `descript.ion` file:
|
|
||||||
|
|
||||||
```
|
|
||||||
bookmark.mp3 Taishi feat. Rita - Bookmark Memories
|
|
||||||
slowstep.mp3 Taishi feat. 向日葵 - Slow Step -F.L.C.A-
|
|
||||||
prsnlzr.mp3 Taishi feat. みとせのりこ - Personalizer
|
|
||||||
cosmos.mp3 Taishi feat. Rita - Into the cosmos
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## dothidden
|
|
||||||
|
|
||||||
cosmetically hide specific files in a folder by adding them to a textfile named `.hidden`
|
|
||||||
|
|
||||||
this option is default-disabled; enable the volflag and/or global-option `dothidden`
|
|
||||||
|
|
||||||
this is **cosmetic only!** the files are still easily accessible in many ways, for example with download-as-zip/tar, so **do not** rely on this for security.
|
|
||||||
|
|
||||||
> also see the [--unlist](https://copyparty.eu/cli/#g-unlist) option which is somewhat similar -- `unlist` applies to the whole volume instead of just one folder; however, while dothidden also affects sftp and ftp, the `unlist` option is http/https-only
|
|
||||||
|
|
||||||
|
|
||||||
## thumbnail pregen
|
|
||||||
|
|
||||||
if you want to pre-generate everything on startup (usually a bad idea);
|
|
||||||
|
|
||||||
by default, thumbnails are created on-the-fly when a client needs it, and then cached on the server for [--th-maxage](https://copyparty.eu/cli/#g-th-maxage) seconds (default is one week), so most thumbnails only need to be created once, and are then eventually deleted from the cache to preserver disk space
|
|
||||||
|
|
||||||
but if you need every thumbnail instantly available when a folder is viewed, then first increase the thumbnail expiration time to something really big, and then set global-option `th-pregen` and volflag `th_pregen` to a comma-separated list of thumbnail formats to automatically generate on server startup;
|
|
||||||
|
|
||||||
the full list of all possible formats is: `j,jf,jf3,j3,w,wf,wf3,w3,x,xf,xf3,x3,opus,mp3,flac,wav` and I'll explain what those mean soon
|
|
||||||
|
|
||||||
* `j` = jpeg cropped, `jf` = jpeg uncropped, `jf3` = jpeg uncropped triplesize, `j3` jpeg cropped triplesize
|
|
||||||
* `w` = webm cropped, `wf` = webm uncropped, ..., `x` = jxl cropped, `xf` = jxl uncropped, ...
|
|
||||||
* and yes, audio-transcodes are technically thumbnails according to copyparty -- don't think too much about it ( ゚ ヮ゚)
|
|
||||||
* unlike thumbnails, the expiry time for audio-transcodes is configured with [--ac-maxage](https://copyparty.eu/cli/#g-ac-maxage)
|
|
||||||
|
|
||||||
anyways, obviously you **do not** want to pregenerate flac/wav because they're HUGE, and everything else also gets pretty big because it all adds up;
|
|
||||||
|
|
||||||
* each regular thumbnail ( j, jf, w, wf, x, xf ) takes about 16 KiB of disk space
|
|
||||||
* each triplesize thumb ( j3, jf3, w3, wf3, x3, xf3 ) takes about 96 KiB
|
|
||||||
* each opus / mp3 audiotranscode takes... idk, 6 MiB? depends on song length
|
|
||||||
|
|
||||||
so a thousand pictures converted to every possible regular-size image format (`j,jf,w,wf,x,xf`) takes **96 MiB,** and every possible 3x-size (`jf3,j3,wf3,w3,xf3,x3`) takes **562 MiB,** alternatively **658 MiB** in total for all, so that's why the default is to *not* pregenerate on startup, but instead do on-demand with a cache
|
|
||||||
|
|
||||||
|
|
||||||
## database location
|
## database location
|
||||||
|
|
||||||
in-volume (`.hist/up2k.db`, default) or somewhere else
|
in-volume (`.hist/up2k.db`, default) or somewhere else
|
||||||
|
|
@ -2588,23 +2500,6 @@ after installing, start either the system service or the user service and naviga
|
||||||
does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
|
does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
|
||||||
|
|
||||||
|
|
||||||
## gentoo ::guru package
|
|
||||||
|
|
||||||
`emerge www-servers/copyparty::guru` (in [::guru](https://wiki.gentoo.org/wiki/Project:GURU))
|
|
||||||
|
|
||||||
but first enable the `::guru` repo;
|
|
||||||
|
|
||||||
```bash
|
|
||||||
emerge -an app-eselect/eselect-repository
|
|
||||||
eselect repository enable guru
|
|
||||||
emerge --sync guru
|
|
||||||
```
|
|
||||||
|
|
||||||
to start the service as a user:
|
|
||||||
* OpenRC: `rc-service -U copyparty start && rc-update -U add copyparty default`
|
|
||||||
* systemd: [todo]
|
|
||||||
|
|
||||||
|
|
||||||
## homebrew formulae
|
## homebrew formulae
|
||||||
|
|
||||||
`brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty
|
`brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty
|
||||||
|
|
@ -2749,12 +2644,6 @@ services.copyparty = {
|
||||||
};
|
};
|
||||||
# you may increase the open file limit for the process
|
# you may increase the open file limit for the process
|
||||||
openFilesLimit = 8192;
|
openFilesLimit = 8192;
|
||||||
|
|
||||||
# override the package used by the module to add dependencies, e.g. for hooks
|
|
||||||
package = pkgs.copyparty.override {
|
|
||||||
# provides exiftool for bin/hooks/image-noexif.py
|
|
||||||
extraPackages = [ pkgs.exiftool ];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
# /!\ Warning: be careful, as webdav clients often generate a large number of 404 requets.
|
|
||||||
|
|
||||||
# In your `jail.local`, add:
|
|
||||||
# [copyparty]
|
|
||||||
# enabled = true
|
|
||||||
# logtimezone = UTC
|
|
||||||
# logpath = /path/to/log/file # or keep the default value if you're using systemd
|
|
||||||
|
|
||||||
# Create the `copyparty.conf` file in `filter.d` with the following:
|
|
||||||
# [Definition]
|
|
||||||
# failregex = ^ <ADDR>$
|
|
||||||
# ignoreregex =
|
|
||||||
# datepattern = ^fail2ban: %%Y-%%m-%%dT%%H:%%M:%%S
|
|
||||||
|
|
||||||
# First check `--dont-ban`, and if it doesn't match, log the line to be intercepted by fail2ban.
|
|
||||||
from datetime import datetime, UTC
|
|
||||||
def main(cli, vn="", rem=""):
|
|
||||||
now = datetime.now(UTC).isoformat()[:19]
|
|
||||||
msg = "\nfail2ban: %s %s"
|
|
||||||
if not vn and not rem:
|
|
||||||
# got called by --xban
|
|
||||||
cli["log"](msg % (now, cli["ip"]), 3)
|
|
||||||
return {"rc": 0}
|
|
||||||
|
|
||||||
cond = cli.args.dont_ban
|
|
||||||
if (
|
|
||||||
cond == "any"
|
|
||||||
or (cond == "auth" and cli.uname != "*")
|
|
||||||
or (cond == "aa" and cli.avol)
|
|
||||||
or (cond == "av" and cli.can_admin)
|
|
||||||
or (cond == "rw" and cli.can_read and cli.can_write)
|
|
||||||
):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
cli.log(msg % (now, cli.ip), 3)
|
|
||||||
return ""
|
|
||||||
|
|
@ -22,11 +22,10 @@ each plugin must define a `main()` which takes 3 arguments;
|
||||||
|
|
||||||
* [redirect.py](redirect.py) sends an HTTP 301 or 302, redirecting the client to another page/file
|
* [redirect.py](redirect.py) sends an HTTP 301 or 302, redirecting the client to another page/file
|
||||||
* [randpic.py](randpic.py) redirects `/foo/bar/randpic.jpg` to a random pic in `/foo/bar/`
|
* [randpic.py](randpic.py) redirects `/foo/bar/randpic.jpg` to a random pic in `/foo/bar/`
|
||||||
* [sorry.py](sorry.py) replies with a custom message instead of the usual 404
|
* [sorry.py](answer.py) replies with a custom message instead of the usual 404
|
||||||
* [nooo.py](nooo.py) replies with an endless noooooooooooooo
|
* [nooo.py](nooo.py) replies with an endless noooooooooooooo
|
||||||
* [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary
|
* [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary
|
||||||
* [caching-proxy.py](caching-proxy.py) transforms copyparty into a squid/varnish knockoff
|
* [caching-proxy.py](caching-proxy.py) transforms copyparty into a squid/varnish knockoff
|
||||||
* [404-to-fail2ban.py](404-to-fail2ban.py) creates 404 logs for fail2ban
|
|
||||||
|
|
||||||
## on403
|
## on403
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,9 @@ server {
|
||||||
# recommendation: replace cpp_tcp with cpp_uds below
|
# recommendation: replace cpp_tcp with cpp_uds below
|
||||||
proxy_pass http://cpp_tcp;
|
proxy_pass http://cpp_tcp;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
# disable buffering (next 3 lines)
|
# disable buffering (next 4 lines)
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
client_max_body_size 0;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_request_buffering off;
|
proxy_request_buffering off;
|
||||||
# improve download speed from 600 to 1500 MiB/s
|
# improve download speed from 600 to 1500 MiB/s
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
||||||
|
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.20.13"
|
pkgver="1.20.10"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
|
|
@ -24,7 +24,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")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}/copyparty.conf" )
|
backup=("etc/${pkgname}/copyparty.conf" )
|
||||||
sha256sums=("b2af9250f7ef97a5df26df412ee082c6d2be0f0cd31d579b4fbb6aa2f3e5c271")
|
sha256sums=("a651df2ab768ebdf2f41b7ff1e1fec788ae8a34848ce228c189f2d0f566c9fd9")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
|
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver=1.20.13
|
pkgver=1.20.10
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
|
|
@ -21,7 +21,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")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("/etc/${pkgname}.d/init" )
|
backup=("/etc/${pkgname}.d/init" )
|
||||||
sha256sums=("b2af9250f7ef97a5df26df412ee082c6d2be0f0cd31d579b4fbb6aa2f3e5c271")
|
sha256sums=("a651df2ab768ebdf2f41b7ff1e1fec788ae8a34848ce228c189f2d0f566c9fd9")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.20.13/copyparty-1.20.13.tar.gz",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.20.10/copyparty-1.20.10.tar.gz",
|
||||||
"version": "1.20.13",
|
"version": "1.20.10",
|
||||||
"hash": "sha256-sq+SUPfvl6XfJt9BLuCCxtK+DwzTHVebT7tqovPlwnE="
|
"hash": "sha256-plHfKrdo698vQbf/Hh/seIroo0hIziKMGJ8tD1Zsn9k="
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,5 @@
|
||||||
accs:
|
accs:
|
||||||
rw: * # everyone gets read-write access, but
|
rw: * # everyone gets read-write access, but
|
||||||
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
||||||
flags:
|
|
||||||
e2ds # enable filesystem-scanning for this volume only
|
|
||||||
# uid: 1000 # If you're running as root, you can change the owner of this volume here
|
# uid: 1000 # If you're running as root, you can change the owner of this volume here
|
||||||
# gid: 1000 # If you're running as root, you can change the group of this volume here
|
# gid: 1000 # If you're running as root, you can change the group of this volume here
|
||||||
|
|
|
||||||
|
|
@ -49,5 +49,3 @@
|
||||||
accs:
|
accs:
|
||||||
rw: * # everyone gets read-write access, but
|
rw: * # everyone gets read-write access, but
|
||||||
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
||||||
flags:
|
|
||||||
e2ds # enable filesystem-scanning for this volume only
|
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,6 @@ ANYWIN = WINDOWS or sys.platform in ["msys", "cygwin"]
|
||||||
|
|
||||||
MACOS = platform.system() == "Darwin"
|
MACOS = platform.system() == "Darwin"
|
||||||
|
|
||||||
FREEBSD = platform.system() == "FreeBSD"
|
|
||||||
|
|
||||||
OPENBSD = platform.system() == "OpenBSD"
|
|
||||||
|
|
||||||
ANYBSD = FREEBSD or OPENBSD
|
|
||||||
|
|
||||||
UNIX = MACOS or ANYBSD
|
|
||||||
|
|
||||||
GRAAL = platform.python_implementation() == "GraalVM"
|
GRAAL = platform.python_implementation() == "GraalVM"
|
||||||
|
|
||||||
EXE = bool(getattr(sys, "frozen", False))
|
EXE = bool(getattr(sys, "frozen", False))
|
||||||
|
|
|
||||||
|
|
@ -1520,7 +1520,6 @@ def add_smb(ap):
|
||||||
ap2.add_argument("--smb-nwa-1", action="store_true", help="truncate directory listings to 64kB (~400 files); avoids impacket-0.11 bug, fixes impacket-0.12 performance")
|
ap2.add_argument("--smb-nwa-1", action="store_true", help="truncate directory listings to 64kB (~400 files); avoids impacket-0.11 bug, fixes impacket-0.12 performance")
|
||||||
ap2.add_argument("--smb-nwa-2", action="store_true", help="disable impacket workaround for filecopy globs")
|
ap2.add_argument("--smb-nwa-2", action="store_true", help="disable impacket workaround for filecopy globs")
|
||||||
ap2.add_argument("--smba", action="store_true", help="small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does)")
|
ap2.add_argument("--smba", action="store_true", help="small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does)")
|
||||||
ap2.add_argument("--smb6", action="store_true", help="enable IPv6")
|
|
||||||
ap2.add_argument("--smbv", action="store_true", help="verbose")
|
ap2.add_argument("--smbv", action="store_true", help="verbose")
|
||||||
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
||||||
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
||||||
|
|
@ -1607,7 +1606,7 @@ def add_optouts(ap):
|
||||||
def add_safety(ap):
|
def add_safety(ap):
|
||||||
ap2 = ap.add_argument_group("safety options")
|
ap2 = ap.add_argument_group("safety options")
|
||||||
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
|
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
|
||||||
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --no-html --no-readme --no-logues --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
|
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
|
||||||
ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
|
ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
|
||||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
|
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
|
||||||
ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
|
ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
|
||||||
|
|
@ -1618,8 +1617,6 @@ def add_safety(ap):
|
||||||
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile")
|
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile")
|
||||||
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
||||||
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings")
|
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings")
|
||||||
ap2.add_argument("--no-script", action="store_true", help="disables javascript in html files; helps prevent XSS but kills interactive websites (volflag=noscript)")
|
|
||||||
ap2.add_argument("--no-html", action="store_true", help="show html-files as plain text; helps prevent XSS but kills websites/blogs, also enables --no-script (volflag=nohtml)")
|
|
||||||
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise). \033[1;31mWARNING:\033[0m Not compatible with WebDAV")
|
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise). \033[1;31mWARNING:\033[0m Not compatible with WebDAV")
|
||||||
ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
|
ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
|
||||||
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
|
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
|
||||||
|
|
@ -1661,7 +1658,7 @@ def add_shutdown(ap):
|
||||||
ap2 = ap.add_argument_group("shutdown options")
|
ap2 = ap.add_argument_group("shutdown options")
|
||||||
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
||||||
ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all")
|
ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all")
|
||||||
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing, [\033[32mthgen\033[0m] thumbnail-pregen")
|
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing")
|
||||||
|
|
||||||
|
|
||||||
def add_logging(ap):
|
def add_logging(ap):
|
||||||
|
|
@ -1724,12 +1721,9 @@ def add_thumbnail(ap):
|
||||||
ap2.add_argument("--th-no-jxl", action="store_true", help="disable jpeg-xl output")
|
ap2.add_argument("--th-no-jxl", action="store_true", help="disable jpeg-xl output")
|
||||||
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
|
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
|
||||||
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds)")
|
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds)")
|
||||||
ap2.add_argument("--th-vips-jxl", metavar="N", type=int, default=1, help="when to allow generating jxl thumbnails with libvips; 0=never, 1=musl-mallocng, 2=always")
|
|
||||||
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than \033[33mSEC\033[0m seconds")
|
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than \033[33mSEC\033[0m seconds")
|
||||||
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
||||||
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds")
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds")
|
||||||
ap2.add_argument("--th-pregen", metavar="F,F", type=u, default="", help="pregenerate thumbnails on startup; \033[33mF,F\033[0m is comma-separated list of formats; example: [\033[32mj,jf,w,w3,wf,wf3,x,xf\033[0m] NOTE: remember to set \033[33m--th-maxage 123456789\033[0m (volflag=th_pregen)")
|
|
||||||
ap2.add_argument("--th-pre-rl", metavar="SEC", type=int, default=30, help="while pregen is running, ratelimit the thumbnailer logger to one message every \033[33mSEC\033[0m seconds (only works with \033[33m-j1\033[0m); set 0 to disable ratelimit")
|
|
||||||
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
|
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
|
||||||
ap2.add_argument("--th-spec-p", metavar="N", type=u, default=1, help="for music, do spectrograms or embedded coverart? [\033[32m0\033[0m]=only-art, [\033[32m1\033[0m]=prefer-art, [\033[32m2\033[0m]=only-spec")
|
ap2.add_argument("--th-spec-p", metavar="N", type=u, default=1, help="for music, do spectrograms or embedded coverart? [\033[32m0\033[0m]=only-art, [\033[32m1\033[0m]=prefer-art, [\033[32m2\033[0m]=only-spec")
|
||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
|
|
@ -1869,10 +1863,8 @@ def add_ui(ap, retry: int):
|
||||||
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
|
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
|
||||||
ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
|
ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
|
||||||
ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, fuzzy (see UI)")
|
ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, fuzzy (see UI)")
|
||||||
ap2.add_argument("--gauto", metavar="PERCENT", type=int, default=0, help="switch to gridview if more than \033[33mPERCENT\033[0m of files are pics/vids; 0=disabled")
|
|
||||||
ap2.add_argument("--rcm", metavar="TXT", default="yy", help="rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double")
|
ap2.add_argument("--rcm", metavar="TXT", default="yy", help="rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double")
|
||||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
|
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
|
||||||
ap2.add_argument("--glang", action="store_true", help="guess the browser's default language, otherwise fall back to \033[33m--lang\033[0m")
|
|
||||||
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,))
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,))
|
||||||
ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
|
||||||
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
|
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
|
||||||
|
|
@ -1883,7 +1875,6 @@ def add_ui(ap, retry: int):
|
||||||
ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
|
ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
|
||||||
ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag:dlni)")
|
ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag:dlni)")
|
||||||
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
||||||
ap2.add_argument("--dothidden", action="store_true", help="hide specific files in a folder by listing them in a file named .hidden -- WARNING: Mostly cosmetic! Download-as-zip/tar will still download them. Do not rely on this for security (volflag=dothidden)")
|
|
||||||
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("--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("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
|
ap2.add_argument("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
|
||||||
ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m 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)")
|
ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m 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)")
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 20, 13)
|
VERSION = (1, 20, 11)
|
||||||
CODENAME = "sftp is fine too"
|
CODENAME = "sftp is fine too"
|
||||||
BUILD_DT = (2026, 3, 23)
|
BUILD_DT = (2026, 3, 8)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|
|
||||||
|
|
@ -705,22 +705,18 @@ class VFS(object):
|
||||||
if rem:
|
if rem:
|
||||||
ap += "/" + rem
|
ap += "/" + rem
|
||||||
|
|
||||||
rap = ""
|
rap = absreal(ap)
|
||||||
if self.shr_files:
|
if self.shr_files:
|
||||||
assert self.shr_src # !rm
|
assert self.shr_src # !rm
|
||||||
if rem and rem not in self.shr_files:
|
vn, rem = self.shr_src
|
||||||
return "\n\n\0\n\n"
|
chk = absreal(os.path.join(vn.realpath, rem))
|
||||||
if resolve:
|
if chk != rap:
|
||||||
rap = absreal(ap)
|
# not the dir itself; assert file allowed
|
||||||
vn, rem = self.shr_src
|
ad, fn = os.path.split(rap)
|
||||||
chk = absreal(os.path.join(vn.realpath, rem))
|
if chk != ad or fn not in self.shr_files:
|
||||||
if chk != rap:
|
return "\n\n"
|
||||||
# not the dir itself; assert file allowed
|
|
||||||
ad, fn = os.path.split(rap)
|
|
||||||
if chk != ad or fn not in self.shr_files:
|
|
||||||
return "\n\n\0\n\n"
|
|
||||||
|
|
||||||
return (rap or absreal(ap)) if resolve else ap
|
return rap if resolve else ap
|
||||||
|
|
||||||
def _dcanonical_shr(self, rem: str) -> str:
|
def _dcanonical_shr(self, rem: str) -> str:
|
||||||
"""resolves until the final component (filename)"""
|
"""resolves until the final component (filename)"""
|
||||||
|
|
@ -737,7 +733,7 @@ class VFS(object):
|
||||||
if chk != absreal(ap):
|
if chk != absreal(ap):
|
||||||
# not the dir itself; assert file allowed
|
# not the dir itself; assert file allowed
|
||||||
if ad != chk or fn not in self.shr_files:
|
if ad != chk or fn not in self.shr_files:
|
||||||
return "\n\n\0\n\n"
|
return "\n\n"
|
||||||
|
|
||||||
return os.path.join(ad, fn)
|
return os.path.join(ad, fn)
|
||||||
|
|
||||||
|
|
@ -880,6 +876,9 @@ class VFS(object):
|
||||||
yield dbv, vrem, rel, fsroot, rfiles, rdirs, vfs_virt
|
yield dbv, vrem, rel, fsroot, rfiles, rdirs, vfs_virt
|
||||||
|
|
||||||
for rdir, _ in rdirs:
|
for rdir, _ in rdirs:
|
||||||
|
if not dots_ok and rdir.startswith("."):
|
||||||
|
continue
|
||||||
|
|
||||||
wrel = (rel + "/" + rdir).lstrip("/")
|
wrel = (rel + "/" + rdir).lstrip("/")
|
||||||
wrem = (rem + "/" + rdir).lstrip("/")
|
wrem = (rem + "/" + rdir).lstrip("/")
|
||||||
for x in self.walk(
|
for x in self.walk(
|
||||||
|
|
@ -1936,7 +1935,7 @@ class AuthSrv(object):
|
||||||
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
||||||
vol.root = vfs
|
vol.root = vfs
|
||||||
|
|
||||||
zs = "du_iwho emb_all ls_q_m neversymlink oh_f oh_g"
|
zs = "du_iwho emb_all ls_q_m neversymlink"
|
||||||
k_ign = set(zs.split())
|
k_ign = set(zs.split())
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_vols.values():
|
||||||
unknown_flags = set()
|
unknown_flags = set()
|
||||||
|
|
@ -1985,10 +1984,6 @@ class AuthSrv(object):
|
||||||
[sun] if "m" in s_pr else [],
|
[sun] if "m" in s_pr else [],
|
||||||
[sun] if "d" in s_pr else [],
|
[sun] if "d" in s_pr else [],
|
||||||
[sun] if "g" in s_pr else [],
|
[sun] if "g" in s_pr else [],
|
||||||
[], # G
|
|
||||||
[], # h
|
|
||||||
[], # a
|
|
||||||
[sun] if "." in s_pr or self.args.ed else [],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# don't know the abspath yet + wanna ensure the user
|
# don't know the abspath yet + wanna ensure the user
|
||||||
|
|
@ -3258,7 +3253,6 @@ class AuthSrv(object):
|
||||||
"idxh": int(self.args.ih),
|
"idxh": int(self.args.ih),
|
||||||
"dutc": not self.args.localtime,
|
"dutc": not self.args.localtime,
|
||||||
"dfszf": self.args.ui_filesz.strip("-"),
|
"dfszf": self.args.ui_filesz.strip("-"),
|
||||||
"dgauto": self.args.gauto,
|
|
||||||
"themes": self.args.themes,
|
"themes": self.args.themes,
|
||||||
"turbolvl": self.args.turbo,
|
"turbolvl": self.args.turbo,
|
||||||
"nosubtle": self.args.nosubtle,
|
"nosubtle": self.args.nosubtle,
|
||||||
|
|
@ -3274,7 +3268,7 @@ class AuthSrv(object):
|
||||||
for zs in zs.split():
|
for zs in zs.split():
|
||||||
if vf.get(zs):
|
if vf.get(zs):
|
||||||
js_htm[zs] = 1
|
js_htm[zs] = 1
|
||||||
zs = "glang notooltips"
|
zs = "notooltips"
|
||||||
for zs in zs.split():
|
for zs in zs.split():
|
||||||
if getattr(self.args, zs, False):
|
if getattr(self.args, zs, False):
|
||||||
js_htm[zs] = 1
|
js_htm[zs] = 1
|
||||||
|
|
|
||||||
|
|
@ -146,27 +146,24 @@ class BrokerMp(object):
|
||||||
returns a Queue object which eventually contains the response if want_retval
|
returns a Queue object which eventually contains the response if want_retval
|
||||||
(not-impl here since nothing uses it yet)
|
(not-impl here since nothing uses it yet)
|
||||||
"""
|
"""
|
||||||
if dest.startswith("httpsrv."):
|
if dest == "httpsrv.listen":
|
||||||
if dest == "httpsrv.listen":
|
for p in self.procs:
|
||||||
for p in self.procs:
|
p.q_pend.put((0, dest, [args[0], len(self.procs)]))
|
||||||
p.q_pend.put((0, dest, [args[0], len(self.procs)]))
|
|
||||||
else:
|
elif dest == "httpsrv.set_netdevs":
|
||||||
for p in self.procs:
|
for p in self.procs:
|
||||||
p.q_pend.put((0, dest, list(args)))
|
p.q_pend.put((0, dest, list(args)))
|
||||||
|
|
||||||
elif dest == "cb_httpsrv_up":
|
elif dest == "cb_httpsrv_up":
|
||||||
self.hub.cb_httpsrv_up()
|
self.hub.cb_httpsrv_up()
|
||||||
|
|
||||||
|
elif dest == "httpsrv.set_bad_ver":
|
||||||
|
for p in self.procs:
|
||||||
|
p.q_pend.put((0, dest, list(args)))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("what is " + str(dest))
|
raise Exception("what is " + str(dest))
|
||||||
|
|
||||||
def say1(self, dest: str, *args: Any) -> None:
|
|
||||||
"""
|
|
||||||
send message to one lucky recipient
|
|
||||||
"""
|
|
||||||
p = self.procs[0]
|
|
||||||
p.q_pend.put((0, dest, list(args)))
|
|
||||||
|
|
||||||
def periodic(self) -> None:
|
def periodic(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,13 @@ class BrokerThr(BrokerCli):
|
||||||
self.httpsrv.listen(args[0], 1)
|
self.httpsrv.listen(args[0], 1)
|
||||||
return
|
return
|
||||||
|
|
||||||
getattr(self.httpsrv, dest[8:])(*args)
|
if dest == "httpsrv.set_netdevs":
|
||||||
return
|
self.httpsrv.set_netdevs(args[0])
|
||||||
|
return
|
||||||
|
|
||||||
|
if dest == "httpsrv.set_bad_ver":
|
||||||
|
self.httpsrv.set_bad_ver()
|
||||||
|
return
|
||||||
|
|
||||||
# new ipc invoking managed service in hub
|
# new ipc invoking managed service in hub
|
||||||
obj = self.hub
|
obj = self.hub
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,8 @@ def vf_bmap() -> dict[str, str]:
|
||||||
"no_dupe": "nodupe",
|
"no_dupe": "nodupe",
|
||||||
"no_dupe_m": "nodupem",
|
"no_dupe_m": "nodupem",
|
||||||
"no_forget": "noforget",
|
"no_forget": "noforget",
|
||||||
"no_html": "nohtml",
|
|
||||||
"no_pipe": "nopipe",
|
"no_pipe": "nopipe",
|
||||||
"no_robots": "norobots",
|
"no_robots": "norobots",
|
||||||
"no_script": "noscript",
|
|
||||||
"no_tail": "notail",
|
"no_tail": "notail",
|
||||||
"no_thumb": "dthumb",
|
"no_thumb": "dthumb",
|
||||||
"no_vthumb": "dvthumb",
|
"no_vthumb": "dvthumb",
|
||||||
|
|
@ -35,7 +33,6 @@ def vf_bmap() -> dict[str, str]:
|
||||||
for k in (
|
for k in (
|
||||||
"dedup",
|
"dedup",
|
||||||
"dlni",
|
"dlni",
|
||||||
"dothidden",
|
|
||||||
"dotsrch",
|
"dotsrch",
|
||||||
"e2d",
|
"e2d",
|
||||||
"e2ds",
|
"e2ds",
|
||||||
|
|
@ -150,7 +147,6 @@ def vf_vmap() -> dict[str, str]:
|
||||||
"tail_tmax",
|
"tail_tmax",
|
||||||
"tail_who",
|
"tail_who",
|
||||||
"tcolor",
|
"tcolor",
|
||||||
"th_pregen",
|
|
||||||
"th_qv",
|
"th_qv",
|
||||||
"th_qvx",
|
"th_qvx",
|
||||||
"th_spec_p",
|
"th_spec_p",
|
||||||
|
|
@ -318,7 +314,6 @@ flagcats = {
|
||||||
"aconvt": "convert-to-audio timeout in seconds",
|
"aconvt": "convert-to-audio timeout in seconds",
|
||||||
"th_spec_p=1": "make spectrograms? 0=never 1=fallback 2=always",
|
"th_spec_p=1": "make spectrograms? 0=never 1=fallback 2=always",
|
||||||
"ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
|
"ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
|
||||||
"th_pregen=w,wf": "pregenerate thumbs for these formats",
|
|
||||||
},
|
},
|
||||||
"handlers\n(better explained in --help-handlers)": {
|
"handlers\n(better explained in --help-handlers)": {
|
||||||
"on404=PY": "handle 404s by executing PY file",
|
"on404=PY": "handle 404s by executing PY file",
|
||||||
|
|
@ -345,7 +340,6 @@ flagcats = {
|
||||||
"hsortn": "number of sort-rules to add to media URLs",
|
"hsortn": "number of sort-rules to add to media URLs",
|
||||||
"ufavico=URL": "per-volume favicon (.ico/png/gif/svg)",
|
"ufavico=URL": "per-volume favicon (.ico/png/gif/svg)",
|
||||||
"unlist": "dont list files matching REGEX",
|
"unlist": "dont list files matching REGEX",
|
||||||
"dothidden": "enable support for .hidden files",
|
|
||||||
"dlni": "force-download (no-inline) files on click",
|
"dlni": "force-download (no-inline) files on click",
|
||||||
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
|
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
|
||||||
"html_head_s=TXT": "additional static text in the html <head>",
|
"html_head_s=TXT": "additional static text in the html <head>",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .__init__ import ANYWIN, FREEBSD, MACOS, UNIX
|
from .__init__ import ANYWIN, MACOS
|
||||||
from .authsrv import AXS, VFS, AuthSrv
|
from .authsrv import AXS, VFS, AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import chkcmd, json_hesc, min_ex, undot
|
from .util import chkcmd, json_hesc, min_ex, undot
|
||||||
|
|
@ -88,7 +88,7 @@ class Fstab(object):
|
||||||
|
|
||||||
def _from_sp_mount(self) -> dict[str, str]:
|
def _from_sp_mount(self) -> dict[str, str]:
|
||||||
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
|
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
|
||||||
if MACOS or FREEBSD:
|
if MACOS:
|
||||||
sptn = r"^.*? on (.*) \(([^ ]+), .*"
|
sptn = r"^.*? on (.*) \(([^ ]+), .*"
|
||||||
|
|
||||||
ptn = re.compile(sptn)
|
ptn = re.compile(sptn)
|
||||||
|
|
@ -118,7 +118,7 @@ class Fstab(object):
|
||||||
|
|
||||||
def build_tab(self) -> None:
|
def build_tab(self) -> None:
|
||||||
self.log("inspecting mtab for changes")
|
self.log("inspecting mtab for changes")
|
||||||
dtab = self._from_sp_mount() if UNIX else self._from_proc()
|
dtab = self._from_sp_mount() if MACOS else self._from_proc()
|
||||||
|
|
||||||
# keep empirically-correct values if mounttab unchanged
|
# keep empirically-correct values if mounttab unchanged
|
||||||
srctab = str(sorted(dtab.items()))
|
srctab = str(sorted(dtab.items()))
|
||||||
|
|
@ -130,7 +130,7 @@ class Fstab(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fuses = [mp for mp, fs in dtab.items() if fs == "fuseblk"]
|
fuses = [mp for mp, fs in dtab.items() if fs == "fuseblk"]
|
||||||
if not fuses or UNIX:
|
if not fuses or MACOS:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
try:
|
try:
|
||||||
so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINT"]) # centos6
|
so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINT"]) # centos6
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ from .util import (
|
||||||
ODict,
|
ODict,
|
||||||
Pebkac,
|
Pebkac,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
exclude_dothidden,
|
|
||||||
fsenc,
|
fsenc,
|
||||||
ipnorm,
|
ipnorm,
|
||||||
pybin,
|
pybin,
|
||||||
|
|
@ -201,13 +200,11 @@ class FtpFs(AbstractedFS):
|
||||||
cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname]
|
cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname]
|
||||||
if r and not cr or w and not cw or m and not cm or d and not cd:
|
if r and not cr or w and not cw or m and not cm or d and not cd:
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
else:
|
|
||||||
ap = vfs.canonical(rem, False)
|
|
||||||
|
|
||||||
if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
|
if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
|
||||||
raise FSE("No such file or directory", 1)
|
raise FSE("No such file or directory", 1)
|
||||||
|
|
||||||
return ap, vfs, rem
|
return os.path.join(vfs.realpath, rem), vfs, rem
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
raise FSE(str(ex))
|
raise FSE(str(ex))
|
||||||
|
|
||||||
|
|
@ -351,10 +348,7 @@ class FtpFs(AbstractedFS):
|
||||||
vfs_ls.extend(vfs_virt.keys())
|
vfs_ls.extend(vfs_virt.keys())
|
||||||
|
|
||||||
if self.uname not in vfs.axs.udot:
|
if self.uname not in vfs.axs.udot:
|
||||||
if "dothidden" in vfs.flags and ".hidden" in [x[0] for x in vfs_ls]:
|
vfs_ls = exclude_dotfiles(vfs_ls)
|
||||||
vfs_ls = exclude_dothidden(vfs_ls, fsroot)
|
|
||||||
else:
|
|
||||||
vfs_ls = exclude_dotfiles(vfs_ls)
|
|
||||||
|
|
||||||
vfs_ls.sort()
|
vfs_ls.sort()
|
||||||
return vfs_ls
|
return vfs_ls
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,6 @@ from .util import (
|
||||||
eol_conv,
|
eol_conv,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
exclude_dotfiles_ls,
|
exclude_dotfiles_ls,
|
||||||
exclude_dothidden,
|
|
||||||
exclude_dothidden_ls,
|
|
||||||
formatdate,
|
formatdate,
|
||||||
fsenc,
|
fsenc,
|
||||||
gen_content_disposition,
|
gen_content_disposition,
|
||||||
|
|
@ -154,7 +152,7 @@ _ = (argparse, threading)
|
||||||
|
|
||||||
USED4SEC = {"usedforsecurity": False} if sys.version_info > (3, 9) else {}
|
USED4SEC = {"usedforsecurity": False} if sys.version_info > (3, 9) else {}
|
||||||
|
|
||||||
ALL_COOKIES = "cplng cppwd cppws dots idxh js k304 no304".split()
|
ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
|
||||||
|
|
||||||
BADXFF = " due to dangerous misconfiguration (the http-header specified by --xff-hdr was received from an untrusted reverse-proxy)"
|
BADXFF = " due to dangerous misconfiguration (the http-header specified by --xff-hdr was received from an untrusted reverse-proxy)"
|
||||||
BADXFF2 = ". Some copyparty features are now disabled as a safety measure.\n\n\n"
|
BADXFF2 = ". Some copyparty features are now disabled as a safety measure.\n\n\n"
|
||||||
|
|
@ -230,7 +228,8 @@ class HttpCli(object):
|
||||||
self.args = conn.args # mypy404
|
self.args = conn.args # mypy404
|
||||||
self.E: EnvParams = self.args.E
|
self.E: EnvParams = self.args.E
|
||||||
self.asrv = conn.asrv # mypy404
|
self.asrv = conn.asrv # mypy404
|
||||||
self.thumbcli = conn.hsrv.thumbcli
|
self.ico = conn.ico # mypy404
|
||||||
|
self.thumbcli = conn.thumbcli # mypy404
|
||||||
self.u2fh = conn.u2fh # mypy404
|
self.u2fh = conn.u2fh # mypy404
|
||||||
self.pipes = conn.pipes # mypy404
|
self.pipes = conn.pipes # mypy404
|
||||||
self.log_func = conn.log_func # mypy404
|
self.log_func = conn.log_func # mypy404
|
||||||
|
|
@ -535,7 +534,7 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
|
|
||||||
ptn = self.conn.lf_url
|
ptn: Optional[Pattern[str]] = self.conn.lf_url # mypy404
|
||||||
self.do_log = not ptn or not ptn.search(self.req)
|
self.do_log = not ptn or not ptn.search(self.req)
|
||||||
|
|
||||||
if self.args.ihead and self.do_log:
|
if self.args.ihead and self.do_log:
|
||||||
|
|
@ -1845,7 +1844,7 @@ class HttpCli(object):
|
||||||
raise Pebkac(401, "authenticate")
|
raise Pebkac(401, "authenticate")
|
||||||
|
|
||||||
elif depth == "1":
|
elif depth == "1":
|
||||||
fsroot, vfs_ls, vfs_virt = vn.ls(
|
_, vfs_ls, vfs_virt = vn.ls(
|
||||||
rem,
|
rem,
|
||||||
self.uname,
|
self.uname,
|
||||||
not self.args.no_scandir,
|
not self.args.no_scandir,
|
||||||
|
|
@ -1856,20 +1855,13 @@ class HttpCli(object):
|
||||||
if not self.can_read:
|
if not self.can_read:
|
||||||
vfs_ls = []
|
vfs_ls = []
|
||||||
if not self.can_dot:
|
if not self.can_dot:
|
||||||
if "dothidden" in vn.flags and ".hidden" in [x[0] for x in vfs_ls]:
|
vfs_ls = exclude_dotfiles_ls(vfs_ls)
|
||||||
vfs_ls = exclude_dothidden_ls(vfs_ls, fsroot)
|
|
||||||
self.dothid = True
|
|
||||||
else:
|
|
||||||
vfs_ls = exclude_dotfiles_ls(vfs_ls)
|
|
||||||
fgen = [{"vp": vp, "st": st} for vp, st in vfs_ls]
|
fgen = [{"vp": vp, "st": st} for vp, st in vfs_ls]
|
||||||
|
|
||||||
if vfs_virt:
|
if vfs_virt:
|
||||||
zsl = list(vfs_virt)
|
zsl = list(vfs_virt)
|
||||||
if not self.can_dot:
|
if not self.can_dot:
|
||||||
if "dothidden" in vn.flags and getattr(self, "dothid", False):
|
zsl = exclude_dotfiles(zsl)
|
||||||
zsl = exclude_dothidden(zsl, fsroot)
|
|
||||||
else:
|
|
||||||
zsl = exclude_dotfiles(zsl)
|
|
||||||
fgen += [{"vp": v, "st": vst} for v in zsl]
|
fgen += [{"vp": v, "st": vst} for v in zsl]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -4775,7 +4767,7 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
mime = guess_mime(cdis)
|
mime = guess_mime(cdis)
|
||||||
|
|
||||||
if mime not in SAFE_MIMES and "nohtml" in self.vn.flags and oh_k != "oh_g":
|
if mime not in SAFE_MIMES and "nohtml" in self.vn.flags:
|
||||||
mime = safe_mime(mime)
|
mime = safe_mime(mime)
|
||||||
|
|
||||||
self.out_headers["Accept-Ranges"] = "bytes"
|
self.out_headers["Accept-Ranges"] = "bytes"
|
||||||
|
|
@ -5293,7 +5285,7 @@ class HttpCli(object):
|
||||||
# chrome cannot handle more than ~2000 unique SVGs
|
# chrome cannot handle more than ~2000 unique SVGs
|
||||||
# so url-param "raster" returns a png/webp instead
|
# so url-param "raster" returns a png/webp instead
|
||||||
# (useragent-sniffing kinshi due to caching proxies)
|
# (useragent-sniffing kinshi due to caching proxies)
|
||||||
mime, ico = self.conn.hsrv.ico.get(txt, not small, "raster" in self.uparam)
|
mime, ico = self.ico.get(txt, not small, "raster" in self.uparam)
|
||||||
|
|
||||||
lm = formatdate(self.E.t0)
|
lm = formatdate(self.E.t0)
|
||||||
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
||||||
|
|
@ -5652,7 +5644,7 @@ class HttpCli(object):
|
||||||
no304vis=self.args.no304 > 0,
|
no304vis=self.args.no304 > 0,
|
||||||
msg=(
|
msg=(
|
||||||
BADVER
|
BADVER
|
||||||
if self.conn.hsrv.bad_ver and avol
|
if self.conn.hsrv.bad_ver and self.can_admin
|
||||||
else BADXFFB
|
else BADXFFB
|
||||||
if hasattr(self, "bad_xff")
|
if hasattr(self, "bad_xff")
|
||||||
else ""
|
else ""
|
||||||
|
|
@ -5868,7 +5860,6 @@ class HttpCli(object):
|
||||||
dk_sz = vn.flags.get("dk")
|
dk_sz = vn.flags.get("dk")
|
||||||
except:
|
except:
|
||||||
dk_sz = None
|
dk_sz = None
|
||||||
vn = vfs
|
|
||||||
vfs_ls = []
|
vfs_ls = []
|
||||||
vfs_virt = {}
|
vfs_virt = {}
|
||||||
for v in self.rvol:
|
for v in self.rvol:
|
||||||
|
|
@ -5879,11 +5870,7 @@ class HttpCli(object):
|
||||||
dirs = [x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)]
|
dirs = [x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)]
|
||||||
|
|
||||||
if not dots:
|
if not dots:
|
||||||
if "dothidden" in vn.flags and ".hidden" in [x[0] for x in vfs_ls]:
|
dirs = exclude_dotfiles(dirs)
|
||||||
dirs = exclude_dothidden(dirs, fsroot)
|
|
||||||
self.dothid = True
|
|
||||||
else:
|
|
||||||
dirs = exclude_dotfiles(dirs)
|
|
||||||
|
|
||||||
dirs = [quotep(x) for x in dirs if x != excl]
|
dirs = [quotep(x) for x in dirs if x != excl]
|
||||||
|
|
||||||
|
|
@ -5913,10 +5900,7 @@ class HttpCli(object):
|
||||||
x += "\n"
|
x += "\n"
|
||||||
dirs.append(quotep(x))
|
dirs.append(quotep(x))
|
||||||
if not dots:
|
if not dots:
|
||||||
if "dothidden" in vn.flags and getattr(self, "dothid", False):
|
dirs = exclude_dotfiles(dirs)
|
||||||
dirs = exclude_dothidden(dirs, fsroot)
|
|
||||||
else:
|
|
||||||
dirs = exclude_dotfiles(dirs)
|
|
||||||
|
|
||||||
ret["a"] = dirs
|
ret["a"] = dirs
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -6452,18 +6436,13 @@ class HttpCli(object):
|
||||||
s_rd = "read" in req["perms"]
|
s_rd = "read" in req["perms"]
|
||||||
s_wr = "write" in req["perms"]
|
s_wr = "write" in req["perms"]
|
||||||
s_get = "get" in req["perms"]
|
s_get = "get" in req["perms"]
|
||||||
s_dot = "dot" in req["perms"]
|
|
||||||
s_axs = [s_rd, s_wr, False, False, s_get]
|
s_axs = [s_rd, s_wr, False, False, s_get]
|
||||||
s_axsd = s_axs + [s_dot]
|
|
||||||
|
|
||||||
if s_axs == [False] * 5:
|
if s_axs == [False] * 5:
|
||||||
raise Pebkac(400, "select at least one permission")
|
raise Pebkac(400, "select at least one permission")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vfs, rem = self.asrv.vfs.get(vp, self.uname, *s_axs)
|
vfs, rem = self.asrv.vfs.get(vp, self.uname, *s_axs)
|
||||||
can_dot = self.uname in vfs.axs.udot
|
|
||||||
if s_dot and not can_dot:
|
|
||||||
raise Exception()
|
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
||||||
|
|
||||||
|
|
@ -6476,10 +6455,7 @@ class HttpCli(object):
|
||||||
raise Pebkac(400, "you dont have perms to create shares from this volume")
|
raise Pebkac(400, "you dont have perms to create shares from this volume")
|
||||||
|
|
||||||
ap, reals, _ = vfs.ls(rem, self.uname, not self.args.no_scandir, [s_axs])
|
ap, reals, _ = vfs.ls(rem, self.uname, not self.args.no_scandir, [s_axs])
|
||||||
zsl = [x[0] for x in reals]
|
rfns = set([x[0] for x in reals])
|
||||||
if not can_dot:
|
|
||||||
zsl = exclude_dotfiles(zsl)
|
|
||||||
rfns = set(zsl)
|
|
||||||
for fn in fns:
|
for fn in fns:
|
||||||
if fn not in rfns:
|
if fn not in rfns:
|
||||||
raise Pebkac(400, "selected file not found on disk: %r" % (fn,))
|
raise Pebkac(400, "selected file not found on disk: %r" % (fn,))
|
||||||
|
|
@ -6490,7 +6466,7 @@ class HttpCli(object):
|
||||||
sexp = req["exp"]
|
sexp = req["exp"]
|
||||||
exp = int(sexp) if sexp else 0
|
exp = int(sexp) if sexp else 0
|
||||||
exp = now + exp * 60 if exp else 0
|
exp = now + exp * 60 if exp else 0
|
||||||
pr = "".join(zc for zc, zb in zip("rwmdg.", s_axsd) if zb)
|
pr = "".join(zc for zc, zb in zip("rwmdg", s_axs) if zb)
|
||||||
|
|
||||||
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
||||||
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
|
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
|
||||||
|
|
@ -7026,8 +7002,6 @@ class HttpCli(object):
|
||||||
perms.append("move")
|
perms.append("move")
|
||||||
if self.can_delete:
|
if self.can_delete:
|
||||||
perms.append("delete")
|
perms.append("delete")
|
||||||
if self.can_dot:
|
|
||||||
perms.append("dot")
|
|
||||||
if self.can_get:
|
if self.can_get:
|
||||||
perms.append("get")
|
perms.append("get")
|
||||||
if self.can_upget:
|
if self.can_upget:
|
||||||
|
|
@ -7176,10 +7150,7 @@ class HttpCli(object):
|
||||||
if not self.can_dot or (
|
if not self.can_dot or (
|
||||||
"dots" not in self.uparam and (is_ls or "dots" not in self.cookies)
|
"dots" not in self.uparam and (is_ls or "dots" not in self.cookies)
|
||||||
):
|
):
|
||||||
if "dothidden" in vf and ".hidden" in ls_names:
|
ls_names = exclude_dotfiles(ls_names)
|
||||||
ls_names = exclude_dothidden(ls_names, fsroot)
|
|
||||||
else:
|
|
||||||
ls_names = exclude_dotfiles(ls_names)
|
|
||||||
|
|
||||||
add_dk = vf.get("dk")
|
add_dk = vf.get("dk")
|
||||||
add_fk = vf.get("fk")
|
add_fk = vf.get("fk")
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,12 @@ from . import util as Util
|
||||||
from .__init__ import TYPE_CHECKING, EnvParams
|
from .__init__ import TYPE_CHECKING, EnvParams
|
||||||
from .authsrv import AuthSrv # typechk
|
from .authsrv import AuthSrv # typechk
|
||||||
from .httpcli import HttpCli
|
from .httpcli import HttpCli
|
||||||
|
from .ico import Ico
|
||||||
|
from .mtag import HAVE_FFMPEG
|
||||||
|
from .th_cli import ThumbCli
|
||||||
|
from .th_srv import HAVE_PIL, HAVE_VIPS
|
||||||
from .u2idx import U2idx
|
from .u2idx import U2idx
|
||||||
from .util import HMaccas, NetMap, min_ex, shut_socket
|
from .util import HMaccas, NetMap, shut_socket
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Optional, Pattern, Union
|
from typing import Optional, Pattern, Union
|
||||||
|
|
@ -65,15 +69,21 @@ class HttpConn(object):
|
||||||
self.bans: dict[str, int] = hsrv.bans
|
self.bans: dict[str, int] = hsrv.bans
|
||||||
self.aclose: dict[str, int] = hsrv.aclose
|
self.aclose: dict[str, int] = hsrv.aclose
|
||||||
|
|
||||||
|
enth = (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb
|
||||||
|
self.thumbcli: Optional[ThumbCli] = ThumbCli(hsrv) if enth else None # mypy404
|
||||||
|
self.ico: Ico = Ico(self.args) # mypy404
|
||||||
|
|
||||||
self.t0: float = time.time() # mypy404
|
self.t0: float = time.time() # mypy404
|
||||||
self.freshen_pwd: float = 0.0
|
self.freshen_pwd: float = 0.0
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.nreq: int = -1 # mypy404
|
self.nreq: int = -1 # mypy404
|
||||||
self.nbyte: int = 0 # mypy404
|
self.nbyte: int = 0 # mypy404
|
||||||
self.u2idx: Optional[U2idx] = None
|
self.u2idx: Optional[U2idx] = None
|
||||||
self.lf_url: Optional[Pattern[str]] = self.args.lf_url
|
|
||||||
self.log_func: "Util.RootLogger" = hsrv.log # mypy404
|
self.log_func: "Util.RootLogger" = hsrv.log # mypy404
|
||||||
self.log_src: str = "httpconn" # mypy404
|
self.log_src: str = "httpconn" # mypy404
|
||||||
|
self.lf_url: Optional[Pattern[str]] = (
|
||||||
|
re.compile(self.args.lf_url) if self.args.lf_url else None
|
||||||
|
) # mypy404
|
||||||
self.set_rproxy()
|
self.set_rproxy()
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
|
|
@ -194,12 +204,12 @@ class HttpConn(object):
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
em = str(ex)
|
em = str(ex)
|
||||||
|
|
||||||
if "ALERT_" in em:
|
if "ALERT_CERTIFICATE_UNKNOWN" in em:
|
||||||
self.log("client refused our TLS cert or config: " + em, c=6)
|
# android-chrome keeps doing this
|
||||||
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
t = "https-handshake failed, probably due to client:\n"
|
self.log("handshake\033[0m " + em, c=5)
|
||||||
self.log(t + min_ex(), c=5)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,8 @@ except SyntaxError:
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from .authsrv import LEELOO_DALLAS
|
|
||||||
from .httpconn import HttpConn
|
from .httpconn import HttpConn
|
||||||
from .ico import Ico
|
|
||||||
from .metrics import Metrics
|
from .metrics import Metrics
|
||||||
from .mtag import HAVE_FFMPEG
|
|
||||||
from .sutil import gfilter2
|
|
||||||
from .th_cli import ThumbCli
|
|
||||||
from .th_srv import HAVE_PIL, HAVE_VIPS
|
|
||||||
from .u2idx import U2idx
|
from .u2idx import U2idx
|
||||||
from .util import (
|
from .util import (
|
||||||
E_SCK,
|
E_SCK,
|
||||||
|
|
@ -139,9 +133,6 @@ class HttpSrv(object):
|
||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.aclose: dict[str, int] = {}
|
self.aclose: dict[str, int] = {}
|
||||||
|
|
||||||
self.thumbcli: Optional[ThumbCli] = None
|
|
||||||
self.ico: Ico = Ico(self.args)
|
|
||||||
|
|
||||||
dli: dict[str, tuple[float, int, "VFS", str, str]] = {} # info
|
dli: dict[str, tuple[float, int, "VFS", str, str]] = {} # info
|
||||||
dls: dict[str, tuple[float, int]] = {} # state
|
dls: dict[str, tuple[float, int]] = {} # state
|
||||||
self.dli = self.tdli = dli
|
self.dli = self.tdli = dli
|
||||||
|
|
@ -239,20 +230,16 @@ class HttpSrv(object):
|
||||||
if self.args.log_thrs:
|
if self.args.log_thrs:
|
||||||
start_log_thrs(self.log, self.args.log_thrs, nid)
|
start_log_thrs(self.log, self.args.log_thrs, nid)
|
||||||
|
|
||||||
if (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb:
|
self.th_cfg: dict[str, set[str]] = {}
|
||||||
Daemon(self.post_init, "hsrv-init2")
|
Daemon(self.post_init, "hsrv-init2")
|
||||||
|
|
||||||
def post_init(self) -> None:
|
def post_init(self) -> None:
|
||||||
try:
|
try:
|
||||||
x = self.broker.ask("thumbsrv.getcfg")
|
x = self.broker.ask("thumbsrv.getcfg")
|
||||||
self.thumbcli = ThumbCli(self, x.get())
|
self.th_cfg = x.get()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_th_cfg(self, c: dict[str, set[str]], opts: tuple[bool]) -> None:
|
|
||||||
self.args.th_no_jxl = opts[0]
|
|
||||||
self.thumbcli = ThumbCli(self, c)
|
|
||||||
|
|
||||||
def set_bad_ver(self) -> None:
|
def set_bad_ver(self) -> None:
|
||||||
self.bad_ver = True
|
self.bad_ver = True
|
||||||
|
|
||||||
|
|
@ -659,73 +646,3 @@ class HttpSrv(object):
|
||||||
|
|
||||||
self.tdli = dli
|
self.tdli = dli
|
||||||
self.tdls = dls
|
self.tdls = dls
|
||||||
|
|
||||||
def pregen_thumbs(self) -> None:
|
|
||||||
Daemon(self._pregen_thumbs, "th_pregen")
|
|
||||||
|
|
||||||
def _pregen_thumbs(self) -> None:
|
|
||||||
def log(msg, n):
|
|
||||||
self.log("thumb-pregen", msg, n)
|
|
||||||
|
|
||||||
if getattr(self, "_pregen", False):
|
|
||||||
log("already running", 1)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._pregen = True
|
|
||||||
|
|
||||||
for n in range(9999999):
|
|
||||||
x = self.broker.ask("up2k.is_busy")
|
|
||||||
zb, zi = x.get()
|
|
||||||
if zi:
|
|
||||||
break
|
|
||||||
if not n:
|
|
||||||
log("waiting for up2k to finish initializing", 6)
|
|
||||||
time.sleep(1 if n < 10 else 5 if n < 300 else 15)
|
|
||||||
|
|
||||||
if not self.thumbcli:
|
|
||||||
log("no thumbcli", 1)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.args.th_pre_rl:
|
|
||||||
try:
|
|
||||||
self.broker.hub.thumbsrv.log = self.broker.hub.thumbsrv._slog
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
nfiles = 0
|
|
||||||
t0 = time.time()
|
|
||||||
scandir = not self.args.no_scandir
|
|
||||||
for vn in self.asrv.vfs.all_nodes.values():
|
|
||||||
fmts = vn.flags.get("th_pregen", "")
|
|
||||||
if not fmts:
|
|
||||||
continue
|
|
||||||
log("starting for volume /%s" % (vn.vpath,), 6)
|
|
||||||
g = vn.walk("x", "/", [], LEELOO_DALLAS, [[True]], 2, scandir, False, False)
|
|
||||||
g = gfilter2(g, self, vn.vpath, fmts.split(","))
|
|
||||||
for f in g:
|
|
||||||
nfiles += 1
|
|
||||||
if not nfiles % 256:
|
|
||||||
now = time.time()
|
|
||||||
for n in range(9999999):
|
|
||||||
x = self.broker.ask("up2k.is_busy")
|
|
||||||
zb, zi = x.get()
|
|
||||||
if not zb:
|
|
||||||
if n:
|
|
||||||
t0 += time.time() - now
|
|
||||||
break
|
|
||||||
if not n:
|
|
||||||
log("waiting for up2k to finish indexing", 6)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
if self.args.th_pre_rl:
|
|
||||||
try:
|
|
||||||
self.broker.hub.thumbsrv.log = self.broker.hub.thumbsrv._log
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
t = "finished; %d files in %d seconds"
|
|
||||||
log(t % (nfiles, time.time() - t0), 6)
|
|
||||||
self._pregen = False
|
|
||||||
|
|
||||||
if self.args.exit == "thgen":
|
|
||||||
self.broker.say("sigterm")
|
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,6 @@ from .util import (
|
||||||
Daemon,
|
Daemon,
|
||||||
ODict,
|
ODict,
|
||||||
Pebkac,
|
Pebkac,
|
||||||
exclude_dotfiles,
|
|
||||||
exclude_dotfiles_ls,
|
|
||||||
exclude_dothidden,
|
|
||||||
exclude_dothidden_ls,
|
|
||||||
ipnorm,
|
ipnorm,
|
||||||
min_ex,
|
min_ex,
|
||||||
read_utf8,
|
read_utf8,
|
||||||
|
|
@ -353,13 +349,11 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
|
||||||
cr, cw, cm, cd, _, _, _, _, _ = avn.uaxs[self.uname]
|
cr, cw, cm, cd, _, _, _, _, _ = avn.uaxs[self.uname]
|
||||||
if r and not cr or w and not cw or m and not cm or d and not cd:
|
if r and not cr or w and not cw or m and not cm or d and not cd:
|
||||||
raise OSError(errno.EPERM, "permission denied in [/%s]" % (vpath,))
|
raise OSError(errno.EPERM, "permission denied in [/%s]" % (vpath,))
|
||||||
else:
|
|
||||||
ap = vn.canonical(rem, False)
|
|
||||||
|
|
||||||
if "bcasechk" in vn.flags and not vn.casechk(rem, True):
|
if "bcasechk" in vn.flags and not vn.casechk(rem, True):
|
||||||
raise OSError(errno.ENOENT, "file does not exist case-sensitively")
|
raise OSError(errno.ENOENT, "file does not exist case-sensitively")
|
||||||
|
|
||||||
return ap, vn, rem
|
return os.path.join(vn.realpath, rem), vn, rem
|
||||||
|
|
||||||
def list_folder(self, path: str) -> list[SATTR] | int:
|
def list_folder(self, path: str) -> list[SATTR] | int:
|
||||||
try:
|
try:
|
||||||
|
|
@ -405,27 +399,21 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
|
||||||
self.log("ls(%s): vfs-vols; |%d|" % (path, len(ret)))
|
self.log("ls(%s): vfs-vols; |%d|" % (path, len(ret)))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
fsroot, vfs_ls, vfs_virt = vn.ls(
|
_, vfs_ls, vfs_virt = vn.ls(
|
||||||
rem,
|
rem,
|
||||||
self.uname,
|
self.uname,
|
||||||
not self.args.no_scandir,
|
not self.args.no_scandir,
|
||||||
[[True, False], [False, True]],
|
[[True, False], [False, True]],
|
||||||
throw=True,
|
throw=True,
|
||||||
)
|
)
|
||||||
vnames = list(vfs_virt)
|
|
||||||
if self.uname not in vn.axs.udot:
|
|
||||||
if "dothidden" in vn.flags and ".hidden" in [x[0] for x in vfs_ls]:
|
|
||||||
vfs_ls = exclude_dothidden_ls(vfs_ls, fsroot)
|
|
||||||
vnames = exclude_dothidden(vnames, fsroot)
|
|
||||||
else:
|
|
||||||
vfs_ls = exclude_dotfiles_ls(vfs_ls)
|
|
||||||
vnames = exclude_dotfiles(vnames)
|
|
||||||
ret = [SATTR.from_stat(x[1], filename=x[0]) for x in vfs_ls]
|
ret = [SATTR.from_stat(x[1], filename=x[0]) for x in vfs_ls]
|
||||||
for zs, vn2 in vfs_virt.items():
|
for zs, vn2 in vfs_virt.items():
|
||||||
if not vn2.realpath or zs not in vnames:
|
if not vn2.realpath:
|
||||||
continue
|
continue
|
||||||
st = bos.stat(vn2.realpath)
|
st = bos.stat(vn2.realpath)
|
||||||
ret.append(SATTR.from_stat(st, filename=zs))
|
ret.append(SATTR.from_stat(st, filename=zs))
|
||||||
|
if self.uname not in vn.axs.udot:
|
||||||
|
ret = [x for x in ret if not x.filename.split("/")[-1].startswith(".")]
|
||||||
ret.sort(key=lambda x: x.filename)
|
ret.sort(key=lambda x: x.filename)
|
||||||
self.log("ls(%s): |%d|" % (path, len(ret)))
|
self.log("ls(%s): |%d|" % (path, len(ret)))
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -496,7 +484,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vn, rem = self.asrv.vfs.get(vp, self.uname, rd, wr)
|
vn, rem = self.asrv.vfs.get(vp, self.uname, rd, wr)
|
||||||
ap = vn.canonical(rem, False)
|
ap = os.path.join(vn.realpath, rem)
|
||||||
vf = vn.flags
|
vf = vn.flags
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
t = "denied open file [%s], iflag=%s, read=%s, write=%s: %s"
|
t = "denied open file [%s], iflag=%s, read=%s, write=%s: %s"
|
||||||
|
|
@ -669,7 +657,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
|
||||||
self.log("mkdir(%s)" % (vp,))
|
self.log("mkdir(%s)" % (vp,))
|
||||||
try:
|
try:
|
||||||
vn, rem = self.asrv.vfs.get(vp, self.uname, False, True)
|
vn, rem = self.asrv.vfs.get(vp, self.uname, False, True)
|
||||||
ap = vn.canonical(rem, False)
|
ap = os.path.join(vn.realpath, rem)
|
||||||
bos.makedirs(ap, vf=vn.flags) # filezilla expects this
|
bos.makedirs(ap, vf=vn.flags) # filezilla expects this
|
||||||
if attr is not None:
|
if attr is not None:
|
||||||
paramiko.SFTPServer.set_file_attr(ap, attr)
|
paramiko.SFTPServer.set_file_attr(ap, attr)
|
||||||
|
|
|
||||||
|
|
@ -89,15 +89,13 @@ class SMB(object):
|
||||||
smbserver.isInFileJail = self._is_in_file_jail
|
smbserver.isInFileJail = self._is_in_file_jail
|
||||||
self._disarm()
|
self._disarm()
|
||||||
|
|
||||||
zs = " " if self.args.smb6 else ":"
|
ip = next((x for x in self.args.smb_i if ":" not in x), None)
|
||||||
ip = next((x for x in self.args.smb_i if zs not in x), None)
|
|
||||||
if not ip:
|
if not ip:
|
||||||
self.log("smb", "IPv6 not enabled with --smb6; listening on 0.0.0.0", 3)
|
self.log("smb", "IPv6 not supported for SMB; listening on 0.0.0.0", 3)
|
||||||
ip = "0.0.0.0"
|
ip = "0.0.0.0"
|
||||||
|
|
||||||
port = int(self.args.smb_port)
|
port = int(self.args.smb_port)
|
||||||
kw = {"ipv6": True} if ":" in ip else {}
|
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port)
|
||||||
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port, **kw)
|
|
||||||
try:
|
try:
|
||||||
if self.accs:
|
if self.accs:
|
||||||
srv.setAuthCallback(self._auth_cb)
|
srv.setAuthCallback(self._auth_cb)
|
||||||
|
|
@ -123,7 +121,6 @@ class SMB(object):
|
||||||
|
|
||||||
self.srv = srv
|
self.srv = srv
|
||||||
self.stop = srv.stop
|
self.stop = srv.stop
|
||||||
ip = "[%s]" % (ip,) if kw else ip
|
|
||||||
self.log("smb", "listening @ {}:{}".format(ip, port))
|
self.log("smb", "listening @ {}:{}".format(ip, port))
|
||||||
|
|
||||||
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
|
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
|
@ -194,7 +191,7 @@ class SMB(object):
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
|
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
|
||||||
if not vfs.realpath:
|
if not vfs.realpath:
|
||||||
raise Exception("unmapped vfs")
|
raise Exception("unmapped vfs")
|
||||||
return vfs, vfs.canonical(rem, False)
|
return vfs, vjoin(vfs.realpath, rem)
|
||||||
|
|
||||||
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
|
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
|
||||||
vpath = vpath.replace("\\", "/").lstrip("/")
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,18 @@ import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .__init__ import CORES, TYPE_CHECKING
|
from .__init__ import CORES
|
||||||
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
|
from .authsrv import VFS, AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .th_cli import ThumbCli
|
from .th_cli import ThumbCli
|
||||||
from .th_srv import TH_CH
|
from .th_srv import TH_CH
|
||||||
from .util import UTC, sigblock, vjoin, vol_san
|
from .util import UTC, vjoin, vol_san
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Generator, Optional
|
from typing import Any, Generator, Optional
|
||||||
|
|
||||||
from .util import NamedLogger
|
from .util import NamedLogger
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from httpsrv import HttpSrv
|
|
||||||
|
|
||||||
|
|
||||||
TAR_NO_OPUS = set("aac|m4a|m4b|m4r|mp3|oga|ogg|opus|wma".split("|"))
|
TAR_NO_OPUS = set("aac|m4a|m4b|m4r|mp3|oga|ogg|opus|wma".split("|"))
|
||||||
|
|
||||||
|
|
@ -45,17 +42,6 @@ class StreamArc(object):
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
|
||||||
|
|
||||||
_pools = {}
|
|
||||||
|
|
||||||
|
|
||||||
def close_pools() -> None:
|
|
||||||
for p in list(_pools):
|
|
||||||
try:
|
|
||||||
p.shutdown(wait=False, cancel_futures=True)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def gfilter(
|
def gfilter(
|
||||||
fgen: Generator[dict[str, Any], None, None],
|
fgen: Generator[dict[str, Any], None, None],
|
||||||
thumbcli: ThumbCli,
|
thumbcli: ThumbCli,
|
||||||
|
|
@ -66,8 +52,7 @@ def gfilter(
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
pend = []
|
pend = []
|
||||||
with ThreadPoolExecutor(max_workers=CORES, initializer=sigblock) as tp:
|
with ThreadPoolExecutor(max_workers=CORES) as tp:
|
||||||
_pools[tp] = 1
|
|
||||||
try:
|
try:
|
||||||
for f in fgen:
|
for f in fgen:
|
||||||
task = tp.submit(enthumb, thumbcli, uname, vtop, f, fmt)
|
task = tp.submit(enthumb, thumbcli, uname, vtop, f, fmt)
|
||||||
|
|
@ -94,61 +79,6 @@ def gfilter(
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
thumbcli.log("gfilter flushed")
|
thumbcli.log("gfilter flushed")
|
||||||
_pools.pop(tp, None)
|
|
||||||
|
|
||||||
|
|
||||||
def gfilter2(
|
|
||||||
fgen: Generator[
|
|
||||||
tuple[
|
|
||||||
"VFS",
|
|
||||||
str,
|
|
||||||
str,
|
|
||||||
str,
|
|
||||||
list[tuple[str, os.stat_result]],
|
|
||||||
list[tuple[str, os.stat_result]],
|
|
||||||
dict[str, "VFS"],
|
|
||||||
],
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
],
|
|
||||||
hsrv: "HttpSrv",
|
|
||||||
vtop: str,
|
|
||||||
fmts: list[str],
|
|
||||||
) -> Generator[dict[str, Any], None, None]:
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
pend = []
|
|
||||||
with ThreadPoolExecutor(max_workers=CORES, initializer=sigblock) as tp:
|
|
||||||
_pools[tp] = 1
|
|
||||||
for _, _, vpath, apath, files, rd, vd in fgen:
|
|
||||||
if "/.hist/" in vpath:
|
|
||||||
continue
|
|
||||||
fnames = [n[0] for n in files]
|
|
||||||
vpaths = [vpath + "/" + n for n in fnames] if vpath else fnames
|
|
||||||
for vp, fi in zip(vpaths, files):
|
|
||||||
for fmt in fmts:
|
|
||||||
try:
|
|
||||||
f = {"vp": vp, "st": fi[1]}
|
|
||||||
task = tp.submit(
|
|
||||||
enthumb, hsrv.thumbcli, LEELOO_DALLAS, vtop, f, fmt
|
|
||||||
)
|
|
||||||
pend.append((task, f))
|
|
||||||
if pend[0][0].done() or len(pend) > CORES * 4:
|
|
||||||
task, f = pend.pop(0)
|
|
||||||
try:
|
|
||||||
f = task.result(600)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
yield f
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
for task, f in pend:
|
|
||||||
try:
|
|
||||||
f = task.result(600)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
yield f
|
|
||||||
_pools.pop(tp, None)
|
|
||||||
|
|
||||||
|
|
||||||
def enthumb(
|
def enthumb(
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ from .cert import ensure_cert
|
||||||
from .fsutil import ramdisk_chk
|
from .fsutil import ramdisk_chk
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
|
||||||
from .pwhash import HAVE_ARGON2
|
from .pwhash import HAVE_ARGON2
|
||||||
from .sutil import close_pools as sutil_close_pools
|
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .th_srv import (
|
from .th_srv import (
|
||||||
H_PIL_AVIF,
|
H_PIL_AVIF,
|
||||||
|
|
@ -172,9 +171,6 @@ class SvcHub(object):
|
||||||
args.reflink = True
|
args.reflink = True
|
||||||
args.dav_auth = True
|
args.dav_auth = True
|
||||||
args.vague_403 = True
|
args.vague_403 = True
|
||||||
args.no_html = True
|
|
||||||
args.no_readme = True
|
|
||||||
args.no_logues = True
|
|
||||||
args.nih = True
|
args.nih = True
|
||||||
|
|
||||||
if args.s:
|
if args.s:
|
||||||
|
|
@ -200,14 +196,7 @@ class SvcHub(object):
|
||||||
self.log_div = 10 ** (6 - args.log_tdec)
|
self.log_div = 10 ** (6 - args.log_tdec)
|
||||||
self.log_efmt = "%02d:%02d:%02d.%0{}d".format(args.log_tdec)
|
self.log_efmt = "%02d:%02d:%02d.%0{}d".format(args.log_tdec)
|
||||||
self.log_dfmt = "%04d-%04d-%06d.%0{}d".format(args.log_tdec)
|
self.log_dfmt = "%04d-%04d-%06d.%0{}d".format(args.log_tdec)
|
||||||
|
self.log = self._log_disabled if args.q else self._log_enabled
|
||||||
if args.q:
|
|
||||||
self.log = self._log_disabled
|
|
||||||
elif args.lo and args.flo == 2 and not self.no_ansi:
|
|
||||||
self.log = self._log_en_f2
|
|
||||||
else:
|
|
||||||
self.log = self._log_enabled
|
|
||||||
|
|
||||||
if args.lo:
|
if args.lo:
|
||||||
self._setup_logfile(printed)
|
self._setup_logfile(printed)
|
||||||
|
|
||||||
|
|
@ -264,13 +253,6 @@ class SvcHub(object):
|
||||||
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
||||||
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
||||||
|
|
||||||
if args.vc_url:
|
|
||||||
zi = max(1, int(args.vc_age))
|
|
||||||
if zi < 3 and "api.copyparty.eu" in args.vc_url:
|
|
||||||
zi = 3
|
|
||||||
self.log("root", "vc-age too low for copyparty.eu; will use 3 hours")
|
|
||||||
args.vc_age = zi
|
|
||||||
|
|
||||||
zs = ""
|
zs = ""
|
||||||
if args.th_ram_max < 0.22:
|
if args.th_ram_max < 0.22:
|
||||||
zs = "generate thumbnails"
|
zs = "generate thumbnails"
|
||||||
|
|
@ -497,8 +479,6 @@ class SvcHub(object):
|
||||||
for nm in args.ipr_u.values():
|
for nm in args.ipr_u.values():
|
||||||
nm.mutex = threading.Lock()
|
nm.mutex = threading.Lock()
|
||||||
|
|
||||||
self._reload_thumbsrv()
|
|
||||||
|
|
||||||
def _db_onfail_ses(self) -> None:
|
def _db_onfail_ses(self) -> None:
|
||||||
self.args.no_ses = True
|
self.args.no_ses = True
|
||||||
|
|
||||||
|
|
@ -1158,7 +1138,7 @@ class SvcHub(object):
|
||||||
vsa = [x.upper() for x in vsa if x]
|
vsa = [x.upper() for x in vsa if x]
|
||||||
setattr(al, k + "_set", set(vsa))
|
setattr(al, k + "_set", set(vsa))
|
||||||
|
|
||||||
zs = "dav_ua1 lf_url sus_urls nonsus_urls ua_nodav ua_nodoc ua_nozip"
|
zs = "dav_ua1 sus_urls nonsus_urls ua_nodav ua_nodoc ua_nozip"
|
||||||
for k in zs.split(" "):
|
for k in zs.split(" "):
|
||||||
vs = getattr(al, k)
|
vs = getattr(al, k)
|
||||||
if not vs or vs == "no":
|
if not vs or vs == "no":
|
||||||
|
|
@ -1264,6 +1244,13 @@ class SvcHub(object):
|
||||||
except:
|
except:
|
||||||
raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
|
raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
|
||||||
|
|
||||||
|
if self.args.vc_url:
|
||||||
|
zi = max(1, int(self.args.vc_age))
|
||||||
|
if zi < 3 and "api.copyparty.eu" in self.args.vc_url:
|
||||||
|
zi = 3
|
||||||
|
self.log("root", "vc-age too low for copyparty.eu; will use 3 hours")
|
||||||
|
self.args.vc_age = zi
|
||||||
|
|
||||||
al.js_utc = "false" if al.localtime else "true"
|
al.js_utc = "false" if al.localtime else "true"
|
||||||
|
|
||||||
al.tcolor = al.tcolor.lstrip("#")
|
al.tcolor = al.tcolor.lstrip("#")
|
||||||
|
|
@ -1481,17 +1468,8 @@ class SvcHub(object):
|
||||||
self.log("root", "reload done")
|
self.log("root", "reload done")
|
||||||
t += "\n\nchanges to global options (if any) require a restart of copyparty to take effect"
|
t += "\n\nchanges to global options (if any) require a restart of copyparty to take effect"
|
||||||
self.broker.reload()
|
self.broker.reload()
|
||||||
self._reload_thumbsrv()
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def _reload_thumbsrv(self) -> None:
|
|
||||||
if not self.thumbsrv:
|
|
||||||
return
|
|
||||||
vols = list(self.asrv.vfs.all_nodes.values())
|
|
||||||
if next((x for x in vols if x.flags.get("th_pregen", "")), None):
|
|
||||||
fun = getattr(self.broker, "say1", self.broker.say)
|
|
||||||
fun("httpsrv.pregen_thumbs")
|
|
||||||
|
|
||||||
def _reload_sessions(self) -> None:
|
def _reload_sessions(self) -> None:
|
||||||
with self.asrv.mutex:
|
with self.asrv.mutex:
|
||||||
self.asrv.load_sessions(True)
|
self.asrv.load_sessions(True)
|
||||||
|
|
@ -1576,7 +1554,6 @@ class SvcHub(object):
|
||||||
|
|
||||||
if self.thumbsrv:
|
if self.thumbsrv:
|
||||||
self.thumbsrv.shutdown()
|
self.thumbsrv.shutdown()
|
||||||
sutil_close_pools()
|
|
||||||
|
|
||||||
for n in range(200): # 10s
|
for n in range(200): # 10s
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
@ -1737,69 +1714,6 @@ class SvcHub(object):
|
||||||
if not self.args.no_logflush:
|
if not self.args.no_logflush:
|
||||||
self.logf.flush()
|
self.logf.flush()
|
||||||
|
|
||||||
def _log_en_f2(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
|
|
||||||
with self.log_mutex:
|
|
||||||
dt = datetime.now(self.tz)
|
|
||||||
if dt.day != self.cday or dt.month != self.cmon:
|
|
||||||
if self.args.log_date:
|
|
||||||
zs = dt.strftime(self.args.log_date)
|
|
||||||
self.log_efmt = "%s %s" % (zs, self.log_efmt.split(" ")[-1])
|
|
||||||
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
|
|
||||||
zs = zs.format(dt.strftime("%Y-%m-%d"))
|
|
||||||
print(zs, end="")
|
|
||||||
self._set_next_day(dt)
|
|
||||||
if self.logf:
|
|
||||||
self.logf.write(zs)
|
|
||||||
|
|
||||||
ts = self.log_efmt % (
|
|
||||||
dt.hour,
|
|
||||||
dt.minute,
|
|
||||||
dt.second,
|
|
||||||
dt.microsecond // self.log_div,
|
|
||||||
)
|
|
||||||
|
|
||||||
# logfile:
|
|
||||||
if not c:
|
|
||||||
fmt = "%s %-21s LOG: %s\n"
|
|
||||||
elif c == 1:
|
|
||||||
fmt = "%s %-21s CRIT: %s\n"
|
|
||||||
elif c == 3:
|
|
||||||
fmt = "%s %-21s WARN: %s\n"
|
|
||||||
elif c == 6:
|
|
||||||
fmt = "%s %-21s BTW: %s\n"
|
|
||||||
else:
|
|
||||||
fmt = "%s %-21s LOG: %s\n"
|
|
||||||
fsrc = RE_ANSI.sub("", src) if "\033" in src else src
|
|
||||||
fmsg = RE_ANSI.sub("", msg) if "\033" in msg else msg
|
|
||||||
fmsg = fmt % (ts, fsrc, fmsg)
|
|
||||||
|
|
||||||
# stdout ansi:
|
|
||||||
fmt = "\033[36m%s \033[33m%-21s \033[0m%s\n"
|
|
||||||
if not c:
|
|
||||||
pass
|
|
||||||
elif isinstance(c, int):
|
|
||||||
msg = "\033[3%sm%s\033[0m" % (c, msg)
|
|
||||||
elif "\033" not in c:
|
|
||||||
msg = "\033[%sm%s\033[0m" % (c, msg)
|
|
||||||
else:
|
|
||||||
msg = "%s%s\033[0m" % (c, msg)
|
|
||||||
|
|
||||||
msg = fmt % (ts, src, msg)
|
|
||||||
try:
|
|
||||||
print(msg, end="")
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
try:
|
|
||||||
print(msg.encode("utf-8", "replace").decode(), end="")
|
|
||||||
except:
|
|
||||||
print(msg.encode("ascii", "replace").decode(), end="")
|
|
||||||
except OSError as ex:
|
|
||||||
if ex.errno != errno.EPIPE:
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.logf.write(fmsg)
|
|
||||||
if not self.args.no_logflush:
|
|
||||||
self.logf.flush()
|
|
||||||
|
|
||||||
def pr(self, *a: Any, **ka: Any) -> None:
|
def pr(self, *a: Any, **ka: Any) -> None:
|
||||||
try:
|
try:
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
|
|
@ -1957,7 +1871,3 @@ class SvcHub(object):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
t = "failed to process vulnerability advisory; %s"
|
t = "failed to process vulnerability advisory; %s"
|
||||||
self.log("ver-chk", t % (min_ex()), 1)
|
self.log("ver-chk", t % (min_ex()), 1)
|
||||||
try:
|
|
||||||
os.unlink(fpath)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .__init__ import ANYWIN, OPENBSD, PY2, TYPE_CHECKING, UNIX, unicode
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
|
||||||
from .cert import gencert
|
from .cert import gencert
|
||||||
from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
|
from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
|
||||||
from .util import (
|
from .util import (
|
||||||
|
|
@ -510,13 +510,6 @@ class TcpSrv(object):
|
||||||
return eps
|
return eps
|
||||||
|
|
||||||
def _extdevs_nix(self) -> Generator[str, None, None]:
|
def _extdevs_nix(self) -> Generator[str, None, None]:
|
||||||
if UNIX:
|
|
||||||
so, _ = chkcmd(["netstat", "-nrf", "inet"])
|
|
||||||
for ln in so.split("\n"):
|
|
||||||
if not ln.startswith("default"):
|
|
||||||
continue
|
|
||||||
yield ln.split()[7] if OPENBSD else ln.split()[3]
|
|
||||||
return
|
|
||||||
with open("/proc/net/route", "rb") as f:
|
with open("/proc/net/route", "rb") as f:
|
||||||
next(f)
|
next(f)
|
||||||
for ln in f:
|
for ln in f:
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ from .util import (
|
||||||
Daemon,
|
Daemon,
|
||||||
ODict,
|
ODict,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
exclude_dothidden,
|
|
||||||
min_ex,
|
min_ex,
|
||||||
runhook,
|
runhook,
|
||||||
set_fperms,
|
set_fperms,
|
||||||
|
|
@ -319,11 +318,7 @@ class Tftpd(object):
|
||||||
ls = virs + reals
|
ls = virs + reals
|
||||||
|
|
||||||
if "*" not in vn.axs.udot:
|
if "*" not in vn.axs.udot:
|
||||||
zsl = [x[2] for x in ls]
|
names = set(exclude_dotfiles([x[2] for x in ls]))
|
||||||
if "dothidden" in vn.flags and ".hidden" in zsl:
|
|
||||||
names = set(exclude_dothidden(zsl, fsroot))
|
|
||||||
else:
|
|
||||||
names = set(exclude_dotfiles(zsl))
|
|
||||||
ls = [x for x in ls if x[2] in names]
|
ls = [x for x in ls if x[2] in names]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ IMG_EXTS = set(["webp", "jpg", "png", "jxl"])
|
||||||
|
|
||||||
|
|
||||||
class ThumbCli(object):
|
class ThumbCli(object):
|
||||||
def __init__(self, hsrv: "HttpSrv", c: dict[str, set[str]]) -> None:
|
def __init__(self, hsrv: "HttpSrv") -> None:
|
||||||
self.broker = hsrv.broker
|
self.broker = hsrv.broker
|
||||||
self.log_func = hsrv.log
|
self.log_func = hsrv.log
|
||||||
self.args = hsrv.args
|
self.args = hsrv.args
|
||||||
|
|
@ -33,6 +33,16 @@ class ThumbCli(object):
|
||||||
# cache on both sides for less broker spam
|
# cache on both sides for less broker spam
|
||||||
self.cooldown = Cooldown(self.args.th_poke)
|
self.cooldown = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c = hsrv.th_cfg
|
||||||
|
if not c:
|
||||||
|
raise Exception()
|
||||||
|
except:
|
||||||
|
c = {
|
||||||
|
k: set()
|
||||||
|
for k in ["thumbable", "pil", "vips", "raw", "ffi", "ffv", "ffa"]
|
||||||
|
}
|
||||||
|
|
||||||
self.thumbable = c["thumbable"]
|
self.thumbable = c["thumbable"]
|
||||||
self.fmt_pil = c["pil"]
|
self.fmt_pil = c["pil"]
|
||||||
self.fmt_vips = c["vips"]
|
self.fmt_vips = c["vips"]
|
||||||
|
|
@ -43,8 +53,8 @@ class ThumbCli(object):
|
||||||
|
|
||||||
# defer args.th_ff_jpg, can change at runtime
|
# defer args.th_ff_jpg, can change at runtime
|
||||||
nonpil = next((x for x in self.args.th_dec if x in ("vips", "ff")), None)
|
nonpil = next((x for x in self.args.th_dec if x in ("vips", "ff")), None)
|
||||||
self.can_webp = (H_PIL_WEBP or nonpil) and not self.args.th_no_webp
|
self.can_webp = H_PIL_WEBP or nonpil
|
||||||
self.can_jxl = (H_PIL_JXL or nonpil) and not self.args.th_no_jxl
|
self.can_jxl = H_PIL_JXL or nonpil
|
||||||
|
|
||||||
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
self.log_func("thumbcli", msg, c)
|
self.log_func("thumbcli", msg, c)
|
||||||
|
|
@ -85,20 +95,23 @@ class ThumbCli(object):
|
||||||
if rem.startswith(".hist/th/") and rem.split(".")[-1] in IMG_EXTS:
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in IMG_EXTS:
|
||||||
return os.path.join(ptop, rem)
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
sfmt = fmt[:1]
|
if fmt[:1] in "jwx" and fmt != "wav":
|
||||||
if sfmt in "jwx" and fmt != "wav":
|
sfmt = fmt[:1]
|
||||||
|
|
||||||
if sfmt == "j" and self.args.th_no_jpg:
|
if sfmt == "j" and self.args.th_no_jpg:
|
||||||
sfmt = "w"
|
sfmt = "w"
|
||||||
|
|
||||||
if sfmt == "w":
|
if sfmt == "w":
|
||||||
if not self.can_webp or (
|
if (
|
||||||
self.args.th_ff_jpg and (not is_img or preferred == "ff")
|
self.args.th_no_webp
|
||||||
|
or not self.can_webp
|
||||||
|
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
|
||||||
):
|
):
|
||||||
sfmt = "j"
|
sfmt = "j"
|
||||||
|
|
||||||
if sfmt == "x" and not self.can_jxl:
|
if sfmt == "x":
|
||||||
sfmt = "w"
|
if self.args.th_no_jxl or not self.can_jxl:
|
||||||
|
sfmt = "w"
|
||||||
|
|
||||||
vf_crop = dbv.flags["crop"]
|
vf_crop = dbv.flags["crop"]
|
||||||
vf_th3x = dbv.flags["th3x"]
|
vf_th3x = dbv.flags["th3x"]
|
||||||
|
|
@ -115,7 +128,7 @@ class ThumbCli(object):
|
||||||
|
|
||||||
fmt = sfmt
|
fmt = sfmt
|
||||||
|
|
||||||
elif sfmt == "p" and not is_au and not is_vid:
|
elif fmt[:1] == "p" and not is_au and not is_vid:
|
||||||
t = "cannot thumbnail %r: png only allowed for waveforms"
|
t = "cannot thumbnail %r: png only allowed for waveforms"
|
||||||
self.log(t % (rem,), 6)
|
self.log(t % (rem,), 6)
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -186,10 +186,6 @@ try:
|
||||||
if os.environ.get("PRTY_NO_VIPS"):
|
if os.environ.get("PRTY_NO_VIPS"):
|
||||||
raise ImportError()
|
raise ImportError()
|
||||||
|
|
||||||
if "VIPS_CONCURRENCY" not in os.environ:
|
|
||||||
# reduces glibc RAM usage from 4.7 to 3.5 GiB ...yep, still bonkers
|
|
||||||
os.environ["VIPS_CONCURRENCY"] = "1"
|
|
||||||
|
|
||||||
HAVE_VIPS = True
|
HAVE_VIPS = True
|
||||||
import pyvips
|
import pyvips
|
||||||
|
|
||||||
|
|
@ -260,9 +256,6 @@ class ThumbSrv(object):
|
||||||
self.args = hub.args
|
self.args = hub.args
|
||||||
self.log_func = hub.log
|
self.log_func = hub.log
|
||||||
|
|
||||||
self.log = self._log
|
|
||||||
self.nextlog = 0
|
|
||||||
|
|
||||||
self.poke_cd = Cooldown(self.args.th_poke)
|
self.poke_cd = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
|
@ -276,18 +269,6 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
self.exts_spec_unsafe = set(self.args.th_spec_cnv.split(","))
|
self.exts_spec_unsafe = set(self.args.th_spec_cnv.split(","))
|
||||||
|
|
||||||
# libvips can easily gobble up 4 GiB of RAM when generating JXL thumbnails on glibc so let's not
|
|
||||||
self.vips_jxl = False
|
|
||||||
if HAVE_VIPS and self.args.th_vips_jxl == 2:
|
|
||||||
self.vips_jxl = True
|
|
||||||
elif HAVE_VIPS and self.args.th_vips_jxl == 1:
|
|
||||||
try:
|
|
||||||
with open("/proc/self/maps", "rb") as f:
|
|
||||||
zb = f.read()
|
|
||||||
self.vips_jxl = b"/ld-musl-" in zb and b"mimalloc" not in zb
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
||||||
for n in range(self.nthr):
|
for n in range(self.nthr):
|
||||||
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
||||||
|
|
@ -346,10 +327,6 @@ class ThumbSrv(object):
|
||||||
self.fmt_pil.discard(f)
|
self.fmt_pil.discard(f)
|
||||||
|
|
||||||
self.thumbable: set[str] = set()
|
self.thumbable: set[str] = set()
|
||||||
self._build_thumbable()
|
|
||||||
|
|
||||||
def _build_thumbable(self) -> None:
|
|
||||||
self.thumbable.clear()
|
|
||||||
|
|
||||||
if "pil" in self.args.th_dec:
|
if "pil" in self.args.th_dec:
|
||||||
self.thumbable |= self.fmt_pil
|
self.thumbable |= self.fmt_pil
|
||||||
|
|
@ -364,14 +341,7 @@ class ThumbSrv(object):
|
||||||
for zss in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
|
for zss in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
|
||||||
self.thumbable |= zss
|
self.thumbable |= zss
|
||||||
|
|
||||||
def _log(self, msg: str, c: Union[int, str] = 0) -> None:
|
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
self.log_func("thumb", msg, c)
|
|
||||||
|
|
||||||
def _slog(self, msg: str, c: Union[int, str] = 0) -> None:
|
|
||||||
now = time.time()
|
|
||||||
if c in (0, 6) and now < self.nextlog:
|
|
||||||
return
|
|
||||||
self.nextlog = now + self.args.th_pre_rl
|
|
||||||
self.log_func("thumb", msg, c)
|
self.log_func("thumb", msg, c)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
|
|
@ -447,10 +417,6 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _rebuild_thumbable(self) -> None:
|
|
||||||
self._build_thumbable()
|
|
||||||
self.hub.broker.say("httpsrv.set_th_cfg", self.getcfg(), (self.args.th_no_jxl,))
|
|
||||||
|
|
||||||
def getcfg(self) -> dict[str, set[str]]:
|
def getcfg(self) -> dict[str, set[str]]:
|
||||||
return {
|
return {
|
||||||
"thumbable": self.thumbable,
|
"thumbable": self.thumbable,
|
||||||
|
|
@ -551,11 +517,7 @@ class ThumbSrv(object):
|
||||||
|
|
||||||
if lib == "pil" and ext in self.fmt_pil and tex in self.fmt_pil:
|
if lib == "pil" and ext in self.fmt_pil and tex in self.fmt_pil:
|
||||||
funs.append(self.conv_pil)
|
funs.append(self.conv_pil)
|
||||||
elif (
|
elif lib == "vips" and ext in self.fmt_vips:
|
||||||
lib == "vips"
|
|
||||||
and ext in self.fmt_vips
|
|
||||||
and (tex != "jxl" or self.vips_jxl)
|
|
||||||
):
|
|
||||||
funs.append(self.conv_vips)
|
funs.append(self.conv_vips)
|
||||||
elif lib == "raw" and ext in self.fmt_raw:
|
elif lib == "raw" and ext in self.fmt_raw:
|
||||||
funs.append(self.conv_raw)
|
funs.append(self.conv_raw)
|
||||||
|
|
@ -845,7 +807,7 @@ class ThumbSrv(object):
|
||||||
b"-q:v",
|
b"-q:v",
|
||||||
unicode(vn.flags["th_qvx"]).encode("ascii"), # default=??
|
unicode(vn.flags["th_qvx"]).encode("ascii"), # default=??
|
||||||
b"-effort:v",
|
b"-effort:v",
|
||||||
b"7", # default=7, 1=fast, 9=max, 9~=8 but slower
|
b"8", # default=7, 1=fast, 9=max, 9~=8 but slower
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
cmd += [
|
cmd += [
|
||||||
|
|
@ -872,22 +834,6 @@ class ThumbSrv(object):
|
||||||
ret = 321
|
ret = 321
|
||||||
c = 3
|
c = 3
|
||||||
|
|
||||||
elif cmd[-1].lower().endswith(b".jxl") and (
|
|
||||||
"Error selecting an encoder" in serr
|
|
||||||
or "find a suitable output format" in serr
|
|
||||||
or "Automatic encoder selection failed" in serr
|
|
||||||
or "Default encoder for format webp" in serr
|
|
||||||
or "Unrecognized option 'effort:v" in serr
|
|
||||||
or "Please choose an encoder manually" in serr
|
|
||||||
):
|
|
||||||
self.args.th_no_jxl = True
|
|
||||||
self.fmt_ffi.discard("jxl")
|
|
||||||
self.fmt_ffv.discard("jxl")
|
|
||||||
self._rebuild_thumbable()
|
|
||||||
t = "FFmpeg failed because it was compiled without jpegxl; enabling --th-no-jxl to force webp output:\n"
|
|
||||||
ret = 321
|
|
||||||
c = 1
|
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
(not self.args.th_ff_jpg or time.time() - int(self.args.th_ff_jpg) < 60)
|
(not self.args.th_ff_jpg or time.time() - int(self.args.th_ff_jpg) < 60)
|
||||||
and cmd[-1].lower().endswith(b".webp")
|
and cmd[-1].lower().endswith(b".webp")
|
||||||
|
|
|
||||||
|
|
@ -337,10 +337,6 @@ class Up2k(object):
|
||||||
if not self.stop:
|
if not self.stop:
|
||||||
self.log("uploads are now possible", 2)
|
self.log("uploads are now possible", 2)
|
||||||
|
|
||||||
def is_busy(self) -> tuple[bool, float]:
|
|
||||||
# returns ( currently-busy , have-finished-at-least-once )
|
|
||||||
return bool(self.pp), self.gt1
|
|
||||||
|
|
||||||
def get_state(self, get_q: bool, uname: str) -> str:
|
def get_state(self, get_q: bool, uname: str) -> str:
|
||||||
mtpq: Union[int, str] = 0
|
mtpq: Union[int, str] = 0
|
||||||
ups = []
|
ups = []
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,6 @@ IMPLICATIONS = [
|
||||||
["tftpvv", "tftpv"],
|
["tftpvv", "tftpv"],
|
||||||
["nodupem", "nodupe"],
|
["nodupem", "nodupe"],
|
||||||
["no_dupe_m", "no_dupe"],
|
["no_dupe_m", "no_dupe"],
|
||||||
["no_html", "no_script"],
|
|
||||||
["nohtml", "noscript"],
|
["nohtml", "noscript"],
|
||||||
["sftpvv", "sftpv"],
|
["sftpvv", "sftpv"],
|
||||||
["smbw", "smb"],
|
["smbw", "smb"],
|
||||||
|
|
@ -491,12 +490,13 @@ font woff woff2 otf ttf
|
||||||
for v in vs.strip().split():
|
for v in vs.strip().split():
|
||||||
MIMES[v] = "{}/{}".format(k, v)
|
MIMES[v] = "{}/{}".format(k, v)
|
||||||
|
|
||||||
for ln in """text md=plain js=javascript ass=plain ssa=plain txt=plain
|
for ln in """text md=plain txt=plain js=javascript
|
||||||
application 7z=x-7z-compressed tar=x-tar bz2=x-bzip2 gz=gzip rar=x-rar-compressed zst=zstd xz=x-xz lz=lzip cpio=x-cpio
|
application 7z=x-7z-compressed tar=x-tar bz2=x-bzip2 gz=gzip rar=x-rar-compressed zst=zstd xz=x-xz lz=lzip cpio=x-cpio
|
||||||
application msi=x-ms-installer cab=vnd.ms-cab-compressed rpm=x-rpm crx=x-chrome-extension
|
application msi=x-ms-installer cab=vnd.ms-cab-compressed rpm=x-rpm crx=x-chrome-extension
|
||||||
application epub=epub+zip mobi=x-mobipocket-ebook lit=x-ms-reader rss=rss+xml atom=atom+xml torrent=x-bittorrent
|
application epub=epub+zip mobi=x-mobipocket-ebook lit=x-ms-reader rss=rss+xml atom=atom+xml torrent=x-bittorrent
|
||||||
application p7s=pkcs7-signature dcm=dicom shx=vnd.shx shp=vnd.shp dbf=x-dbf gml=gml+xml gpx=gpx+xml amf=x-amf
|
application p7s=pkcs7-signature dcm=dicom shx=vnd.shx shp=vnd.shp dbf=x-dbf gml=gml+xml gpx=gpx+xml amf=x-amf
|
||||||
application swf=x-shockwave-flash m3u=vnd.apple.mpegurl db3=vnd.sqlite3 sqlite=vnd.sqlite3
|
application swf=x-shockwave-flash m3u=vnd.apple.mpegurl db3=vnd.sqlite3 sqlite=vnd.sqlite3
|
||||||
|
text ass=plain ssa=plain
|
||||||
image jpg=jpeg xpm=x-xpixmap psd=vnd.adobe.photoshop jpf=jpx tif=tiff ico=x-icon djvu=vnd.djvu
|
image jpg=jpeg xpm=x-xpixmap psd=vnd.adobe.photoshop jpf=jpx tif=tiff ico=x-icon djvu=vnd.djvu
|
||||||
image heics=heic-sequence heifs=heif-sequence hdr=vnd.radiance svg=svg+xml
|
image heics=heic-sequence heifs=heif-sequence hdr=vnd.radiance svg=svg+xml
|
||||||
image arw=x-sony-arw cr2=x-canon-cr2 crw=x-canon-crw dcr=x-kodak-dcr dng=x-adobe-dng erf=x-epson-erf
|
image arw=x-sony-arw cr2=x-canon-cr2 crw=x-canon-crw dcr=x-kodak-dcr dng=x-adobe-dng erf=x-epson-erf
|
||||||
|
|
@ -2396,33 +2396,6 @@ def exclude_dotfiles_ls(
|
||||||
return [x for x in vfs_ls if not x[0].split("/")[-1].startswith(".")]
|
return [x for x in vfs_ls if not x[0].split("/")[-1].startswith(".")]
|
||||||
|
|
||||||
|
|
||||||
def exclude_dothidden(filepaths: list[str], fsroot: Any) -> list[str]:
|
|
||||||
ret = [x for x in filepaths if not x.split("/")[-1].startswith(".")]
|
|
||||||
filt = load_dothidden(fsroot)
|
|
||||||
if filt:
|
|
||||||
ret = [x for x in ret if x.split("/")[-1] not in filt]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def exclude_dothidden_ls(
|
|
||||||
vfs_ls: list[tuple[str, os.stat_result]], fsroot: Any
|
|
||||||
) -> list[tuple[str, os.stat_result]]:
|
|
||||||
ret = [x for x in vfs_ls if not x[0].split("/")[-1].startswith(".")]
|
|
||||||
filt = load_dothidden(fsroot)
|
|
||||||
if filt:
|
|
||||||
ret = [x for x in ret if x[0].split("/")[-1] not in filt]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def load_dothidden(dpath: str) -> list[str]:
|
|
||||||
try:
|
|
||||||
with open(os.path.join(dpath, ".hidden"), "rb") as f:
|
|
||||||
zsl = f.read().decode("utf-8").splitlines()
|
|
||||||
return [x.strip() for x in zsl]
|
|
||||||
except OSError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def odfusion(
|
def odfusion(
|
||||||
base: Union[ODict[str, bool], ODict["LiteralString", bool]], oth: str
|
base: Union[ODict[str, bool], ODict["LiteralString", bool]], oth: str
|
||||||
) -> ODict[str, bool]:
|
) -> ODict[str, bool]:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
var J_BRW = 1;
|
var J_BRW = 1;
|
||||||
|
|
||||||
if (window.dgauto === undefined)
|
if (window.rw_edit === undefined)
|
||||||
alert('FATAL ERROR: receiving stale data from the server; this may be due to a broken reverse-proxy (stuck cache). Try restarting copyparty and press CTRL-SHIFT-R in the browser');
|
alert('FATAL ERROR: receiving stale data from the server; this may be due to a broken reverse-proxy (stuck cache). Try restarting copyparty and press CTRL-SHIFT-R in the browser');
|
||||||
|
|
||||||
var XHR = XMLHttpRequest;
|
var XHR = XMLHttpRequest;
|
||||||
|
|
@ -228,7 +228,6 @@ if (1)
|
||||||
"cl_hpick": "tap on column headers to hide in the table below",
|
"cl_hpick": "tap on column headers to hide in the table below",
|
||||||
"cl_hcancel": "column hiding aborted",
|
"cl_hcancel": "column hiding aborted",
|
||||||
"cl_rcm": "right-click menu",
|
"cl_rcm": "right-click menu",
|
||||||
"cl_gauto": "autogrid",
|
|
||||||
|
|
||||||
"ct_grid": '田 the grid',
|
"ct_grid": '田 the grid',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
|
||||||
|
|
@ -283,8 +282,6 @@ if (1)
|
||||||
"tt_dynt": "autogrow as tree expands",
|
"tt_dynt": "autogrow as tree expands",
|
||||||
"tt_wrap": "word wrap",
|
"tt_wrap": "word wrap",
|
||||||
"tt_hover": "reveal overflowing lines on hover$N( breaks scrolling unless mouse $N cursor is in the left gutter )",
|
"tt_hover": "reveal overflowing lines on hover$N( breaks scrolling unless mouse $N cursor is in the left gutter )",
|
||||||
"tt_gauto": "display as grid or list depending on folder contents",
|
|
||||||
"tt_gathr": "use grid if this percentage of files are pics/vids",
|
|
||||||
|
|
||||||
"ml_pmode": "at end of folder...",
|
"ml_pmode": "at end of folder...",
|
||||||
"ml_btns": "cmds",
|
"ml_btns": "cmds",
|
||||||
|
|
@ -716,54 +713,6 @@ var L = Ls[lang] || Ls.eng, LANGS = [];
|
||||||
for (var a = 0; a < LANGN.length; a++)
|
for (var a = 0; a < LANGN.length; a++)
|
||||||
LANGS.push(LANGN[a][0]);
|
LANGS.push(LANGN[a][0]);
|
||||||
|
|
||||||
if (window.glang && navigator.languages && !/\bcplng=/.test(document.cookie))
|
|
||||||
(function() {
|
|
||||||
var lmap = [
|
|
||||||
["eng", /^en/i],
|
|
||||||
["nor", /^n[ob]/i],
|
|
||||||
["chi", /^zh-cn/i],
|
|
||||||
["cze", /^cs/i],
|
|
||||||
["deu", /^de/i],
|
|
||||||
["epo", /^eo/i],
|
|
||||||
["fin", /^fi/i],
|
|
||||||
["fra", /^fr/i],
|
|
||||||
["grc", /^el/i],
|
|
||||||
["hun", /^hu/i],
|
|
||||||
["ita", /^it/i],
|
|
||||||
["jpn", /^ja/i],
|
|
||||||
["kor", /^ko/i],
|
|
||||||
["nld", /^nl/i],
|
|
||||||
["nno", /^nn/i],
|
|
||||||
["pol", /^pl/i],
|
|
||||||
["por", /^pt/i],
|
|
||||||
["rus", /^ru/i],
|
|
||||||
["spa", /^es/i],
|
|
||||||
["swe", /^sv/i],
|
|
||||||
["tur", /^tr/i],
|
|
||||||
["ukr", /^uk/i],
|
|
||||||
["vie", /^vi/i],
|
|
||||||
];
|
|
||||||
for (var a = 0; a < navigator.languages.length; a++) {
|
|
||||||
for (var b = 0; b < lmap.length; b++) {
|
|
||||||
var n = lmap[b][0];
|
|
||||||
if (!lmap[b][1].test(navigator.languages[a]) || !has(LANGS, n))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (Ls[n]) {
|
|
||||||
lang = n;
|
|
||||||
L = Ls[n];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (window.stop)
|
|
||||||
window.stop();
|
|
||||||
document.body.innerHTML = 'Loading ' + n;
|
|
||||||
setck("cplng=" + n, location.reload.bind(location));
|
|
||||||
crashed = true;
|
|
||||||
throw 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
function langtest() {
|
function langtest() {
|
||||||
var n = LANGS.length - 1;
|
var n = LANGS.length - 1;
|
||||||
|
|
@ -772,9 +721,7 @@ function langtest() {
|
||||||
}
|
}
|
||||||
function langtest2() {
|
function langtest2() {
|
||||||
for (var a = 0; a < LANGS.length; a++) {
|
for (var a = 0; a < LANGS.length; a++) {
|
||||||
if (!Ls[LANGS[a]]) continue;
|
|
||||||
for (var b = a + 1; b < LANGS.length; b++) {
|
for (var b = a + 1; b < LANGS.length; b++) {
|
||||||
if (!Ls[LANGS[b]]) continue;
|
|
||||||
var i1 = Object.keys(Ls[LANGS[a]]).length > Object.keys(Ls[LANGS[b]]).length ? a : b,
|
var i1 = Object.keys(Ls[LANGS[a]]).length > Object.keys(Ls[LANGS[b]]).length ? a : b,
|
||||||
i2 = i1 == a ? b : a,
|
i2 = i1 == a ? b : a,
|
||||||
t1 = Ls[LANGS[i1]],
|
t1 = Ls[LANGS[i1]],
|
||||||
|
|
@ -788,7 +735,6 @@ for (var a = 0; a < LANGS.length; a++) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
langtest2();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -990,13 +936,6 @@ ebi('op_cfg').innerHTML = (
|
||||||
' </div>\n' +
|
' </div>\n' +
|
||||||
'</div>\n' +
|
'</div>\n' +
|
||||||
'<div>\n' +
|
'<div>\n' +
|
||||||
' <h3>' + L.cl_gauto + '</h3>\n' +
|
|
||||||
' <div>\n' +
|
|
||||||
' <a id="gauto" class="tgl btn" href="#" tt="' + L.tt_gauto + '">' + L.enable + '</a>\n' +
|
|
||||||
' <input type="text" id="ga_thresh" value="" ' + NOAC + ' style="width:1.5em" tt="' + L.tt_gathr + '" />' +
|
|
||||||
' </div>\n' +
|
|
||||||
'</div>\n' +
|
|
||||||
'<div>\n' +
|
|
||||||
' <h3>' + L.cl_hfsz + '</h3>\n' +
|
' <h3>' + L.cl_hfsz + '</h3>\n' +
|
||||||
' <div><select id="fszfmt">\n' +
|
' <div><select id="fszfmt">\n' +
|
||||||
' <option value="0">0 ┃ 1234567</option>\n' +
|
' <option value="0">0 ┃ 1234567</option>\n' +
|
||||||
|
|
@ -1216,7 +1155,9 @@ onresize100.add(read_sbw, true);
|
||||||
|
|
||||||
|
|
||||||
function check_image_support(format, uri) {
|
function check_image_support(format, uri) {
|
||||||
var cached = window['have_' + format] = sread('have_' + format);
|
var cached
|
||||||
|
= window['have_' + format]
|
||||||
|
= sread('have_' + format);
|
||||||
if (cached !== null)
|
if (cached !== null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -1778,9 +1719,7 @@ function MPlayer() {
|
||||||
if (!tid || tid.indexOf('af-') !== 0)
|
if (!tid || tid.indexOf('af-') !== 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
tid = tid.slice(1);
|
order.push(tid.slice(1));
|
||||||
if (r.tracks[tid])
|
|
||||||
order.push(tid);
|
|
||||||
}
|
}
|
||||||
r.order = order;
|
r.order = order;
|
||||||
r.shuffle();
|
r.shuffle();
|
||||||
|
|
@ -5409,8 +5348,8 @@ var showfile = (function () {
|
||||||
Prism.highlightElement(el);
|
Prism.highlightElement(el);
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
btn.setAttribute('download', ebi('docname').innerHTML);
|
btn.setAttribute('download', ebi('docname').innerHTML);
|
||||||
btn.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(jt));
|
btn.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(jt));
|
||||||
};
|
};
|
||||||
|
|
||||||
r.mktree = function () {
|
r.mktree = function () {
|
||||||
|
|
@ -5618,16 +5557,6 @@ var thegrid = (function () {
|
||||||
r.setvis();
|
r.setvis();
|
||||||
};
|
};
|
||||||
|
|
||||||
r.autogrid = function (res) {
|
|
||||||
var ni = 0;
|
|
||||||
var nf = res.files.length;
|
|
||||||
for (var a = 0; a < nf; a++)
|
|
||||||
if (img_re.test('.' + res.files[a].ext))
|
|
||||||
ni++;
|
|
||||||
if (nf)
|
|
||||||
thegrid.en = 100 * ni / nf >= r.gathr;
|
|
||||||
};
|
|
||||||
|
|
||||||
function setln(v) {
|
function setln(v) {
|
||||||
if (v) {
|
if (v) {
|
||||||
r.ln += v;
|
r.ln += v;
|
||||||
|
|
@ -5850,7 +5779,7 @@ var thegrid = (function () {
|
||||||
ihref = addq(ihref, 'th=' + (
|
ihref = addq(ihref, 'th=' + (
|
||||||
have_jxl ? 'x' :
|
have_jxl ? 'x' :
|
||||||
have_webp ? 'w' :
|
have_webp ? 'w' :
|
||||||
'j'
|
'j'
|
||||||
));
|
));
|
||||||
if (!r.crop)
|
if (!r.crop)
|
||||||
ihref += 'f';
|
ihref += 'f';
|
||||||
|
|
@ -6015,17 +5944,6 @@ var thegrid = (function () {
|
||||||
pbar.onresize();
|
pbar.onresize();
|
||||||
vbar.onresize();
|
vbar.onresize();
|
||||||
});
|
});
|
||||||
bcfg_bind(r, 'gaen', 'gauto', !!dgauto, function(v) {
|
|
||||||
if (r.en && sread("griden") != 1) {
|
|
||||||
r.en = false;
|
|
||||||
r.setvis(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ebi('ga_thresh').value = r.gathr = icfg_get('ga_thresh', dgauto || 70);
|
|
||||||
ebi('ga_thresh').oninput = function (e) {
|
|
||||||
var n = parseInt(this.value);
|
|
||||||
swrite('ga_thresh', r.gathr = (isNum(n) ? n : 0) || 70);
|
|
||||||
};
|
|
||||||
ebi('wtgrid').onclick = ebi('griden').onclick;
|
ebi('wtgrid').onclick = ebi('griden').onclick;
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -7738,9 +7656,6 @@ var treectl = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thegrid.gaen && sread('griden') != 1)
|
|
||||||
thegrid.autogrid(res);
|
|
||||||
|
|
||||||
if (url) setTimeout(asdf, 1); else asdf();
|
if (url) setTimeout(asdf, 1); else asdf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9711,7 +9626,7 @@ var rcm = (function () {
|
||||||
var row = mknod('tr', 'rcm_tmp',
|
var row = mknod('tr', 'rcm_tmp',
|
||||||
'<td>-new-</td><td colspan="' + (QSA("#files thead th").length - 1) + '"><input id="tempname" class="i" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></td>');
|
'<td>-new-</td><td colspan="' + (QSA("#files thead th").length - 1) + '"><input id="tempname" class="i" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></td>');
|
||||||
QS("#files tbody").appendChild(row);
|
QS("#files tbody").appendChild(row);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var row = mknod('a', 'rcm_tmp',
|
var row = mknod('a', 'rcm_tmp',
|
||||||
'<span class="dir" style="align-self:end"><input id="tempname" class="dir" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></span>');
|
'<span class="dir" style="align-self:end"><input id="tempname" class="dir" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></span>');
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,7 @@
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if logues[0] %}
|
{%- if logues[0] %}
|
||||||
{%- if sb_lg %}
|
<div>{{ logues[0] }}</div><br />
|
||||||
<div>{{ logues[0][:2000]|e }}</div><br />
|
|
||||||
{%- else %}
|
|
||||||
<div>{{ logues[0][:2000] }}</div><br />
|
|
||||||
{%- endif %}
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<table id="files">
|
<table id="files">
|
||||||
|
|
@ -57,11 +53,7 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{%- if logues[1] %}
|
{%- if logues[1] %}
|
||||||
{%- if sb_lg %}
|
<div>{{ logues[1] }}</div><br />
|
||||||
<div>{{ logues[1][:2000]|e }}</div><br />
|
|
||||||
{%- else %}
|
|
||||||
<div>{{ logues[1][:2000] }}</div><br />
|
|
||||||
{%- endif %}
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||||
|
|
|
||||||
|
|
@ -65,21 +65,13 @@ o = ebi('u');
|
||||||
if (o && /[0-9]+$/.exec(o.innerHTML))
|
if (o && /[0-9]+$/.exec(o.innerHTML))
|
||||||
o.innerHTML = shumantime(o.innerHTML);
|
o.innerHTML = shumantime(o.innerHTML);
|
||||||
|
|
||||||
o = ebi('uhash');
|
o = ebi('uhash')
|
||||||
if (o)
|
if (o)
|
||||||
o.value = '' + location.hash;
|
o.value = '' + location.hash;
|
||||||
|
|
||||||
if (/\&re=/.test('' + location))
|
if (/\&re=/.test('' + location))
|
||||||
ebi('a').className = 'af g';
|
ebi('a').className = 'af g';
|
||||||
|
|
||||||
o = ebi('v');
|
|
||||||
if (o) o.onclick = function (e) {
|
|
||||||
if (e && e.shiftKey) {
|
|
||||||
ev(e);
|
|
||||||
document.location = '//youtu.be/8Ok5Sey1MoU';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
if (!ebi('x'))
|
if (!ebi('x'))
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.chi = {
|
||||||
"cl_hpick": "在下方文件列表中点击某列表头即可从表中隐去该列",
|
"cl_hpick": "在下方文件列表中点击某列表头即可从表中隐去该列",
|
||||||
"cl_hcancel": "列隐藏操作已中止",
|
"cl_hcancel": "列隐藏操作已中止",
|
||||||
"cl_rcm": "右键菜单",
|
"cl_rcm": "右键菜单",
|
||||||
"cl_gauto": "自动网格", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 网格',
|
"ct_grid": '田 网格',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ 提示',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ 提示',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.chi = {
|
||||||
"tt_dynt": "自动随着目录树展开而变宽",
|
"tt_dynt": "自动随着目录树展开而变宽",
|
||||||
"tt_wrap": "自动换行",
|
"tt_wrap": "自动换行",
|
||||||
"tt_hover": "悬停时完整显示出写不下的文字$N(启用后,鼠标光标只有$N 位于左边线上才滚得动)",
|
"tt_hover": "悬停时完整显示出写不下的文字$N(启用后,鼠标光标只有$N 位于左边线上才滚得动)",
|
||||||
"tt_gauto": "根据文件夹内容以网格或列表显示", //m
|
|
||||||
"tt_gathr": "当此比例的文件为图片/视频时使用网格", //m
|
|
||||||
|
|
||||||
"ml_pmode": "文件夹播完后",
|
"ml_pmode": "文件夹播完后",
|
||||||
"ml_btns": "命令",
|
"ml_btns": "命令",
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,6 @@ Ls.cze = {
|
||||||
"cl_hpick": "klepněte na záhlaví sloupců pro skrytí v tabulce níže",
|
"cl_hpick": "klepněte na záhlaví sloupců pro skrytí v tabulce níže",
|
||||||
"cl_hcancel": "skrývání sloupců zrušeno",
|
"cl_hcancel": "skrývání sloupců zrušeno",
|
||||||
"cl_rcm": "kontextová nabídka", //m
|
"cl_rcm": "kontextová nabídka", //m
|
||||||
"cl_gauto": "auto mřížka", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 mřížka',
|
"ct_grid": '田 mřížka',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ nápovědy',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ nápovědy',
|
||||||
|
|
@ -280,8 +279,6 @@ Ls.cze = {
|
||||||
"tt_dynt": "automaticky rozrůstat jak se strom rozšiřuje",
|
"tt_dynt": "automaticky rozrůstat jak se strom rozšiřuje",
|
||||||
"tt_wrap": "zalomení řádků",
|
"tt_wrap": "zalomení řádků",
|
||||||
"tt_hover": "odhalit přetékající řádky při najetí$N( ruší posun pokud kurzor myši $N není v levém okraji )",
|
"tt_hover": "odhalit přetékající řádky při najetí$N( ruší posun pokud kurzor myši $N není v levém okraji )",
|
||||||
"tt_gauto": "zobrazit jako mřížku nebo seznam podle obsahu složky", //m
|
|
||||||
"tt_gathr": "použít mřížku, pokud toto procento souborů tvoří obrázky/videa", //m
|
|
||||||
|
|
||||||
"ml_pmode": "na konci složky...",
|
"ml_pmode": "na konci složky...",
|
||||||
"ml_btns": "příkazy",
|
"ml_btns": "příkazy",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.deu = {
|
||||||
"cl_hpick": "zum Verstecken, tippe auf Spaltenüberschriften in der Tabelle unten",
|
"cl_hpick": "zum Verstecken, tippe auf Spaltenüberschriften in der Tabelle unten",
|
||||||
"cl_hcancel": "Spaltenbearbeitung abgebrochen",
|
"cl_hcancel": "Spaltenbearbeitung abgebrochen",
|
||||||
"cl_rcm": "Rechtsklick-Menü",
|
"cl_rcm": "Rechtsklick-Menü",
|
||||||
"cl_gauto": "auto-raster", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 Das Raster™',
|
"ct_grid": '田 Das Raster™',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ Tooltips',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ Tooltips',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.deu = {
|
||||||
"tt_dynt": "autom. wachsen wenn Baum wächst",
|
"tt_dynt": "autom. wachsen wenn Baum wächst",
|
||||||
"tt_wrap": "Zeilenumbruch",
|
"tt_wrap": "Zeilenumbruch",
|
||||||
"tt_hover": "Beim Hovern überlange Zeilen anzeigen$N(Scrollen funktioniert nicht ausser $N Cursor ist im linken Gutter)",
|
"tt_hover": "Beim Hovern überlange Zeilen anzeigen$N(Scrollen funktioniert nicht ausser $N Cursor ist im linken Gutter)",
|
||||||
"tt_gauto": "je nach ordnerinhalt als raster oder liste anzeigen", //m
|
|
||||||
"tt_gathr": "raster verwenden, wenn dieser prozentsatz der dateien bilder/videos sind", //m
|
|
||||||
|
|
||||||
"ml_pmode": "am Ende des Ordners...",
|
"ml_pmode": "am Ende des Ordners...",
|
||||||
"ml_btns": "cmds",
|
"ml_btns": "cmds",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.epo = {
|
||||||
"cl_hpick": "alklaki la kapojn de kolumnoj por kasi en la suban tabelon",
|
"cl_hpick": "alklaki la kapojn de kolumnoj por kasi en la suban tabelon",
|
||||||
"cl_hcancel": "kaŝado de kolumno nuligita",
|
"cl_hcancel": "kaŝado de kolumno nuligita",
|
||||||
"cl_rcm": "dekstra-klaka menuo",
|
"cl_rcm": "dekstra-klaka menuo",
|
||||||
"cl_gauto": "aŭto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 krado',
|
"ct_grid": '田 krado',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ ŝpruchelpiloj',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ ŝpruchelpiloj',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.epo = {
|
||||||
"tt_dynt": "aŭtomate pligrandigi panelon",
|
"tt_dynt": "aŭtomate pligrandigi panelon",
|
||||||
"tt_wrap": "linifaldo",
|
"tt_wrap": "linifaldo",
|
||||||
"tt_hover": "montri kompletajn nomojn sur musumo$N( paneas rulumadon, se la kursoro de muso $N ne estas en la maldekstra malplenaĵo )",
|
"tt_hover": "montri kompletajn nomojn sur musumo$N( paneas rulumadon, se la kursoro de muso $N ne estas en la maldekstra malplenaĵo )",
|
||||||
"tt_gauto": "montri kiel krado aŭ listo laŭ dosieruja enhavo", //m
|
|
||||||
"tt_gathr": "uzi kradon se ĉi tiu procento de dosieroj estas bildoj/filmetoj", //m
|
|
||||||
|
|
||||||
"ml_pmode": "je la fino de dosierujo...",
|
"ml_pmode": "je la fino de dosierujo...",
|
||||||
"ml_btns": "komandoj",
|
"ml_btns": "komandoj",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.fin = {
|
||||||
"cl_hpick": "napauta sarakeotsikoita piilottaaksesi alla olevassa taulukossa",
|
"cl_hpick": "napauta sarakeotsikoita piilottaaksesi alla olevassa taulukossa",
|
||||||
"cl_hcancel": "sarakkeiden piilotus peruttu",
|
"cl_hcancel": "sarakkeiden piilotus peruttu",
|
||||||
"cl_rcm": "hiiren pikavalikko",
|
"cl_rcm": "hiiren pikavalikko",
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 kuvanäkymä',
|
"ct_grid": '田 kuvanäkymä',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ vihjelaatikot',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ vihjelaatikot',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.fin = {
|
||||||
"tt_dynt": "kasvata automaattisesti hakemistosyvyyden kasvaessa",
|
"tt_dynt": "kasvata automaattisesti hakemistosyvyyden kasvaessa",
|
||||||
"tt_wrap": "rivitys",
|
"tt_wrap": "rivitys",
|
||||||
"tt_hover": "paljasta ylivuotavat rivit leijutettaessa$N( rikkoo vierityksen ellei hiiri $N ole vasemmassa marginaalissa )",
|
"tt_hover": "paljasta ylivuotavat rivit leijutettaessa$N( rikkoo vierityksen ellei hiiri $N ole vasemmassa marginaalissa )",
|
||||||
"tt_gauto": "näytä ruudukkona tai listana kansion sisällön mukaan", //m
|
|
||||||
"tt_gathr": "käytä ruudukkoa jos tämä prosentti tiedostoista on kuvia/videoita", //m
|
|
||||||
|
|
||||||
"ml_pmode": "hakemiston lopussa...",
|
"ml_pmode": "hakemiston lopussa...",
|
||||||
"ml_btns": "komennot",
|
"ml_btns": "komennot",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.fra = {
|
||||||
"cl_hpick": "cliquez sur les en-têtes de colonnes pour les masquer dans le tableau ci-dessous",
|
"cl_hpick": "cliquez sur les en-têtes de colonnes pour les masquer dans le tableau ci-dessous",
|
||||||
"cl_hcancel": "masquage des colonnes annulé",
|
"cl_hcancel": "masquage des colonnes annulé",
|
||||||
"cl_rcm": "menu contextuel", //m
|
"cl_rcm": "menu contextuel", //m
|
||||||
"cl_gauto": "auto-grille", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 grille',
|
"ct_grid": '田 grille',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ infobulles',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ infobulles',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.fra = {
|
||||||
"tt_dynt": "croissance automatique à mesure que l'arborescence s'étend",
|
"tt_dynt": "croissance automatique à mesure que l'arborescence s'étend",
|
||||||
"tt_wrap": "retour à la ligne",
|
"tt_wrap": "retour à la ligne",
|
||||||
"tt_hover": "révéler les lignes débordantes au survol$N( interrompt le défilement à moins que le curseur de la souris ne soit dans la gouttière gauche )",
|
"tt_hover": "révéler les lignes débordantes au survol$N( interrompt le défilement à moins que le curseur de la souris ne soit dans la gouttière gauche )",
|
||||||
"tt_gauto": "afficher en grille ou liste selon le contenu du dossier", //m
|
|
||||||
"tt_gathr": "utiliser la grille si ce pourcentage de fichiers sont des images/vidéos", //m
|
|
||||||
|
|
||||||
"ml_pmode": "à la fin du dossier…",
|
"ml_pmode": "à la fin du dossier…",
|
||||||
"ml_btns": "cmds",
|
"ml_btns": "cmds",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.grc = {
|
||||||
"cl_hpick": "πάτησε στις κεφαλίδες στηλών για να τις κρύψεις στον πίνακα παρακάτω",
|
"cl_hpick": "πάτησε στις κεφαλίδες στηλών για να τις κρύψεις στον πίνακα παρακάτω",
|
||||||
"cl_hcancel": "η απόκρυψη στηλών ακυρώθηκε",
|
"cl_hcancel": "η απόκρυψη στηλών ακυρώθηκε",
|
||||||
"cl_rcm": "μενού δεξιού κλικ", //m
|
"cl_rcm": "μενού δεξιού κλικ", //m
|
||||||
"cl_gauto": "αυτόματο田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 το πλέγμα',
|
"ct_grid": '田 το πλέγμα',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.grc = {
|
||||||
"tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών",
|
"tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών",
|
||||||
"tt_wrap": "αναδίπλωση λέξεων",
|
"tt_wrap": "αναδίπλωση λέξεων",
|
||||||
"tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N είναι στην αριστερή στήλη )",
|
"tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N είναι στην αριστερή στήλη )",
|
||||||
"tt_gauto": "εμφάνιση ως πλέγμα ή λίστα ανάλογα με τα περιεχόμενα του φακέλου", //m
|
|
||||||
"tt_gathr": "χρήση πλέγματος αν αυτό το ποσοστό αρχείων είναι εικόνες/βίντεο", //m
|
|
||||||
|
|
||||||
"ml_pmode": "στο τέλος του φακέλου...",
|
"ml_pmode": "στο τέλος του φακέλου...",
|
||||||
"ml_btns": "εντολές",
|
"ml_btns": "εντολές",
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,6 @@ Ls.hun = {
|
||||||
"cl_hpick": 'kattints az oszlopfejlécre az elrejtéshez',
|
"cl_hpick": 'kattints az oszlopfejlécre az elrejtéshez',
|
||||||
"cl_hcancel": 'elrejtés megszakítva',
|
"cl_hcancel": 'elrejtés megszakítva',
|
||||||
"cl_rcm": 'jobb-klikkes menü',
|
"cl_rcm": 'jobb-klikkes menü',
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 rács nézet',
|
"ct_grid": '田 rács nézet',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ segítő szövegek',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ segítő szövegek',
|
||||||
|
|
@ -277,8 +276,6 @@ Ls.hun = {
|
||||||
"tt_dynt": 'automatikus méretezés nyitáskor',
|
"tt_dynt": 'automatikus méretezés nyitáskor',
|
||||||
"tt_wrap": 'sortörés',
|
"tt_wrap": 'sortörés',
|
||||||
"tt_hover": 'túl hosszú sorok mutatása rámutatáskor',
|
"tt_hover": 'túl hosszú sorok mutatása rámutatáskor',
|
||||||
"tt_gauto": "megjelenítés rácsban vagy listában a mappa tartalmától függően", //m
|
|
||||||
"tt_gathr": "rács használata, ha a fájlok ezen százaléka kép/videó", //m
|
|
||||||
|
|
||||||
"ml_pmode": 'mappa végén...',
|
"ml_pmode": 'mappa végén...',
|
||||||
"ml_btns": 'gombok',
|
"ml_btns": 'gombok',
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.ita = {
|
||||||
"cl_hpick": "tocca le intestazioni delle colonne per nascondere nella tabella sottostante",
|
"cl_hpick": "tocca le intestazioni delle colonne per nascondere nella tabella sottostante",
|
||||||
"cl_hcancel": "nascondere colonne annullato",
|
"cl_hcancel": "nascondere colonne annullato",
|
||||||
"cl_rcm": "menu contestuale", //m
|
"cl_rcm": "menu contestuale", //m
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 griglia',
|
"ct_grid": '田 griglia',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltip',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltip',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.ita = {
|
||||||
"tt_dynt": "crescita automatica mentre l'albero si espande",
|
"tt_dynt": "crescita automatica mentre l'albero si espande",
|
||||||
"tt_wrap": "a capo parola",
|
"tt_wrap": "a capo parola",
|
||||||
"tt_hover": "rivela righe che traboccano al passaggio del mouse$N( interrompe lo scorrimento a meno che il cursore $N del mouse non sia nella grondaia sinistra )",
|
"tt_hover": "rivela righe che traboccano al passaggio del mouse$N( interrompe lo scorrimento a meno che il cursore $N del mouse non sia nella grondaia sinistra )",
|
||||||
"tt_gauto": "mostra come griglia o lista in base al contenuto della cartella", //m
|
|
||||||
"tt_gathr": "usa la griglia se questa percentuale di file sono immagini/video", //m
|
|
||||||
|
|
||||||
"ml_pmode": "alla fine della cartella...",
|
"ml_pmode": "alla fine della cartella...",
|
||||||
"ml_btns": "comandi",
|
"ml_btns": "comandi",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.jpn = {
|
||||||
"cl_hpick": "下の表で非表示にするには列ヘッダーをタップします",
|
"cl_hpick": "下の表で非表示にするには列ヘッダーをタップします",
|
||||||
"cl_hcancel": "列の非表示を解除",
|
"cl_hcancel": "列の非表示を解除",
|
||||||
"cl_rcm": "右クリックメニュー",
|
"cl_rcm": "右クリックメニュー",
|
||||||
"cl_gauto": "自動グリッド", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 グリッド',
|
"ct_grid": '田 グリッド',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ ツールチップ',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ ツールチップ',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.jpn = {
|
||||||
"tt_dynt": "ツリーが拡大するにつれて自動的に増加",
|
"tt_dynt": "ツリーが拡大するにつれて自動的に増加",
|
||||||
"tt_wrap": "単語の折り返し",
|
"tt_wrap": "単語の折り返し",
|
||||||
"tt_hover": "ホバーすると溢れた線を表示する$N( マウスを押さない限りスクロールが中断されます $N カーソルは左余白です )",
|
"tt_hover": "ホバーすると溢れた線を表示する$N( マウスを押さない限りスクロールが中断されます $N カーソルは左余白です )",
|
||||||
"tt_gauto": "フォルダー内容に応じてグリッドまたはリスト表示", //m
|
|
||||||
"tt_gathr": "この割合のファイルが画像/動画ならグリッドを使用", //m
|
|
||||||
|
|
||||||
"ml_pmode": "フォルダの末尾...",
|
"ml_pmode": "フォルダの末尾...",
|
||||||
"ml_btns": "コマンド",
|
"ml_btns": "コマンド",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.kor = {
|
||||||
"cl_hpick": "아래 테이블에서 숨기고 싶은 열의 헤더를 탭하세요",
|
"cl_hpick": "아래 테이블에서 숨기고 싶은 열의 헤더를 탭하세요",
|
||||||
"cl_hcancel": "열 숨기기가 중단되었습니다",
|
"cl_hcancel": "열 숨기기가 중단되었습니다",
|
||||||
"cl_rcm": "우클릭 메뉴", //m
|
"cl_rcm": "우클릭 메뉴", //m
|
||||||
"cl_gauto": "자동 田", //m
|
|
||||||
|
|
||||||
"ct_grid": "田 그리드",
|
"ct_grid": "田 그리드",
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ 도움말',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ 도움말',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.kor = {
|
||||||
"tt_dynt": "트리가 확장될 때 자동으로 너비 증가",
|
"tt_dynt": "트리가 확장될 때 자동으로 너비 증가",
|
||||||
"tt_wrap": "자동 줄 바꿈",
|
"tt_wrap": "자동 줄 바꿈",
|
||||||
"tt_hover": "마우스를 올리면 넘어가는 줄 표시$N(마우스 커서가 왼쪽 여백에$N 있지 않으면 스크롤이 깨짐)",
|
"tt_hover": "마우스를 올리면 넘어가는 줄 표시$N(마우스 커서가 왼쪽 여백에$N 있지 않으면 스크롤이 깨짐)",
|
||||||
"tt_gauto": "폴더 내용에 따라 그리드 또는 목록으로 표시", //m
|
|
||||||
"tt_gathr": "파일 중 이 비율이 이미지/동영상이면 그리드 사용", //m
|
|
||||||
|
|
||||||
"ml_pmode": "폴더 끝에서...",
|
"ml_pmode": "폴더 끝에서...",
|
||||||
"ml_btns": "명령",
|
"ml_btns": "명령",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.nld = {
|
||||||
"cl_hpick": "Tik op de kolomkoppen om ze in de onderstaande tabel te verbergen",
|
"cl_hpick": "Tik op de kolomkoppen om ze in de onderstaande tabel te verbergen",
|
||||||
"cl_hcancel": "Kolumn verbergen geannuleerd",
|
"cl_hcancel": "Kolumn verbergen geannuleerd",
|
||||||
"cl_rcm": "Rechtermuisknopmenu", //m
|
"cl_rcm": "Rechtermuisknopmenu", //m
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 grid',
|
"ct_grid": '田 grid',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.nld = {
|
||||||
"tt_dynt": "Automatisch groeien naarmate de directoryboom zich uitbreidt",
|
"tt_dynt": "Automatisch groeien naarmate de directoryboom zich uitbreidt",
|
||||||
"tt_wrap": "Automatische terugloop",
|
"tt_wrap": "Automatische terugloop",
|
||||||
"tt_hover": "Laat overlopenden lijnen zien bij zweven$N(stopt het scrollen tenzij de muis in de linker gedeelte van het scherm is)",
|
"tt_hover": "Laat overlopenden lijnen zien bij zweven$N(stopt het scrollen tenzij de muis in de linker gedeelte van het scherm is)",
|
||||||
"tt_gauto": "weergeven als grid of lijst afhankelijk van mapinhoud", //m
|
|
||||||
"tt_gathr": "gebruik grid als dit percentage bestanden afbeeldingen/video's zijn", //m
|
|
||||||
|
|
||||||
"ml_pmode": "Aan het einde van de map...",
|
"ml_pmode": "Aan het einde van de map...",
|
||||||
"ml_btns": "Cmds",
|
"ml_btns": "Cmds",
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,6 @@ Ls.nno = {
|
||||||
"cl_hpick": "klikk på overskrifta åt kolonnene du ønskjer å skjule i tabellen nedanfor",
|
"cl_hpick": "klikk på overskrifta åt kolonnene du ønskjer å skjule i tabellen nedanfor",
|
||||||
"cl_hcancel": "kolonne-skjuling avbrote",
|
"cl_hcancel": "kolonne-skjuling avbrote",
|
||||||
"cl_rcm": "høgreklikkmeny",
|
"cl_rcm": "høgreklikkmeny",
|
||||||
"cl_gauto": "auto田",
|
|
||||||
|
|
||||||
"ct_grid": '田 ikon',
|
"ct_grid": '田 ikon',
|
||||||
"ct_ttips": 'vis hjelpetekst ved å holde musa over ting">ℹ️ tips',
|
"ct_ttips": 'vis hjelpetekst ved å holde musa over ting">ℹ️ tips',
|
||||||
|
|
@ -273,8 +272,6 @@ Ls.nno = {
|
||||||
"tt_dynt": "øk bredda på panelet ettersom treet utvider seg",
|
"tt_dynt": "øk bredda på panelet ettersom treet utvider seg",
|
||||||
"tt_wrap": "linjebryting",
|
"tt_wrap": "linjebryting",
|
||||||
"tt_hover": "vis heile mappenamnet når musepeikaren treff mappa$N( gjer diverre at scrollhjulet fusker dersom musepeikaren ikkje finn seg i grøfta )",
|
"tt_hover": "vis heile mappenamnet når musepeikaren treff mappa$N( gjer diverre at scrollhjulet fusker dersom musepeikaren ikkje finn seg i grøfta )",
|
||||||
"tt_gauto": "byt visingsmodus (liste/ikon) avhengig av mappeinnhald",
|
|
||||||
"tt_gathr": "vis som ikon når denne prosentdelen er bilete/videoar",
|
|
||||||
|
|
||||||
"ml_pmode": "ved enden av mappa",
|
"ml_pmode": "ved enden av mappa",
|
||||||
"ml_btns": "knapper",
|
"ml_btns": "knapper",
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,6 @@ Ls.nor = {
|
||||||
"cl_hpick": "klikk på overskriften til kolonnene du ønsker å skjule i tabellen nedenfor",
|
"cl_hpick": "klikk på overskriften til kolonnene du ønsker å skjule i tabellen nedenfor",
|
||||||
"cl_hcancel": "kolonne-skjuling avbrutt",
|
"cl_hcancel": "kolonne-skjuling avbrutt",
|
||||||
"cl_rcm": "høyreklikkmeny",
|
"cl_rcm": "høyreklikkmeny",
|
||||||
"cl_gauto": "auto田",
|
|
||||||
|
|
||||||
"ct_grid": '田 ikoner',
|
"ct_grid": '田 ikoner',
|
||||||
"ct_ttips": 'vis hjelpetekst ved å holde musen over ting">ℹ️ tips',
|
"ct_ttips": 'vis hjelpetekst ved å holde musen over ting">ℹ️ tips',
|
||||||
|
|
@ -273,8 +272,6 @@ Ls.nor = {
|
||||||
"tt_dynt": "øk bredden på panelet ettersom treet utvider seg",
|
"tt_dynt": "øk bredden på panelet ettersom treet utvider seg",
|
||||||
"tt_wrap": "linjebryting",
|
"tt_wrap": "linjebryting",
|
||||||
"tt_hover": "vis hele mappenavnet når musepekeren treffer mappen$N( gjør dessverre at scrollhjulet fusker dersom musepekeren ikke befinner seg i grøfta )",
|
"tt_hover": "vis hele mappenavnet når musepekeren treffer mappen$N( gjør dessverre at scrollhjulet fusker dersom musepekeren ikke befinner seg i grøfta )",
|
||||||
"tt_gauto": "bytt visningsmodus (liste/ikoner) avhengig av mappeinnhold",
|
|
||||||
"tt_gathr": "vis som ikoner når denne prosentandelen er bilder/videoer",
|
|
||||||
|
|
||||||
"ml_pmode": "ved enden av mappen",
|
"ml_pmode": "ved enden av mappen",
|
||||||
"ml_btns": "knapper",
|
"ml_btns": "knapper",
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,6 @@ Ls.pol = {
|
||||||
"cl_hpick": "kliknij nagłówki kolumn, aby ukryć je w tabeli niżej",
|
"cl_hpick": "kliknij nagłówki kolumn, aby ukryć je w tabeli niżej",
|
||||||
"cl_hcancel": "ukrywanie kolumn przerwane",
|
"cl_hcancel": "ukrywanie kolumn przerwane",
|
||||||
"cl_rcm": "menu kontekstowe", //m
|
"cl_rcm": "menu kontekstowe", //m
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 siatka',
|
"ct_grid": '田 siatka',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ podpowiedzi',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ podpowiedzi',
|
||||||
|
|
@ -279,8 +278,6 @@ Ls.pol = {
|
||||||
"tt_dynt": "rozszerzaj panel wraz z drzewem",
|
"tt_dynt": "rozszerzaj panel wraz z drzewem",
|
||||||
"tt_wrap": "zawijaj tekst",
|
"tt_wrap": "zawijaj tekst",
|
||||||
"tt_hover": "pokazuj za długie linie po najechaniu kursorem$N( psuje przewijanie gdy $N kursor nie jest w lewym marginesie )",
|
"tt_hover": "pokazuj za długie linie po najechaniu kursorem$N( psuje przewijanie gdy $N kursor nie jest w lewym marginesie )",
|
||||||
"tt_gauto": "wyświetl jako siatkę lub listę w zależności od zawartości folderu", //m
|
|
||||||
"tt_gathr": "użyj siatki, jeśli ten procent plików to obrazy/wideo", //m
|
|
||||||
|
|
||||||
"ml_pmode": "na końcu folderu...",
|
"ml_pmode": "na końcu folderu...",
|
||||||
"ml_btns": "komendy",
|
"ml_btns": "komendy",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.por = {
|
||||||
"cl_hpick": "toque nos cabeçalhos das colunas para ocultá-los na tabela abaixo",
|
"cl_hpick": "toque nos cabeçalhos das colunas para ocultá-los na tabela abaixo",
|
||||||
"cl_hcancel": "ocultar coluna abortado",
|
"cl_hcancel": "ocultar coluna abortado",
|
||||||
"cl_rcm": "menu de clique direito",
|
"cl_rcm": "menu de clique direito",
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 a grade',
|
"ct_grid": '田 a grade',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ dicas de ferramentas',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ dicas de ferramentas',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.por = {
|
||||||
"tt_dynt": "crescer automaticamente à medida que a árvore se expande",
|
"tt_dynt": "crescer automaticamente à medida que a árvore se expande",
|
||||||
"tt_wrap": "quebra de linha",
|
"tt_wrap": "quebra de linha",
|
||||||
"tt_hover": "revelar linhas transbordando ao passar o mouse$N( quebra a rolagem a menos que o cursor do mouse $N esteja na margem esquerda )",
|
"tt_hover": "revelar linhas transbordando ao passar o mouse$N( quebra a rolagem a menos que o cursor do mouse $N esteja na margem esquerda )",
|
||||||
"tt_gauto": "exibir como grade ou lista dependendo do conteúdo da pasta", //m
|
|
||||||
"tt_gathr": "usar grade se esta porcentagem de arquivos for imagens/vídeos", //m
|
|
||||||
|
|
||||||
"ml_pmode": "ao final da pasta...",
|
"ml_pmode": "ao final da pasta...",
|
||||||
"ml_btns": "comandos",
|
"ml_btns": "comandos",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.rus = {
|
||||||
"cl_hpick": "нажмите на заголовки столбцов, чтобы скрыть их в таблице ниже",
|
"cl_hpick": "нажмите на заголовки столбцов, чтобы скрыть их в таблице ниже",
|
||||||
"cl_hcancel": "скрытие столбца отменено",
|
"cl_hcancel": "скрытие столбца отменено",
|
||||||
"cl_rcm": "контекстное меню", //m
|
"cl_rcm": "контекстное меню", //m
|
||||||
"cl_gauto": "авто田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 сетка',
|
"ct_grid": '田 сетка',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ подсказки',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ подсказки',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.rus = {
|
||||||
"tt_dynt": "автоматическое расширение панели",
|
"tt_dynt": "автоматическое расширение панели",
|
||||||
"tt_wrap": "перенос слов",
|
"tt_wrap": "перенос слов",
|
||||||
"tt_hover": "раскрывать обрезанные строки при наведении$N( ломает скроллинг, если $N курсор не в пустоте слева )",
|
"tt_hover": "раскрывать обрезанные строки при наведении$N( ломает скроллинг, если $N курсор не в пустоте слева )",
|
||||||
"tt_gauto": "показывать как сетку или список в зависимости от содержимого папки", //m
|
|
||||||
"tt_gathr": "использовать сетку, если этот процент файлов — изображения/видео", //m
|
|
||||||
|
|
||||||
"ml_pmode": "в конце папки...",
|
"ml_pmode": "в конце папки...",
|
||||||
"ml_btns": "команды",
|
"ml_btns": "команды",
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,6 @@ Ls.spa = {
|
||||||
"cl_hpick": "toca en las cabeceras de columna para ocultarlas en la tabla de abajo",
|
"cl_hpick": "toca en las cabeceras de columna para ocultarlas en la tabla de abajo",
|
||||||
"cl_hcancel": "ocultación de columna cancelada",
|
"cl_hcancel": "ocultación de columna cancelada",
|
||||||
"cl_rcm": "menú contextual", //m
|
"cl_rcm": "menú contextual", //m
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 cuadrícula',
|
"ct_grid": '田 cuadrícula',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
|
||||||
|
|
@ -275,8 +274,6 @@ Ls.spa = {
|
||||||
"tt_dynt": "crecimiento automático a medida que el árbol se expande",
|
"tt_dynt": "crecimiento automático a medida que el árbol se expande",
|
||||||
"tt_wrap": "ajuste de línea",
|
"tt_wrap": "ajuste de línea",
|
||||||
"tt_hover": "revelar líneas que se desbordan al pasar el ratón$N( rompe el desplazamiento a menos que el $N cursor esté en el margen izquierdo )",
|
"tt_hover": "revelar líneas que se desbordan al pasar el ratón$N( rompe el desplazamiento a menos que el $N cursor esté en el margen izquierdo )",
|
||||||
"tt_gauto": "mostrar como cuadrícula o lista según el contenido de la carpeta", //m
|
|
||||||
"tt_gathr": "usar cuadrícula si este porcentaje de archivos son imágenes/videos", //m
|
|
||||||
|
|
||||||
"ml_pmode": "al final de la carpeta...",
|
"ml_pmode": "al final de la carpeta...",
|
||||||
"ml_btns": "acciones",
|
"ml_btns": "acciones",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.swe = {
|
||||||
"cl_hpick": "tryck på en kolumntitel för att dölja den i filvyn",
|
"cl_hpick": "tryck på en kolumntitel för att dölja den i filvyn",
|
||||||
"cl_hcancel": "kolumndöljning avbruten",
|
"cl_hcancel": "kolumndöljning avbruten",
|
||||||
"cl_rcm": "högerklicksmeny", //m
|
"cl_rcm": "högerklicksmeny", //m
|
||||||
"cl_gauto": "auto田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 rutnätet',
|
"ct_grid": '田 rutnätet',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ tips',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ tips',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.swe = {
|
||||||
"tt_dynt": "väx vyn när trädet expanderar",
|
"tt_dynt": "väx vyn när trädet expanderar",
|
||||||
"tt_wrap": "automatisk radbrytning",
|
"tt_wrap": "automatisk radbrytning",
|
||||||
"tt_hover": "visa överlånga rader när muspekaren hovrar över dem$N( skrollhjulet fungerar ej såvida inte pekaren$Nstår till vänster )",
|
"tt_hover": "visa överlånga rader när muspekaren hovrar över dem$N( skrollhjulet fungerar ej såvida inte pekaren$Nstår till vänster )",
|
||||||
"tt_gauto": "visa som rutnät eller lista beroende på mappens innehåll", //m
|
|
||||||
"tt_gathr": "använd rutnät om denna andel filer är bilder/videor", //m
|
|
||||||
|
|
||||||
"ml_pmode": "vid mappens slut...",
|
"ml_pmode": "vid mappens slut...",
|
||||||
"ml_btns": "komm.",
|
"ml_btns": "komm.",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.tur = {
|
||||||
"cl_hpick": "aşağıdaki tabloda gizlemek için sütun başlıklarına dokunun",
|
"cl_hpick": "aşağıdaki tabloda gizlemek için sütun başlıklarına dokunun",
|
||||||
"cl_hcancel": "sütun gizleme iptal edildi",
|
"cl_hcancel": "sütun gizleme iptal edildi",
|
||||||
"cl_rcm": "sağ tık menüsü", //m
|
"cl_rcm": "sağ tık menüsü", //m
|
||||||
"cl_gauto": "otomatik田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 ızgara',
|
"ct_grid": '田 ızgara',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ ipuçları',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ ipuçları',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.tur = {
|
||||||
"tt_dynt": "ağaç genişledikçe otomatik büyüt",
|
"tt_dynt": "ağaç genişledikçe otomatik büyüt",
|
||||||
"tt_wrap": "kelime sarma",
|
"tt_wrap": "kelime sarma",
|
||||||
"tt_hover": "fare ile üzerine gelindiğinde taşan satırları göster$N( fare imleci sol kenarda değilse kaydırmayı bozar )",
|
"tt_hover": "fare ile üzerine gelindiğinde taşan satırları göster$N( fare imleci sol kenarda değilse kaydırmayı bozar )",
|
||||||
"tt_gauto": "klasör içeriğine bağlı olarak ızgara veya liste olarak göster", //m
|
|
||||||
"tt_gathr": "dosyaların bu yüzdesi resim/video ise ızgara kullan", //m
|
|
||||||
|
|
||||||
"ml_pmode": "klasör sonunda...",
|
"ml_pmode": "klasör sonunda...",
|
||||||
"ml_btns": "komutlar",
|
"ml_btns": "komutlar",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.ukr = {
|
||||||
"cl_hpick": "натисніть на заголовки стовпців, щоб приховати їх у таблиці нижче",
|
"cl_hpick": "натисніть на заголовки стовпців, щоб приховати їх у таблиці нижче",
|
||||||
"cl_hcancel": "приховання стовпців скасовано",
|
"cl_hcancel": "приховання стовпців скасовано",
|
||||||
"cl_rcm": "контекстне меню", //m
|
"cl_rcm": "контекстне меню", //m
|
||||||
"cl_gauto": "авто田", //m
|
|
||||||
|
|
||||||
"ct_grid": '田 сітка',
|
"ct_grid": '田 сітка',
|
||||||
"ct_ttips": '◔ ◡ ◔">ℹ️ підказки',
|
"ct_ttips": '◔ ◡ ◔">ℹ️ підказки',
|
||||||
|
|
@ -276,8 +275,6 @@ Ls.ukr = {
|
||||||
"tt_dynt": "автоматично збільшуватися при розширенні дерева",
|
"tt_dynt": "автоматично збільшуватися при розширенні дерева",
|
||||||
"tt_wrap": "перенесення слів",
|
"tt_wrap": "перенесення слів",
|
||||||
"tt_hover": "показувати переповнені рядки при наведенні$N( порушує прокрутку, якщо курсор $N миші не знаходиться в лівому відступі )",
|
"tt_hover": "показувати переповнені рядки при наведенні$N( порушує прокрутку, якщо курсор $N миші не знаходиться в лівому відступі )",
|
||||||
"tt_gauto": "показувати як сітку або список залежно від вмісту папки", //m
|
|
||||||
"tt_gathr": "використовувати сітку, якщо цей відсоток файлів — зображення/відео", //m
|
|
||||||
|
|
||||||
"ml_pmode": "в кінці папки...",
|
"ml_pmode": "в кінці папки...",
|
||||||
"ml_btns": "команди",
|
"ml_btns": "команди",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,6 @@ Ls.vie = {
|
||||||
"cl_hpick": "chạm vào tiêu đề cột để ẩn trong bảng bên dưới",
|
"cl_hpick": "chạm vào tiêu đề cột để ẩn trong bảng bên dưới",
|
||||||
"cl_hcancel": "đã hủy việc ẩn cột",
|
"cl_hcancel": "đã hủy việc ẩn cột",
|
||||||
"cl_rcm": "menu chuột phải", //m
|
"cl_rcm": "menu chuột phải", //m
|
||||||
"cl_gauto": "lưới tự động", //m
|
|
||||||
|
|
||||||
// settings / tuỳ chọn
|
// settings / tuỳ chọn
|
||||||
"ct_grid": '田 chế độ lưới',
|
"ct_grid": '田 chế độ lưới',
|
||||||
|
|
@ -280,8 +279,6 @@ Ls.vie = {
|
||||||
"tt_dynt": "tự mở rộng khi cây mở rộng",
|
"tt_dynt": "tự mở rộng khi cây mở rộng",
|
||||||
"tt_wrap": "ngắt dòng",
|
"tt_wrap": "ngắt dòng",
|
||||||
"tt_hover": "hiện thị dòng tràn khi rê chuột$N( không cuộn được nếu $N con trỏ chuột nằm ngoài cột trái )",
|
"tt_hover": "hiện thị dòng tràn khi rê chuột$N( không cuộn được nếu $N con trỏ chuột nằm ngoài cột trái )",
|
||||||
"tt_gauto": "hiển thị dạng lưới hoặc danh sách tùy theo nội dung thư mục", //m
|
|
||||||
"tt_gathr": "dùng lưới nếu tỷ lệ tệp này là ảnh/video", //m
|
|
||||||
|
|
||||||
"ml_pmode": "ở cuối thư mục...",
|
"ml_pmode": "ở cuối thư mục...",
|
||||||
"ml_btns": "lệnh",
|
"ml_btns": "lệnh",
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,6 @@ html.y #tth {
|
||||||
margin: .1em auto;
|
margin: .1em auto;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 60%;
|
height: 60%;
|
||||||
max-height: 22em;
|
|
||||||
background: #999;
|
background: #999;
|
||||||
background: rgba(128,128,128,0.2);
|
background: rgba(128,128,128,0.2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,3 @@
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
||||||
# 2026-0308-2106 `v1.20.11` what? nohtml is evolving!
|
|
||||||
|
|
||||||
## ⚠️ ATTN: this release fixes a vulnerability
|
|
||||||
|
|
||||||
[GHSA-m6hv-x64c-27mm](https://github.com/9001/copyparty/security/advisories/GHSA-m6hv-x64c-27mm) the `nohtml` volflag did not prevent javascript inside SVG images from executing -- a malicious user with write-access could upload an SVG file which would execute as javascript when someone opens it 1c9f894e
|
|
||||||
|
|
||||||
## 🧪 new features
|
|
||||||
|
|
||||||
* version-checker (thx @icxes!) c6965f06
|
|
||||||
* default-disabled; you must [choose a URL](https://github.com/9001/copyparty/#version-checker) to grab security advisories from to enable it
|
|
||||||
* periodically checks the security advisories and shows a warning in the controlpanel if you're running a vulnerable version
|
|
||||||
* can optionally panic and shutdown the server if you prefer that
|
|
||||||
* man, the timing on this though... absolute cinema
|
|
||||||
|
|
||||||
## 🩹 bugfixes
|
|
||||||
|
|
||||||
* fix `nohtml` not being aware that SVG images can execute javascript 1c9f894e
|
|
||||||
* a new volflag [noscript](https://github.com/9001/copyparty/#security) was also added; `nohtml` will automatically enable `noscript`, but `noscript` can also be useful on its own; see readme
|
|
||||||
* various [upload rules](https://github.com/9001/copyparty/#upload-rules) fixes:
|
|
||||||
* #1335 `rotf` couldn't handle trailing slash (thx @NecRaul!) 8e20506d
|
|
||||||
* #1337 `rotn` didn't always count correctly (thx @NecRaul!) 23d4a62e
|
|
||||||
* `rotn` didn't apply to dupes 00e821db
|
|
||||||
* combining [rp-loc](https://copyparty.eu/cli/#g-rp-loc) and [site](https://copyparty.eu/cli/#g-site) was a bit jank (thx @new-sashok724!) 31b23843
|
|
||||||
* global-option [idp-store: 2](https://copyparty.eu/cli/#g-idp-store) would result in excessive config reloading 1272de9d
|
|
||||||
* fix fd-leak when indexing certain compressed files, including epub books 8b5ac23e
|
|
||||||
* [forget-ip](https://copyparty.eu/cli/#g-forget-ip): fix sqlite cursor-locking 37123e33
|
|
||||||
|
|
||||||
## 🔧 other changes
|
|
||||||
|
|
||||||
* #1316 Chinese translation got a huge makeover (thx @satgo1546 and @lxdlam!) b0152741
|
|
||||||
* #1324 better rclone advice on the connect-page 8941701a
|
|
||||||
* static website resources, previously served from `/.cpr/` have moved to `/.cpr/w/` for easier configuration of allowlists in reverseproxies and authentication middlewares 753ff548
|
|
||||||
|
|
||||||
## 🌠 fun facts
|
|
||||||
|
|
||||||
* according to [the SVG spec](https://www.w3.org/TR/SVG11/script.html), images being able to execute javascript is a feature and intentional behavior... what a concept!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2026-0225-1533 `v1.20.10` fix login (ノ ゚ヮ゚)ノ ~┻━┻
|
# 2026-0225-1533 `v1.20.10` fix login (ノ ゚ヮ゚)ノ ~┻━┻
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -359,48 +359,6 @@ for the `re`pack to work, first run one of the sfx'es once to unpack it
|
||||||
**note:** you can also just download and run [/scripts/copyparty-repack.sh](https://github.com/9001/copyparty/blob/hovudstraum/scripts/copyparty-repack.sh) -- this will grab the latest copyparty release from github and do a few repacks; works on linux/macos (and windows with msys2 or WSL)
|
**note:** you can also just download and run [/scripts/copyparty-repack.sh](https://github.com/9001/copyparty/blob/hovudstraum/scripts/copyparty-repack.sh) -- this will grab the latest copyparty release from github and do a few repacks; works on linux/macos (and windows with msys2 or WSL)
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
|
|
||||||
## vendored dependencies
|
|
||||||
|
|
||||||
some third-party code has been vendored into the git repo; some for convenience, some because they have been lightly hacked to fit copyparty's usecase better:
|
|
||||||
|
|
||||||
* inside the folder [/copyparty/stolen](https://github.com/9001/copyparty/tree/hovudstraum/copyparty/stolen) is python-libraries which runs on the serverside:
|
|
||||||
* `surrogateescape.py` (BSD2) can be removed; only needed for python2 support
|
|
||||||
* `qrcodegen.py` (MIT) can be removed and replaced with a systemwide install of the original [qrcodegen.py](https://github.com/nayuki/QR-Code-generator/blob/daa3114/python/qrcodegen.py);
|
|
||||||
* modifications: removed code/features that copyparty does not need/use
|
|
||||||
* `ifaddr` (BSD2) can be removed and replaced with a systemwide install of the original [ifaddr](https://github.com/ifaddr/ifaddr);
|
|
||||||
* modifications: support python2, support s390x / irix32 / graal
|
|
||||||
* `dnslib` (MIT) may be deleted and replaced with a systemwide install of the original [dnslib](https://github.com/paulc/dnslib/), HOWEVER:
|
|
||||||
* will cause problems for mDNS in some network environments; 6c1cf68bca7376c6291c3cfe710ebd5bd5ed3e6c + 94d1924fa97e5faaf1ebfd85cae73faebcb89fa1
|
|
||||||
|
|
||||||
* inside the folder `/copyparty/web/deps` (only in distributed archives/builds) is [fuse.py](https://github.com/fusepy/fusepy/blob/master/fuse.py), to make it downloadable from the connect-page on the web-ui
|
|
||||||
|
|
||||||
* inside the folder `/copyparty/web` (only in distributed archives/builds) is a collection of javascript libraries (produced by [deps-docker](https://github.com/9001/copyparty/tree/hovudstraum/scripts/deps-docker)) which are used clientside by the web-UI:
|
|
||||||
* [marked.js](https://github.com/markedjs/marked/releases) (MIT) powers the markdown editor, and has been [patched](https://github.com/9001/copyparty/blob/hovudstraum/scripts/deps-docker/marked-ln.patch) to include the line-numbers of each input line, to enable scroll-sync between the editor and the preview-pane. This patch is [not strictly necessary anymore](https://github.com/markedjs/marked/issues/2134) but I haven't gotten around to making the change yet
|
|
||||||
* [easyMDE](https://github.com/Ionaru/easy-markdown-editor/) (MIT), the alternative markdown editor, has the same [patch](https://github.com/9001/copyparty/blob/hovudstraum/scripts/deps-docker/easymde-ln.patch) to enable scroll-sync, and also some [size-golfing](https://github.com/9001/copyparty/blob/hovudstraum/scripts/deps-docker/easymde.patch)
|
|
||||||
* [codemirror5](https://github.com/codemirror/codemirror5/) (MIT) has no noteworthy changes, and has only been [size-golfed](https://github.com/9001/copyparty/blob/hovudstraum/scripts/deps-docker/codemirror.patch), could have been used as-is
|
|
||||||
* [DOMPurify](https://github.com/cure53/DOMPurify) (Apache2) is used as-is
|
|
||||||
* [hash-wasm](https://github.com/Daninet/hash-wasm/) (MIT) is used entirely as-is
|
|
||||||
* [asmcrypto.js](https://github.com/openpgpjs/asmcrypto.js/) (MIT) is abandoned software, and used almost as-is (slightly golfed for size); it is probably fine to exclude/remove this, since it will only break support for uploading from really old browsers (IE10/IE11) using up2k (the "fancy uploader")
|
|
||||||
* [prism.js](https://github.com/PrismJS/prism/) (MIT) is built with a [selection of languages](https://github.com/9001/copyparty/blob/hovudstraum/scripts/deps-docker/genprism.py); there is an assumption about the exact subset of languages elsewhere in copyparty, but there shouldn't be any big consequences of replacing it with a different build if that exists in Fedora
|
|
||||||
* an old version of [SourceCodePro](https://github.com/adobe-fonts/source-code-pro) (OFL-1.1), is size-reduced to [only the necessary characters](https://github.com/9001/copyparty/blob/41ed559faabdc180efc37fd027e7f1bb2d14d174/scripts/deps-docker/mini-fa.sh#L30-L31). There will be subtle layout issues if this is replaced with a newer version, because they changed some line-heights or something in later versions, but shouldn't be a big issue
|
|
||||||
* an old version of [font-awesome](https://github.com/FortAwesome/Font-Awesome) (OFL-1.1), size-reduced to [only the necessary icons](https://github.com/9001/copyparty/blob/hovudstraum/scripts/deps-docker/mini-fa.sh). I believe a newer version should also work.
|
|
||||||
|
|
||||||
## optional dependencies
|
|
||||||
|
|
||||||
explained in the [main readme](https://github.com/9001/copyparty/tree/hovudstraum#optional-dependencies), but a quick recap:
|
|
||||||
|
|
||||||
* recommended python libraries: `argon2-cffi paramiko pyftpdlib pyopenssl pillow rawpy pyzmq` [python-magic](https://pypi.org/project/python-magic/)
|
|
||||||
* only recommended on Windows: `psutil` (not very useful on Linux)
|
|
||||||
* NOT recommended: `impacket` because the feature it enables is a security nightmare
|
|
||||||
* NOT recommended: `mutagen` because ffmpeg produces better results (albeit slower)
|
|
||||||
* NOT recommended: `pyvips` because converting to jxl is extremely RAM-heavy
|
|
||||||
* NOT recommended: `pillow-heif` due to [legal reasons](https://github.com/9001/copyparty/blob/hovudstraum/docs/bad-codecs.md)
|
|
||||||
* recommended programs: `ffmpeg ffprobe cfssl cfssljson cfssl-certinfo`
|
|
||||||
* FFmpeg powers audio transcoding, and thumbnails of formats not covered by pillow/pyvips
|
|
||||||
|
|
||||||
|
|
||||||
# building
|
# building
|
||||||
|
|
||||||
## dev env setup
|
## dev env setup
|
||||||
|
|
|
||||||
|
|
@ -38,5 +38,3 @@
|
||||||
accs:
|
accs:
|
||||||
rw: * # everyone gets read-write access, but
|
rw: * # everyone gets read-write access, but
|
||||||
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
||||||
flags:
|
|
||||||
e2ds # enable filesystem-scanning for this volume only
|
|
||||||
144
docs/versus.md
144
docs/versus.md
|
|
@ -86,33 +86,33 @@ the table headers in the matrixes below are the different softwares, with a quic
|
||||||
|
|
||||||
the softwares,
|
the softwares,
|
||||||
|
|
||||||
[C]: https://github.com/9001/copyparty "copyparty"
|
[a]: https://github.com/9001/copyparty "copyparty"
|
||||||
[h2]: https://github.com/rejetto/hfs2/ "hfs2"
|
[b]: https://github.com/rejetto/hfs2/ "hfs2"
|
||||||
[h3]: https://rejetto.com/hfs/ "hfs3"
|
[c]: https://rejetto.com/hfs/ "hfs3"
|
||||||
[nc]: https://github.com/nextcloud/server "nextcloud"
|
[d]: https://github.com/nextcloud/server "nextcloud"
|
||||||
[sf]: https://github.com/haiwen/seafile "seafile"
|
[e]: https://github.com/haiwen/seafile "seafile"
|
||||||
[rc]: https://github.com/rclone/rclone "rclone"
|
[f]: https://github.com/rclone/rclone "rclone"
|
||||||
[df]: https://github.com/sigoden/dufs "dufs"
|
[g]: https://github.com/sigoden/dufs "dufs"
|
||||||
[cs]: https://github.com/chibisafe/chibisafe "chibisafe"
|
[h]: https://github.com/chibisafe/chibisafe "chibisafe"
|
||||||
[kb]: https://github.com/kalcaddle/kodbox "kodbox"
|
[i]: https://github.com/kalcaddle/kodbox "kodbox"
|
||||||
[fb]: https://github.com/filebrowser/filebrowser "filebrowser"
|
[j]: https://github.com/filebrowser/filebrowser "filebrowser"
|
||||||
[fg]: https://github.com/filegator/filegator "filegator"
|
[k]: https://github.com/filegator/filegator "filegator"
|
||||||
[sg]: https://github.com/drakkan/sftpgo "sftpgo"
|
[l]: https://github.com/drakkan/sftpgo "sftpgo"
|
||||||
[az]: https://github.com/tobychui/arozos "arozos"
|
[m]: https://github.com/tobychui/arozos "arozos"
|
||||||
|
|
||||||
* `C` = [copyparty][C]
|
* `a` = [copyparty][a]
|
||||||
* `h2` = [hfs2][h2] 🔥
|
* `b` = [hfs2][b] 🔥
|
||||||
* `h3` = [hfs3][h3]
|
* `c` = [hfs3][c]
|
||||||
* `nc` = [nextcloud][nc]
|
* `d` = [nextcloud][d]
|
||||||
* `sf` = [seafile][sf]
|
* `e` = [seafile][e]
|
||||||
* `rc` = [rclone][rc], specifically `rclone serve webdav .`
|
* `f` = [rclone][f], specifically `rclone serve webdav .`
|
||||||
* `df` = [dufs][df]
|
* `g` = [dufs][g]
|
||||||
* `cs` = [chibisafe][cs]
|
* `h` = [chibisafe][h]
|
||||||
* `kb` = [kodbox][kb]
|
* `i` = [kodbox][i]
|
||||||
* `fb` = [filebrowser][fb]
|
* `j` = [filebrowser][j]
|
||||||
* `fg` = [filegator][fg]
|
* `k` = [filegator][k]
|
||||||
* `sg` = [sftpgo][sg]
|
* `l` = [sftpgo][l]
|
||||||
* `az` = [arozos][az]
|
* `m` = [arozos][m]
|
||||||
|
|
||||||
some softwares not in the matrixes,
|
some softwares not in the matrixes,
|
||||||
* [updog](#updog)
|
* [updog](#updog)
|
||||||
|
|
@ -134,8 +134,8 @@ symbol legend,
|
||||||
|
|
||||||
## general
|
## general
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ----------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| intuitive UX | | ╱ | █ | █ | █ | | █ | █ | █ | █ | █ | █ | █ |
|
| intuitive UX | | ╱ | █ | █ | █ | | █ | █ | █ | █ | █ | █ | █ |
|
||||||
| config GUI | | █ | █ | █ | █ | | | █ | █ | █ | | █ | █ |
|
| config GUI | | █ | █ | █ | █ | | | █ | █ | █ | | █ | █ |
|
||||||
| good documentation | | | █ | █ | █ | █ | █ | | | █ | █ | ╱ | ╱ |
|
| good documentation | | | █ | █ | █ | █ | █ | | | █ | █ | ╱ | ╱ |
|
||||||
|
|
@ -155,28 +155,28 @@ symbol legend,
|
||||||
| iOS app | ╱ | | | █ | █ | | | | | | | | |
|
| iOS app | ╱ | | | █ | █ | | | | | | | | |
|
||||||
|
|
||||||
* `zero setup` = you can get a mostly working setup by just launching the app, without having to install any software or configure whatever
|
* `zero setup` = you can get a mostly working setup by just launching the app, without having to install any software or configure whatever
|
||||||
* `C`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* no gui for server settings; only for client-side stuff
|
* no gui for server settings; only for client-side stuff
|
||||||
* runs on iOS / iPads using [a-Shell](https://holzschu.github.io/a-Shell_iOS/) (pretty good) or [iSH](https://ish.app/) (very slow) but cannot run in the background and is not able to share all of your phone storage (just a separate dedicated folder)
|
* runs on iOS / iPads using [a-Shell](https://holzschu.github.io/a-Shell_iOS/) (pretty good) or [iSH](https://ish.app/) (very slow) but cannot run in the background and is not able to share all of your phone storage (just a separate dedicated folder)
|
||||||
* [android app](https://f-droid.org/en/packages/me.ocv.partyup/) is for uploading only
|
* [android app](https://f-droid.org/en/packages/me.ocv.partyup/) is for uploading only
|
||||||
* no iOS app but has [shortcuts](https://github.com/9001/copyparty#ios-shortcuts) for easy uploading
|
* no iOS app but has [shortcuts](https://github.com/9001/copyparty#ios-shortcuts) for easy uploading
|
||||||
* validated on aarch64-BE by [Øl Telecom](http://ol-tele.com/) during eth0:2025; [photo1](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/aallwinner.jpg?cache) and [diploma](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/be-ready.png?cache)
|
* validated on aarch64-BE by [Øl Telecom](http://ol-tele.com/) during eth0:2025; [photo1](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/aallwinner.jpg?cache) and [diploma](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/be-ready.png?cache)
|
||||||
* validated on [SGI IRIX](https://en.wikipedia.org/wiki/IRIX) ([an O2](https://en.wikipedia.org/wiki/SGI_O2)) by [Øl Telecom](http://ol-tele.com/) during 39c3; [photo1](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/sgi-o2.jpg?cache) and [screenshot](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/sgi-o2.png?cache)
|
* validated on [SGI IRIX](https://en.wikipedia.org/wiki/IRIX) ([an O2](https://en.wikipedia.org/wiki/SGI_O2)) by [Øl Telecom](http://ol-tele.com/) during 39c3; [photo1](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/sgi-o2.jpg?cache) and [screenshot](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/sgi-o2.png?cache)
|
||||||
* `h2`/hfs2 runs on linux through wine
|
* `b`/hfs2 runs on linux through wine
|
||||||
* `rc`/rclone must be started with the command `rclone serve webdav .` or similar
|
* `f`/rclone must be started with the command `rclone serve webdav .` or similar
|
||||||
* `cs`/chibisafe has undocumented windows support
|
* `h`/chibisafe has undocumented windows support
|
||||||
* `sg`/sftpgo:
|
* `l`/sftpgo:
|
||||||
* Must be launched with a command
|
* Must be launched with a command
|
||||||
* On Termux, just run `pkg in sftpgo`
|
* On Termux, just run `pkg in sftpgo`
|
||||||
* `az`/arozos has partial windows support
|
* `m`/arozos has partial windows support
|
||||||
|
|
||||||
|
|
||||||
## file transfer
|
## file transfer
|
||||||
|
|
||||||
*the thing that copyparty is actually kinda good at*
|
*the thing that copyparty is actually kinda good at*
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ----------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| download folder as zip | █ | █ | █ | █ | ╱ | | █ | | █ | █ | ╱ | █ | ╱ |
|
| download folder as zip | █ | █ | █ | █ | ╱ | | █ | | █ | █ | ╱ | █ | ╱ |
|
||||||
| download folder as tar | █ | | | | | | | | | | | | |
|
| download folder as tar | █ | | | | | | | | | | | | |
|
||||||
| upload | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | ╱ | █ | █ |
|
| upload | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | ╱ | █ | █ |
|
||||||
|
|
@ -224,23 +224,23 @@ symbol legend,
|
||||||
|
|
||||||
* `cloud storage backend` = able to serve files from (and write to) s3 or similar cloud services; `╱` means the software can do this with some help from `rclone mount` as a bridge
|
* `cloud storage backend` = able to serve files from (and write to) s3 or similar cloud services; `╱` means the software can do this with some help from `rclone mount` as a bridge
|
||||||
|
|
||||||
* `C`/copyparty can reject uploaded files (based on complex conditions), for example [by extension](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reject-extension.py) or [mimetype](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reject-mimetype.py)
|
* `a`/copyparty can reject uploaded files (based on complex conditions), for example [by extension](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reject-extension.py) or [mimetype](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reject-mimetype.py)
|
||||||
* `sf`/seafile download-as-zip is not streaming; it creates the full zipfile before download can start, and fails on big folders
|
* `e`/seafile download-as-zip is not streaming; it creates the full zipfile before download can start, and fails on big folders
|
||||||
* `fg`/filebrowser remarks:
|
* `j`/filebrowser remarks:
|
||||||
* can provide checksums for single files on request
|
* can provide checksums for single files on request
|
||||||
* can probably do extension/mimetype rejection similar to copyparty
|
* can probably do extension/mimetype rejection similar to copyparty
|
||||||
* `fg`/filegator download-as-zip is not streaming; it creates the full zipfile before download can start
|
* `k`/filegator download-as-zip is not streaming; it creates the full zipfile before download can start
|
||||||
* `sg`/sftpgo:
|
* `l`/sftpgo:
|
||||||
* resumable/segmented uploads only over SFTP, not over HTTP
|
* resumable/segmented uploads only over SFTP, not over HTTP
|
||||||
* upload rules are totals only, not over time
|
* upload rules are totals only, not over time
|
||||||
* can probably do extension/mimetype rejection similar to copyparty
|
* can probably do extension/mimetype rejection similar to copyparty
|
||||||
* `az`/arozos download-as-zip is not streaming; it creates the full zipfile before download can start, and fails on big folders
|
* `m`/arozos download-as-zip is not streaming; it creates the full zipfile before download can start, and fails on big folders
|
||||||
|
|
||||||
|
|
||||||
## protocols and client support
|
## protocols and client support
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ----------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| serve https | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
| serve https | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
||||||
| serve webdav | █ | | | █ | █ | █ | █ | | █ | | | █ | █ |
|
| serve webdav | █ | | | █ | █ | █ | █ | | █ | | | █ | █ |
|
||||||
| serve ftp (tcp) | █ | | | | | █ | | | | | | █ | █ |
|
| serve ftp (tcp) | █ | | | | | █ | | | | | | █ | █ |
|
||||||
|
|
@ -261,17 +261,17 @@ symbol legend,
|
||||||
* `mojibake filenames` = filenames decoded with the wrong codec and then reencoded (usually to utf-8), so `宇多田ヒカル` might look like `ëFæ╜ôcâqâJâï`
|
* `mojibake filenames` = filenames decoded with the wrong codec and then reencoded (usually to utf-8), so `宇多田ヒカル` might look like `ëFæ╜ôcâqâJâï`
|
||||||
* `undecodable filenames` = pure binary garbage which cannot be parsed as utf-8
|
* `undecodable filenames` = pure binary garbage which cannot be parsed as utf-8
|
||||||
* you can successfully play `$'\355\221'` with mpv through mounting a remote copyparty server with rclone, pog
|
* you can successfully play `$'\355\221'` with mpv through mounting a remote copyparty server with rclone, pog
|
||||||
* `C`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* extremely minimal samba/cifs server
|
* extremely minimal samba/cifs server
|
||||||
* netscape 4 / ie6 support is mostly listed as a joke altho some people have actually found it useful ([ie4 tho](https://user-images.githubusercontent.com/241032/118192791-fb31fe00-b446-11eb-9647-898ea8efc1f7.png))
|
* netscape 4 / ie6 support is mostly listed as a joke altho some people have actually found it useful ([ie4 tho](https://user-images.githubusercontent.com/241032/118192791-fb31fe00-b446-11eb-9647-898ea8efc1f7.png))
|
||||||
* `sg`/sftpgo translates mojibake filenames into valid utf-8 (information loss)
|
* `l`/sftpgo translates mojibake filenames into valid utf-8 (information loss)
|
||||||
* `az`/arozos has readonly-support for older browsers; no uploading
|
* `m`/arozos has readonly-support for older browsers; no uploading
|
||||||
|
|
||||||
|
|
||||||
## server configuration
|
## server configuration
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ----------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| config from cmd args | █ | | █ | | | █ | █ | | | █ | | ╱ | ╱ |
|
| config from cmd args | █ | | █ | | | █ | █ | | | █ | | ╱ | ╱ |
|
||||||
| config files | █ | █ | █ | ╱ | ╱ | █ | | █ | | █ | • | ╱ | ╱ |
|
| config files | █ | █ | █ | ╱ | ╱ | █ | | █ | | █ | • | ╱ | ╱ |
|
||||||
| runtime config reload | █ | █ | █ | | | | | █ | █ | █ | █ | | █ |
|
| runtime config reload | █ | █ | █ | | | | | █ | █ | █ | █ | | █ |
|
||||||
|
|
@ -282,17 +282,17 @@ symbol legend,
|
||||||
| folder-rproxy ok | █ | | █ | | █ | █ | | • | • | █ | • | | • |
|
| folder-rproxy ok | █ | | █ | | █ | █ | | • | • | █ | • | | • |
|
||||||
|
|
||||||
* `folder-rproxy` = reverse-proxying without dedicating an entire (sub)domain, using a subfolder instead
|
* `folder-rproxy` = reverse-proxying without dedicating an entire (sub)domain, using a subfolder instead
|
||||||
* `sg`/sftpgo:
|
* `l`/sftpgo:
|
||||||
* config: user can be added by cmd command in [Portable mode](https://docs.sftpgo.com/2.6/cli/#portable-mode); if not in Portable mode users must be added through gui / api calls
|
* config: user can be added by cmd command in [Portable mode](https://docs.sftpgo.com/2.6/cli/#portable-mode); if not in Portable mode users must be added through gui / api calls
|
||||||
* `az`/arozos:
|
* `m`/arozos:
|
||||||
* configuration is primarily through GUI
|
* configuration is primarily through GUI
|
||||||
* reverse-proxy is not guaranteed to see the correct client IP
|
* reverse-proxy is not guaranteed to see the correct client IP
|
||||||
|
|
||||||
|
|
||||||
## server capabilities
|
## server capabilities
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ----------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| accounts | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
| accounts | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
||||||
| per-account chroot | | | | | | | | | | | | █ | |
|
| per-account chroot | | | | | | | | | | | | █ | |
|
||||||
| single-sign-on | ╱ | | | █ | █ | | | | • | | | | |
|
| single-sign-on | ╱ | | | █ | █ | | | | • | | | | |
|
||||||
|
|
@ -337,7 +337,7 @@ symbol legend,
|
||||||
* `speed throttle` = rate limiting (per ip, per user, per connection, anything like that)
|
* `speed throttle` = rate limiting (per ip, per user, per connection, anything like that)
|
||||||
* `curl-friendly ls` = returns a [sortable plaintext folder listing](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png) when curled
|
* `curl-friendly ls` = returns a [sortable plaintext folder listing](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png) when curled
|
||||||
* `curl-friendly upload` = uploading with curl is just `curl -T some.bin http://.../`
|
* `curl-friendly upload` = uploading with curl is just `curl -T some.bin http://.../`
|
||||||
* `C`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* single-sign-on, token-auth, and 2fa is *possible* through authelia/authentik or similar, but nobody's made an example yet
|
* single-sign-on, token-auth, and 2fa is *possible* through authelia/authentik or similar, but nobody's made an example yet
|
||||||
* one-way folder sync from local to server can be done efficiently with [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy), or with webdav and conventional rsync
|
* one-way folder sync from local to server can be done efficiently with [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy), or with webdav and conventional rsync
|
||||||
* can hot-reload config files (with just a few exceptions)
|
* can hot-reload config files (with just a few exceptions)
|
||||||
|
|
@ -346,21 +346,21 @@ symbol legend,
|
||||||
* [version-checker](https://github.com/9001/copyparty/#version-checker) can check if the current version has a known vulnerability and immediately exit/shutdown, but automatic self-updating is **not** available
|
* [version-checker](https://github.com/9001/copyparty/#version-checker) can check if the current version has a known vulnerability and immediately exit/shutdown, but automatic self-updating is **not** available
|
||||||
* [event hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) ([discord](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png), [desktop](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png)) inspired by filebrowser, as well as the more complex [media parser](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag) alternative
|
* [event hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) ([discord](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png), [desktop](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png)) inspired by filebrowser, as well as the more complex [media parser](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag) alternative
|
||||||
* upload history can be visualized using [partyjournal](https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py)
|
* upload history can be visualized using [partyjournal](https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py)
|
||||||
* `fg`/filegator remarks:
|
* `k`/filegator remarks:
|
||||||
* `per-* permissions` -- can limit a user to one folder and its subfolders
|
* `per-* permissions` -- can limit a user to one folder and its subfolders
|
||||||
* `unmap subfolders` -- can globally filter a list of paths
|
* `unmap subfolders` -- can globally filter a list of paths
|
||||||
* `sg`/sftpgo:
|
* `l`/sftpgo:
|
||||||
* `file action event hooks` also include on-download triggers
|
* `file action event hooks` also include on-download triggers
|
||||||
* `upload tracking / log` in main logfile
|
* `upload tracking / log` in main logfile
|
||||||
* `az`/arozos:
|
* `m`/arozos:
|
||||||
* `2fa` maybe possible through LDAP/Oauth
|
* `2fa` maybe possible through LDAP/Oauth
|
||||||
* `h3`/hfs3
|
* `c`/hfs3
|
||||||
* `2fa` available by installing a plugin
|
* `2fa` available by installing a plugin
|
||||||
|
|
||||||
## client features
|
## client features
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ---------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ---------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| single-page app | █ | | █ | █ | █ | | | █ | █ | █ | █ | | █ |
|
| single-page app | █ | | █ | █ | █ | | | █ | █ | █ | █ | | █ |
|
||||||
| themes | █ | █ | █ | █ | | | | | █ | | | | |
|
| themes | █ | █ | █ | █ | | | | | █ | | | | |
|
||||||
| directory tree nav | █ | ╱ | | | █ | | | | █ | | ╱ | | |
|
| directory tree nav | █ | ╱ | | | █ | | | | █ | | ╱ | | |
|
||||||
|
|
@ -404,22 +404,22 @@ symbol legend,
|
||||||
* `search by custom tags` = ability to tag files through the UI and search by those
|
* `search by custom tags` = ability to tag files through the UI and search by those
|
||||||
* `find local file` = drop a file into the browser to see if it exists on the server
|
* `find local file` = drop a file into the browser to see if it exists on the server
|
||||||
* `undo recent uploads` = accounts without delete permissions have a time window where they can undo their own uploads
|
* `undo recent uploads` = accounts without delete permissions have a time window where they can undo their own uploads
|
||||||
* `C`/copyparty has teeny-tiny skips playing gapless albums depending on audio codec (opus best)
|
* `a`/copyparty has teeny-tiny skips playing gapless albums depending on audio codec (opus best)
|
||||||
* `h2`/hfs2 has a very basic directory tree view, not showing sibling folders
|
* `b`/hfs2 has a very basic directory tree view, not showing sibling folders
|
||||||
* `rc`/rclone can do some file management (mkdir, rename, delete) when hosting througn webdav
|
* `f`/rclone can do some file management (mkdir, rename, delete) when hosting througn webdav
|
||||||
* `fg`/filebrowser remarks:
|
* `j`/filebrowser remarks:
|
||||||
* audio playback does not continue into next song
|
* audio playback does not continue into next song
|
||||||
* plaintext viewer/editor
|
* plaintext viewer/editor
|
||||||
* `fg`/filegator directory tree is a modal window
|
* `k`/filegator directory tree is a modal window
|
||||||
* `sg`/sftpgo remarks:
|
* `l`/sftpgo remarks:
|
||||||
* audio/video playback does not continue into next song/video
|
* audio/video playback does not continue into next song/video
|
||||||
* plaintext viewer/editor
|
* plaintext viewer/editor
|
||||||
|
|
||||||
|
|
||||||
## integration
|
## integration
|
||||||
|
|
||||||
| feature / software |[C]|[h2]|[h3]|[nc]|[sf]|[rc]|[df]|[cs]|[kb]|[fb]|[fg]|[sg]|[az]|
|
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||||
| ----------------------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| OS alert on upload | ╱ | | | | | | | | | ╱ | | ╱ | |
|
| OS alert on upload | ╱ | | | | | | | | | ╱ | | ╱ | |
|
||||||
| discord | ╱ | | | | | | | | | ╱ | | ╱ | |
|
| discord | ╱ | | | | | | | | | ╱ | | ╱ | |
|
||||||
| ┗ announce uploads | ╱ | | | | | | | | | | | ╱ | |
|
| ┗ announce uploads | ╱ | | | | | | | | | | | ╱ | |
|
||||||
|
|
@ -428,11 +428,11 @@ symbol legend,
|
||||||
| flameshot | | | | | | █ | | | | | | | |
|
| flameshot | | | | | | █ | | | | | | | |
|
||||||
|
|
||||||
* sharex `╱` = yes, but does not provide example sharex config
|
* sharex `╱` = yes, but does not provide example sharex config
|
||||||
* `C`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
* `OS alert on upload` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/notify.py)
|
* `OS alert on upload` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/notify.py)
|
||||||
* `discord » announce uploads` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/discord-announce.py)
|
* `discord » announce uploads` available as [a plugin](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/discord-announce.py)
|
||||||
* `fg`/filebrowser can probably pull those off with command runners similar to copyparty
|
* `j`/filebrowser can probably pull those off with command runners similar to copyparty
|
||||||
* `sg`/sftpgo has nothing built-in but is very extensible
|
* `l`/sftpgo has nothing built-in but is very extensible
|
||||||
|
|
||||||
|
|
||||||
## another matrix
|
## another matrix
|
||||||
|
|
@ -777,7 +777,7 @@ symbol legend,
|
||||||
|
|
||||||
|
|
||||||
# briefly considered
|
# briefly considered
|
||||||
* [pydio](https://github.com/pydio/cells): go/agpl3, looks great, fantastic ux -- but needs mariadb, systemwide install, SSO is 3280€/year
|
* [pydio](https://github.com/pydio/cells): python/agpl3, looks great, fantastic ux -- but needs mariadb, systemwide install
|
||||||
* [gossa](https://github.com/pldubouilh/gossa): go/mit, minimalistic, basic file upload, text editor, mkdir and rename (no delete/move)
|
* [gossa](https://github.com/pldubouilh/gossa): go/mit, minimalistic, basic file upload, text editor, mkdir and rename (no delete/move)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ FROM alpine:3.23
|
||||||
WORKDIR /z
|
WORKDIR /z
|
||||||
ENV ver_hashwasm=4.12.0 \
|
ENV ver_hashwasm=4.12.0 \
|
||||||
ver_marked=4.3.0 \
|
ver_marked=4.3.0 \
|
||||||
ver_dompf=3.3.3 \
|
ver_dompf=3.3.1 \
|
||||||
ver_mde=2.18.0 \
|
ver_mde=2.18.0 \
|
||||||
ver_codemirror=5.65.18 \
|
ver_codemirror=5.65.18 \
|
||||||
ver_fontawesome=5.13.0 \
|
ver_fontawesome=5.13.0 \
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ rm -rf mods/magic/
|
||||||
sed -ri /pickle/d mods/jinja2/_compat.py
|
sed -ri /pickle/d mods/jinja2/_compat.py
|
||||||
sed -ri '/(bccache|PackageLoader)/d' mods/jinja2/__init__.py
|
sed -ri '/(bccache|PackageLoader)/d' mods/jinja2/__init__.py
|
||||||
af '/^class/{s=0}/^class PackageLoader/{s=1}!s' mods/jinja2/loaders.py
|
af '/^class/{s=0}/^class PackageLoader/{s=1}!s' mods/jinja2/loaders.py
|
||||||
sed -ri 's/from url.*Request, urlopen.*/pass/' mods/copyparty/svchub.py
|
|
||||||
sed -ri 's/(.*"--vc-.*, help=).*/\1argparse.SUPPRESS)/' mods/copyparty/__main__.py
|
|
||||||
}
|
}
|
||||||
[ $w10 ] && {
|
[ $w10 ] && {
|
||||||
sed -ri '/(bccache|PackageLoader)/d' $spkgs/jinja2/__init__.py
|
sed -ri '/(bccache|PackageLoader)/d' $spkgs/jinja2/__init__.py
|
||||||
|
|
@ -86,6 +84,8 @@ excl=(
|
||||||
ctypes.macholib
|
ctypes.macholib
|
||||||
curses
|
curses
|
||||||
email._header_value_parser
|
email._header_value_parser
|
||||||
|
email.header
|
||||||
|
email.parser
|
||||||
importlib.resources
|
importlib.resources
|
||||||
importlib_resources
|
importlib_resources
|
||||||
multiprocessing
|
multiprocessing
|
||||||
|
|
@ -95,6 +95,8 @@ excl=(
|
||||||
pkg_resources
|
pkg_resources
|
||||||
PIL.EpsImagePlugin
|
PIL.EpsImagePlugin
|
||||||
pyftpdlib.prefork
|
pyftpdlib.prefork
|
||||||
|
urllib.request
|
||||||
|
urllib.response
|
||||||
urllib.robotparser
|
urllib.robotparser
|
||||||
)
|
)
|
||||||
[ $w10 ] && excl+=(
|
[ $w10 ] && excl+=(
|
||||||
|
|
@ -109,16 +111,12 @@ excl=(
|
||||||
PIL.PdfParser
|
PIL.PdfParser
|
||||||
zipimport
|
zipimport
|
||||||
) || excl+=(
|
) || excl+=(
|
||||||
email.header
|
|
||||||
email.parser
|
|
||||||
inspect
|
inspect
|
||||||
PIL
|
PIL
|
||||||
PIL.ExifTags
|
PIL.ExifTags
|
||||||
PIL.Image
|
PIL.Image
|
||||||
PIL.ImageDraw
|
PIL.ImageDraw
|
||||||
PIL.ImageOps
|
PIL.ImageOps
|
||||||
urllib.request
|
|
||||||
urllib.response
|
|
||||||
zipfile
|
zipfile
|
||||||
)
|
)
|
||||||
excl=( "${excl[@]/#/--exclude-module }" )
|
excl=( "${excl[@]/#/--exclude-module }" )
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,6 @@ import re, os, sys, time, shutil, signal, tarfile, hashlib, platform, tempfile,
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
# skip 1
|
|
||||||
#
|
|
||||||
# py2sfx (sfx.py) - bundle python-modules into an executable sfx.py
|
|
||||||
# (c)2020, ed <oss@ocv.me>, MIT-licensed, originally from copyparty:
|
|
||||||
# https://github.com/9001/copyparty/blob/hovudstraum/scripts/sfx.py
|
|
||||||
#
|
|
||||||
# ( no need to include the remaining lines of this comment-block
|
|
||||||
# for attribution; the rest is just for context )
|
|
||||||
#
|
|
||||||
# to create an sfx, use this:
|
|
||||||
# https://github.com/9001/copyparty/blob/hovudstraum/scripts/make-sfx.sh
|
|
||||||
#
|
|
||||||
# wanna steal this for your own project? then maybe
|
|
||||||
# see the r0c version, since that's slightly simpler:
|
|
||||||
# https://github.com/9001/r0c/blob/master/scripts/sfx.py
|
|
||||||
#
|
|
||||||
# skip 0
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
to edit this file, use HxD or "vim -b"
|
to edit this file, use HxD or "vim -b"
|
||||||
(there is compressed stuff at the end)
|
(there is compressed stuff at the end)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from copyparty.__init__ import MACOS
|
|
||||||
from copyparty.authsrv import AuthSrv
|
from copyparty.authsrv import AuthSrv
|
||||||
from copyparty.httpcli import HttpCli
|
from copyparty.httpcli import HttpCli
|
||||||
from tests import util as tu
|
from tests import util as tu
|
||||||
|
|
@ -219,8 +218,7 @@ class TestHttpCli(TC):
|
||||||
# float x-oc-mtime should be accepted
|
# float x-oc-mtime should be accepted
|
||||||
h, b = self.req(RCLONE_PUT_FLOAT % ("a/fb",))
|
h, b = self.req(RCLONE_PUT_FLOAT % ("a/fb",))
|
||||||
self.assertStart("HTTP/1.1 201 Created\r", h)
|
self.assertStart("HTTP/1.1 201 Created\r", h)
|
||||||
zi = 0 if MACOS else 3 # its okay macos you tried your best
|
self.assertAlmostEqual(os.path.getmtime("a/fb"), 1689453578.123, places=3)
|
||||||
self.assertAlmostEqual(os.path.getmtime("a/fb"), 1689453578.123, places=zi)
|
|
||||||
|
|
||||||
# then it does a propfind to confirm
|
# then it does a propfind to confirm
|
||||||
h, b = self.req(RCLONE_PROPFIND % ("a/fa",))
|
h, b = self.req(RCLONE_PROPFIND % ("a/fa",))
|
||||||
|
|
|
||||||
|
|
@ -143,13 +143,13 @@ class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt dlni dothidden e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_dupe_m no_fnugg no_html no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_script no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip no_zls nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rm_partial rmagic rss smb srch_dbg srch_excl srch_icase stats ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi uqe usernames vague_403 vc ver vol_nospawn vol_or_crash wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt dlni e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_dupe_m no_fnugg 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_u2abrt no_zip no_zls nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rm_partial rmagic rss smb srch_dbg srch_excl srch_icase stats ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi uqe usernames vague_403 vc ver vol_nospawn vol_or_crash wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
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 wram re_dhash see_dots 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 wram re_dhash see_dots plain_ip"
|
||||||
ka.update(**{k: True for k in ex.split()})
|
ka.update(**{k: True for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_cli ah_gen css_browser dbpath hist ipu js_browser js_other lf_url mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua ua_nodoc ua_nozip"
|
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()})
|
ka.update(**{k: None for k in ex.split()})
|
||||||
|
|
||||||
ex = "gid uid"
|
ex = "gid uid"
|
||||||
|
|
@ -161,10 +161,10 @@ class Cfg(Namespace):
|
||||||
ex = "ac_convt au_vol dl_list du_iwho mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt th_qv th_qvx ups_who ver_iwho zip_who"
|
ex = "ac_convt au_vol dl_list du_iwho mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt th_qv th_qvx ups_who ver_iwho zip_who"
|
||||||
ka.update(**{k: 9 for k in ex.split()})
|
ka.update(**{k: 9 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ctl_re db_act forget_ip gauto idp_cookie idp_store k304 loris no304 nosubtle qr_pin qr_wait re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle qr_pin qr_wait re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_alg bname chdir chmod_f chpw_db db_xattr doctitle df epilogues exit favico fika ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes shr shr1 shr_site site smsg tcolor textfiles th_pregen txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR"
|
ex = "ah_alg bname chdir chmod_f chpw_db db_xattr doctitle df epilogues exit favico fika ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes shr shr1 shr_site site smsg tcolor textfiles txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR"
|
||||||
ka.update(**{k: "" for k in ex.split()})
|
ka.update(**{k: "" for k in ex.split()})
|
||||||
|
|
||||||
ex = "apnd_who ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url dont_ban cachectl http_vary rcm rss_fmt_d rss_fmt_t spinner"
|
ex = "apnd_who ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url dont_ban cachectl http_vary rcm rss_fmt_d rss_fmt_t spinner"
|
||||||
|
|
@ -316,8 +316,6 @@ class VHttpSrv(object):
|
||||||
self.g403 = Garda("")
|
self.g403 = Garda("")
|
||||||
self.gurl = Garda("")
|
self.gurl = Garda("")
|
||||||
|
|
||||||
self.ico = Ico(args)
|
|
||||||
self.thumbcli = None
|
|
||||||
self.u2idx = None
|
self.u2idx = None
|
||||||
|
|
||||||
def cachebuster(self):
|
def cachebuster(self):
|
||||||
|
|
@ -358,6 +356,7 @@ class VHttpConn(object):
|
||||||
|
|
||||||
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
|
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
|
||||||
self.hsrv = Ctor(args, asrv, log)
|
self.hsrv = Ctor(args, asrv, log)
|
||||||
|
self.ico = Ico(args)
|
||||||
self.ipr = None
|
self.ipr = None
|
||||||
self.ipa_nm = None
|
self.ipa_nm = None
|
||||||
self.ipar_nm = None
|
self.ipar_nm = None
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue