diff --git a/.gitattributes b/.gitattributes
index 1ea1ff51..530f4727 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,3 +4,4 @@
*.png binary
*.gif binary
+*.gz binary
diff --git a/README.md b/README.md
index f153b929..151593f4 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [other flags](#other-flags)
* [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 xattrs](#metadata-from-xattrs) - unix extended file attributes
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags
* [event hooks](#event-hooks) - trigger a program on uploads, renames etc ([examples](./bin/hooks/))
* [zeromq](#zeromq) - event-hooks can send zeromq messages
@@ -398,6 +399,8 @@ same order here too
* [Firefox issue 1790500](https://bugzilla.mozilla.org/show_bug.cgi?id=1790500) -- entire browser can crash after uploading ~4000 small files
+* Windows: Uploading from a webbrowser may fail with "directory iterator got stuck" due to the [max path length](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry); try moving the files somewhere shorter before uploading
+
* Android: music playback randomly stops due to [battery usage settings](#fix-unreliable-playback-on-android)
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
@@ -1877,6 +1880,23 @@ see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copy
`--mtag-to` sets the tag-scan timeout; very high default (60 sec) to cater for zfs and other randomly-freezing filesystems. Lower values like 10 are usually safe, allowing for faster processing of tricky files
+### metadata from xattrs
+
+unix extended file attributes (Linux-only) can be indexed into the db and made searchable;
+
+* `--db-xattr user.foo,user.bar` will index the xattrs `user.foo` and `user.bar`,
+* `--db-xattr user.foo=foo,user.bar=bar` will index them with the names `foo` and `bar`,
+* `--db-xattr ~~user.foo,user.bar` will index everything *except* `user.foo` and `user.bar`,
+* `--db-xattr ~~` will index everything
+
+however note that the tags must also be enabled with `-mte` so here are some complete examples:
+* `-e2ts --db-xattr user.foo,user.bar -mte +user.foo,user.bar`
+* `-e2ts --db-xattr user.foo=foo,user.bar=bar -mte +foo,bar`
+
+as for actually adding the xattr `user.foo` to a file in the first place,
+* `setfattr -n user.foo -v 'utsikt fra fløytoppen' photo.jpg`
+
+
## file parser plugins
provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
@@ -2235,7 +2255,7 @@ example webserver / reverse-proxy configs:
* [lighttpd subdomain](contrib/lighttpd/subdomain.conf) -- entire domain/subdomain
* [lighttpd subpath](contrib/lighttpd/subpath.conf) -- location-based (not optimal, but in case you need it)
* [nginx config](contrib/nginx/copyparty.conf) -- recommended
-* [traefik config](contrib/traefik/copyparty.yaml)
+* [traefik config](contrib/traefik/copyparty.yaml) -- only use v3.6.7 or newer [due to CVE-2025-66490](https://github.com/9001/copyparty/issues/1205)
### real-ip
diff --git a/bin/u2c.py b/bin/u2c.py
index dd97784c..25877a96 100755
--- a/bin/u2c.py
+++ b/bin/u2c.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from __future__ import print_function, unicode_literals
-S_VERSION = "2.18"
-S_BUILD_DT = "2026-01-02"
+S_VERSION = "2.19"
+S_BUILD_DT = "2026-01-18"
"""
u2c.py: upload to copyparty
@@ -100,7 +100,7 @@ except:
ub64enc = base64.urlsafe_b64encode
-class BadAuth(Exception):
+class Fatal(Exception):
pass
@@ -835,10 +835,15 @@ def handshake(ar, file, search):
url = ""
url = ar.vtop + url
+ t0 = time.time()
+ tmax = t0 + ar.t_hs
while True:
sc = 600
txt = ""
- t0 = time.time()
+ t1 = time.time()
+ if t1 >= tmax:
+ print("\nERROR: server offline for longer than --t-hs; giving up")
+ raise Fatal()
try:
zs = json.dumps(req, separators=(",\n", ": "))
sc, txt = web.req("POST", url, {}, zs.encode("utf-8"), MJ)
@@ -861,11 +866,11 @@ def handshake(ar, file, search):
return [], False
elif sc == 403 or sc == 401:
print("\nERROR: login required, or wrong password:\n%s" % (txt,))
- raise BadAuth()
+ raise Fatal()
- t = "handshake failed, retrying: %s\n t0=%.3f t1=%.3f td=%.3f\n %s\n\n"
+ t = "handshake failed, retrying: %s\n t0=%.3f t1=%.3f t2=%.3f td1=%.3f td2=%.3f\n %s\n\n"
now = time.time()
- eprint(t % (file.name, t0, now, now - t0, em))
+ eprint(t % (file.name, t0, t1, now, now - t0, now - t1, em))
time.sleep(ar.cd)
try:
@@ -1051,7 +1056,7 @@ class Ctl(object):
print(" hs...")
try:
hs, _ = handshake(self.ar, file, search)
- except BadAuth:
+ except Fatal:
sys.exit(1)
if search:
@@ -1356,7 +1361,7 @@ class Ctl(object):
try:
hs, sprs = handshake(self.ar, file, search)
- except BadAuth:
+ except Fatal:
self.panik = 1
break
@@ -1591,6 +1596,7 @@ NOTE: if server has --usernames enabled, then password is "username:password"
ap.add_argument("-ns", action="store_true", help="no status panel (for slow consoles and macos)")
ap.add_argument("--cxp", type=float, metavar="SEC", default=57, help="assume http connections expired after SEConds")
ap.add_argument("--cd", type=float, metavar="SEC", default=5, help="delay before reattempting a failed handshake/upload")
+ ap.add_argument("--t-hs", type=float, metavar="SEC", default=186, help="crash if handshakes fail due to server-offline for this long")
ap.add_argument("--safe", action="store_true", help="use simple fallback approach")
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
diff --git a/contrib/nixos/modules/copyparty.nix b/contrib/nixos/modules/copyparty.nix
index 6bb51b68..13a8c21c 100644
--- a/contrib/nixos/modules/copyparty.nix
+++ b/contrib/nixos/modules/copyparty.nix
@@ -69,11 +69,8 @@ in
options.services.copyparty = {
enable = mkEnableOption "web-based file manager";
- package = mkOption {
- type = types.package;
- default = pkgs.copyparty;
- defaultText = "pkgs.copyparty";
- description = ''
+ package = mkPackageOption pkgs "copyparty" {
+ extraDescription = ''
Package of the application to run, exposed for overriding purposes.
'';
};
@@ -82,7 +79,7 @@ in
type = types.bool;
default = true;
description = ''
- Make a shell script wrapper called 'copyparty-hash' with all options set here,
+ Make a shell script wrapper called {command}`copyparty-hash` with all options set here,
that launches the hashing cli.
'';
};
@@ -117,9 +114,9 @@ in
type = types.attrs;
description = ''
Global settings to apply.
- Directly maps to values in the [global] section of the copyparty config.
+ Directly maps to values in the `[global]` section of the copyparty config.
Cannot set "c" or "hist", those are set by this module.
- See `${getExe cfg.package} --help` for more details.
+ See {command}`copyparty --help` for more details.
'';
default = {
i = "127.0.0.1";
@@ -138,7 +135,7 @@ in
globalExtraConfig = mkOption {
type = types.str;
default = "";
- description = "Appended to the end of the [global] section verbatim. This is useful for flags which are used in a repeating manner (e.g. ipu: 255.255.255.1=user) which can't be repeated in the settings = {} attribute set.";
+ description = "Appended to the end of the `[global]` section verbatim. This is useful for flags which are used in a repeating manner (e.g. `ipu: 255.255.255.1=user`) which can't be repeated in the settings = {} attribute set.";
};
accounts = mkOption {
@@ -201,21 +198,21 @@ in
Attribute list of permissions and the users to apply them to.
The key must be a string containing any combination of allowed permission:
- "r" (read): list folder contents, download files
- "w" (write): upload files; need "r" to see the uploads
- "m" (move): move files and folders; need "w" at destination
- "d" (delete): permanently delete files and folders
- "g" (get): download files, but cannot see folder contents
- "G" (upget): "get", but can see filekeys of their own uploads
- "h" (html): "get", but folders return their index.html
- "a" (admin): can see uploader IPs, config-reload
+ * "r" (read): list folder contents, download files
+ * "w" (write): upload files; need "r" to see the uploads
+ * "m" (move): move files and folders; need "w" at destination
+ * "d" (delete): permanently delete files and folders
+ * "g" (get): download files, but cannot see folder contents
+ * "G" (upget): "get", but can see filekeys of their own uploads
+ * "h" (html): "get", but folders return their index.html
+ * "a" (admin): can see uploader IPs, config-reload
For example: "rwmd"
The value must be one of:
- an account name, defined in `accounts`
- a list of account names
- "*", which means "any account"
+ * an account name, defined in `accounts`
+ * a list of account names
+ * "*", which means "any account"
'';
example = literalExpression ''
{
@@ -230,7 +227,7 @@ in
type = types.attrs;
description = ''
Attribute list of volume flags to apply.
- See `${getExe cfg.package} --help-flags` for more details.
+ See {command}`copyparty --help-flags` for more details.
'';
example = literalExpression ''
{
diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD
index fa8237d1..386083d8 100644
--- a/contrib/package/arch/PKGBUILD
+++ b/contrib/package/arch/PKGBUILD
@@ -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.
pkgname=copyparty
-pkgver="1.20.1"
+pkgver="1.20.4"
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
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")
backup=("etc/${pkgname}/copyparty.conf" )
-sha256sums=("4f513ca9e3d1c11a7bb4e1a8a925dda2449b9565e91f6ef7cbe10367fa4e2935")
+sha256sums=("25dac1edc91c5228d24ef4d07c31c07331cf697951afe31883fba74e2d102cc2")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
diff --git a/contrib/package/makedeb-mpr/PKGBUILD b/contrib/package/makedeb-mpr/PKGBUILD
index 0941869c..60e10e6a 100644
--- a/contrib/package/makedeb-mpr/PKGBUILD
+++ b/contrib/package/makedeb-mpr/PKGBUILD
@@ -2,7 +2,7 @@
pkgname=copyparty
-pkgver=1.20.1
+pkgver=1.20.4
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
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")
backup=("/etc/${pkgname}.d/init" )
-sha256sums=("4f513ca9e3d1c11a7bb4e1a8a925dda2449b9565e91f6ef7cbe10367fa4e2935")
+sha256sums=("25dac1edc91c5228d24ef4d07c31c07331cf697951afe31883fba74e2d102cc2")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
diff --git a/contrib/package/nix/copyparty/pin.json b/contrib/package/nix/copyparty/pin.json
index e15e49ed..2f7159e2 100644
--- a/contrib/package/nix/copyparty/pin.json
+++ b/contrib/package/nix/copyparty/pin.json
@@ -1,5 +1,5 @@
{
- "url": "https://github.com/9001/copyparty/releases/download/v1.20.1/copyparty-1.20.1.tar.gz",
- "version": "1.20.1",
- "hash": "sha256-T1E8qePRwRp7tOGoqSXdokSblWXpH273y+EDZ/pOKTU="
+ "url": "https://github.com/9001/copyparty/releases/download/v1.20.4/copyparty-1.20.4.tar.gz",
+ "version": "1.20.4",
+ "hash": "sha256-JdrB7ckcUijSTvTQfDHAczHPaXlRr+MYg/unTi0QLMI="
}
\ No newline at end of file
diff --git a/copyparty/__main__.py b/copyparty/__main__.py
index b2186d36..1ae4eec8 100644
--- a/copyparty/__main__.py
+++ b/copyparty/__main__.py
@@ -1173,6 +1173,7 @@ def add_general(ap, nc, srvname):
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
ap2.add_argument("--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("--site", metavar="URL", type=u, default="", help="public URL to assume when creating links; example: [\033[32mhttps://example.com/\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("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
@@ -1220,6 +1221,7 @@ def add_share(ap):
ap2.add_argument("--shr-who", metavar="TXT", type=u, default="auth", help="who can create a share? [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mauth\033[0m]=authenticated (volflag=shr_who)")
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
ap2.add_argument("--shr-rt", metavar="MIN", type=int, default=1440, help="shares can be revived by their owner if they expired less than MIN minutes ago; [\033[32m60\033[0m]=hour, [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week")
+ ap2.add_argument("--shr-site", metavar="URL", type=u, default="--site", help="public URL to assume when creating share-links; example: [\033[32mhttps://example.com/\033[0m]")
ap2.add_argument("--shr-v", action="store_true", help="debug")
@@ -1227,6 +1229,7 @@ def add_upload(ap):
ap2 = ap.add_argument_group("upload options")
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
+ ap2.add_argument("--up-site", metavar="URL", type=u, default="--site", help="public URL to assume when creating links to uploaded files; example: [\033[32mhttps://example.com/\033[0m]")
ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)")
ap2.add_argument("--put-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck)")
ap2.add_argument("--bup-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck)")
@@ -1559,6 +1562,7 @@ def add_optouts(ap):
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI. This is the same as --du-who no")
ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
+ ap2.add_argument("--smsg", metavar="T,T", type=u, default="POST", help="HTTP-methods to allow ?smsg for; will execute xm hooks like urlform / message-to-serverlog; dangerous example: [\033[32mGET,POST\033[0m]. \033[1;31mWARNING:\033[0m The default (POST) is safe, but GET is dangerous; security/CSRF hazard")
ap2.add_argument("--zipmaxn", metavar="N", type=u, default="0", help="reject download-as-zip if more than \033[33mN\033[0m files in total; optionally takes a unit suffix: [\033[32m256\033[0m], [\033[32m9K\033[0m], [\033[32m4G\033[0m] (volflag=zipmaxn)")
ap2.add_argument("--zipmaxs", metavar="SZ", type=u, default="0", help="reject download-as-zip if total download size exceeds \033[33mSZ\033[0m bytes; optionally takes a unit suffix: [\033[32m256M\033[0m], [\033[32m4G\033[0m], [\033[32m2T\033[0m] (volflag=zipmaxs)")
ap2.add_argument("--zipmaxt", metavar="TXT", type=u, default="", help="custom errormessage when download size exceeds max (volflag=zipmaxt)")
@@ -1582,6 +1586,8 @@ def add_safety(ap):
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("--xdev", action="store_true", help="stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD, ...) (volflag=xdev)")
+ ap2.add_argument("--vol-nospawn", action="store_true", help="if a volume's folder does not exist on the HDD, then do not create it (continue with warning) (volflag=nospawn)")
+ ap2.add_argument("--vol-or-crash", action="store_true", help="if a volume's folder does not exist on the HDD, then burst into flames (volflag=assert_root)")
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
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")
@@ -1591,7 +1597,7 @@ def add_safety(ap):
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("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
ap2.add_argument("--dont-ban", metavar="TXT", type=u, default="no", help="anyone at this accesslevel or above will not get banned: [\033[32mav\033[0m]=admin-in-volume, [\033[32maa\033[0m]=has-admin-anywhere, [\033[32mrw\033[0m]=read-write, [\033[32mauth\033[0m]=authenticated, [\033[32many\033[0m]=disable-all-bans, [\033[32mno\033[0m]=anyone-can-get-banned")
- ap2.add_argument("--banmsg", metavar="TXT", type=u, default="thank you for playing \u00a0 (see serverlog and readme)", help="the response to send to banned users; can be @ban.html to send the contents of ban.html")
+ ap2.add_argument("--banmsg", metavar="TXT", type=u, default="thank you for playing \u00a0 (see fileserver log and readme)", help="the response to send to banned users; can be @ban.html to send the contents of ban.html")
ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
ap2.add_argument("--ban-pwc", metavar="N,W,B", type=u, default="5,60,1440", help="more than \033[33mN\033[0m password-changes in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
@@ -1634,6 +1640,7 @@ def add_logging(ap):
ap2 = ap.add_argument_group("logging options")
ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages")
ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile; use .txt for plaintext or .xz for compressed. Example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
+ ap2.add_argument("--flo", metavar="N", type=int, default=1, help="log format for \033[33m-lo\033[0m; [\033[32m1\033[0m]=classic/colors, [\033[32m2\033[0m]=no-color")
ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
@@ -1781,10 +1788,12 @@ def add_db_metadata(ap):
ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for tag scanning")
ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such")
ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/FFprobe parsers")
+ ap2.add_argument("--db-xattr", metavar="t,t", type=u, default="", help="read file xattrs as metadata tags; [\033[32ma,b\033[0m] reads keys \033[33ma\033[0m and \033[33mb\033[0m as tags \033[33ma\033[0m and \033[33mb\033[0m, [\033[32ma=foo,b=bar\033[0m] reads keys \033[33ma\033[0m and \033[33mb\033[0m as tags \033[33mfoo\033[0m and \033[33mbar\033[0m, [\033[32m~~a,b\033[0m] does everything except \033[33ma\033[0m and \033[33mb\033[0m, [\033[32m~~\033[0m] does everything. NOTE: Each tag must also be enabled with \033[33m-mte\033[0m (volflag=db_xattr)")
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add/replace metadata mapping")
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE)
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH)
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file")
+ ap2.add_argument("--have-db-xattr", action="store_true", help=argparse.SUPPRESS)
def add_txt(ap):
@@ -1825,6 +1834,7 @@ 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("--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("--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("--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")
diff --git a/copyparty/__version__.py b/copyparty/__version__.py
index ec5f5dd7..2fd34538 100644
--- a/copyparty/__version__.py
+++ b/copyparty/__version__.py
@@ -1,8 +1,8 @@
# coding: utf-8
-VERSION = (1, 20, 1)
+VERSION = (1, 20, 4)
CODENAME = "sftp is fine too"
-BUILD_DT = (2026, 1, 9)
+BUILD_DT = (2026, 1, 23)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py
index 59830e1c..0a6b1d17 100644
--- a/copyparty/authsrv.py
+++ b/copyparty/authsrv.py
@@ -22,6 +22,7 @@ from .util import (
DEF_MTH,
EXTS,
FAVICON_MIMES,
+ FN_EMB,
HAVE_SQLITE3,
IMPLICATIONS,
META_NOBOTS,
@@ -101,7 +102,7 @@ UP_MTE_MAP = { # db-order
}
SEE_LOG = "see log for details"
-SEESLOG = " (see serverlog for details)"
+SEESLOG = " (see fileserver log for details)"
SSEELOG = " ({})".format(SEE_LOG)
BAD_CFG = "invalid config; {}".format(SEE_LOG)
SBADCFG = " ({})".format(BAD_CFG)
@@ -1070,6 +1071,7 @@ class AuthSrv(object):
"tcolor": self.args.tcolor,
"du_iwho": self.args.du_iwho,
"shr_who": self.args.shr_who if self.args.shr else "no",
+ "emb_all": FN_EMB,
"ls_q_m": ("", ""),
}
self._vf0 = self._vf0b.copy()
@@ -2090,6 +2092,49 @@ class AuthSrv(object):
t = "WARNING: the account [%s] is not mentioned in any volume definitions and thus has the same access-level and privileges that guests have; please see --help-accounts for details. For example, if you intended to give that user full access to the current directory, you could do this: -v .::A,%s"
self.log(t % (usr, usr), 1)
+ dropvols = []
+ errors = False
+ for vol in vfs.all_vols.values():
+ if (
+ not vol.realpath
+ or (
+ "assert_root" not in vol.flags
+ and "nospawn" not in vol.flags
+ and not self.args.vol_or_crash
+ and not self.args.vol_nospawn
+ )
+ or bos.path.exists(vol.realpath)
+ ):
+ pass
+ elif "assert_root" in vol.flags or self.args.vol_or_crash:
+ t = "ERROR: volume [/%s] root folder %r does not exist on server HDD; will now crash due to volflag 'assert_root'"
+ self.log(t % (vol.vpath, vol.realpath), 1)
+ errors = True
+ else:
+ t = "WARNING: volume [/%s] root folder %r does not exist on server HDD; volume will be unavailable due to volflag 'nospawn'"
+ self.log(t % (vol.vpath, vol.realpath), 3)
+ dropvols.append(vol)
+ if errors:
+ sys.exit(1)
+ for vol in dropvols:
+ vol.axs = AXS()
+ vol.uaxs = {}
+ vfs.all_vols.pop(vol.vpath, None)
+ vfs.all_nodes.pop(vol.vpath, None)
+ for zv in vfs.all_nodes.values():
+ try:
+ zv.all_aps.remove(vol.realpath)
+ zv.all_vps.remove(vol.vpath)
+ # pointless but might as well:
+ zv.all_vols.pop(vol.vpath)
+ zv.all_nodes.pop(vol.vpath)
+ except:
+ pass
+ zs = next((x for x, y in zv.nodes.items() if y == vol), "")
+ if zs:
+ zv.nodes.pop(zs)
+ vol.realpath = ""
+
promote = []
demote = []
for vol in vfs.all_vols.values():
@@ -2538,16 +2583,22 @@ class AuthSrv(object):
t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
self.log(t % (vol.vpath, etv), 3)
+ emb_all = vol.flags["emb_all"] = set()
+
zsl1 = [x for x in vol.flags["preadmes"].split(",") if x]
zsl2 = [x for x in vol.flags["readmes"].split(",") if x]
zsl3 = list(set([x.lower() for x in zsl1]))
zsl4 = list(set([x.lower() for x in zsl2]))
+ emb_all.update(zsl3)
+ emb_all.update(zsl4)
vol.flags["emb_mds"] = [[0, zsl1, zsl3], [1, zsl2, zsl4]]
zsl1 = [x for x in vol.flags["prologues"].split(",") if x]
zsl2 = [x for x in vol.flags["epilogues"].split(",") if x]
zsl3 = list(set([x.lower() for x in zsl1]))
zsl4 = list(set([x.lower() for x in zsl2]))
+ emb_all.update(zsl3)
+ emb_all.update(zsl4)
vol.flags["emb_lgs"] = [[0, zsl1, zsl3], [1, zsl2, zsl4]]
zs = str(vol.flags.get("html_head") or "")
@@ -2591,6 +2642,19 @@ class AuthSrv(object):
vol.check_landmarks()
+ if vol.flags.get("db_xattr"):
+ self.args.have_db_xattr = True
+ zs = str(vol.flags["db_xattr"])
+ neg = zs.startswith("~~")
+ if neg:
+ zs = zs[2:]
+ zsl = [x.strip() for x in zs.split(",")]
+ zsl = [x for x in zsl if x]
+ if neg:
+ vol.flags["db_xattr_no"] = set(zsl)
+ else:
+ vol.flags["db_xattr_yes"] = zsl
+
# d2d drops all database features for a volume
for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
if not vol.flags.get(grp, False):
@@ -2709,7 +2773,7 @@ class AuthSrv(object):
for zs in zs.split():
vol.flags.pop(zs, None)
- for vol in vfs.all_nodes.values():
+ for vol in vfs.all_vols.values():
if not vol.realpath or vol.flags.get("is_file"):
continue
ccs = vol.flags["casechk"][:1].lower()
@@ -3145,6 +3209,7 @@ class AuthSrv(object):
"dsort": vf["sort"],
"dcrop": vf["crop"],
"dth3x": vf["th3x"],
+ "drcm": self.args.rcm,
"dvol": self.args.au_vol,
"idxh": int(self.args.ih),
"dutc": not self.args.localtime,
@@ -3168,6 +3233,11 @@ class AuthSrv(object):
for zs in zs.split():
if getattr(self.args, zs, False):
js_htm[zs] = 1
+ zs = "up_site"
+ for zs in zs.split():
+ zs2 = getattr(self.args, zs, "")
+ if zs2:
+ js_htm[zs] = zs2
vn.js_htm = json_hesc(json.dumps(js_htm))
vols = list(vfs.all_nodes.values())
diff --git a/copyparty/bos/bos.py b/copyparty/bos/bos.py
index 9013a066..7aadb2cf 100644
--- a/copyparty/bos/bos.py
+++ b/copyparty/bos/bos.py
@@ -106,14 +106,14 @@ def utime(
def utime_c(
log: Union["NamedLogger", Any],
p: str,
- ts: int,
+ ts: float,
follow_symlinks: bool = True,
throw: bool = False,
-) -> Optional[int]:
+) -> Optional[float]:
clamp = 0
ov = ts
bp = fsenc(p)
- now = int(time.time())
+ now = time.time()
while True:
try:
if SYMTIME:
diff --git a/copyparty/cfg.py b/copyparty/cfg.py
index d33287e9..29c05990 100644
--- a/copyparty/cfg.py
+++ b/copyparty/cfg.py
@@ -27,6 +27,8 @@ def vf_bmap() -> dict[str, str]:
"no_thumb": "dthumb",
"no_vthumb": "dvthumb",
"no_athumb": "dathumb",
+ "vol_nospawn": "nospawn",
+ "vol_or_crash": "assert_root",
}
for k in (
"dedup",
@@ -101,6 +103,7 @@ def vf_vmap() -> dict[str, str]:
"chmod_d",
"chmod_f",
"dbd",
+ "db_xattr",
"du_who",
"epilogues",
"ufavico",
@@ -286,6 +289,7 @@ flagcats = {
"dotsrch": "show dotfiles in search results",
"nodotsrch": "hide dotfiles in search results (default)",
"srch_excl": "exclude search results with URL matching this regex",
+ "db_xattr=user.foo,user.bar": "index file xattrs as media-tags",
},
'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
"mte=artist,title": "media-tags to index/display",
@@ -425,6 +429,8 @@ flagcats = {
"cachectl=no-cache": "controls caching in webbrowsers",
"mv_retry": "ms-windows: timeout for renaming busy files",
"rm_retry": "ms-windows: timeout for deleting busy files",
+ "nospawn": "don't create volume's folder if not exist",
+ "assert_root": "crash on startup if volume's folder not exist",
"davauth": "ask webdav clients to login for all folders",
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
},
diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py
index c1bc543b..e5d8ff9e 100644
--- a/copyparty/ftpd.py
+++ b/copyparty/ftpd.py
@@ -19,7 +19,6 @@ from .__init__ import PY2, TYPE_CHECKING
from .authsrv import VFS
from .bos import bos
from .util import (
- FN_EMB,
VF_CAREFUL,
Daemon,
ODict,
@@ -179,7 +178,7 @@ class FtpFs(AbstractedFS):
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
if (
w
- and fn.lower() in FN_EMB
+ and fn.lower() in vfs.flags["emb_all"]
and self.h.uname not in vfs.axs.uread
and "wo_up_readme" not in vfs.flags
):
diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py
index f7ff5b62..f0ffb1a1 100644
--- a/copyparty/httpcli.py
+++ b/copyparty/httpcli.py
@@ -45,7 +45,6 @@ from .util import (
BITNESS,
DAV_ALLPROPS,
E_SCK_WR,
- FN_EMB,
HAVE_SQLITE3,
HTTPCODE,
UTC,
@@ -161,6 +160,7 @@ H_CONN_CLOSE = "Connection: Close"
RSS_SORT = {"m": "mt", "u": "at", "n": "fn", "s": "sz"}
ACODE2_FMT = set(["opus", "owa", "caf", "mp3", "flac", "wav"])
+IDX_HTML = set(["index.htm", "index.html"])
A_FILE = os.stat_result(
(0o644, -1, -1, 1, 1000, 1000, 8, 0x39230101, 0x39230101, 0x39230101)
@@ -285,12 +285,6 @@ class HttpCli(object):
uname = self.asrv.iacct.get(b) or self.asrv.sesa.get(b)
return "%s\033[7m %s \033[27m%s" % (a, uname, c)
- def _check_nonfatal(self, ex: Pebkac, post: bool) -> bool:
- if post:
- return ex.code < 300
-
- return ex.code < 400 or ex.code in [404, 429]
-
def _assert_safe_rem(self, rem: str) -> None:
# sanity check to prevent any disasters
# (this function hopefully serves no purpose; validation has already happened at this point, this only exists as a last-ditch effort just in case)
@@ -348,11 +342,6 @@ class HttpCli(object):
if not headerlines:
return False
- if not headerlines[0]:
- # seen after login with IE6.0.2900.5512.xpsp.080413-2111 (xp-sp3)
- self.log("BUG: trailing newline from previous request", c="1;31")
- headerlines.pop(0)
-
try:
self.mode, self.req, self.http_ver = headerlines[0].split(" ")
@@ -361,6 +350,8 @@ class HttpCli(object):
for header_line in headerlines[1:]:
k, zs = header_line.split(":", 1)
self.headers[k.lower()] = zs.strip()
+ if zs.endswith(" HTTP/1.1"):
+ raise Exception()
except:
headerlines = [repr(x) for x in headerlines]
msg = "#[ " + " ]\n#[ ".join(headerlines) + " ]"
@@ -405,6 +396,7 @@ class HttpCli(object):
if n:
zso = self.headers.get(self.args.xff_hdr)
if zso:
+ self.keepalive = False
if n > 0:
n -= 1
@@ -854,7 +846,7 @@ class HttpCli(object):
guess = "modifying" if (origin and host) else "stripping"
t = "cors-reject %s because request-header Origin=%r does not match request-protocol %r and host %r based on request-header Host=%r (note: if this request is not malicious, check if your reverse-proxy is accidentally %s request headers, in particular 'Origin', for example by running copyparty with --ihead='*' to show all request headers)"
self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
- raise Pebkac(403, "rejected by cors-check (see serverlog)")
+ raise Pebkac(403, "rejected by cors-check (see fileserver log)")
# getattr(self.mode) is not yet faster than this
if self.mode == "POST":
@@ -889,8 +881,8 @@ class HttpCli(object):
self.terse_reply(b"", 500)
return False
- post = self.mode in ["POST", "PUT"] or "content-length" in self.headers
- if not self._check_nonfatal(pex, post):
+ post = self.mode in ("POST", "PUT") or "content-length" in self.headers
+ if pex.code >= (300 if post else 400):
self.keepalive = False
em = str(ex)
@@ -997,8 +989,8 @@ class HttpCli(object):
rt = bans[ip] - time.time()
if rt < 0:
- self.log("client unbanned", 3)
del bans[ip]
+ self.log("client unbanned", 3)
return False
self.log("banned for {:.0f} sec".format(rt), 6)
@@ -1434,6 +1426,9 @@ class HttpCli(object):
self.uparam["h"] = ""
+ if "smsg" in self.uparam:
+ return self.handle_smsg()
+
if "tree" in self.uparam:
return self.tx_tree()
@@ -1835,7 +1830,9 @@ class HttpCli(object):
zi = (
vn.flags["du_iwho"]
- if vn.realpath and "quota-available-bytes" in props
+ if vn.realpath
+ and "quota-available-bytes" in props
+ and "quotaused" not in props # macos finder; ingnore it
else 0
)
if zi and (
@@ -1866,10 +1863,6 @@ class HttpCli(object):
"quota-available-bytes": str(bfree),
"quota-used-bytes": str(btot - bfree),
}
- if "quotaused" in props: # macos finder crazytalk
- df["quotaused"] = df["quota-used-bytes"]
- if "quota" in props:
- df["quota"] = df["quota-available-bytes"] # idk, makes it happy
else:
df = {}
else:
@@ -2210,7 +2203,7 @@ class HttpCli(object):
raise Pebkac(403 if self.pw else 401, t % (self.uname, self.vn.vpath))
if not self.args.no_dav and self._applesan():
- return self.headers.get("content-length") == "0"
+ return False
if self.headers.get("expect", "").lower() == "100-continue":
try:
@@ -2246,6 +2239,9 @@ class HttpCli(object):
):
return self.handle_post_json()
+ if "smsg" in self.uparam:
+ return self.handle_smsg()
+
if "move" in self.uparam:
return self.handle_mv()
@@ -2332,6 +2328,37 @@ class HttpCli(object):
raise Pebkac(405, "don't know how to handle POST(%r)" % (ctype,))
+ def handle_smsg(self) -> bool:
+ if self.mode not in self.args.smsg_set:
+ raise Pebkac(403, "smsg is disabled for this http-method in server config")
+
+ msg = self.uparam["smsg"]
+ self.log("smsg %d @ %r\n %r\n" % (len(msg), "/" + self.vpath, msg))
+
+ xm = self.vn.flags.get("xm")
+ if xm:
+ xm_rsp = runhook(
+ self.log,
+ self.conn.hsrv.broker,
+ None,
+ "xm",
+ xm,
+ self.vn.canonical(self.rem),
+ self.vpath,
+ self.host,
+ self.uname,
+ self.asrv.vfs.get_perms(self.vpath, self.uname),
+ time.time(),
+ len(msg),
+ self.ip,
+ time.time(),
+ [msg, msg],
+ )
+ self.loud_reply(xm_rsp.get("stdout") or "", status=202)
+ else:
+ self.loud_reply("k", status=202)
+ return True
+
def get_xml_enc(self, txt: str) -> str:
ofs = txt[:512].find(' encoding="')
enc = ""
@@ -2586,7 +2613,7 @@ class HttpCli(object):
at = mt = time.time() - lifetime
cli_mt = self.headers.get("x-oc-mtime")
if cli_mt:
- bos.utime_c(self.log, path, int(cli_mt), False)
+ bos.utime_c(self.log, path, float(cli_mt), False)
if nameless and "magic" in vfs.flags:
try:
@@ -2683,11 +2710,20 @@ class HttpCli(object):
vpath = "/".join([x for x in [vfs.vpath, rem, fn] if x])
vpath = quotep(vpath)
- url = "{}://{}/{}".format(
- "https" if self.is_https else "http",
- self.host,
- self.args.RS + vpath + vsuf,
- )
+ if self.args.up_site:
+ url = "%s%s%s" % (
+ self.args.up_site,
+ vpath,
+ vsuf,
+ )
+ else:
+ url = "%s://%s/%s%s%s" % (
+ "https" if self.is_https else "http",
+ self.host,
+ self.args.RS,
+ vpath,
+ vsuf,
+ )
return post_sz, halg, sha_hex, sha_b64, remains, path, url
@@ -2912,7 +2948,7 @@ class HttpCli(object):
if (
not self.can_read
and self.can_write
- and name.lower() in FN_EMB
+ and name.lower() in dbv.flags["emb_all"]
and "wo_up_readme" not in dbv.flags
):
name = "_wo_" + name
@@ -2961,7 +2997,7 @@ class HttpCli(object):
raise Pebkac(500, t % zt)
ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
- if self.is_vproxied:
+ if self.is_vproxied and not self.args.up_site:
if "purl" in ret:
ret["purl"] = self.args.SR + ret["purl"]
@@ -3229,7 +3265,7 @@ class HttpCli(object):
if num_left < 0:
if bail1:
return False
- raise Pebkac(500, "unconfirmed; see serverlog")
+ raise Pebkac(500, "unconfirmed; see fileserver log")
if not num_left and fpool:
with self.u2mutex:
@@ -3839,9 +3875,9 @@ class HttpCli(object):
errmsg = "ERROR: " + errmsg
if halg:
- file_fmt = '{0}: {1} // {2} // {3} bytes // {5} {6}\n'
+ file_fmt = '{0}: {1} // {2} // {3} bytes // {5} {6}\n'
else:
- file_fmt = '{3} bytes // {5} {6}\n'
+ file_fmt = '{3} bytes // {5} {6}\n'
for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
vsuf = ""
@@ -3859,25 +3895,31 @@ class HttpCli(object):
if "media" in self.uparam or "medialinks" in vfs.flags:
vsuf += "&v" if vsuf else "?v"
- vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
- rel_url = quotep(self.args.RS + vpath) + vsuf
+ vpath = vjoin(upload_vpath, lfn)
+ if self.args.up_site:
+ ah_url = j_url = self.args.up_site + quotep(vpath) + vsuf
+ rel_url = "/" + j_url.split("//", 1)[-1].split("/", 1)[-1]
+ else:
+ ah_url = rel_url = "/%s%s%s" % (self.args.RS, quotep(vpath), vsuf)
+ j_url = "%s://%s%s" % (
+ "https" if self.is_https else "http",
+ self.host,
+ rel_url,
+ )
+
msg += file_fmt.format(
halg,
sha_hex[:56],
sha_b64,
sz,
- rel_url,
+ ah_url,
html_escape(ofn, crlf=True),
vsuf,
)
# truncated SHA-512 prevents length extension attacks;
# using SHA-512/224, optionally SHA-512/256 = :64
jpart = {
- "url": "{}://{}/{}".format(
- "https" if self.is_https else "http",
- self.host,
- rel_url,
- ),
+ "url": j_url,
"sz": sz,
"fn": lfn,
"fn_orig": ofn,
@@ -5434,8 +5476,8 @@ class HttpCli(object):
if self.args.have_unlistc:
allvols = self.asrv.vfs.all_nodes
- rvol = [x for x in rvol if "unlistcr" not in allvols[x[1:-1]].flags]
- wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags]
+ rvol = [x for x in rvol if "unlistcr" not in allvols[x.strip("/")].flags]
+ wvol = [x for x in wvol if "unlistcw" not in allvols[x.strip("/")].flags]
fmt = self.uparam.get("ls", "")
if not fmt and self.ua.startswith(("curl/", "fetch")):
@@ -6169,8 +6211,20 @@ class HttpCli(object):
zsl = [html_escape(zst[0]) for zst in zstl]
r[4] = "
".join(zsl)
+ if self.args.shr_site:
+ site = self.args.shr_site[:-1]
+ elif self.is_vproxied:
+ site = self.args.SR
+ else:
+ site = ""
+
html = self.j2s(
- "shares", this=self, shr=self.args.shr, rows=rows, now=int(time.time())
+ "shares",
+ this=self,
+ shr=self.args.shr,
+ site=site,
+ rows=rows,
+ now=int(time.time()),
)
self.reply(html.encode("utf-8"), status=200)
return True
@@ -6325,14 +6379,22 @@ class HttpCli(object):
fn = quotep(fns[0]) if len(fns) == 1 else ""
# NOTE: several clients (frontend, party-up) expect url at response[15:]
- surl = "created share: %s://%s%s%s%s/%s" % (
- "https" if self.is_https else "http",
- self.host,
- self.args.SR,
- self.args.shr,
- skey,
- fn,
- )
+ if self.args.shr_site:
+ surl = "created share: %s%s%s/%s" % (
+ self.args.shr_site,
+ self.args.shr[1:],
+ skey,
+ fn,
+ )
+ else:
+ surl = "created share: %s://%s%s%s%s/%s" % (
+ "https" if self.is_https else "http",
+ self.host,
+ self.args.SR,
+ self.args.shr,
+ skey,
+ fn,
+ )
self.loud_reply(surl, status=201)
return True
@@ -6450,11 +6512,12 @@ class HttpCli(object):
if self.args.have_unlistc:
rvol = [x for x in rvol if "unlistcr" not in allvols[x].flags]
wvol = [x for x in wvol if "unlistcw" not in allvols[x].flags]
- vols = list(set(rvol + wvol))
+ vols = [(x, allvols[x]) for x in list(set(rvol + wvol))]
if self.vpath:
zs = "%s/" % (self.vpath,)
- vols = [x[len(zs) :] for x in vols if x.startswith(zs)]
- vols = [x.split("/", 1)[0] for x in vols if x]
+ vols = [(x[len(zs) :], y) for x, y in vols if x.startswith(zs)]
+ vols = [(x.split("/", 1)[0], y) for x, y in vols]
+ vols = list(({x: y for x, y in vols if x}).items())
if not vols and self.vpath:
return self.tx_404(True)
dirs = [
@@ -6467,9 +6530,9 @@ class HttpCli(object):
"tags": e_d,
"dt": 0,
"name": 0,
- "perms": allvols[x].get_perms("", self.uname),
+ "perms": vn.get_perms("", self.uname),
}
- for x in sorted(vols)
+ for x, vn in sorted(vols)
]
ls = {
"dirs": dirs,
@@ -6722,7 +6785,7 @@ class HttpCli(object):
vrem = vjoin(vrem, fn)
abspath = ap2
break
- elif self.vpath.rsplit("/", 1)[-1] in ("index.htm", "index.html"):
+ elif self.vpath.rsplit("/", 1)[-1] in IDX_HTML:
fk_pass = True
if not is_dir and (self.can_read or self.can_get):
@@ -6910,6 +6973,14 @@ class HttpCli(object):
if "zip" in self.uparam or "tar" in self.uparam:
raise Pebkac(403)
+ zsl = j2a["files"] = []
+ if is_js:
+ j2a["ls0"] = cgv["ls0"] = {
+ "dirs": zsl,
+ "files": zsl,
+ "taglist": zsl,
+ }
+
html = self.j2s(tpl, **j2a)
self.reply(html.encode("utf-8", "replace"))
return True
@@ -7092,13 +7163,16 @@ class HttpCli(object):
and "v" not in self.uparam
and not is_opds
):
- idx_html = set(["index.htm", "index.html"])
for item in files:
- if item["name"] in idx_html:
+ if item["name"] in IDX_HTML:
# do full resolve in case of shadowed file
vp = vjoin(self.vpath.split("?")[0], item["name"])
vn, rem = self.asrv.vfs.get(vp, self.uname, True, False)
ap = vn.canonical(rem)
+ if not self.trailing_slash and bos.path.isfile(ap):
+ return self.redirect(
+ self.vpath + "/", flavor="redirecting to", use302=True
+ )
return self.tx_file(ap) # is no-cache
if icur:
diff --git a/copyparty/mtag.py b/copyparty/mtag.py
index 41c28a09..cedc0c27 100644
--- a/copyparty/mtag.py
+++ b/copyparty/mtag.py
@@ -465,6 +465,8 @@ class MTag(object):
"ffprobe" if args.no_mutagen or (HAVE_FFPROBE and EXE) else "mutagen"
)
self.can_ffprobe = HAVE_FFPROBE and not args.no_mtag_ff
+ self.read_xattrs = args.have_db_xattr
+ self.get = self._get_xattr if self.read_xattrs else self._get_main
mappings = args.mtm
or_ffprobe = " or FFprobe"
@@ -486,7 +488,13 @@ class MTag(object):
msg = "found FFprobe but it was disabled by --no-mtag-ff"
self.log(msg, c=3)
+ if self.read_xattrs and not self.usable:
+ t = "don't have the necessary dependencies to read conventional media tags, but will read xattrs"
+ self.log(t)
+ self.usable = True
+
if not self.usable:
+ self._get = None
if EXE:
t = "copyparty.exe cannot use mutagen; need ffprobe.exe to read media tags: "
self.log(t + FFMPEG_URL)
@@ -645,7 +653,36 @@ class MTag(object):
return r1
- def get(self, abspath: str) -> dict[str, Union[str, float]]:
+ def _get_xattr(
+ self, abspath: str, vf: dict[str, Any]
+ ) -> dict[str, Union[str, float]]:
+ ret = self._get_main(abspath, vf) if self._get else {}
+ if "db_xattr_no" in vf:
+ try:
+ neg = vf["db_xattr_no"]
+ zsl = os.listxattr(abspath)
+ zsl = [x for x in zsl if x not in neg]
+ for xattr in zsl:
+ zb = os.getxattr(abspath, xattr)
+ ret[xattr] = zb.decode("utf-8", "replace")
+ except:
+ self.log("failed to read xattrs from [%s]\n%s", abspath, min_ex(), 3)
+ elif "db_xattr_yes" in vf:
+ for xattr in vf["db_xattr_yes"]:
+ if "=" in xattr:
+ xattr, name = xattr.split("=", 1)
+ else:
+ name = xattr
+ try:
+ zs = os.getxattr(abspath, xattr)
+ ret[name] = zs.decode("utf-8", "replace")
+ except:
+ pass
+ return ret
+
+ def _get_main(
+ self, abspath: str, vf: dict[str, Any]
+ ) -> dict[str, Union[str, float]]:
ext = abspath.split(".")[-1].lower()
if ext not in self.args.au_unpk:
return self._get(abspath)
diff --git a/copyparty/sftpd.py b/copyparty/sftpd.py
index deb92042..790ff5b6 100644
--- a/copyparty/sftpd.py
+++ b/copyparty/sftpd.py
@@ -26,7 +26,6 @@ from .__init__ import ANYWIN, TYPE_CHECKING
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
from .bos import bos
from .util import (
- FN_EMB,
VF_CAREFUL,
Daemon,
ODict,
@@ -328,7 +327,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
vn, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
if (
w
- and fn.lower() in FN_EMB
+ and fn.lower() in vn.flags["emb_all"]
and self.uname not in vn.axs.uread
and "wo_up_readme" not in vn.flags
):
diff --git a/copyparty/svchub.py b/copyparty/svchub.py
index a66848b1..39c0491a 100644
--- a/copyparty/svchub.py
+++ b/copyparty/svchub.py
@@ -124,6 +124,7 @@ class SvcHub(object):
self.argv = argv
self.E: EnvParams = args.E
self.no_ansi = args.no_ansi
+ self.flo = args.flo
self.tz = UTC if args.log_utc else None
self.logf: Optional[typing.TextIO] = None
self.logf_base_fn = ""
@@ -315,6 +316,10 @@ class SvcHub(object):
args.doctitle = args.doctitle.replace("--name", args.vname)
args.bname = args.bname.replace("--name", args.vname) or args.vname
+ for zs in "shr_site up_site".split():
+ if getattr(args, zs) == "--site":
+ setattr(args, zs, args.site)
+
if args.log_fk:
args.log_fk = re.compile(args.log_fk)
@@ -1113,6 +1118,12 @@ class SvcHub(object):
vsa = [x.lower() for x in vsa if x]
setattr(al, k + "_set", set(vsa))
+ for k in "smsg".split(" "):
+ vs = getattr(al, k)
+ vsa = [x.strip() for x in vs.split(",")]
+ vsa = [x.upper() for x in vsa if x]
+ setattr(al, k + "_set", set(vsa))
+
zs = "dav_ua1 sus_urls nonsus_urls ua_nodav ua_nodoc ua_nozip"
for k in zs.split(" "):
vs = getattr(al, k)
@@ -1567,21 +1578,38 @@ class SvcHub(object):
dt.microsecond // self.log_div,
)
- if c and not self.args.no_ansi:
- if isinstance(c, int):
+ if self.flo == 1:
+ fmt = "@%s [%-21s] %s\n"
+ if not c:
+ if "\033" in msg:
+ msg += "\033[0m"
+ 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)
- if "\033" in src:
- src += "\033[0m"
+ if "\033" in src:
+ src += "\033[0m"
+ else:
+ if not c:
+ fmt = "@%s LOG [%-21s] %s\n"
+ elif c == 1:
+ fmt = "@%s CRIT [%-21s] %s\n"
+ elif c == 3:
+ fmt = "@%s WARN [%-21s] %s\n"
+ elif c == 6:
+ fmt = "@%s BTW [%-21s] %s\n"
+ else:
+ fmt = "@%s LOG [%-21s] %s\n"
- if "\033" in msg:
- msg += "\033[0m"
+ if "\033" in src:
+ src = RE_ANSI.sub("", src)
+ if "\033" in msg:
+ msg = RE_ANSI.sub("", msg)
- self.logf.write("@%s [%-21s] %s\n" % (ts, src, msg))
+ self.logf.write(fmt % (ts, src, msg))
if not self.args.no_logflush:
self.logf.flush()
@@ -1611,9 +1639,10 @@ class SvcHub(object):
if self.logf:
self.logf.write(zs)
- fmt = "\033[36m%s \033[33m%-21s \033[0m%s\n"
if self.no_ansi:
- if c == 1:
+ 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"
@@ -1621,12 +1650,16 @@ class SvcHub(object):
fmt = "%s %-21s BTW: %s\n"
else:
fmt = "%s %-21s LOG: %s\n"
+
if "\033" in msg:
msg = RE_ANSI.sub("", msg)
if "\033" in src:
src = RE_ANSI.sub("", src)
- elif c:
- if isinstance(c, int):
+ else:
+ 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)
diff --git a/copyparty/tftpd.py b/copyparty/tftpd.py
index 9eb76643..8e4c70b7 100644
--- a/copyparty/tftpd.py
+++ b/copyparty/tftpd.py
@@ -37,7 +37,6 @@ from .__init__ import EXE, PY2, TYPE_CHECKING
from .authsrv import VFS
from .bos import bos
from .util import (
- FN_EMB,
UTC,
BytesIO,
Daemon,
@@ -268,7 +267,7 @@ class Tftpd(object):
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
if perms[1] and "*" not in vfs.axs.uread and "wo_up_readme" not in vfs.flags:
zs, fn = vsplit(vpath)
- if fn.lower() in FN_EMB:
+ if fn.lower() in vfs.flags["emb_all"]:
vpath = vjoin(zs, "_wo_" + fn)
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
diff --git a/copyparty/up2k.py b/copyparty/up2k.py
index 06696cf4..b5356bfd 100644
--- a/copyparty/up2k.py
+++ b/copyparty/up2k.py
@@ -61,6 +61,8 @@ from .util import (
s3dec,
s3enc,
sanitize_fn,
+ set_ap_perms,
+ set_fperms,
sfsenc,
spack,
statdir,
@@ -130,6 +132,7 @@ class Mpqe(object):
self,
mtp: dict[str, MParser],
entags: set[str],
+ vf: dict[str, Any],
w: str,
abspath: str,
oth_tags: dict[str, Any],
@@ -137,6 +140,7 @@ class Mpqe(object):
# mtp empty = mtag
self.mtp = mtp
self.entags = entags
+ self.vf = vf
self.w = w
self.abspath = abspath
self.oth_tags = oth_tags
@@ -1148,7 +1152,7 @@ class Up2k(object):
ft = "\033[0;32m{}{:.0}"
ff = "\033[0;35m{}{:.0}"
fv = "\033[0;36m{}:\033[90m{}"
- zs = "bcasechk du_iwho emb_lgs emb_mds ext_th_d html_head html_head_d html_head_s ls_q_m put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
+ zs = "bcasechk du_iwho emb_all emb_lgs emb_mds ext_th_d html_head html_head_d html_head_s ls_q_m put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
fx = set(zs.split())
fd = vf_bmap()
fd.update(vf_cmap())
@@ -2199,7 +2203,7 @@ class Up2k(object):
abspath = djoin(ptop, rd, fn)
self.pp.msg = "c%d %s" % (nq, abspath)
if not mpool:
- n_tags = self._tagscan_file(cur, entags, w, abspath, ip, at, un)
+ n_tags = self._tagscan_file(cur, entags, flags, w, abspath, ip, at, un)
else:
oth_tags = {}
if ip:
@@ -2209,7 +2213,7 @@ class Up2k(object):
if un:
oth_tags["up_by"] = un
- mpool.put(Mpqe({}, entags, w, abspath, oth_tags))
+ mpool.put(Mpqe({}, entags, flags, w, abspath, oth_tags))
with self.mutex:
n_tags = len(self._flush_mpool(cur))
@@ -2316,9 +2320,10 @@ class Up2k(object):
return
entags = self.entags[ptop]
+ vf = self.flags[ptop]
parsers = {}
- for parser in self.flags[ptop]["mtp"]:
+ for parser in vf["mtp"]:
try:
parser = MParser(parser)
except:
@@ -2393,7 +2398,7 @@ class Up2k(object):
if un:
oth_tags["up_by"] = un
- jobs.append(Mpqe(parsers, set(), w, abspath, oth_tags))
+ jobs.append(Mpqe(parsers, set(), vf, w, abspath, oth_tags))
in_progress[w] = True
with self.mutex:
@@ -2524,7 +2529,7 @@ class Up2k(object):
return
for _ in range(mpool.maxsize):
- mpool.put(Mpqe({}, set(), "", "", {}))
+ mpool.put(Mpqe({}, set(), {}, "", "", {}))
mpool.join()
@@ -2543,7 +2548,7 @@ class Up2k(object):
t = "tag-thr: {}({})"
self.log(t.format(self.mtag.backend, qe.abspath), "90")
- tags = self.mtag.get(qe.abspath) if st.st_size else {}
+ tags = self.mtag.get(qe.abspath, qe.vf) if st.st_size else {}
else:
if self.args.mtag_vv:
t = "tag-thr: {}({})"
@@ -2576,6 +2581,7 @@ class Up2k(object):
self,
write_cur: "sqlite3.Cursor",
entags: set[str],
+ vf: dict[str, Any],
wark: str,
abspath: str,
ip: str,
@@ -2594,7 +2600,7 @@ class Up2k(object):
return 0
try:
- tags = self.mtag.get(abspath) if st.st_size else {}
+ tags = self.mtag.get(abspath, vf) if st.st_size else {}
except Exception as ex:
self._log_tag_err("", abspath, ex)
return 0
@@ -3542,7 +3548,7 @@ class Up2k(object):
if self.args.nw:
return
- linked = False
+ linked = 0
try:
if rm and bos.path.exists(dst):
wunlink(self.log, dst, flags)
@@ -3556,6 +3562,8 @@ class Up2k(object):
try:
with open(fsenc(src), "rb") as fi, open(fsenc(dst), "wb") as fo:
fcntl.ioctl(fo.fileno(), fcntl.FICLONE, fi.fileno())
+ if "fperms" in flags:
+ set_fperms(fo, flags)
except:
if bos.path.exists(dst):
wunlink(self.log, dst, flags)
@@ -3593,7 +3601,7 @@ class Up2k(object):
try:
if "hardlink" in flags:
os.link(fsenc(absreal(src)), fsenc(dst))
- linked = True
+ linked = 2
except Exception as ex:
self.log("cannot hardlink: " + repr(ex))
if "hardlinkonly" in flags:
@@ -3612,7 +3620,7 @@ class Up2k(object):
else:
os.symlink(fsenc(lsrc), fsenc(ldst))
- linked = True
+ linked = 1
except Exception as ex:
if str(ex) != "reflink":
self.log("cannot link; creating copy: " + repr(ex))
@@ -3626,8 +3634,11 @@ class Up2k(object):
raise Exception(t % (src, fsrc, dst))
trystat_shutil_copy2(self.log, fsenc(csrc), fsenc(dst))
- if lmod and (not linked or SYMTIME):
- bos.utime_c(self.log, dst, int(lmod), False)
+ if linked < 2 and (linked < 1 or SYMTIME):
+ if lmod:
+ bos.utime_c(self.log, dst, int(lmod), False)
+ if "fperms" in flags:
+ set_ap_perms(dst, flags)
def handle_chunks(
self, ptop: str, wark: str, chashes: list[str]
@@ -4503,6 +4514,8 @@ class Up2k(object):
bos.utime(dabs, times, False)
except:
pass
+ if "fperms" in dvn.flags:
+ set_ap_perms(dabs, dvn.flags)
if xac:
runhook(
@@ -4788,6 +4801,8 @@ class Up2k(object):
wunlink(self.log, sabs, svn.flags)
else:
atomic_move(self.log, sabs, dabs, svn.flags)
+ if svn != dvn and "fperms" in dvn.flags:
+ set_ap_perms(dabs, dvn.flags)
except OSError as ex:
if ex.errno != errno.EXDEV:
@@ -4821,6 +4836,8 @@ class Up2k(object):
bos.utime(dabs, times, False)
except:
pass
+ if "fperms" in dvn.flags:
+ set_ap_perms(dabs, dvn.flags)
wunlink(self.log, sabs, svn.flags)
@@ -5404,7 +5421,7 @@ class Up2k(object):
# self.log("\n " + repr([ptop, rd, fn]))
abspath = djoin(ptop, rd, fn)
try:
- tags = self.mtag.get(abspath) if sz else {}
+ tags = self.mtag.get(abspath, self.flags[ptop]) if sz else {}
ntags1 = len(tags)
parsers = self._get_parsers(ptop, tags, abspath)
if self.args.mtag_vv:
diff --git a/copyparty/util.py b/copyparty/util.py
index 05cd4b02..1b9b5431 100644
--- a/copyparty/util.py
+++ b/copyparty/util.py
@@ -961,11 +961,13 @@ class _Unrecv(object):
self.s = s
self.log = log
self.buf: bytes = b""
+ self.nb = 0
def recv(self, nbytes: int, spins: int = 1) -> bytes:
if self.buf:
ret = self.buf[:nbytes]
self.buf = self.buf[nbytes:]
+ self.nb += len(ret)
return ret
while True:
@@ -985,6 +987,7 @@ class _Unrecv(object):
if not ret:
raise UnrecvEOF("client stopped sending data")
+ self.nb += len(ret)
return ret
def recv_ex(self, nbytes: int, raise_on_trunc: bool = True) -> bytes:
@@ -1012,6 +1015,7 @@ class _Unrecv(object):
def unrecv(self, buf: bytes) -> None:
self.buf = buf + self.buf
+ self.nb -= len(buf)
# !rm.yes>
@@ -1024,6 +1028,7 @@ class _LUnrecv(object):
self.s = s
self.log = log
self.buf = b""
+ self.nb = 0
def recv(self, nbytes: int, spins: int) -> bytes:
if self.buf:
@@ -1031,6 +1036,7 @@ class _LUnrecv(object):
self.buf = self.buf[nbytes:]
t = "\033[0;7mur:pop:\033[0;1;32m {}\n\033[0;7mur:rem:\033[0;1;35m {}\033[0m"
print(t.format(ret, self.buf))
+ self.nb += len(ret)
return ret
ret = self.s.recv(nbytes)
@@ -1039,6 +1045,7 @@ class _LUnrecv(object):
if not ret:
raise UnrecvEOF("client stopped sending data")
+ self.nb += len(ret)
return ret
def recv_ex(self, nbytes: int, raise_on_trunc: bool = True) -> bytes:
@@ -1067,6 +1074,7 @@ class _LUnrecv(object):
def unrecv(self, buf: bytes) -> None:
self.buf = buf + self.buf
+ self.nb -= len(buf)
t = "\033[0;7mur:push\033[0;1;31m {}\n\033[0;7mur:rem:\033[0;1;35m {}\033[0m"
print(t.format(buf, self.buf))
@@ -1807,6 +1815,11 @@ class MultipartParser(object):
self.log = log_func
self.args = args
self.headers = http_headers
+ try:
+ self.clen = int(http_headers["content-length"])
+ sr.nb = 0
+ except:
+ self.clen = 0
self.re_ctype = RE_CTYPE
self.re_cdisp = RE_CDISP
@@ -1962,7 +1975,10 @@ class MultipartParser(object):
if tail == b"--":
# EOF indicated by this immediately after final boundary
- tail = self.sr.recv_ex(2, False)
+ if self.clen == self.sr.nb:
+ tail = b"\r\n" # dillo doesn't terminate with trailing \r\n
+ else:
+ tail = self.sr.recv_ex(2, False)
run = False
if tail != b"\r\n":
@@ -1980,6 +1996,8 @@ class MultipartParser(object):
def parse(self) -> None:
boundary = get_boundary(self.headers)
+ if boundary.startswith('"') and boundary.endswith('"'):
+ boundary = boundary[1:-1] # dillo uses quotes
self.log("boundary=%r" % (boundary,))
# spec says there might be junk before the first boundary,
@@ -2721,6 +2739,14 @@ def set_fperms(f: Union[typing.BinaryIO, typing.IO[Any]], vf: dict[str, Any]) ->
os.fchown(fno, vf["uid"], vf["gid"])
+def set_ap_perms(ap: str, vf: dict[str, Any]) -> None:
+ zb = fsenc(ap)
+ if "chmod_f" in vf:
+ os.chmod(zb, vf["chmod_f"])
+ if "chown" in vf:
+ os.chown(zb, vf["uid"], vf["gid"])
+
+
def trystat_shutil_copy2(log: "NamedLogger", src: bytes, dst: bytes) -> bytes:
try:
return shutil.copy2(src, dst)
diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css
index 53b59bff..aa2e1fba 100644
--- a/copyparty/web/browser.css
+++ b/copyparty/web/browser.css
@@ -4168,4 +4168,14 @@ html.e #detree {
border-color: var(--a);
border-radius: .2em;
padding: .2em .3em;
-}
\ No newline at end of file
+}
+
+.selbox {
+ position: fixed;
+ border: .5em solid #f0f;
+ border: .2em solid var(--btn-1h-bg);
+ background-color: rgba(128, 128, 128, 0.6);
+ background-color: rgb(from var(--btn-1h-bg) r g b / 0.5);
+ pointer-events: none;
+ z-index: 99;
+}
diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html
index e8a91c5a..b372cd58 100644
--- a/copyparty/web/browser.html
+++ b/copyparty/web/browser.html
@@ -1,5 +1,5 @@
-
+
.PARTIAL file extension. Please press CANCEL or Escape to do this.\n\nPressing OK / Enter will ignore this warning and continue downloading the .PARTIAL scratchfile instead, which will almost definitely give you corrupted data.",
@@ -664,6 +668,8 @@ if (1)
"rc_nfi": "new file",
"rc_sal": "select all",
"rc_sin": "invert selection",
+ "rc_shf": "share this folder",
+ "rc_shs": "share selection",
"lang_set": "refresh to make the change take effect?",
};
@@ -909,6 +915,7 @@ ebi('op_cfg').innerHTML = (
' ' + L.ct_grid + '\n' +
' \n' +
- (!MOBILE ? '' : '') +
+ (!MOBILE ? '' : '') +
'.PARTIAL 文件扩展名的同名文件。请按取消或 Escape 执行此操作。\n\n按 确定 / Enter 将忽略此警告并继续下载 .PARTIAL 临时文件,这几乎肯定会导致数据损坏。",
@@ -653,10 +656,13 @@ Ls.chi = {
"rc_cut": "剪切", //m
"rc_cpy": "复制", //m
"rc_pst": "粘贴", //m
+ "rc_rnm": "重命名", //m
"rc_nfo": "新建文件夹", //m
"rc_nfi": "新建文件", //m
"rc_sal": "全选", //m
"rc_sin": "反选", //m
+ "rc_shf": "共享此文件夹", //m
+ "rc_shs": "共享所选内容", //m
"lang_set": "刷新以使更改生效?",
diff --git a/copyparty/web/tl/cze.js b/copyparty/web/tl/cze.js
index 41f7d770..a1c8c088 100644
--- a/copyparty/web/tl/cze.js
+++ b/copyparty/web/tl/cze.js
@@ -230,6 +230,7 @@ Ls.cze = {
"ct_ttips": '◔ ◡ ◔">ℹ️ nápovědy',
"ct_thumb": 'v zobrazení mřížky přepnout ikony nebo náhledy$NKlávesová zkratka: T">🖼️ náhledy',
"ct_csel": 'použít CTRL a SHIFT pro výběr souborů v zobrazení mřížky">výběr',
+ "ct_dsel": 'použít tažený výběr v zobrazení mřížky">tažení', //m
"ct_dl": 'vynutit stažení (nezobrazovat inline) při kliknutí na soubor">dl', //m
"ct_ihop": 'když se zavře prohlížeč obrázků, posunout dolů k naposledy zobrazenému souboru">g⮯',
"ct_dots": 'zobrazit skryté soubory (pokud to server povoluje)">dotfiles',
@@ -268,6 +269,7 @@ Ls.cze = {
"cdt_ask": "při posunování na konec,$Nmísto načítání více souborů,$N se zeptat co dělat",
"cdt_hsort": "kolik pravidel řazení (<code>,sorthref</code>) zahrnout do media-URL. Nastavení na 0 bude také ignorovat pravidla řazení zahrnutá v media odkazech při kliknutí na ně",
"cdt_ren": "povolit vlastní kontextovou nabídku, běžnou nabídku lze otevřít podržením klávesy shift a kliknutím pravým tlačítkem", //m
+ "cdt_rdb": "zobrazit běžné menu pravého tlačítka, když je vlastní již otevřené a znovu se klikne pravým", //m
"tt_entree": "zobrazit navigační panel (postranní strom adresářů)$NKlávesová zkratka: B",
"tt_detree": "zobrazit drobečkovou navigaci$NKlávesová zkratka: B",
@@ -358,6 +360,7 @@ Ls.cze = {
"f_anota": "pouze {0} z {1} položek bylo vybráno;\npro výběr celé složky nejprve přejděte na konec",
"f_dls": 'odkazy na soubory v aktuální složce byly\nzměněny na odkazy ke stažení',
+ "f_dl_nd": 'přeskakuje se složka (místo toho použijte stažení zip/tar):\n', //m
"f_partial": "Pro bezpečné stažení souboru, který se aktuálně nahrává, klikněte prosím na soubor se stejným názvem, ale bez přípony .PARTIAL. Stiskněte prosím Zrušit nebo Escape.\n\nStisknutím OK / Enter ignorujete toto varování a pokračujete ve stahování .PARTIAL dočasného souboru, což téměř jistě vyústí jako poškozená data.",
@@ -657,10 +660,13 @@ Ls.cze = {
"rc_cut": "vyjmout", //m
"rc_cpy": "kopírovat", //m
"rc_pst": "vložit", //m
+ "rc_rnm": "přejmenovat", //m
"rc_nfo": "nová složka", //m
"rc_nfi": "nový soubor", //m
"rc_sal": "vybrat vše", //m
"rc_sin": "invertovat výběr", //m
+ "rc_shf": "sdílet tuto složku", //m
+ "rc_shs": "sdílet výběr", //m
"lang_set": "obnovit stránku, aby se změna projevila?",
diff --git a/copyparty/web/tl/deu.js b/copyparty/web/tl/deu.js
index 80eab254..c9e35b3e 100644
--- a/copyparty/web/tl/deu.js
+++ b/copyparty/web/tl/deu.js
@@ -226,6 +226,7 @@ Ls.deu = {
"ct_ttips": '◔ ◡ ◔">ℹ️ Tooltips',
"ct_thumb": 'In Raster-Ansicht, zwischen Icons und Vorschau wechseln$NHotkey: T">🖼️ Vorschaubilder',
"ct_csel": 'Benutze STRG und UMSCHALT für Dateiauswahl in Raster-Ansicht">sel',
+ "ct_dsel": 'Ziehauswahl in Raster-Ansicht verwenden">ziehen', //m
"ct_dl": 'Beim Klick auf Dateien sie immer herunterladen (nicht einbetten)">dl',
"ct_ihop": 'Wenn die Bildanzeige geschlossen ist, scrolle runter zu den zuletzt angesehenen Dateien">g⮯',
"ct_dots": 'Verstecke Dateien anzeigen (wenn durch den Server erlaubt)">dotfiles',
@@ -264,6 +265,7 @@ Ls.deu = {
"cdt_ask": "beim Runterscrollen nach $NAktion fragen statt mehr,$NDateien zu laden",
"cdt_hsort": "Menge an Sortierregeln (<code>,sorthref</code>) in Media-URLs enthalten sein sollen. Ein Wert von 0 sorgt dafür, dass Sortierregeln in Media-URLs ignoriert werden",
"cdt_ren": "spezielles Rechtsklick-Menü aktivieren, das Browser-Menü ist weiterhin mit Shift + Rechtsklick erreichbar",
+ "cdt_rdb": "normales Rechtsklick-Menü anzeigen, wenn das benutzerdefinierte bereits offen ist und erneut rechts geklickt wird", //m
"tt_entree": "Navpane anzeigen (Ordnerbaum Sidebar)$NHotkey: B",
"tt_detree": "Breadcrumbs anzeigen$NHotkey: B",
@@ -354,6 +356,7 @@ Ls.deu = {
"f_anota": "nur {0} der {1} Elemente wurden ausgewählt;\num den gesamten Ordner auszuwählen, zuerst nach unten scrollen",
"f_dls": 'die Dateilinks im aktuellen Ordner wurden\nin Downloadlinks geändert',
+ "f_dl_nd": 'ordner wird übersprungen (bitte zip/tar-download verwenden):\n', //m
"f_partial": "Um eine Datei sicher herunterzuladen, die gerade hochgeladen wird, klicke bitte die Datei mit dem gleichen Namen, aber ohne die .PARTIAL-Endung. Bitte drücke Abbrechen oder Escape, um dies zu tun.\n\nWenn du auf OK / Eingabe drückst, ignorierst du diese Warnung und lädst die .PARTIAL-Datei herunter, die ziemlich sicher beschädigte Daten enthält.",
@@ -653,10 +656,13 @@ Ls.deu = {
"rc_cut": "ausschneiden",
"rc_cpy": "kopieren",
"rc_pst": "einfügen",
+ "rc_rnm": "umbenennen", //m
"rc_nfo": "neuer Ordner",
"rc_nfi": "neue Datei",
"rc_sal": "alles auswählen",
"rc_sin": "auswahl umkehren",
+ "rc_shf": "diesen ordner teilen", //m
+ "rc_shs": "auswahl teilen", //m
"lang_set": "Neuladen um Änderungen anzuwenden?",
diff --git a/copyparty/web/tl/epo.js b/copyparty/web/tl/epo.js
index 39a6024c..c55ba251 100644
--- a/copyparty/web/tl/epo.js
+++ b/copyparty/web/tl/epo.js
@@ -84,8 +84,8 @@ Ls.epo = {
["M", "fermi dosieron"],
["E", "redakti dosieron"],
["S", "elekti dosieron (por eltondado/kopiado/alinomado)"],
- ["Y", "elŝuti tekstodosieron"], //m
- ["⇧ J", "beligi json"], //m
+ ["Y", "elŝuti tekstodosieron"],
+ ["⇧ J", "beligi JSONon"],
]
],
@@ -118,7 +118,7 @@ Ls.epo = {
"ot_unpost": "unpost: forigi viaj plej lastaj alŝutoj, aŭ ĉesigi nefinigitajn",
"ot_bup": "bup: fundamenta alŝutilo, funkias eĉ kun netscape 4.0",
"ot_mkdir": "mkdir: krei novan dosierujon",
- "ot_md": "new-file: krei novan tekstodosieron", //m
+ "ot_md": "new-file: krei novan tekstodosieron",
"ot_msg": "msg: sendi mesaĝon al servila protokolo",
"ot_mp": "agordoj de medialudilo",
"ot_cfg": "aliaj agordoj",
@@ -127,7 +127,7 @@ Ls.epo = {
"ot_noie": 'Bonvolu uzi retumilojn Chrome / Firefox / Edge',
"ab_mkdir": "krei dosierujon",
- "ab_mkdoc": "krei tekstodosieron", //m
+ "ab_mkdoc": "krei tekstodosieron",
"ab_msg": "sendi mesaĝon al protokolo",
"ay_path": "iri al dosierujoj",
@@ -155,7 +155,7 @@ Ls.epo = {
"ul_par": "paralelaj alŝutoj:",
"ut_rand": "hazardigi dosiernomojn",
"ut_u2ts": "kopii la tempon de lasta modifo$Nel via dosiersistemo al la servilo\">📅",
- "ut_ow": "ĉu anstataŭigi dosierojn ĉe la servilo?$N🛡️: neniam (dosiero estos alŝutita kun nova dosiernomo)$N🕒: anstataŭigi, se servila dosiero estas pli malnova ol via$N♻️: ĉiam anstataŭigi, se dosieroj estas malsamaj$N⏭️: senkondiĉe preterlasi ĉiujn ekzistantajn dosierojn", //m
+ "ut_ow": "ĉu anstataŭigi dosierojn ĉe la servilo?$N🛡️: neniam (dosiero estos alŝutita kun nova dosiernomo)$N🕒: anstataŭigi, se servila dosiero estas pli malnova ol via$N♻️: ĉiam anstataŭigi, se dosieroj estas malsamaj$N⏭️: senkondiĉe preterlasi ĉiujn ekzistantajn dosierojn",
"ut_mt": "daŭri kalkuladon de kontrolsumoj por aliaj dosieroj dum alŝutado$N$Nmalŝaltinda, se via procesoro aŭ disko ne estas sufiĉe rapidaj",
"ut_ask": 'peti konfirmon antaŭ komenco de alŝutado">💭',
"ut_pot": "plirapidigi alŝutadon por malrapidaj komputiloj$Nper malkomplikado de fasado",
@@ -220,13 +220,14 @@ Ls.epo = {
"cl_reset": "restarigi",
"cl_hpick": "alklaki la kapojn de kolumnoj por kasi en la suban tabelon",
"cl_hcancel": "kaŝado de kolumno nuligita",
- "cl_rcm": "dekstra-klaka menuo", //m
+ "cl_rcm": "dekstra-klaka menuo",
"ct_grid": '田 krado',
"ct_ttips": '◔ ◡ ◔">ℹ️ ŝpruchelpiloj',
"ct_thumb": 'dum krado-vido, baskuli montradon de simboloj aŭ bildetoj$NFulmoklavo: T">🖼️ bildetoj',
"ct_csel": 'uzi STIR kaj MAJ por elekti dosierojn en krado-vido">elekto',
- "ct_dl": 'devigi elŝuton (ne montri enkadre) kiam dosiero estas alklakita">dl', //m
+ "ct_dsel": 'uzi tren-elekton en krado-vido">treni',
+ "ct_dl": 'devigi elŝuton (ne montri enkadre), kiam dosiero estas alklakita">dl',
"ct_ihop": 'montri la lastan viditan bildo-dosieron post fermado de bildo-vidilo">g⮯',
"ct_dots": 'montri kaŝitajn dosierojn (se servilo permesas)">kaŝitaj',
"ct_qdel": 'peti konfirmon nur unufoje antaŭ forigado">rapid-forig.',
@@ -263,7 +264,8 @@ Ls.epo = {
"cdt_lim": "maks. nombro de dosieroj por montri en dosierujo",
"cdt_ask": "je malsupro de paĝo, peti por ago$Nanstataŭ ŝarĝi pli da dosieroj",
"cdt_hsort": "kiom da ordigo-reguloj (<code>,sorthref</code>) inkludi en adreso de la paĝo. Se agordita kiel 0, reguloj, inkluditaj en la adreso, estos ignoritaj",
- "cdt_ren": "ebligi propran dekstra-klakan menuon, la normala menuo restas alirebla per shift + dekstra klako", //m
+ "cdt_ren": "ebligi propran dekstra-klakan menuon, la normala menuo restas alirebla per MAJ + dekstra klako",
+ "cdt_rdb": "montri la normalan dekstraklakan menuon, kiam la propra jam estas malfermita kaj oni denove dekstre klakas",
"tt_entree": "montri arbovidan navig-panelon$NFulmoklavo: B",
"tt_detree": "montri paĝnivelan navig-panelon$NFulmoklavo: B",
@@ -354,6 +356,7 @@ Ls.epo = {
"f_anota": "nur {0} de {1} eroj estis elektita;\nrulumi al la malsupro por elekti la tutan dosierujon",
"f_dls": 'la ligiloj de dosieroj en ĉi tiu dosierujo estis\nanstataŭigitaj per elŝuto-ligiloj',
+ "f_dl_nd": 'preterlasante dosierujon (uzu zip/tar-elŝuton anstataŭe):\n', //m
"f_partial": "Por sendifekta elŝuto de nune-alŝutata dosiero, elektu dosieron kun sama nomo, sed sen etendaĵo .PARTIAL. Bonvolu uzi la butonon \"Rezigni\" aŭ klavon ESK por fari tion.\n\nSe vi uzas OK / Enter, la provizora dosiero .PARTIAL estos elŝutita, kiu tre probable enhavas nekompletajn datumojn.",
@@ -456,7 +459,7 @@ Ls.epo = {
"tvt_prev": "montri malsekvan dokumenton$NFulmoklavo: i\">⬆ malsekva",
"tvt_next": "montri sekvan dokumenton$NFulmoklavo: K\">⬇ sekva",
"tvt_sel": "elekti dosieron ( por eltondado / kopiado / forigado / ... )$NFulmoklavo: S\">elekti",
- "tvt_j": "beligi json$NFulmoklavo: shift-J\">j", //m
+ "tvt_j": "beligi JSONon$NFulmoklavo: MAJ-J\">j",
"tvt_edit": "malfermi dosieron en teksto-redaktilo$NFulmoklavo: E\">✏️ redakti",
"tvt_tail": "observi ŝanĝojn en dosiero; novaj linioj estos tuje montritaj\">📡 gvati",
"tvt_wrap": "linifaldo\">↵",
@@ -653,10 +656,13 @@ Ls.epo = {
"rc_cut": "eltondi", //m
"rc_cpy": "kopii", //m
"rc_pst": "alglui", //m
+ "rc_rnm": "alinomi", //m
"rc_nfo": "nova dosierujo", //m
"rc_nfi": "nova dosiero", //m
"rc_sal": "elekti ĉion", //m
"rc_sin": "inversigi elekton", //m
+ "rc_shf": "kunhavigi ĉi tiun dosierujon", //m
+ "rc_shs": "kunhavigi elekton", //m
"lang_set": "ĉu reŝargi paĝon por efektivigi lingvo-ŝanĝon?",
diff --git a/copyparty/web/tl/fin.js b/copyparty/web/tl/fin.js
index 3ea70587..4ca9ed41 100644
--- a/copyparty/web/tl/fin.js
+++ b/copyparty/web/tl/fin.js
@@ -226,6 +226,7 @@ Ls.fin = {
"ct_ttips": '◔ ◡ ◔">ℹ️ vihjelaatikot',
"ct_thumb": 'valitse kuvakkeiden / pienoiskuvien välillä kuvanäkymässä $NPikanäppäin: T">🖼️ pienoiskuvat',
"ct_csel": 'käytä CTRL ja SHIFT tiedostojen valintaan kuvanäkymässä">valitse',
+ "ct_dsel": 'käytä aluevalintaa tiedostojen valintaan kuvanäkymässä">aluevalinta',
"ct_dl": 'pakota lataus (älä näytä upotettuna), kun tiedostoa napsautetaan">dl', //m
"ct_ihop": 'kun kuvakatselin suljetaan, vieritä alas viimeksi katsottuun tiedostoon">g⮯',
"ct_dots": 'näytä piilotetut tiedostot (jos palvelin sallii)">piilotiedostot',
@@ -264,6 +265,7 @@ Ls.fin = {
"cdt_ask": "sivun lopussa, sen sijaan että lataa $Nautomaattisesti lisää tiedostoja, kysy mitä tehdä",
"cdt_hsort": "kuinka monta lajittelusääntöä (<code>,sorthref</code>) sisällyttää media-URL:eihin. Tämän asettaminen nollaan jättää myös huomioimatta media-linkeissä sisällytetyt lajittelusäännöt kun napsautat niitä",
"cdt_ren": "ota käyttöön mukautettu valikko, tavallinen valikko on käytettävissä painamalla shift ja napsauttamalla oikealla", //m
+ "cdt_rdb": "näytä tavallinen oikean painikkeen valikko, kun mukautettu on jo auki ja oikeaa painiketta painetaan uudelleen", //m
"tt_entree": "näytä navigointipaneeli$NPikanäppäin: B",
"tt_detree": "näytä linkkipolku$NPikanäppäin: B",
@@ -354,6 +356,7 @@ Ls.fin = {
"f_anota": "vain {0} / {1} kohdetta valittiin;\nvalitaksesi koko hakemiston, vieritä ensin loppuun",
"f_dls": 'nykyisen hakemiston tiedostolinkit on\nvaihdettu latauslinkeiksi',
+ "f_dl_nd": 'ohitetaan kansio (käytä zip/tar-latausta sen sijaan):\n', //m
"f_partial": "Ladataksesi turvallisesti tiedoston joka on parhaillaan latautumassa, klikkaa tiedostoa jolla on sama nimi mutta ilman .PARTIAL päätettä. Paina PERUUTA tai Escape tehdäksesi tämän.\n\nOK / Enter painaminen sivuuttaa tämän varoituksen ja jatkaa .PARTIAL väliaikaistiedoston lataamista, mikä todennäköisesti antaa sinulle vioittunutta dataa.",
@@ -653,10 +656,13 @@ Ls.fin = {
"rc_cut": "Leikkaa", //m
"rc_cpy": "kopioi", //m
"rc_pst": "Liitä", //m
+ "rc_rnm": "nimeä uudelleen", //m
"rc_nfo": "uusi kansio", //m
"rc_nfi": "uusi tiedosto", //m
"rc_sal": "valitse kaikki", //m
"rc_sin": "käännä valinta", //m
+ "rc_shf": "jaa tämä kansio", //m
+ "rc_shs": "jaa valinta", //m
"lang_set": "ladataanko sivu uudestaan kielen vaihtamiseksi?",
diff --git a/copyparty/web/tl/fra.js b/copyparty/web/tl/fra.js
index 785eb4fb..aeb5b09a 100644
--- a/copyparty/web/tl/fra.js
+++ b/copyparty/web/tl/fra.js
@@ -226,6 +226,7 @@ Ls.fra = {
"ct_ttips": '◔ ◡ ◔">ℹ️ infobulles',
"ct_thumb": 'vue en grille, activer les icônes ou les miniatures$NHotkey: T">🖼️ minia',
"ct_csel": 'utiliser CTRL et MAJ pour selectioner des fichiers en vue en grille">sel',
+ "ct_dsel": 'utiliser la sélection par glisser en vue en grille">glisser', //m
"ct_dl": 'forcer le téléchargement (ne pas afficher en ligne) lorsqu’un fichier est cliqué">dl', //m
"ct_ihop": 'quand le visionneuse d\'image est fermé, faire defiller vers le bas jusqu\'au dernier fichier">g⮯',
"ct_dots": 'voir les fichiers caché (si le serveur le permet)">dotfiles',
@@ -264,6 +265,7 @@ Ls.fra = {
"cdt_ask": "lorsque vous faites défiler vers le bas,$Nau lieu de charger plus de fichiers,$Ndemander quoi faire",
"cdt_hsort": "combien de règles de tri (<code>,sorthref</code>) à inclure dans les media-URLs. Définir cette valeur à 0 ignorera également les règles de tri incluses dans les liens média lorsque vous cliquez dessus.",
"cdt_ren": "activer le menu contextuel personnalisé, le menu normal reste accessible avec shift + clic droit", //m
+ "cdt_rdb": "afficher le menu clic droit normal lorsque le menu personnalisé est déjà ouvert et qu’on clique à nouveau", //m
"tt_entree": "afficher le panneau de navigation (arborescence des dossiers)$NHotkey: B",
"tt_detree": "afficher le fil d’Ariane$NHotkey: B",
@@ -354,6 +356,7 @@ Ls.fra = {
"f_anota": "seulement {0} des {1} elements sont selectioné;\npour selectioner le dossier entier, fait défiler jusqu'au fond",
"f_dls": 'le lien de fichier dans le répertoire actuel\nà été changé en lien de téléchargement',
+ "f_dl_nd": 'dossier ignoré (utilisez le téléchargement zip/tar à la place):\n', //m
"f_partial": "Pour télécharger de façon sécurisée un fichier qui est entrain de se faire téléverser, cliquez sur le fichier qui a le même nom, mais sans l'extension de fichier .PARTIAL. Choisissez ANNULER ou appuiez sur la touche Échap pour faire cela.\n\nAppuyer sur OK / Entrée ignorera cet avertissement et continuera à télécharger le fichier temporaire .PARTIAL à la place, ce qui donnera presque certainement des données corrompues.",
@@ -653,10 +656,13 @@ Ls.fra = {
"rc_cut": "couper", //m
"rc_cpy": "copier", //m
"rc_pst": "coller", //m
+ "rc_rnm": "renommer", //m
"rc_nfo": "nouveau dossier", //m
"rc_nfi": "nouveau fichier", //m
"rc_sal": "tout sélectionner", //m
"rc_sin": "inverser la sélection", //m
+ "rc_shf": "partager ce dossier", //m
+ "rc_shs": "partager la sélection", //m
"lang_set": "rafraîchir pour que les changements prennent effet ?",
diff --git a/copyparty/web/tl/grc.js b/copyparty/web/tl/grc.js
index da3f0ae0..e2308479 100644
--- a/copyparty/web/tl/grc.js
+++ b/copyparty/web/tl/grc.js
@@ -226,6 +226,7 @@ Ls.grc = {
"ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων',
"ct_thumb": 'σε προβολή πλέγματος, εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες',
"ct_csel": 'χρησιμοποίησε CTRL και SHIFT για επιλογή αρχείων σε προβολή πλέγματος">επιλογή',
+ "ct_dsel": 'χρησιμοποίησε επιλογή με σύρσιμο σε προβολή πλέγματος">σύρσιμο', //m
"ct_dl": 'εξαναγκασμός λήψης (να μην εμφανίζεται ενσωματωμένα) όταν γίνεται κλικ σε ένα αρχείο">dl', //m
"ct_ihop": 'όταν η προβολή εικόνων κλείνει, κάνε scroll στο τελευταίο προβαλλόμενο αρχείο">g⮯',
"ct_dots": 'εμφάνιση κρυφών αρχείων (αν το επιτρέπει ο server)">dotfiles',
@@ -264,6 +265,7 @@ Ls.grc = {
"cdt_ask": "όταν φτάνεις στο τέλος,$Nαντί να φορτώσει περισσότερα αρχεία,$Nρωτά τι να κάνει",
"cdt_hsort": "πόσους κανόνες ταξινόμησης (<code>,sorthref</code>) να συμπεριλάβει σε URLs πολυμέσων. Αν το βάλεις 0 αγνοεί και κανόνες ταξινόμησης στους συνδέσμους πολυμέσων",
"cdt_ren": "ενεργοποίηση προσαρμοσμένου μενού δεξιού κλικ, το κανονικό μενού είναι προσβάσιμο με shift + δεξί κλικ", //m
+ "cdt_rdb": "εμφάνιση του κανονικού μενού δεξιού κλικ όταν το προσαρμοσμένο είναι ήδη ανοιχτό και γίνεται ξανά δεξί κλικ", //m
"tt_entree": "εμφάνιση navpane (δέντρο διαδρομών)$NΠλήκτρο συντόμευσης: B",
"tt_detree": "εμφάνιση breadcrumbs (καρτέλες διαδρομών)$NΠλήκτρο συντόμευσης: B",
@@ -354,6 +356,7 @@ Ls.grc = {
"f_anota": "μόνο {0} από τα {1} αντικείμενα επιλέχθηκαν;\nγια να επιλέξεις ολόκληρο το φάκελο, κύλησε πρώτα μέχρι κάτω",
"f_dls": 'οι σύνδεσμοι αρχείων στον τρέχοντα φάκελο έχουν\nμετατραπεί σε συνδέσμους λήψης',
+ "f_dl_nd": 'παράλειψη φακέλου (χρησιμοποιήστε λήψη zip/tar αντί γι’ αυτό):\n', //m
"f_partial": "Για να κατεβάσεις με ασφάλεια ένα αρχείο που ανεβαίνει, κλίκαρε το αρχείο με το ίδιο όνομα, αλλά χωρίς την κατάληξη .PARTIAL. Πάτα Άκυρο ή Escape για να σταματήσεις.\n\nΠάτα Εντάξει / Enter αν αγνοείς την προειδοποίηση και κατέβασε το .PARTIAL αρχείο, που σχεδόν σίγουρα θα είναι κατεστραμμένο.",
@@ -653,10 +656,13 @@ Ls.grc = {
"rc_cut": "αποκοπή", //m
"rc_cpy": "αντιγραφή", //m
"rc_pst": "επικόλληση", //m
+ "rc_rnm": "μετονομασία", //m
"rc_nfo": "νέος φάκελος", //m
"rc_nfi": "νέο αρχείο", //m
"rc_sal": "επιλογή όλων", //m
"rc_sin": "αντιστροφή επιλογής", //m
+ "rc_shf": "κοινή χρήση αυτού του φακέλου", //m
+ "rc_shs": "κοινή χρήση επιλογής", //m
"lang_set": "ανανέωση σελίδας για εφαρμογή της αλλαγής;",
diff --git a/copyparty/web/tl/ita.js b/copyparty/web/tl/ita.js
index 52cf0c12..b4b108b5 100644
--- a/copyparty/web/tl/ita.js
+++ b/copyparty/web/tl/ita.js
@@ -226,6 +226,7 @@ Ls.ita = {
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltip',
"ct_thumb": 'nella vista griglia, alterna icone o miniature$NTasto rapido: T">🖼️ miniature',
"ct_csel": 'usa CTRL e SHIFT per la selezione file nella vista griglia">sel',
+ "ct_dsel": 'usa la selezione tramite trascinamento nella vista griglia">trascina', //m
"ct_dl": 'forza il download (non visualizzare inline) quando si clicca su un file">dl', //m
"ct_ihop": 'quando il visualizzatore immagini è chiuso, scorri fino all\'ultimo file visualizzato">g⮯',
"ct_dots": 'mostra file nascosti (se il server lo permette)">dotfile',
@@ -264,6 +265,7 @@ Ls.ita = {
"cdt_ask": "quando scorri verso il fondo,$Ninvece di caricare più file,$Nchiedi cosa fare",
"cdt_hsort": "quante regole di ordinamento (<code>,sorthref</code>) includere negli URL multimediali. Impostandolo a 0 ignorerà anche le regole di ordinamento incluse nei link multimediali quando li clicchi",
"cdt_ren": "abilita il menu contestuale personalizzato, il menu normale è accessibile con shift + clic destro", //m
+ "cdt_rdb": "mostra il menu normale con il tasto destro quando quello personalizzato è già aperto e si clicca di nuovo", //m
"tt_entree": "mostra pannello nav (barra laterale albero directory)$NTasto rapido: B",
"tt_detree": "mostra breadcrumb$NTasto rapido: B",
@@ -354,6 +356,7 @@ Ls.ita = {
"f_anota": "solo {0} dei {1} elementi sono stati selezionati;\nper selezionare l'intera cartella, prima scorri fino in fondo",
"f_dls": 'i link dei file nella cartella corrente sono stati\ncambiati in link di download',
+ "f_dl_nd": 'cartella ignorata (usa invece il download zip/tar):\n', //m
"f_partial": "Per scaricare in sicurezza un file che è attualmente in fase di caricamento, clicca il file che ha lo stesso nome, ma senza l'estensione .PARTIAL. Premi ANNULLA o Escape per farlo.\n\nPremendo OK / Invio ignorerai questo avviso e continuerai a scaricare il file .PARTIAL scratch, che quasi sicuramente ti darà dati corrotti.",
@@ -653,10 +656,13 @@ Ls.ita = {
"rc_cut": "taglia", //m
"rc_cpy": "copia", //m
"rc_pst": "incolla", //m
+ "rc_rnm": "rinomina", //m
"rc_nfo": "nuova cartella", //m
"rc_nfi": "nuovo file", //m
"rc_sal": "seleziona tutto", //m
"rc_sin": "inverti selezione", //m
+ "rc_shf": "condividi questa cartella", //m
+ "rc_shs": "condividi selezione", //m
"lang_set": "aggiornare per rendere effettivo il cambiamento?",
diff --git a/copyparty/web/tl/jpn.js b/copyparty/web/tl/jpn.js
index 6593a2df..807cd861 100644
--- a/copyparty/web/tl/jpn.js
+++ b/copyparty/web/tl/jpn.js
@@ -226,6 +226,7 @@ Ls.jpn = {
"ct_ttips": '◔ ◡ ◔">ℹ️ ツールチップ',
"ct_thumb": 'グリッドビューではアイコンまたはサムネイルを切り替える$Nホットキー: T">🖼️ サムネイル',
"ct_csel": 'グリッドビューでファイルを選択するにはCtrlとShiftを使用する。">選択',
+ "ct_dsel": 'グリッドビューでドラッグ選択を使用する。">ドラッグ', //m
"ct_dl": 'ファイルをクリックしたときに強制的にダウンロードする(インラインで表示しない)">dl',
"ct_ihop": '画像ビューアを閉じたら最後に表示したファイルまでスクロールする。">g⮯',
"ct_dots": '隠しファイルを表示する(サーバーが許可している場合)">隠しファイル',
@@ -264,6 +265,7 @@ Ls.jpn = {
"cdt_ask": "一番下までスクロールしたときに$N更にファイルを読み込む代わりに$N何をするか尋ねる",
"cdt_hsort": "メディアURLに含めるソートルール (<code>,sorthref</code>) の数。0に設定するとメディアリンクをクリックした際にそのリンクに含まれるソートルールも無視されます。",
"cdt_ren": "カスタム右クリックメニューを有効にしてもShiftキーを押しながら右クリックすることで通常のメニューにアクセスできます。",
+ "cdt_rdb": "カスタム右クリックメニューが開いている状態で再度右クリックしたときに通常のメニューを表示する", //m
"tt_entree": "ナビペインを表示(ディレクトリツリーサイドバー)$Nホットキー: B",
"tt_detree": "パンくずリストを表示$Nホットキー: B",
@@ -354,6 +356,7 @@ Ls.jpn = {
"f_anota": "{1}件のアイテムのうち {0}件が選択されました;\nフォルダ全体を選択するには、まず一番下までスクロールします",
"f_dls": '現在のフォルダ内のファイルリンクは\nダウンロードリンクに変更されました',
+ "f_dl_nd": 'フォルダーをスキップしています(代わりに zip/tar ダウンロードを使用してください):\n', //m
"f_partial": "現在アップロード中のファイルを安全にダウンロードするには、同じファイル名で.PARTIAL拡張子がないファイルをクリックしてください。これを行うにはキャンセルまたはEscキーを押してください。\n\nOK / Enter を押すとこの警告は無視され、代わりに.PARTIALスクラッチファイルのダウンロードが続行されますが、ほとんどの場合データが破損することになります。",
@@ -653,10 +656,13 @@ Ls.jpn = {
"rc_cut": "切り取り",
"rc_cpy": "コピー",
"rc_pst": "貼り付け",
+ "rc_rnm": "名前を変更", //m
"rc_nfo": "新しいフォルダ",
"rc_nfi": "新しいファイル",
"rc_sal": "すべて選択",
"rc_sin": "選択を反転",
+ "rc_shf": "このフォルダを共有", //m
+ "rc_shs": "選択項目を共有", //m
"lang_set": "変更を反映させるために更新しますか?",
diff --git a/copyparty/web/tl/kor.js b/copyparty/web/tl/kor.js
index 9962279d..3f71eb19 100644
--- a/copyparty/web/tl/kor.js
+++ b/copyparty/web/tl/kor.js
@@ -226,6 +226,7 @@ Ls.kor = {
"ct_ttips": '◔ ◡ ◔">ℹ️ 도움말',
"ct_thumb": '그리드 보기에서 아이콘 또는 미리보기 이미지 전환$N단축키: T">🖼️ 미리보기',
"ct_csel": '그리드 보기에서 CTRL과 SHIFT를 사용하여 파일 선택">선택',
+ "ct_dsel": '그리드 보기에서 드래그 선택 사용">드래그', //m
"ct_dl": '파일을 클릭하면 다운로드를 강제로 수행 (인라인으로 표시하지 않음)">dl', //m
"ct_ihop": '이미지 뷰어를 닫으면 마지막으로 본 파일로 스크롤">g⮯',
"ct_dots": '숨김 파일 표시 (서버가 허용하는 경우)">숨김파일',
@@ -264,6 +265,7 @@ Ls.kor = {
"cdt_ask": "맨 아래로 스크롤할 때$N더 많은 파일을 불러오는 대신$N무엇을 할지 묻기",
"cdt_hsort": "미디어 URL에 포함할 정렬 규칙 (<code>,sorthref</code>)의 수. 0으로 설정하면 미디어 링크를 클릭할 때 포함된 정렬 규칙도 무시됩니다.",
"cdt_ren": "사용자 지정 우클릭 메뉴를 활성화합니다. shift 키를 누른 채 우클릭하면 기본 메뉴를 사용할 수 있습니다", //m
+ "cdt_rdb": "사용자 정의 우클릭 메뉴가 이미 열려 있을 때 다시 우클릭하면 기본 메뉴 표시", //m
"tt_entree": "탐색 창 (디렉터리 트리 사이드바) 표시$N단축키: B",
"tt_detree": "이동 경로 표시$N단축키: B",
@@ -354,6 +356,7 @@ Ls.kor = {
"f_anota": "{1}개 항목 중 {0}개만 선택되었습니다.\n전체 폴더를 선택하려면 먼저 맨 아래로 스크롤하세요.",
"f_dls": '현재 폴더의 파일 링크가\n다운로드 링크로 변경되었습니다',
+ "f_dl_nd": '폴더를 건너뜁니다 (대신 zip/tar 다운로드를 사용하세요):\n', //m
"f_partial": "현재 업로드 중인 파일을 안전하게 다운로드하려면, 파일 이름이 같지만 .PARTIAL 확장자가 없는 파일을 클릭하세요. 이 경고를 무시하려면 \"취소\" 또는 ESC를 누르세요.\n\n\"확인\"/Enter를 누르면 이 경고를 무시하고 .PARTIAL 임시 파일을 계속 다운로드하며, 이 경우 거의 확실히 손상된 데이터를 받게 됩니다.",
@@ -653,10 +656,13 @@ Ls.kor = {
"rc_cut": "잘라내기", //m
"rc_cpy": "복사", //m
"rc_pst": "붙여넣기", //m
+ "rc_rnm": "이름 변경", //m
"rc_nfo": "새 폴더", //m
"rc_nfi": "새 파일", //m
"rc_sal": "모두 선택", //m
"rc_sin": "선택 반전", //m
+ "rc_shf": "이 폴더 공유", //m
+ "rc_shs": "선택 항목 공유", //m
"lang_set": '변경 사항을 적용하기 위해 새로고침하시겠습니까?',
diff --git a/copyparty/web/tl/nld.js b/copyparty/web/tl/nld.js
index 4c0aed64..a5b38095 100644
--- a/copyparty/web/tl/nld.js
+++ b/copyparty/web/tl/nld.js
@@ -226,6 +226,7 @@ Ls.nld = {
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
"ct_thumb": 'In grid-overzicht, wissel tussen iconen of thumbnails$NHotkey: T">🖼️ thumbs',
"ct_csel": 'Gebruik CTRL en SHIFT voor de bestand selectie in grid-overzicht>sel',
+ "ct_dsel": 'Gebruik slepen om te selecteren in grid-overzicht>slepen', //m
"ct_dl": 'download afdwingen (niet inline weergeven) wanneer op een bestand wordt geklikt">dl', //m
"ct_ihop": 'Als je afbeeldingviewer afsluit, scroll omlaag naar de laatst bekeken bestand">g⮯',
"ct_dots": 'Laat verborgen bestanden zien (als de server dat toestaat)">dotfiles',
@@ -264,6 +265,7 @@ Ls.nld = {
"cdt_ask": "Als helemaal naar beneden gescrolld bent,$Nin plaats van meer inladen,$Nvraag wat het moet doen",
"cdt_hsort": "Hoeveel sorteerregels (<code>,sorthref</code>) moeten er in media-URL's worden opgenomen? Als je dit op 0 instelt, worden de sorteerregels in medialinks ook genegeerd wanneer erop geklikt word.",
"cdt_ren": "Aangepast rechtermuisknopmenu inschakelen, het normale menu blijft beschikbaar met shift + rechtermuisknop", //m
+ "cdt_rdb": "toon het normale rechtermuisknopmenu wanneer het aangepaste al open is en opnieuw wordt geklikt", //m
"tt_entree": "Laat navpane zien (directoryboom zijbalk)$NHotkey: B",
"tt_detree": "Laat breadcrumbs zien$NHotkey: B",
@@ -354,6 +356,7 @@ Ls.nld = {
"f_anota": "Alleen {0} van de {1} items zijn geselecteerd;\nom de volledige map te selecteren, scrol je eerst naar beneden",
"f_dls": 'de bestandslinks in de huidige map zijn veranderd in downloadlinks',
+ "f_dl_nd": 'map wordt overgeslagen (gebruik in plaats daarvan zip/tar-download):\n', //m
"f_partial": "Om een bestand dat momenteel wordt geüpload veilig te downloaden, klikt u op het bestand met dezelfde bestandsnaam, maar zonder de bestandsextensie .PARTIAL. Druk op Annuleren of Escape om dit te doen.\n\nAls u op OK / Enter drukt, wordt deze waarschuwing genegeerd en gaat u verder met het downloaden van het gedeeltelijke .PARTIAL scratchbestand, waardoor u vrijwel zeker beschadigde gegevens krijgt.",
@@ -653,10 +656,13 @@ Ls.nld = {
"rc_cut": "Knippen", //m
"rc_cpy": "Kopiëren", //m
"rc_pst": "Plakken", //m
+ "rc_rnm": "hernoemen", //m
"rc_nfo": "Nieuwe map", //m
"rc_nfi": "Nieuw bestand", //m
"rc_sal": "Alles selecteren", //m
"rc_sin": "Selectie omkeren", //m
+ "rc_shf": "deel deze map", //m
+ "rc_shs": "deel selectie", //m
"lang_set": "Vernieuw de pagina om de wijziging door te voeren?",
diff --git a/copyparty/web/tl/nno.js b/copyparty/web/tl/nno.js
index 9e7065b4..45b3afd2 100644
--- a/copyparty/web/tl/nno.js
+++ b/copyparty/web/tl/nno.js
@@ -223,6 +223,7 @@ Ls.nno = {
"ct_ttips": 'vis hjelpetekst ved å holde musa over ting">ℹ️ tips',
"ct_thumb": 'vis miniatyrbilder i staden for ikon$NSnarvei: T">🖼️ bilder',
"ct_csel": 'bruk tastane CTRL og SHIFT for markering av filer i ikonvising">merk',
+ "ct_dsel": 'klikk-og-dra for å merke filer i ikonvising">dra',
"ct_dl": 'last ned filer (ikkje vis i nettleseren)">dl',
"ct_ihop": 'bla ned åt sist viste bilde når bildevisaren lukkast">g⮯',
"ct_dots": 'vis skjulte filer (gitt at serveren tillèt det)">.synlig',
@@ -261,6 +262,7 @@ Ls.nno = {
"cdt_ask": "vis knappar for å laste fleire filer nederst på sida i staden for å gradvis laste meir av mappea når man scroller ned",
"cdt_hsort": "antall sorteringsreglar (<code>,sorthref</code>) som skal inkluderast når media-URL'ar genererast. Dersom denne er 0 så vil sorteringsreglar i URL'ar korkje bli generert eller lest",
"cdt_ren": "slå på tilpassa høgreklikkmeny (den vanlege menyen er tilgjengeleg med shift + høgreklikk)",
+ "cdt_rdb": "høgreklikk to gonger for å vise den vanlege høgreklikkmenyen",
"tt_entree": "bytt åt mappehierarki$NSnarvei: B",
"tt_detree": "bytt åt tradisjonell stivising$NSnarvei: B",
@@ -351,6 +353,7 @@ Ls.nno = {
"f_anota": "kun {0} av totalt {1} element blei markert;\nfor å velje alt må du bla åt bunnen av mappa først",
"f_dls": 'lenkane i denne mappa er no\nomgjort åt nedlastingsknappar',
+ "f_dl_nd": 'hoppar over mappe (bruk zip/tar-nedlasting i staden):\n',
"f_partial": "For å laste ned ei fil som enda ikkje er ferdig opplasta, klikk på filen som har same filnamn som denne, men uten .PARTIAL på slutten. Da vil serveren passe på at nedlastinga går bra. Derfor anbefalast det sterkt å trykkje AVBRYT eller Escape-tasten.\n\nViss du verkelig ønskjer å laste ned denne .PARTIAL-filen på ein ukontrollert måte, trykk OK / Enter for å ignorere denne advarselen. Slik vil du høgst sannsynleg motta korrupt data.",
@@ -650,10 +653,13 @@ Ls.nno = {
"rc_cut": "klipp ut",
"rc_cpy": "kopier",
"rc_pst": "Lim inn",
+ "rc_rnm": "endre namn",
"rc_nfo": "ny mappe",
"rc_nfi": "ny fil",
"rc_sal": "vel alle",
"rc_sin": "inverter val",
+ "rc_shf": "del denne mappa",
+ "rc_shs": "del markering",
"lang_set": "passar det å laste sida på nytt?",
diff --git a/copyparty/web/tl/nor.js b/copyparty/web/tl/nor.js
index e005fe8d..0f75ddc7 100644
--- a/copyparty/web/tl/nor.js
+++ b/copyparty/web/tl/nor.js
@@ -223,6 +223,7 @@ Ls.nor = {
"ct_ttips": 'vis hjelpetekst ved å holde musen over ting">ℹ️ tips',
"ct_thumb": 'vis miniatyrbilder istedenfor ikoner$NSnarvei: T">🖼️ bilder',
"ct_csel": 'bruk tastene CTRL og SHIFT for markering av filer i ikonvisning">merk',
+ "ct_dsel": 'marker filer med klikk-og-dra i ikonvisning">dsel',
"ct_dl": 'last ned filer (ikke vis i nettleseren)">dl',
"ct_ihop": 'bla ned til sist viste bilde når bildeviseren lukkes">g⮯',
"ct_dots": 'vis skjulte filer (gitt at serveren tillater det)">.synlig',
@@ -261,6 +262,7 @@ Ls.nor = {
"cdt_ask": "vis knapper for å laste flere filer nederst på siden istedenfor å gradvis laste mer av mappen når man scroller ned",
"cdt_hsort": "antall sorterings-regler (<code>,sorthref</code>) som skal inkluderes når media-URL'er genereres. Hvis denne er 0 så vil sorterings-regler i URL'er hverken bli generert eller lest",
"cdt_ren": "bruk egendefinert høyreklikkmeny (den vanlige menyen er tilgjengelig med shift + høyreklikk)",
+ "cdt_rdb": "høyreklikk to ganger for å vise den vanlige høyreklikkmenyen",
"tt_entree": "bytt til mappehierarki$NSnarvei: B",
"tt_detree": "bytt til tradisjonell sti-visning$NSnarvei: B",
@@ -351,6 +353,7 @@ Ls.nor = {
"f_anota": "kun {0} av totalt {1} elementer ble markert;\nfor å velge alt må du bla til bunnen av mappen først",
"f_dls": 'linkene i denne mappen er nå\nomgjort til nedlastningsknapper',
+ "f_dl_nd": 'hopper over mappe (bruk zip/tar-nedlasting i stedet):\n',
"f_partial": "For å laste ned en fil som enda ikke er ferdig opplastet, klikk på filen som har samme filnavn som denne, men uten .PARTIAL på slutten. Da vil serveren passe på at nedlastning går bra. Derfor anbefales det sterkt å trykke AVBRYT eller Escape-tasten.\n\nHvis du virkelig ønsker å laste ned denne .PARTIAL-filen på en ukontrollert måte, trykk OK / Enter for å ignorere denne advarselen. Slik vil du høyst sannsynlig motta korrupt data.",
@@ -650,10 +653,13 @@ Ls.nor = {
"rc_cut": "klipp ut",
"rc_cpy": "kopier",
"rc_pst": "Lim inn",
+ "rc_rnm": "gi nytt navn",
"rc_nfo": "ny mappe",
"rc_nfi": "ny fil",
"rc_sal": "velg alle",
"rc_sin": "inverter utvalg",
+ "rc_shf": "del denne mappen",
+ "rc_shs": "del markering",
"lang_set": "passer det å laste siden på nytt?",
diff --git a/copyparty/web/tl/pol.js b/copyparty/web/tl/pol.js
index 21335b0b..76046af6 100644
--- a/copyparty/web/tl/pol.js
+++ b/copyparty/web/tl/pol.js
@@ -229,6 +229,7 @@ Ls.pol = {
"ct_ttips": '◔ ◡ ◔">ℹ️ podpowiedzi',
"ct_thumb": 'w widoku siatki, przełącz ikony i miniaturki$NSkrót: T">🖼️ miniaturki',
"ct_csel": 'użyj CTRL i SHIFT do wybierania plików w widoku siatki">wybierz',
+ "ct_dsel": 'użyj zaznaczania przez przeciąganie w widoku siatki">przeciągnij', //m
"ct_dl": 'wymuś pobieranie (nie wyświetlaj inline) po kliknięciu pliku">dl', //m
"ct_ihop": 'przejdź do ostatniego pliku po zamknięciu przeglądarki obrazów">g⮯',
"ct_dots": 'pokaż ukryte pliki (jeśli pozwala serwer)">ukryte',
@@ -267,6 +268,7 @@ Ls.pol = {
"cdt_ask": "przy przewijaniu w dół,$Nzapytaj co robić,$Nzamiast wczytywać kolejne pliki",
"cdt_hsort": "ile zasad sortowania (<code>,sorthref</code>) zawierać w generowanych linkach multimediów. Wartość 0 sprawi, że zasady sortowania zawarte w linkach multimediów przy otwarciu również będą ignorowane",
"cdt_ren": "włącz niestandardowe menu kontekstowe, standardowe menu jest dostępne po wciśnięciu shift i kliknięciu prawym przyciskiem", //m
+ "cdt_rdb": "pokaż standardowe menu prawego przycisku, gdy niestandardowe jest już otwarte i nastąpi ponowne kliknięcie", //m
"tt_entree": "pokaż panel nawigacyjny (panel boczny z drzewem folderów)$NSkrót: B",
"tt_detree": "pokaż ślad nawigacyjny$NSkrót: B",
@@ -357,6 +359,7 @@ Ls.pol = {
"f_anota": "{0} z {1} elementów zostało wybranych;\naby pokazać cały folder, zjedź na dół",
"f_dls": 'linki do plików w aktualnym folderze\nzostały zmienione w linki pobierania',
+ "f_dl_nd": 'pomijanie folderu (użyj zamiast tego pobierania zip/tar):\n', //m
"f_partial": "Aby bezpiecznie pobrać plik, który aktualnie jest przesyłany, wybierz plik o tej samej nazwie, lecz bez rozszerzenia .PARTIAL. Żeby to zrobić, naciśnij ANULUJ lub klawisz ESC.\n\nWciśnięcie OK / Enter zignoruje to ostrzeżenie i pobierze plik tymczasowy .PARTIAL, który prawie z pewnością będzie zepsuty",
@@ -656,10 +659,13 @@ Ls.pol = {
"rc_cut": "wytnij", //m
"rc_cpy": "kopiuj", //m
"rc_pst": "wklej", //m
+ "rc_rnm": "zmień nazwę", //m
"rc_nfo": "nowy folder", //m
"rc_nfi": "nowy plik", //m
"rc_sal": "zaznacz wszystko", //m
"rc_sin": "odwróć zaznaczenie", //m
+ "rc_shf": "udostępnij ten folder", //m
+ "rc_shs": "udostępnij zaznaczenie", //m
"lang_set": "odśwież stronę (F5), aby zastosować zmianę.",
diff --git a/copyparty/web/tl/por.js b/copyparty/web/tl/por.js
index 5ece210d..6fd11845 100644
--- a/copyparty/web/tl/por.js
+++ b/copyparty/web/tl/por.js
@@ -84,8 +84,8 @@ Ls.por = {
["M", "fechar arquivo de texto"],
["E", "editar arquivo de texto"],
["S", "selecionar arquivo (para recortar/copiar/renomear)"],
- ["Y", "baixar arquivo de texto"], //m
- ["⇧ J", "embelezar json"], //m
+ ["Y", "baixar arquivo de texto"],
+ ["⇧ J", "embelezar json"],
]
],
@@ -118,7 +118,7 @@ Ls.por = {
"ot_unpost": "despublicar: excluir seus uploads recentes, ou abortar os que não foram concluídos",
"ot_bup": "bup: uploader básico, até suporta netscape 4.0",
"ot_mkdir": "mkdir: criar um novo diretório",
- "ot_md": "new-file: criar um novo ficheiro de texto", //m
+ "ot_md": "new-file: criar um novo arquivo de texto",
"ot_msg": "msg: enviar uma mensagem para o log do servidor",
"ot_mp": "opções do reprodutor de mídia",
"ot_cfg": "opções de configuração",
@@ -127,7 +127,7 @@ Ls.por = {
"ot_noie": 'Por favor, use Chrome / Firefox / Edge',
"ab_mkdir": "criar diretório",
- "ab_mkdoc": "novo ficheiro de texto", //m
+ "ab_mkdoc": "novo arquivo de texto",
"ab_msg": "enviar msg para o log do srv",
"ay_path": "pular para pastas",
@@ -155,12 +155,12 @@ Ls.por = {
"ul_par": "uploads paralelos:",
"ut_rand": "randomizar nomes de arquivos",
"ut_u2ts": "copiar o carimbo de data/hora de última modificação$Ndo seu sistema de arquivos para o servidor\">📅",
- "ut_ow": "substituir arquivos existentes no servidor?$N🛡️: nunca (irá gerar um novo nome de arquivo em vez disso)$N🕒: substituir se o arquivo no servidor for mais antigo que o seu$N♻️: sempre substituir se os arquivos forem diferentes$N⏭️: ignorar incondicionalmente todos os arquivos existentes", //m
+ "ut_ow": "substituir arquivos existentes no servidor?$N🛡️: nunca (irá gerar um novo nome de arquivo em vez disso)$N🕒: substituir se o arquivo no servidor for mais antigo que o seu$N♻️: sempre substituir se os arquivos forem diferentes$N⏭️: ignorar incondicionalmente todos os arquivos existentes",
"ut_mt": "continuar a fazer o hash de outros arquivos enquanto faz upload$N$Ntalvez desativar se sua CPU ou HDD for um gargalo",
"ut_ask": 'pedir confirmação antes do upload começar">💭',
"ut_pot": "melhorar a velocidade de upload em dispositivos lentos$Ntornando a UI menos complexa",
"ut_srch": "não fazer upload, em vez disso verificar se os arquivos já$N existem no servidor (irá escanear todas as pastas que você pode ler)",
- "ut_par": "pausar uploads definindo para 0$N$naumentar se sua conexão for lenta / alta latência$N$nmanter em 1 em LAN ou se o HDD do servidor for um gargalo",
+ "ut_par": "pausar uploads definindo para 0$N$Naumentar se sua conexão for lenta / alta latência$N$Nmanter em 1 em LAN ou se o HDD do servidor for um gargalo",
"ul_btn": "soltar arquivos / pastas.PARTIAL. Por favor, pressione CANCELAR ou Escape para fazer isso.\n\nPressionar OK / Enter irá ignorar este aviso e continuar baixando o arquivo temporário .PARTIAL, o que quase certamente lhe dará dados corrompidos.",
@@ -422,10 +425,10 @@ Ls.por = {
"fcc_warn": 'copiado {0} itens para a área de transferência\n\nmas: apenas esta aba do navegador pode colá-los\n(já que a seleção é tão absolutamente massiva)',
"fp_apply": "usar estes nomes",
- "fp_skip": "pular conflitos", //m
+ "fp_skip": "pular conflitos",
"fp_ecut": "primeiro recorte ou copie alguns arquivos / pastas para colar / mover\n\nnota: você pode recortar / colar entre abas diferentes do navegador",
- "fp_ename": "{0} itens não podem ser movidos para cá porque os nomes já estão em uso. Dê a eles novos nomes abaixo para continuar, ou deixe o nome em branco (\"pular conflitos\") para pular:", //m
- "fcp_ename": "{0} itens não podem ser copiados para cá porque os nomes já estão em uso. Dê a eles novos nomes abaixo para continuar, ou deixe o nome em branco (\"pular conflitos\") para pular:", //m
+ "fp_ename": "{0} itens não podem ser movidos para cá porque os nomes já estão em uso. Dê a eles novos nomes abaixo para continuar, ou deixe o nome em branco (\"pular conflitos\") para pular:",
+ "fcp_ename": "{0} itens não podem ser copiados para cá porque os nomes já estão em uso. Dê a eles novos nomes abaixo para continuar, ou deixe o nome em branco (\"pular conflitos\") para pular:",
"fp_emore": "ainda há algumas colisões de nome de arquivo para consertar",
"fp_ok": "movimento OK",
"fcp_ok": "cópia OK",
@@ -444,8 +447,8 @@ Ls.por = {
"fcp_both_b": 'CopiarEnviar',
"mk_noname": "digite um nome no campo de texto à esquerda antes de fazer isso :p",
- "nmd_i1": "também pode adicionar a extensão desejada, por exemplo .md", //m
- "nmd_i2": "só pode criar ficheiros .md porque não tem permissão para apagar", //m
+ "nmd_i1": "também adicione a extensão desejada, por exemplo .md",
+ "nmd_i2": "só pode criar arquivos .md porque não tem permissão para apagar",
"tv_load": "Carregando documento de texto:\n\n{0}\n\n{1}% ({2} de {3} MiB carregados)",
"tv_xe1": "não foi possível carregar o arquivo de texto:\n\nerro ",
@@ -456,7 +459,7 @@ Ls.por = {
"tvt_prev": "mostrar documento anterior$NHotkey: i\">⬆ anterior",
"tvt_next": "mostrar próximo documento$NHotkey: K\">⬇ próximo",
"tvt_sel": "selecionar arquivo ( para recortar / copiar / excluir / ... )$NHotkey: S\">sel",
- "tvt_j": "embelezar json$NHotkey: shift-J\">j", //m
+ "tvt_j": "embelezar json$NHotkey: shift-J\">j",
"tvt_edit": "abrir arquivo no editor de texto$NHotkey: E\">✏️ editar",
"tvt_tail": "monitorar arquivo para alterações; mostrar novas linhas em tempo real\">📡 seguir",
"tvt_wrap": "quebra de linha\">↵",
@@ -469,7 +472,7 @@ Ls.por = {
"m3u_clip": "playlist m3u agora copiada para a área de transferência\n\nvocê deve criar um novo arquivo de texto chamado something.m3u e colar a playlist nesse documento; isso a tornará reproduzível",
"gt_vau": "não mostrar vídeos, apenas tocar o áudio\">🎧",
- "gt_msel": "ativar seleção de arquivo; ctrl-clique em um arquivo para substituir$N$n<em>quando ativo: clique duas vezes em um arquivo / pasta para abri-lo&>t;/em>$N$nHotkey: S\">multisseleção",
+ "gt_msel": "ativar seleção de arquivo; ctrl-clique em um arquivo para substituir$N$N<em>quando ativo: clique duas vezes em um arquivo / pasta para abri-lo&>t;/em>$N$NHotkey: S\">multisseleção",
"gt_crop": "cortar miniaturas ao centro\">cortar",
"gt_3x": "miniaturas de alta resolução\">3x",
"gt_zoom": "zoom",
@@ -527,11 +530,11 @@ Ls.por = {
"fz_tar": "arquivo gnu-tar não comprimido (linux / mac)",
"fz_pax": "tar de formato pax não comprimido (mais lento)",
- "fz_targz": "gnu-tar com compressão gzip nível 3$N$nisto é geralmente muito lento, então$nuse tar não comprimido em vez disso",
- "fz_tarxz": "gnu-tar com compressão xz nível 1$N$nisto é geralmente muito lento, então$nuse tar não comprimido em vez disso",
+ "fz_targz": "gnu-tar com compressão gzip nível 3$N$Nisto é geralmente muito lento, então$Nuse tar não comprimido em vez disso",
+ "fz_tarxz": "gnu-tar com compressão xz nível 1$N$Nisto é geralmente muito lento, então$Nuse tar não comprimido em vez disso",
"fz_zip8": "zip com nomes de arquivos utf8 (pode ser instável no windows 7 e mais antigos)",
"fz_zipd": "zip com nomes de arquivos cp437 tradicionais, para software realmente antigo",
- "fz_zipc": "cp437 com crc32 calculado antecipadamente,$npara MS-DOS PKZIP v2.04g (outubro de 1993)$n(leva mais tempo para processar antes que o download possa começar)",
+ "fz_zipc": "cp437 com crc32 calculado antecipadamente,$Npara MS-DOS PKZIP v2.04g (outubro de 1993)$N(leva mais tempo para processar antes que o download possa começar)",
"un_m1": "você pode excluir seus uploads recentes (ou abortar os que não foram concluídos) abaixo",
"un_upd": "atualizar",
@@ -641,22 +644,25 @@ Ls.por = {
"ur_um": "Concluído;\n{0} uploads OK,\n{1} uploads falharam, desculpe",
"ur_sm": "Concluído;\n{0} arquivos encontrados no servidor,\n{1} arquivos NÃO encontrados no servidor",
- "rc_opn": "abrir", //m
- "rc_ply": "reproduzir", //m
- "rc_pla": "reproduzir como áudio", //m
- "rc_txt": "abrir no visualizador de arquivos", //m
- "rc_md": "abrir no editor de texto", //m
- "rc_dl": "baixar", //m
- "rc_zip": "baixar como arquivo", //m
- "rc_cpl": "copiar link", //m
- "rc_del": "excluir", //m
- "rc_cut": "recortar", //m
- "rc_cpy": "copiar", //m
- "rc_pst": "colar", //m
- "rc_nfo": "nova pasta", //m
- "rc_nfi": "novo arquivo", //m
- "rc_sal": "selecionar tudo", //m
- "rc_sin": "inverter seleção", //m
+ "rc_opn": "abrir",
+ "rc_ply": "reproduzir",
+ "rc_pla": "reproduzir como áudio",
+ "rc_txt": "abrir no leitor de texto",
+ "rc_md": "abrir no leitor markdown",
+ "rc_dl": "baixar",
+ "rc_zip": "baixar compactado",
+ "rc_cpl": "copiar link",
+ "rc_del": "excluir",
+ "rc_cut": "recortar",
+ "rc_cpy": "copiar",
+ "rc_pst": "colar",
+ "rc_rnm": "renomear",
+ "rc_nfo": "nova pasta",
+ "rc_nfi": "novo arquivo",
+ "rc_sal": "selecionar tudo",
+ "rc_sin": "inverter seleção",
+ "rc_shf": "compartilhar esta pasta",
+ "rc_shs": "compartilhar seleção",
"lang_set": "atualizar para a mudança ter efeito?",
@@ -699,8 +705,8 @@ Ls.por = {
"ta1": "primeiro digite sua nova senha",
"ta2": "repita para confirmar a nova senha:",
"ta3": "há um erro; por favor, tente novamente",
- "nop": "ERRO: A senha não pode estar em branco", //m
- "nou": "ERRO: O nome de usuário e/ou a senha não podem estar em branco", //m
+ "nop": "ERRO: A senha não pode estar em branco",
+ "nou": "ERRO: O nome de usuário e/ou a senha não podem estar em branco",
"aa1": "arquivos de entrada:",
"ab1": "desativar no304",
"ac1": "ativar no304",
diff --git a/copyparty/web/tl/rus.js b/copyparty/web/tl/rus.js
index 947b1f9a..4c48ae1a 100644
--- a/copyparty/web/tl/rus.js
+++ b/copyparty/web/tl/rus.js
@@ -226,6 +226,7 @@ Ls.rus = {
"ct_ttips": '◔ ◡ ◔">ℹ️ подсказки',
"ct_thumb": 'переключение между иконками и миниатюрами в режиме сетки$NГорячая клавиша: T">🖼️ миниат.',
"ct_csel": 'держите CTRL или SHIFT для выделения файлов в режиме сетки">выбор',
+ "ct_dsel": 'использовать выделение перетаскиванием в режиме сетки">перетащить', //m
"ct_dl": 'принудительная загрузка (не показывать встроенно) при щелчке по файлу">dl', //m
"ct_ihop": 'показывать последний открытый файл после закрытия просмотрщика изображений">g⮯',
"ct_dots": 'показывать скрытые файлы (если есть доступ)">скрыт.',
@@ -264,6 +265,7 @@ Ls.rus = {
"cdt_ask": "внизу страницы спрашивать о действии вместо автоматической загрузки следующих файлов",
"cdt_hsort": "сколько правил сортировки (<code>,sorthref</code>) включать в адрес страницы. Если значение равно 0, по нажатии на ссылки будут игнорироваться правила, включённые в них",
"cdt_ren": "включить настраиваемое контекстное меню, обычное меню доступно при нажатии shift и правой кнопки мыши", //m
+ "cdt_rdb": "показывать обычное меню правого клика, если пользовательское уже открыто и выполняется повторный клик", //m
"tt_entree": "показать панель навигации$NГорячая клавиша: B",
"tt_detree": "скрыть панель навигации$NГорячая клавиша: B",
@@ -354,6 +356,7 @@ Ls.rus = {
"f_anota": "только {0} из {1} файлов было выделено;\nчтобы выделить всё папку, отмотайте до низа",
"f_dls": 'ссылки на файлы в данной папке были\nзаменены ссылками на скачивание',
+ "f_dl_nd": 'пропуск папки (используйте загрузку zip/tar вместо этого):\n', //m
"f_partial": "Чтобы безопасно скачать файл, который в текущий момент загружается, нажмите на файл с таким же названием, но без расширения .PARTIAL. Пожалуйста, нажмите Отмена или ESC, чтобы сделать это.\n\nПри нажатии OK / Enter, вы скачаете этот временный файл, который с огромной вероятностью содержит лишь неполные данные.",
@@ -653,10 +656,13 @@ Ls.rus = {
"rc_cut": "вырезать", //m
"rc_cpy": "копировать", //m
"rc_pst": "вставить", //m
+ "rc_rnm": "переименовать", //m
"rc_nfo": "новая папка", //m
"rc_nfi": "новый файл", //m
"rc_sal": "выбрать всё", //m
"rc_sin": "инвертировать выделение", //m
+ "rc_shf": "поделиться этой папкой", //m
+ "rc_shs": "поделиться выделенным", //m
"lang_set": "перезагрузить страницу, чтобы применить изменения?",
diff --git a/copyparty/web/tl/spa.js b/copyparty/web/tl/spa.js
index 7c2ea5db..508a639f 100644
--- a/copyparty/web/tl/spa.js
+++ b/copyparty/web/tl/spa.js
@@ -225,6 +225,7 @@ Ls.spa = {
"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
"ct_thumb": 'en vista de cuadrícula, alternar iconos o miniaturas$NAtajo: T">🖼️ miniaturas',
"ct_csel": 'usa CTRL y SHIFT para seleccionar archivos en la vista de cuadrícula">sel',
+ "ct_dsel": 'usa la selección por arrastre en la vista de cuadrícula">arrastrar', //m
"ct_dl": 'forzar descarga (no mostrar en línea) al hacer clic en un archivo">dl', //m
"ct_ihop": 'al cerrar el visor de imágenes, desplazarse hasta el último archivo visto">g⮯',
"ct_dots": 'mostrar archivos ocultos (si el servidor lo permite)">archivos ocultos',
@@ -263,6 +264,7 @@ Ls.spa = {
"cdt_ask": "al llegar al final,$Nen lugar de cargar más archivos,$Npreguntar qué hacer",
"cdt_hsort": "cuántas reglas de ordenación (<code>,sorthref</code>) incluir en las URLs de medios. Ponerlo a 0 también ignorará las reglas de ordenación incluidas en los enlaces de medios al hacer clic en ellos",
"cdt_ren": "habilitar menú contextual personalizado, el menú normal sigue siendo accesible con shift + clic derecho", //m
+ "cdt_rdb": "mostrar el menú normal de clic derecho cuando el personalizado ya está abierto y se vuelve a hacer clic", //m
"tt_entree": "mostrar panel de navegación (barra lateral con árbol de directorios)$NAtajo: B",
"tt_detree": "mostrar breadcrumbs$NAtajo: B",
@@ -353,6 +355,7 @@ Ls.spa = {
"f_anota": "solo {0} de los {1} elementos fueron seleccionados;\npara seleccionar la carpeta completa, primero desplázate hasta el final",
"f_dls": "los enlaces a archivos en la carpeta actual se han\nconvertido en enlaces de descarga",
+ "f_dl_nd": 'omitiendo carpeta (use la descarga zip/tar en su lugar):\n', //m
"f_partial": "Para descargar de forma segura un archivo que se está subiendo actualmente, por favor haz clic en el archivo con el mismo nombre, pero sin la extensión .PARTIAL. Por favor, pulsa CANCELAR o Escape para hacer esto.\n\nPulsar ACEPTAR o Intro ignorará esta advertencia y continuará descargando el archivo temporal .PARTIAL, lo que casi con toda seguridad te dará datos corruptos.",
@@ -652,10 +655,13 @@ Ls.spa = {
"rc_cut": "cortar", //m
"rc_cpy": "copiar", //m
"rc_pst": "pegar", //m
+ "rc_rnm": "renombrar", //m
"rc_nfo": "nueva carpeta", //m
"rc_nfi": "nuevo archivo", //m
"rc_sal": "seleccionar todo", //m
"rc_sin": "invertir selección", //m
+ "rc_shf": "compartir esta carpeta", //m
+ "rc_shs": "compartir selección", //m
"lang_set": "¿refrescar para que el cambio surta efecto?",
diff --git a/copyparty/web/tl/swe.js b/copyparty/web/tl/swe.js
index 39009b15..6f5e9b0b 100644
--- a/copyparty/web/tl/swe.js
+++ b/copyparty/web/tl/swe.js
@@ -226,6 +226,7 @@ Ls.swe = {
"ct_ttips": '◔ ◡ ◔">ℹ️ tips',
"ct_thumb": 'växla mellan miniatyrer och ikoner i rutnätsvyn$NSnabbtangent: T">🖼️ miniatyrer',
"ct_csel": 'använd CTRL och SKIFT för urval av filer i rutnätsvyn">val',
+ "ct_dsel": 'använd dra-urval i rutnätsvyn">dra', //m
"ct_dl": 'tvinga nedladdning (visa inte inline) när en fil klickas">dl', //m
"ct_ihop": 'skrolla till den senast visade filen när bildvisaren stängs">g⮯',
"ct_dots": 'visa dolda filer (om servern tillåter detta)">dolda',
@@ -264,6 +265,7 @@ Ls.swe = {
"cdt_ask": "när du når botten av vyn,$Nbe om en åtgärd istället för att ladda fler filer",
"cdt_hsort": "hur många sorteringsregler (<code>,sorthref</code>) att inkludera i media-URL:er. Sätts detta till 0 kommer regler i klickade medialänkar även att ignoreras",
"cdt_ren": "aktivera anpassad högerklicksmeny, den vanliga menyn är tillgänglig med shift + högerklick", //m
+ "cdt_rdb": "visa den vanliga högerklicksmenyn när den anpassade redan är öppen och man högerklickar igen", //m
"tt_entree": "visa trädvy$NSnabbtangent: B",
"tt_detree": "visa brödsmulor$NSnabbtangent: B",
@@ -354,6 +356,7 @@ Ls.swe = {
"f_anota": "endast {0} av {1} objekt valdes;\nför att välja hela mappen, skrolla först till botten av vyn",
"f_dls": 'fillänkarna i den öppna mappen har\nbytts till nedladdningslänkar',
+ "f_dl_nd": 'hoppar över mapp (använd zip/tar-nedladdning istället):\n', //m
"f_partial": "För att säkert ladda ner en fil som för tillfället laddas upp, vänligen klicka på filen som har samma filnamn men utan .PARTIAL-filändelsen. Vänligen tryck Avbryt eller Escape för att göra detta.\n\nOm du bortser från denna varning och trycker OK eller Enter kommer den tillfälliga .PARTIAL-filen istället att laddas ner, vilket är nästan garanterat att ge dig korrumperad data.",
@@ -653,10 +656,13 @@ Ls.swe = {
"rc_cut": "klipp ut", //m
"rc_cpy": "kopiera", //m
"rc_pst": "klistra in", //m
+ "rc_rnm": "byt namn", //m
"rc_nfo": "ny mapp", //m
"rc_nfi": "ny fil", //m
"rc_sal": "markera alla", //m
"rc_sin": "invertera markering", //m
+ "rc_shf": "dela denna mapp", //m
+ "rc_shs": "dela urval", //m
"lang_set": "uppdatera för att ändringen ska ta effekt?",
diff --git a/copyparty/web/tl/tur.js b/copyparty/web/tl/tur.js
index 7b0d35cd..0efaa2d5 100644
--- a/copyparty/web/tl/tur.js
+++ b/copyparty/web/tl/tur.js
@@ -226,6 +226,7 @@ Ls.tur = {
"ct_ttips": '◔ ◡ ◔">ℹ️ ipuçları',
"ct_thumb": 'ızgara görünümünde, simgeler ve küçük resimler arasında geçiş yapın$NKısayol: T">🖼️ küçük resimler',
"ct_csel": 'ızgara görünümünde dosya seçimi için CTRL ve SHIFT tuşlarını kullanın">seç',
+ "ct_dsel": 'ızgara görünümünde sürükleyerek seçimi kullanın">sürükle', //m
"ct_dl": 'dosyaya tıklandığında indirmeyi zorla (satır içinde görüntüleme)">dl', //m
"ct_ihop": 'resim görüntüleyici kapatıldığında, en son görüntülenen dosyaya kaydırın">g⮯',
"ct_dots": 'gizli dosyaları göster (sunucu izin veriyorsa)">nokta dosyaları',
@@ -264,6 +265,7 @@ Ls.tur = {
"cdt_ask": "aşağı kaydırırken,$Ndaha fazla dosya yüklemek yerine,$Nne yapılacağını sor",
"cdt_hsort": "medya-URL'lerinde dahil edilecek sıralama kurallarının sayısı (<code>,sorthref</code>). Bunu 0 olarak ayarlamak, tıklanırken medya bağlantılarına dahil edilen sıralama kurallarını da yok sayacaktır",
"cdt_ren": "özel sağ tık menüsünü etkinleştir, normal menü shift + sağ tık ile erişilebilir", //m
+ "cdt_rdb": "özel menü zaten açıkken tekrar sağ tıklanınca normal sağ tık menüsünü göster", //m
"tt_entree": "navigasyon panosunu göster (yan dizin panosu)$NHotkey: B",
"tt_detree": "içerik haritasını göster$Kısayol: B",
@@ -354,6 +356,7 @@ Ls.tur = {
"f_anota": "{1} dosyadan sadece {0} tanesi seçildi;\nTüm klasörü seçmek için önce en alta kaydırın.",
"f_dls": 'bu klasördeki dosya linkleri\nindirme linklerine dönüştürüldü',
+ "f_dl_nd": 'klasör atlanıyor (bunun yerine zip/tar indirmesini kullanın):\n', //m
"f_partial": "Mevcutta yüklenen bir dosyayı güvenli bir şekilde indirmek için lütfen aynı adlı ama .PARTIAL uzantısına sahip olmayan dosyaya tıklayın. Lütfen bunu yapmak için İPTAL veya Esc tuşuna basın.\n\nTamam / Enter tuşuna basmak, bu uyarıyı yok sayacak ve bunun yerine .PARTIAL geçici dosyasını indirmeye devam edecektir ki bu da elinize bozuk veriler sunacaktır.",
@@ -653,10 +656,13 @@ Ls.tur = {
"rc_cut": "kes", //m
"rc_cpy": "kopyala", //m
"rc_pst": "yapıştır", //m
+ "rc_rnm": "yeniden adlandır", //m
"rc_nfo": "yeni klasör", //m
"rc_nfi": "yeni dosya", //m
"rc_sal": "tümünü seç", //m
"rc_sin": "seçimi tersine çevir", //m
+ "rc_shf": "bu klasörü paylaş", //m
+ "rc_shs": "seçimi paylaş", //m
"lang_set": "Değişikliklerin etki göstermesi için sayfa yenilensin mi?",
diff --git a/copyparty/web/tl/ukr.js b/copyparty/web/tl/ukr.js
index db56bdaf..2265c2ec 100644
--- a/copyparty/web/tl/ukr.js
+++ b/copyparty/web/tl/ukr.js
@@ -226,6 +226,7 @@ Ls.ukr = {
"ct_ttips": '◔ ◡ ◔">ℹ️ підказки',
"ct_thumb": 'у режимі сітки, перемкнути іконки або мініатюри$NГаряча клавіша: T">🖼️ мініатюри',
"ct_csel": 'використовувати CTRL і SHIFT для вибору файлів у режимі сітки">вибір',
+ "ct_dsel": 'використовувати вибір перетягуванням у режимі сітки">перетягнути', //m
"ct_dl": 'примусове завантаження (не показувати вбудовано) під час натискання на файл">dl', //m
"ct_ihop": 'коли переглядач зображень закрито, прокрутити вниз до останнього переглянутого файлу">g⮯',
"ct_dots": 'показати приховані файли (якщо сервер дозволяє)">приховані файли',
@@ -264,6 +265,7 @@ Ls.ukr = {
"cdt_ask": "при прокрутці до низу,$Nзамість завантаження більше файлів,$Nзапитати, що робити",
"cdt_hsort": "скільки правил сортування (<code>,sorthref</code>) включати в медіа-URL. Встановлення цього в 0 також буде ігнорувати правила сортування, включені в медіа посилання при їх натисканні",
"cdt_ren": "увімкнути користувацьке контекстне меню, звичайне меню доступне при натисканні shift і правої кнопки миші", //m
+ "cdt_rdb": "показувати звичайне меню правої кнопки, якщо власне меню вже відкрите і відбувається повторне клацання", //m
"tt_entree": "показати панель навігації (бічна панель дерева каталогів)$NГаряча клавіша: B",
"tt_detree": "показати хлібні крихти$NГаряча клавіша: B",
@@ -354,6 +356,7 @@ Ls.ukr = {
"f_anota": "лише {0} з {1} елементів було вибрано;\nщоб вибрати всю папку, спочатку прокрутіть до низу",
"f_dls": 'посилання на файли в поточній папці були\nзмінені на посилання для завантаження',
+ "f_dl_nd": 'пропуск папки (скористайтеся завантаженням zip/tar замість цього):\n', //m
"f_partial": "Щоб безпечно завантажити файл, який зараз завантажується, будь ласка, клацніть на файл, який має таке саме ім'я, але без розширення .PARTIAL. Будь ласка, натисніть Скасувати або Escape, щоб зробити це.\n\nНатиснення Гаразд / Enter проігнорує це попередження і продовжить завантаження .PARTIAL робочого файлу замість цього, що майже напевно дасть вам пошкоджені дані.",
@@ -653,10 +656,13 @@ Ls.ukr = {
"rc_cut": "вирізати", //m
"rc_cpy": "копіювати", //m
"rc_pst": "вставити", //m
+ "rc_rnm": "перейменувати", //m
"rc_nfo": "нова папка", //m
"rc_nfi": "новий файл", //m
"rc_sal": "вибрати все", //m
"rc_sin": "інвертувати вибір", //m
+ "rc_shf": "поділитися цією папкою", //m
+ "rc_shs": "поділитися вибраним", //m
"lang_set": "оновити сторінку, щоб зміни набули чинності?",
diff --git a/copyparty/web/tl/vie.js b/copyparty/web/tl/vie.js
index 35a42cc5..4805c0ef 100644
--- a/copyparty/web/tl/vie.js
+++ b/copyparty/web/tl/vie.js
@@ -227,6 +227,7 @@ Ls.vie = {
"ct_ttips": '༼ ◕_◕ ༽">ℹ️ tooltips',
"ct_thumb": 'ở chế độ lưới, chuyển biểu tượng hoặc hình thu nhỏ$NPhím tắt: T">🖼️ ảnh thu nhỏ',
"ct_csel": 'dùng CTRL và SHIFT để chọn tệp trong chế độ lưới">sel',
+ "ct_dsel": 'dùng chọn bằng cách kéo trong chế độ lưới">kéo', //m
"ct_dl": 'cưỡng chế tải xuống (không hiện thị trong dòng) khi nhấp vào tệp">dl',
"ct_ihop": 'khi đóng trình xem ảnh, cuộn xuống tệp đã xem gần nhất">g⮯',
"ct_dots": 'hiển thị tệp ẩn (nếu máy chủ cho phép)">dotfiles',
@@ -268,6 +269,7 @@ Ls.vie = {
"cdt_ask": "khi cuộn xuống cuối,$Nthay vì tải thêm tệp,$Nhỏi người dùng muốn làm gì",
"cdt_hsort": "số lượng luật sắp xếp(<code>,sorthref</code>) được đưa vào URL media. Đặt bằng 0 cũng sẽ bỏ qua các quy tắc sắp xếp trong liên kết media khi nhấp vào chúng",
"cdt_ren": "bật menu chuột phải tùy chỉnh, menu mặc định vẫn truy cập được bằng shift + chuột phải", //m
+ "cdt_rdb": "hiển thị menu chuột phải thông thường khi menu tùy chỉnh đã mở và nhấp chuột phải lần nữa", //m
"tt_entree": "hiển thị thanh điều hướng (cây thư mục)$NPhím tắt: B",
"tt_detree": "hiển thị đường dẫn$NPhím tắt: B",
@@ -363,6 +365,7 @@ Ls.vie = {
"f_anota": "chỉ {0} trong {1} tệp được chọn;\nđể chọn toàn bộ thư mục, trước tiên hãy kéo xuống cuối",
"f_dls": 'những đường dẫn đến tệp trong thư mục này\nđã được chuyển thành đường dẫn tải trực tiếp',
+ "f_dl_nd": 'bỏ qua thư mục (hãy dùng tải zip/tar thay thế):\n', //m
"f_partial": "Để tải an toàn một tệp đang được tải lên, hãy bấm vào tệp có cùng tên nhưng *không* có phần mở rộng .PARTIAL. Hãy nhấn CANCEL hoặc Escape để thực hiện.\n\nNếu nhấn OK / Enter, cảnh báo sẽ bị bỏ qua và bạn sẽ tải tệp tạm .PARTIAL thay vào đó, gần như chắc chắn dẫn đến dữ liệu bị hỏng.",
@@ -686,10 +689,13 @@ Ls.vie = {
"rc_cut": "cắt", //m
"rc_cpy": "sao chép", //m
"rc_pst": "dán", //m
+ "rc_rnm": "đổi tên", //m
"rc_nfo": "thư mục mới", //m
"rc_nfi": "tệp mới", //m
"rc_sal": "chọn tất cả", //m
"rc_sin": "đảo ngược lựa chọn", //m
+ "rc_shf": "chia sẻ thư mục này", //m
+ "rc_shs": "chia sẻ lựa chọn", //m
"lang_set": "tải lại trang để áp dụng thay đổi ngôn ngữ",
diff --git a/copyparty/web/up2k.js b/copyparty/web/up2k.js
index 1a9f3d67..bee1602a 100644
--- a/copyparty/web/up2k.js
+++ b/copyparty/web/up2k.js
@@ -1535,7 +1535,7 @@ function up2k_init(subtle) {
pvis.addfile([
uc.fsearch ? esc(entry.name) : linksplit(
- entry.purl + uricom_enc(entry.name)).join(' / '),
+ entry.purl + uricom_enc(entry.name), window.up_site).join(' / '),
'📐 ' + L.u_hashing,
''
], entry.size, draw_each);
@@ -1575,8 +1575,8 @@ function up2k_init(subtle) {
more_one_file();
function linklist() {
- var ret = [],
- base = location.origin.replace(/\/$/, '');
+ var ret = [],
+ base = (window.up_site || location.origin).replace(/\/$/, '');
for (var a = 0; a < st.files.length; a++) {
var t = st.files[a],
@@ -2562,7 +2562,7 @@ function up2k_init(subtle) {
cdiff = (Math.abs(diff) <= 2) ? '3c0' : 'f0b',
sdiff = 'diff ' + diff;
- msg.push(linksplit(hit.rp).join(' / ') + '.PARTIAL file extension. Please press CANCEL or Escape to do this.\n\nPressing OK / Enter will ignore this warning and continue downloading the .PARTIAL scratchfile instead, which will almost definitely give you corrupted data.",
@@ -674,7 +677,7 @@ Ls.hmn = {
"rc_ply": "play",
"rc_pla": "play as audio",
"rc_txt": "open in textfile viewer",
- "rc_md": "open in text editor",
+ "rc_md": "open in markdown viewer",
"rc_dl": "download",
"rc_zip": "download as archive",
"rc_cpl": "copy link",
@@ -682,10 +685,13 @@ Ls.hmn = {
"rc_cut": "cut",
"rc_cpy": "copy",
"rc_pst": "paste",
+ "rc_rnm": "rename",
"rc_nfo": "new folder",
"rc_nfi": "new file",
"rc_sal": "select all",
"rc_sin": "invert selection",
+ "rc_shf": "share this folder",
+ "rc_shs": "share selection",
"lang_set": "refresh to make the change take effect?",
diff --git a/tests/test_webdav.py b/tests/test_webdav.py
index d7618a03..7714b61b 100644
--- a/tests/test_webdav.py
+++ b/tests/test_webdav.py
@@ -63,6 +63,20 @@ Accept-Encoding: gzip
fgsfds"""
+RCLONE_PUT_FLOAT = """PUT /%s HTTP/1.1
+Host: 127.0.0.1:3923
+User-Agent: rclone/v1.67.0
+Content-Length: 6
+Authorization: Basic azp1
+Content-Type: application/octet-stream
+Oc-Checksum: SHA1:f5e3dc3fb27af53cd0005a1184e2df06481199e8
+Referer: http://127.0.0.1:3923/
+X-Oc-Mtime: 1689453578.123
+Accept-Encoding: gzip
+
+fgsfds"""
+
+
# tcpdump of `rclone delete dav:/a/d1/` (it does propfind recursively and then this on each file)
# (note: `rclone rmdirs dav:/a/d1/` does the same thing but just each folder after asserting they're empty)
RCLONE_DELETE = """DELETE /%s HTTP/1.1
@@ -201,6 +215,11 @@ class TestHttpCli(TC):
h, b = self.req(RCLONE_PUT % ("a/fa",))
self.assertStart("HTTP/1.1 201 Created\r", h)
+ # float x-oc-mtime should be accepted
+ h, b = self.req(RCLONE_PUT_FLOAT % ("a/fb",))
+ self.assertStart("HTTP/1.1 201 Created\r", h)
+ self.assertAlmostEqual(os.path.getmtime("a/fb"), 1689453578.123, places=3)
+
# then it does a propfind to confirm
h, b = self.req(RCLONE_PROPFIND % ("a/fa",))
fns = pfind2ls(b)
diff --git a/tests/util.py b/tests/util.py
index 931d98f0..a665c090 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -143,7 +143,7 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None, **ka0):
ka = {}
- 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 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()})
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"
@@ -164,10 +164,10 @@ class Cfg(Namespace):
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()})
- ex = "ah_alg bname chdir chmod_f chpw_db doctitle df epilogues exit favico 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 tcolor textfiles txt_eol ufavico ufavico_h unlist vname xff_src zipmaxt R RS SR"
+ ex = "ah_alg bname chdir chmod_f chpw_db db_xattr doctitle df epilogues exit favico 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 shr_site site smsg tcolor textfiles txt_eol ufavico ufavico_h unlist up_site vname xff_src zipmaxt R RS SR"
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 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"
ka.update(**{k: "no" for k in ex.split()})
ex = "ext_th grp idp_h_usr idp_hm_usr ipr on403 on404 qr_file xac xad xar xau xban xbc xbd xbr xbu xiu xm"