Merge remote-tracking branch 'upstream/HEAD' into uiv1.5

This commit is contained in:
Til Schmitter 2026-04-21 23:46:12 +02:00
commit 715e8ada11
43 changed files with 420 additions and 92 deletions

View file

@ -120,6 +120,7 @@ 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)
@ -183,7 +184,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
* **Alpine:** `apk add py3-pillow ffmpeg` * **Alpine:** `apk add py3-pillow ffmpeg`
* **Debian:** `apt install --no-install-recommends python3-pil ffmpeg` * **Debian:** `apt install --no-install-recommends python3-pil ffmpeg`
* **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing` * **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing`
* **FreeBSD:** `pkg install py39-sqlite3 py39-pillow ffmpeg` * **FreeBSD:** `pkg install py311-sqlite3 py311-pillow ffmpeg`
* **MacOS:** `port install py-Pillow ffmpeg` * **MacOS:** `port install py-Pillow ffmpeg`
* **MacOS** (alternative): `brew install pillow ffmpeg` * **MacOS** (alternative): `brew install pillow ffmpeg`
* **Windows:** `python -m pip install --user -U Pillow` * **Windows:** `python -m pip install --user -U Pillow`
@ -607,10 +608,12 @@ 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: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` 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`
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
@ -2539,6 +2542,7 @@ buggy feature? rip it out by setting any of the following environment variables
| -------------------- | ------------ | | -------------------- | ------------ |
| `PRTY_NO_CTYPES` | do not use features from external libraries such as kernel32 | | `PRTY_NO_CTYPES` | do not use features from external libraries such as kernel32 |
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access | | `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
| `PRTY_NO_ENVEXPAND` | do not expand environment-variables in configs and args |
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes | | `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` | | `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) | | `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
@ -2585,6 +2589,23 @@ 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
@ -2729,6 +2750,12 @@ 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 ];
};
}; };
``` ```

View file

@ -8,7 +8,7 @@
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the # and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt # full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end) # (note: enable compression by adding .xz at the end)
# q, lo: $LOGS_DIRECTORY/%Y-%m%d.log # q, lo: ${LOGS_DIRECTORY}/%Y-%m%d.log
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE) # p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies) # i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)

View file

@ -16,7 +16,7 @@
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the # and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt # full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end) # (note: enable compression by adding .xz at the end)
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log q, lo: ${LOGS_DIRECTORY}/%Y-%m%d.log
# enable version-checker by uncommenting one of the 'vc-url' lines below; this will # enable version-checker by uncommenting one of the 'vc-url' lines below; this will
# periodically check if your copyparty version has a known security vulnerability, # periodically check if your copyparty version has a known security vulnerability,

View file

@ -44,6 +44,14 @@ 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))

View file

@ -65,6 +65,10 @@ from .util import (
b64enc, b64enc,
ctypes, ctypes,
dedent, dedent,
expand_osenv_c,
expand_osenv_cs,
expand_osenv_noop,
expand_osenv_s,
has_resource, has_resource,
load_resource, load_resource,
min_ex, min_ex,
@ -427,9 +431,22 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
sys.exit(0) sys.exit(0)
def expand_cvars(argv) -> list[str]:
n = 0
for v in argv:
if "=" in v:
a, b = v.split("=", 1)
v = "%s=%s" % (a, os.path.expanduser(expand_osenv_c(b)))
else:
v = os.path.expanduser(expand_osenv_c(v))
argv[n] = v
n += 1
return argv
def args_from_cfg(cfg_path: str) -> list[str]: def args_from_cfg(cfg_path: str) -> list[str]:
lines: list[str] = [] lines: list[str] = []
expand_config_file(None, lines, cfg_path, "") expand_config_file(None, expand_osenv_c, lines, cfg_path, "")
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "") lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
ret: list[str] = [] ret: list[str] = []
@ -453,10 +470,12 @@ def args_from_cfg(cfg_path: str) -> list[str]:
else: else:
ret.append(prefix + k + "=" + v) ret.append(prefix + k + "=" + v)
return ret return expand_cvars(ret)
def expand_cfg(argv) -> list[str]: def expand_cfg(argv) -> list[str]:
argv = expand_cvars(argv)
if CFG_DEF: if CFG_DEF:
supp = args_from_cfg(CFG_DEF[0]) supp = args_from_cfg(CFG_DEF[0])
argv = argv[:1] + supp + argv[1:] argv = argv[:1] + supp + argv[1:]
@ -1197,6 +1216,7 @@ def add_general(ap, nc, srvname):
ap2.add_argument("--name-url", metavar="TXT", type=u, help="URL for server name hyperlink (displayed topleft in browser)") ap2.add_argument("--name-url", metavar="TXT", type=u, help="URL for server name hyperlink (displayed topleft in browser)")
ap2.add_argument("--name-html", type=u, help=argparse.SUPPRESS) ap2.add_argument("--name-html", type=u, help=argparse.SUPPRESS)
ap2.add_argument("--site", metavar="URL", type=u, default="", help="public URL to assume when creating links; example: [\033[32mhttps://example.com/\033[0m]") ap2.add_argument("--site", metavar="URL", type=u, default="", help="public URL to assume when creating links; example: [\033[32mhttps://example.com/\033[0m]")
ap2.add_argument("--env-expand", metavar="N", type=int, default=-1, help="syntax to expect for environment-variables to expand in config-files; [\033[32m0\033[0m]=disable, [\033[32m1\033[0m]=$VAR (old syntax (scary)), [\033[32m2\033[0m]=${VAR} (new syntax (recommended))")
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]") ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit") ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)") ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
@ -1520,6 +1540,7 @@ 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")
@ -1740,7 +1761,7 @@ def add_thumbnail(ap):
ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,sr2,srf,srw,x3f", help="image formats to decode using rawpy") ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,sr2,srf,srw,x3f", help="image formats to decode using rawpy")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg") ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg") ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg") ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bcstm,bfstm,brstm,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)") ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg") ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg")
@ -1868,8 +1889,10 @@ 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="4", 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="4", 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")
@ -2176,6 +2199,15 @@ def main(argv: Optional[list[str]] = None) -> None:
quotecheck(al) quotecheck(al)
if al.env_expand == 2:
al.shenvexp = expand_osenv_c
elif al.env_expand == 1:
al.shenvexp = expand_osenv_s
elif al.env_expand == 0:
al.shenvexp = expand_osenv_noop
else:
al.shenvexp = expand_osenv_cs
if al.chdir: if al.chdir:
os.chdir(al.chdir) os.chdir(al.chdir)

View file

@ -56,7 +56,7 @@ if HAVE_SQLITE3:
if True: # pylint: disable=using-constant-test if True: # pylint: disable=using-constant-test
from collections.abc import Iterable from collections.abc import Iterable
from typing import Any, Generator, Optional, Sequence, Union from typing import Any, Callable, Generator, Optional, Sequence, Union
from .util import NamedLogger, RootLogger from .util import NamedLogger, RootLogger
@ -541,13 +541,11 @@ class VFS(object):
hist = flags.get("hist") hist = flags.get("hist")
if hist and hist != "-": if hist and hist != "-":
zs = "{}/{}".format(hist.rstrip("/"), name) flags["hist"] = "%s/%s" % (hist.rstrip("/"), name)
flags["hist"] = os.path.expandvars(os.path.expanduser(zs))
dbp = flags.get("dbpath") dbp = flags.get("dbpath")
if dbp and dbp != "-": if dbp and dbp != "-":
zs = "{}/{}".format(dbp.rstrip("/"), name) flags["dbpath"] = "%s/%s" % (dbp.rstrip("/"), name)
flags["dbpath"] = os.path.expandvars(os.path.expanduser(zs))
return flags return flags
@ -1279,7 +1277,7 @@ class AuthSrv(object):
daxs: dict[str, AXS], daxs: dict[str, AXS],
mflags: dict[str, dict[str, Any]], mflags: dict[str, dict[str, Any]],
) -> tuple[str, str]: ) -> tuple[str, str]:
src = os.path.expandvars(os.path.expanduser(src)) src = os.path.expanduser(self.args.shenvexp(src))
src = absreal(src) src = absreal(src)
dst = dst.strip("/") dst = dst.strip("/")
@ -1372,7 +1370,7 @@ class AuthSrv(object):
) -> None: ) -> None:
self.line_ctr = 0 self.line_ctr = 0
expand_config_file(self.log, cfg_lines, fp, "") expand_config_file(self.log, self.args.shenvexp, cfg_lines, fp, "")
if self.args.vc: if self.args.vc:
lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)] lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
self.log("expanded config file (unprocessed):\n" + "\n".join(lns)) self.log("expanded config file (unprocessed):\n" + "\n".join(lns))
@ -2174,7 +2172,7 @@ class AuthSrv(object):
if vflag == "-": if vflag == "-":
pass pass
elif vflag: elif vflag:
vflag = os.path.expandvars(os.path.expanduser(vflag)) vflag = os.path.expanduser(self.args.shenvexp(vflag))
vol.histpath = vol.dbpath = uncyg(vflag) if WINDOWS else vflag vol.histpath = vol.dbpath = uncyg(vflag) if WINDOWS else vflag
elif self.args.hist: elif self.args.hist:
for nch in range(len(hid)): for nch in range(len(hid)):
@ -2209,7 +2207,7 @@ class AuthSrv(object):
if vflag == "-": if vflag == "-":
pass pass
elif vflag: elif vflag:
vflag = os.path.expandvars(os.path.expanduser(vflag)) vflag = os.path.expanduser(self.args.shenvexp(vflag))
vol.dbpath = uncyg(vflag) if WINDOWS else vflag vol.dbpath = uncyg(vflag) if WINDOWS else vflag
elif self.args.dbpath: elif self.args.dbpath:
for nch in range(len(hid)): for nch in range(len(hid)):
@ -3258,6 +3256,7 @@ 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,
@ -3273,7 +3272,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 = "notooltips" zs = "glang 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
@ -3963,10 +3962,14 @@ def split_cfg_ln(ln: str) -> dict[str, Any]:
def expand_config_file( def expand_config_file(
log: Optional["NamedLogger"], ret: list[str], fp: str, ipath: str log: Optional["NamedLogger"],
shenvexp: "Callable[[str], str]",
ret: list[str],
fp: str,
ipath: str,
) -> None: ) -> None:
"""expand all % file includes""" """expand all % file includes"""
fp = absreal(fp) fp = absreal(os.path.expanduser(shenvexp(fp)))
if len(ipath.split(" -> ")) > 64: if len(ipath.split(" -> ")) > 64:
raise Exception("hit max depth of 64 includes") raise Exception("hit max depth of 64 includes")
@ -3997,7 +4000,7 @@ def expand_config_file(
if fp2 in ipath: if fp2 in ipath:
continue continue
expand_config_file(log, ret, fp2, ipath) expand_config_file(log, shenvexp, ret, fp2, ipath)
return return
@ -4022,7 +4025,7 @@ def expand_config_file(
fp2 = ln[1:].strip() fp2 = ln[1:].strip()
fp2 = os.path.join(os.path.dirname(fp), fp2) fp2 = os.path.join(os.path.dirname(fp), fp2)
ofs = len(ret) ofs = len(ret)
expand_config_file(log, ret, fp2, ipath) expand_config_file(log, shenvexp, ret, fp2, ipath)
for n in range(ofs, len(ret)): for n in range(ofs, len(ret)):
ret[n] = pad + ret[n] ret[n] = pad + ret[n]
continue continue

View file

@ -7,7 +7,7 @@ import os
import re import re
import time import time
from .__init__ import ANYWIN, MACOS from .__init__ import ANYWIN, FREEBSD, MACOS, UNIX
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: if MACOS or FREEBSD:
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 MACOS else self._from_proc() dtab = self._from_sp_mount() if UNIX 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 MACOS: if not fuses or UNIX:
raise Exception() raise Exception()
try: try:
so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINT"]) # centos6 so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINT"]) # centos6

View file

@ -154,7 +154,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 = "k304 no304 js idxh dots cppwd cppws".split() ALL_COOKIES = "cplng cppwd cppws dots idxh js k304 no304".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"
@ -1800,7 +1800,11 @@ class HttpCli(object):
topdir = {"vp": "", "st": st} topdir = {"vp": "", "st": st}
fgen: Iterable[dict[str, Any]] = [] fgen: Iterable[dict[str, Any]] = []
depth = self.headers.get("depth", "infinity").lower() if stat.S_ISDIR(st.st_mode):
depth = self.headers.get("depth", "infinity").lower()
else:
depth = "0"
if depth == "infinity": if depth == "infinity":
# allow depth:0 from unmapped root, but require read-axs otherwise # allow depth:0 from unmapped root, but require read-axs otherwise
if not self.can_read and (self.vpath or self.asrv.vfs.realpath): if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
@ -1809,12 +1813,6 @@ class HttpCli(object):
self.log(t, 3) self.log(t, 3)
raise Pebkac(401, t) raise Pebkac(401, t)
if not stat.S_ISDIR(topdir["st"].st_mode):
t = "depth:infinity can only be used on folders; %r is 0o%o"
t = t % ("/" + self.vpath, topdir["st"])
self.log(t, 3)
raise Pebkac(400, t)
if not self.args.dav_inf: if not self.args.dav_inf:
self.log("client wants --dav-inf", 3) self.log("client wants --dav-inf", 3)
zb = b'<?xml version="1.0" encoding="utf-8"?>\n<D:error xmlns:D="DAV:"><D:propfind-finite-depth/></D:error>' zb = b'<?xml version="1.0" encoding="utf-8"?>\n<D:error xmlns:D="DAV:"><D:propfind-finite-depth/></D:error>'
@ -1835,7 +1833,7 @@ class HttpCli(object):
wrap=False, wrap=False,
) )
elif depth == "0" or not stat.S_ISDIR(st.st_mode): elif depth == "0":
if depth == "0" and not self.vpath and not vn.realpath: if depth == "0" and not self.vpath and not vn.realpath:
# rootless server; give dummy listing # rootless server; give dummy listing
self.can_read = True self.can_read = True
@ -3721,12 +3719,12 @@ class HttpCli(object):
fdir = fdir_base fdir = fdir_base
fname = sanitize_fn(p_file or "") fname = sanitize_fn(p_file or "")
abspath = os.path.join(fdir, fname)
suffix = "-%.6f-%s" % (time.time(), dip) suffix = "-%.6f-%s" % (time.time(), dip)
if p_file and not nullwrite: if p_file and not nullwrite:
if rnd: if rnd:
fname = rand_name(fdir, fname, rnd) fname = rand_name(fdir, fname, rnd)
abspath = os.path.join(fdir, fname)
open_args = {"fdir": fdir, "suffix": suffix, "vf": vfs.flags} open_args = {"fdir": fdir, "suffix": suffix, "vf": vfs.flags}
if "replace" in self.uparam or "replace" in self.headers: if "replace" in self.uparam or "replace" in self.headers:
@ -3744,7 +3742,7 @@ class HttpCli(object):
tnam = fname = os.devnull tnam = fname = os.devnull
fdir = abspath = "" fdir = abspath = ""
if xbu: if xbu and abspath:
at = time.time() - lifetime at = time.time() - lifetime
hr = runhook( hr = runhook(
self.log, self.log,
@ -3792,7 +3790,7 @@ class HttpCli(object):
else: else:
open_args["fdir"] = fdir open_args["fdir"] = fdir
if p_file and not nullwrite: if abspath:
bos.makedirs(fdir, vf=vfs.flags) bos.makedirs(fdir, vf=vfs.flags)
# reserve destination filename # reserve destination filename
@ -3830,6 +3828,14 @@ class HttpCli(object):
finally: finally:
f.close() f.close()
self.conn.nbyte += sz
if not abspath:
files.append(
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, "")
)
tabspath = ""
continue
if lim: if lim:
lim.nup(self.ip) lim.nup(self.ip)
lim.bup(self.ip, sz) lim.bup(self.ip, sz)
@ -3840,15 +3846,12 @@ class HttpCli(object):
lim.chk_bup(self.ip) lim.chk_bup(self.ip)
lim.chk_nup(self.ip) lim.chk_nup(self.ip)
except: except:
if not nullwrite: wunlink(self.log, tabspath, vfs.flags)
wunlink(self.log, tabspath, vfs.flags) wunlink(self.log, abspath, vfs.flags)
wunlink(self.log, abspath, vfs.flags)
fname = os.devnull fname = os.devnull
raise raise
if not nullwrite: atomic_move(self.log, tabspath, abspath, vfs.flags)
atomic_move(self.log, tabspath, abspath, vfs.flags)
tabspath = "" tabspath = ""
at = time.time() - lifetime at = time.time() - lifetime
@ -3903,9 +3906,7 @@ class HttpCli(object):
abspath = ap2 abspath = ap2
sz = bos.path.getsize(abspath) sz = bos.path.getsize(abspath)
files.append( files.append((sz, sha_hex, sha_b64, p_file, fname, abspath))
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath)
)
dbv, vrem = vfs.get_dbv(rem) dbv, vrem = vfs.get_dbv(rem)
self.conn.hsrv.broker.say( self.conn.hsrv.broker.say(
"up2k.hash_file", "up2k.hash_file",
@ -3919,7 +3920,6 @@ class HttpCli(object):
self.uname, self.uname,
True, True,
) )
self.conn.nbyte += sz
except Pebkac: except Pebkac:
self.parser.drop() self.parser.drop()

View file

@ -22,7 +22,7 @@ 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 .u2idx import U2idx from .u2idx import U2idx
from .util import HMaccas, NetMap, shut_socket from .util import HMaccas, NetMap, min_ex, 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
@ -194,12 +194,12 @@ class HttpConn(object):
except Exception as ex: except Exception as ex:
em = str(ex) em = str(ex)
if "ALERT_CERTIFICATE_UNKNOWN" in em: if "ALERT_" in em:
# android-chrome keeps doing this self.log("client refused our TLS cert or config: " + em, c=6)
pass
else: else:
self.log("handshake\033[0m " + em, c=5) t = "https-handshake failed, probably due to client:\n"
self.log(t + min_ex(), c=5)
return return

View file

@ -17,6 +17,7 @@ from .util import (
FFMPEG_URL, FFMPEG_URL,
REKOBO_LKEY, REKOBO_LKEY,
VF_CAREFUL, VF_CAREFUL,
expand_osenv_c,
fsenc, fsenc,
gzip, gzip,
min_ex, min_ex,
@ -86,7 +87,7 @@ class MParser(object):
while True: while True:
try: try:
bp = os.path.expanduser(args) bp = os.path.expanduser(expand_osenv_c(args))
if WINDOWS: if WINDOWS:
bp = uncyg(bp) bp = uncyg(bp)
@ -216,6 +217,7 @@ def au_unpk(
def ffprobe( def ffprobe(
abspath: str, timeout: int = 60 abspath: str, timeout: int = 60
) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]: ) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]:
# ffprobe -hide_banner -show_streams -show_format --
cmd = [ cmd = [
b"ffprobe", b"ffprobe",
b"-hide_banner", b"-hide_banner",

View file

@ -89,13 +89,15 @@ class SMB(object):
smbserver.isInFileJail = self._is_in_file_jail smbserver.isInFileJail = self._is_in_file_jail
self._disarm() self._disarm()
ip = next((x for x in self.args.smb_i if ":" not in x), None) zs = " " if self.args.smb6 else ":"
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 supported for SMB; listening on 0.0.0.0", 3) self.log("smb", "IPv6 not enabled with --smb6; 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)
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port) kw = {"ipv6": True} if ":" in ip else {}
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)
@ -121,6 +123,7 @@ 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:

View file

@ -1133,17 +1133,23 @@ class SvcHub(object):
al.th_coversd_set = set(al.th_coversd) al.th_coversd_set = set(al.th_coversd)
for k in "c".split(" "): for k in "c".split(" "):
if self.args.env_expand in (0, 2):
break
vl = getattr(al, k) vl = getattr(al, k)
if not vl: if not vl:
continue continue
vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl] vl = [os.path.expanduser(self.args.shenvexp(x)) for x in vl]
setattr(al, k, vl) setattr(al, k, vl)
for k in "lo hist dbpath ssl_log".split(" "): for k in "lo hist dbpath ssl_log".split(" "):
if self.args.env_expand in (0, 2):
break
vs = getattr(al, k) vs = getattr(al, k)
if vs: if vs:
vs = os.path.expandvars(os.path.expanduser(vs)) vs = os.path.expanduser(self.args.shenvexp(vs))
setattr(al, k, vs) setattr(al, k, vs)
for k in "idp_adm stats_u".split(" "): for k in "idp_adm stats_u".split(" "):

View file

@ -7,7 +7,7 @@ import socket
import sys import sys
import time import time
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode from .__init__ import ANYWIN, OPENBSD, PY2, TYPE_CHECKING, UNIX, 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 (
@ -21,6 +21,7 @@ from .util import (
VF_CAREFUL, VF_CAREFUL,
Netdev, Netdev,
atomic_move, atomic_move,
chkcmd,
get_adapters, get_adapters,
min_ex, min_ex,
sunpack, sunpack,
@ -510,6 +511,13 @@ 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:

View file

@ -1219,6 +1219,13 @@ class ThumbSrv(object):
self.log("conv2 %s [%s]" % (container, enc), 6) self.log("conv2 %s [%s]" % (container, enc), 6)
benc = enc.encode("ascii").split(b" ") benc = enc.encode("ascii").split(b" ")
ac = b"2"
try:
if tags["chs"][1] in ("mono", "1", "1.0"):
ac = b"1"
except:
pass
# fmt: off # fmt: off
cmd = [ cmd = [
b"ffmpeg", b"ffmpeg",
@ -1228,6 +1235,7 @@ class ThumbSrv(object):
b"-i", fsenc(abspath), b"-i", fsenc(abspath),
] + tagset + [ ] + tagset + [
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-ac", ac,
] + benc + [ ] + benc + [
b"-f", container, b"-f", container,
fsenc(tpath) fsenc(tpath)
@ -1268,6 +1276,7 @@ class ThumbSrv(object):
b"-i", fsenc(abspath), b"-i", fsenc(abspath),
b"-map_metadata", b"-1", b"-map_metadata", b"-1",
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-ac", b"2",
] + benc + [ ] + benc + [
b"-f", b"opus", b"-f", b"opus",
fsenc(tmp_opus) fsenc(tmp_opus)

View file

@ -491,13 +491,12 @@ 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 txt=plain js=javascript for ln in """text md=plain js=javascript ass=plain ssa=plain txt=plain
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
@ -1502,8 +1501,7 @@ class Garda(object):
return 0, ip return 0, ip
if ":" in ip: if ":" in ip:
# assume /64 clients; drop 4 groups ip = ipnorm(ip)
ip = IPv6Address(ip).exploded[:-20]
if prev and self.uniq: if prev and self.uniq:
if self.prev.get(ip) == prev: if self.prev.get(ip) == prev:
@ -1564,6 +1562,43 @@ def dedent(txt: str) -> str:
return "\n".join([ln[pad:] for ln in lns]) return "\n".join([ln[pad:] for ln in lns])
def expand_osenv_noop(txt) -> str:
return txt
def _expand_osenv_c(txt) -> str:
if "${" not in txt:
return txt
zsl = txt.split("${")
ret = zsl[0]
for v in zsl[1:]:
if "}" not in v:
raise Exception("missing '}' after %r in config-value %r" % (v, txt))
a, b = v.split("}", 1)
try:
ret += os.environ[a] + b
except:
raise Exception("env-var %r not defined; config-value %r" % (a, txt))
return ret
if os.environ.get("PRTY_NO_ENVEXPAND"):
expand_osenv_c = expand_osenv_noop
expand_osenv_s = expand_osenv_noop
else:
expand_osenv_c = _expand_osenv_c
expand_osenv_s = os.path.expandvars
def expand_osenv_cs(txt) -> str:
a = expand_osenv_c(txt)
b = expand_osenv_s(txt)
if a == b:
return a
t = "config-value %r is using the old syntax for environment-variables; choose one of the following options:\noption 1: update the config-value to the new syntax, ${VAR} instead of $VAR or %%VAR%%\noption 2: tell copyparty to allow the old syntax with global-option --env-expand 1 (risky)\noption 3: tell copyparty to only use the new syntax (and not expand this variable) with global-option --env-expand 2\noption 4: disable all environment-variable expansions with PRTY_NO_ENVEXPAND=1 or global-option --env-expand 0"
raise Exception(t % (txt,))
def rice_tid() -> str: def rice_tid() -> str:
tid = threading.current_thread().ident tid = threading.current_thread().ident
c = sunpack(b"B" * 5, spack(b">Q", tid)[-5:]) c = sunpack(b"B" * 5, spack(b">Q", tid)[-5:])
@ -2446,8 +2481,8 @@ def odfusion(
def ipnorm(ip: str) -> str: def ipnorm(ip: str) -> str:
if ":" in ip: if ":" in ip:
# assume /64 clients; drop 4 groups # assume /56 clients; drop final 72 bits
return IPv6Address(ip).exploded[:-20] return str(IPv6Network(ip + "/56", strict=False).network_address)
return ip return ip
@ -3849,7 +3884,7 @@ def _parsehook(
argv = cmd.split(",") if "," in cmd else [cmd] argv = cmd.split(",") if "," in cmd else [cmd]
argv[0] = os.path.expandvars(os.path.expanduser(argv[0])) argv[0] = os.path.expanduser(expand_osenv_c(argv[0]))
return areq, chk, imp, fork, sin, jtxt, wait, sp_ka, argv return areq, chk, imp, fork, sin, jtxt, wait, sp_ka, argv
@ -4192,7 +4227,7 @@ def loadpy(ap: str, hot: bool) -> Any:
depending on what other inconveniently named files happen depending on what other inconveniently named files happen
to be in the same folder to be in the same folder
""" """
ap = os.path.expandvars(os.path.expanduser(ap)) ap = os.path.expanduser(expand_osenv_c(ap))
mdir, mfile = os.path.split(absreal(ap)) mdir, mfile = os.path.split(absreal(ap))
mname = mfile.rsplit(".", 1)[0] mname = mfile.rsplit(".", 1)[0]
sys.path.insert(0, mdir) sys.path.insert(0, mdir)

View file

@ -2,7 +2,7 @@
var J_BRW = 1; var J_BRW = 1;
if (window.rw_edit === undefined) if (window.dgauto === 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;
@ -232,6 +232,7 @@ 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',
@ -287,6 +288,8 @@ 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&nbsp; cursor is in the left gutter )", "tt_hover": "reveal overflowing lines on hover$N( breaks scrolling unless mouse $N&nbsp; 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",
@ -720,6 +723,54 @@ 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;
@ -728,7 +779,9 @@ 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]],
@ -742,6 +795,7 @@ for (var a = 0; a < LANGS.length; a++) {
} }
} }
} }
langtest2();
@ -809,12 +863,7 @@ function mktemp(is_dir) {
sendit(input.value); sendit(input.value);
// Chrome blurs elements when calling remove for some reason // Chrome blurs elements when calling remove for some reason
input.onblur = null; input.onblur = null;
try{ row.remove();
row.remove();
}
catch(e){
console.log(e);
}
}; };
input.onkeydown = function(e) { input.onkeydown = function(e) {
if (e.key == "Enter") if (e.key == "Enter")
@ -1172,6 +1221,13 @@ ebi('op_cfg').innerHTML = (
'</div>\n' + '</div>\n' +
(!MOBILE ? '<div><h3 id="h_mouse">🖱️ ' + L.cl_rcm + '</h3><div><a id="rcm_en" class="tgl btn" tt="' + L.cdt_ren + '</a><a id="rcm_db" class="tgl btn" tt="' + L.cdt_rdb + '</a></div></div>' : '') + (!MOBILE ? '<div><h3 id="h_mouse">🖱️ ' + L.cl_rcm + '</h3><div><a id="rcm_en" class="tgl btn" tt="' + L.cdt_ren + '</a><a id="rcm_db" class="tgl btn" tt="' + L.cdt_rdb + '</a></div></div>' : '') +
'<div>\n' + '<div>\n' +
' <h3 id="h_gauto">🅰️ ' + 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 id="h_filesize">🔢 ' + L.cl_hfsz + '</h3>\n' + ' <h3 id="h_filesize">🔢 ' + 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' +
@ -1501,9 +1557,7 @@ onresize100.add(read_sbw, true);
function check_image_support(format, uri) { function check_image_support(format, uri) {
var cached var cached = window['have_' + format] = sread('have_' + format);
= window['have_' + format]
= sread('have_' + format);
if (cached !== null) if (cached !== null)
return; return;
@ -1978,7 +2032,7 @@ mpl.init_ac2();
var re_m3u = /\.(m3u8?)$/i; var re_m3u = /\.(m3u8?)$/i;
var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4[abr]|mp3|oga|ogg|opus|wav)$/i : /\.(aac|flac|m4[abr]|mp3|wav)$/i, var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4[abr]|mp3|oga|ogg|opus|wav)$/i : /\.(aac|flac|m4[abr]|mp3|wav)$/i,
re_au_vid = /\.(3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i, re_au_vid = /\.(3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i,
re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4[abr]|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|oga|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i; re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|b[cfr]stm|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4[abr]|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|oga|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i;
// extract songs + add play column // extract songs + add play column
@ -2059,7 +2113,9 @@ function MPlayer() {
if (!tid || tid.indexOf('af-') !== 0) if (!tid || tid.indexOf('af-') !== 0)
continue; continue;
order.push(tid.slice(1)); tid = tid.slice(1);
if (r.tracks[tid])
order.push(tid);
} }
r.order = order; r.order = order;
r.shuffle(); r.shuffle();
@ -5735,8 +5791,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 () {
@ -5963,6 +6019,16 @@ 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;
@ -6075,11 +6141,10 @@ var thegrid = (function () {
var ths = QSA('#ggrid>a'); var ths = QSA('#ggrid>a');
for (var a = 0, aa = ths.length; a < aa; a++) { for (var a = 0, aa = ths.length; a < aa; a++) {
var _ref = ebi(ths[a].getAttribute('ref')); var ref = ths[a].getAttribute('ref');
if(_ref == null) if (!ref)
continue; continue;
var tr = _ref.closest('tr'), var cl = ebi(ref).closest('tr').className || '';
cl = tr.className || '';
if (noq_href(ths[a]).endsWith('/')) if (noq_href(ths[a]).endsWith('/'))
cl += ' dir'; cl += ' dir';
@ -6188,7 +6253,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';
@ -6355,6 +6420,18 @@ var thegrid = (function () {
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('wtgrid').onclick =
ebi('gridicon_template').onclick = ebi('gridicon_template').onclick =
ebi('listicon_template').onclick = ebi('listicon_template').onclick =
@ -6602,6 +6679,9 @@ var ahotkeys = function (e) {
return ebi('griden').click(); return ebi('griden').click();
} }
if (aet == 'input')
return;
var in_ftab = (aet == 'tr' || aet == 'td') && ae.closest('#files'); var in_ftab = (aet == 'tr' || aet == 'td') && ae.closest('#files');
if (in_ftab) { if (in_ftab) {
var d = '', rem = 0; var d = '', rem = 0;
@ -8183,6 +8263,9 @@ 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();
} }

View file

@ -221,6 +221,7 @@ 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": '◔ ◡ ◔"> 提示',
@ -275,6 +276,8 @@ Ls.chi = {
"tt_dynt": "自动随着目录树展开而变宽", "tt_dynt": "自动随着目录树展开而变宽",
"tt_wrap": "自动换行", "tt_wrap": "自动换行",
"tt_hover": "悬停时完整显示出写不下的文字$N启用后鼠标光标只有$N&nbsp; 位于左边线上才滚得动)", "tt_hover": "悬停时完整显示出写不下的文字$N启用后鼠标光标只有$N&nbsp; 位于左边线上才滚得动)",
"tt_gauto": "根据文件夹内容以网格或列表显示", //m
"tt_gathr": "当此比例的文件为图片/视频时使用网格", //m
"ml_pmode": "文件夹播完后", "ml_pmode": "文件夹播完后",
"ml_btns": "命令", "ml_btns": "命令",

View file

@ -225,6 +225,7 @@ 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',
@ -279,6 +280,8 @@ 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&nbsp; není v levém okraji )", "tt_hover": "odhalit přetékající řádky při najetí$N( ruší posun pokud kurzor myši $N&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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&trade;', "ct_grid": '田 Das Raster&trade;',
"ct_ttips": '◔ ◡ ◔"> Tooltips', "ct_ttips": '◔ ◡ ◔"> Tooltips',
@ -275,6 +276,8 @@ 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&nbsp; Cursor ist im linken Gutter)", "tt_hover": "Beim Hovern überlange Zeilen anzeigen$N(Scrollen funktioniert nicht ausser $N&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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田",
"ct_grid": '田 krado', "ct_grid": '田 krado',
"ct_ttips": '◔ ◡ ◔"> ŝpruchelpiloj', "ct_ttips": '◔ ◡ ◔"> ŝpruchelpiloj',
@ -275,6 +276,8 @@ 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&nbsp; ne estas en la maldekstra malplenaĵo )", "tt_hover": "montri kompletajn nomojn sur musumo$N( paneas rulumadon, se la kursoro de muso $N&nbsp; ne estas en la maldekstra malplenaĵo )",
"tt_gauto": "montri kiel krado aŭ listo laŭ dosieruja enhavo",
"tt_gathr": "uzi kradon se ĉi tiu elcento da dosieroj estas bildoj/filmetoj",
"ml_pmode": "je la fino de dosierujo...", "ml_pmode": "je la fino de dosierujo...",
"ml_btns": "komandoj", "ml_btns": "komandoj",

View file

@ -221,6 +221,7 @@ 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',
@ -275,6 +276,8 @@ 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&nbsp; ole vasemmassa marginaalissa )", "tt_hover": "paljasta ylivuotavat rivit leijutettaessa$N( rikkoo vierityksen ellei hiiri $N&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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',
@ -275,6 +276,8 @@ 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",

View file

@ -221,6 +221,7 @@ 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": '◔ ◡ ◔"> συμβουλές εργαλείων',
@ -275,6 +276,8 @@ Ls.grc = {
"tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών", "tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών",
"tt_wrap": "αναδίπλωση λέξεων", "tt_wrap": "αναδίπλωση λέξεων",
"tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N&nbsp; είναι στην αριστερή στήλη )", "tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N&nbsp; είναι στην αριστερή στήλη )",
"tt_gauto": "εμφάνιση ως πλέγμα ή λίστα ανάλογα με τα περιεχόμενα του φακέλου", //m
"tt_gathr": "χρήση πλέγματος αν αυτό το ποσοστό αρχείων είναι εικόνες/βίντεο", //m
"ml_pmode": "στο τέλος του φακέλου...", "ml_pmode": "στο τέλος του φακέλου...",
"ml_btns": "εντολές", "ml_btns": "εντολές",

View file

@ -222,6 +222,7 @@ 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',
@ -276,6 +277,8 @@ 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',

View file

@ -221,6 +221,7 @@ 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',
@ -275,6 +276,8 @@ 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&nbsp; 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&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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": '◔ ◡ ◔"> ツールチップ',
@ -275,6 +276,8 @@ Ls.jpn = {
"tt_dynt": "ツリーが拡大するにつれて自動的に増加", "tt_dynt": "ツリーが拡大するにつれて自動的に増加",
"tt_wrap": "単語の折り返し", "tt_wrap": "単語の折り返し",
"tt_hover": "ホバーすると溢れた線を表示する$N( マウスを押さない限りスクロールが中断されます $N&nbsp; カーソルは左余白です )", "tt_hover": "ホバーすると溢れた線を表示する$N( マウスを押さない限りスクロールが中断されます $N&nbsp; カーソルは左余白です )",
"tt_gauto": "フォルダー内容に応じてグリッドまたはリスト表示", //m
"tt_gathr": "この割合のファイルが画像/動画ならグリッドを使用", //m
"ml_pmode": "フォルダの末尾...", "ml_pmode": "フォルダの末尾...",
"ml_btns": "コマンド", "ml_btns": "コマンド",

View file

@ -221,6 +221,7 @@ 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": '◔ ◡ ◔"> 도움말',
@ -275,6 +276,8 @@ Ls.kor = {
"tt_dynt": "트리가 확장될 때 자동으로 너비 증가", "tt_dynt": "트리가 확장될 때 자동으로 너비 증가",
"tt_wrap": "자동 줄 바꿈", "tt_wrap": "자동 줄 바꿈",
"tt_hover": "마우스를 올리면 넘어가는 줄 표시$N(마우스 커서가 왼쪽 여백에$N&nbsp; 있지 않으면 스크롤이 깨짐)", "tt_hover": "마우스를 올리면 넘어가는 줄 표시$N(마우스 커서가 왼쪽 여백에$N&nbsp; 있지 않으면 스크롤이 깨짐)",
"tt_gauto": "폴더 내용에 따라 그리드 또는 목록으로 표시", //m
"tt_gathr": "파일 중 이 비율이 이미지/동영상이면 그리드 사용", //m
"ml_pmode": "폴더 끝에서...", "ml_pmode": "폴더 끝에서...",
"ml_btns": "명령", "ml_btns": "명령",

View file

@ -221,6 +221,7 @@ 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',
@ -275,6 +276,8 @@ 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",

View file

@ -218,6 +218,7 @@ 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',
@ -272,6 +273,8 @@ 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",

View file

@ -218,6 +218,7 @@ 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',
@ -272,6 +273,8 @@ 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",

View file

@ -224,6 +224,7 @@ 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',
@ -278,6 +279,8 @@ 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&nbsp; kursor nie jest w lewym marginesie )", "tt_hover": "pokazuj za długie linie po najechaniu kursorem$N( psuje przewijanie gdy $N&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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',
@ -275,6 +276,8 @@ 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&nbsp; 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&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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": '◔ ◡ ◔"> подсказки',
@ -275,6 +276,8 @@ Ls.rus = {
"tt_dynt": "автоматическое расширение панели", "tt_dynt": "автоматическое расширение панели",
"tt_wrap": "перенос слов", "tt_wrap": "перенос слов",
"tt_hover": "раскрывать обрезанные строки при наведении$N( ломает скроллинг, если $N&nbsp; курсор не в пустоте слева )", "tt_hover": "раскрывать обрезанные строки при наведении$N( ломает скроллинг, если $N&nbsp; курсор не в пустоте слева )",
"tt_gauto": "показывать как сетку или список в зависимости от содержимого папки", //m
"tt_gathr": "использовать сетку, если этот процент файлов — изображения/видео", //m
"ml_pmode": "в конце папки...", "ml_pmode": "в конце папки...",
"ml_btns": "команды", "ml_btns": "команды",

View file

@ -220,6 +220,7 @@ 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',
@ -274,6 +275,8 @@ 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&nbsp; 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&nbsp; 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",

View file

@ -221,6 +221,7 @@ 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',
@ -275,6 +276,8 @@ 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.",

View file

@ -221,6 +221,7 @@ 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ı',
@ -275,6 +276,8 @@ 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",

View file

@ -221,6 +221,7 @@ 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": '◔ ◡ ◔"> підказки',
@ -275,6 +276,8 @@ Ls.ukr = {
"tt_dynt": "автоматично збільшуватися при розширенні дерева", "tt_dynt": "автоматично збільшуватися при розширенні дерева",
"tt_wrap": "перенесення слів", "tt_wrap": "перенесення слів",
"tt_hover": "показувати переповнені рядки при наведенні$N( порушує прокрутку, якщо курсор $N&nbsp; миші не знаходиться в лівому відступі )", "tt_hover": "показувати переповнені рядки при наведенні$N( порушує прокрутку, якщо курсор $N&nbsp; миші не знаходиться в лівому відступі )",
"tt_gauto": "показувати як сітку або список залежно від вмісту папки", //m
"tt_gathr": "використовувати сітку, якщо цей відсоток файлів — зображення/відео", //m
"ml_pmode": "в кінці папки...", "ml_pmode": "в кінці папки...",
"ml_btns": "команди", "ml_btns": "команди",

View file

@ -221,6 +221,7 @@ 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',
@ -279,6 +280,8 @@ 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&nbsp; 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&nbsp; 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",

View file

@ -359,6 +359,48 @@ 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

View file

@ -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.4.0 \
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 \

View file

@ -31,5 +31,5 @@ a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac7
efc712162da7fb005c8869a7612d2f4983d2d073ec79e16a58e7bf1fcd01c88b1cc26656f0893c68edd2294be7c3990db2f6bd77e7e3f2613539d57994b6a033 pillow-12.1.1-cp313-cp313-win_amd64.whl efc712162da7fb005c8869a7612d2f4983d2d073ec79e16a58e7bf1fcd01c88b1cc26656f0893c68edd2294be7c3990db2f6bd77e7e3f2613539d57994b6a033 pillow-12.1.1-cp313-cp313-win_amd64.whl
b9b98714dfca6fa80b0b3f222965724d63be9c54d19435d1fe768e07016913d6db8d6e043fcb185b55a9bd6fe370a80cf961814fc096046a5f4640d99ed575ef pyinstaller-6.15.0-py3-none-win_amd64.whl b9b98714dfca6fa80b0b3f222965724d63be9c54d19435d1fe768e07016913d6db8d6e043fcb185b55a9bd6fe370a80cf961814fc096046a5f4640d99ed575ef pyinstaller-6.15.0-py3-none-win_amd64.whl
cad0f7cf39de691813b1d4abc7d33f8bda99a87d9c5886039b814752e8690364150da26fb61b3e28d5698ff57a90e6dcd619ed2b64b04f72b5aadb75e201bdb0 pyinstaller_hooks_contrib-2025.8-py3-none-any.whl cad0f7cf39de691813b1d4abc7d33f8bda99a87d9c5886039b814752e8690364150da26fb61b3e28d5698ff57a90e6dcd619ed2b64b04f72b5aadb75e201bdb0 pyinstaller_hooks_contrib-2025.8-py3-none-any.whl
50dba4a63957220247be2985bd4ed6928679d9f6dc8cb7cee36394dda4e69cdee910fb39b01965d1358133855ace535eb1e08774fed7090feb8618dfc5fd2441 python-3.13.12-amd64.exe 368ea2da3e3bfe765a37c62227e84774853aaabce6954475fa45c873e5547cb5346ca03a0f6a0789af369285bb3464881fed0275a19066913d9d396d5d9b9947 python-3.13.13-amd64.exe
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl 2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl

View file

@ -42,7 +42,7 @@ fns=(
pillow-12.1.1-cp313-cp313-win_amd64.whl pillow-12.1.1-cp313-cp313-win_amd64.whl
pyinstaller-6.15.0-py3-none-win_amd64.whl pyinstaller-6.15.0-py3-none-win_amd64.whl
pyinstaller_hooks_contrib-2025.8-py3-none-any.whl pyinstaller_hooks_contrib-2025.8-py3-none-any.whl
python-3.13.12-amd64.exe python-3.13.13-amd64.exe
setuptools-80.9.0-py3-none-any.whl setuptools-80.9.0-py3-none-any.whl
) )
[ $w7 ] && fns+=( [ $w7 ] && fns+=(

View file

@ -38,7 +38,7 @@ from copyparty.broker_thr import BrokerThr
from copyparty.ico import Ico from copyparty.ico import Ico
from copyparty.u2idx import U2idx from copyparty.u2idx import U2idx
from copyparty.up2k import Up2k from copyparty.up2k import Up2k
from copyparty.util import FHC, CachedDict, Garda, Unrecv from copyparty.util import FHC, CachedDict, Garda, Unrecv, expand_osenv_c
init_E(E) init_E(E)
@ -78,8 +78,10 @@ def get_ramdisk():
return ret return ret
for vol in ["/dev/shm", "/Volumes/cptd"]: # nosec (singleton test) for vol in ["/dev/shm", "/Volumes/cptd"]: # nosec (singleton test)
if os.path.exists(vol): try:
return subdir(vol) return subdir(vol)
except:
pass
if os.path.exists("/Volumes"): if os.path.exists("/Volumes"):
sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -161,7 +163,7 @@ 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 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 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"
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 th_pregen txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR"
@ -193,6 +195,7 @@ class Cfg(Namespace):
du_who="all", du_who="all",
dk_salt="b" * 16, dk_salt="b" * 16,
fk_salt="a" * 16, fk_salt="a" * 16,
env_expand=2,
fsnt="lin", fsnt="lin",
grp_all="acct", grp_all="acct",
idp_gsep=re.compile("[|:;+,]"), idp_gsep=re.compile("[|:;+,]"),
@ -215,6 +218,7 @@ class Cfg(Namespace):
rw_edit="md", rw_edit="md",
s_rd_sz=256 * 1024, s_rd_sz=256 * 1024,
s_wr_sz=256 * 1024, s_wr_sz=256 * 1024,
shenvexp=expand_osenv_c,
shr_who="auth", shr_who="auth",
sort="href", sort="href",
srch_hits=99999, srch_hits=99999,