diff --git a/.gitignore b/.gitignore index b7b533b4..b70c56b7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ scripts/docker/*.err # nix build output link result + +# IDEA config +.idea/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 9ab69494..4a4217be 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,7 @@ "configurations": [ { "name": "Run copyparty", - "type": "python", + "type": "debugpy", "request": "launch", "module": "copyparty", "console": "integratedTerminal", @@ -11,30 +11,46 @@ "justMyCode": false, "env": { "PYDEVD_DISABLE_FILE_VALIDATION": "1", - "PYTHONWARNINGS": "always", //error + "PYTHONWARNINGS": "always" //error }, "args": [ - //"-nw", - "-ed", - "-emp", + //"-nw", // no-write; for testing uploads without writing to disk + //"-q", // quiet; speedboost when console output is not needed + + // # increase debugger performance: + //"no-htp", + //"hash-mt=0", + //"mtag-mt=1", + //"th-mt=1", + + // # listen for FTP and TFTP + "--ftp=3921", + "--ftp-pr=12000-12099", + "--tftp=3969", + + // # listen on all IPv6, all IPv4, and unix-socket + "-i::,unix:777:a.sock", + + // # misc + "--dedup", "-e2dsa", "-e2ts", - "-mtp=.bpm=f,bin/mtag/audio-bpm.py", + "--rss", + "--shr=/shr", + "--stats", + "-z", + + // # users + volumes "-aed:wark", - "-vsrv::r:rw,ed:c,dupe", - "-vdist:dist:r" + "-vdist:dist:r", + "-vsrv::r:rw,ed", + "-vsrv/junk:junk:r:A,ed", + "--ver" ] }, - { - "name": "No debug", - "preLaunchTask": "no_dbg", - "type": "python", - //"request": "attach", "port": 42069 - // fork: nc -l 42069 ⚠ you are now entering a no-copilot zone -the *only* place where LMM/AI *may* be accepted is for [localization](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#translations) if you are fluent and have confirmed that the translation is accurate. +the *only* place where LLM/AI *may* be accepted is for [localization](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#translations) if you are fluent and have confirmed that the translation is accurate. sorry for the harsh tone, but this is important to me 🙏 diff --git a/README.md b/README.md index 403cc85b..06a35efb 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ made in Norway 🇳🇴 * [shares](#shares) - share a file or folder by creating a temporary link * [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI * [rss feeds](#rss-feeds) - monitor a folder with your RSS reader + * [opds feeds](#opds-feeds) - browse and download files from your e-book reader * [recent uploads](#recent-uploads) - list all recent uploads * [media player](#media-player) - plays almost every audio format there is * [playlists](#playlists) - create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists @@ -90,7 +91,9 @@ made in Norway 🇳🇴 * [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/)) * [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/)) * [ip auth](#ip-auth) - autologin based on IP range (CIDR) + * [restrict to ip](#restrict-to-ip) - limit a user to certain IP ranges (CIDR) * [identity providers](#identity-providers) - replace copyparty passwords with oauth and such + * [generic header auth](#generic-header-auth) - other ways to auth by header * [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords * [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar * [hiding from google](#hiding-from-google) - tell search engines you don't wanna be indexed @@ -110,6 +113,7 @@ made in Norway 🇳🇴 * [packages](#packages) - the party might be closer than you think * [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/)) * [fedora package](#fedora-package) - does not exist yet + * [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg` * [nix package](#nix-package) - `nix profile install github:9001/copyparty` * [nixos module](#nixos-module) * [browser support](#browser-support) - TLDR: yes @@ -139,6 +143,7 @@ made in Norway 🇳🇴 * [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+) * [zipapp](#zipapp) - another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) * [install on android](#install-on-android) +* [install on iOS](#install-on-iOS) * [reporting bugs](#reporting-bugs) - ideas for context to include, and where to submit them * [devnotes](#devnotes) - for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md) @@ -147,10 +152,13 @@ made in Norway 🇳🇴 just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉 +> ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance + * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty` * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package) * or if you are on android, [install copyparty in termux](#install-on-android) +* or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS) * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md) * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty` * or if your computer is messed up and nothing else works, [try the pyz](#zipapp) @@ -238,7 +246,7 @@ also see [comparison to similar software](./docs/versus.md) * ☑ [upnp / zeroconf / mdns / ssdp](#zeroconf) * ☑ [event hooks](#event-hooks) / script runner * ☑ [reverse-proxy support](https://github.com/9001/copyparty#reverse-proxy) - * ☑ cross-platform (Windows, Linux, Macos, Android, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64) + * ☑ cross-platform (Windows, Linux, Macos, Android, iOS, FreeBSD, arm32/arm64, ppc64le, s390x, risc-v/riscv64) * upload * ☑ basic: plain multipart, ie6 support * ☑ [up2k](#uploading): js, resumable, multithreaded @@ -261,10 +269,11 @@ also see [comparison to similar software](./docs/versus.md) * ☑ play video files as audio (converted on server) * ☑ create and play [m3u8 playlists](#playlists) * ☑ image gallery with webm player - * ☑ [textfile browser](#textfile-viewer) with syntax hilighting + * ☑ [textfile browser](#textfile-viewer) with syntax highlighting * ☑ realtime streaming of growing files (logfiles and such) * ☑ [thumbnails](#thumbnails) * ☑ ...of images using Pillow, pyvips, or FFmpeg + * ☑ ...of RAW images using rawpy * ☑ ...of videos using FFmpeg * ☑ ...of audio (spectrograms) using FFmpeg * ☑ cache eviction (max-age; maybe max-size eventually) @@ -436,6 +445,7 @@ upgrade notes * can I link someone to a password-protected volume/file by including the password in the URL? * yes, by adding `?pw=hunter2` to the end; replace `?` with `&` if there are parameters in the URL already, meaning it contains a `?` near the end + * if you have enabled `--usernames` then do `?pw=username:password` instead * how do I stop `.hist` folders from appearing everywhere on my HDD? * by default, a `.hist` folder is created inside each volume for the filesystem index, thumbnails, audio transcodes, and markdown document history. Use the `--hist` global-option or the `hist` volflag to move it somewhere else; see [database location](#database-location) @@ -510,16 +520,23 @@ examples: * replacing the `g` permission with `wg` would let anonymous users upload files, but not see the required filekey to access it * replacing the `g` permission with `wG` would let anonymous users upload files, receiving a working direct link in return +if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct` + anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf` +* you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc) + ```yaml [accounts] u1: p1 # create account "u1" with password "p1" u2: p2 # (note that comments must have u3: p3 # two spaces before the # sign) +[groups] + g1: u1, u2 # create a group + [/] # this URL will be mapped to... /srv # ...this folder on the server filesystem accs: @@ -529,6 +546,8 @@ and if you want to use config files instead of commandline args (good!) then her /mnt/music # which is mapped to this folder accs: r: u1, u2 # only these accounts can read, + r: @g1 # (exactly the same, just with a group instead) + r: @acct # (alternatively, ALL users who are logged in) rw: u3 # and only u3 can read-write [/inc] @@ -555,6 +574,8 @@ for example `-v /mnt::r -v /var/empty:web/certs:r` mounts the server folder `/mn the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead +> ℹ️ this also works for single files, because files can also be volumes + ## dotfiles @@ -817,7 +838,7 @@ the up2k UI is the epitome of polished intuitive experiences: * `[🔎]` switch between upload and [file-search](#file-search) mode * ignore `[🔎]` if you add files by dragging them into the browser -and then theres the tabs below it, +and then there's the tabs below it, * `[ok]` is the files which completed successfully * `[ng]` is the ones that failed / got rejected (already exists, ...) * `[done]` shows a combined list of `[ok]` and `[ng]`, chronological order @@ -849,6 +870,8 @@ the files will be hashed on the client-side, and each hash is sent to the server files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]` * the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much +if you have a "wark" (file-identifier/checksum) then you can also search for that in the [🔎] tab by putting `w = kFpDiztbZc8Z1Lzi` in the `raw` field + ### unpost @@ -1009,6 +1032,7 @@ a feed example: https://cd.ocv.me/a/d2/d22/?rss&fext=mp3 url parameters: * `pw=hunter2` for password auth + * if you enabled `--usernames` then do `pw=username:password` instead * `recursive` to also include subfolders * `title=foo` changes the feed title (default: folder name) * `fext=mp3,opus` only include mp3 and opus files (default: all) @@ -1020,6 +1044,28 @@ url parameters: * uppercase = reverse-sort; `M` = oldest file first +## opds feeds + +browse and download files from your e-book reader + +enabled with the `opds` volflag or `--opds` global option + +add `?opds` to the end of the url you would like to browse, then input that in your opds client. +for example: `https://copyparty.example/books/?opds`. + +to log in with a password, enter it into either of the username or password fields in your client. + +- if you've enabled `--usernames`, then you need to enter both username and password . + +note: some clients (e.g. Moon+ Reader) will not send the password when downloading cover images, which will +cause your ip to be banned by copyparty. to work around this, you can grant the [`g` permission](#accounts-and-volumes) +to unauthenticated requests and enable [filekeys](#filekeys) to prevent guessing filenames. for example: +`-vbooks:books:r,ed:g:c,fk,opds` + +by default, not all file types will be listed in opds feeds. to change this, add the extension to +`--opds-exts` (volflag: `opds_exts`), or empty the list to list everything + + ## recent uploads list all recent uploads by clicking "show recent uploads" in the controlpanel @@ -1048,7 +1094,7 @@ plays almost every audio format there is (if the server has FFmpeg installed fo the following audio formats are usually always playable, even without FFmpeg: `aac|flac|m4a|mp3|ogg|opus|wav` -some hilights: +some highlights: * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png)) * shows the audio waveform in the seekbar * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended @@ -1085,6 +1131,9 @@ open the `[🎺]` media-player-settings tab to configure it, * `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1 * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere + * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones + * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment + * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space * "tint" reduces the contrast of the playback bar @@ -1219,7 +1268,7 @@ using arguments or config files, or a mix of both: **NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags. * if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help` -* or see this (probably outdated): https://ocv.me/copyparty/helptext.html +* or see this: https://ocv.me/copyparty/helptext.html * or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt @@ -1276,6 +1325,12 @@ print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/ * `--qrl lootbox/?pw=hunter2` appends to the url, linking to the `lootbox` folder with password `hunter2` * `--qrz 1` forces 1x zoom instead of autoscaling to fit the terminal size * 1x may render incorrectly on some terminals/fonts, but 2x should always work +* `--qr-pin 1` makes the qr-code stick to the bottom of the console (never scrolls away) +* `--qr-file qr.txt:1:2` writes a small qr-code to `qr.txt` +* `--qr-file qr.txt:2:2` writes a big qr-code to `qr.txt` +* `--qr-file qr.svg:1:2` writes a vector-graphics qr-code to `qr.svg` +* `--qr-file qr.png:8:4:333333:ffcc55` writes an 8x-magnified yellow-on-gray `qr.png` +* `--qr-file qr.png:8:4::ffffff` writes an 8x-magnified white-on-transparent `qr.png` it uses the server hostname if [mdns](#mdns) is enabled, otherwise it'll use your external ip (default route) unless `--qri` specifies a specific ip-prefix or domain @@ -1291,6 +1346,7 @@ an FTP server can be started using `--ftp 3921`, and/or `--ftps` for explicit T * if you enable both `ftp` and `ftps`, the port-range will be divided in half * some older software (filezilla on debian-stable) cannot passive-mode with TLS * login with any username + your password, or put your password in the username field + * unless you enabled `--usernames` some recommended FTP / FTPS clients; `wark` = example password: * https://winscp.net/eng/download.php @@ -1298,6 +1354,16 @@ some recommended FTP / FTPS clients; `wark` = example password: * https://rclone.org/ does FTPS with `tls=false explicit_tls=true` * `lftp -u k,wark -p 3921 127.0.0.1 -e ls` * `lftp -u k,wark -p 3990 127.0.0.1 -e 'set ssl:verify-certificate no; ls'` +* `curl ftp://127.0.0.1:3921/` (plaintext ftp) +* `curl --ssl-reqd ftp://127.0.0.1:3990/` (encrypted ftps) + +config file example, which restricts FTP to only use ports 3921 and 12000-12099 so all of those ports must be opened in your firewall: + +```yaml +[global] + ftp: 3921 + ftp-pr: 12000-12099 +``` ## webdav server @@ -1308,11 +1374,12 @@ click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to se general usage: * login with any username + your password, or put your password in the username field (password field can be empty/whatever) + * unless you enabled `--usernames` on macos, connect from finder: * [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/ -in order to grant full write-access to webdav clients, the volflag `daw` must be set and the account must also have delete-access (otherwise the client won't be allowed to replace the contents of existing files, which is how webdav works) +to upload or edit files with WebDAV clients, enable the `daw` volflag (because most WebDAV clients expect this) and give your account the delete-permission. This avoids getting several copies of the same file on the server. HOWEVER: This will also make all PUT-uploads overwrite existing files if the user has delete-access, so use with caution. > note: if you have enabled [IdP authentication](#identity-providers) then that may cause issues for some/most webdav clients; see [the webdav section in the IdP docs](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients) @@ -1323,6 +1390,7 @@ using the GUI (winXP or later): * rightclick [my computer] -> [map network drive] -> Folder: `http://192.168.123.1:3923/` * on winXP only, click the `Sign up for online storage` hyperlink instead and put the URL there * providing your password as the username is recommended; the password field can be anything or empty + * unless you enabled `--usernames` the webdav client that's built into windows has the following list of bugs; you can avoid all of these by connecting with rclone instead: * win7+ doesn't actually send the password to the server when reauthenticating after a reboot unless you first try to login with an incorrect password and then switch to the correct password @@ -1380,6 +1448,7 @@ some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance: * the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh) or [bubbleparty](./bin/bubbleparty.sh) * account passwords work per-volume as expected, and so does account permissions (read/write/move/delete), but `--smbw` must be given to allow write-access from smb * [shadowing](#shadowing) probably works as expected but no guarantees +* not compatible with pw-hashing or `--usernames` and some minor issues, * clients only see the first ~400 files in big folders; @@ -1390,6 +1459,7 @@ and some minor issues, * win10 onwards does not allow connecting anonymously / without accounts * python3 only * slow (the builtin webdav support in windows is 5x faster, and rclone-webdav is 30x faster) + * those numbers are specifically for copyparty's smb-server (because it sucks); other smb-servers should be similar to webdav known client bugs: * on win7 only, `--smb1` is much faster than smb2 (default) because it keeps rescanning folders on smb2 @@ -1426,6 +1496,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne you can also hotlink files regardless by appending `?raw` to the url +> WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured + if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object) @@ -1508,6 +1580,7 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`, note: * upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings) + * and file checksums can be shown with global-option `-e2d -mte +w` or volflag `e2d,mte=+w` (always active for users with permission `a`) * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise config file example (these options are recommended btw): @@ -1591,6 +1664,7 @@ set upload rules using volflags, some examples: * `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`: * `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1) * `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format + * `:c,rotf_tz=Europe/Oslo` sets the timezone (default is UTC unless global-option `rotf-tz` is changed) * if someone uploads to `/foo/bar` the path would be rewritten to `/foo/bar/2021/08/06/23` for example * but the actual value is not verified, just the structure, so the uploader can choose any values which conform to the format string * just to avoid additional complexity in up2k which is enough of a mess already @@ -1874,6 +1948,20 @@ repeat the option to map additional subnets **be careful with this one!** if you have a reverseproxy, then you definitely want to make sure you have [real-ip](#real-ip) configured correctly, and it's probably a good idea to nullmap the reverseproxy's IP just in case; so if your reverseproxy is sending requests from `172.24.27.9` then that would be `--ipu=172.24.27.9/32=` +### restrict to ip + +limit a user to certain IP ranges (CIDR) , using the global-option `--ipr` + +for example, if the user `spartacus` should get rejected if they're not connecting from an IP that starts with `192.168.123` or `172.16`, then you can either specify `--ipr=192.168.123.0/24,172.16.0.0/16=spartacus` as a commandline option, or put this in a config file: + +```yaml +[global] + ipr: 192.168.123.0/24,172.16.0.0/16=spartacus +``` + +repeat the option to map additional users + + ## identity providers replace copyparty passwords with oauth and such @@ -1882,6 +1970,12 @@ you can disable the built-in password-based login system, and instead replace it * the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header + * `--auth-ord` configured auth precedence, for example to allow overriding the IdP with a copyparty password + +* the login/logout links/buttons can be replaced with links to your IdP with `--idp-login` and `--idp-logout` , for example `--idp-login /idp/login/?redir={dst}` will expand `{dst}` to the page the user was on when clicking Login + +* if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party + some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex) there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type) @@ -1891,6 +1985,20 @@ a more complete example of the copyparty configuration options [look like this]( but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead +### generic header auth + +other ways to auth by header + +if you have a middleware which adds a header with a user identifier, for example tailscale's `Tailscale-User-Login: alice.m@forest.net` then you can automatically auth as `alice` by defining that mapping with `--idp-hm-usr '^Tailscale-User-Login^alice.m@forest.net^alice'` or the following config file: + +```yaml +[global] + idp-hm-usr: ^Tailscale-User-Login^alice.m@forest.net^alice +``` + +repeat the whole `idp-hm-usr` option to add more mappings + + ## user-changeable passwords if permitted, users can change their own passwords in the control-panel @@ -2044,7 +2152,11 @@ you can either: * or do location-based proxying, using `--rp-loc=/stuff` to tell copyparty where it is mounted -- has a slight performance cost and higher chance of bugs * if copyparty says `incorrect --rp-loc or webserver config; expected vpath starting with [...]` it's likely because the webserver is stripping away the proxy location from the request URLs -- see the `ProxyPass` in the apache example below -when running behind a reverse-proxy (this includes services like cloudflare), it is important to configure real-ip correctly, as many features rely on knowing the client's IP. Look out for red and yellow log messages which explain how to do this. But basically, set `--xff-hdr` to the name of the http header to read the IP from (usually `x-forwarded-for`, but cloudflare uses `cf-connecting-ip`), and then `--xff-src` to the IP of the reverse-proxy so copyparty will trust the xff-hdr. Note that `--rp-loc` in particular will not work at all unless you do this +when running behind a reverse-proxy (this includes services like cloudflare), it is important to configure real-ip correctly, as many features rely on knowing the client's IP. The best/safest approach is to configure your reverse-proxy so it gives copyparty a header which only contains the client's true/real IP-address, and then setting `--xff-hdr theHeaderName --rproxy 1` but alternatively, if you want/need to let copyparty handle this, look out for red and yellow log messages which explain how to do that. Basically, the log will say this: + +> set `--xff-hdr` to the name of the http-header to read the IP from (usually `x-forwarded-for`, but cloudflare uses `cf-connecting-ip`), and then `--xff-src` to the IP of the reverse-proxy so copyparty will trust the xff-hdr. You will also need to configure `--rproxy` to `1` if the header only contains one IP (the correct one) or to a *negative value* if it contains multiple; `-1` being the rightmost and most trusted IP (the nearest proxy, so usually not the correct one), `-2` being the second-closest hop, and so on + +Note that `--rp-loc` in particular will not work at all unless you configure the above correctly some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatically obtain a valid https/tls certificate for you, and some support HTTP/2 and QUIC which *could* be a nice speed boost, depending on a lot of factors * **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now @@ -2102,7 +2214,7 @@ when connecting the reverse-proxy to `127.0.0.1` instead (the basic and/or old-f in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds when possible (traefik does not support it yet) -* if these results are bullshit because my config exampels are bad, please submit corrections! +* if these results are bullshit because my config examples are bad, please submit corrections! ## permanent cloudflare tunnel @@ -2238,6 +2350,7 @@ buggy feature? rip it out by setting any of the following environment variables | `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) | | `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy | | `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider | +| `PRTY_UNSAFE_STATE` | allow storing secrets into emergency-fallback locations | example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py` @@ -2263,11 +2376,9 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/)) -it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/` +it comes with a [systemd service](./contrib/systemd/copyparty@.service) as well as a [user service](./contrib/systemd/copyparty-user.service), and expects to find a [config file](./contrib/systemd/copyparty.example.conf) in `/etc/copyparty/copyparty.conf` or `~/.config/copyparty/copyparty.conf` -after installing it, you may want to `cp /usr/lib/systemd/system/copyparty.service /etc/systemd/system/` and then `vim /etc/systemd/system/copyparty.service` to change what user/group it is running as (you only need to do this once) - -NOTE: there used to be an aur package; this evaporated when copyparty was adopted by the official archlinux repos. If you're still using the aur package, please move +after installing, start either the system service or the user service and navigate to http://127.0.0.1:3923 for further instructions (unless you already edited the config files, in which case you are good to go, probably) ## fedora package @@ -2275,6 +2386,15 @@ NOTE: there used to be an aur package; this evaporated when copyparty was adopte does not exist yet; there are rumours that it is being packaged! keep an eye on this space... +## homebrew formulae + +`brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty + +should work on all macs (both intel and apple silicon) and all relevant macos versions + +the homebrew package is maintained by the homebrew team (thanks!) + + ## nix package `nix profile install github:9001/copyparty` @@ -2288,7 +2408,7 @@ some recommended dependencies are enabled by default; [override the package](htt ## nixos module -for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes) installation of NixOS. +for [flake-enabled](https://nixos.wiki/wiki/Flakes) installations of NixOS: ```nix { @@ -2315,6 +2435,33 @@ for this setup, you will need a [flake-enabled](https://nixos.wiki/wiki/Flakes) } ``` +if you don't use a flake in your configuration, you can use other dependency management tools like [npins](https://github.com/andir/npins), [niv](https://github.com/nmattia/niv), or even plain [`fetchTarball`](https://nix.dev/manual/nix/stable/language/builtins#builtins-fetchTarball), like so: + +```nix +{ pkgs, ... }: + +let + # npins example, adjust for your setup. copyparty should be a path to the downloaded repo + # for niv, just replace the npins folder import with the sources.nix file + copyparty = (import ./npins).copyparty; + + # or with fetchTarball: + copyparty = fetchTarball "https://github.com/9001/copyparty/archive/hovudstraum.tar.gz"; +in + +{ + # load the copyparty NixOS module + imports = [ "${copyparty}/contrib/nixos/modules/copyparty.nix" ]; + + # add the copyparty overlay to expose the package to the module + nixpkgs.overlays = [ (import "${copyparty}/contrib/package/nix/overlay.nix") ]; + # (optional) install the package globally + environment.systemPackages = [ pkgs.copyparty ]; + # configure the copyparty module + services.copyparty.enable = true; +} +``` + copyparty on NixOS is configured via `services.copyparty` options, for example: ```nix services.copyparty = { @@ -2430,6 +2577,7 @@ quick summary of more eccentric web-browsers trying to view a directory index: | **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl | | **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) | | **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) | +| **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |

@@ -2489,6 +2637,8 @@ you can provide passwords using header `PW: hunter2`, cookie `cppwd=hunter2`, ur > for basic-authentication, all of the following are accepted: `password` / `whatever:password` / `password:whatever` (the username is ignored) +* unless you've enabled `--usernames`, then it's `PW: usr:pwd`, cookie `cppwd=usr:pwd`, url-param `?pw=usr:pwd` + NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename @@ -2498,11 +2648,20 @@ sync folders to/from copyparty NOTE: full bidirectional sync, like what [nextcloud](https://docs.nextcloud.com/server/latest/user_manual/sv/files/desktop_mobile_sync.html) and [syncthing](https://syncthing.net/) does, will never be supported! Only single-direction sync (server-to-client, or client-to-server) is possible with copyparty +* if you want bidirectional sync, then copyparty and syncthing *should* be entirely safe to combine; they should be able to collaborate on the same folders without causing any trouble for eachother. Many people do this, and there have been no issues so far. But, if you *do* encounter any problems, please [file a copyparty bug](https://github.com/9001/copyparty/issues/new/choose) and I'll try to help -- just keep in mind I've never used syncthing before :-) + the commandline uploader [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy) with `--dr` is the best way to sync a folder to copyparty; verifies checksums and does files in parallel, and deletes unexpected files on the server after upload has finished which makes file-renames really cheap (it'll rename serverside and skip uploading) +if you want to sync with `u2c.py` then: +* the `e2dsa` option (either globally or volflag) must be enabled on the server for the volumes you're syncing into +* ...but DON'T enable global-options `no-hash` or `no-idx` (or volflags `nohash` / `noidx`), or at least make sure they are configured so they do not affect anything you are syncing into +* ...and u2c needs the delete-permission, so either `rwd` at minimum, or just `A` which is the same as `rwmd.a` + * quick reminder that `a` and `A` are different permissions, and `.` is very useful for sync + alternatively there is [rclone](./docs/rclone.md) which allows for bidirectional sync and is *way* more flexible (stream files straight from sftp/s3/gcs to copyparty, ...), although there is no integrity check and it won't work with files over 100 MiB if copyparty is behind cloudflare * starting from rclone v1.63, rclone is faster than u2c.py on low-latency connections + * but this is only true for the initial upload; u2c will be faster for periodic syncing ## mount as drive @@ -2544,6 +2703,8 @@ there is no iPhone app, but the following shortcuts are almost as good: * can download links and rehost the target file on copyparty (see first comment inside the shortcut) * pics become lowres if you share from gallery to shortcut, so better to launch the shortcut and pick stuff from there +if you want to run the copyparty server on your iPhone or iPad, see [install on iOS](#install-on-iOS) + # performance @@ -2572,6 +2733,10 @@ below are some tweaks roughly ordered by usefulness: * using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others * and pypy can sometimes crash on startup with `-j0` (TODO make issue) +* if you are running the copyparty server **on Windows or Macos:** + * `--casechk=n` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem + * this is the same as `casechk: n` in a config-file + ## client-side @@ -2600,7 +2765,7 @@ there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` some notes on hardening -* set `--rproxy 0` if your copyparty is directly facing the internet (not through a reverse-proxy) +* set `--rproxy 0` *if and only if* your copyparty is directly facing the internet (not through a reverse-proxy) * cors doesn't work right otherwise * if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml` * this returns html documents as plaintext, and also disables markdown rendering @@ -2700,6 +2865,12 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop +when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by: +* inspecting the generated salt using `--show-ah-salt` in copyparty service configuration +* setting the same `--ah-salt` in both environments + +> ⚠️ if you have enabled `--usernames` then provide the password as `username:password` when hashing it, for example `ed:hunter2` + ## https @@ -2761,9 +2932,10 @@ enable [music tags](#metadata-from-audio-files): enable [thumbnails](#thumbnails) of... * **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+) * **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH` -* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler) +* **HEIF pictures:** `pyvips` or `ffmpeg` or `pillow-heif` * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+ * **JPEG XL pictures:** `pyvips` or `ffmpeg` +* **RAW images:** `rawpy`, plus one of `pyvips` or `Pillow` (for some formats) enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq` @@ -2794,9 +2966,10 @@ set any of the following environment variables to disable its associated optiona | `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg | | `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails | | `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) | -| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) | +| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) | | `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow | | `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows | +| `PRTY_NO_RAW` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images | | `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg | example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py` @@ -2817,6 +2990,8 @@ these are standalone programs and will never be imported / evaluated by copypart the self-contained "binary" (recommended!) [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course +if you only need english, [copyparty-en.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-en.py) is the same thing but smaller + you can reduce the sfx size by repacking it; see [./docs/devnotes.md#sfx-repack](./docs/devnotes.md#sfx-repack) @@ -2844,7 +3019,7 @@ then again, if you are already into downloading shady binaries from the internet ## zipapp -another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but it does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails +another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails @@ -2867,6 +3042,27 @@ if you want thumbnails (photos+videos) and you're okay with spending another 132 * or if you want to use `vips` for photo-thumbs instead, `pkg install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})` +# install on iOS + +first install one of the following: +* [a-Shell mini](https://apps.apple.com/us/app/a-shell-mini/id1543537943) gives you the essential features +* [a-Shell](https://apps.apple.com/us/app/a-shell/id1473805438) also enables audio transcoding and better thubmnails + +and then copypaste the following command into `a-Shell`: + +```sh +curl -L https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh +``` + +what this does: +* creates a basic [config file](#accounts-and-volumes) named `cpc` which you can edit with `vim cpc` +* adds the command `cpp` to launch copyparty with that config file + +known issues: +* cannot run in the background; it needs to be on-screen to accept connections / uploads / downloads +* the best way to exit copyparty is to swipe away the app + + # reporting bugs ideas for context to include, and where to submit them diff --git a/bin/README.md b/bin/README.md index fd94172f..1fef13f4 100644 --- a/bin/README.md +++ b/bin/README.md @@ -1,7 +1,7 @@ # [`u2c.py`](u2c.py) * command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm) * file uploads, file-search, autoresume of aborted/broken uploads -* sync local folder to server +* [sync local folder to server](https://github.com/9001/copyparty/#folder-sync) * generally faster than browsers * if something breaks just restart it diff --git a/bin/bubbleparty.sh b/bin/bubbleparty.sh index 2e6aab3e..564546ee 100755 --- a/bin/bubbleparty.sh +++ b/bin/bubbleparty.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # usage: ./bubbleparty.sh ./copyparty-sfx.py .... bwrap \ --unshare-all \ @@ -9,7 +9,7 @@ bwrap \ --dev-bind /dev /dev \ --dir /tmp \ --dir /var \ - --bind $(pwd) $(pwd) \ + --bind "$(pwd)" "$(pwd)" \ --share-net \ --die-with-parent \ --file 11 /etc/passwd \ diff --git a/bin/dbtool.py b/bin/dbtool.py index ff92fca3..12e3d535 100755 --- a/bin/dbtool.py +++ b/bin/dbtool.py @@ -8,7 +8,7 @@ import sqlite3 import argparse DB_VER1 = 3 -DB_VER2 = 5 +DB_VER2 = 6 BY_PATH = None NC = None @@ -39,7 +39,7 @@ def ls(db): print(f"{nfiles} files") print(f"{ntags} tags\n") - print("number of occurences for each tag,") + print("number of occurrences for each tag,") print(" 'x' = file has no tags") print(" 't:mtp' = the mtp flag (file not mtp processed yet)") print() diff --git a/bin/handlers/redirect.py b/bin/handlers/redirect.py index 07b91d0c..fde29454 100644 --- a/bin/handlers/redirect.py +++ b/bin/handlers/redirect.py @@ -46,7 +46,7 @@ def main(cli, vn, rem): # uncomment one of these: send_http_302_temporary_redirect(cli, new_path) - #send_http_301_permanent_redirect(cli, new_path) - #send_errorpage_with_redirect_link(cli, new_path) + # send_http_301_permanent_redirect(cli, new_path) + # send_errorpage_with_redirect_link(cli, new_path) return "true" diff --git a/bin/hooks/notify.py b/bin/hooks/notify.py index 6e5dda86..bc12269d 100755 --- a/bin/hooks/notify.py +++ b/bin/hooks/notify.py @@ -9,7 +9,7 @@ from plyer import notification _ = r""" show os notification on upload; works on windows, linux, macos, android -depdencies: +dependencies: windows: python3 -m pip install --user -U plyer linux: python3 -m pip install --user -U plyer macos: python3 -m pip install --user -U plyer pyobjus diff --git a/bin/hooks/reject-and-explain.py b/bin/hooks/reject-and-explain.py new file mode 100644 index 00000000..6413d3a4 --- /dev/null +++ b/bin/hooks/reject-and-explain.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +import json +import os +import re +import sys + + +_ = r""" +reject file upload (with a nice explanation why) + +example usage as global config: + --xbu j,c1,bin/hooks/reject-and-explain.py + +example usage as a volflag (per-volume config): + -v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reject-and-explain.py + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + (share filesystem-path srv/inc as volume /inc, + readable by everyone, read-write for user 'ed', + running this plugin on all uploads with the params listed below) + +example usage as a volflag in a copyparty config file: + [/inc] + srv/inc + accs: + r: * + rw: ed + flags: + xbu: j,c1,bin/hooks/reject-and-explain.py + +parameters explained, + xbu = execute-before-upload (can also be xau, execute-after-upload) + j = this hook needs upload information as json (not just the filename) + c1 = this hook returns json on stdout, so tell copyparty to read that +""" + + +def main(): + inf = json.loads(sys.argv[1]) + vdir, fn = os.path.split(inf["vp"]) + print("inf[vp] = %r" % (inf["vp"],), file=sys.stderr) + + # the following is what decides if we'll accept the upload or reject it: + # we check if the upload-folder url matches the following regex-pattern: + ok = re.search(r"(^|/)day[0-9]+$", vdir, re.IGNORECASE) + + if ok: + # allow the upload + print("{}") + return + + # the upload was rejected; display the following errortext: + errmsg = "Files can only be uploaded into a folder named 'DayN' where N is a number, for example 'Day573'. This file was REJECTED: " + errmsg += inf["vp"] # if you want to mention the file's url + print(json.dumps({"rejectmsg": errmsg})) + + +if __name__ == "__main__": + main() diff --git a/bin/hooks/wget.py b/bin/hooks/wget.py index ad0c71c0..1f5a824b 100755 --- a/bin/hooks/wget.py +++ b/bin/hooks/wget.py @@ -66,7 +66,7 @@ def main(): try: sp.check_call(cmd) except: - t = "-- FAILED TO DONWLOAD " + name + t = "-- FAILED TO DOWNLOAD " + name print(f"{t}\n", end="") open(t, "wb").close() diff --git a/bin/mtag/guestbook-read.py b/bin/mtag/guestbook-read.py index 704addbe..e9119903 100755 --- a/bin/mtag/guestbook-read.py +++ b/bin/mtag/guestbook-read.py @@ -7,7 +7,7 @@ example copyparty config to use this: --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=guestbook=t10,ad,p,bin/mtag/guestbook-read.py:mte=+guestbook explained: - for realpath srv/hello (served at /hello), write-only for eveyrone, + for realpath srv/hello (served at /hello), write-only for everyone, enable file analysis on upload (e2ts), use mtp plugin "bin/mtag/guestbook-read.py" to provide metadata tag "guestbook", do this on all uploads regardless of extension, diff --git a/bin/mtag/guestbook.py b/bin/mtag/guestbook.py index 437289b6..84f8fa53 100644 --- a/bin/mtag/guestbook.py +++ b/bin/mtag/guestbook.py @@ -11,7 +11,7 @@ example copyparty config to use this: --urlform save,get -vsrv/hello:hello:w:c,e2ts,mtp=xgb=ebin,t10,ad,p,bin/mtag/guestbook.py:mte=+xgb explained: - for realpath srv/hello (served at /hello),write-only for eveyrone, + for realpath srv/hello (served at /hello),write-only for everyone, enable file analysis on upload (e2ts), use mtp plugin "bin/mtag/guestbook.py" to provide metadata tag "xgb", do this on all uploads with the file extension "bin", diff --git a/bin/mtag/wget.py b/bin/mtag/wget.py index 26a1fa45..d706052b 100644 --- a/bin/mtag/wget.py +++ b/bin/mtag/wget.py @@ -84,7 +84,7 @@ def main(): # on success, delete the .bin file which contains the URL os.unlink(fp) except: - open("-- FAILED TO DONWLOAD " + name, "wb").close() + open("-- FAILED TO DOWNLOAD " + name, "wb").close() os.unlink(tfn) print(url) diff --git a/bin/partyfuse.py b/bin/partyfuse.py index da7ce54e..0d88caca 100755 --- a/bin/partyfuse.py +++ b/bin/partyfuse.py @@ -6,8 +6,8 @@ __copyright__ = 2019 __license__ = "MIT" __url__ = "https://github.com/9001/copyparty/" -S_VERSION = "2.0" -S_BUILD_DT = "2024-10-01" +S_VERSION = "2.1" +S_BUILD_DT = "2025-09-06" """ mount a copyparty server (local or remote) as a filesystem @@ -99,7 +99,7 @@ except: elif MACOS: libfuse = "install https://osxfuse.github.io/" else: - libfuse = "apt install libfuse3-3\n modprobe fuse" + libfuse = "apt install libfuse2\n modprobe fuse" m = """\033[33m could not import fuse; these may help: @@ -359,7 +359,7 @@ class Gateway(object): def sendreq(self, meth, path, headers, **kwargs): tid = get_tid() if self.password: - headers["Cookie"] = "=".join(["cppwd", self.password]) + headers["PW"] = self.password try: c = self.getconn(tid) @@ -902,9 +902,7 @@ class CPPF(Operations): return ret def _readdir(self, path, fh=None): - path = path.strip("/") - dbg("readdir %r [%s]", path, fh) - + dbg("dircache miss") ret = self.gw.listdir(path) if not self.n_dircache: return ret @@ -914,11 +912,17 @@ class CPPF(Operations): self.dircache.append(cn) self.clean_dircache() - # import pprint; pprint.pprint(ret) return ret def readdir(self, path, fh=None): - return [".", ".."] + list(self._readdir(path, fh)) + dbg("readdir %r [%s]", path, fh) + path = path.strip("/") + cn = self.get_cached_dir(path) + if cn: + ret = cn.data + else: + ret = self._readdir(path, fh) + return [".", ".."] + list(ret) def read(self, path, length, offset, fh=None): req_max = 1024 * 1024 * 8 @@ -993,7 +997,6 @@ class CPPF(Operations): if cn: dents = cn.data else: - dbg("cache miss") dents = self._readdir(dirpath) try: @@ -1141,10 +1144,15 @@ def main(): if WINDOWS: examples.append("http://192.168.1.69:3923/music/ M:") + epi = "example:" + ex_pre + ex_pre.join(examples) + epi += """\n +NOTE: if server has --usernames enabled, then password is "username:password" +""" + ap = argparse.ArgumentParser( formatter_class=TheArgparseFormatter, description="mount a copyparty server as a local filesystem -- " + ver, - epilog="example:" + ex_pre + ex_pre.join(examples), + epilog=epi, ) # fmt: off ap.add_argument("base_url", type=str, help="remote copyparty URL to mount") diff --git a/bin/prisonparty.sh b/bin/prisonparty.sh index d495c833..403f1588 100755 --- a/bin/prisonparty.sh +++ b/bin/prisonparty.sh @@ -141,7 +141,7 @@ chmod 777 "$jail/tmp" # create a dev -(cd $jail; mkdir -p dev; cd dev +(cd "$jail"; mkdir -p dev; cd dev [ -e null ] || mknod -m 666 null c 1 3 [ -e zero ] || mknod -m 666 zero c 1 5 [ -e random ] || mknod -m 444 random c 1 8 diff --git a/bin/u2c.py b/bin/u2c.py index 85298730..aec03918 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.11" -S_BUILD_DT = "2025-05-18" +S_VERSION = "2.13" +S_BUILD_DT = "2025-09-05" """ u2c.py: upload to copyparty @@ -10,7 +10,7 @@ u2c.py: upload to copyparty https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py - dependencies: no -- supports python 2.6, 2.7, and 3.3 through 3.12 +- supports python 2.6, 2.7, and 3.3 through 3.14 - if something breaks just try again and it'll autoresume """ @@ -52,7 +52,7 @@ if PY2: sys.dont_write_bytecode = True bytes = str - files_decoder = lambda s: unicode(s, 'utf8') + files_decoder = lambda s: unicode(s, "utf8") else: from urllib.parse import quote_from_bytes as quote from urllib.parse import unquote_to_bytes as unquote @@ -590,9 +590,10 @@ def undns(url): def _scd(err, top): """non-recursive listing of directory contents, along with stat() info""" + top_ = os.path.join(top, b"") with os.scandir(top) as dh: for fh in dh: - abspath = os.path.join(top, fh.name) + abspath = top_ + fh.name try: yield [abspath, fh.stat()] except Exception as ex: @@ -601,8 +602,9 @@ def _scd(err, top): def _lsd(err, top): """non-recursive listing of directory contents, along with stat() info""" + top_ = os.path.join(top, b"") for name in os.listdir(top): - abspath = os.path.join(top, name) + abspath = top_ + name try: yield [abspath, os.stat(abspath)] except Exception as ex: @@ -677,7 +679,7 @@ def walkdirs(err, tops, excl): yield stop, ap[len(stop) :].lstrip(sep), inf else: d, n = top.rsplit(sep, 1) - yield d, n, os.stat(top) + yield d or b"/", n, os.stat(top) # mostly from copyparty/util.py @@ -1527,10 +1529,10 @@ def main(): # fmt: off ap = app = argparse.ArgumentParser(formatter_class=APF, description="copyparty up2k uploader / filesearch tool " + ver, epilog=""" -NOTE: -source file/folder selection uses rsync syntax, meaning that: +NOTE: source file/folder selection uses rsync syntax, meaning that: "foo" uploads the entire folder to URL/foo/ "foo/" uploads the CONTENTS of the folder into URL/ +NOTE: if server has --usernames enabled, then password is "username:password" """) ap.add_argument("url", type=unicode, help="server url, including destination folder") diff --git a/contrib/README.md b/contrib/README.md index 7256766b..e924f3db 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -7,6 +7,12 @@ * works on windows, linux and macos * assumes `copyparty-sfx.py` was renamed to `copyparty.py` in the same folder as `copyparty.bat` +### [`setup-ashell.sh`](setup-ashell.sh) +* run copyparty on an iPhone/iPad using [a-Shell](https://holzschu.github.io/a-Shell_iOS/) +* not very useful due to limitations in iOS: + * not able to share all of your phone's storage + * cannot run in the background + ### [`index.html`](index.html) * drop-in redirect from an httpd to copyparty * assumes the webserver and copyparty is running on the same server/IP diff --git a/contrib/nginx/copyparty.conf b/contrib/nginx/copyparty.conf index 121e52ab..f0d382cc 100644 --- a/contrib/nginx/copyparty.conf +++ b/contrib/nginx/copyparty.conf @@ -31,7 +31,7 @@ # generate the list of permitted IP ranges like so: # (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf # -# and then enable it below by uncomenting the cloudflare-only.conf line +# and then enable it below by uncommenting the cloudflare-only.conf line # # ====================================================================== diff --git a/contrib/nixos/modules/copyparty.nix b/contrib/nixos/modules/copyparty.nix index 994b2575..2a553003 100644 --- a/contrib/nixos/modules/copyparty.nix +++ b/contrib/nixos/modules/copyparty.nix @@ -50,7 +50,9 @@ let configStr = '' ${mkSection "global" cfg.settings} + ${cfg.globalExtraConfig} ${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)} + ${mkSection "groups" cfg.groups} ${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)} ''; @@ -131,6 +133,12 @@ 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."; + }; + accounts = mkOption { type = types.attrsOf ( types.submodule ( @@ -160,6 +168,19 @@ in ''; }; + groups = mkOption { + type = types.attrsOf (types.listOf types.str); + description = '' + A set of copyparty groups to create and the users that should be part of each group. + ''; + default = { }; + example = literalExpression '' + { + group_name = [ "user1" "user2" ]; + }; + ''; + }; + volumes = mkOption { type = types.attrsOf ( types.submodule ( @@ -373,3 +394,4 @@ in } ); } + diff --git a/contrib/package/arch/PKGBUILD b/contrib/package/arch/PKGBUILD index 40c0b62b..872dfd51 100644 --- a/contrib/package/arch/PKGBUILD +++ b/contrib/package/arch/PKGBUILD @@ -1,57 +1,48 @@ # Maintainer: icxes +# Contributor: Morgan Adamiec +# 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.18.9" +pkgver="1.19.15" pkgrel=1 pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" arch=("any") url="https://github.com/9001/${pkgname}" license=('MIT') -depends=("python" "lsof" "python-jinja") +depends=("bash" "python" "lsof" "python-jinja") makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz") optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags" - "cfssl: generate TLS certificates on startup (pointless when reverse-proxied)" - "python-mutagen: music tags (alternative)" - "python-pillow: thumbnails for images" - "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" - "libkeyfinder-git: detection of musical keys" - "qm-vamp-plugins: BPM detection" - "python-pyopenssl: ftps functionality" - "python-pyzmq: send zeromq messages from event-hooks" - "python-argon2-cffi: hashed passwords in config" - "python-impacket-git: smb support (bad idea)" + "cfssl: generate TLS certificates on startup" + "python-mutagen: music tags (alternative)" + "python-pillow: thumbnails for images" + "python-pyvips: thumbnails for images (higher quality, faster, uses more ram)" + "libkeyfinder: detection of musical keys" + "python-pyopenssl: ftps functionality" + "python-pyzmq: send zeromq messages from event-hooks" + "python-argon2-cffi: hashed passwords in config" ) source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") -backup=("etc/${pkgname}.d/init" ) -sha256sums=("d5d33b50d6717e52427956beb1687061a6f28b467997506505151e3ae18c58e5") +backup=("etc/${pkgname}/copyparty.conf" ) +sha256sums=("95f4c1e517acaadd5fdc38b47047f16a7cd51495bd94ae9182274f256db59834") build() { + cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web" + make + cd "${srcdir}/${pkgname}-${pkgver}" - - pushd copyparty/web - make -j$(nproc) - rm Makefile - popd - - python3 -m build -wn + python -m build --wheel --no-isolation } package() { cd "${srcdir}/${pkgname}-${pkgver}" - python3 -m installer -d "$pkgdir" dist/*.whl + python -m installer --destdir="$pkgdir" dist/*.whl - install -dm755 "${pkgdir}/etc/${pkgname}.d" + install -dm755 "${pkgdir}/etc/${pkgname}" install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty" - install -Dm644 "contrib/package/arch/${pkgname}.conf" "${pkgdir}/etc/${pkgname}.d/init" - install -Dm644 "contrib/package/arch/${pkgname}.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}.service" - install -Dm644 "contrib/package/arch/prisonparty.service" "${pkgdir}/usr/lib/systemd/system/prisonparty.service" - install -Dm644 "contrib/package/arch/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" + install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf" + install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service" + install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service" + install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service" + install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md" install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" - - find /etc/${pkgname}.d -iname '*.conf' 2>/dev/null | grep -qE . && return - echo "┏━━━━━━━━━━━━━━━──-" - echo "┃ Configure ${pkgname} by adding .conf files into /etc/${pkgname}.d/" - echo "┃ and maybe copy+edit one of the following to /etc/systemd/system/:" - echo "┣━♦ /usr/lib/systemd/system/${pkgname}.service (standard)" - echo "┣━♦ /usr/lib/systemd/system/prisonparty.service (chroot)" - echo "┗━━━━━━━━━━━━━━━──-" } diff --git a/contrib/package/arch/copyparty.conf b/contrib/package/arch/copyparty.conf deleted file mode 100644 index 1d90d772..00000000 --- a/contrib/package/arch/copyparty.conf +++ /dev/null @@ -1,7 +0,0 @@ -## import all *.conf files from the current folder (/etc/copyparty.d) -% ./ - -# add additional .conf files to this folder; -# see example config files for reference: -# https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf -# https://github.com/9001/copyparty/tree/hovudstraum/docs/copyparty.d diff --git a/contrib/package/arch/copyparty.service b/contrib/package/arch/copyparty.service deleted file mode 100644 index 22dac3d6..00000000 --- a/contrib/package/arch/copyparty.service +++ /dev/null @@ -1,32 +0,0 @@ -# this will start `/usr/bin/copyparty-sfx.py` -# and read config from `/etc/copyparty.d/*.conf` -# -# you probably want to: -# change "User=cpp" and "/home/cpp/" to another user -# -# unless you add -q to disable logging, you may want to remove the -# following line to allow buffering (slightly better performance): -# Environment=PYTHONUNBUFFERED=x - -[Unit] -Description=copyparty file server - -[Service] -Type=notify -SyslogIdentifier=copyparty -Environment=PYTHONUNBUFFERED=x -WorkingDirectory=/var/lib/copyparty-jail -ExecReload=/bin/kill -s USR1 $MAINPID - -# user to run as + where the TLS certificate is (if any) -User=cpp -Environment=XDG_CONFIG_HOME=/home/cpp/.config - -# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running -ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' - -# run copyparty -ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init - -[Install] -WantedBy=multi-user.target diff --git a/contrib/package/arch/index.md b/contrib/package/arch/index.md deleted file mode 100644 index 016c0b0a..00000000 --- a/contrib/package/arch/index.md +++ /dev/null @@ -1,3 +0,0 @@ -this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured - -please add some `*.conf` files to `/etc/copyparty.d/` diff --git a/contrib/package/makedeb-mpr/PKGBUILD b/contrib/package/makedeb-mpr/PKGBUILD index 227fc4cf..a9ef2a88 100644 --- a/contrib/package/makedeb-mpr/PKGBUILD +++ b/contrib/package/makedeb-mpr/PKGBUILD @@ -2,7 +2,7 @@ pkgname=copyparty -pkgver=1.18.9 +pkgver=1.19.15 pkgrel=1 pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" arch=("any") @@ -20,7 +20,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=("d5d33b50d6717e52427956beb1687061a6f28b467997506505151e3ae18c58e5") +sha256sums=("95f4c1e517acaadd5fdc38b47047f16a7cd51495bd94ae9182274f256db59834") build() { cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web" diff --git a/contrib/package/makedeb-mpr/copyparty.service b/contrib/package/makedeb-mpr/copyparty.service index 22dac3d6..e6f3b229 100644 --- a/contrib/package/makedeb-mpr/copyparty.service +++ b/contrib/package/makedeb-mpr/copyparty.service @@ -26,7 +26,7 @@ Environment=XDG_CONFIG_HOME=/home/cpp/.config ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' # run copyparty -ExecStart=/usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init +ExecStart=/usr/bin/python3 /usr/local/bin/copyparty -c /etc/copyparty.d/init [Install] WantedBy=multi-user.target diff --git a/contrib/package/nix/copyparty/default.nix b/contrib/package/nix/copyparty/default.nix index 28d733ab..22ae5bcd 100644 --- a/contrib/package/nix/copyparty/default.nix +++ b/contrib/package/nix/copyparty/default.nix @@ -1,10 +1,10 @@ { lib, - stdenv, - makeWrapper, + buildPythonApplication, fetchurl, util-linux, python, + setuptools, jinja2, impacket, pyopenssl, @@ -15,6 +15,10 @@ pyzmq, ffmpeg, mutagen, + pyftpdlib, + magic, + partftpy, + fusepy, # for partyfuse # use argon2id-hashed passwords in config files (sha2 is always available) withHashedPasswords ? true, @@ -40,12 +44,21 @@ # send ZeroMQ messages from event-hooks withZeroMQ ? true, + # enable FTP server + withFTP ? true, + # enable FTPS support in the FTP server withFTPS ? false, + # enable TFTP server + withTFTP ? false, + # samba/cifs server; dangerous and buggy, enable if you really need it withSMB ? false, + # enables filetype detection for nameless uploads + withMagic ? false, + # extra packages to add to the PATH extraPackages ? [ ], @@ -58,14 +71,23 @@ let pinData = lib.importJSON ./pin.json; - pyEnv = python.withPackages ( - ps: - with ps; + runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg); +in +buildPythonApplication { + pname = "copyparty"; + inherit (pinData) version; + src = fetchurl { + inherit (pinData) url hash; + }; + dependencies = [ jinja2 + fusepy ] ++ lib.optional withSMB impacket + ++ lib.optional withFTP pyftpdlib ++ lib.optional withFTPS pyopenssl + ++ lib.optional withTFTP partftpy ++ lib.optional withCertgen cfssl ++ lib.optional withThumbnails pillow ++ lib.optional withFastThumbnails pyvips @@ -73,25 +95,14 @@ let ++ lib.optional withBasicAudioMetadata mutagen ++ lib.optional withHashedPasswords argon2-cffi ++ lib.optional withZeroMQ pyzmq - ++ (extraPythonPackages ps) - ); + ++ lib.optional withMagic magic + ++ (extraPythonPackages python.pkgs); + makeWrapperArgs = [ "--prefix PATH : ${lib.makeBinPath runtimeDeps}" ]; - runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg); -in -stdenv.mkDerivation { - pname = "copyparty"; - inherit (pinData) version; - src = fetchurl { - inherit (pinData) url hash; - }; - nativeBuildInputs = [ makeWrapper ]; - dontUnpack = true; - installPhase = '' - install -Dm755 $src $out/share/copyparty-sfx.py - makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \ - --prefix PATH : ${lib.makeBinPath runtimeDeps} \ - --add-flag $out/share/copyparty-sfx.py - ''; + pyproject = true; + build-system = [ + setuptools + ]; meta = { description = "Turn almost any device into a file server"; longDescription = '' @@ -101,8 +112,7 @@ stdenv.mkDerivation { homepage = "https://github.com/9001/copyparty"; changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}"; license = lib.licenses.mit; - inherit (python.meta) platforms; mainProgram = "copyparty"; - sourceProvenance = [ lib.sourceTypes.binaryBytecode ]; + sourceProvenance = [ lib.sourceTypes.fromSource ]; }; } diff --git a/contrib/package/nix/copyparty/pin.json b/contrib/package/nix/copyparty/pin.json index 734dec31..2fc5016c 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.18.9/copyparty-sfx.py", - "version": "1.18.9", - "hash": "sha256-R1OVx4f8GERAG80ZcHAIP6HK2TlBbKJZpvnJmJbGPRY=" + "url": "https://github.com/9001/copyparty/releases/download/v1.19.15/copyparty-1.19.15.tar.gz", + "version": "1.19.15", + "hash": "sha256-lfTB5Resqt1f3Di0cEfxanzVFJW9lK6RgidPJW21mDQ=" } \ No newline at end of file diff --git a/contrib/package/nix/copyparty/update.py b/contrib/package/nix/copyparty/update.py index 363773c0..90542d2e 100755 --- a/contrib/package/nix/copyparty/update.py +++ b/contrib/package/nix/copyparty/update.py @@ -11,14 +11,14 @@ import base64 import json import hashlib import sys -import re +import tarfile from pathlib import Path OUTPUT_FILE = Path("pin.json") -TARGET_ASSET = "copyparty-sfx.py" +TARGET_ASSET = lambda version: f"copyparty-{version}.tar.gz" HASH_TYPE = "sha256" LATEST_RELEASE_URL = "https://api.github.com/repos/9001/copyparty/releases/latest" -DOWNLOAD_URL = lambda version: f"https://github.com/9001/copyparty/releases/download/v{version}/{TARGET_ASSET}" +DOWNLOAD_URL = lambda version: f"https://github.com/9001/copyparty/releases/download/v{version}/{TARGET_ASSET(version)}" def get_formatted_hash(binary): @@ -29,11 +29,13 @@ def get_formatted_hash(binary): return f"{HASH_TYPE}-{encoded_hash}" -def version_from_sfx(binary): - result = re.search(b'^VER = "(.*)"$', binary, re.MULTILINE) - if result: - return result.groups(1)[0].decode("ascii") +def version_from_tar_gz(path): + with tarfile.open(path) as tarball: + release_name = tarball.getmembers()[0].name + prefix = "copyparty-" + if release_name.startswith(prefix): + return release_name.replace(prefix, "") raise ValueError("version not found in provided file") @@ -42,7 +44,7 @@ def remote_release_pin(): response = requests.get(LATEST_RELEASE_URL).json() version = response["tag_name"].lstrip("v") - asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET][0] + asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET(version)][0] download_url = asset_info["browser_download_url"] asset = requests.get(download_url) formatted_hash = get_formatted_hash(asset.content) @@ -52,10 +54,9 @@ def remote_release_pin(): def local_release_pin(path): - asset = path.read_bytes() - version = version_from_sfx(asset) + version = version_from_tar_gz(path) download_url = DOWNLOAD_URL(version) - formatted_hash = get_formatted_hash(asset) + formatted_hash = get_formatted_hash(path.read_bytes()) result = {"url": download_url, "version": version, "hash": formatted_hash} return result diff --git a/contrib/package/nix/overlay.nix b/contrib/package/nix/overlay.nix new file mode 100644 index 00000000..ca8d5c6e --- /dev/null +++ b/contrib/package/nix/overlay.nix @@ -0,0 +1,11 @@ +final: prev: { + copyparty = final.python3.pkgs.callPackage ./copyparty { + ffmpeg = final.ffmpeg-full; + }; + + python3 = prev.python3.override { + packageOverrides = pyFinal: pyPrev: { + partftpy = pyFinal.callPackage ./partftpy { }; + }; + }; +} diff --git a/contrib/package/nix/partftpy/default.nix b/contrib/package/nix/partftpy/default.nix new file mode 100644 index 00000000..8293b44a --- /dev/null +++ b/contrib/package/nix/partftpy/default.nix @@ -0,0 +1,30 @@ +{ + lib, + buildPythonPackage, + fetchurl, + setuptools, +}: +let + pinData = lib.importJSON ./pin.json; +in + +buildPythonPackage rec { + pname = "partftpy"; + inherit (pinData) version; + pyproject = true; + + src = fetchurl { + inherit (pinData) url hash; + }; + + build-system = [ setuptools ]; + + pythonImportsCheck = [ "partftpy.TftpServer" ]; + + meta = { + description = "Pure Python TFTP library (copyparty edition)"; + homepage = "https://github.com/9001/partftpy"; + changelog = "https://github.com/9001/partftpy/releases/tag/${version}"; + license = lib.licenses.mit; + }; +} diff --git a/contrib/package/nix/partftpy/pin.json b/contrib/package/nix/partftpy/pin.json new file mode 100644 index 00000000..99882f05 --- /dev/null +++ b/contrib/package/nix/partftpy/pin.json @@ -0,0 +1,5 @@ +{ + "url": "https://github.com/9001/partftpy/releases/download/v0.4.0/partftpy-0.4.0.tar.gz", + "version": "0.4.0", + "hash": "sha256-5Q2zyuJ892PGZmb+YXg0ZPW/DK8RDL1uE0j5HPd4We0=" +} \ No newline at end of file diff --git a/contrib/package/nix/partftpy/update.py b/contrib/package/nix/partftpy/update.py new file mode 100755 index 00000000..01f046b5 --- /dev/null +++ b/contrib/package/nix/partftpy/update.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# Update the Nix package pin +# +# Usage: ./update.sh + +import base64 +import json +import hashlib +import sys +from pathlib import Path + +OUTPUT_FILE = Path("pin.json") +TARGET_ASSET = lambda version: f"partftpy-{version}.tar.gz" +HASH_TYPE = "sha256" +LATEST_RELEASE_URL = "https://api.github.com/repos/9001/partftpy/releases/latest" + + +def get_formatted_hash(binary): + hasher = hashlib.new("sha256") + hasher.update(binary) + asset_hash = hasher.digest() + encoded_hash = base64.b64encode(asset_hash).decode("ascii") + return f"{HASH_TYPE}-{encoded_hash}" + + +def remote_release_pin(): + import requests + + response = requests.get(LATEST_RELEASE_URL).json() + version = response["tag_name"].lstrip("v") + asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET(version)][0] + download_url = asset_info["browser_download_url"] + asset = requests.get(download_url) + formatted_hash = get_formatted_hash(asset.content) + + result = {"url": download_url, "version": version, "hash": formatted_hash} + return result + + +def main(): + result = remote_release_pin() + + print(result) + json_result = json.dumps(result, indent=4) + OUTPUT_FILE.write_text(json_result) + + +if __name__ == "__main__": + main() diff --git a/contrib/package/nix/partyfuse/default.nix b/contrib/package/nix/partyfuse/default.nix deleted file mode 100644 index 59faa914..00000000 --- a/contrib/package/nix/partyfuse/default.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ - stdenvNoCC, - copyparty, - python3, - makeBinaryWrapper, -}: -let - python = python3.withPackages (p: [ p.fusepy ]); -in -stdenvNoCC.mkDerivation { - pname = "partyfuse"; - inherit (copyparty) version meta; - src = ../../../..; - - nativeBuildInputs = [ makeBinaryWrapper ]; - - installPhase = '' - runHook preInstall - - install -Dm444 bin/partyfuse.py -t $out/share/copyparty - makeWrapper ${python.interpreter} $out/bin/partyfuse \ - --add-flag $out/share/copyparty/partyfuse.py - - runHook postInstall - ''; -} diff --git a/contrib/package/nix/u2c/default.nix b/contrib/package/nix/u2c/default.nix deleted file mode 100644 index dc1e4c56..00000000 --- a/contrib/package/nix/u2c/default.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ - stdenvNoCC, - copyparty, - python312, - makeBinaryWrapper, -}: -stdenvNoCC.mkDerivation { - pname = "u2c"; - inherit (copyparty) version meta; - src = ../../../..; - - nativeBuildInputs = [ makeBinaryWrapper ]; - - installPhase = '' - runHook preInstall - - install -Dm444 bin/u2c.py -t $out/share/copyparty - mkdir $out/bin - makeWrapper ${python312.interpreter} $out/bin/u2c \ - --add-flag $out/share/copyparty/u2c.py - - runHook postInstall - ''; -} diff --git a/contrib/package/rpm/copyparty.spec b/contrib/package/rpm/copyparty.spec new file mode 100644 index 00000000..6d3ebb3f --- /dev/null +++ b/contrib/package/rpm/copyparty.spec @@ -0,0 +1,62 @@ +Name: copyparty +Version: $pkgver +Release: $pkgrel +License: MIT +Group: Utilities +URL: https://github.com/9001/copyparty +Source0: copyparty-$pkgver.tar.gz +Summary: File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++ +BuildArch: noarch +BuildRequires: python3, python3-devel, pyproject-rpm-macros, python-setuptools, python-wheel, make +Requires: python3, (python3-jinja2 or python-jinja2), lsof +Recommends: ffmpeg, (golang-github-cloudflare-cfssl or cfssl), python-mutagen, python-pillow, python-pyvips +Recommends: qm-vamp-plugins, python-argon2-cffi, (python-pyopenssl or pyopenssl), python-impacket + +%description +Portable file server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps + +See release at https://github.com/9001/copyparty/releases + +%global debug_package %{nil} + +%generate_buildrequires +%pyproject_buildrequires + +%prep +%setup -q + +%build +cd "copyparty/web" +make +cd - +%pyproject_wheel + +%install +mkdir -p %{buildroot}%{_bindir} +mkdir -p %{buildroot}%{_libdir}/systemd/{system,user} +mkdir -p %{buildroot}/etc/%{name} +mkdir -p %{buildroot}/var/lib/%{name}-jail +mkdir -p %{buildroot}%{_datadir}/licenses/%{name} + +%pyproject_install +%pyproject_save_files copyparty + +install -m 0755 bin/prisonparty.sh %{buildroot}%{_bindir}/prisonpary.sh +install -m 0644 contrib/systemd/%{name}.conf %{buildroot}/etc/%{name}/%{name}.conf +install -m 0644 contrib/systemd/%{name}@.service %{buildroot}%{_libdir}/systemd/system/%{name}@.service +install -m 0644 contrib/systemd/%{name}-user.service %{buildroot}%{_libdir}/systemd/user/%{name}.service +install -m 0644 contrib/systemd/prisonparty@.service %{buildroot}%{_libdir}/systemd/system/prisonparty@.service +install -m 0644 contrib/systemd/index.md %{buildroot}/var/lib/%{name}-jail/README.md +install -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE + +%files -n copyparty -f %{pyproject_files} +%license LICENSE +%{_bindir}/copyparty +%{_bindir}/partyfuse +%{_bindir}/u2c +%{_bindir}/prisonpary.sh +/etc/%{name}/%{name}.conf +%{_libdir}/systemd/system/%{name}@.service +%{_libdir}/systemd/user/%{name}.service +%{_libdir}/systemd/system/prisonparty@.service +/var/lib/%{name}-jail/README.md diff --git a/contrib/plugins/quickmove.js b/contrib/plugins/quickmove.js index 34b3a0d3..3a0381df 100644 --- a/contrib/plugins/quickmove.js +++ b/contrib/plugins/quickmove.js @@ -101,10 +101,10 @@ function our_hotkey_handler(e) { // bail if either ALT, CTRL, or SHIFT is pressed - if (e.altKey || e.shiftKey || e.isComposing || ctrl(e)) + if (anymod(e)) return main_hotkey_handler(e); // let copyparty handle this keystroke - var key_name = (e.code || e.key) + '', + var keycode = (e.key || e.code) + '', ae = document.activeElement, aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : ''; @@ -114,7 +114,7 @@ if (aet && !/^(a|button|tr|td|div|pre)$/.test(aet)) return main_hotkey_handler(e); // let copyparty handle this keystroke - if (key_name == 'KeyW') { + if (keycode == 'w' || keycode == 'KeyW') { // okay, this one's for us... do the thing action_to_perform(); return ev(e); diff --git a/contrib/setup-ashell.sh b/contrib/setup-ashell.sh new file mode 100644 index 00000000..04395735 --- /dev/null +++ b/contrib/setup-ashell.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# +# this script will install copyparty onto an iOS device (iPhone/iPad) +# +# step 1: install a-Shell: +# https://apps.apple.com/us/app/a-shell/id1473805438 +# +# step 2: copypaste the following command into a-Shell: +# curl -L https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh +# +# step 3: launch copyparty with this command: cpp +# +# if you ever want to upgrade copyparty, just repeat step 2 + + + +cd "$HOME/Documents" +curl -Locopyparty https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py + + + +# create the config file? (cannot use heredoc because body too large) +[ -e cpc ] || { +echo '[global]' >cpc +echo ' p: 80, 443, 3923 # enable http and https on these ports' >>cpc +echo ' e2dsa # enable file indexing and filesystem scanning' >>cpc +echo ' e2ts # and enable multimedia indexing' >>cpc +echo ' ver # show copyparty version in the controlpanel' >>cpc +echo ' qrz: 2 # enable qr-code and make it big' >>cpc +echo ' qrp: 1 # reduce qr-code padding' >>cpc +echo ' qr-fg: -1 # optimize for basic/simple terminals' >>cpc +echo ' qr-wait: 0.3 # less chance of getting scrolled away' >>cpc +echo '' >>cpc +echo ' # enable these by uncommenting them:' >>cpc +echo ' # ftp: 21 # enable ftp server on port 21' >>cpc +echo ' # tftp: 69 # enable tftp server on port 69' >>cpc +echo '' >>cpc +echo '[/]' >>cpc +echo ' ~/Documents' >>cpc +echo ' accs:' >>cpc +echo ' A: *' >>cpc +} + + + +# create the launcher? +[ -e cpp ] || { +echo '#!/bin/sh' >cpp +echo '' >>cpp +echo '# change the font so the qr-code draws correctly:' >>cpp +echo 'config -n "Menlo" # name' >>cpp +echo 'config -s 8 # size' >>cpp +echo '' >>cpp +echo '# launch copyparty' >>cpp +echo 'exec copyparty -c cpc "$@"' >>cpp +} + + + +chmod 755 copyparty cpp +echo +echo ================================= +echo +echo 'okay, all done!' +echo +echo 'you can edit your config' +echo 'with this command: vim cpc' +echo +echo 'you can run copyparty' +echo 'with this command: cpp' +echo diff --git a/contrib/systemd/copyparty-user.service b/contrib/systemd/copyparty-user.service new file mode 100644 index 00000000..c6f61000 --- /dev/null +++ b/contrib/systemd/copyparty-user.service @@ -0,0 +1,26 @@ +# this will start `/usr/bin/copyparty` +# and read config from `$HOME/.config/copyparty.conf` +# +# unless you add -q to disable logging, you may want to remove the +# following line to allow buffering (slightly better performance): +# Environment=PYTHONUNBUFFERED=x + +[Unit] +Description=copyparty file server + +[Service] +Type=notify +SyslogIdentifier=copyparty +WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf +ExecReload=/bin/kill -s USR1 $MAINPID + +# ensure there is a config +ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi' + +# run copyparty +ExecStart=/usr/bin/python3 /usr/bin/copyparty + +[Install] +WantedBy=default.target diff --git a/contrib/systemd/copyparty.conf b/contrib/systemd/copyparty.conf index 79560d0d..5d9bdbe3 100644 --- a/contrib/systemd/copyparty.conf +++ b/contrib/systemd/copyparty.conf @@ -1,42 +1,13 @@ -# not actually YAML but lets pretend: -# -*- mode: yaml -*- -# vim: ft=yaml: - - -# put this file in /etc/ - - [global] - e2dsa # enable file indexing and filesystem scanning - e2ts # and enable multimedia indexing - ansi # and colors in log messages - - # disable logging to stdout/journalctl and log to a file instead; - # $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd) - # and copyparty replaces %Y-%m%d with Year-MonthDay, so the - # full path will be something like /var/log/copyparty/2023-1130.txt - # (note: enable compression by adding .xz at the end) - q, lo: $LOGS_DIRECTORY/%Y-%m%d.log - - # p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE) - # i: 127.0.0.1 # only allow connections from localhost (reverse-proxies) - # ftp: 3921 # enable ftp server on port 3921 - # p: 3939 # listen on another port - # df: 16 # stop accepting uploads if less than 16 GB free disk space - # ver # show copyparty version in the controlpanel - # grid # show thumbnails/grid-view by default - # theme: 2 # monokai - # name: datasaver # change the server-name that's displayed in the browser - # stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) - # no-robots, force-js # make it harder for search engines to read your server - + i: 127.0.0.1 [accounts] - ed: wark # username: password + user: password - -[/] # create a volume at "/" (the webroot), which will - /mnt # share the contents of the "/mnt" folder +[/] + /var/lib/copyparty-jail accs: - rw: * # everyone gets read-write access, but - rwmda: ed # the user "ed" gets read-write-move-delete-admin + r: * + rwdma: user + flags: + grid \ No newline at end of file diff --git a/contrib/systemd/copyparty.example.conf b/contrib/systemd/copyparty.example.conf new file mode 100644 index 00000000..79560d0d --- /dev/null +++ b/contrib/systemd/copyparty.example.conf @@ -0,0 +1,42 @@ +# not actually YAML but lets pretend: +# -*- mode: yaml -*- +# vim: ft=yaml: + + +# put this file in /etc/ + + +[global] + e2dsa # enable file indexing and filesystem scanning + e2ts # and enable multimedia indexing + ansi # and colors in log messages + + # disable logging to stdout/journalctl and log to a file instead; + # $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd) + # and copyparty replaces %Y-%m%d with Year-MonthDay, so the + # full path will be something like /var/log/copyparty/2023-1130.txt + # (note: enable compression by adding .xz at the end) + q, lo: $LOGS_DIRECTORY/%Y-%m%d.log + + # p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE) + # i: 127.0.0.1 # only allow connections from localhost (reverse-proxies) + # ftp: 3921 # enable ftp server on port 3921 + # p: 3939 # listen on another port + # df: 16 # stop accepting uploads if less than 16 GB free disk space + # ver # show copyparty version in the controlpanel + # grid # show thumbnails/grid-view by default + # theme: 2 # monokai + # name: datasaver # change the server-name that's displayed in the browser + # stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) + # no-robots, force-js # make it harder for search engines to read your server + + +[accounts] + ed: wark # username: password + + +[/] # create a volume at "/" (the webroot), which will + /mnt # share the contents of the "/mnt" folder + accs: + rw: * # everyone gets read-write access, but + rwmda: ed # the user "ed" gets read-write-move-delete-admin diff --git a/contrib/systemd/copyparty@.service b/contrib/systemd/copyparty@.service new file mode 100644 index 00000000..cd891eab --- /dev/null +++ b/contrib/systemd/copyparty@.service @@ -0,0 +1,30 @@ +# this will start `/usr/bin/copyparty` +# and read config from `/etc/copyparty/copyparty.conf` +# +# the %i refers to whatever you put after the copyparty@ +# so with copyparty@foo.service, %i == foo +# +# unless you add -q to disable logging, you may want to remove the +# following line to allow buffering (slightly better performance): +# Environment=PYTHONUNBUFFERED=x + +[Unit] +Description=copyparty file server + +[Service] +Type=notify +SyslogIdentifier=copyparty +WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf +ExecReload=/bin/kill -s USR1 $MAINPID + +# user to run as + where the TLS certificate is (if any) +User=%i +Environment=XDG_CONFIG_HOME=/home/%i/.config + +# run copyparty +ExecStart=/usr/bin/python3 /usr/bin/copyparty + +[Install] +WantedBy=multi-user.target diff --git a/contrib/systemd/index.md b/contrib/systemd/index.md new file mode 100644 index 00000000..cfd1b715 --- /dev/null +++ b/contrib/systemd/index.md @@ -0,0 +1,10 @@ +this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured + +please edit `/etc/copyparty/copyparty.conf` (if running as a system service) +or `$HOME/.config/copyparty/copyparty.conf` if running as a user service + +a basic configuration example is available at https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf +a configuration example that explains most flags is available at https://github.com/9001/copyparty/blob/hovudstraum/docs/chungus.conf + +the full list of configuration options can be seen at https://ocv.me/copyparty/helptext.html +or by running `copyparty --help` diff --git a/contrib/package/arch/prisonparty.service b/contrib/systemd/prisonparty@.service similarity index 51% rename from contrib/package/arch/prisonparty.service rename to contrib/systemd/prisonparty@.service index cd35ba99..d30010ae 100644 --- a/contrib/package/arch/prisonparty.service +++ b/contrib/systemd/prisonparty@.service @@ -1,11 +1,13 @@ -# this will start `/usr/bin/copyparty-sfx.py` +# this will start `/usr/bin/copyparty` # in a chroot, preventing accidental access elsewhere, -# and read copyparty config from `/etc/copyparty.d/*.conf` +# and read copyparty config from `/etc/copyparty/copyparty.conf` # # expose additional filesystem locations to copyparty -# by listing them between the last `cpp` and `--` +# by listing them between the last `%i` and `--` # -# `cpp cpp` = user/group to run copyparty as; can be IDs (1000 1000) +# `%i %i` = user/group to run copyparty as; can be IDs (1000 1000) +# the %i refers to whatever you put after the prisonparty@ +# so with prisonparty@foo.service, %i == foo # # unless you add -q to disable logging, you may want to remove the # following line to allow buffering (slightly better performance): @@ -15,19 +17,22 @@ Description=copyparty file server [Service] +Type=notify SyslogIdentifier=prisonparty -Environment=PYTHONUNBUFFERED=x WorkingDirectory=/var/lib/copyparty-jail +Environment=PYTHONUNBUFFERED=x +Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf ExecReload=/bin/kill -s USR1 $MAINPID -# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running -ExecStartPre=+/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' +# user to run as + where the TLS certificate is (if any) +User=%i +Environment=XDG_CONFIG_HOME=/home/%i/.config # run copyparty -ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail cpp cpp \ - /etc/copyparty.d \ +ExecStart=/bin/bash /usr/bin/prisonparty /var/lib/copyparty-jail %i %i \ + /etc/copyparty \ -- \ - /usr/bin/python3 /usr/bin/copyparty -c /etc/copyparty.d/init + /usr/bin/python3 /usr/bin/copyparty [Install] WantedBy=multi-user.target diff --git a/copyparty/__init__.py b/copyparty/__init__.py index 86b7fe1e..eba3651e 100644 --- a/copyparty/__init__.py +++ b/copyparty/__init__.py @@ -63,10 +63,6 @@ web/browser.js web/browser2.html web/cf.html web/copyparty.gif -web/dd/2.png -web/dd/3.png -web/dd/4.png -web/dd/5.png web/deps/busy.mp3 web/deps/easymde.css web/deps/easymde.js @@ -92,6 +88,7 @@ web/mde.html web/mde.js web/msg.css web/msg.html +web/opds.xml web/rups.css web/rups.html web/rups.js @@ -115,7 +112,9 @@ class EnvParams(object): def __init__(self) -> None: self.t0 = time.time() self.mod = "" + self.mod_ = "" self.cfg = "" + self.scfg = True E = EnvParams() diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 71a06033..16c48aa4 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -36,6 +36,7 @@ from .__init__ import ( ) from .__version__ import CODENAME, S_BUILD_DT, S_VERSION from .authsrv import expand_config_file, split_cfg_ln, upgrade_cfg_fmt +from .bos import bos from .cfg import flagcats, onedash from .svchub import SvcHub from .util import ( @@ -93,6 +94,10 @@ u = unicode printed: list[str] = [] zsid = uuid.uuid4().urn[4:] +CFG_DEF = [os.environ.get("PRTY_CONFIG", "")] +if not CFG_DEF[0]: + CFG_DEF.pop() + class RiceFormatter(argparse.HelpFormatter): def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -182,7 +187,7 @@ def init_E(EE: EnvParams) -> None: E = EE # pylint: disable=redefined-outer-name - def get_unixdir() -> str: + def get_unixdir() -> tuple[str, bool]: paths: list[tuple[Callable[..., Any], str]] = [ (os.environ.get, "XDG_CONFIG_HOME"), (os.path.expanduser, "~/.config"), @@ -192,41 +197,57 @@ def init_E(EE: EnvParams) -> None: (unicode, "/tmp"), ] errs = [] - for chk in [os.listdir, os.mkdir]: - for npath, (pf, pa) in enumerate(paths): - p = "" + for npath, (pf, pa) in enumerate(paths): + priv = npath < 2 # private/trusted location + ram = npath > 1 # "nonvolatile"; not semantically same as `not priv` + p = "" + try: + p = pf(pa) + if not p or p.startswith("~"): + continue + + p = os.path.normpath(p) + mkdir = not os.path.isdir(p) + if mkdir: + os.mkdir(p, 0o700) + + p = os.path.join(p, "copyparty") + if not priv and os.path.isdir(p): + uid = os.geteuid() + if os.stat(p).st_uid != uid: + p += ".%s" % (uid,) + if os.path.isdir(p) and os.stat(p).st_uid != uid: + raise Exception("filesystem has broken unix permissions") try: - p = pf(pa) - # print(chk.__name__, p, pa) - if not p or p.startswith("~"): - continue + os.listdir(p) + except: + os.mkdir(p, 0o700) - p = os.path.normpath(p) - chk(p) # type: ignore - p = os.path.join(p, "copyparty") - if not os.path.isdir(p): - os.mkdir(p) + if ram: + t = "Using %s/copyparty [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/" + errs.append(t % (pa, p)) + elif mkdir: + t = "Using %s/copyparty [%s] for config%s (Warning: %s did not exist and was created just now)" + errs.append(t % (pa, p, " instead" if npath else "", pa)) + elif errs: + errs.append("Using %s/copyparty [%s] instead" % (pa, p)) - if npath > 1: - t = "Using [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/" - errs.append(t % (p,)) - elif errs: - errs.append("Using [%s] instead" % (p,)) + if errs: + warn(". ".join(errs)) - if errs: - warn(". ".join(errs)) + return p, priv + except Exception as ex: + if p: + t = "Unable to store config in %s [%s] due to %r" + errs.append(t % (pa, p, ex)) - return p # type: ignore - except Exception as ex: - if p and npath < 2: - t = "Unable to store config in [%s] due to %r" - errs.append(t % (p, ex)) - - raise Exception("could not find a writable path for config") + t = "could not find a writable path for runtime state:\n> %s" + raise Exception(t % ("\n> ".join(errs))) E.mod = os.path.dirname(os.path.realpath(__file__)) if E.mod.endswith("__init__"): E.mod = os.path.dirname(E.mod) + E.mod_ = os.path.join(E.mod, "") try: p = os.environ.get("XDG_CONFIG_HOME") @@ -237,7 +258,7 @@ def init_E(EE: EnvParams) -> None: p = os.path.abspath(os.path.realpath(p)) p = os.path.join(p, "copyparty") if not os.path.isdir(p): - os.mkdir(p) + os.mkdir(p, 0o700) os.listdir(p) except: p = "" @@ -250,11 +271,11 @@ def init_E(EE: EnvParams) -> None: elif sys.platform == "darwin": E.cfg = os.path.expanduser("~/Library/Preferences/copyparty") else: - E.cfg = get_unixdir() + E.cfg, E.scfg = get_unixdir() E.cfg = E.cfg.replace("\\", "/") try: - os.makedirs(E.cfg) + bos.makedirs(E.cfg, bos.MKD_700) except: if not os.path.isdir(E.cfg): raise @@ -432,6 +453,29 @@ def args_from_cfg(cfg_path: str) -> list[str]: return ret +def expand_cfg(argv) -> list[str]: + if CFG_DEF: + supp = args_from_cfg(CFG_DEF[0]) + argv = argv[:1] + supp + argv[1:] + + n = 0 + while n < len(argv): + v1 = argv[n] + v1v = v1[2:].lstrip("=") + try: + v2 = argv[n + 1] + except: + v2 = "" + + n += 1 + if v1 == "-c" and v2 and os.path.isfile(v2): + n += 1 + argv = argv[:n] + args_from_cfg(v2) + argv[n:] + elif v1.startswith("-c") and v1v and os.path.isfile(v1v): + argv = argv[:n] + args_from_cfg(v1v) + argv[n:] + return argv + + def sighandler(sig: Optional[int] = None, frame: Optional[FrameType] = None) -> None: msg = [""] * 5 for th in threading.enumerate(): @@ -532,7 +576,7 @@ def get_sects(): dedent( """ \033[33m-i\033[0m takes a comma-separated list of interfaces to listen on; - IP-addresses and/or unix-sockets (Unix Domain Sockets) + IP-addresses, unix-sockets, and/or open file descriptors the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses @@ -558,7 +602,9 @@ def get_sects(): \033[32m-i unix:\033[33m/dev/shm/party.sock\033[0m keeps umask-defined permission (usually \033[33m0600\033[0m) and the same user/group as copyparty - \033[33m-p\033[0m (tcp ports) is ignored for unix sockets + \033[32m-i fd:\033[33m3\033[0m uses the socket passed to copyparty on file descriptor 3 + + \033[33m-p\033[0m (tcp ports) is ignored for unix-sockets and FDs """ ), ], @@ -574,7 +620,7 @@ def get_sects(): --grp takes groupname:username1,username2,... and groupnames can be used instead of usernames in -v - by prefixing the groupname with % + by prefixing the groupname with @ list of permissions: "r" (read): list folder contents, download files @@ -603,8 +649,77 @@ def get_sects(): if no accounts or volumes are configured, current folder will be read/write for everyone + the group @acct will always have every user with an account + (the name of that group can be changed with --grp-all) + consider the config file for more flexible account/volume management, including dynamic reload at runtime (and being more readable w) + + see \033[32m--help-auth\033[0m for ways to provide the password in requests; + see \033[32m--help-idp\033[0m for replacing it with SSO and auth-middlewares + """ + ), + ], + [ + "auth", + "how to login from a client", + dedent( + """ + different ways to provide the password so you become authenticated: + + login with the ui: + go to \033[36mhttp://127.0.0.1:3923/?h\033[0m and login there + + send the password in the '\033[36mPW\033[0m' http-header: + \033[36mPW: \033[35mhunter2\033[0m + or if you have \033[33m--accounts\033[0m enabled, + \033[36mPW: \033[35med:hunter2\033[0m + + send the password in the URL itself: + \033[36mhttp://127.0.0.1:3923/\033[35m?pw=hunter2\033[0m + or if you have \033[33m--accounts\033[0m enabled, + \033[36mhttp://127.0.0.1:3923/\033[35m?pw=ed:hunter2\033[0m + + use basic-authentication: + \033[36mhttp://\033[35med:hunter2\033[36m@127.0.0.1:3923/\033[0m + which should be the same as this header: + \033[36mAuthorization: Basic \033[35mZWQ6aHVudGVyMg==\033[0m + """ + ), + ], + [ + "auth-ord", + "authentication precedence", + dedent( + """ + \033[33m--auth-ord\033[0m is a comma-separated list of auth options + (one or more of the [\033[35moptions\033[0m] below); first one wins + + [\033[35mpw\033[0m] is conventional login, for example the "\033[36mPW\033[0m" header, + or the \033[36m?pw=\033[0m[...] URL-suffix, or a valid session cookie + (see \033[33m--help-auth\033[0m) + + [\033[35midp\033[0m] is a username provided in the http-request-header + defined by \033[33m--idp-h-usr\033[0m and/or \033[33m--idp-hm-usr\033[0m, which is + provided by an authentication middleware such as + authentik, authelia, tailscale, ... (see \033[33m--help-idp\033[0m) + + [\033[35midp-h\033[0m] is specifically an \033[33m--idp-h-usr\033[0m header, + [\033[35midp-hm\033[0m] is specifically an \033[33m--idp-hm-usr\033[0m header; + [\033[35midp\033[0m] is the same as [\033[35midp-hm,idp-h\033[0m] + + [\033[35mipu\033[0m] is a mapping from an IP-address to a username, + auto-authing that client-IP to that account + (see the description of \033[36m--ipu\033[0m in \033[33m--help\033[0m) + + NOTE: even if an option (\033[35mpw\033[0m/\033[35mipu\033[0m/...) is not in the list, + it may still be enabled and can still take effect if + none of the other alternatives identify the user + + NOTE: if [\033[35mipu\033[0m] is in the list, it must be FIRST or LAST + + NOTE: if [\033[35mpw\033[0m] is not in the list, the logout-button + will be hidden when any idp feature is enabled """ ), ], @@ -713,7 +828,7 @@ def get_sects(): \033[36mc0\033[35m show all process output (default) \033[36mc1\033[35m show only stderr \033[36mc2\033[35m show only stdout - \033[36mc3\033[35m mute all process otput + \033[36mc3\033[35m mute all process output \033[0m examples: @@ -756,6 +871,41 @@ def get_sects(): the upload speed can easily drop to 10% for small files)""" ), ], + [ + "idp", + "replacing the login system with fancy middleware", + dedent( + """ + if you already have a centralized service which handles + user-authentication for other services already, you can + integrate copyparty with that for automatic login + + if the middleware is providing the username in an http-header + named '\033[35mtheUsername\033[0m' then do this: \033[36m--idp-h-usr theUsername\033[0m + + if the middleware is providing a list of groups in the header + named '\033[35mtheGroups\033[0m' then do this: \033[36m--idp-h-grp theGroup\033[0m + + if the list of groups is separated by '\033[35m%\033[0m' then \033[36m--idp-gsep %\033[0m + + if the middleware is providing a header named '\033[35mAccount\033[0m' + and the value is '\033[35malice@forest.net\033[0m' but the username is + actually '\033[35mmarisa\033[0m' then do this for each user: + \033[36m--idp-hm-usr ^Account^alice@forest.net^marisa\033[0m + (the separator '\033[35m^\033[0m' can be any character) + + make ABSOLUTELY SURE that the header can only be set by your + middleware and not by clients! and, as an extra precaution, + send a header named '\033[36mfinalmasterspark\033[0m' (a secret keyword) + and then \033[36m--idp-h-key finalmasterspark\033[0m to require that + + the login/logout links/buttons can be replaced with links + going to your IdP's UI; \033[36m--idp-login /login/?redir={dst}\033[0m + will expand \033[36m{dst}\033[0m to the URL of the current page, so + the IdP can redirect the user back to where they were + """ + ), + ], [ "urlform", "how to handle url-form POSTs", @@ -806,7 +956,7 @@ def get_sects(): \033[36m{{vf.thsize}} \033[35mthumbnail size \033[36m{{srv.itime}} \033[35mserver time in seconds \033[36m{{srv.htime}} \033[35mserver time as YY-mm-dd, HH:MM:SS (UTC) - \033[36m{{hdr.cf_ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank) + \033[36m{{hdr.cf-ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank) \033[0m so the following types of placeholders can be added to the lists: * any client header can be accessed through {{hdr.*}} @@ -912,6 +1062,9 @@ def get_sects(): copyparty will also hash and print any passwords that are non-hashed (password which do not start with '+') and then terminate afterwards + if you have enabled --usernames then the password + must be provided as username:password for hashing + \033[36m--ah-alg\033[0m specifies the hashing algorithm and a list of optional comma-separated arguments: @@ -989,18 +1142,20 @@ def build_flags_desc(): def add_general(ap, nc, srvname): - ap2 = ap.add_argument_group('general options') - ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file") + ap2 = ap.add_argument_group("general options") + ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="\033[34mREPEATABLE:\033[0m add config file") ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients") ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all") - ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]") - ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts") - ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]") + ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]") + ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m], see --help-accounts") + ap2.add_argument("--grp", metavar="G:N,N", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add group, \033[33mNAME\033[0m:\033[33mUSER1\033[0m,\033[33mUSER2\033[0m,\033[33m...\033[0m; example [\033[32madmins:ed,foo,bar\033[0m]") + ap2.add_argument("--usernames", action="store_true", help="require username and password for login; default is just password") + ap2.add_argument("--chdir", metavar="PATH", type=u, help="change working-directory to \033[33mPATH\033[0m before mapping volumes") ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots)") ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m") ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]") ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)") - ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]") + ap2.add_argument("--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)") ap2.add_argument("--license", action="store_true", help="show licenses and exit") @@ -1008,20 +1163,28 @@ def add_general(ap, nc, srvname): def add_qr(ap, tty): - ap2 = ap.add_argument_group('qr options') - ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup") - ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup") + ap2 = ap.add_argument_group("qr options") + ap2.add_argument("--qr", action="store_true", help="show QR-code on startup") + ap2.add_argument("--qrs", action="store_true", help="change the QR-code URL to https://") ap2.add_argument("--qrl", metavar="PATH", type=u, default="", help="location to include in the url, for example [\033[32mpriv/?pw=hunter2\033[0m]") ap2.add_argument("--qri", metavar="PREFIX", type=u, default="", help="select IP which starts with \033[33mPREFIX\033[0m; [\033[32m.\033[0m] to force default IP when mDNS URL would have been used instead") - ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] if the qr-code is unreadable") + ap2.add_argument("--qr-fg", metavar="COLOR", type=int, default=0 if tty else 16, help="foreground; try [\033[32m0\033[0m] or [\033[32m-1\033[0m] if the qr-code is unreadable") ap2.add_argument("--qr-bg", metavar="COLOR", type=int, default=229, help="background (white=255)") ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)") ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)") + ap2.add_argument("--qr-pin", metavar="N", type=int, default=0, help="sticky/pin the qr-code to always stay on-screen; [\033[32m0\033[0m]=disabled, [\033[32m1\033[0m]=with-url, [\033[32m2\033[0m]=just-qr") + ap2.add_argument("--qr-wait", metavar="SEC", type=float, default=0, help="wait \033[33mSEC\033[0m before printing the qr-code to the log") + ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)") + ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m") + ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent") + ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m") + ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m") def add_fs(ap): ap2 = ap.add_argument_group("filesystem options") rm_re_def = "15/0.1" if ANYWIN else "0/0" + ap2.add_argument("--casechk", metavar="N", type=u, default="auto", help="detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [\033[32my\033[0m] = detect and prevent, [\033[32mn\033[0m] = ignore and allow, [\033[32mauto\033[0m] = \033[32my\033[0m if CI fs detected. NOTE: \033[32my\033[0m is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk)") ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)") ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)") ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)") @@ -1030,22 +1193,24 @@ def add_fs(ap): def add_share(ap): db_path = os.path.join(E.cfg, "shares.db") - ap2 = ap.add_argument_group('share-url options') + ap2 = ap.add_argument_group("share-url options") ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]") ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in") + 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-v", action="store_true", help="debug") def add_upload(ap): - ap2 = ap.add_argument_group('upload options') + 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("--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)") ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h") + ap2.add_argument("--unp-who", metavar="NUM", type=int, default=1, help="clients can undo recent uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=unp_who)") ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)") ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)") ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600") @@ -1055,6 +1220,7 @@ def add_upload(ap): ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)") ap2.add_argument("--uid", metavar="N", type=int, default=-1, help="unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid)") ap2.add_argument("--gid", metavar="N", type=int, default=-1, help="unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid)") + ap2.add_argument("--wram", action="store_true", help="allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram)") ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)") ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)") ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)") @@ -1065,7 +1231,9 @@ def add_upload(ap): ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually") ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash") ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440.0, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)") + ap2.add_argument("--rm-partial", action="store_true", help="delete the .PARTIAL file when an unfinished upload expires after \033[33m--snap-drop\033[0m (volflag=rm_partial)") ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)") + ap2.add_argument("--rotf-tz", metavar="TXT", type=u, default="UTC", help="default timezone for the rotf upload rule; examples: [\033[32mEurope/Oslo\033[0m], [\033[32mAmerica/Toronto\033[0m], [\033[32mAntarctica/South_Pole\033[0m] (volflag=rotf_tz)") ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)") ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)") ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)") @@ -1081,15 +1249,16 @@ def add_upload(ap): def add_network(ap): - ap2 = ap.add_argument_group('network options') - ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6") + ap2 = ap.add_argument_group("network options") + ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (comma-separated list; see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6") ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets") ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)") - ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy") + ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=9999999, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m-1\033[0m]=closest-proxy, [\033[32m-2\033[0m]=second-hop, [\033[32m-3\033[0m]=third-hop") ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from") - ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="comma-separated list of trusted reverse-proxy CIDRs; only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") - ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") + ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]") + ap2.add_argument("--http-no-tcp", action="store_true", help="do not listen on TCP/IP for http/https; only listen on unix-domain-sockets") if ANYWIN: ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") elif not MACOS: @@ -1106,7 +1275,7 @@ def add_network(ap): def add_tls(ap, cert_path): - ap2 = ap.add_argument_group('SSL/TLS options') + ap2 = ap.add_argument_group("SSL/TLS options") ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext") ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls") ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain") @@ -1118,7 +1287,7 @@ def add_tls(ap, cert_path): def add_cert(ap, cert_path): cert_dir = os.path.dirname(cert_path) - ap2 = ap.add_argument_group('TLS certificate generator options') + ap2 = ap.add_argument_group("TLS certificate generator options") ap2.add_argument("--no-crt", action="store_true", help="disable automatic certificate creation") ap2.add_argument("--crt-ns", metavar="N,N", type=u, default="", help="comma-separated list of FQDNs (domains) to add into the certificate") ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each \033[33m--crt-ns\033[0m") @@ -1138,27 +1307,41 @@ def add_cert(ap, cert_path): def add_auth(ap): idp_db = os.path.join(E.cfg, "idp.db") ses_db = os.path.join(E.cfg, "sessions.db") - ap2 = ap.add_argument_group('IdP / identity provider / user authentication options') - ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy") + ap2 = ap.add_argument_group("IdP / identity provider / user authentication options") + ap2.add_argument("--idp-h-usr", metavar="HN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy") + ap2.add_argument("--idp-hm-usr", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mT\033[0m is provided, and its value exists in a mapping defined by this option; see --help-idp") ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control") ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present") ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m") ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP") ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)") + ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)") + ap2.add_argument("--idp-login", metavar="L", type=u, default="", help="replace all login-buttons with a link to URL \033[33mL\033[0m (unless \033[32mpw\033[0m is in \033[33m--auth-ord\033[0m then both will be shown); [\033[32m{dst}\033[0m] expands to url of current page") + ap2.add_argument("--idp-login-t", metavar="T", type=u, default="Login with SSO", help="the label/text for the idp-login button") + ap2.add_argument("--idp-logout", metavar="L", type=u, default="", help="replace all logout-buttons with a link to URL \033[33mL\033[0m") + ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see --help-auth-ord") ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app") ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins") ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)") ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies") - ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]") + ap2.add_argument("--grp-all", metavar="NAME", type=u, default="acct", help="the name of the auto-generated group which contains every username which is known") + ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]") + ap2.add_argument("--ipr", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m username \033[33mUSR\033[0m can only connect from an IP matching one or more \033[33mCIDR\033[0m (comma-sep.); example: [\033[32m192.168.123.0/24,172.16.0.0/16=dave]") + ap2.add_argument("--have-idp-hdrs", type=u, default="", help=argparse.SUPPRESS) + ap2.add_argument("--have-ipu-or-ipr", type=u, default="", help=argparse.SUPPRESS) + ap2.add_argument("--ao-idp-before-pw", type=u, default="", help=argparse.SUPPRESS) + ap2.add_argument("--ao-h-before-hm", type=u, default="", help=argparse.SUPPRESS) + ap2.add_argument("--ao-ipu-wins", type=u, default="", help=argparse.SUPPRESS) + ap2.add_argument("--ao-have-pw", type=u, default="", help=argparse.SUPPRESS) def add_chpw(ap): db_path = os.path.join(E.cfg, "chpw.json") - ap2 = ap.add_argument_group('user-changeable passwords options') + ap2 = ap.add_argument_group("user-changeable passwords options") ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords") - ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="do not allow password-changes for this comma-separated list of usernames") + ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="\033[34mREPEATABLE:\033[0m do not allow password-changes for this comma-separated list of usernames") ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length") ap2.add_argument("--chpw-v", metavar="LVL", type=int, default=2, help="verbosity of summary on config load [\033[32m0\033[0m] = nothing at all, [\033[32m1\033[0m] = number of users, [\033[32m2\033[0m] = list users with default-pw, [\033[32m3\033[0m] = list all users") @@ -1183,6 +1366,8 @@ def add_zc_mdns(ap): ap2.add_argument("--zm6", action="store_true", help="IPv6 only") ap2.add_argument("--zmv", action="store_true", help="verbose mdns") ap2.add_argument("--zmvv", action="store_true", help="verboser mdns") + ap2.add_argument("--zm-http", metavar="PORT", type=int, default=-1, help="port to announce for http/webdav; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649") + ap2.add_argument("--zm-https", metavar="PORT", type=int, default=-1, help="port to announce for https/webdavs; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649") ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)") ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)") ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)") @@ -1190,6 +1375,7 @@ def add_zc_mdns(ap): ap2.add_argument("--zm-lh", metavar="PATH", type=u, default="", help="link a specific folder for http shares") ap2.add_argument("--zm-lf", metavar="PATH", type=u, default="", help="link a specific folder for ftp shares") ap2.add_argument("--zm-ls", metavar="PATH", type=u, default="", help="link a specific folder for smb shares") + ap2.add_argument("--zm-fqdn", metavar="FQDN", type=u, default="--name.local", help="the domain to announce; NOTE: using anything other than .local is nonstandard and could cause problems") ap2.add_argument("--zm-mnic", action="store_true", help="merge NICs which share subnets; assume that same subnet means same network") ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working, and clients cannot be in subnets that the server is not") ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty") @@ -1207,12 +1393,12 @@ def add_zc_ssdp(ap): def add_ftp(ap): - ap2 = ap.add_argument_group('FTP options (TCP only)') + ap2 = ap.add_argument_group("FTP options (TCP only)") ap2.add_argument("--ftp", metavar="PORT", type=int, default=0, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921") ap2.add_argument("--ftps", metavar="PORT", type=int, default=0, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990") ap2.add_argument("--ftpv", action="store_true", help="verbose") ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4") - ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite") ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)") ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections") @@ -1220,7 +1406,7 @@ def add_ftp(ap): def add_webdav(ap): - ap2 = ap.add_argument_group('WebDAV options') + ap2 = ap.add_argument_group("WebDAV options") ap2.add_argument("--daw", action="store_true", help="enable full write support, even if client may not be webdav. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)") ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this") ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)") @@ -1230,7 +1416,7 @@ def add_webdav(ap): def add_tftp(ap): - ap2 = ap.add_argument_group('TFTP options (UDP only)') + ap2 = ap.add_argument_group("TFTP options (UDP only)") ap2.add_argument("--tftp", metavar="PORT", type=int, default=0, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969") ap2.add_argument("--tftp4", action="store_true", help="only listen on IPv4") ap2.add_argument("--tftpv", action="store_true", help="verbose") @@ -1238,12 +1424,12 @@ def add_tftp(ap): ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations") ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...") ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing") - ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") + ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--tftp-pr", metavar="P-P", type=u, default="", help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000") def add_smb(ap): - ap2 = ap.add_argument_group('SMB/CIFS options') + ap2 = ap.add_argument_group("SMB/CIFS options") ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless \033[33m--smb-port\033[0m is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is DANGEROUS and buggy! Never expose to the internet!") ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)") ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)") @@ -1255,32 +1441,36 @@ def add_smb(ap): ap2.add_argument("--smbvv", action="store_true", help="verboser") ap2.add_argument("--smbvvv", action="store_true", help="verbosest") +def add_opds(ap): + ap2 = ap.add_argument_group("OPDS options") + ap2.add_argument("--opds", action="store_true", help="enable opds -- allows e-book readers to browse and download files (volflag=opds)") + ap2.add_argument("--opds-exts", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts)") def add_handlers(ap): - ap2 = ap.add_argument_group('handlers (see --help-handlers)') - ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="handle 404s by executing \033[33mPY\033[0m file") - ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing \033[33mPY\033[0m file") + ap2 = ap.add_argument_group("handlers (see --help-handlers)") + ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 404s by executing \033[33mPY\033[0m file") + ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 403s by executing \033[33mPY\033[0m file") ap2.add_argument("--hot-handlers", action="store_true", help="recompile handlers on each request -- expensive but convenient when hacking on stuff") def add_hooks(ap): - ap2 = ap.add_argument_group('event hooks (see --help-hooks)') - ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file upload starts") - ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file upload finishes") - ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after all uploads finish and volume is idle") - ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file copy") - ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file copy") - ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file move/rename") - ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file move/rename") - ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m before a file delete") - ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file delete") - ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m on message") - ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)") + ap2 = ap.add_argument_group("event hooks (see --help-hooks)") + ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file upload starts") + ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file upload finishes") + ap2.add_argument("--xiu", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after all uploads finish and volume is idle") + ap2.add_argument("--xbc", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file copy") + ap2.add_argument("--xac", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file copy") + ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file move/rename") + ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file move/rename") + ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m before a file delete") + ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m after a file delete") + ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m on message") + ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="\033[34mREPEATABLE:\033[0m execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)") ap2.add_argument("--hook-v", action="store_true", help="verbose hooks") def add_stats(ap): - ap2 = ap.add_argument_group('grafana/prometheus metrics endpoint') + ap2 = ap.add_argument_group("grafana/prometheus metrics endpoint") ap2.add_argument("--stats", action="store_true", help="enable openmetrics at /.cpr/metrics for admin accounts") ap2.add_argument("--nos-hdd", action="store_true", help="disable disk-space metrics (used/free space)") ap2.add_argument("--nos-vol", action="store_true", help="disable volume size metrics (num files, total bytes, vmaxb/vmaxn)") @@ -1290,24 +1480,27 @@ def add_stats(ap): def add_yolo(ap): - ap2 = ap.add_argument_group('yolo options') + ap2 = ap.add_argument_group("yolo options") ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests") ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF") + ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI") ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET") ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)") + ap2.add_argument("--unsafe-state", action="store_true", help="when one of the emergency fallback locations are used for runtime state ($TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets") def add_optouts(ap): - ap2 = ap.add_argument_group('opt-outs') + ap2 = ap.add_argument_group("opt-outs") ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)") ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution)") ap2.add_argument("--no-dav", action="store_true", help="disable webdav support") ap2.add_argument("--no-del", action="store_true", help="disable delete operations") ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations") ap2.add_argument("--no-cp", action="store_true", help="disable copy operations") + ap2.add_argument("--no-fs-abrt", action="store_true", help="disable ability to abort ongoing copy/move") ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in ") 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") + 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("--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)") @@ -1324,9 +1517,9 @@ def add_optouts(ap): def add_safety(ap): - ap2 = ap.add_argument_group('safety options') + ap2 = ap.add_argument_group("safety options") ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js") - ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih") + ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih") ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r") ap2.add_argument("--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)") @@ -1348,6 +1541,8 @@ def add_safety(ap): ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]") ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]") ap2.add_argument("--early-ban", action="store_true", help="if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy") + ap2.add_argument("--cookie-nmax", metavar="N", type=int, default=50, help="reject HTTP-request from client if they send more than N cookies") + ap2.add_argument("--cookie-cmax", metavar="N", type=int, default=8192, help="reject HTTP-request from client if more than N characters in Cookie header") ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for \033[33mMIN\033[0m minutes (and also kill its active connections) -- disable with 0") ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]") ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives") @@ -1355,7 +1550,7 @@ def add_safety(ap): def add_salt(ap, fk_salt, dk_salt, ah_salt): - ap2 = ap.add_argument_group('salting options') + ap2 = ap.add_argument_group("salting options") ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)") ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)") ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]") @@ -1369,32 +1564,36 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt): def add_shutdown(ap): - ap2 = ap.add_argument_group('shutdown options') + ap2 = ap.add_argument_group("shutdown options") ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints") ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all") ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing") def add_logging(ap): - ap2 = ap.add_argument_group('logging options') + 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, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)") + 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("--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") ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup") ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)") ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals") - ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed") + ap2.add_argument("--log-badpwd", metavar="N", type=int, default=2, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed") + ap2.add_argument("--log-badxml", action="store_true", help="log any invalid XML received from a client") ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs") ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling") ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all") ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all") ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m") + ap2.add_argument("--scan-st-r", metavar="SEC", type=float, default=0.1, help="fs-indexing: wait \033[33mSEC\033[0m between each status-message") + ap2.add_argument("--scan-pr-r", metavar="SEC", type=float, default=10, help="fs-indexing: wait \033[33mSEC\033[0m between each 'progress:' message") + ap2.add_argument("--scan-pr-s", metavar="MiB", type=float, default=1, help="fs-indexing: say 'file: <name>' when a file larger than \033[33mMiB\033[0m is about to be hashed") def add_admin(ap): - ap2 = ap.add_argument_group('admin panel options') + ap2 = ap.add_argument_group("admin panel options") ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)") ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)") ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)") @@ -1408,17 +1607,18 @@ def add_admin(ap): def add_thumbnail(ap): th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6 th_ram = int(max(min(th_ram, 6), 0.3) * 10) / 10 - ap2 = ap.add_argument_group('thumbnail options') + ap2 = ap.add_argument_group("thumbnail options") ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)") ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)") ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)") ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)") ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails") - ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)") + ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="convert-to-image timeout in seconds (volflag=convt)") + ap2.add_argument("--ac-convt", metavar="SEC", type=float, default=150.0, help="convert-to-audio timeout in seconds (volflag=aconvt)") ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate") ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)") ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)") - ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference") + ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,raw,ff", help="image decoders, in order of preference") ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output") ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output") ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)") @@ -1427,22 +1627,27 @@ def add_thumbnail(ap): ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled") ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds") ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern") + ap2.add_argument("--th-spec-p", metavar="N", type=u, default=1, help="for music, do spectrograms or embedded coverart? [\033[32m0\033[0m]=only-art, [\033[32m1\033[0m]=prefer-art, [\033[32m2\033[0m]=only-spec") # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html # https://github.com/libvips/libvips + # https://stackoverflow.com/a/47612661 # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:' - ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow") - ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips") - ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg") + ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow") + ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips") + ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mrw,nef,orf,pef,raf,raw,sr2,srf,x3f", help="image formats to decode using rawpy") + ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg") ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg") ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg") ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)") - ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg") + ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg") def add_transcoding(ap): - ap2 = ap.add_argument_group('transcoding options') + ap2 = ap.add_argument_group("transcoding options") ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable") ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable") + ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)") + ap2.add_argument("--allow-flac", action="store_true", help="allow transcoding to flac (lossless, compressed)") ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead") ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead") ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding") @@ -1451,7 +1656,7 @@ def add_transcoding(ap): def add_tail(ap): - ap2 = ap.add_argument_group('tailing options (realtime streaming of a growing file)') + ap2 = ap.add_argument_group("tailing options (realtime streaming of a growing file)") ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)") ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads") ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)") @@ -1461,7 +1666,7 @@ def add_tail(ap): def add_rss(ap): - ap2 = ap.add_argument_group('RSS options') + ap2 = ap.add_argument_group("RSS options") ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)") ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')") ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all") @@ -1470,7 +1675,7 @@ def add_rss(ap): def add_db_general(ap, hcores): noidx = APPLESAN_TXT if MACOS else "" - ap2 = ap.add_argument_group('general db options') + ap2 = ap.add_argument_group("general db options") ap2.add_argument("-e2d", action="store_true", help="enable up2k database; this enables file search, upload-undo, improves deduplication") ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m") ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m") @@ -1479,8 +1684,8 @@ def add_db_general(ap, hcores): ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty") ap2.add_argument("--hist", metavar="PATH", type=u, default="", help="where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist)") ap2.add_argument("--dbpath", metavar="PATH", type=u, default="", help="override where the volume databases are to be placed; default is the same as \033[33m--hist\033[0m (volflag=dbpath)") - ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)") - ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)") + ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (must be specified as one big regex, not multiple times) (volflag=nohash)") + ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scan (must be specified as one big regex, not multiple times) (volflag=noidx)") ap2.add_argument("--no-dirsz", action="store_true", help="do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz)") ap2.add_argument("--re-dirsz", action="store_true", help="if the directory-sizes in the UI are bonkers, use this along with \033[33m-e2dsa\033[0m to rebuild the index from scratch") ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower") @@ -1492,6 +1697,7 @@ def add_db_general(ap, hcores): ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing") ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)") ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)") + ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags") ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds") ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially") ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)") @@ -1499,7 +1705,7 @@ def add_db_general(ap, hcores): def add_db_metadata(ap): - ap2 = ap.add_argument_group('metadata db options') + ap2 = ap.add_argument_group("metadata db options") ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...") ap2.add_argument("-e2ts", action="store_true", help="scan newly discovered files for metadata on startup; sets \033[33m-e2t\033[0m") ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets \033[33m-e2ts\033[0m") @@ -1509,15 +1715,17 @@ 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("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping") + 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="read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file") + 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") def add_txt(ap): - ap2 = ap.add_argument_group('textfile options') + ap2 = ap.add_argument_group("textfile options") + ap2.add_argument("--md-no-br", action="store_true", help="markdown: disable newline-is-newline; will only render a newline into the html given two trailing spaces or a double-newline (volflag=md_no_br)") ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)") + ap2.add_argument("--txt-eol", metavar="TYPE", type=u, default="", help="enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol)") ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds") ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk") ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)") @@ -1527,7 +1735,7 @@ def add_txt(ap): def add_og(ap): - ap2 = ap.add_argument_group('og / open graph / discord-embed options') + ap2 = ap.add_argument_group("og / open graph / discord-embed options") ap2.add_argument("--og", action="store_true", help="disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own (volflag=og)") ap2.add_argument("--og-ua", metavar="RE", type=u, default="", help="only disable hotlinking / engage OG behavior if the useragent matches regex \033[33mRE\033[0m (volflag=og_ua)") ap2.add_argument("--og-tpl", metavar="PATH", type=u, default="", help="do not return the regular copyparty html, but instead load the jinja2 template at \033[33mPATH\033[0m (if path contains 'EXT' then EXT will be replaced with the requested file's extension) (volflag=og_tpl)") @@ -1544,14 +1752,16 @@ def add_og(ap): ap2.add_argument("--uqe", action="store_true", help="query-string parceling; translate a request for \033[33m/foo/.uqe/BASE64\033[0m into \033[33m/foo?TEXT\033[0m, or \033[33m/foo/?TEXT\033[0m if the first character in \033[33mTEXT\033[0m is a slash. Automatically enabled for \033[33m--og\033[0m") -def add_ui(ap, retry): - ap2 = ap.add_argument_group('ui options') +def add_ui(ap, retry: int): + THEMES = 10 + ap2 = ap.add_argument_group("ui options") ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)") 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("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m") - ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)") - ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed") + 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("--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") ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent") ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)") ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)") @@ -1560,22 +1770,29 @@ def add_ui(ap, retry): ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)") ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)") ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable") - ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)") - ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])") + ap2.add_argument("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)") + ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)") + ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS) ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]") ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html") ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html") ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages") ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)") + ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s)") ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)") ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext") ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)") ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents") ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)") ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-nb\033[0m") - ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)") + ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as --ver-who all") + ap2.add_argument("--ver-who", metavar="TXT", type=u, default="no", help="only show version for: [\033[32ma\033[0m]=admin-permission-anywhere, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone") + ap2.add_argument("--du-who", metavar="TXT", type=u, default="all", help="only show disk usage for: [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mrw\033[0m]=read-write, [\033[32mw\033[0m]=write, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone (volflag=du_who)") + ap2.add_argument("--ver-iwho", type=int, default=0, help=argparse.SUPPRESS) + ap2.add_argument("--du-iwho", type=int, default=0, help=argparse.SUPPRESS) ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on") ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on") + ap2.add_argument("--ctl-re", metavar="SEC", type=int, default=1, help="the controlpanel Refresh-button will autorefresh every SEC; [\033[32m0\033[0m] = just once") ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox") ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf)") ap2.add_argument("--md-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for README.md docs, for example [\033[32mfullscreen\033[0m] (volflag=md_sba)") @@ -1586,7 +1803,7 @@ def add_ui(ap, retry): def add_debug(ap): - ap2 = ap.add_argument_group('debug options') + ap2 = ap.add_argument_group("debug options") ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)") ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)") ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies") @@ -1613,7 +1830,7 @@ def add_debug(ap): def run_argparse( - argv: list[str], formatter: Any, retry: bool, nc: int, verbose=True + argv: list[str], formatter: Any, retry: int, nc: int, verbose=True ) -> argparse.Namespace: ap = argparse.ArgumentParser( formatter_class=formatter, @@ -1659,6 +1876,7 @@ def run_argparse( add_webdav(ap) add_tftp(ap) add_smb(ap) + add_opds(ap) add_safety(ap) add_salt(ap, fk_salt, dk_salt, ah_salt) add_optouts(ap) @@ -1680,18 +1898,21 @@ def run_argparse( for k, h, _ in sects: ap2.add_argument("--help-" + k, action="store_true", help=h) - try: - if not retry: - raise Exception() - + if retry: + a = ["ascii", "replace"] for x in ap._actions: - if not x.help: - continue + try: + x.default = x.default.encode(*a).decode(*a) + except: + pass - a = ["ascii", "replace"] - x.help = x.help.encode(*a).decode(*a) + "\033[0m" - except: - pass + try: + if x.help and x.help is not argparse.SUPPRESS: + x.help = x.help.replace("└─", "`-").encode(*a).decode(*a) + if retry > 2: + x.help = RE_ANSI.sub("", x.help) + except: + pass ret = ap.parse_args(args=argv[1:]) for k, h, t in sects: @@ -1751,16 +1972,7 @@ def main(argv: Optional[list[str]] = None) -> None: ensure_webdeps() - for k, v in zip(argv[1:], argv[2:]): - if k == "-c" and os.path.isfile(v): - supp = args_from_cfg(v) - argv.extend(supp) - - for k in argv[1:]: - v = k[2:] - if k.startswith("-c") and v and os.path.isfile(v): - supp = args_from_cfg(v) - argv.extend(supp) + argv = expand_cfg(argv) deprecated: list[tuple[str, str]] = [ ("--salt", "--warksalt"), @@ -1786,7 +1998,7 @@ def main(argv: Optional[list[str]] = None) -> None: argv[idx] = nk + ov time.sleep(2) - da = len(argv) == 1 + da = len(argv) == 1 and not CFG_DEF try: if da: argv.extend(["--qr"]) @@ -1810,7 +2022,7 @@ def main(argv: Optional[list[str]] = None) -> None: except: nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows - retry = False + retry = 0 for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]: try: al = run_argparse(argv, fmtr, retry, nc) @@ -1819,8 +2031,9 @@ def main(argv: Optional[list[str]] = None) -> None: except SystemExit: raise except: - retry = True - lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex())) + retry += 1 + t = "WARNING: due to limitations in your terminal and/or OS, the helptext cannot be displayed correctly. Will show a simplified version due to the following error:\n[ %s ]:\n%s\n" + lprint(t % (fmtr, min_ex())) try: assert al # type: ignore @@ -1829,6 +2042,9 @@ def main(argv: Optional[list[str]] = None) -> None: except: sys.exit(1) + if al.chdir: + os.chdir(al.chdir) + if al.ansi: al.no_ansi = False elif not al.no_ansi: @@ -1856,7 +2072,7 @@ def main(argv: Optional[list[str]] = None) -> None: if not HAVE_IPV6 and al.i == "::": al.i = "0.0.0.0" - al.i = al.i.split(",") + al.i = [x.strip() for x in al.i.split(",")] try: if "-" in al.p: lo, hi = [int(x) for x in al.p.split("-")] diff --git a/copyparty/__version__.py b/copyparty/__version__.py index a82053fb..53340821 100644 --- a/copyparty/__version__.py +++ b/copyparty/__version__.py @@ -1,8 +1,8 @@ # coding: utf-8 -VERSION = (1, 18, 9) -CODENAME = "logtail" -BUILD_DT = (2025, 8, 1) +VERSION = (1, 19, 15) +CODENAME = "usernames" +BUILD_DT = (2025, 9, 29) 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 01fad895..9c55b3ea 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -13,7 +13,7 @@ import threading import time from datetime import datetime -from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E +from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E from .bos import bos from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap from .pwhash import PWHash @@ -21,8 +21,10 @@ from .util import ( DEF_MTE, DEF_MTH, EXTS, + FAVICON_MIMES, HAVE_SQLITE3, IMPLICATIONS, + META_NOBOTS, MIMES, SQLITE_VER, UNPLICATIONS, @@ -99,6 +101,8 @@ SBADCFG = " ({})".format(BAD_CFG) PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}") PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}") +PTN_U_ANY = re.compile(r"(\${[u][}%])") +PTN_G_ANY = re.compile(r"(\${[g][}%])") PTN_SIGIL = re.compile(r"(\${[ug][}%])") @@ -167,14 +171,19 @@ class Lim(object): self.rotn = 0 # rot num files self.rotl = 0 # rot depth self.rotf = "" # rot datefmt + self.rotf_tz = UTC # rot timezone self.rot_re = re.compile("") # rotf check def log(self, msg: str, c: Union[int, str] = 0) -> None: if self.log_func: self.log_func("up-lim", msg, c) - def set_rotf(self, fmt: str) -> None: + def set_rotf(self, fmt: str, tz: str) -> None: self.rotf = fmt + if tz != "UTC": + from zoneinfo import ZoneInfo + + self.rotf_tz = ZoneInfo(tz) r = re.escape(fmt).replace("%Y", "[0-9]{4}").replace("%j", "[0-9]{3}") r = re.sub("%[mdHMSWU]", "[0-9]{2}", r) self.rot_re = re.compile("(^|/)" + r + "$") @@ -278,7 +287,7 @@ class Lim(object): if self.rot_re.search(path.replace("\\", "/")): return path, "" - suf = datetime.now(UTC).strftime(self.rotf) + suf = datetime.now(self.rotf_tz).strftime(self.rotf) if path: path += "/" @@ -424,10 +433,14 @@ class VFS(object): self.all_nodes[vpath] = self self.all_aps = [(rp, [self])] self.all_vps = [(vp, self)] + self.canonical = self._canonical + self.dcanonical = self._dcanonical else: self.histpath = self.dbpath = "" self.all_aps = [] self.all_vps = [] + self.canonical = self._canonical_null + self.dcanonical = self._dcanonical_null self.get_dbv = self._get_dbv self.ls = self._ls @@ -624,7 +637,39 @@ class VFS(object): vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem) return dbv, vrem - def canonical(self, rem: str, resolve: bool = True) -> str: + def casechk(self, rem: str, do_stat: bool) -> bool: + ap = self.canonical(rem, False) + if do_stat and not bos.path.exists(ap): + return True # doesn't exist at all; good to go + dp, fn = os.path.split(ap) + if not fn: + return True # filesystem root + try: + fns = os.listdir(dp) + except: + return True # maybe chmod 111; assume ok + if fn in fns: + return True + hit = "<?>" + lfn = fn.lower() + for zs in fns: + if lfn == zs.lower(): + hit = zs + break + if not hit: + return True # NFC/NFD or something, can't be helped either way + if self.log: + t = "returning 404 due to underlying case-insensitive filesystem:\n http-req: %r\n local-fs: %r" + self.log("vfs", t % (fn, hit)) + return False + + def _canonical_null(self, rem: str, resolve: bool = True) -> str: + return "" + + def _dcanonical_null(self, rem: str) -> str: + return "" + + def _canonical(self, rem: str, resolve: bool = True) -> str: """returns the canonical path (fully-resolved absolute fs path)""" ap = self.realpath if rem: @@ -632,7 +677,7 @@ class VFS(object): return absreal(ap) if resolve else ap - def dcanonical(self, rem: str) -> str: + def _dcanonical(self, rem: str) -> str: """resolves until the final component (filename)""" ap = self.realpath if rem: @@ -641,6 +686,44 @@ class VFS(object): ad, fn = os.path.split(ap) return os.path.join(absreal(ad), fn) + def _canonical_shr(self, rem: str, resolve: bool = True) -> str: + """returns the canonical path (fully-resolved absolute fs path)""" + ap = self.realpath + if rem: + ap += "/" + rem + + rap = absreal(ap) + if self.shr_files: + assert self.shr_src # !rm + vn, rem = self.shr_src + chk = absreal(os.path.join(vn.realpath, rem)) + if chk != rap: + # not the dir itself; assert file allowed + ad, fn = os.path.split(rap) + if chk != ad or fn not in self.shr_files: + return "\n\n" + + return rap if resolve else ap + + def _dcanonical_shr(self, rem: str) -> str: + """resolves until the final component (filename)""" + ap = self.realpath + if rem: + ap += "/" + rem + + ad, fn = os.path.split(ap) + ad = absreal(ad) + if self.shr_files: + assert self.shr_src # !rm + vn, rem = self.shr_src + chk = absreal(os.path.join(vn.realpath, rem)) + if chk != absreal(ap): + # not the dir itself; assert file allowed + if ad != chk or fn not in self.shr_files: + return "\n\n" + + return os.path.join(ad, fn) + def _ls_nope( self, *a, **ka ) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]: @@ -673,8 +756,12 @@ class VFS(object): """return user-readable [fsdir,real,virt] items at vpath""" virt_vis = {} # nodes readable by user abspath = self.canonical(rem) - real = list(statdir(self.log, scandir, lstat, abspath, throw)) - real.sort() + if abspath: + real = list(statdir(self.log, scandir, lstat, abspath, throw)) + real.sort() + else: + real = [] + if not rem: # no vfs nodes in the list of real inodes real = [x for x in real if x[0] not in self.nodes] @@ -881,6 +968,15 @@ class VFS(object): return None if "xvol" in self.flags: + self_ap = self.realpath + os.sep + if aps.startswith(self_ap): + vp = aps[len(self_ap) :] + if ANYWIN: + vp = vp.replace(os.sep, "/") + vn2, _ = self._find(vp) + if self == vn2: + return self + all_aps = self.shr_all_aps or self.root.all_aps for vap, vns in all_aps: @@ -967,6 +1063,14 @@ class AuthSrv(object): self.indent = "" self.is_lxc = args.c == ["/z/initcfg"] + self._vf0b = { + "tcolor": self.args.tcolor, + "du_iwho": self.args.du_iwho, + "shr_who": self.args.shr_who if self.args.shr else "no", + } + self._vf0 = self._vf0b.copy() + self._vf0["d2d"] = True + # fwd-decl self.vfs = VFS(log_func, "", "", "", AXS(), {}) self.acct: dict[str, str] = {} # uname->pw @@ -1005,7 +1109,10 @@ class AuthSrv(object): yield prev, True def vf0(self): - return {"d2d": True, "tcolor": self.args.tcolor} + return self._vf0.copy() + + def vf0b(self): + return self._vf0b.copy() def idp_checkin( self, broker: Optional["BrokerCli"], uname: str, gname: str @@ -1071,6 +1178,16 @@ class AuthSrv(object): src0 = src # abspath dst0 = dst # vpath + zsl = [] + for ptn, sigil in ((PTN_U_ANY, "${u}"), (PTN_G_ANY, "${g}")): + if bool(ptn.search(src)) != bool(ptn.search(dst)): + zsl.append(sigil) + if zsl: + t = "ERROR: if %s is mentioned in a volume definition, it must be included in both the filesystem-path [%s] and the volume-url [/%s]" + t = "\n".join([t % (x, src, dst) for x in zsl]) + self.log(t, 1) + raise Exception(t) + un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns] if not un_gn: # ensure volume creation if there's no users @@ -1099,6 +1216,9 @@ class AuthSrv(object): if rejected: continue + if gn == self.args.grp_all: + gn = "" + # if ap/vp has a user/group placeholder, make sure to keep # track so the same user/group is mapped when setting perms; # otherwise clear un/gn to indicate it's a regular volume @@ -1160,8 +1280,8 @@ class AuthSrv(object): self.log(t, c=3) raise Exception(BAD_CFG) - if not bos.path.isdir(src): - self.log("warning: filesystem-path does not exist: {}".format(src), 3) + if not bos.path.exists(src): + self.log("warning: filesystem-path did not exist: %r" % (src,), 3) mount[dst] = (src, dst0) daxs[dst] = AXS() @@ -1208,6 +1328,7 @@ class AuthSrv(object): self.load_idp_db(bool(self.idp_accs)) ret = {un: gns[:] for un, gns in self.idp_accs.items()} ret.update({zs: [""] for zs in acct if zs not in ret}) + grps[self.args.grp_all] = list(ret.keys()) for gn, uns in grps.items(): for un in uns: try: @@ -1315,6 +1436,10 @@ class AuthSrv(object): zt = split_cfg_ln(ln) for zs, za in zt.items(): zs = zs.lstrip("-") + if "=" in zs: + t = "WARNING: found an option named [%s] in your [global] config; did you mean to say [%s: %s] instead?" + zs1, zs2 = zs.split("=", 1) + self.log(t % (zs, zs1, zs2), 3) if za is True: self._e("└─argument [{}]".format(zs)) else: @@ -1324,6 +1449,10 @@ class AuthSrv(object): if cat == cata: try: u, p = [zs.strip() for zs in ln.split(":", 1)] + if "=" in u and not p: + t = "WARNING: found username [%s] in your [accounts] config; did you mean to say [%s: %s] instead?" + zs1, zs2 = u.split("=", 1) + self.log(t % (u, zs1, zs2), 3) self._l(ln, 5, "account [{}], password [{}]".format(u, p)) acct[u] = p except: @@ -1394,6 +1523,10 @@ class AuthSrv(object): zd = split_cfg_ln(ln) fstr = "" for sk, sv in zd.items(): + if "=" in sk: + t = "WARNING: found a volflag named [%s] in your config; did you mean to say [%s: %s] instead?" + zs1, zs2 = sk.split("=", 1) + self.log(t % (sk, zs1, zs2), 3) bad = re.sub(r"[a-z0-9_-]", "", sk).lstrip("-") if bad: err = "bad characters [{}] in volflag name [{}]; " @@ -1634,6 +1767,7 @@ class AuthSrv(object): # accept both , and : as separators between usernames zs1, zs2 = x.replace("=", ":").split(":", 1) grps[zs1] = zs2.replace(":", ",").split(",") + grps[zs1] = [x.strip() for x in grps[zs1]] except: t = '\n invalid value "{}" for argument --grp, must be groupname:username1,username2,...' raise Exception(t.format(x)) @@ -1685,6 +1819,9 @@ class AuthSrv(object): self.log("\n{0}\n{1}{0}".format(t, "\n".join(slns))) raise + derive_args(self.args) + self.setup_auth_ord() + self.setup_pwhash(acct) defpw = acct.copy() self.setup_chpw(acct) @@ -1697,9 +1834,10 @@ class AuthSrv(object): mount = cased - if not mount and not self.args.idp_h_usr: + if not mount and not self.args.have_idp_hdrs: # -h says our defaults are CWD at root and read/write for everyone axs = AXS(["*"], ["*"], None, None) + ehint = "" if self.is_lxc: t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw" if len(cfg_files_loaded) == 1: @@ -1709,11 +1847,26 @@ class AuthSrv(object): else: self.log(t % ("the config does not define any volumes",), 1) axs = AXS() + ehint = "; please try moving them up one level, into the parent folder:" elif self.args.c: t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw" self.log(t, 1) axs = AXS() - vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0()) + ehint = ":" + if ehint: + try: + files = os.listdir(E.cfg) + except: + files = [] + hits = [ + x + for x in files + if x.lower().endswith(".conf") and not x.startswith(".") + ] + if hits: + t = "Hint: Found some config files in [%s], but these were not automatically loaded because they are in the wrong place%s %s\n" + self.log(t % (E.cfg, ehint, ", ".join(hits)), 3) + vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0b()) if not axs.uread: self.badcfg1 = True elif "" not in mount: @@ -1749,7 +1902,7 @@ class AuthSrv(object): vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True) vol.root = vfs - zs = "neversymlink" + zs = "neversymlink du_iwho" k_ign = set(zs.split()) for vol in vfs.all_vols.values(): unknown_flags = set() @@ -1857,7 +2010,7 @@ class AuthSrv(object): if missing_users: zs = ", ".join(k for k in sorted(missing_users)) - if self.args.idp_h_usr: + if self.args.have_idp_hdrs: t = "the following users are unknown, and assumed to come from IdP: " self.log(t + zs, c=6) else: @@ -1868,6 +2021,16 @@ class AuthSrv(object): if LEELOO_DALLAS in all_users: raise Exception("sorry, reserved username: " + LEELOO_DALLAS) + zsl = [] + for usr in list(acct)[:]: + zs = acct[usr].strip() + if not zs: + zs = ub64enc(os.urandom(48)).decode("ascii") + zsl.append(usr) + acct[usr] = zs + if zsl: + self.log("generated random passwords for users %r" % (zsl,), 6) + seenpwds = {} for usr, pwd in acct.items(): if pwd in seenpwds: @@ -1891,6 +2054,8 @@ class AuthSrv(object): promote = [] demote = [] for vol in vfs.all_vols.values(): + if not vol.realpath: + continue hid = self.hid_cache.get(vol.realpath) if not hid: zb = hashlib.sha512(afsenc(vol.realpath)).digest() @@ -1929,6 +2094,8 @@ class AuthSrv(object): vol.histpath = absreal(vol.histpath) for vol in vfs.all_vols.values(): + if not vol.realpath: + continue hid = self.hid_cache[vol.realpath] vflag = vol.flags.get("dbpath") if vflag == "-": @@ -2057,7 +2224,7 @@ class AuthSrv(object): zs = vol.flags.get("rotf") if zs: use = True - lim.set_rotf(zs) + lim.set_rotf(zs, vol.flags.get("rotf_tz") or "UTC") zs = vol.flags.get("maxn") if zs: @@ -2188,12 +2355,12 @@ class AuthSrv(object): if vf not in vol.flags: vol.flags[vf] = getattr(self.args, ga) - zs = "forget_ip gid nrand tail_who u2abort u2ow uid ups_who zip_who" + zs = "forget_ip gid nrand tail_who th_spec_p u2abort u2ow uid unp_who ups_who zip_who" for k in zs.split(): if k in vol.flags: vol.flags[k] = int(vol.flags[k]) - zs = "convt tail_fd tail_rate tail_tmax" + zs = "aconvt convt tail_fd tail_rate tail_tmax" for k in zs.split(): if k in vol.flags: vol.flags[k] = float(vol.flags[k]) @@ -2237,6 +2404,11 @@ class AuthSrv(object): vol.lim.uid = vol.flags["uid"] vol.lim.gid = vol.flags["gid"] + vol.flags["du_iwho"] = n_du_who(vol.flags["du_who"]) + + if not enshare: + vol.flags["shr_who"] = self.args.shr_who = "no" + if vol.flags.get("og"): self.args.uqe = True @@ -2323,6 +2495,41 @@ class AuthSrv(object): t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s" self.log(t % (vol.vpath, etv), 3) + zs = str(vol.flags.get("html_head") or "") + if zs and zs[:1] in "%@": + vol.flags["html_head_d"] = zs + head_s = str(vol.flags.get("html_head_s") or "") + else: + zs2 = str(vol.flags.get("html_head_s") or "") + if zs2 and zs: + head_s = "%s\n%s\n" % (zs2.strip(), zs.strip()) + else: + head_s = zs2 or zs + + if head_s and not head_s.endswith("\n"): + head_s += "\n" + + if "norobots" in vol.flags: + head_s += META_NOBOTS + + ico_url = vol.flags.get("ufavico") + if ico_url: + ico_ext = ico_url.split("?")[0].split(".")[-1].lower() + if ico_ext in FAVICON_MIMES: + zs = '<link rel="icon" type="%s" href="%s">\n' + head_s += zs % (FAVICON_MIMES[ico_ext], ico_url) + elif ico_ext == "ico": + zs = '<link rel="shortcut icon" href="%s">\n' + head_s += zs % (ico_url,) + + if head_s: + vol.flags["html_head_s"] = head_s + else: + vol.flags.pop("html_head_s", None) + + if not vol.flags.get("html_head_d"): + vol.flags.pop("html_head_d", None) + vol.check_landmarks() # d2d drops all database features for a volume @@ -2412,6 +2619,47 @@ class AuthSrv(object): self.log(t.format(vol.vpath, mtp), 1) errors = True + for vol in vfs.all_nodes.values(): + if not vol.realpath or os.path.isfile(vol.realpath): + continue + ccs = vol.flags["casechk"][:1].lower() + if ccs in ("y", "n"): + if ccs == "y": + vol.flags["bcasechk"] = True + continue + try: + bos.makedirs(vol.realpath, vf=vol.flags) + files = os.listdir(vol.realpath) + for fn in files: + fn2 = fn.lower() + if fn == fn2: + fn2 = fn.upper() + if fn == fn2 or fn2 in files: + continue + is_ci = os.path.exists(os.path.join(vol.realpath, fn2)) + ccs = "y" if is_ci else "n" + break + if ccs not in ("y", "n"): + ap = os.path.join(vol.realpath, "casechk") + open(ap, "wb").close() + ccs = "y" if os.path.exists(ap[:-1] + "K") else "n" + os.unlink(ap) + except Exception as ex: + if ANYWIN: + zs = "Windows" + ccs = "y" + elif MACOS: + zs = "Macos" + ccs = "y" + else: + zs = "Linux" + ccs = "n" + t = "unable to determine if filesystem at %r is case-insensitive due to %r; assuming casechk=%s due to %s" + self.log(t % (vol.realpath, ex, ccs, zs), 3) + vol.flags["casechk"] = ccs + if ccs == "y": + vol.flags["bcasechk"] = True + tags = self.args.mtp or [] tags = [x.split("=")[0] for x in tags] tags = [y for x in tags for y in x.split(",")] @@ -2515,7 +2763,11 @@ class AuthSrv(object): if "dedup" in zv.flags: have_dedup = True - if "e2d" not in zv.flags and "hardlink" not in zv.flags: + if ( + "e2d" not in zv.flags + and "hardlink" not in zv.flags + and "reflink" not in zv.flags + ): unsafe_dedup.append("/" + zv.vpath) t += "\n" @@ -2524,7 +2776,7 @@ class AuthSrv(object): if not self.args.no_voldump: self.log(t) - if have_e2d or self.args.idp_h_usr: + if have_e2d or self.args.have_idp_hdrs: t = self.chk_sqlite_threadsafe() if t: self.log("\n\033[{}\033[0m\n".format(t)) @@ -2629,6 +2881,8 @@ class AuthSrv(object): self.re_pwd = None pwds = [re.escape(x) for x in self.iacct.keys()] pwds.extend(list(self.sesa)) + if self.args.usernames: + pwds.extend([x.split(":", 1)[1] for x in pwds if ":" in x]) if pwds: if self.ah.on: zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)" @@ -2677,8 +2931,12 @@ class AuthSrv(object): shn.shr_files = set(fns) shn.ls = shn._ls_shr + shn.canonical = shn._canonical_shr + shn.dcanonical = shn._dcanonical_shr else: shn.ls = shn._ls + shn.canonical = shn._canonical + shn.dcanonical = shn._dcanonical shn.shr_owner = s_un shn.shr_src = (s_vfs, s_rem) @@ -2741,6 +2999,7 @@ class AuthSrv(object): "dcrop": vf["crop"], "dth3x": vf["th3x"], "u2ts": vf["u2ts"], + "shr_who": vf["shr_who"], "frand": bool(vf.get("rand")), "lifetime": vf.get("lifetime") or 0, "unlist": vf.get("unlist") or "", @@ -2749,14 +3008,19 @@ class AuthSrv(object): js_htm = { "SPINNER": self.args.spinner, "s_name": self.args.bname, + "idp_login": self.args.idp_login, "have_up2k_idx": "e2d" in vf, "have_acode": not self.args.no_acode, + "have_c2flac": self.args.allow_flac, + "have_c2wav": self.args.allow_wav, "have_shr": self.args.shr, + "shr_who": vf["shr_who"], "have_zip": not self.args.no_zip, "have_mv": not self.args.no_mv, "have_del": not self.args.no_del, "have_unpost": int(self.args.unpost), - "have_emp": self.args.emp, + "have_emp": int(self.args.emp), + "md_no_br": int(vf.get("md_no_br") or 0), "ext_th": vf.get("ext_th_d") or {}, "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"), "sba_md": vf.get("md_sba") or "", @@ -2776,6 +3040,7 @@ class AuthSrv(object): "dvol": self.args.au_vol, "idxh": int(self.args.ih), "dutc": not self.args.localtime, + "dfszf": self.args.ui_filesz, "themes": self.args.themes, "turbolvl": self.args.turbo, "nosubtle": self.args.nosubtle, @@ -2807,10 +3072,22 @@ class AuthSrv(object): zs = str(vol.flags.get("tcolor") or self.args.tcolor) vol.flags["tcolor"] = zs.lstrip("#") + def setup_auth_ord(self) -> None: + ao = [x.strip() for x in self.args.auth_ord.split(",")] + if "idp" in ao: + zi = ao.index("idp") + ao = ao[:zi] + ["idp-hm", "idp-h"] + ao[zi:] + zsl = "pw idp-h idp-hm ipu".split() + pw, h, hm, ipu = [ao.index(x) if x in ao else 99 for x in zsl] + self.args.ao_idp_before_pw = min(h, hm) < pw + self.args.ao_h_before_hm = h < hm + self.args.ao_ipu_wins = ipu == 0 + self.args.ao_have_pw = pw < 99 or not self.args.have_idp_hdrs + def load_idp_db(self, quiet=False) -> None: # mutex me level = self.args.idp_store - if level < 2 or not self.args.idp_h_usr: + if level < 2 or not self.args.have_idp_hdrs: return assert sqlite3 # type: ignore # !rm @@ -2866,7 +3143,10 @@ class AuthSrv(object): n = [] q = "insert into us values (?,?,?)" - for uname in self.acct: + accs = list(self.acct) + if self.args.have_idp_hdrs and self.args.idp_cookie: + accs.extend(self.idp_accs.keys()) + for uname in accs: if uname not in ases: sid = ub64enc(os.urandom(blen)).decode("ascii") cur.execute(q, (uname, sid, int(time.time()))) @@ -2924,6 +3204,9 @@ class AuthSrv(object): t = "minimum password length: %d characters" return False, t % (self.args.chpw_len,) + if self.args.usernames: + pw = "%s:%s" % (uname, pw) + hpw = self.ah.hash(pw) if self.ah.on else pw if hpw == self.acct[uname]: @@ -3015,6 +3298,12 @@ class AuthSrv(object): self.log("chpw: " + msg, 6) def setup_pwhash(self, acct: dict[str, str]) -> None: + if self.args.usernames: + for uname, pw in list(acct.items())[:]: + if pw.startswith("+") and len(pw) == 33: + continue + acct[uname] = "%s:%s" % (uname, pw) + self.ah = PWHash(self.args) if not self.ah.on: if self.args.ah_cli or self.args.ah_gen: @@ -3387,6 +3676,35 @@ class AuthSrv(object): self.log("generated config:\n\n" + "\n".join(ret)) +def derive_args(args: argparse.Namespace) -> None: + args.have_idp_hdrs = bool(args.idp_h_usr or args.idp_hm_usr) + args.have_ipu_or_ipr = bool(args.ipu or args.ipr) + + +def n_du_who(s: str) -> int: + if s == "all": + return 9 + if s == "auth": + return 7 + if s == "w": + return 5 + if s == "rw": + return 4 + if s == "a": + return 3 + return 0 + + +def n_ver_who(s: str) -> int: + if s == "all": + return 9 + if s == "auth": + return 6 + if s == "a": + return 3 + return 0 + + def split_cfg_ln(ln: str) -> dict[str, Any]: # "a, b, c: 3" => {a:true, b:true, c:3} ret = {} @@ -3419,7 +3737,9 @@ def expand_config_file( if os.path.isdir(fp): names = list(sorted(os.listdir(fp))) - cnames = [x for x in names if x.lower().endswith(".conf")] + cnames = [ + x for x in names if x.lower().endswith(".conf") and not x.startswith(".") + ] if not cnames: t = "warning: tried to read config-files from folder '%s' but it does not contain any " if names: diff --git a/copyparty/bos/bos.py b/copyparty/bos/bos.py index 6c876e04..408244b9 100644 --- a/copyparty/bos/bos.py +++ b/copyparty/bos/bos.py @@ -2,18 +2,22 @@ from __future__ import print_function, unicode_literals import os +import time from ..util import SYMTIME, fsdec, fsenc from . import path as path if True: # pylint: disable=using-constant-test - from typing import Any, Optional + from typing import Any, Optional, Union + + from ..util import NamedLogger MKD_755 = {"chmod_d": 0o755} MKD_700 = {"chmod_d": 0o700} +UTIME_CLAMPS = ((max, -2147483647), (max, 1), (min, 4294967294), (min, 2147483646)) -_ = (path, MKD_755, MKD_700) -__all__ = ["path", "MKD_755", "MKD_700"] +_ = (path, MKD_755, MKD_700, UTIME_CLAMPS) +__all__ = ["path", "MKD_755", "MKD_700", "UTIME_CLAMPS"] # grep -hRiE '(^|[^a-zA-Z_\.-])os\.' . | gsed -r 's/ /\n/g;s/\(/(\n/g' | grep -hRiE '(^|[^a-zA-Z_\.-])os\.' | sort | uniq -c # printf 'os\.(%s)' "$(grep ^def bos/__init__.py | gsed -r 's/^def //;s/\(.*//' | tr '\n' '|' | gsed -r 's/.$//')" @@ -99,6 +103,40 @@ def utime( return os.utime(fsenc(p), times) +def utime_c( + log: Union["NamedLogger", Any], p: str, ts: int, follow_symlinks: bool = True, throw: bool = False +) -> Optional[int]: + clamp = 0 + ov = ts + bp = fsenc(p) + now = int(time.time()) + while True: + try: + if SYMTIME: + os.utime(bp, (now, ts), follow_symlinks=follow_symlinks) + else: + os.utime(bp, (now, ts)) + if clamp: + t = "filesystem rejected utime(%r); clamped %s to %s" + log(t % (p, ov, ts)) + return ts + except Exception as ex: + pv = ts + while clamp < len(UTIME_CLAMPS): + fun, cv = UTIME_CLAMPS[clamp] + ts = fun(ts, cv) + clamp += 1 + if ts != pv: + break + if clamp >= len(UTIME_CLAMPS): + if throw: + raise + else: + t = "could not utime(%r) to %s; %s, %r" + log(t % (p, ov, ex, ex)) + return None + + if hasattr(os, "lstat"): def lstat(p: str) -> os.stat_result: diff --git a/copyparty/broker_mpw.py b/copyparty/broker_mpw.py index f1db3a29..c921cd7e 100644 --- a/copyparty/broker_mpw.py +++ b/copyparty/broker_mpw.py @@ -12,6 +12,7 @@ import queue from .__init__ import ANYWIN from .authsrv import AuthSrv from .broker_util import BrokerCli, ExceptionalQueue, NotExQueue +from .fsutil import ramdisk_chk from .httpsrv import HttpSrv from .util import FAKE_MP, Daemon, HMaccas @@ -56,6 +57,7 @@ class MpWorker(BrokerCli): # starting to look like a good idea self.asrv = AuthSrv(args, None, False) + ramdisk_chk(self.asrv) # instantiate all services here (TODO: inheritance?) self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8) @@ -99,6 +101,7 @@ class MpWorker(BrokerCli): if dest == "reload": self.logw("mpw.asrv reloading") self.asrv.reload() + ramdisk_chk(self.asrv) self.logw("mpw.asrv reloaded") continue diff --git a/copyparty/broker_util.py b/copyparty/broker_util.py index ea987200..10c778dc 100644 --- a/copyparty/broker_util.py +++ b/copyparty/broker_util.py @@ -2,7 +2,6 @@ from __future__ import print_function, unicode_literals import argparse -import traceback from queue import Queue diff --git a/copyparty/cert.py b/copyparty/cert.py index a22d2857..49fa9ea2 100644 --- a/copyparty/cert.py +++ b/copyparty/cert.py @@ -130,6 +130,7 @@ def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]): nlog: "NamedLogger" = lambda msg, c=0: log("cert-gen-srv", msg, c) names = args.crt_ns.split(",") if args.crt_ns else [] + names = [x.strip() for x in names] if not args.crt_exact: for n in names[:]: names.append("*.{}".format(n)) diff --git a/copyparty/cfg.py b/copyparty/cfg.py index 2f75ab28..93c146af 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -44,6 +44,7 @@ def vf_bmap() -> dict[str, str]: "gsel", "hardlink", "magic", + "md_no_br", "no_db_ip", "no_sb_md", "no_sb_lg", @@ -51,11 +52,14 @@ def vf_bmap() -> dict[str, str]: "og", "og_no_head", "og_s_title", + "opds", "rand", "reflink", + "rm_partial", "rmagic", "rss", "wo_up_readme", + "wram", "xdev", "xlink", "xvol", @@ -68,6 +72,7 @@ def vf_bmap() -> dict[str, str]: def vf_vmap() -> dict[str, str]: """argv-to-volflag: simple values""" ret = { + "ac_convt": "aconvt", "no_hash": "nohash", "no_idx": "noidx", "re_maxage": "scan", @@ -79,12 +84,16 @@ def vf_vmap() -> dict[str, str]: } for k in ( "bup_ck", + "casechk", "chmod_d", "chmod_f", "dbd", + "du_who", + "ufavico", "forget_ip", "hsortn", "html_head", + "html_head_s", "lg_sbf", "md_sbf", "lg_sba", @@ -101,21 +110,26 @@ def vf_vmap() -> dict[str, str]: "og_title_i", "og_tpl", "og_ua", + "opds_exts", "put_ck", "put_name", "mv_retry", "rm_retry", + "shr_who", "sort", "tail_fd", "tail_rate", "tail_tmax", "tail_who", "tcolor", + "th_spec_p", + "txt_eol", "unlist", "u2abort", "u2ts", "uid", "gid", + "unp_who", "ups_who", "zip_who", "zipmaxn", @@ -179,8 +193,10 @@ flagcats = { "chmod_f=644": "unix-permission for new files", "uid=573": "change owner of new files/folders to unix-user 573", "gid=999": "change owner of new files/folders to unix-group 999", + "wram": "allow uploading into ramdisks", "sparse": "force use of sparse files, mainly for s3-backed storage", "nosparse": "deny use of sparse files, mainly for slow storage", + "rm_partial": "delete unfinished uploads from HDD when they timeout", "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files", "nosub": "forces all uploads into the top folder of the vfs", "magic": "enables filetype detection for nameless uploads", @@ -209,6 +225,7 @@ flagcats = { "upload rotation\n(moves all uploads into the specified folder structure)": { "rotn=100,3": "3 levels of subfolders with 100 entries in each", "rotf=%Y-%m/%d-%H": "date-formatted organizing", + "rotf_tz=Europe/Oslo": "timezone (default=UTC)", "lifetime=3600": "uploads are deleted after 1 hour", }, "database, general": { @@ -237,6 +254,7 @@ flagcats = { "no_db_ip": "never store uploader-IP in the db; disables unpost", "fat32": "avoid excessive reindexing on android sdcardfs", "dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff", + "casechk=auto": "actively prevent case-insensitive filesystem? y/n", "xlink": "cross-volume dupe detection / linking (dangerous)", "xdev": "do not descend into other filesystems", "xvol": "do not follow symlinks leaving the volume root", @@ -259,7 +277,9 @@ flagcats = { "thsize": "thumbnail res; WxH", "crop": "center-cropping (y/n/fy/fn)", "th3x": "3x resolution (y/n/fy/fn)", - "convt": "conversion timeout in seconds", + "convt": "convert-to-image timeout in seconds", + "aconvt": "convert-to-audio timeout in seconds", + "th_spec_p=1": "make spectrograms? 0=never 1=fallback 2=always", "ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s", }, "handlers\n(better explained in --help-handlers)": { @@ -285,10 +305,13 @@ flagcats = { "sort": "default sort order", "nsort": "natural-sort of leading digits in filenames", "hsortn": "number of sort-rules to add to media URLs", + "ufavico=URL": "per-volume favicon (.ico/png/gif/svg)", "unlist": "dont list files matching REGEX", "html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH", + "html_head_s=TXT": "additional static text in the html <head>", "tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)", "nodirsz": "don't show total folder size", + "du_who=all": "show disk-usage info to everyone", "robots": "allows indexing by search engines (default)", "norobots": "kindly asks search engines to leave", "unlistcr": "don't list read-access in controlpanel", @@ -317,11 +340,17 @@ flagcats = { "og_no_head": "you want to add tags manually with og_tpl", "og_ua": "if defined: only send OG html if useragent matches this regex", }, + "opds": { + "opds": "enable OPDS", + "opds_exts": "file formats to list in OPDS feeds; leave empty to show everything", + }, "textfiles": { + "md_no_br": "newline only on double-newline or two tailing spaces", "md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope", "exp": "enable textfile expansion; see --help-exp", "exp_md": "placeholders to expand in markdown files; see --help", "exp_lg": "placeholders to expand in prologue/epilogue; see --help", + "txt_eol=lf": "enable EOL conversion when writing docs (LF or CRLF)", }, "tailing": { "notail": "disable ?tail (download a growing file continuously)", @@ -339,6 +368,8 @@ flagcats = { "dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so', "rss": "allow '?rss' URL suffix (experimental)", "rmagic": "expensive analysis for mimetype accuracy", + "shr_who=auth": "who can create shares? no/auth/a", + "unp_who=2": "unpost only if same... 1=ip+name, 2=ip, 3=name", "ups_who=2": "restrict viewing the list of recent uploads", "zip_who=2": "restrict access to download-as-zip/tar", "zipmaxn=9k": "reject download-as-zip if more than 9000 files", diff --git a/copyparty/dxml.py b/copyparty/dxml.py index b49f060b..6271da8e 100644 --- a/copyparty/dxml.py +++ b/copyparty/dxml.py @@ -65,6 +65,9 @@ DXMLParser = _DXMLParser def parse_xml(txt: str) -> ET.Element: + """ + Parse XML into an xml.etree.ElementTree.Element while defusing some unsafe parts. + """ parser = DXMLParser() parser.feed(txt) return parser.close() # type: ignore diff --git a/copyparty/fsutil.py b/copyparty/fsutil.py index 606cea49..70421e8f 100644 --- a/copyparty/fsutil.py +++ b/copyparty/fsutil.py @@ -7,7 +7,7 @@ import re import time from .__init__ import ANYWIN, MACOS -from .authsrv import AXS, VFS +from .authsrv import AXS, VFS, AuthSrv from .bos import bos from .util import chkcmd, min_ex, undot @@ -18,22 +18,25 @@ if True: # pylint: disable=using-constant-test class Fstab(object): - def __init__(self, log: "RootLogger", args: argparse.Namespace): + def __init__(self, log: "RootLogger", args: argparse.Namespace, verbose: bool): self.log_func = log + self.verbose = verbose self.warned = False self.trusted = False self.tab: Optional[VFS] = None self.oldtab: Optional[VFS] = None self.srctab = "a" - self.cache: dict[str, str] = {} + self.cache: dict[str, tuple[str, str]] = {} self.age = 0.0 self.maxage = args.mtab_age def log(self, msg: str, c: Union[int, str] = 0) -> None: + if not c or self.verbose: + return self.log_func("fstab", msg, c) - def get(self, path: str) -> str: + def get(self, path: str) -> tuple[str, str]: now = time.time() if now - self.age > self.maxage or len(self.cache) > 9000: self.age = now @@ -41,6 +44,7 @@ class Fstab(object): self.tab = None self.cache = {} + mp = "" fs = "ext4" msg = "failed to determine filesystem at %r; assuming %s\n%s" @@ -50,7 +54,7 @@ class Fstab(object): path = self._winpath(path) except: self.log(msg % (path, fs, min_ex()), 3) - return fs + return fs, "" path = undot(path) try: @@ -59,14 +63,14 @@ class Fstab(object): pass try: - fs = self.get_w32(path) if ANYWIN else self.get_unix(path) + fs, mp = self.get_w32(path) if ANYWIN else self.get_unix(path) except: self.log(msg % (path, fs, min_ex()), 3) fs = fs.lower() - self.cache[path] = fs - self.log("found %s at %r" % (fs, path)) - return fs + self.cache[path] = (fs, mp) + self.log("found %s at %r, %r" % (fs, mp, path)) + return fs, mp def _winpath(self, path: str) -> str: # try to combine volume-label + st_dev (vsn) @@ -81,34 +85,49 @@ class Fstab(object): self.tab = VFS(self.log_func, "idk", "/", "/", AXS(), {}) self.trusted = False - def build_tab(self) -> None: - self.log("inspecting mtab for changes") - + def _from_sp_mount(self) -> dict[str, str]: sptn = r"^.*? on (.*) type ([^ ]+) \(.*" if MACOS: sptn = r"^.*? on (.*) \(([^ ]+), .*" ptn = re.compile(sptn) so, _ = chkcmd(["mount"]) - tab1: list[tuple[str, str]] = [] - atab = [] + dtab: dict[str, str] = {} for ln in so.split("\n"): m = ptn.match(ln) if not m: continue zs1, zs2 = m.groups() - tab1.append((str(zs1), str(zs2))) - atab.append(ln) + dtab[str(zs1)] = str(zs2) + + return dtab + + def _from_proc(self) -> dict[str, str]: + ret: dict[str, str] = {} + with open("/proc/self/mounts", "rb", 262144) as f: + src = f.read(262144).decode("utf-8", "replace").split("\n") + for zsl in [x.split(" ") for x in src]: + if len(zsl) < 3: + continue + zs = zsl[1] + zs = zs.replace("\\011", "\t").replace("\\040", " ").replace("\\134", "\\") + ret[zs] = zsl[2] + return ret + + def build_tab(self) -> None: + self.log("inspecting mtab for changes") + dtab = self._from_sp_mount() if MACOS else self._from_proc() # keep empirically-correct values if mounttab unchanged - srctab = "\n".join(sorted(atab)) + srctab = str(sorted(dtab.items())) if srctab == self.srctab: self.tab = self.oldtab return self.log("mtab has changed; reevaluating support for sparse files") + tab1 = list(dtab.items()) tab1.sort(key=lambda x: (len(x[0]), x[0])) path1, fs1 = tab1[0] tab = VFS(self.log_func, fs1, path1, path1, AXS(), {}) @@ -146,7 +165,7 @@ class Fstab(object): vn.realpath = ptn.sub(nval, vn.realpath) visit.extend(list(vn.nodes.values())) - def get_unix(self, path: str) -> str: + def get_unix(self, path: str) -> tuple[str, str]: if not self.tab: try: self.build_tab() @@ -155,20 +174,44 @@ class Fstab(object): # prisonparty or other restrictive environment if not self.warned: self.warned = True - self.log("failed to build tab:\n{}".format(min_ex()), 3) + t = "failed to associate fs-mounts with the VFS (this is fine):\n%s" + self.log(t % (min_ex(),), 6) self.build_fallback() assert self.tab # !rm ret = self.tab._find(path)[0] if self.trusted or path == ret.vpath: - return ret.realpath.split("/")[0] + return ret.realpath.split("/")[0], ret.vpath else: - return "idk" + return "idk", "" - def get_w32(self, path: str) -> str: + def get_w32(self, path: str) -> tuple[str, str]: if not self.tab: self.build_fallback() assert self.tab # !rm ret = self.tab._find(path)[0] - return ret.realpath + return ret.realpath, "" + + +def ramdisk_chk(asrv: AuthSrv) -> None: + # should have been in authsrv but that's a circular import + mods = [] + ramfs = ("tmpfs", "overlay") + log = asrv.log_func or print + fstab = Fstab(log, asrv.args, False) + for vn in asrv.vfs.all_nodes.values(): + if not vn.axs.uwrite or "wram" in vn.flags: + continue + ap = vn.realpath + if not ap or os.path.isfile(ap): + continue + fs, mp = fstab.get(ap) + mp = "/" + mp.strip("/") + if fs == "tmpfs" or (mp == "/" and fs in ramfs): + mods.append((vn.vpath, ap, fs, mp)) + vn.axs.uwrite.clear() + if mods: + t = "WARNING: write-access was removed from the following volumes because they are not mapped to an actual HDD for storage! All uploaded data would live in RAM only, and all uploaded files would be LOST on next reboot. To allow uploading and ignore this hazard, enable the 'wram' option (global/volflag). List of affected volumes:" + t2 = ["\n volume=[/%s], abspath=%r, type=%s, root=%r" % x for x in mods] + log("vfs", t + "".join(t2) + "\n", 1) diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index 2f45c3f4..5482436b 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -68,13 +68,13 @@ class FtpAuth(DummyAuthorizer): if ip.startswith("::ffff:"): ip = ip[7:] - ip = ipnorm(ip) + ipn = ipnorm(ip) bans = self.hub.bans - if ip in bans: - rt = bans[ip] - time.time() + if ipn in bans: + rt = bans[ipn] - time.time() if rt < 0: logging.info("client unbanned") - del bans[ip] + del bans[ipn] else: raise AuthenticationFailed("banned") @@ -83,7 +83,12 @@ class FtpAuth(DummyAuthorizer): uname = "*" if username != "anonymous": uname = "" - for zs in (password, username): + if args.usernames: + alts = ["%s:%s" % (username, password)] + else: + alts = password, username + + for zs in alts: zs = asrv.iacct.get(asrv.ah.hash(zs), "") if zs: uname = zs @@ -91,6 +96,10 @@ class FtpAuth(DummyAuthorizer): if args.ipu and uname == "*": uname = args.ipu_iu[args.ipu_nm.map(ip)] + if args.ipr and uname in args.ipr_u: + if not args.ipr_u[uname].map(ip): + logging.warning("username [%s] rejected by --ipr", uname) + uname = "*" if not uname or not (asrv.vfs.aread.get(uname) or asrv.vfs.awrite.get(uname)): g = self.hub.gpwd @@ -143,10 +152,6 @@ class FtpFs(AbstractedFS): self.cwd = "/" # pyftpdlib convention of leading slash self.root = "/var/lib/empty" - self.can_read = self.can_write = self.can_move = False - self.can_delete = self.can_get = self.can_upget = False - self.can_admin = self.can_dot = False - self.listdirinfo = self.listdir self.chdir(".") @@ -197,6 +202,9 @@ class FtpFs(AbstractedFS): if r and not cr or w and not cw or m and not cm or d and not cd: raise FSE(t.format(vpath), 1) + if "bcasechk" in vfs.flags and not vfs.casechk(rem, True): + raise FSE("No such file or directory", 1) + return os.path.join(vfs.realpath, rem), vfs, rem except Pebkac as ex: raise FSE(str(ex)) @@ -209,7 +217,7 @@ class FtpFs(AbstractedFS): m: bool = False, d: bool = False, ) -> tuple[str, VFS, str]: - return self.v2a(os.path.join(self.cwd, vpath), r, w, m, d) + return self.v2a(join(self.cwd, vpath), r, w, m, d) def ftp2fs(self, ftppath: str) -> str: # return self.v2a(ftppath) @@ -271,6 +279,10 @@ class FtpFs(AbstractedFS): def chdir(self, path: str) -> None: nwd = join(self.cwd, path) vfs, rem = self.hub.asrv.vfs.get(nwd, self.uname, False, False) + if not vfs.realpath: + self.cwd = nwd + return + ap = vfs.canonical(rem) try: st = bos.stat(ap) @@ -285,16 +297,6 @@ class FtpFs(AbstractedFS): raise FSE("Permission denied", 1) self.cwd = nwd - ( - self.can_read, - self.can_write, - self.can_move, - self.can_delete, - self.can_get, - self.can_upget, - self.can_admin, - self.can_dot, - ) = avfs.can_access("", self.h.uname) def mkdir(self, path: str) -> None: ap, vfs, _ = self.rv2a(path, w=True) @@ -317,7 +319,7 @@ class FtpFs(AbstractedFS): vfs_ls = [x[0] for x in vfs_ls1] vfs_ls.extend(vfs_virt.keys()) - if not self.can_dot: + if self.uname not in vfs.axs.udot: vfs_ls = exclude_dotfiles(vfs_ls) vfs_ls.sort() @@ -365,16 +367,13 @@ class FtpFs(AbstractedFS): raise FSE(str(ex)) def rename(self, src: str, dst: str) -> None: - if not self.can_move: - raise FSE("Not allowed for user " + self.h.uname) - if self.args.no_mv: raise FSE("The rename/move feature is disabled in server config") svp = join(self.cwd, src).lstrip("/") dvp = join(self.cwd, dst).lstrip("/") try: - self.hub.up2k.handle_mv(self.uname, self.h.cli_ip, svp, dvp) + self.hub.up2k.handle_mv("", self.uname, self.h.cli_ip, svp, dvp) except Exception as ex: raise FSE(str(ex)) @@ -398,7 +397,7 @@ class FtpFs(AbstractedFS): def utime(self, path: str, timeval: float) -> None: ap = self.rv2a(path, w=True)[0] - return bos.utime(ap, (timeval, timeval)) + bos.utime_c(logging.warning, ap, int(timeval), False) def lstat(self, path: str) -> os.stat_result: ap = self.rv2a(path)[0] @@ -487,27 +486,37 @@ class FtpHandler(FTPHandler): def ftp_STOR(self, file: str, mode: str = "w") -> Any: # Optional[str] vp = join(self.fs.cwd, file).lstrip("/") - ap, vfs, rem = self.fs.v2a(vp, w=True) + try: + ap, vfs, rem = self.fs.v2a(vp, w=True) + except Exception as ex: + self.respond("550 %s" % (ex,), logging.info) + return self.vfs_map[ap] = vp xbu = vfs.flags.get("xbu") - if xbu and not runhook( - None, - None, - self.hub.up2k, - "xbu.ftpd", - xbu, - ap, - vp, - "", - self.uname, - self.hub.asrv.vfs.get_perms(vp, self.uname), - 0, - 0, - self.cli_ip, - time.time(), - "", - ): - raise FSE("Upload blocked by xbu server config") + if xbu: + hr = runhook( + None, + None, + self.hub.up2k, + "xbu.ftpd", + xbu, + ap, + vp, + "", + self.uname, + self.hub.asrv.vfs.get_perms(vp, self.uname), + 0, + 0, + self.cli_ip, + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "Upload blocked by xbu server config: %r" % (vp,) + self.respond("550 %s" % (t,), logging.info) + return # print("ftp_STOR: {} {} => {}".format(vp, mode, ap)) ret = FTPHandler.ftp_STOR(self, file, mode) @@ -607,7 +616,7 @@ class Ftpd(object): if "::" in ips: ips.append("0.0.0.0") - ips = [x for x in ips if "unix:" not in x] + ips = [x for x in ips if not x.startswith(("unix:", "fd:"))] if self.args.ftp4: ips = [x for x in ips if ":" not in x] diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index bea4ce43..7bc09c16 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -12,7 +12,6 @@ import random import re import socket import stat -import string import sys import threading # typechk import time @@ -31,7 +30,7 @@ try: except: pass -from .__init__ import ANYWIN, PY2, RES, TYPE_CHECKING, EnvParams, unicode +from .__init__ import ANYWIN, RES, TYPE_CHECKING, EnvParams, unicode from .__version__ import S_VERSION from .authsrv import LEELOO_DALLAS, VFS # typechk from .bos import bos @@ -49,7 +48,6 @@ from .util import ( FN_EMB, HAVE_SQLITE3, HTTPCODE, - META_NOBOTS, UTC, Garda, MultipartParser, @@ -62,9 +60,11 @@ from .util import ( alltrace, atomic_move, b64dec, + eol_conv, exclude_dotfiles, formatdate, fsenc, + gen_content_disposition, gen_filekey, gen_filekey_dbg, gencookie, @@ -262,7 +262,8 @@ class HttpCli(object): def _assert_safe_rem(self, rem: str) -> None: # sanity check to prevent any disasters - if rem.startswith("/") or rem.startswith("../") or "/../" in rem: + # (this function hopefully serves no purpose; validation has already happened at this point, this only exists as a last-ditch effort just in case) + if rem.startswith(("/", "../")) or "/../" in rem: raise Exception("that was close") def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str: @@ -284,10 +285,9 @@ class HttpCli(object): zs += "&" if "?" in zs else "?" ka["js"] = zs - zso = self.vn.flags.get("html_head") - if zso: + if "html_head_d" in self.vn.flags: ka["this"] = self - self._build_html_head(zso, ka) + self._build_html_head(ka) ka["html_head"] = self.html_head return tpl.render(**ka) # type: ignore @@ -331,6 +331,7 @@ class HttpCli(object): k, zs = header_line.split(":", 1) self.headers[k.lower()] = zs.strip() except: + headerlines = [repr(x) for x in headerlines] msg = "#[ " + " ]\n#[ ".join(headerlines) + " ]" raise Pebkac(400, "bad headers", log=msg) @@ -383,9 +384,20 @@ class HttpCli(object): try: cli_ip = zsl[n].strip() except: - cli_ip = zsl[0].strip() - t = "rproxy={} oob x-fwd {}" - self.log(t.format(self.args.rproxy, zso), c=3) + cli_ip = self.ip + self.bad_xff = True + if self.args.rproxy != 9999999: + t = "global-option --rproxy %d could not be used (out-of-bounds) for the received header [%s]" + self.log(t % (self.args.rproxy, zso), c=3) + else: + zsl = [ + " rproxy: %d if this client's IP-address is [%s]" + % (-1 - zd, zs.strip()) + for zd, zs in enumerate(zsl[::-1]) + ] + t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! The raw header value was [%s]. Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:' + t = t % (self.args.xff_hdr, zso) + self.log("%s\n\n%s\n" % (t, "\n".join(zsl)), 3) pip = self.conn.addr[0] xffs = self.conn.xff_nm @@ -549,19 +561,23 @@ class HttpCli(object): zso = self.headers.get("cookie") if zso: - if len(zso) > 8192: + if len(zso) > self.args.cookie_cmax: self.loud_reply("cookie header too big", status=400) return False zsll = [x.split("=", 1) for x in zso.split(";") if "=" in x] cookies = {k.strip(): unescape_cookie(zs) for k, zs in zsll} - cookie_pw = cookies.get("cppws") or cookies.get("cppwd") or "" + cookie_pw = cookies.get("cppws" if self.is_https else "cppwd") or "" if "b" in cookies and "b" not in uparam: uparam["b"] = cookies["b"] + if len(cookies) > self.args.cookie_nmax: + self.loud_reply("too many cookies", status=400) else: cookies = {} cookie_pw = "" - if len(uparam) > 10 or len(cookies) > 50: + if len(uparam) > 12: + t = "http-request rejected; num.params: %d %r" + self.log(t % (len(uparam), self.req), 3) self.loud_reply("u wot m8", status=400) return False @@ -607,8 +623,24 @@ class HttpCli(object): or "*" ) - if self.args.idp_h_usr: - idp_usr = self.headers.get(self.args.idp_h_usr) or "" + if self.args.have_idp_hdrs and ( + self.uname == "*" or self.args.ao_idp_before_pw + ): + idp_usr = "" + if self.args.idp_hm_usr: + for hn, hmv in self.args.idp_hm_usr_p.items(): + zs = self.headers.get(hn) + if zs: + for zs1, zs2 in hmv.items(): + if zs == zs1: + idp_usr = zs2 + break + if idp_usr: + break + for hn in self.args.idp_h_usr: + if idp_usr and not self.args.ao_h_before_hm: + break + idp_usr = self.headers.get(hn) or idp_usr if idp_usr: idp_grp = ( self.headers.get(self.args.idp_h_grp) or "" @@ -657,12 +689,24 @@ class HttpCli(object): if idp_usr in self.asrv.vfs.aread: self.pw = "" self.uname = idp_usr - self.html_head += "<script>var is_idp=1</script>\n" + if self.args.ao_have_pw or self.args.idp_logout: + self.html_head += "<script>var is_idp=1</script>\n" + else: + self.html_head += "<script>var is_idp=2</script>\n" + zs = self.asrv.ases.get(idp_usr) + if zs: + self.set_idp_cookie(zs) else: self.log("unknown username: %r" % (idp_usr,), 1) - if self.args.ipu and self.uname == "*": - self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)] + if self.args.have_ipu_or_ipr: + if self.args.ipu and (self.uname == "*" or self.args.ao_ipu_wins): + self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)] + ipr = self.conn.hsrv.ipr + if ipr and self.uname in ipr: + if not ipr[self.uname].map(self.ip): + self.log("username [%s] rejected by --ipr" % (self.uname,), 3) + self.uname = "*" self.rvol = self.asrv.vfs.aread[self.uname] self.wvol = self.asrv.vfs.awrite[self.uname] @@ -684,12 +728,15 @@ class HttpCli(object): cookies["b"] = "" vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False) - if "xdev" in vn.flags or "xvol" in vn.flags: + if vn.realpath and ("xdev" in vn.flags or "xvol" in vn.flags): ap = vn.canonical(rem) avn = vn.chk_ap(ap) else: avn = vn + if "bcasechk" in vn.flags and not vn.casechk(rem, True): + return self.tx_404() and False + ( self.can_read, self.can_write, @@ -709,9 +756,11 @@ class HttpCli(object): self.s.settimeout(self.args.s_tbody or None) if "norobots" in vn.flags: - self.html_head += META_NOBOTS self.out_headers["X-Robots-Tag"] = "noindex, nofollow" + if "html_head_s" in vn.flags: + self.html_head += vn.flags["html_head_s"] + try: cors_k = self._cors() if self.mode in ("GET", "HEAD"): @@ -748,7 +797,7 @@ class HttpCli(object): elif self.mode in ("MOVE", "COPY"): return self.handle_cpmv() and self.keepalive else: - raise Pebkac(400, 'invalid HTTP verb "{0}"'.format(self.mode)) + raise Pebkac(400, "invalid HTTP verb %r" % (self.mode,)) except Exception as ex: if not isinstance(ex, Pebkac): @@ -774,6 +823,15 @@ class HttpCli(object): 6 if em.startswith("client d/c ") else 3, ) + if self.hint and self.hint.startswith("<xml> "): + if self.args.log_badxml: + t = "invalid XML received from client: %r" + self.log(t % (self.hint[6:],), 6) + else: + t = "received invalid XML from client; enable --log-badxml to see the whole XML in the log" + self.log(t, 6) + self.hint = "" + msg = "%s\r\nURL: %s\r\n" % (em, self.vpath) if self.hint: msg += "hint: %s\r\n" % (self.hint,) @@ -873,8 +931,8 @@ class HttpCli(object): no304 = self.cookies.get("no304") return no304 == "y" or (self.args.no304 == 2 and no304 != "n") - def _build_html_head(self, maybe_html: Any, kv: dict[str, Any]) -> None: - html = str(maybe_html) + def _build_html_head(self, kv: dict[str, Any]) -> None: + html = str(self.vn.flags["html_head_d"]) is_jinja = html[:2] in "%@%" if is_jinja: html = html.replace("%", "", 1) @@ -1196,21 +1254,12 @@ class HttpCli(object): self.reply(b"ssdp is disabled in server config", 404) return False - if self.vpath.startswith(".cpr/dd/") and self.args.mpmc: - if self.args.mpmc == ".": - raise Pebkac(404) - - loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :] - h = {"Location": loc, "Cache-Control": "max-age=39"} - self.reply(b"", 301, headers=h) - return True - if self.vpath == ".cpr/metrics": return self.conn.hsrv.metrics.tx(self) res_path = "web/" + self.vpath[5:] if res_path in RES: - ap = os.path.join(self.E.mod, res_path) + ap = self.E.mod_ + res_path if bos.path.exists(ap) or bos.path.exists(ap + ".gz"): return self.tx_file(ap) else: @@ -1226,7 +1275,7 @@ class HttpCli(object): if not self.can_read and not self.can_write and not self.can_get: t = "@%s has no access to %r" - if "on403" in self.vn.flags: + if self.vn.realpath and "on403" in self.vn.flags: t += " (on403)" self.log(t % (self.uname, "/" + self.vpath)) ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem) @@ -1260,6 +1309,9 @@ class HttpCli(object): self.reply(html.encode("utf-8", "replace"), 500) return True + if "ls" in self.uparam: + return self.tx_ls_vols() + if self.vpath: ptn = self.args.nonsus_urls if not ptn or not ptn.search(self.vpath): @@ -1499,11 +1551,14 @@ class HttpCli(object): if not rbuf or len(buf) >= 32768: break - xroot = parse_xml(buf.decode(enc, "replace")) + sbuf = buf.decode(enc, "replace") + self.hint = "<xml> " + sbuf + xroot = parse_xml(sbuf) xtag = next((x for x in xroot if x.tag.split("}")[-1] == "prop"), None) if xtag is not None: props = set([y.tag.split("}")[-1] for y in xtag]) # assume <allprop/> otherwise; nobody ever gonna <propname/> + self.hint = "" zi = int(time.time()) vst = os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, zi, zi, zi)) @@ -1513,7 +1568,9 @@ class HttpCli(object): except OSError as ex: if ex.errno not in (errno.ENOENT, errno.ENOTDIR): raise - raise Pebkac(404) + if tap: + raise Pebkac(404) + st = vst topdir = {"vp": "", "st": st} fgen: Iterable[dict[str, Any]] = [] @@ -1553,6 +1610,9 @@ class HttpCli(object): ) elif depth == "0" or not stat.S_ISDIR(st.st_mode): + if depth == "0" and not self.vpath and not vn.realpath: + # rootless server; give dummy listing + self.can_read = True # propfind on a file; return as topdir if not self.can_read and not self.can_get: self.log("inaccessible: %r" % ("/" + self.vpath,)) @@ -1585,13 +1645,28 @@ class HttpCli(object): self.log("inaccessible: %r" % ("/" + self.vpath,)) raise Pebkac(401, "authenticate") - if "quota-available-bytes" in props and not self.args.nid: + zi = ( + vn.flags["du_iwho"] + if vn.realpath and "quota-available-bytes" in props + else 0 + ) + if zi and ( + zi == 9 + or (zi == 7 and self.uname != "*") + or (zi == 5 and self.can_write) + or (zi == 4 and self.can_write and self.can_read) + or (zi == 3 and self.can_admin) + ): bfree, btot, _ = get_df(vn.realpath, False) if btot: df = { "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: @@ -1701,6 +1776,7 @@ class HttpCli(object): uenc = enc.upper() txt = buf.decode(enc, "replace") + self.hint = "<xml> " + txt ET.register_namespace("D", "DAV:") xroot = mkenod("D:orz") xroot.insert(0, parse_xml(txt)) @@ -1708,6 +1784,7 @@ class HttpCli(object): assert xprop # !rm for ze in xprop: ze.clear() + self.hint = "" txt = """<multistatus xmlns="DAV:"><response><propstat><status>HTTP/1.1 403 Forbidden</status></propstat></response></multistatus>""" xroot = parse_xml(txt) @@ -1761,9 +1838,11 @@ class HttpCli(object): uenc = enc.upper() txt = buf.decode(enc, "replace") + self.hint = "<xml> " + txt ET.register_namespace("D", "DAV:") lk = parse_xml(txt) assert lk.tag == "{DAV:}lockinfo" + self.hint = "" token = str(uuid.uuid4()) @@ -1976,6 +2055,9 @@ class HttpCli(object): if "eshare" in self.uparam: return self.handle_eshare() + if "fs_abrt" in self.uparam: + return self.handle_fs_abrt() + if "application/octet-stream" in ctype: return self.handle_post_binary() @@ -2043,9 +2125,9 @@ class HttpCli(object): if "get" in opt: return self.handle_get() - raise Pebkac(405, "POST({}) is disabled in server config".format(ctype)) + raise Pebkac(405, "POST(%r) is disabled in server config" % (ctype,)) - raise Pebkac(405, "don't know how to handle POST({})".format(ctype)) + raise Pebkac(405, "don't know how to handle POST(%r)" % (ctype,)) def get_xml_enc(self, txt: str) -> str: ofs = txt[:512].find(' encoding="') @@ -2083,16 +2165,16 @@ class HttpCli(object): rnd, lifetime, xbu, xau = self.upload_flags(vfs) lim = vfs.get_dbv(rem)[0].lim fdir = vfs.canonical(rem) - if lim: - fdir, rem = lim.all( - self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker - ) - fn = None if rem and not self.trailing_slash and not bos.path.isdir(fdir): fdir, fn = os.path.split(fdir) rem, _ = vsplit(rem) + if lim: + fdir, rem = lim.all( + self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker + ) + bos.makedirs(fdir, vf=vfs.flags) open_ka: dict[str, Any] = {"fun": open} @@ -2187,8 +2269,10 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xbu server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" % (vp,) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -2220,7 +2304,11 @@ class HttpCli(object): if ( self.can_delete - and (vfs.flags.get("daw") or "x-oc-mtime" in self.headers) + and ( + vfs.flags.get("daw") + or "replace" in self.headers + or "x-oc-mtime" in self.headers + ) ) or ( not bos.path.exists(os.path.join(fdir, tnam)) and not bos.path.getsize(path) @@ -2273,12 +2361,7 @@ class HttpCli(object): at = mt = time.time() - lifetime cli_mt = self.headers.get("x-oc-mtime") if cli_mt: - try: - mt = int(cli_mt) - times = (int(time.time()), mt) - bos.utime(path, times, False) - except: - pass + bos.utime_c(self.log, path, int(cli_mt), False) if nameless and "magic" in vfs.flags: try: @@ -2321,8 +2404,10 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xau server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xau server config: %r" % (vp,) self.log(t, 1) wunlink(self.log, path, vfs.flags) raise Pebkac(403, t) @@ -2516,7 +2601,7 @@ class HttpCli(object): if act == "logout": return self.handle_logout() - raise Pebkac(422, 'invalid action "{}"'.format(act)) + raise Pebkac(422, "invalid action %r" % (act,)) def handle_zip_post(self) -> bool: assert self.parser # !rm @@ -2760,51 +2845,51 @@ class HttpCli(object): bail1 = False # used in sad path to avoid contradicting error-text treport = time.time() # ratelimit up2k reporting to reduce overhead - if "x-up2k-subc" in self.headers: - sc_ofs = int(self.headers["x-up2k-subc"]) - chash = chashes[0] - - u2sc = self.conn.hsrv.u2sc - try: - sc_pofs, hasher = u2sc[chash] - if not sc_ofs: - t = "client restarted the chunk; forgetting subchunk offset %d" - self.log(t % (sc_pofs,)) - raise Exception() - except: - sc_pofs = 0 - hasher = hashlib.sha512() - - et = "subchunk protocol error; resetting chunk " - if sc_pofs != sc_ofs: - u2sc.pop(chash, None) - t = "%s[%s]: the expected resume-point was %d, not %d" - raise Pebkac(400, t % (et, chash, sc_pofs, sc_ofs)) - if len(cstarts) > 1: - u2sc.pop(chash, None) - t = "%s[%s]: only a single subchunk can be uploaded in one request; you are sending %d chunks" - raise Pebkac(400, t % (et, chash, len(cstarts))) - csize = min(chunksize, fsize - cstart0[0]) - cstart0[0] += sc_ofs # also sets cstarts[0][0] - sc_next_ofs = sc_ofs + postsize - if sc_next_ofs > csize: - u2sc.pop(chash, None) - t = "%s[%s]: subchunk offset (%d) plus postsize (%d) exceeds chunksize (%d)" - raise Pebkac(400, t % (et, chash, sc_ofs, postsize, csize)) - else: - final_subchunk = sc_next_ofs == csize - t = "subchunk %s %d:%d/%d %s" - zs = "END" if final_subchunk else "" - self.log(t % (chash[:15], sc_ofs, sc_next_ofs, csize, zs), 6) - if final_subchunk: - u2sc.pop(chash, None) - else: - u2sc[chash] = (sc_next_ofs, hasher) - else: - hasher = None - final_subchunk = True - try: + if "x-up2k-subc" in self.headers: + sc_ofs = int(self.headers["x-up2k-subc"]) + chash = chashes[0] + + u2sc = self.conn.hsrv.u2sc + try: + sc_pofs, hasher = u2sc[chash] + if not sc_ofs: + t = "client restarted the chunk; forgetting subchunk offset %d" + self.log(t % (sc_pofs,)) + raise Exception() + except: + sc_pofs = 0 + hasher = hashlib.sha512() + + et = "subchunk protocol error; resetting chunk " + if sc_pofs != sc_ofs: + u2sc.pop(chash, None) + t = "%s[%s]: the expected resume-point was %d, not %d" + raise Pebkac(400, t % (et, chash, sc_pofs, sc_ofs)) + if len(cstarts) > 1: + u2sc.pop(chash, None) + t = "%s[%s]: only a single subchunk can be uploaded in one request; you are sending %d chunks" + raise Pebkac(400, t % (et, chash, len(cstarts))) + csize = min(chunksize, fsize - cstart0[0]) + cstart0[0] += sc_ofs # also sets cstarts[0][0] + sc_next_ofs = sc_ofs + postsize + if sc_next_ofs > csize: + u2sc.pop(chash, None) + t = "%s[%s]: subchunk offset (%d) plus postsize (%d) exceeds chunksize (%d)" + raise Pebkac(400, t % (et, chash, sc_ofs, postsize, csize)) + else: + final_subchunk = sc_next_ofs == csize + t = "subchunk %s %d:%d/%d %s" + zs = "END" if final_subchunk else "" + self.log(t % (chash[:15], sc_ofs, sc_next_ofs, csize, zs), 6) + if final_subchunk: + u2sc.pop(chash, None) + else: + u2sc[chash] = (sc_next_ofs, hasher) + else: + hasher = None + final_subchunk = True + if self.args.nw: path = os.devnull @@ -2876,7 +2961,9 @@ class HttpCli(object): if now - treport < 1: continue treport = now - x = broker.ask("up2k.fast_confirm_chunks", ptop, wark, written) + x = broker.ask( + "up2k.fast_confirm_chunks", ptop, wark, written, locked + ) num_left, t = x.get() if num_left < -1: self.loud_reply(t, status=500) @@ -2930,12 +3017,16 @@ class HttpCli(object): def handle_chpw(self) -> bool: assert self.parser # !rm + if self.args.usernames: + self.parser.require("uname", 64) pwd = self.parser.require("pw", 64) self.parser.drop() ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd) if ok: self.cbonk(self.conn.hsrv.gpwc, pwd, "pw", "too many password changes") + if self.args.usernames: + pwd = "%s:%s" % (self.uname, pwd) ok, msg = self.get_pwd_cookie(pwd) if ok: msg = "new password OK" @@ -2948,6 +3039,15 @@ class HttpCli(object): def handle_login(self) -> bool: assert self.parser # !rm + if self.args.usernames and not ( + self.args.shr and self.vpath.startswith(self.args.shr1) + ): + try: + un = self.parser.require("uname", 64) + except: + un = "" + else: + un = "" pwd = self.parser.require("cppwd", 64) try: uhash = self.parser.require("uhash", 256) @@ -2958,6 +3058,9 @@ class HttpCli(object): if not pwd: raise Pebkac(422, "password cannot be blank") + if un: + pwd = "%s:%s" % (un, pwd) + dst = self.args.SRS if self.vpath: dst += quotep(self.vpaths) @@ -2984,7 +3087,7 @@ class HttpCli(object): self.asrv.forget_session(self.conn.hsrv.broker, self.uname) self.get_pwd_cookie("x") - dst = self.args.SRS + "?h" + dst = self.args.idp_logout or (self.args.SRS + "?h") h2 = '<a href="' + dst + '">continue</a>' html = self.j2s("msg", h1="ok bye", h2=h2, redir=dst) self.reply(html.encode("utf-8")) @@ -2997,6 +3100,11 @@ class HttpCli(object): uname = self.asrv.iacct.get(hpwd) if uname: pwd = self.asrv.ases.get(uname) or pwd + if uname and self.conn.hsrv.ipr: + znm = self.conn.hsrv.ipr.get(uname) + if znm and not znm.map(self.ip): + self.log("username [%s] rejected by --ipr" % (self.uname,), 3) + uname = "" if uname: msg = "hi " + uname dur = int(60 * 60 * self.args.logout) @@ -3038,6 +3146,19 @@ class HttpCli(object): return dur > 0, msg + def set_idp_cookie(self, ases) -> None: + k = "cppws" if self.is_https else "cppwd" + ck = gencookie( + k, + ases, + self.args.R, + self.args.cookie_lax, + self.is_https, + self.args.idp_cookie, + "; HttpOnly", + ) + self.out_headers["Set-Cookie"] = ck + def handle_mkdir(self) -> bool: assert self.parser # !rm new_dir = self.parser.require("name", 512) @@ -3098,11 +3219,38 @@ class HttpCli(object): new_file += ".md" sanitized = sanitize_fn(new_file, "") + fdir = vfs.canonical(rem) + fn = os.path.join(fdir, sanitized) + + for hn in ("xbu", "xau"): + xxu = vfs.flags.get(hn) + if xxu: + hr = runhook( + self.log, + self.conn.hsrv.broker, + None, + "%s.http.new-md" % (hn,), + xxu, + fn, + vjoin(self.vpath, sanitized), + self.host, + self.uname, + self.asrv.vfs.get_perms(self.vpath, self.uname), + time.time(), + 0, + self.ip, + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "new-md blocked by " + hn + " server config: %r" + t = t % (vjoin(vfs.vpath, rem),) + self.log(t, 1) + raise Pebkac(403, t) if not nullwrite: - fdir = vfs.canonical(rem) - fn = os.path.join(fdir, sanitized) - if bos.path.exists(fn): raise Pebkac(500, "that file exists already") @@ -3232,7 +3380,7 @@ class HttpCli(object): open_args = {"fdir": fdir, "suffix": suffix, "vf": vfs.flags} - if "replace" in self.uparam: + if "replace" in self.uparam or "replace" in self.headers: if not self.can_delete: self.log("user not allowed to overwrite with ?replace") elif bos.path.exists(abspath): @@ -3266,8 +3414,11 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xbu server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" + t = t % (vjoin(upload_vpath, fname),) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -3327,8 +3478,6 @@ class HttpCli(object): sz, sha_hex, sha_b64 = copier( p_data, f, hasher, max_sz, self.args.s_wr_slp ) - if sz == 0: - raise Pebkac(400, "empty files in post") finally: f.close() @@ -3372,8 +3521,11 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xau server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xau server config: %r" + t = t % (vjoin(upload_vpath, fname),) self.log(t, 1) wunlink(self.log, abspath, vfs.flags) raise Pebkac(403, t) @@ -3576,7 +3728,7 @@ class HttpCli(object): rem = "{}/{}".format(rp, fn).strip("/") dbv, vrem = vfs.get_dbv(rem) - if not rem.endswith(".md") and not self.can_delete: + if not rem.lower().endswith(".md") and not self.can_delete: raise Pebkac(400, "only markdown pls") if nullwrite: @@ -3658,11 +3810,14 @@ class HttpCli(object): assert self.parser.gen # !rm p_field, _, p_data = next(self.parser.gen) if p_field != "body": - raise Pebkac(400, "expected body, got {}".format(p_field)) + raise Pebkac(400, "expected body, got %r" % (p_field,)) + + if "txt_eol" in vfs.flags: + p_data = eol_conv(p_data, vfs.flags["txt_eol"]) xbu = vfs.flags.get("xbu") if xbu: - if not runhook( + hr = runhook( self.log, self.conn.hsrv.broker, None, @@ -3678,8 +3833,11 @@ class HttpCli(object): self.ip, time.time(), "", - ): - t = "save blocked by xbu server config" + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "save blocked by xbu server config" self.log(t, 1) raise Pebkac(403, t) @@ -3706,27 +3864,31 @@ class HttpCli(object): sha512 = sha512[:56] xau = vfs.flags.get("xau") - if xau and not runhook( - self.log, - self.conn.hsrv.broker, - None, - "xau.http.txt", - xau, - fp, - self.vpath, - self.host, - self.uname, - self.asrv.vfs.get_perms(self.vpath, self.uname), - new_lastmod, - sz, - self.ip, - new_lastmod, - "", - ): - t = "save blocked by xau server config" - self.log(t, 1) - wunlink(self.log, fp, vfs.flags) - raise Pebkac(403, t) + if xau: + hr = runhook( + self.log, + self.conn.hsrv.broker, + None, + "xau.http.txt", + xau, + fp, + self.vpath, + self.host, + self.uname, + self.asrv.vfs.get_perms(self.vpath, self.uname), + new_lastmod, + sz, + self.ip, + new_lastmod, + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "save blocked by xau server config" + self.log(t, 1) + wunlink(self.log, fp, vfs.flags) + raise Pebkac(403, t) self.conn.hsrv.broker.say( "up2k.hash_file", @@ -3943,6 +4105,13 @@ class HttpCli(object): if not editions: return self.tx_404() + # + # force download + + if "dl" in self.ouparam: + cdis = gen_content_disposition(os.path.basename(req_path)) + self.out_headers["Content-Disposition"] = cdis + # # if-modified @@ -4111,6 +4280,13 @@ class HttpCli(object): if not editions: return self.tx_404() + # + # force download + + if "dl" in self.ouparam: + cdis = gen_content_disposition(os.path.basename(req_path)) + self.out_headers["Content-Disposition"] = cdis + # # if-modified @@ -4624,13 +4800,13 @@ class HttpCli(object): packer = StreamZip ext = "zip" - fn = items[0] if items and items[0] else self.vpath - if fn: - fn = fn.rstrip("/").split("/")[-1] - else: - fn = self.host.split(":")[0] + fn = self.vpath.split("/")[-1] or self.host.split(":")[0] + if items: + fn = "sel-" + fn - if vn.flags.get("zipmax") and (not self.uname or not "zipmaxu" in vn.flags): + if vn.flags.get("zipmax") and not ( + vn.flags.get("zipmaxu") and self.uname != "*" + ): maxs = vn.flags.get("zipmaxs_v") or 0 maxn = vn.flags.get("zipmaxn_v") or 0 nf = 0 @@ -4657,24 +4833,7 @@ class HttpCli(object): if maxn < nf: raise Pebkac(400, t) - safe = (string.ascii_letters + string.digits).replace("%", "") - afn = "".join([x if x in safe.replace('"', "") else "_" for x in fn]) - bascii = unicode(safe).encode("utf-8") - zb = fn.encode("utf-8", "xmlcharrefreplace") - if not PY2: - zbl = [ - chr(x).encode("utf-8") - if x in bascii - else "%{:02x}".format(x).encode("ascii") - for x in zb - ] - else: - zbl = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in zb] - - ufn = b"".join(zbl).decode("ascii") - - cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}" - cdis = cdis.format(afn, ext, ufn, ext) + cdis = gen_content_disposition("%s.%s" % (fn, ext)) self.log(repr(cdis)) self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis}) @@ -4684,7 +4843,7 @@ class HttpCli(object): # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]})) cfmt = "" if self.thumbcli and not self.args.no_bacode: - for zs in ("opus", "mp3", "w", "j", "p"): + for zs in ("opus", "mp3", "flac", "wav", "w", "j", "p"): if zs in self.ouparam or uarg == zs: cfmt = zs @@ -4819,8 +4978,8 @@ class HttpCli(object): else: fullfile = b"" - if not sz_md and b"\n" in buf[:2]: - lead = buf[: buf.find(b"\n") + 1] + if not sz_md and buf.startswith((b"\n", b"\r\n")): + lead = b"\n" if buf.startswith(b"\n") else b"\r\n" sz_md += len(lead) sz_md += len(buf) @@ -4861,7 +5020,8 @@ class HttpCli(object): "lastmod": int(ts_md * 1000), "lang": self.args.lang, "favico": self.args.favico, - "have_emp": self.args.emp, + "have_emp": int(self.args.emp), + "md_no_br": int(vn.flags.get("md_no_br") or 0), "md_chk_rate": self.args.mcr, "md": boundary, "arg_base": arg_base, @@ -4872,10 +5032,9 @@ class HttpCli(object): zs += "&" if "?" in zs else "?" targs["js"] = zs - zfv = self.vn.flags.get("html_head") - if zfv: + if "html_head_d" in self.vn.flags: targs["this"] = self - self._build_html_head(zfv, targs) + self._build_html_head(targs) targs["html_head"] = self.html_head zs = template.render(**targs).encode("utf-8", "replace") @@ -4929,10 +5088,20 @@ class HttpCli(object): else: rip = host + defpw = "dave:hunter2" if self.args.usernames else "hunter2" + vp = (self.uparam["hc"] or "").lstrip("/") - pw = self.pw or "hunter2" + pw = self.ouparam.get("pw") or defpw if pw in self.asrv.sesa: - pw = "hunter2" + pw = defpw + + unpw = pw + try: + un, pw = unpw.split(":") + except: + un = "" + if self.args.usernames: + un = "dave" html = self.j2s( "svcs", @@ -4946,7 +5115,10 @@ class HttpCli(object): host=html_sh_esc(host), hport=html_sh_esc(hport), aname=aname, + b_un=("<b>%s</b>" % (html_sh_esc(un),)) if un else "k", + un=html_sh_esc(un), pw=html_sh_esc(pw), + unpw=html_sh_esc(unpw), ) self.reply(html.encode("utf-8")) return True @@ -5024,7 +5196,7 @@ class HttpCli(object): wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags] fmt = self.uparam.get("ls", "") - if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")): + if not fmt and self.ua.startswith(("curl/", "fetch")): fmt = "v" if fmt in ["v", "t", "txt"]: @@ -5064,6 +5236,18 @@ class HttpCli(object): self.reply(zb, mime="text/plain; charset=utf-8") return True + re_btn = "" + nre = self.args.ctl_re + if "re" in self.uparam: + self.out_headers["Refresh"] = str(nre) + elif nre: + re_btn = "&re=%s" % (nre,) + + zi = self.args.ver_iwho + show_ver = zi and ( + zi == 9 or (zi == 6 and self.uname != "*") or (zi == 3 and avol) + ) + html = self.j2s( "splash", this=self, @@ -5081,11 +5265,12 @@ class HttpCli(object): mtpq=vs["mtpq"], dbwt=vs["dbwt"], url_suf=suf, + re=re_btn, k304=self.k304(), no304=self.no304(), k304vis=self.args.k304 > 0, no304vis=self.args.no304 > 0, - ver=S_VERSION if self.args.ver else "", + ver=S_VERSION if show_ver else "", chpw=self.args.chpw and self.uname != "*", ahttps="" if self.is_https else "https://" + self.host + self.req, ) @@ -5126,7 +5311,7 @@ class HttpCli(object): t = '<h1 id="n">404 not found  ┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>' pt = "404 not found ┐( ´ -`)┌" - if self.ua.startswith("curl/") or self.ua.startswith("fetch"): + if self.ua.startswith(("curl/", "fetch")): pt = "# acct: %s\n%s\n" % (self.uname, pt) self.reply(pt.encode("utf-8"), status=rc) return True @@ -5300,8 +5485,9 @@ class HttpCli(object): if dk_sz and fsroot: kdirs = [] + fsroot_ = os.path.join(fsroot, "") for dn in dirs: - ap = os.path.join(fsroot, dn) + ap = fsroot_ + dn zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_sz] kdirs.append(dn + "?k=" + zs) dirs = kdirs @@ -5421,6 +5607,10 @@ class HttpCli(object): and ("*" in x.axs.uwrite or self.uname in x.axs.uwrite or x == shr_dbv) ] + q = "" + qp = (0,) + q_c = -1 + for vol in allvols: cur = idx.get_cur(vol) if not cur: @@ -5428,18 +5618,34 @@ class HttpCli(object): nfk, fk_alg = fk_vols.get(vol) or (0, 0) + zi = vol.flags["unp_who"] + if q_c != zi: + q_c = zi + q = "select sz, rd, fn, at from up where " + if zi == 1: + q += "ip=? and un=?" + qp = (self.ip, self.uname, lim) + elif zi == 2: + q += "ip=?" + qp = (self.ip, lim) + if zi == 3: + q += "un=?" + qp = (self.uname, lim) + q += " and at>? order by at desc" + n = 2000 - q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc" - for sz, rd, fn, at in cur.execute(q, (self.ip, lim)): + for sz, rd, fn, at in cur.execute(q, qp): vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x) - if nfi == 0 or (nfi == 1 and vfi in vp): + if nfi == 0 or (nfi == 1 and vfi in vp.lower()): pass elif nfi == 2: - if not vp.startswith(vfi): + if not vp.lower().startswith(vfi): continue elif nfi == 3: - if not vp.endswith(vfi): + if not vp.lower().endswith(vfi): continue + else: + continue n -= 1 if not n: @@ -5553,17 +5759,19 @@ class HttpCli(object): continue n = 1000 - q = "select sz, rd, fn, ip, at from up where at>0 order by at desc" - for sz, rd, fn, ip, at in cur.execute(q): + q = "select sz, rd, fn, ip, at, un from up where at>0 order by at desc" + for sz, rd, fn, ip, at, un in cur.execute(q): vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x) - if nfi == 0 or (nfi == 1 and vfi in vp): + if nfi == 0 or (nfi == 1 and vfi in vp.lower()): pass elif nfi == 2: - if not vp.startswith(vfi): + if not vp.lower().startswith(vfi): continue elif nfi == 3: - if not vp.endswith(vfi): + if not vp.lower().endswith(vfi): continue + else: + continue if not dots and "/." in vp: continue @@ -5573,6 +5781,7 @@ class HttpCli(object): "sz": sz, "ip": ip, "at": at, + "un": un, "nfk": nfk, "adm": adm, } @@ -5617,12 +5826,16 @@ class HttpCli(object): adm = rv.pop("adm") if not adm: rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)" + if rv["un"] not in ("*", self.uname): + rv["un"] = "(?)" else: for rv in ret: adm = rv.pop("adm") if not adm: rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)" rv["at"] = 0 + if rv["un"] not in ("*", self.uname): + rv["un"] = "(?)" if self.is_vproxied: for v in ret: @@ -5772,7 +5985,7 @@ class HttpCli(object): vp2, fn = zs.rsplit("/", 1) fns.append(fn) if vp != vp2: - t = "mismatching base paths in selection:\n [%s]\n [%s]" + t = "mismatching base paths in selection:\n %r\n %r" raise Pebkac(400, t % (vp, vp2)) vp = vp.strip("/") @@ -5781,7 +5994,7 @@ class HttpCli(object): m = re.search(r"([^0-9a-zA-Z_-])", skey) if m: - raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],)) + raise Pebkac(400, "sharekey has illegal character %r" % (m[1],)) if vp.startswith(self.args.shr1): raise Pebkac(400, "yo dawg...") @@ -5794,7 +6007,7 @@ class HttpCli(object): qr = cur.execute(q, (skey,)).fetchall() if qr and qr[0]: self.log("sharekey taken by %r" % (qr,)) - raise Pebkac(400, "sharekey [%s] is already in use" % (skey,)) + raise Pebkac(400, "sharekey %r is already in use" % (skey,)) # ensure user has requested perms s_rd = "read" in req["perms"] @@ -5806,13 +6019,21 @@ class HttpCli(object): except: raise Pebkac(400, "you dont have all the perms you tried to grant") + zs = vfs.flags["shr_who"] + if zs == "auth" and self.uname != "*": + pass + elif zs == "a" and self.uname in vfs.axs.uadmin: + pass + else: + raise Pebkac(400, "you dont have perms to create shares from this volume") + ap, reals, _ = vfs.ls( rem, self.uname, not self.args.no_scandir, [[s_rd, s_wr, s_mv, s_del]] ) rfns = set([x[0] for x in reals]) for fn in fns: if fn not in rfns: - raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,)) + raise Pebkac(400, "selected file not found on disk: %r" % (fn,)) pw = req.get("pw") or "" pw = self.asrv.ah.hash(pw) @@ -5901,7 +6122,9 @@ class HttpCli(object): self.asrv.vfs.get(vdst, self.uname, False, True, False, True) wunlink(self.log, dabs, dvn.flags) - x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst) + x = self.conn.hsrv.broker.ask( + "up2k.handle_mv", self.ouparam.get("akey"), self.uname, self.ip, vsrc, vdst + ) self.loud_reply(x.get(), status=201) return True @@ -5931,10 +6154,62 @@ class HttpCli(object): self.asrv.vfs.get(vdst, self.uname, False, True, False, True) wunlink(self.log, dabs, dvn.flags) - x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst) + x = self.conn.hsrv.broker.ask( + "up2k.handle_cp", self.ouparam.get("akey"), self.uname, self.ip, vsrc, vdst + ) self.loud_reply(x.get(), status=201) return True + def handle_fs_abrt(self): + if self.args.no_fs_abrt: + t = "aborting an ongoing copy/move is disabled in server config" + raise Pebkac(403, t) + + self.conn.hsrv.broker.say("up2k.handle_fs_abrt", self.uparam["fs_abrt"]) + self.loud_reply("aborting", status=200) + return True + + def tx_ls_vols(self) -> bool: + e_d = {} + eses = ["", ""] + rvol = self.rvol + wvol = self.wvol + if self.args.have_unlistc: + allvols = self.asrv.vfs.all_vols + 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] + vols = 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] + if not vols and self.vpath: + return self.tx_404(True) + dirs = [ + { + "lead": "", + "href": "%s/" % (x,), + "ext": "---", + "sz": 0, + "ts": 0, + "tags": e_d, + "dt": 0, + "name": 0, + } + for x in sorted(vols) + ] + ls = { + "dirs": dirs, + "files": [], + "acct": self.uname, + "perms": [], + "taglist": [], + "logues": eses, + "readmes": eses, + "srvinf": "" if self.args.nih else self.args.name, + } + return self.tx_ls(ls) + def tx_ls(self, ls: dict[str, Any]) -> bool: dirs = ls["dirs"] files = ls["files"] @@ -5994,6 +6269,12 @@ class HttpCli(object): else: [x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y] + # nonce (tlnote: norwegian for flake as in snowflake) + if self.args.no_fnugg: + ls["fnugg"] = "nei" + elif "fnugg" in self.headers: + ls["fnugg"] = self.headers["fnugg"] + ret = json.dumps(ls) mime = "application/json" @@ -6047,17 +6328,15 @@ class HttpCli(object): add_og = "og" in vn.flags if add_og: - if "th" in self.uparam or "raw" in self.uparam: - og_ua = add_og = False - elif self.args.og_ua: - og_ua = add_og = self.args.og_ua.search(self.ua) - else: - og_ua = False - add_og = True + if "th" in self.uparam or "raw" in self.uparam or "opds" in self.uparam: + add_og = False + elif vn.flags["og_ua"]: + add_og = vn.flags["og_ua"].search(self.ua) og_fn = "" if "v" in self.uparam: - add_og = og_ua = True + add_og = True + og_fn = "" if "b" in self.uparam: self.out_headers["X-Robots-Tag"] = "noindex, nofollow" @@ -6176,8 +6455,9 @@ class HttpCli(object): if not use_filekey: return self.tx_404(True) - if add_og and not abspath.lower().endswith(".md"): - if og_ua or self.host not in self.headers.get("referer", ""): + is_md = abspath.lower().endswith(".md") + if add_og and not is_md: + if self.host not in self.headers.get("referer", ""): self.vpath, og_fn = vsplit(self.vpath) vpath = self.vpath vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False) @@ -6188,10 +6468,10 @@ class HttpCli(object): vpnodes.pop() if ( - (abspath.endswith(".md") or self.can_delete) + (is_md or self.can_delete) and "nohtml" not in vn.flags and ( - ("v" in self.uparam and abspath.endswith(".md")) + (is_md and "v" in self.uparam) or "edit" in self.uparam or "edit2" in self.uparam ) @@ -6217,7 +6497,14 @@ class HttpCli(object): except: self.log("#wow #whoa") - if not self.args.nid: + zi = vn.flags["du_iwho"] + if zi and ( + zi == 9 + or (zi == 7 and self.uname != "*") + or (zi == 5 and self.can_write) + or (zi == 4 and self.can_write and self.can_read) + or (zi == 3 and self.can_admin) + ): free, total, zs = get_df(abspath, False) if total: h1 = humansize(free or 0) @@ -6246,13 +6533,10 @@ class HttpCli(object): url_suf = self.urlq({}, ["k"]) is_ls = "ls" in self.uparam + is_opds = "opds" in self.uparam is_js = self.args.force_js or self.cookies.get("js") == "y" - if ( - not is_ls - and not add_og - and (self.ua.startswith("curl/") or self.ua.startswith("fetch")) - ): + if not is_ls and not add_og and self.ua.startswith(("curl/", "fetch")): self.uparam["ls"] = "v" is_ls = True @@ -6260,6 +6544,13 @@ class HttpCli(object): if "b" in self.uparam: tpl = "browser2" is_js = False + elif is_opds: + # Display directory listing as OPDS v1.2 catalog feed + if not (self.args.opds or "opds" in self.vn.flags): + raise Pebkac(405, "OPDS is disabled in server config") + if not self.can_read: + raise Pebkac(401, "OPDS requires read permission") + is_js = is_ls = False vf = vn.flags ls_ret = { @@ -6389,10 +6680,17 @@ class HttpCli(object): dirs = [] files = [] ptn_hr = RE_HR + use_abs_url = ( + not is_opds + and not is_ls + and not is_js + and not self.trailing_slash + and vpath + ) for fn in ls_names: base = "" href = fn - if not is_ls and not is_js and not self.trailing_slash and vpath: + if use_abs_url: base = "/" + vpath + "/" href = base + fn @@ -6492,6 +6790,7 @@ class HttpCli(object): self.cookies.get("idxh") == "y" and "ls" not in self.uparam and "v" not in self.uparam + and not is_opds ): idx_html = set(["index.htm", "index.html"]) for item in files: @@ -6502,60 +6801,63 @@ class HttpCli(object): ap = vn.canonical(rem) return self.tx_file(ap) # is no-cache - mte = vn.flags.get("mte", {}) - add_up_at = ".up_at" in mte - is_admin = self.can_admin - tagset: set[str] = set() - rd = vrem - for fe in files if icur else []: - assert icur # !rm - fn = fe["name"] - erd_efn = (rd, fn) - q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'" - try: - r = icur.execute(q, erd_efn) - except Exception as ex: - if "database is locked" in str(ex): - break - - try: - erd_efn = s3enc(idx.mem_cur, rd, fn) - r = icur.execute(q, erd_efn) - except: - self.log("tag read error, %r / %r\n%s" % (rd, fn, min_ex())) - break - - tags = {k: v for k, v in r} - - if is_admin: - q = "select ip, at from up where rd=? and fn=?" - try: - zs1, zs2 = icur.execute(q, erd_efn).fetchone() - if zs1: - tags["up_ip"] = zs1 - if zs2: - tags[".up_at"] = zs2 - except: - pass - elif add_up_at: - q = "select at from up where rd=? and fn=?" - try: - (zs1,) = icur.execute(q, erd_efn).fetchone() - if zs1: - tags[".up_at"] = zs1 - except: - pass - - _ = [tagset.add(k) for k in tags] - fe["tags"] = tags - if icur: + mte = vn.flags.get("mte") or {} + tagset: set[str] = set() + rd = vrem + if self.can_admin: + up_q = "select substr(w,1,16), ip, at, un from up where rd=? and fn=?" + up_m = ["w", "up_ip", ".up_at", "up_by"] + elif ".up_at" in mte: + if "w" in mte: + up_q = "select substr(w,1,16), at from up where rd=? and fn=?" + up_m = ["w", ".up_at"] + else: + up_q = "select at from up where rd=? and fn=?" + up_m = [".up_at"] + elif "w" in mte: + up_q = "select substr(w,1,16) from up where rd=? and fn=?" + up_m = ["w"] + else: + up_q = "" + + mt_q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'" + for fe in files: + fn = fe["name"] + erd_efn = (rd, fn) + try: + r = icur.execute(mt_q, erd_efn) + except Exception as ex: + if "database is locked" in str(ex): + break + + try: + erd_efn = s3enc(idx.mem_cur, rd, fn) + r = icur.execute(mt_q, erd_efn) + except: + self.log("tag read error, %r / %r\n%s" % (rd, fn, min_ex())) + break + + tags = {k: v for k, v in r} + + if up_q: + try: + up_v = icur.execute(up_q, erd_efn).fetchone() + for zs1, zs2 in zip(up_m, up_v): + if zs2: + tags[zs1] = zs2 + except: + pass + + _ = [tagset.add(k) for k in tags] + fe["tags"] = tags + for fe in dirs: fe["tags"] = ODict() lmte = list(mte) if self.can_admin: - lmte.extend(("up_ip", ".up_at")) + lmte.extend(("w", "up_by", "up_ip", ".up_at")) if "nodirsz" not in vf: tagset.add(".files") @@ -6570,7 +6872,7 @@ class HttpCli(object): taglist = [k for k in lmte if k in tagset] else: - taglist = list(tagset) + taglist = [] logues, readmes = self._add_logues(vn, abspath, lnames) ls_ret["logues"] = j2a["logues"] = logues @@ -6658,6 +6960,48 @@ class HttpCli(object): dirs.sort(key=itemgetter("name")) + if is_opds: + # exclude files which don't match --opds-exts + allowed_exts = vf.get("opds_exts") or self.args.opds_exts + if allowed_exts: + files = [ + x for x in files if x["name"].rsplit(".", 1)[-1] in allowed_exts + ] + for item in dirs: + href = item["href"] + href += ("&" if "?" in href else "?") + "opds" + item["href"] = href + item["iso8601"] = "%sZ" % (item["dt"].replace(" ", "T"),) + + for item in files: + href = item["href"] + href += ("&" if "?" in href else "?") + "dl" + item["href"] = href + item["iso8601"] = "%sZ" % (item["dt"].replace(" ", "T"),) + + if "rmagic" in self.vn.flags: + ap = "%s/%s" % (fsroot, item["name"]) + item["mime"] = guess_mime(item["name"], ap) + else: + item["mime"] = guess_mime(item["name"]) + + # Make sure we can actually generate JPEG thumbnails + if ( + not self.args.th_no_jpg + and self.thumbcli + and "dthumb" not in dbv.flags + and "dithumb" not in dbv.flags + ): + item["jpeg_thumb_href"] = href + "&th=jf" + item["jpeg_thumb_href_hires"] = item["jpeg_thumb_href"] + "3" + + j2a["files"] = files + j2a["dirs"] = dirs + html = self.j2s("opds", **j2a) + mime = "application/atom+xml;profile=opds-catalog" + self.reply(html.encode("utf-8", "replace"), mime=mime) + return True + if is_js: j2a["ls0"] = cgv["ls0"] = { "dirs": dirs, @@ -6757,7 +7101,15 @@ class HttpCli(object): if zs not in ("", "-"): ogh["og:site_name"] = zs + try: + zs1, zs2 = file["tags"]["res"].split("x") + file["tags"][".resw"] = zs1 + file["tags"][".resh"] = zs2 + except: + pass + tagmap = {} + if is_au: title = str(vn.flags.get("og_title_a") or "") ogh["og:type"] = "music.song" @@ -6771,15 +7123,23 @@ class HttpCli(object): title = str(vn.flags.get("og_title_v") or "") ogh["og:type"] = "video.other" ogh["og:video"] = j2a["og_raw"] + tagmap = { "title": "og:title", ".dur": "og:video:duration", + ".resw": "og:video:width", + ".resh": "og:video:height", } elif is_pic: title = str(vn.flags.get("og_title_i") or "") ogh["twitter:card"] = "summary_large_image" ogh["twitter:image"] = ogh["og:image"] = j2a["og_raw"] + tagmap = { + ".resw": "og:image:width", + ".resh": "og:image:height", + } + try: for k, v in file["tags"].items(): zs = "{{ %s }}" % (k,) diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 56638003..da41fbca 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -70,6 +70,7 @@ from .util import ( build_netmap, has_resource, ipnorm, + load_ipr, load_ipu, load_resource, min_ex, @@ -186,6 +187,7 @@ class HttpSrv(object): "svcs", ] self.j2 = {x: env.get_template(x + ".html") for x in jn} + self.j2["opds"] = env.get_template("opds.xml") self.prism = has_resource(self.E, "web/deps/prism.js.gz") if self.args.ipu: @@ -193,6 +195,11 @@ class HttpSrv(object): else: self.ipu_iu = self.ipu_nm = None + if self.args.ipr: + self.ipr = load_ipr(self.log, self.args.ipr) + else: + self.ipr = None + self.ipa_nm = build_netmap(self.args.ipa) self.xff_nm = build_netmap(self.args.xff_src) self.xff_lan = build_netmap("lan") @@ -324,7 +331,8 @@ class HttpSrv(object): spins = 0 while self.ncli >= self.nclimax: if not spins: - self.log(self.name, "at connection limit; waiting", 3) + t = "at connection limit (global-option 'nc'); waiting" + self.log(self.name, t, 3) spins += 1 time.sleep(0.1) @@ -564,7 +572,7 @@ class HttpSrv(object): v = self.E.t0 try: - with os.scandir(os.path.join(self.E.mod, "web")) as dh: + with os.scandir(self.E.mod_ + "web") as dh: for fh in dh: inf = fh.stat() v = max(v, inf.st_mtime) diff --git a/copyparty/mdns.py b/copyparty/mdns.py index a9d36fe7..fc429aad 100644 --- a/copyparty/mdns.py +++ b/copyparty/mdns.py @@ -12,7 +12,9 @@ from ipaddress import IPv4Network, IPv6Network from .__init__ import TYPE_CHECKING from .__init__ import unicode as U from .multicast import MC_Sck, MCast -from .stolen.dnslib import AAAA +from .stolen.dnslib import ( + AAAA, +) from .stolen.dnslib import CLASS as DC from .stolen.dnslib import ( NSEC, @@ -27,7 +29,7 @@ from .stolen.dnslib import ( DNSRecord, set_avahi_379, ) -from .util import CachedSet, Daemon, Netdev, list_ips, min_ex +from .util import IP6_LL, CachedSet, Daemon, Netdev, list_ips, min_ex if TYPE_CHECKING: from .svchub import SvcHub @@ -76,7 +78,8 @@ class MDNS(MCast): if not self.args.zm_nwa_1: set_avahi_379() - zs = self.args.name + ".local." + zs = self.args.zm_fqdn or (self.args.name + ".local") + zs = zs.replace("--name", self.args.name).rstrip(".") + "." zs = zs.encode("ascii", "replace").decode("ascii", "replace") self.hn = "-".join(x for x in zs.split("?") if x) or ( "vault-{}".format(random.randint(1, 255)) @@ -99,9 +102,14 @@ class MDNS(MCast): self.log_func(self.logsrc, msg, c) def build_svcs(self) -> tuple[dict[str, dict[str, Any]], set[str]]: + ar = self.args zms = self.args.zms - http = {"port": 80 if 80 in self.args.p else self.args.p[0]} - https = {"port": 443 if 443 in self.args.p else self.args.p[0]} + + zi = ar.zm_http + http = {"port": zi if zi != -1 else 80 if 80 in ar.p else ar.p[0]} + zi = ar.zm_https + https = {"port": zi if zi != -1 else 443 if 443 in ar.p else ar.p[0]} + webdav = http.copy() webdavs = https.copy() webdav["u"] = webdavs["u"] = "u" # KDE requires username @@ -126,16 +134,16 @@ class MDNS(MCast): svcs: dict[str, dict[str, Any]] = {} - if "d" in zms: + if "d" in zms and http["port"]: svcs["_webdav._tcp.local."] = webdav - if "D" in zms: + if "D" in zms and https["port"]: svcs["_webdavs._tcp.local."] = webdavs - if "h" in zms: + if "h" in zms and http["port"]: svcs["_http._tcp.local."] = http - if "H" in zms: + if "H" in zms and https["port"]: svcs["_https._tcp.local."] = https if "f" in zms.lower(): @@ -374,7 +382,7 @@ class MDNS(MCast): cip = addr[0] v6 = ":" in cip if (cip.startswith("169.254") and not self.ll_ok) or ( - v6 and not cip.startswith("fe80") + v6 and not cip.startswith(IP6_LL) ): return diff --git a/copyparty/mtag.py b/copyparty/mtag.py index cacfda39..e006b688 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -29,7 +29,7 @@ from .util import ( ) if True: # pylint: disable=using-constant-test - from typing import Any, Optional, Union + from typing import IO, Any, Optional, Union from .util import NamedLogger, RootLogger @@ -67,6 +67,8 @@ HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe") CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split()) CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b") +FMT_AU = set("mp3 ogg flac wav".split()) + class MParser(object): def __init__(self, cmdline: str) -> None: @@ -174,6 +176,10 @@ def au_unpk( raise Exception("no images inside cbz") fi = zf.open(using) + elif pk == "epub": + fi = get_cover_from_epub(log, abspath) + assert fi # !rm + else: raise Exception("unknown compression %s" % (pk,)) @@ -203,7 +209,7 @@ def au_unpk( def ffprobe( abspath: str, timeout: int = 60 -) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]]]: +) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]: cmd = [ b"ffprobe", b"-hide_banner", @@ -217,8 +223,17 @@ def ffprobe( return parse_ffprobe(so) -def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]]]: - """ffprobe -show_format -show_streams""" +def parse_ffprobe( + txt: str, +) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]: + """ + txt: output from ffprobe -show_format -show_streams + returns: + * normalized tags + * original/raw tags + * list of streams + * format props + """ streams = [] fmt = {} g = {} @@ -242,7 +257,7 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[ ret: dict[str, Any] = {} # processed md: dict[str, list[Any]] = {} # raw tags - is_audio = fmt.get("format_name") in ["mp3", "ogg", "flac", "wav"] + is_audio = fmt.get("format_name") in FMT_AU if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]: is_audio = True @@ -270,6 +285,8 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[ ["channel_layout", "chs"], ["sample_rate", ".hz"], ["bit_rate", ".aq"], + ["bits_per_sample", ".bps"], + ["bits_per_raw_sample", ".bprs"], ["duration", ".dur"], ] @@ -309,7 +326,7 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[ ret[rk] = v1 if ret.get("vc") == "ansi": # shellscript - return {}, {} + return {}, {}, [], {} for strm in streams: for sk, sv in strm.items(): @@ -358,7 +375,76 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[ zero = int("0") zd = {k: (zero, v) for k, v in ret.items()} - return zd, md + return zd, md, streams, fmt + + +def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]: + import zipfile + + from .dxml import parse_xml + + try: + from urlparse import urljoin # Python2 + except ImportError: + from urllib.parse import urljoin # Python3 + + with zipfile.ZipFile(abspath, "r") as z: + # First open the container file to find the package document (.opf file) + try: + container_root = parse_xml(z.read("META-INF/container.xml").decode()) + except KeyError: + log("epub: no container file found in %s" % (abspath,)) + return None + + # https://www.w3.org/TR/epub-33/#sec-container.xml-rootfile-elem + container_ns = {"": "urn:oasis:names:tc:opendocument:xmlns:container"} + # One file could contain multiple package documents, default to the first one + rootfile_path = container_root.find("./rootfiles/rootfile", container_ns).get( + "full-path" + ) + + # Then open the first package document to find the path of the cover image + try: + package_root = parse_xml(z.read(rootfile_path).decode()) + except KeyError: + log("epub: no package document found in %s" % (abspath,)) + return None + + # https://www.w3.org/TR/epub-33/#sec-package-doc + package_ns = {"": "http://www.idpf.org/2007/opf"} + # https://www.w3.org/TR/epub-33/#sec-cover-image + coverimage_path_node = package_root.find( + "./manifest/item[@properties='cover-image']", package_ns + ) + if coverimage_path_node is not None: + coverimage_path = coverimage_path_node.get("href") + else: + # This might be an EPUB2 file, try the legacy way of specifying covers + coverimage_path = _get_cover_from_epub2(log, package_root, package_ns) + + # This url is either absolute (in the .epub) or relative to the package document + adjusted_cover_path = urljoin(rootfile_path, coverimage_path) + + return z.open(adjusted_cover_path) + + +def _get_cover_from_epub2( + log: "NamedLogger", package_root, package_ns +) -> Optional[str]: + # <meta name="cover" content="id-to-cover-image"> in <metadata>, then + # <item> in <manifest> + xn = package_root.find("./metadata/meta[@name='cover']", package_ns) + cover_id = xn.get("content") if xn is not None else None + + if not cover_id: + return None + + for node in package_root.iterfind("./manifest/item", package_ns): + if node.get("id") == cover_id: + cover_path = node.get("href") + return cover_path + + return None class MTag(object): @@ -424,7 +510,6 @@ class MTag(object): "album-artist", "tpe2", "aart", - "conductor", "organization", "band", ], @@ -629,7 +714,7 @@ class MTag(object): if not bos.path.isfile(abspath): return {} - ret, md = ffprobe(abspath, self.args.mtag_to) + ret, md, _, _ = ffprobe(abspath, self.args.mtag_to) if self.args.mtag_vv: for zd in (ret, dict(md)): diff --git a/copyparty/multicast.py b/copyparty/multicast.py index 6d6507f8..7459696e 100644 --- a/copyparty/multicast.py +++ b/copyparty/multicast.py @@ -15,7 +15,7 @@ from ipaddress import ( ) from .__init__ import MACOS, TYPE_CHECKING -from .util import Daemon, Netdev, find_prefix, min_ex, spack +from .util import IP6_LL, IP64_LL, Daemon, Netdev, find_prefix, min_ex, spack if TYPE_CHECKING: from .svchub import SvcHub @@ -96,7 +96,10 @@ class MCast(object): def create_servers(self) -> list[str]: bound: list[str] = [] netdevs = self.hub.tcpsrv.netdevs - ips = [x[0] for x in self.hub.tcpsrv.bound] + blist = self.hub.tcpsrv.bound + if self.args.http_no_tcp: + blist = self.hub.tcpsrv.seen_eps + ips = [x[0] for x in blist] if "::" in ips: ips = [x for x in ips if x != "::"] + list( @@ -145,7 +148,7 @@ class MCast(object): all_selected = ips[:] # discard non-linklocal ipv6 - ips = [x for x in ips if ":" not in x or x.startswith("fe80")] + ips = [x for x in ips if ":" not in x or x.startswith(IP6_LL)] if not ips: raise NoIPs() @@ -183,11 +186,7 @@ class MCast(object): srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False) # gvfs breaks if a linklocal ip appears in a dns reply - ll = { - k: v - for k, v in srv.ips.items() - if k.startswith("169.254") or k.startswith("fe80") - } + ll = {k: v for k, v in srv.ips.items() if k.startswith(IP64_LL)} rt = {k: v for k, v in srv.ips.items() if k not in ll} if self.args.ll or not rt: diff --git a/copyparty/pwhash.py b/copyparty/pwhash.py index ebbd78f3..d642d3c0 100644 --- a/copyparty/pwhash.py +++ b/copyparty/pwhash.py @@ -25,6 +25,7 @@ class PWHash(object): self.args = args zsl = args.ah_alg.split(",") + zsl = [x.strip() for x in zsl] alg = zsl[0] if alg == "none": alg = "" @@ -147,6 +148,10 @@ class PWHash(object): def cli(self) -> None: import getpass + if self.args.usernames: + t = "since you have enabled --usernames, please provide username:password" + print(t) + while True: try: p1 = getpass.getpass("password> ") diff --git a/copyparty/smbd.py b/copyparty/smbd.py index 2b9b3d77..0d03ac4e 100644 --- a/copyparty/smbd.py +++ b/copyparty/smbd.py @@ -246,24 +246,29 @@ class SMB(object): ap = absreal(ap) xbu = vfs.flags.get("xbu") - if xbu and not runhook( - self.nlog, - None, - self.hub.up2k, - "xbu.smb", - xbu, - ap, - vpath, - "", - "", - "", - 0, - 0, - "1.7.6.2", - time.time(), - "", - ): - yeet("blocked by xbu server config: %r" % (vpath,)) + if xbu: + hr = runhook( + self.nlog, + None, + self.hub.up2k, + "xbu.smb", + xbu, + ap, + vpath, + "", + "", + "", + 0, + 0, + "1.7.6.2", + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "blocked by xbu server config: %r" % (vpath,) + yeet(t) ret = bos.open(ap, flags, *a, mode=chmod, **ka) if wr: @@ -318,7 +323,7 @@ class SMB(object): t = "blocked rename (no-move-acc %s): /%s @%s" yeet(t % (vfs1.axs.umove, vp1, uname)) - self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2) + self.hub.up2k.handle_mv("", uname, "1.7.6.2", vp1, vp2) try: bos.makedirs(ap2, vf=vfs2.flags) except: @@ -373,7 +378,7 @@ class SMB(object): t = "blocked utime (no-write-acc %s): /%s @%s" yeet(t % (vfs.axs.uwrite, vpath, uname)) - return bos.utime(ap, times) + bos.utime_c(info, ap, int(times[1]), False) def _p_exists(self, vpath: str) -> bool: # ap = "?" diff --git a/copyparty/stolen/qrcodegen.py b/copyparty/stolen/qrcodegen.py index 38296359..8790e859 100644 --- a/copyparty/stolen/qrcodegen.py +++ b/copyparty/stolen/qrcodegen.py @@ -200,6 +200,25 @@ class QrCode(object): return "\n".join(rows) + def to_png(self, zoom, pad, bg, fg, ap) -> None: + from PIL import Image + + tab = self.modules + sz = self.size + psz = sz + pad * 2 + if bg: + img = Image.new("RGB", (psz, psz), bg) + else: + img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0)) + fg = (fg[0], fg[1], fg[2], 255) + for y in range(sz): + for x in range(sz): + if tab[y][x]: + img.putpixel((x + pad, y + pad), fg) + if zoom != 1: + img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST) + img.save(ap) + def _draw_function_patterns(self) -> None: # Draw horizontal and vertical timing patterns for i in range(self.size): diff --git a/copyparty/svchub.py b/copyparty/svchub.py index bd7562e4..46261e22 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -2,6 +2,7 @@ from __future__ import print_function, unicode_literals import argparse +import atexit import errno import logging import os @@ -26,9 +27,10 @@ if True: # pylint: disable=using-constant-test from typing import Any, Optional, Union from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode -from .authsrv import BAD_CFG, AuthSrv +from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who from .bos import bos from .cert import ensure_cert +from .fsutil import ramdisk_chk from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN from .pwhash import HAVE_ARGON2 from .tcpsrv import TcpSrv @@ -38,6 +40,7 @@ from .th_srv import ( HAVE_FFPROBE, HAVE_HEIF, HAVE_PIL, + HAVE_RAW, HAVE_VIPS, HAVE_WEBP, ThumbSrv, @@ -64,6 +67,7 @@ from .util import ( build_netmap, expat_ver, gzip, + load_ipr, load_ipu, lock_file, min_ex, @@ -72,6 +76,7 @@ from .util import ( pybin, start_log_thrs, start_stackmon, + termsize, ub64enc, ) @@ -130,6 +135,7 @@ class SvcHub(object): self.nsigs = 3 self.retcode = 0 self.httpsrv_up = 0 + self.qr_tsz = None self.log_mutex = threading.Lock() self.cday = 0 @@ -151,7 +157,8 @@ class SvcHub(object): args.unpost = 0 args.no_del = True args.no_mv = True - args.hardlink = True + args.reflink = True + args.dav_auth = True args.vague_403 = True args.nih = True @@ -240,8 +247,8 @@ class SvcHub(object): t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s" self.log("root", t % (args.th_ram_max, zs), 3) - if args.chpw and args.idp_h_usr: - t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr" + if args.chpw and args.have_idp_hdrs and "pw" not in args.auth_ord.split(","): + t = "ERROR: user-changeable passwords is not compatible with your current configuration. Choose one of these options to fix it:\n option1: disable --chpw\n option2: remove all use of IdP features; --idp-*\n option3: change --auth-ord to something like pw,idp,ipu" self.log("root", t, 1) raise Exception(t) @@ -256,6 +263,10 @@ class SvcHub(object): setattr(args, "ipu_iu", iu) setattr(args, "ipu_nm", nm) + if args.ipr: + ipr = load_ipr(self.log, args.ipr, True) + setattr(args, "ipr_u", ipr) + for zs in "ah_salt fk_salt dk_salt".split(): if getattr(args, "show_%s" % (zs,)): self.log("root", "effective %s is %s" % (zs, getattr(args, zs))) @@ -265,7 +276,7 @@ class SvcHub(object): args.no_ses = True args.shr = "" - if args.idp_store and args.idp_h_usr: + if args.idp_store and args.have_idp_hdrs: self.setup_db("idp") if not self.args.no_ses: @@ -279,6 +290,14 @@ class SvcHub(object): ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)] args.theme = "{0}{1} {0} {1}".format(ch, bri) + if args.nid: + args.du_who = "no" + args.du_iwho = n_du_who(args.du_who) + + if args.ver and args.ver_who == "no": + args.ver_who = "all" + args.ver_iwho = n_ver_who(args.ver_who) + if args.nih: args.vname = "" args.doctitle = args.doctitle.replace(" @ --name", "") @@ -292,6 +311,7 @@ class SvcHub(object): # initiate all services to manage self.asrv = AuthSrv(self.args, self.log, dargs=self.dargs) + ramdisk_chk(self.asrv) if args.cgen: self.asrv.cgen() @@ -316,11 +336,13 @@ class SvcHub(object): self._feature_test() - decs = {k: 1 for k in self.args.th_dec.split(",")} + decs = {k.strip(): 1 for k in self.args.th_dec.split(",")} if not HAVE_VIPS: decs.pop("vips", None) if not HAVE_PIL: decs.pop("pil", None) + if not HAVE_RAW: + decs.pop("raw", None) if not HAVE_FFMPEG or not HAVE_FFPROBE: decs.pop("ff", None) @@ -369,7 +391,10 @@ class SvcHub(object): t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]" raise Exception(t % (args.q_mp3,)) else: - args.au_unpk = {} + zss = set(args.th_r_ffa.split(",") + args.th_r_ffv.split(",")) + args.au_unpk = { + k: v for k, v in args.au_unpk.items() if v.split(".")[0] not in zss + } args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage) @@ -422,11 +447,14 @@ class SvcHub(object): # create netmaps early to avoid firewall gaps, # but the mutex blocks multiprocessing startup - for zs in "ipu_iu ftp_ipa_nm tftp_ipa_nm".split(): + for zs in "ipu_nm ftp_ipa_nm tftp_ipa_nm".split(): try: getattr(args, zs).mutex = threading.Lock() except: pass + if args.ipr: + for nm in args.ipr_u.values(): + nm.mutex = threading.Lock() def _db_onfail_ses(self) -> None: self.args.no_ses = True @@ -772,6 +800,84 @@ class SvcHub(object): def sigterm(self) -> None: self.signal_handler(signal.SIGTERM, None) + def sticky_qr(self) -> None: + self._sticky_qr() + + def _unsticky_qr(self, flush=True) -> None: + print("\033[s\033[J\033[r\033[u", file=sys.stderr, end="") + if flush: + sys.stderr.flush() + + def _sticky_qr(self, force: bool = False) -> None: + sz = termsize() + if self.qr_tsz == sz: + if not force: + return + else: + force = False + + if self.qr_tsz: + self._unsticky_qr(False) + else: + atexit.register(self._unsticky_qr) + + tw, th = self.qr_tsz = sz + zs1, qr = self.tcpsrv.qr.split("\n", 1) + url, colr = zs1.split(" ", 1) + nl = len(qr.split("\n")) # numlines + lp = 3 if nl * 2 + 4 < tw else 0 # leftpad + lp0 = lp + if self.args.qr_pin == 2: + url = "" + else: + while lp and (nl + lp) * 2 + len(url) + 1 > tw: + lp -= 1 + if (nl + lp) * 2 + len(url) + 1 > tw: + qr = url + "\n" + qr + url = "" + nl += 1 + lp = lp0 + sh = 1 + th - nl + if lp: + zs = " " * lp + qr = zs + qr.replace("\n", "\n" + zs) + if url: + url = "%s\033[%d;%dH%s\033[0m" % (colr, sh + 1, (nl + lp) * 2, url) + qr = colr + qr + + t = "%s\033[%dA" % ("\n" * nl, nl) + t = "%s\033[s\033[1;%dr\033[%dH%s%s\033[u" % (t, sh - 1, sh, qr, url) + if not force: + self.log("qr", "sticky-qrcode %sx%s,%s" % (tw, th, sh), 6) + self.pr(t, file=sys.stderr, end="") + + def _qr_thr(self): + qr = self.tcpsrv.qr + w8 = self.args.qr_wait + if w8: + time.sleep(w8) + self.log("qr-code", qr) + if self.args.qr_stdout: + self.pr(self.tcpsrv.qr) + if self.args.qr_stderr: + self.pr(self.tcpsrv.qr, file=sys.stderr) + w8 = self.args.qr_every + msg = "%s\033[%dA" % (qr, len(qr.split("\n"))) + while w8: + time.sleep(w8) + if self.stopping: + break + if self.args.qr_pin: + self._sticky_qr(True) + else: + self.log("qr-code", msg) + w8 = self.args.qr_winch + while w8: + time.sleep(w8) + if self.stopping: + break + self._sticky_qr() + def cb_httpsrv_up(self) -> None: self.httpsrv_up += 1 if self.httpsrv_up != self.broker.num_workers: @@ -784,7 +890,17 @@ class SvcHub(object): break if self.tcpsrv.qr: - self.log("qr-code", self.tcpsrv.qr) + if self.args.qr_pin: + self.sticky_qr() + if self.args.qr_wait or self.args.qr_every or self.args.qr_winch: + Daemon(self._qr_thr, "qr") + else: + if not self.args.qr_pin: + self.log("qr-code", self.tcpsrv.qr) + if self.args.qr_stdout: + self.pr(self.tcpsrv.qr) + if self.args.qr_stderr: + self.pr(self.tcpsrv.qr, file=sys.stderr) else: self.log("root", "workers OK\n") @@ -811,6 +927,7 @@ class SvcHub(object): (HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"), (HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"), (HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"), + (HAVE_RAW, "rawpy", "read RAW images"), ] if ANYWIN: to_check += [ @@ -850,15 +967,6 @@ class SvcHub(object): def _check_env(self) -> None: al = self.args - try: - files = os.listdir(E.cfg) - except: - files = [] - - hits = [x for x in files if x.lower().endswith(".conf")] - if hits: - t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)" - self.log("root", t % (E.cfg, ", ".join(hits)), 3) if self.args.no_bauth: t = "WARNING: --no-bauth disables support for the Android app; you may want to use --bauth-last instead" @@ -868,7 +976,7 @@ class SvcHub(object): have_tcp = False for zs in al.i: - if not zs.startswith("unix:"): + if not zs.startswith(("unix:", "fd:")): have_tcp = True if not have_tcp: zb = False @@ -878,7 +986,7 @@ class SvcHub(object): setattr(al, zs, False) zb = True if zb: - t = "only listening on unix-sockets; cannot enable zeroconf/mdns/ssdp as requested" + t = "not listening on any ip-addresses (only unix-sockets and/or FDs); cannot enable zeroconf/mdns/ssdp as requested" self.log("root", t, 3) if not self.args.no_dav: @@ -890,6 +998,24 @@ class SvcHub(object): t = "WARNING:\nDisabling WebDAV support because dxml selftest failed. Please report this bug;\n%s\n...and include the following information in the bug-report:\n%s | expat %s\n" self.log("root", t % (URL_BUG, VERSIONS, expat_ver()), 1) + if not E.scfg and not al.unsafe_state and not os.getenv("PRTY_UNSAFE_STATE"): + t = "because runtime config is currently being stored in an untrusted emergency-fallback location. Please fix your environment so either XDG_CONFIG_HOME or ~/.config can be used instead, or disable this safeguard with --unsafe-state or env-var PRTY_UNSAFE_STATE=1." + if not al.no_ses: + al.no_ses = True + t2 = "A consequence of this misconfiguration is that passwords will now be sent in the HTTP-header of every request!" + self.log("root", "WARNING:\nWill disable sessions %s %s" % (t, t2), 1) + if al.idp_store == 1: + al.idp_store = 0 + self.log("root", "WARNING:\nDisabling --idp-store %s" % (t,), 3) + if al.idp_store: + t2 = "ERROR: Cannot enable --idp-store %s" % (t,) + self.log("root", t2, 1) + raise Exception(t2) + if al.shr: + t2 = "ERROR: Cannot enable shares %s" % (t,) + self.log("root", t2, 1) + raise Exception(t2) + def _process_config(self) -> bool: al = self.args @@ -978,10 +1104,23 @@ class SvcHub(object): al.sus_urls = None al.xff_hdr = al.xff_hdr.lower() - al.idp_h_usr = al.idp_h_usr.lower() + al.idp_h_usr = [x.lower() for x in al.idp_h_usr or []] al.idp_h_grp = al.idp_h_grp.lower() al.idp_h_key = al.idp_h_key.lower() + al.idp_hm_usr_p = {} + for zs0 in al.idp_hm_usr or []: + try: + sep = zs0[:1] + hn, zs1, zs2 = zs0[1:].split(sep) + hn = hn.lower() + if hn in al.idp_hm_usr_p: + al.idp_hm_usr_p[hn][zs1] = zs2 + else: + al.idp_hm_usr_p[hn] = {zs1: zs2} + except: + raise Exception("invalid --idp-hm-usr [%s]" % (zs0,)) + al.ftp_ipa_nm = build_netmap(al.ftp_ipa or al.ipa, True) al.tftp_ipa_nm = build_netmap(al.tftp_ipa or al.ipa, True) @@ -1033,7 +1172,7 @@ class SvcHub(object): al.tcolor = "".join([x * 2 for x in al.tcolor]) zs = al.u2sz - zsl = zs.split(",") + zsl = [x.strip() for x in zs.split(",")] if len(zsl) not in (1, 3): t = "invalid --u2sz; must be either one number, or a comma-separated list of three numbers (min,default,max)" raise Exception(t) @@ -1050,6 +1189,7 @@ class SvcHub(object): zi2 = zi al.u2sz = ",".join(zsl) + derive_args(al) return True def _ipa2re(self, txt) -> Optional[re.Pattern]: @@ -1225,6 +1365,7 @@ class SvcHub(object): with self.reload_mutex: self.log("root", "reloading config") self.asrv.reload(9 if up2k else 4) + ramdisk_chk(self.asrv) if up2k: self.up2k.reload(rescan_all_vols) t += "; volumes are now reinitializing" @@ -1409,7 +1550,14 @@ class SvcHub(object): fmt = "\033[36m%s \033[33m%-21s \033[0m%s\n" if self.no_ansi: - fmt = "%s %-21s %s\n" + if c == 1: + fmt = "%s %-21s CRIT: %s\n" + elif c == 3: + fmt = "%s %-21s WARN: %s\n" + elif c == 6: + fmt = "%s %-21s BTW: %s\n" + else: + fmt = "%s %-21s LOG: %s\n" if "\033" in msg: msg = RE_ANSI.sub("", msg) if "\033" in src: diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index 65f343d0..c44adf8e 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -9,13 +9,14 @@ import time from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode from .cert import gencert -from .stolen.qrcodegen import QrCode +from .stolen.qrcodegen import QrCode, qr2svg from .util import ( E_ACCESS, E_ADDR_IN_USE, E_ADDR_NOT_AVAIL, E_UNREACH, HAVE_IPV6, + IP6_LL, IP6ALL, VF_CAREFUL, Netdev, @@ -25,8 +26,8 @@ from .util import ( termsize, ) -if True: - from typing import Generator, Union +if True: # pylint: disable=using-constant-test + from typing import Generator, Optional, Union if TYPE_CHECKING: from .svchub import SvcHub @@ -58,6 +59,7 @@ class TcpSrv(object): self.stopping = False self.srv: list[socket.socket] = [] self.bound: list[tuple[str, int]] = [] + self.seen_eps: list[tuple[str, int]] = [] # also skipped by uds-only self.netdevs: dict[str, Netdev] = {} self.netlist = "" self.nsrv = 0 @@ -140,12 +142,12 @@ class TcpSrv(object): # keep IPv6 LL-only nics ll_ok: set[str] = set() for ip, nd in self.netdevs.items(): - if not ip.startswith("fe80"): + if not ip.startswith(IP6_LL): continue just_ll = True for ip2, nd2 in self.netdevs.items(): - if nd == nd2 and ":" in ip2 and not ip2.startswith("fe80"): + if nd == nd2 and ":" in ip2 and not ip2.startswith(IP6_LL): just_ll = False if just_ll or self.args.ll: @@ -164,7 +166,7 @@ class TcpSrv(object): title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")] t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)" for ip, desc in sorted(eps.items(), key=lambda x: x[1]): - if ip.startswith("fe80") and ip not in ll_ok: + if ip.startswith(IP6_LL) and ip not in ll_ok: continue for port in sorted(self.args.p): @@ -245,8 +247,10 @@ class TcpSrv(object): def _listen(self, ip: str, port: int) -> None: uds_perm = uds_gid = -1 + bound: Optional[socket.socket] = None + tcp = False + if "unix:" in ip: - tcp = False ipv = socket.AF_UNIX uds = ip.split(":") ip = uds[-1] @@ -259,7 +263,12 @@ class TcpSrv(object): import grp uds_gid = grp.getgrnam(uds[2]).gr_gid + elif "fd:" in ip: + fd = ip[3:] + bound = socket.socket(fileno=int(fd)) + tcp = bound.proto == socket.IPPROTO_TCP + ipv = bound.family elif ":" in ip: tcp = True ipv = socket.AF_INET6 @@ -267,7 +276,7 @@ class TcpSrv(object): tcp = True ipv = socket.AF_INET - srv = socket.socket(ipv, socket.SOCK_STREAM) + srv = bound or socket.socket(ipv, socket.SOCK_STREAM) if not ANYWIN or self.args.reuseaddr: srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -285,14 +294,25 @@ class TcpSrv(object): if getattr(self.args, "freebind", False): srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1) + if bound: + self.srv.append(srv) + return + try: if tcp: + if self.args.http_no_tcp: + self.seen_eps.append((ip, port)) + return srv.bind((ip, port)) else: if ANYWIN or self.args.rm_sck: if os.path.exists(ip): os.unlink(ip) srv.bind(ip) + if uds_gid != -1: + os.chown(ip, -1, uds_gid) + if uds_perm != -1: + os.chmod(ip, uds_perm) else: tf = "%s.%d" % (ip, os.getpid()) if os.path.exists(tf): @@ -393,6 +413,7 @@ class TcpSrv(object): self.srv = srvs self.bound = bound + self.seen_eps = list(set(self.seen_eps + bound)) self.nsrv = len(srvs) self._distribute_netdevs() @@ -437,7 +458,7 @@ class TcpSrv(object): def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]: from .stolen.ifaddr import get_adapters - listen_ips = [x for x in listen_ips if "unix:" not in x] + listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))] nics = get_adapters(True) eps: dict[str, Netdev] = {} @@ -583,8 +604,7 @@ class TcpSrv(object): if not ip: return "" - if ":" in ip: - ip = "[{}]".format(ip) + hip = "[%s]" % (ip,) if ":" in ip else ip if self.args.http_only: https = "" @@ -596,7 +616,7 @@ class TcpSrv(object): ports = t1.get(ip, t2.get(ip, [])) dport = 443 if https else 80 port = "" if dport in ports or not ports else ":{}".format(ports[0]) - txt = "http{}://{}{}/{}".format(https, ip, port, self.args.qrl) + txt = "http{}://{}{}/{}".format(https, hip, port, self.args.qrl) btxt = txt.encode("utf-8") if PY2: @@ -604,9 +624,17 @@ class TcpSrv(object): fg = self.args.qr_fg bg = self.args.qr_bg + nocolor = fg == -1 + if nocolor: + fg = 0 + pad = self.args.qrp zoom = self.args.qrz qrc = QrCode.encode_binary(btxt) + + for zs in self.args.qr_file or []: + self._qr2file(qrc, zs) + if zoom == 0: try: tw, th = termsize() @@ -622,6 +650,8 @@ class TcpSrv(object): halfc = "\033[40;48;5;{0}m{1}\033[47;48;5;{2}m" if not fg: halfc = "\033[0;40m{1}\033[0;47m" + if nocolor: + halfc = "\033[0;7m{1}\033[0m" def ansify(m: re.Match) -> str: return halfc.format(fg, " " * len(m.group(1)), bg) @@ -631,6 +661,8 @@ class TcpSrv(object): qr = qr.replace("\n", "\033[K\n") + "\033[K" # win10do cc = " \033[0;38;5;{0};47;48;5;{1}m" if fg else " \033[0;30;47m" + if nocolor: + cc = " \033[0m" t = cc + "\n{2}\033[999G\033[0m\033[J" t = t.format(fg, bg, qr) if ANYWIN: @@ -638,3 +670,29 @@ class TcpSrv(object): t = t.replace("\n", "`\n`") return txt + t + + def _qr2file(self, qrc: QrCode, txt: str): + if ".txt:" in txt or ".svg:" in txt: + ap, zs1, zs2 = txt.rsplit(":", 2) + bg = fg = "" + else: + ap, zs1, zs2, bg, fg = txt.rsplit(":", 4) + zoom = int(zs1) + pad = int(zs2) + + if ap.endswith(".txt"): + if zoom not in (1, 2): + raise Exception("invalid zoom for qr.txt; must be 1 or 2") + with open(ap, "wb") as f: + f.write(qrc.render(zoom, pad).encode("utf-8")) + elif ap.endswith(".svg"): + with open(ap, "wb") as f: + f.write(qr2svg(qrc, pad).encode("utf-8")) + else: + qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap) + + def _h2i(self, hs): + try: + return tuple(int(hs[i : i + 2], 16) for i in (0, 2, 4)) + except: + return None diff --git a/copyparty/tftpd.py b/copyparty/tftpd.py index 6f5726b3..4b95a2b6 100644 --- a/copyparty/tftpd.py +++ b/copyparty/tftpd.py @@ -179,7 +179,7 @@ class Tftpd(object): if "::" in ips: ips.append("0.0.0.0") - ips = [x for x in ips if "unix:" not in x] + ips = [x for x in ips if not x.startswith(("unix:", "fd:"))] if self.args.tftp4: ips = [x for x in ips if ":" not in x] @@ -363,24 +363,29 @@ class Tftpd(object): yeet("blocked write; folder not world-deletable: /%s" % (vpath,)) xbu = vfs.flags.get("xbu") - if xbu and not runhook( - self.nlog, - None, - self.hub.up2k, - "xbu.tftpd", - xbu, - ap, - vpath, - "", - "", - "", - 0, - 0, - "8.3.8.7", - time.time(), - "", - ): - yeet("blocked by xbu server config: %r" % (vpath,)) + if xbu: + hr = runhook( + self.nlog, + None, + self.hub.up2k, + "xbu.tftpd", + xbu, + ap, + vpath, + "", + "", + "", + 0, + 0, + "8.3.8.7", + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" % (vpath,) + yeet(t) if not self.args.tftp_nols and bos.path.isdir(ap): return self._ls(vpath, "", 0, True) diff --git a/copyparty/th_cli.py b/copyparty/th_cli.py index cfb148c2..384316ee 100644 --- a/copyparty/th_cli.py +++ b/copyparty/th_cli.py @@ -36,11 +36,15 @@ class ThumbCli(object): if not c: raise Exception() except: - c = {k: set() for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]} + c = { + k: set() + for k in ["thumbable", "pil", "vips", "raw", "ffi", "ffv", "ffa"] + } self.thumbable = c["thumbable"] self.fmt_pil = c["pil"] self.fmt_vips = c["vips"] + self.fmt_raw = c["raw"] self.fmt_ffi = c["ffi"] self.fmt_ffv = c["ffv"] self.fmt_ffa = c["ffa"] @@ -88,7 +92,7 @@ class ThumbCli(object): if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]: return os.path.join(ptop, rem) - if fmt[:1] in "jw": + if fmt[:1] in "jw" and fmt != "wav": sfmt = fmt[:1] if sfmt == "j" and self.args.th_no_jpg: @@ -129,7 +133,7 @@ class ThumbCli(object): tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa) tpaths = [tpath] - if fmt[:1] == "w": + if fmt[:1] == "w" and fmt != "wav": # also check for jpg (maybe webp is unavailable) tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg") diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index 67413c10..86c3c469 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -2,6 +2,7 @@ from __future__ import print_function, unicode_literals import hashlib +import io import logging import os import re @@ -50,7 +51,7 @@ HAVE_AVIF = False HAVE_WEBP = False EXTS_TH = set(["jpg", "webp", "png"]) -EXTS_AC = set(["opus", "owa", "caf", "mp3"]) +EXTS_AC = set(["opus", "owa", "caf", "mp3", "flac", "wav"]) EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split()) PTN_TS = re.compile("^-?[0-9a-f]{8,10}$") @@ -85,7 +86,10 @@ try: if os.environ.get("PRTY_NO_PIL_HEIF"): raise Exception() - from pyheif_pillow_opener import register_heif_opener + try: + from pillow_heif import register_heif_opener + except ImportError: + from pyheif_pillow_opener import register_heif_opener register_heif_opener() HAVE_HEIF = True @@ -112,14 +116,28 @@ except: try: if os.environ.get("PRTY_NO_VIPS"): - raise Exception() + raise ImportError() HAVE_VIPS = True import pyvips logging.getLogger("pyvips").setLevel(logging.WARNING) -except: +except Exception as e: HAVE_VIPS = False + if not isinstance(e, ImportError): + logging.warning("libvips found, but failed to load: " + str(e)) + + +try: + if os.environ.get("PRTY_NO_RAW"): + raise Exception() + + HAVE_RAW = True + import rawpy + + logging.getLogger("rawpy").setLevel(logging.WARNING) +except: + HAVE_RAW = False th_dir_cache = {} @@ -205,11 +223,19 @@ class ThumbSrv(object): if self.args.th_clean: Daemon(self.cleaner, "thumb.cln") - self.fmt_pil, self.fmt_vips, self.fmt_ffi, self.fmt_ffv, self.fmt_ffa = [ + ( + self.fmt_pil, + self.fmt_vips, + self.fmt_raw, + self.fmt_ffi, + self.fmt_ffv, + self.fmt_ffa, + ) = [ set(y.split(",")) for y in [ self.args.th_r_pil, self.args.th_r_vips, + self.args.th_r_raw, self.args.th_r_ffi, self.args.th_r_ffv, self.args.th_r_ffa, @@ -232,6 +258,9 @@ class ThumbSrv(object): if "vips" in self.args.th_dec: self.thumbable |= self.fmt_vips + if "raw" in self.args.th_dec: + self.thumbable |= self.fmt_raw + if "ff" in self.args.th_dec: for zss in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]: self.thumbable |= zss @@ -313,6 +342,7 @@ class ThumbSrv(object): "thumbable": self.thumbable, "pil": self.fmt_pil, "vips": self.fmt_vips, + "raw": self.fmt_raw, "ffi": self.fmt_ffi, "ffv": self.fmt_ffv, "ffa": self.fmt_ffa, @@ -355,8 +385,10 @@ class ThumbSrv(object): tex = tpath.rsplit(".", 1)[-1] want_mp3 = tex == "mp3" want_opus = tex in ("opus", "owa", "caf") + want_flac = tex == "flac" + want_wav = tex == "wav" want_png = tex == "png" - want_au = want_mp3 or want_opus + want_au = want_mp3 or want_opus or want_flac or want_wav for lib in self.args.th_dec: can_au = lib == "ff" and ( ext in self.fmt_ffa or ext in self.fmt_ffv @@ -366,11 +398,17 @@ class ThumbSrv(object): funs.append(self.conv_pil) elif lib == "vips" and ext in self.fmt_vips: funs.append(self.conv_vips) + elif lib == "raw" and ext in self.fmt_raw: + funs.append(self.conv_raw) elif can_au and (want_png or want_au): if want_opus: funs.append(self.conv_opus) elif want_mp3: funs.append(self.conv_mp3) + elif want_flac: + funs.append(self.conv_flac) + elif want_wav: + funs.append(self.conv_wav) elif want_png: funs.append(self.conv_waves) png_ok = True @@ -474,35 +512,38 @@ class ThumbSrv(object): return im + def conv_image_pil(self, im: "Image.Image", tpath: str, fmt: str, vn: VFS) -> None: + try: + im = self.fancy_pillow(im, fmt, vn) + except Exception as ex: + self.log("fancy_pillow {}".format(ex), "90") + im.thumbnail(self.getres(vn, fmt)) + + fmts = ["RGB", "L"] + args = {"quality": 40} + + if tpath.endswith(".webp"): + # quality 80 = pillow-default + # quality 75 = ffmpeg-default + # method 0 = pillow-default, fast + # method 4 = ffmpeg-default + # method 6 = max, slow + fmts.extend(("RGBA", "LA")) + args["method"] = 6 + else: + # default q = 75 + args["progressive"] = True + + if im.mode not in fmts: + # print("conv {}".format(im.mode)) + im = im.convert("RGB") + + im.save(tpath, **args) + def conv_pil(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: self.wait4ram(0.2, tpath) with Image.open(fsenc(abspath)) as im: - try: - im = self.fancy_pillow(im, fmt, vn) - except Exception as ex: - self.log("fancy_pillow {}".format(ex), "90") - im.thumbnail(self.getres(vn, fmt)) - - fmts = ["RGB", "L"] - args = {"quality": 40} - - if tpath.endswith(".webp"): - # quality 80 = pillow-default - # quality 75 = ffmpeg-default - # method 0 = pillow-default, fast - # method 4 = ffmpeg-default - # method 6 = max, slow - fmts.extend(("RGBA", "LA")) - args["method"] = 6 - else: - # default q = 75 - args["progressive"] = True - - if im.mode not in fmts: - # print("conv {}".format(im.mode)) - im = im.convert("RGB") - - im.save(tpath, **args) + self.conv_image_pil(im, tpath, fmt, vn) def conv_vips(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: self.wait4ram(0.2, tpath) @@ -525,9 +566,53 @@ class ThumbSrv(object): assert img # type: ignore # !rm img.write_to_file(tpath, Q=40) + def conv_raw(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: + self.wait4ram(0.2, tpath) + with rawpy.imread(abspath) as raw: + thumb = raw.extract_thumb() + if thumb.format == rawpy.ThumbFormat.JPEG and tpath.endswith(".jpg"): + # if we have a jpg thumbnail and no webp output is available, + # just write the jpg directly (it'll be the wrong size, but it's fast) + with open(tpath, "wb") as f: + f.write(thumb.data) + if HAVE_VIPS: + crops = ["centre", "none"] + if "f" in fmt: + crops = ["none"] + w, h = self.getres(vn, fmt) + kw = {"height": h, "size": "down", "intent": "relative"} + + for c in crops: + try: + kw["crop"] = c + if thumb.format == rawpy.ThumbFormat.BITMAP: + img = pyvips.Image.new_from_array( + thumb.data, interpretation="rgb" + ) + img = img.thumbnail_image(w, **kw) + else: + img = pyvips.Image.thumbnail_buffer(thumb.data, w, **kw) + break + except: + if c == crops[-1]: + raise + + assert img # type: ignore # !rm + img.write_to_file(tpath, Q=40) + elif HAVE_PIL: + if thumb.format == rawpy.ThumbFormat.BITMAP: + im = Image.fromarray(thumb.data, "RGB") + else: + im = Image.open(io.BytesIO(thumb.data)) + self.conv_image_pil(im, tpath, fmt, vn) + else: + raise Exception( + "either pil or vips is needed to process embedded bitmap thumbnails in raw files" + ) + def conv_ffmpeg(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: self.wait4ram(0.2, tpath) - ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) + ret, _, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) if not ret: return @@ -538,6 +623,17 @@ class ThumbSrv(object): dur = ret[".dur"][1] if ".dur" in ret else 4 seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")] + self._ffmpeg_im(abspath, tpath, fmt, vn, seek, b"0:v:0") + + def _ffmpeg_im( + self, + abspath: str, + tpath: str, + fmt: str, + vn: VFS, + seek: list[bytes], + imap: bytes, + ) -> None: scale = "scale={0}:{1}:force_original_aspect_ratio=" if "f" in fmt: scale += "decrease,setsar=1:1" @@ -556,7 +652,7 @@ class ThumbSrv(object): cmd += seek cmd += [ b"-i", fsenc(abspath), - b"-map", b"0:v:0", + b"-map", imap, b"-vf", bscale, b"-frames:v", b"1", b"-metadata:s:v:0", b"rotate=0", @@ -577,11 +673,11 @@ class ThumbSrv(object): ] cmd += [fsenc(tpath)] - self._run_ff(cmd, vn) + self._run_ff(cmd, vn, "convt") - def _run_ff(self, cmd: list[bytes], vn: VFS, oom: int = 400) -> None: + def _run_ff(self, cmd: list[bytes], vn: VFS, kto: str, oom: int = 400) -> None: # self.log((b" ".join(cmd)).decode("utf-8")) - ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"], nice=True, oom=oom) + ret, _, serr = runcmd(cmd, timeout=vn.flags[kto], nice=True, oom=oom) if not ret: return @@ -625,7 +721,7 @@ class ThumbSrv(object): raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1])) def conv_waves(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: - ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) + ret, _, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) if "ac" not in ret: raise Exception("not audio") @@ -663,7 +759,7 @@ class ThumbSrv(object): # fmt: on cmd += [fsenc(tpath)] - self._run_ff(cmd, vn) + self._run_ff(cmd, vn, "convt") if "pngquant" in vn.flags: wtpath = tpath + ".png" @@ -684,11 +780,31 @@ class ThumbSrv(object): else: atomic_move(self.log, wtpath, tpath, vn.flags) + def conv_emb_cv( + self, abspath: str, tpath: str, fmt: str, vn: VFS, strm: dict[str, Any] + ) -> None: + self.wait4ram(0.2, tpath) + self._ffmpeg_im( + abspath, tpath, fmt, vn, [], b"0:" + strm["index"].encode("ascii") + ) + def conv_spec(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: - ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) + ret, raw, strms, ctnr = ffprobe(abspath, int(vn.flags["convt"] / 2)) if "ac" not in ret: raise Exception("not audio") + want_spec = vn.flags.get("th_spec_p", 1) + if want_spec < 2: + for strm in strms: + if ( + strm.get("codec_type") == "video" + and strm.get("DISPOSITION:attached_pic") == "1" + ): + return self.conv_emb_cv(abspath, tpath, fmt, vn, strm) + + if not want_spec: + raise Exception("spectrograms forbidden by volflag") + fext = abspath.split(".")[-1].lower() # https://trac.ffmpeg.org/ticket/10797 @@ -724,7 +840,7 @@ class ThumbSrv(object): b"-y", fsenc(infile), ] # fmt: on - self._run_ff(cmd, vn) + self._run_ff(cmd, vn, "convt") fc = "[0:a:0]aresample=48000{},showspectrumpic=s=" if "3" in fmt: @@ -766,7 +882,7 @@ class ThumbSrv(object): ] cmd += [fsenc(tpath)] - self._run_ff(cmd, vn) + self._run_ff(cmd, vn, "convt") def conv_mp3(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: quality = self.args.q_mp3.lower() @@ -774,7 +890,7 @@ class ThumbSrv(object): raise Exception("disabled in server config") self.wait4ram(0.2, tpath) - tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2)) + tags, rawtags, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) if "ac" not in tags: raise Exception("not audio") @@ -805,14 +921,74 @@ class ThumbSrv(object): fsenc(tpath) ] # fmt: on - self._run_ff(cmd, vn, oom=300) + self._run_ff(cmd, vn, "aconvt", oom=300) + + def conv_flac(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: + if self.args.no_acode or not self.args.allow_flac: + raise Exception("flac not permitted in server config") + + self.wait4ram(0.2, tpath) + tags, _, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) + if "ac" not in tags: + raise Exception("not audio") + + self.log("conv2 flac", 6) + + # fmt: off + cmd = [ + b"ffmpeg", + b"-nostdin", + b"-v", b"error", + b"-hide_banner", + b"-i", fsenc(abspath), + b"-map", b"0:a:0", + b"-c:a", b"flac", + fsenc(tpath) + ] + # fmt: on + self._run_ff(cmd, vn, "aconvt", oom=300) + + def conv_wav(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: + if self.args.no_acode or not self.args.allow_wav: + raise Exception("wav not permitted in server config") + + self.wait4ram(0.2, tpath) + tags, _, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) + if "ac" not in tags: + raise Exception("not audio") + + bits = tags[".bps"][1] + if bits == 0.0: + bits = tags[".bprs"][1] + + codec = b"pcm_s32le" + if bits <= 16.0: + codec = b"pcm_s16le" + elif bits <= 24.0: + codec = b"pcm_s24le" + + self.log("conv2 wav", 6) + + # fmt: off + cmd = [ + b"ffmpeg", + b"-nostdin", + b"-v", b"error", + b"-hide_banner", + b"-i", fsenc(abspath), + b"-map", b"0:a:0", + b"-c:a", codec, + fsenc(tpath) + ] + # fmt: on + self._run_ff(cmd, vn, "aconvt", oom=300) def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: if self.args.no_acode or not self.args.q_opus: raise Exception("disabled in server config") self.wait4ram(0.2, tpath) - tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2)) + tags, rawtags, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2)) if "ac" not in tags: raise Exception("not audio") @@ -861,7 +1037,7 @@ class ThumbSrv(object): fsenc(tpath) ] # fmt: on - self._run_ff(cmd, vn, oom=300) + self._run_ff(cmd, vn, "aconvt", oom=300) def _conv_caf( self, @@ -901,7 +1077,7 @@ class ThumbSrv(object): fsenc(tmp_opus) ] # fmt: on - self._run_ff(cmd, vn, oom=300) + self._run_ff(cmd, vn, "aconvt", oom=300) # iOS fails to play some "insufficiently complex" files # (average file shorter than 8 seconds), so of course we @@ -928,7 +1104,7 @@ class ThumbSrv(object): fsenc(tpath) ] # fmt: on - self._run_ff(cmd, vn, oom=300) + self._run_ff(cmd, vn, "aconvt", oom=300) else: # simple remux should be safe @@ -947,7 +1123,7 @@ class ThumbSrv(object): fsenc(tpath) ] # fmt: on - self._run_ff(cmd, vn, oom=300) + self._run_ff(cmd, vn, "aconvt", oom=300) try: wunlink(self.log, tmp_opus, vn.flags) diff --git a/copyparty/u2idx.py b/copyparty/u2idx.py index ff0e329f..ca1014d1 100644 --- a/copyparty/u2idx.py +++ b/copyparty/u2idx.py @@ -53,6 +53,11 @@ class U2idx(object): self.log("your python does not have sqlite3; searching will be disabled") return + if self.args.srch_icase: + self._open_db = self._open_db_icase + else: + self._open_db = self._open_db_std + assert sqlite3 # type: ignore # !rm self.active_id = "" @@ -69,6 +74,16 @@ class U2idx(object): def log(self, msg: str, c: Union[int, str] = 0) -> None: self.log_func("u2idx", msg, c) + def _open_db_std(self, *args, **kwargs): + assert sqlite3 # type: ignore # !rm + kwargs["check_same_thread"] = False + return sqlite3.connect(*args, **kwargs) + + def _open_db_icase(self, *args, **kwargs): + db = self._open_db_std(*args, **kwargs) + db.create_function("casefold", 1, lambda x: x.casefold() if x else x) + return db + def shutdown(self) -> None: if not HAVE_SQLITE3: return @@ -148,8 +163,7 @@ class U2idx(object): uri = "" try: uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri()) - db = sqlite3.connect(uri, timeout=2, uri=True, check_same_thread=False) - cur = db.cursor() + cur = self._open_db(uri, timeout=2, uri=True).cursor() cur.execute('pragma table_info("up")').fetchone() self.log("ro: %r" % (db_path,)) except: @@ -160,7 +174,7 @@ class U2idx(object): if not cur: # on windows, this steals the write-lock from up2k.deferred_init -- # seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2 - cur = sqlite3.connect(db_path, timeout=2, check_same_thread=False).cursor() + cur = self._open_db(db_path, timeout=2).cursor() self.log("opened %r" % (db_path,)) self.cur[ptop] = cur @@ -173,6 +187,8 @@ class U2idx(object): if not HAVE_SQLITE3: return [], [], False + icase = self.args.srch_icase + q = "" v: Union[str, int] = "" va: list[Union[str, int]] = [] @@ -180,6 +196,7 @@ class U2idx(object): is_key = True is_size = False is_date = False + is_wark = False field_end = "" # closing parenthesis or whatever kw_key = ["(", ")", "and ", "or ", "not "] kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "] @@ -198,6 +215,8 @@ class U2idx(object): is_key = kw in kw_key uq = uq[len(kw) :] ok = True + if is_wark: + kw = "= " q += kw break @@ -232,9 +251,17 @@ class U2idx(object): elif v == "path": v = "trim(?||up.rd,'/')" va.append("\nrd") + if icase: + v = "casefold(%s)" % (v,) elif v == "name": v = "up.fn" + if icase: + v = "casefold(%s)" % (v,) + + elif v == "w": + v = "substr(up.w,1,16)" + is_wark = True elif v == "tags" or ptn_mt.match(v): have_mt = True @@ -247,7 +274,7 @@ class U2idx(object): v = "exists(select 1 from mt where mt.w = mtw and " + vq else: - raise Pebkac(400, "invalid key [{}]".format(v)) + raise Pebkac(400, "invalid key %r" % (v,)) q += v + " " continue @@ -276,6 +303,14 @@ class U2idx(object): is_size = False v = int(float(v) * 1024 * 1024) + elif is_wark: + is_wark = False + v = v.strip("*") + if len(v) > 16: + v = v[:16] + if len(v) < 16: + raise Pebkac(400, "w/filehash must be 16+ chars") + else: if v.startswith("*"): head = "'%'||" @@ -285,6 +320,12 @@ class U2idx(object): tail = "||'%'" v = v[:-1] + if icase and "casefold(" in q: + try: + v = unicode(v).casefold() + except: + v = unicode(v).lower() + q += " {}?{} ".format(head, tail) va.append(v) is_key = True @@ -319,7 +360,7 @@ class U2idx(object): uname: str, vols: list[VFS], uq: str, - uv: list[Union[str, int]], + uv: Union[list[str], list[Union[str, int]]], have_mt: bool, sort: bool, lim: int, @@ -391,7 +432,7 @@ class U2idx(object): fk_alg = 2 if "fka" in flags else 1 c = cur.execute(uq, tuple(vuv)) for hit in c: - w, ts, sz, rd, fn, ip, at = hit[:7] + w, ts, sz, rd, fn = hit[:5] if rd.startswith("//") or fn.startswith("//"): rd, fn = s3dec(rd, fn) diff --git a/copyparty/up2k.py b/copyparty/up2k.py index ae5ed07f..db6931b3 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -18,13 +18,15 @@ from copy import deepcopy from queue import Queue -from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E +from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E from .authsrv import LEELOO_DALLAS, SEESLOG, VFS, AuthSrv from .bos import bos from .cfg import vf_bmap, vf_cmap, vf_vmap from .fsutil import Fstab from .mtag import MParser, MTag from .util import ( + E_FS_CRIT, + E_FS_MEH, HAVE_SQLITE3, SYMTIME, VF_CAREFUL, @@ -60,6 +62,7 @@ from .util import ( sfsenc, spack, statdir, + trystat_shutil_copy2, ub64enc, unhumanize, vjoin, @@ -77,7 +80,7 @@ except: if HAVE_SQLITE3: import sqlite3 -DB_VER = 5 +DB_VER = 6 if True: # pylint: disable=using-constant-test from typing import Any, Optional, Pattern, Union @@ -86,7 +89,13 @@ if TYPE_CHECKING: from .svchub import SvcHub zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp" -CV_EXTS = set(zsg.split(",")) +ICV_EXTS = set(zsg.split(",")) + +zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv" +VCV_EXTS = set(zsg.split(",")) + +zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv" +ACV_EXTS = set(zsg.split(",")) zsg = "nohash noidx xdev xvol" VF_AFFECTS_INDEXING = set(zsg.split(" ")) @@ -141,6 +150,7 @@ class Up2k(object): self.salt = self.args.warksalt self.r_hash = re.compile("^[0-9a-zA-Z_-]{44}$") + self.abrt_key = "" self.gid = 0 self.gt0 = 0 @@ -205,7 +215,7 @@ class Up2k(object): t = "could not initialize sqlite3, will use in-memory registry only" self.log(t, 3) - self.fstab = Fstab(self.log_func, self.args) + self.fstab = Fstab(self.log_func, self.args, True) self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey if self.args.hash_mt < 2: @@ -372,11 +382,12 @@ class Up2k(object): if ineed == ihash or not ineed: continue + poke = job["poke"] zt = ( ineed / ihash, job["size"], - int(job["t0c"]), - int(job["poke"]), + int(job.get("t0c", poke)), + int(poke), djoin(vtop, job["prel"], job["name"]), ) ret.append(zt) @@ -406,10 +417,11 @@ class Up2k(object): ret: list[tuple[int, str, int, int, int]] = [] userset = set([(uname or "\n"), "*"]) + e_d = {} n = 1000 try: for ptop, tab2 in self.registry.items(): - cfg = self.flags.get(ptop, {}).get("u2abort", 1) + cfg = self.flags.get(ptop, e_d).get("u2abort", 1) if not cfg: continue addr = (ip or "\n") if cfg in (1, 2) else "" @@ -898,7 +910,7 @@ class Up2k(object): self.iacct = self.asrv.iacct self.grps = self.asrv.grps - have_e2d = self.args.idp_h_usr or self.args.chpw or self.args.shr + have_e2d = self.args.have_idp_hdrs or self.args.chpw or self.args.shr vols = list(all_vols.values()) t0 = time.time() @@ -918,6 +930,12 @@ class Up2k(object): with self.mutex, self.reg_mutex: # only need to protect register_vpath but all in one go feels right for vol in vols: + if bos.path.isfile(vol.realpath): + self.volstate[vol.vpath] = "online (just-a-file)" + t = "NOTE: volume [/%s] is a file, not a folder" + self.log(t % (vol.vpath,)) + continue + try: # mkdir gonna happen at snap anyways; bos.makedirs(vol.realpath, vf=vol.flags) @@ -1124,7 +1142,7 @@ class Up2k(object): ft = "\033[0;32m{}{:.0}" ff = "\033[0;35m{}{:.0}" fv = "\033[0;36m{}:\033[90m{}" - zs = "ext_th_d html_head 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 ext_th_d html_head html_head_d html_head_s 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()) @@ -1478,10 +1496,12 @@ class Up2k(object): unreg: list[str] = [] files: list[tuple[int, int, str]] = [] fat32 = True - cv = "" + cv = vcv = acv = "" + e_d = {} th_cvd = self.args.th_coversd th_cvds = self.args.th_coversd_set + scan_pr_s = self.args.scan_pr_s assert self.pp and self.mem_cur # !rm self.pp.msg = "a%d %s" % (self.pp.n, cdir) @@ -1573,25 +1593,26 @@ class Up2k(object): rsz += sz files.append((sz, lmod, iname)) - liname = iname.lower() - if ( - sz - and ( + if sz: + liname = iname.lower() + ext = liname.rsplit(".", 1)[-1] + if ( liname in th_cvds - or ( - not cv - and liname.rsplit(".", 1)[-1] in CV_EXTS - and not iname.startswith(".") - ) - ) - and ( + or (not cv and ext in ICV_EXTS and not iname.startswith(".")) + ) and ( not cv or liname not in th_cvds or cv.lower() not in th_cvds or th_cvd.index(liname) < th_cvd.index(cv.lower()) - ) - ): - cv = iname + ): + cv = iname + elif not vcv and ext in VCV_EXTS and not iname.startswith("."): + vcv = iname + elif not acv and ext in ACV_EXTS and not iname.startswith("."): + acv = iname + + if not cv: + cv = vcv or acv if not self.args.no_dirsz: tnf += len(files) @@ -1651,7 +1672,7 @@ class Up2k(object): abspath = cdirs + fn nohash = reh.search(abspath) if reh else False - sql = "select w, mt, sz, ip, at from up where rd = ? and fn = ?" + sql = "select w, mt, sz, ip, at, un from up where rd = ? and fn = ?" try: c = db.c.execute(sql, (rd, fn)) except: @@ -1660,7 +1681,7 @@ class Up2k(object): in_db = list(c.fetchall()) if in_db: self.pp.n -= 1 - dw, dts, dsz, ip, at = in_db[0] + dw, dts, dsz, ip, at, un = in_db[0] if len(in_db) > 1: t = "WARN: multiple entries: %r => %r |%d|\n%r" rep_db = "\n".join([repr(x) for x in in_db]) @@ -1673,9 +1694,12 @@ class Up2k(object): if dts == lmod and dsz == sz and (nohash or dw[0] != "#" or not sz): continue + if un is None: + un = "" + t = "reindex %r => %r mtime(%s/%s) size(%s/%s)" self.log(t % (top, rp, dts, lmod, dsz, sz)) - self.db_rm(db.c, rd, fn, 0) + self.db_rm(db.c, e_d, rd, fn, 0) tfa += 1 db.n += 1 in_db = [] @@ -1683,13 +1707,14 @@ class Up2k(object): dw = "" ip = "" at = 0 + un = "" self.pp.msg = "a%d %s" % (self.pp.n, abspath) if nohash or not sz: wark = up2k_wark_from_metadata(self.salt, sz, lmod, rd, fn) else: - if sz > 1024 * 1024: + if sz > 1024 * 1024 * scan_pr_s: self.log("file: %r" % (abspath,)) try: @@ -1697,7 +1722,7 @@ class Up2k(object): abspath, "a{}, ".format(self.pp.n) ) except Exception as ex: - self.log("hash: %r @ %r" % (ex, abspath)) + self._ex_hash(ex, abspath) continue if not hashes: @@ -1708,9 +1733,10 @@ class Up2k(object): if dw and dw != wark: ip = "" at = 0 + un = "" # skip upload hooks by not providing vflags - self.db_add(db.c, {}, rd, fn, lmod, sz, "", "", wark, wark, "", "", ip, at) + self.db_add(db.c, e_d, rd, fn, lmod, sz, "", "", wark, wark, "", un, ip, at) db.n += 1 db.nf += 1 tfa += 1 @@ -1770,7 +1796,7 @@ class Up2k(object): rm_files = [x for x in hits if x not in seen_files] n_rm = len(rm_files) for fn in rm_files: - self.db_rm(db.c, rd, fn, 0) + self.db_rm(db.c, e_d, rd, fn, 0) if n_rm: self.log("forgot {} deleted files".format(n_rm)) @@ -1957,7 +1983,7 @@ class Up2k(object): try: hashes, _ = self._hashlist_from_file(abspath, pf) except Exception as ex: - self.log("hash: %r @ %r" % (ex, abspath)) + self._ex_hash(ex, abspath) continue if not hashes: @@ -2147,8 +2173,8 @@ class Up2k(object): with self.mutex: try: - q = "select rd, fn, ip, at from up where substr(w,1,16)=? and +w=?" - rd, fn, ip, at = cur.execute(q, (w16, w)).fetchone() + q = "select rd, fn, ip, at, un from up where substr(w,1,16)=? and +w=?" + rd, fn, ip, at, un = cur.execute(q, (w16, w)).fetchone() except: # file modified/deleted since spooling continue @@ -2167,12 +2193,15 @@ 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) + n_tags = self._tagscan_file(cur, entags, w, abspath, ip, at, un) else: + oth_tags = {} if ip: - oth_tags = {"up_ip": ip, "up_at": at} - else: - oth_tags = {} + oth_tags["up_ip"] = ip + if at: + oth_tags["up_at"] = at + if un: + oth_tags["up_by"] = un mpool.put(Mpqe({}, entags, w, abspath, oth_tags)) with self.mutex: @@ -2328,8 +2357,8 @@ class Up2k(object): if w in in_progress: continue - q = "select rd, fn, ip, at from up where substr(w,1,16)=? limit 1" - rd, fn, ip, at = cur.execute(q, (w,)).fetchone() + q = "select rd, fn, ip, at, un from up where substr(w,1,16)=? limit 1" + rd, fn, ip, at, un = cur.execute(q, (w,)).fetchone() rd, fn = s3dec(rd, fn) abspath = djoin(ptop, rd, fn) @@ -2353,7 +2382,10 @@ class Up2k(object): if ip: oth_tags["up_ip"] = ip + if at: oth_tags["up_at"] = at + if un: + oth_tags["up_by"] = un jobs.append(Mpqe(parsers, set(), w, abspath, oth_tags)) in_progress[w] = True @@ -2542,6 +2574,7 @@ class Up2k(object): abspath: str, ip: str, at: float, + un: Optional[str], ) -> int: """will mutex(main)""" assert self.mtag # !rm @@ -2562,7 +2595,10 @@ class Up2k(object): if ip: tags["up_ip"] = ip + if at: tags["up_at"] = at + if un: + tags["up_by"] = un with self.mutex: return self._tag_file(write_cur, entags, wark, abspath, tags) @@ -2666,16 +2702,19 @@ class Up2k(object): if not existed and ver is None: return self._try_create_db(db_path, cur) - if ver == 4: + for upver in (4, 5): + if ver != upver: + continue try: t = "creating backup before upgrade: " cur = self._backup_db(db_path, cur, ver, t) - self._upgrade_v4(cur) - ver = 5 + getattr(self, "_upgrade_v%d" % (upver,))(cur) + ver += 1 # type: ignore except: - self.log("WARN: failed to upgrade from v4", 3) + self.log("WARN: failed to upgrade from v%d" % (ver,), 3) if ver == DB_VER: + # these no longer serve their intended purpose but they're great as additional sanchks self._add_dhash_tab(cur) self._add_xiu_tab(cur) self._add_cv_tab(cur) @@ -2733,7 +2772,7 @@ class Up2k(object): cur.close() db.close() - shutil.copy2(fsenc(db_path), fsenc(bak)) + trystat_shutil_copy2(self.log, fsenc(db_path), fsenc(bak)) return self._orz(db_path) def _read_ver(self, cur: "sqlite3.Cursor") -> Optional[int]: @@ -2777,7 +2816,7 @@ class Up2k(object): idx = r"create index up_w on up(w)" for cmd in [ - r"create table up (w text, mt int, sz int, rd text, fn text, ip text, at int)", + r"create table up (w text, mt int, sz int, rd text, fn text, ip text, at int, un text)", r"create index up_vp on up(rd, fn)", r"create index up_fn on up(fn)", r"create index up_ip on up(ip)", @@ -2810,6 +2849,15 @@ class Up2k(object): cur.connection.commit() + def _upgrade_v5(self, cur: "sqlite3.Cursor") -> None: + for cmd in [ + r"alter table up add column un text", + r"update kv set v=6 where k='sver'", + ]: + cur.execute(cmd) + + cur.connection.commit() + def _add_dhash_tab(self, cur: "sqlite3.Cursor") -> None: # v5 -> v5a try: @@ -2961,7 +3009,7 @@ class Up2k(object): # check if filesystem supports sparse files; # refuse out-of-order / multithreaded uploading if sprs False - sprs = self.fstab.get(pdir) != "ng" + sprs = self.fstab.get(pdir)[0] != "ng" if True: jcur = self.cur.get(ptop) @@ -3007,7 +3055,7 @@ class Up2k(object): argv = [dwark[:16], dwark] c2 = cur.execute(q, tuple(argv)) - for _, dtime, dsize, dp_dir, dp_fn, ip, at in c2: + for _, dtime, dsize, dp_dir, dp_fn, ip, at, _ in c2: if dp_dir.startswith("//") or dp_fn.startswith("//"): dp_dir, dp_fn = s3dec(dp_dir, dp_fn) @@ -3099,7 +3147,7 @@ class Up2k(object): for cur, dp_dir, dp_fn in lost: t = "forgetting desynced db entry: %r" self.log(t % ("/" + vjoin(vjoin(vfs.vpath, dp_dir), dp_fn))) - self.db_rm(cur, dp_dir, dp_fn, cj["size"]) + self.db_rm(cur, vfs.flags, dp_dir, dp_fn, cj["size"]) if c2 and c2 != cur: c2.connection.commit() @@ -3252,8 +3300,11 @@ class Up2k(object): job["at"], "", ) - if not hr: - t = "upload blocked by xbu server config: %r" % (dst,) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" + t = t % (vp,) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -3393,10 +3444,9 @@ class Up2k(object): cur.connection.commit() ap = djoin(job["ptop"], job["prel"], job["name"]) - times = (int(time.time()), int(cj["lmod"])) - bos.utime(ap, times, False) + mt = bos.utime_c(self.log, ap, int(cj["lmod"]), False, True) - self.log("touched %r from %d to %d" % (ap, job["lmod"], cj["lmod"])) + self.log("touched %r from %d to %d" % (ap, job["lmod"], mt)) except Exception as ex: self.log("umod failed, %r" % (ex,), 3) @@ -3429,8 +3479,8 @@ class Up2k(object): try: vrel = vjoin(job["prel"], fname) xlink = bool(vf.get("xlink")) - cur, wark, _, _, _, _ = self._find_from_vpath(ptop, vrel) - self._forget_file(ptop, vrel, cur, wark, True, st.st_size, xlink) + cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, vrel) + self._forget_file(ptop, vrel, vf, cur, wark, True, st.st_size, xlink) except Exception as ex: self.log("skipping replace-relink: %r" % (ex,)) finally: @@ -3548,11 +3598,10 @@ class Up2k(object): t = "BUG: no valid sources to link from! orig(%r) fsrc(%r) link(%r)" self.log(t, 1) raise Exception(t % (src, fsrc, dst)) - shutil.copy2(fsenc(csrc), fsenc(dst)) + trystat_shutil_copy2(self.log, fsenc(csrc), fsenc(dst)) if lmod and (not linked or SYMTIME): - times = (int(time.time()), int(lmod)) - bos.utime(dst, times, False) + bos.utime_c(self.log, dst, int(lmod), False) def handle_chunks( self, ptop: str, wark: str, chashes: list[str] @@ -3635,14 +3684,15 @@ class Up2k(object): t = t.format(job["name"], nchunks[0][0], coffsets[0][0], cur_sz) raise Pebkac(400, t) - job["busy"][chash] = 1 + for chash in chashes: + job["busy"][chash] = 1 job["poke"] = time.time() return chashes, chunksize, coffsets, path, job["lmod"], job["size"], job["sprs"] def fast_confirm_chunks( - self, ptop: str, wark: str, chashes: list[str] + self, ptop: str, wark: str, chashes: list[str], locked: list[str] ) -> tuple[int, str]: if not self.mutex.acquire(False): return -1, "" @@ -3650,7 +3700,7 @@ class Up2k(object): self.mutex.release() return -1, "" try: - return self._confirm_chunks(ptop, wark, chashes, chashes) + return self._confirm_chunks(ptop, wark, chashes, locked, False) finally: self.reg_mutex.release() self.mutex.release() @@ -3659,10 +3709,10 @@ class Up2k(object): self, ptop: str, wark: str, written: list[str], locked: list[str] ) -> tuple[int, str]: with self.mutex, self.reg_mutex: - return self._confirm_chunks(ptop, wark, written, locked) + return self._confirm_chunks(ptop, wark, written, locked, True) def _confirm_chunks( - self, ptop: str, wark: str, written: list[str], locked: list[str] + self, ptop: str, wark: str, written: list[str], locked: list[str], final: bool ) -> tuple[int, str]: if True: self.db_act = self.vol_act[ptop] = time.time() @@ -3674,14 +3724,16 @@ class Up2k(object): except Exception as ex: return -2, "confirm_chunk, wark(%r)" % (ex,) # type: ignore - for chash in locked: + for chash in locked if final else written: job["busy"].pop(chash, None) try: for chash in written: job["need"].remove(chash) except Exception as ex: - # dead tcp connections can get here by timeout (OK) + for zs in locked: + if job["busy"].pop(zs, None): + self.log("panic-unlock wark(%s) chunk(%s)" % (wark, zs), 1) return -2, "confirm_chunk, chash(%s) %r" % (chash, ex) # type: ignore ret = len(job["need"]) @@ -3725,10 +3777,8 @@ class Up2k(object): times = (int(time.time()), int(job["lmod"])) t = "no more chunks, setting times %s (%d) on %r" self.log(t % (times, bos.path.getsize(dst), dst)) - try: - bos.utime(dst, times) - except: - self.log("failed to utime (%r, %s)" % (dst, times)) + bos.utime_c(self.log, dst, times[1], False) + # the above logmsg (and associated logic) is retained due to unforget.py zs = "prel name lmod size ptop vtop wark dwrk host user addr" z2 = [job[x] for x in zs.split()] @@ -3847,7 +3897,9 @@ class Up2k(object): return True - def db_rm(self, db: "sqlite3.Cursor", rd: str, fn: str, sz: int) -> None: + def db_rm( + self, db: "sqlite3.Cursor", vflags: dict[str, Any], rd: str, fn: str, sz: int + ) -> None: sql = "delete from up where rd = ? and fn = ?" try: r = db.execute(sql, (rd, fn)) @@ -3855,9 +3907,22 @@ class Up2k(object): assert self.mem_cur # !rm r = db.execute(sql, s3enc(self.mem_cur, rd, fn)) - if r.rowcount: - self.volsize[db] -= sz - self.volnfiles[db] -= 1 + if not r.rowcount: + return + + self.volsize[db] -= sz + self.volnfiles[db] -= 1 + + if "nodirsz" not in vflags: + try: + q = "update ds set nf=nf-1, sz=sz-? where rd=?" + while True: + db.execute(q, (sz, rd)) + if not rd: + break + rd = rd.rsplit("/", 1)[0] if "/" in rd else "" + except: + pass def db_add( self, @@ -3878,7 +3943,7 @@ class Up2k(object): skip_xau: bool = False, ) -> None: """mutex(main) me""" - self.db_rm(db, rd, fn, sz) + self.db_rm(db, vflags, rd, fn, sz) if not ip: db_ip = "" @@ -3886,14 +3951,14 @@ class Up2k(object): # plugins may expect this to look like an actual IP db_ip = "1.1.1.1" if "no_db_ip" in vflags else ip - sql = "insert into up values (?,?,?,?,?,?,?)" - v = (dwark, int(ts), sz, rd, fn, db_ip, int(at or 0)) + sql = "insert into up values (?,?,?,?,?,?,?,?)" + v = (dwark, int(ts), sz, rd, fn, db_ip, int(at or 0), usr) try: db.execute(sql, v) except: assert self.mem_cur # !rm rd, fn = s3enc(self.mem_cur, rd, fn) - v = (dwark, int(ts), sz, rd, fn, db_ip, int(at or 0)) + v = (dwark, int(ts), sz, rd, fn, db_ip, int(at or 0), usr) db.execute(sql, v) self.volsize[db] += sz @@ -3919,8 +3984,11 @@ class Up2k(object): at or time.time(), "", ) - if not hr: - t = "upload blocked by xau server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xau server config: %r" + t = t % (djoin(vtop, rd, fn),) self.log(t, 1) wunlink(self.log, dst, vflags) self.registry[ptop].pop(wark, None) @@ -3985,6 +4053,9 @@ class Up2k(object): except: pass + def handle_fs_abrt(self, akey: str) -> None: + self.abrt_key = akey + def handle_rm( self, uname: str, @@ -4031,7 +4102,7 @@ class Up2k(object): vn, rem = vn0.get_dbv(rem0) ptop = vn.realpath with self.mutex, self.reg_mutex: - abrt_cfg = self.flags.get(ptop, {}).get("u2abort", 1) + abrt_cfg = vn.flags.get("u2abort", 1) addr = (ip or "\n") if abrt_cfg in (1, 2) else "" user = ((uname or "\n"), "*") if abrt_cfg in (1, 3) else None reg = self.registry.get(ptop, {}) if abrt_cfg else {} @@ -4052,17 +4123,22 @@ class Up2k(object): if partial: dip = ip dat = time.time() + dun = uname + un_cfg = 1 else: - if not self.args.unpost: + un_cfg = vn.flags["unp_who"] + if not self.args.unpost or not un_cfg: t = "the unpost feature is disabled in server config" raise Pebkac(400, t) - _, _, _, _, dip, dat = self._find_from_vpath(ptop, rem) + _, _, _, _, dip, dat, dun = self._find_from_vpath(ptop, rem) t = "you cannot delete this: " if not dip: t += "file not found" - elif dip != ip: + elif dip != ip and un_cfg in (1, 2): + t += "not uploaded by (You)" + elif dun != uname and un_cfg in (1, 3): t += "not uploaded by (You)" elif dat < time.time() - self.args.unpost: t += "uploaded too long ago" @@ -4082,6 +4158,9 @@ class Up2k(object): except: raise Pebkac(400, "file not found on disk (already deleted?)") + if "bcasechk" in vn.flags and not vn.casechk(rem, False): + raise Pebkac(400, "file does not exist case-sensitively") + scandir = not self.args.no_scandir if is_dir: # note: deletion inside shares would require a rewrite here; @@ -4151,9 +4230,9 @@ class Up2k(object): try: ptop = dbv.realpath xlink = bool(dbv.flags.get("xlink")) - cur, wark, _, _, _, _ = self._find_from_vpath(ptop, volpath) + cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, volpath) self._forget_file( - ptop, volpath, cur, wark, True, st.st_size, xlink + ptop, volpath, dbv.flags, cur, wark, True, st.st_size, xlink ) finally: if cur: @@ -4194,7 +4273,7 @@ class Up2k(object): return n_files, ok + ok2, ng + ng2 - def handle_cp(self, uname: str, ip: str, svp: str, dvp: str) -> str: + def handle_cp(self, abrt: str, uname: str, ip: str, svp: str, dvp: str) -> str: if svp == dvp or dvp.startswith(svp + "/"): raise Pebkac(400, "cp: cannot copy parent into subfolder") @@ -4206,6 +4285,9 @@ class Up2k(object): self.db_act = self.vol_act[svn_dbv.realpath] = time.time() st = bos.stat(sabs) + if "bcasechk" in svn.flags and not svn.casechk(srem, False): + raise Pebkac(400, "file does not exist case-sensitively") + if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode): with self.mutex: try: @@ -4241,6 +4323,8 @@ class Up2k(object): dvpf = dvp + svpf[len(svp) :] self._cp_file(uname, ip, svpf, dvpf, curs) + if abrt and abrt == self.abrt_key: + raise Pebkac(400, "filecopy aborted by http-api") for v in curs: v.connection.commit() @@ -4310,7 +4394,7 @@ class Up2k(object): bos.makedirs(os.path.dirname(dabs), vf=dvn.flags) - c1, w, ftime_, fsize_, ip, at = self._find_from_vpath( + c1, w, ftime_, fsize_, ip, at, un = self._find_from_vpath( svn_dbv.realpath, srem_dbv ) c2 = self.cur.get(dvn.realpath) @@ -4335,7 +4419,7 @@ class Up2k(object): w, w, "", - "", + un or "", ip or "", at or 0, ) @@ -4362,7 +4446,7 @@ class Up2k(object): b1, b2 = fsenc(sabs), fsenc(dabs) is_link = os.path.islink(b1) # due to _relink try: - shutil.copy2(b1, b2) + trystat_shutil_copy2(self.log, b1, b2) except: try: wunlink(self.log, dabs, dvn.flags) @@ -4408,7 +4492,7 @@ class Up2k(object): return "k" - def handle_mv(self, uname: str, ip: str, svp: str, dvp: str) -> str: + def handle_mv(self, abrt: str, uname: str, ip: str, svp: str, dvp: str) -> str: if svp == dvp or dvp.startswith(svp + "/"): raise Pebkac(400, "mv: cannot move parent into subfolder") @@ -4422,6 +4506,9 @@ class Up2k(object): raise Pebkac(400, "mv: cannot move a mountpoint") st = bos.lstat(sabs) + if "bcasechk" in svn.flags and not svn.casechk(srem, False): + raise Pebkac(400, "file does not exist case-sensitively") + if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode): with self.mutex: try: @@ -4463,6 +4550,8 @@ class Up2k(object): dvpf = dvp + svpf[len(svp) :] self._mv_file(uname, ip, svpf, dvpf, curs) + if abrt and abrt == self.abrt_key: + raise Pebkac(400, "filemove aborted by http-api") for v in curs: v.connection.commit() @@ -4594,7 +4683,7 @@ class Up2k(object): return "k" - c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(svn.realpath, srem) + c1, w, ftime_, fsize_, ip, at, un = self._find_from_vpath(svn.realpath, srem) c2 = self.cur.get(dvn.realpath) has_dupes = False @@ -4607,7 +4696,14 @@ class Up2k(object): with self.reg_mutex: has_dupes = self._forget_file( - svn.realpath, srem, c1, w, is_xvol, fsize_ or fsize, xlink + svn.realpath, + srem, + svn.flags, + c1, + w, + is_xvol, + fsize_ or fsize, + xlink, ) if not is_xvol: @@ -4628,7 +4724,7 @@ class Up2k(object): w, w, "", - "", + un or "", ip or "", at or 0, ) @@ -4659,7 +4755,7 @@ class Up2k(object): b1, b2 = fsenc(sabs), fsenc(dabs) is_link = os.path.islink(b1) # due to _relink try: - shutil.copy2(b1, b2) + trystat_shutil_copy2(self.log, b1, b2) except: try: wunlink(self.log, dabs, dvn.flags) @@ -4728,13 +4824,14 @@ class Up2k(object): Optional[int], str, Optional[int], + str, ]: cur = self.cur.get(ptop) if not cur: - return None, None, None, None, "", None + return None, None, None, None, "", None, "" rd, fn = vsplit(vrem) - q = "select w, mt, sz, ip, at from up where rd=? and fn=? limit 1" + q = "select w, mt, sz, ip, at, un from up where rd=? and fn=? limit 1" try: c = cur.execute(q, (rd, fn)) except: @@ -4743,14 +4840,15 @@ class Up2k(object): hit = c.fetchone() if hit: - wark, ftime, fsize, ip, at = hit - return cur, wark, ftime, fsize, ip, at - return cur, None, None, None, "", None + wark, ftime, fsize, ip, at, un = hit + return cur, wark, ftime, fsize, ip, at, un + return cur, None, None, None, "", None, "" def _forget_file( self, ptop: str, vrem: str, + vflags: dict[str, Any], cur: Optional["sqlite3.Cursor"], wark: Optional[str], drop_tags: bool, @@ -4775,7 +4873,7 @@ class Up2k(object): q = "delete from mt where w=?" cur.execute(q, (wark[:16],)) - self.db_rm(cur, srd, sfn, sz) + self.db_rm(cur, vflags, srd, sfn, sz) reg = self.registry.get(ptop) if reg: @@ -4864,7 +4962,10 @@ class Up2k(object): mt = bos.path.getmtime(slabs, False) flags = self.flags.get(ptop) or {} atomic_move(self.log, sabs, slabs, flags) - bos.utime(slabs, (int(time.time()), int(mt)), False) + try: + bos.utime(slabs, (int(time.time()), int(mt)), False) + except: + self.log("relink: failed to utime(%r, %s)" % (slabs, mt), 3) self._symlink(slabs, sabs, flags, False, is_mv=True) full[slabs] = (ptop, rem) sabs = slabs @@ -4937,7 +5038,7 @@ class Up2k(object): for k in cj["hash"]: if not self.r_hash.match(k): raise Pebkac( - 400, "at least one hash is not according to spec: {}".format(k) + 400, "at least one hash is not according to spec: %r" % (k,) ) # try to use client-provided timestamp, don't care if it fails somehow @@ -4994,6 +5095,16 @@ class Up2k(object): return ret, st + def _ex_hash(self, ex: Exception, ap: str) -> None: + eno = getattr(ex, "errno", 0) + if eno in E_FS_MEH: + return self.log("hashing failed; %r @ %r" % (ex, ap)) + if eno not in E_FS_CRIT: + return self.log("hashing failed; %r @ %r\n%s" % (ex, ap, min_ex()), 3) + t = "hashing failed; %r @ %r\n%s\nWARNING: This MAY indicate a serious issue with your harddisk or filesystem! Please investigate %sOS-logs\n" + t2 = "" if ANYWIN or MACOS else "dmesg and " + return self.log(t % (ex, ap, min_ex(), t2), 1) + def _new_upload(self, job: dict[str, Any], vfs: VFS, depth: int) -> dict[str, str]: pdir = djoin(job["ptop"], job["prel"]) if not job["size"]: @@ -5027,8 +5138,10 @@ class Up2k(object): job["t0"], "", ) - if not hr: - t = "upload blocked by xbu server config: %r" % (vp_chk,) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" % (vp_chk,) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -5090,7 +5203,7 @@ class Up2k(object): sprs = False if not ANYWIN and sprs and sz > 1024 * 1024: - fs = self.fstab.get(pdir) + fs, mnt = self.fstab.get(pdir) if fs == "ok": pass elif "nosparse" in vf: @@ -5176,17 +5289,21 @@ class Up2k(object): self.log("\n".join([t] + vis)) for job in rm: del reg[job["wark"]] + rsv_cleared = False try: # remove the filename reservation path = djoin(job["ptop"], job["prel"], job["name"]) if bos.path.getsize(path) == 0: bos.unlink(path) + rsv_cleared = True except: pass try: - if len(job["hash"]) == len(job["need"]): - # PARTIAL is empty, delete that too + if len(job["hash"]) == len(job["need"]) or ( + rsv_cleared and "rm_partial" in self.flags[job["ptop"]] + ): + # PARTIAL is empty (hash==need) or --rm-partial, so delete that too path = djoin(job["ptop"], job["prel"], job["tnam"]) bos.unlink(path) except: diff --git a/copyparty/util.py b/copyparty/util.py index 15b1924b..67391967 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -52,6 +52,7 @@ from .__init__ import ( VT100, WINDOWS, EnvParams, + unicode, ) from .__version__ import S_BUILD_DT, S_VERSION from .stolen import surrogateescape @@ -110,9 +111,17 @@ E_ADDR_NOT_AVAIL = _ens("EADDRNOTAVAIL WSAEADDRNOTAVAIL") E_ADDR_IN_USE = _ens("EADDRINUSE WSAEADDRINUSE") E_ACCESS = _ens("EACCES WSAEACCES") E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH") +E_FS_MEH = _ens("EPERM EACCES ENOENT ENOTCAPABLE") +E_FS_CRIT = _ens("EIO EFAULT EUCLEAN ENOTBLK") IP6ALL = "0:0:0:0:0:0:0:0" +IP6_LL = ("fe8", "fe9", "fea", "feb") +IP64_LL = ("fe8", "fe9", "fea", "feb", "169.254") +UC_CDISP = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._" +BC_CDISP = UC_CDISP.encode("ascii") +UC_CDISP_SET = set(UC_CDISP) +BC_CDISP_SET = set(BC_CDISP) try: import fcntl @@ -369,6 +378,13 @@ DAV_ALLPROP_L = [ DAV_ALLPROPS = set(DAV_ALLPROP_L) +FAVICON_MIMES = { + "gif": "image/gif", + "png": "image/png", + "svg": "image/svg+xml", +} + + MIMES = { "opus": "audio/ogg; codecs=opus", "owa": "audio/webm; codecs=opus", @@ -399,6 +415,9 @@ application swf=x-shockwave-flash m3u=vnd.apple.mpegurl db3=vnd.sqlite3 sqlite=v text ass=plain ssa=plain image jpg=jpeg xpm=x-xpixmap psd=vnd.adobe.photoshop jpf=jpx tif=tiff ico=x-icon djvu=vnd.djvu image heic=heic-sequence heif=heif-sequence hdr=vnd.radiance svg=svg+xml +image arw=x-sony-arw cr2=x-canon-cr2 crw=x-canon-crw dcr=x-kodak-dcr dng=x-adobe-dng erf=x-epson-erf +image k25=x-kodak-k25 kdc=x-kodak-kdc mrw=x-minolta-mrw nef=x-nikon-nef orf=x-olympus-orf +image pef=x-pentax-pef raf=x-fuji-raf raw=x-panasonic-raw sr2=x-sony-sr2 srf=x-sony-srf x3f=x-sigma-x3f audio caf=x-caf mp3=mpeg m4a=mp4 mid=midi mpc=musepack aif=aiff au=basic qcp=qcelp video mkv=x-matroska mov=quicktime avi=x-msvideo m4v=x-m4v ts=mp2t video asf=x-ms-asf flv=x-flv 3gp=3gpp 3g2=3gpp2 rmvb=vnd.rn-realmedia-vbr @@ -420,7 +439,7 @@ EXTS["vnd.mozilla.apng"] = "png" MAGIC_MAP = {"jpeg": "jpg"} -DEF_EXP = "self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf_ipcountry srv.itime srv.htime" +DEF_EXP = "self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime" DEF_MTE = ".files,circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash" @@ -536,6 +555,8 @@ def py_desc() -> str: ofs = py_ver.find(".final.") if ofs > 0: py_ver = py_ver[:ofs] + if "free-threading" in sys.version: + py_ver += "t" host_os = platform.system() compiler = platform.python_compiler().split("http")[0] @@ -646,6 +667,9 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool) with open(ap, "rb") as f: buf = f.read() + if buf.startswith(b"\xef\xbb\xbf"): + buf = buf[3:] + try: return buf.decode("utf-8", "strict") except UnicodeDecodeError as ex: @@ -1111,16 +1135,18 @@ class ProgressPrinter(threading.Thread): sigblock() tp = 0 msg = None - no_stdout = self.args.q + slp_pr = self.args.scan_pr_r + slp_ps = min(slp_pr, self.args.scan_st_r) + no_stdout = self.args.q or slp_pr == slp_ps fmt = " {}\033[K\r" if VT100 else " {} $\r" while not self.end: - time.sleep(0.1) + time.sleep(slp_ps) if msg == self.msg or self.end: continue msg = self.msg now = time.time() - if msg and now - tp > 10: + if msg and now - tp >= slp_pr: tp = now self.log("progress: %r" % (msg,), 6) @@ -1178,21 +1204,21 @@ class MTHash(object): for nch in range(nchunks): self.work_q.put(nch) - ex = "" + ex: Optional[Exception] = None for nch in range(nchunks): qe = self.done_q.get() try: nch, dig, ofs, csz = qe chunks[nch] = (dig, ofs, csz) except: - ex = ex or str(qe) + ex = ex or qe # type: ignore if pp: mb = (fsz - nch * chunksz) // (1024 * 1024) pp.msg = prefix + str(mb) + suffix if ex: - raise Exception(ex) + raise ex ret = [] for n in range(nchunks): @@ -1209,7 +1235,7 @@ class MTHash(object): try: v = self.hash_at(ofs) except Exception as ex: - v = str(ex) # type: ignore + v = ex # type: ignore self.done_q.put(v) @@ -1567,10 +1593,12 @@ def vol_san(vols: list["VFS"], txt: bytes) -> bytes: bvp = vol.vpath.encode("utf-8") bvph = b"$hist(/" + bvp + b")" - txt = txt.replace(bap, bvp) - txt = txt.replace(bhp, bvph) - txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp) - txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph) + if bap: + txt = txt.replace(bap, bvp) + txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp) + if bhp: + txt = txt.replace(bhp, bvph) + txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph) if vol.histpath != vol.dbpath: bdp = vol.dbpath.encode("utf-8") @@ -1749,12 +1777,12 @@ class MultipartParser(object): continue if m.group(1).lower() != "form-data": - raise Pebkac(400, "not form-data: {}".format(ln)) + raise Pebkac(400, "not form-data: %r" % (ln,)) try: field = self.re_cdisp_field.match(ln).group(1) # type: ignore except: - raise Pebkac(400, "missing field name: {}".format(ln)) + raise Pebkac(400, "missing field name: %r" % (ln,)) try: fn = self.re_cdisp_file.match(ln).group(1) # type: ignore @@ -1926,7 +1954,7 @@ def get_boundary(headers: dict[str, str]) -> str: ct = headers["content-type"] m = re.match(ptn, ct, re.IGNORECASE) if not m: - raise Pebkac(400, "invalid content-type for a multipart post: {}".format(ct)) + raise Pebkac(400, "invalid content-type for a multipart post: %r" % (ct,)) return m.group(2) @@ -2068,6 +2096,29 @@ def gencookie( ) +def gen_content_disposition(fn: str) -> str: + safe = UC_CDISP_SET + bsafe = BC_CDISP_SET + fn = fn.replace("/", "_").replace("\\", "_") + zb = fn.encode("utf-8", "xmlcharrefreplace") + if not PY2: + zbl = [ + chr(x).encode("utf-8") + if x in bsafe + else "%{:02X}".format(x).encode("ascii") + for x in zb + ] + else: + zbl = [unicode(x) if x in bsafe else "%{:02X}".format(ord(x)) for x in zb] + + ufn = b"".join(zbl).decode("ascii") + afn = "".join([x if x in safe else "_" for x in fn]).lstrip(".") + while ".." in afn: + afn = afn.replace("..", ".") + + return "attachment; filename=\"%s\"; filename*=UTF-8''%s" % (afn, ufn) + + def humansize(sz: float, terse: bool = False) -> str: for unit in HUMANSIZE_UNITS: if sz < 1024: @@ -2075,6 +2126,7 @@ def humansize(sz: float, terse: bool = False) -> str: sz /= 1024.0 + assert unit # type: ignore # !rm if terse: return "%s%s" % (str(sz)[:4].rstrip("."), unit[:1]) else: @@ -2227,14 +2279,14 @@ def odfusion( ret = base.copy() if oth.startswith("+"): for k in words1: - ret[k] = True + ret[k] = True # type: ignore elif oth[:1] in ("-", "/"): for k in words1: - ret.pop(k, None) + ret.pop(k, None) # type: ignore else: ret = ODict.fromkeys(words0, True) - return ret + return ret # type: ignore def ipnorm(ip: str) -> str: @@ -2606,6 +2658,24 @@ def set_fperms(f: Union[typing.BinaryIO, typing.IO[Any]], vf: dict[str, Any]) -> os.fchown(fno, vf["uid"], vf["gid"]) +def trystat_shutil_copy2(log: "NamedLogger", src: bytes, dst: bytes) -> bytes: + try: + return shutil.copy2(src, dst) + except: + # ignore failed mtime on linux+ntfs; for example: + # shutil.py:437 <copy2>: copystat(src, dst, follow_symlinks=follow_symlinks) + # shutil.py:376 <copystat>: lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), + # [PermissionError] [Errno 1] Operation not permitted, '/windows/_videos' + _, _, tb = sys.exc_info() + for _, _, fun, _ in traceback.extract_tb(tb): + if fun == "copystat": + if log: + t = "warning: failed to retain some file attributes (timestamp and/or permissions) during copy from %r to %r:\n%s" + log(t % (src, dst, min_ex()), 3) + return dst # close enough + raise + + def _fs_mvrm( log: "NamedLogger", src: str, dst: str, atomic: bool, flags: dict[str, Any] ) -> bool: @@ -2644,7 +2714,7 @@ def _fs_mvrm( t = "something appeared at dst; aborting rename %r ==> %r" log(t % (src, dst), 1) return False - osfun(*args) + osfun(*args) # type: ignore if attempt: now = time.time() t = "%sd in %.2f sec, attempt %d: %r" @@ -2694,7 +2764,7 @@ def atomic_move(log: "NamedLogger", src: str, dst: str, flags: dict[str, Any]) - os.unlink(bdst) except: pass - shutil.move(bsrc, bdst) + shutil.move(bsrc, bdst) # type: ignore def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool: @@ -2737,6 +2807,8 @@ def get_df(abspath: str, prune: bool) -> tuple[int, int, str]: if not ANYWIN and not MACOS: def siocoutq(sck: socket.socket) -> int: + assert fcntl # type: ignore # !rm + assert termios # type: ignore # !rm # SIOCOUTQ^sockios.h == TIOCOUTQ^ioctl.h try: zb = fcntl.ioctl(sck.fileno(), termios.TIOCOUTQ, b"AAAA") @@ -2951,6 +3023,27 @@ def load_ipu( return ip_u, nm +def load_ipr( + log: "RootLogger", iprs: list[str], defer_mutex: bool = False +) -> dict[str, NetMap]: + ret = {} + for ipr in iprs: + try: + zs, uname = ipr.split("=") + cidrs = zs.split(",") + except: + t = "\n invalid value %r for argument --ipr; must be CIDR[,CIDR[,...]]=UNAME (192.168.0.0/16=amelia)" + raise Exception(t % (ipr,)) + try: + nm = NetMap(["::"], cidrs, True, True, defer_mutex) + except Exception as ex: + t = "failed to translate --ipr into netmap, probably due to invalid config: %r" + log("root", t % (ex,), 1) + raise + ret[uname] = nm + return ret + + def yieldfile(fn: str, bufsz: int) -> Generator[bytes, None, None]: readsz = min(bufsz, 128 * 1024) with open(fsenc(fn), "rb", bufsz) as f: @@ -2982,6 +3075,17 @@ def justcopy( return tlen, "checksum-disabled", "checksum-disabled" +def eol_conv( + fin: Generator[bytes, None, None], conv: str +) -> Generator[bytes, None, None]: + crlf = conv.lower() == "crlf" + for buf in fin: + buf = buf.replace(b"\r", b"") + if crlf: + buf = buf.replace(b"\n", b"\r\n") + yield buf + + def hashcopy( fin: Generator[bytes, None, None], fout: Union[typing.BinaryIO, typing.IO[Any]], @@ -3068,7 +3172,7 @@ def sendfile_kern( try: req = min(0x2000000, upper - ofs) # 32 MiB if use_poll: - poll.poll(10000) + poll.poll(10000) # type: ignore else: select.select([], [out_fd], [], 10) n = os.sendfile(out_fd, in_fd, ofs, req) @@ -3124,8 +3228,9 @@ def statdir( else: src = "listdir" fun: Any = os.lstat if lstat else os.stat + btop_ = os.path.join(btop, b"") for name in os.listdir(btop): - abspath = os.path.join(btop, name) + abspath = btop_ + name try: yield (fsdec(name), fun(abspath)) except Exception as ex: @@ -3164,7 +3269,9 @@ def rmdirs( stats = statdir(logger, scandir, lstat, top, False) dirs = [x[0] for x in stats if stat.S_ISDIR(x[1].st_mode)] - dirs = [os.path.join(top, x) for x in dirs] + if dirs: + top_ = os.path.join(top, "") + dirs = [top_ + x for x in dirs] ok = [] ng = [] for d in reversed(dirs): @@ -3355,7 +3462,9 @@ NICEB = NICES.encode("utf-8") def runcmd( - argv: Union[list[bytes], list[str]], timeout: Optional[float] = None, **ka: Any + argv: Union[list[bytes], list[str], list["LiteralString"]], + timeout: Optional[float] = None, + **ka: Any ) -> tuple[int, str, str]: isbytes = isinstance(argv[0], (bytes, bytearray)) oom = ka.pop("oom", 0) # 0..1000 @@ -3374,19 +3483,19 @@ def runcmd( if ANYWIN: if isbytes: if argv[0] in CMD_EXEB: - argv[0] += b".exe" + argv[0] += b".exe" # type: ignore else: if argv[0] in CMD_EXES: - argv[0] += ".exe" + argv[0] += ".exe" # type: ignore if ka.pop("nice", None): if WINDOWS: ka["creationflags"] = 0x4000 elif NICEB: if isbytes: - argv = [NICEB] + argv + argv = [NICEB] + argv # type: ignore else: - argv = [NICES] + argv + argv = [NICES] + argv # type: ignore p = sp.Popen(argv, stdout=cout, stderr=cerr, **ka) @@ -3398,10 +3507,10 @@ def runcmd( pass if not timeout or PY2: - bout, berr = p.communicate(sin) + bout, berr = p.communicate(sin) # type: ignore else: try: - bout, berr = p.communicate(sin, timeout=timeout) + bout, berr = p.communicate(sin, timeout=timeout) # type: ignore except sp.TimeoutExpired: if kill == "n": return -18, "", "" # SIGCONT; leave it be @@ -3411,7 +3520,7 @@ def runcmd( killtree(p.pid) try: - bout, berr = p.communicate(timeout=1) + bout, berr = p.communicate(timeout=1) # type: ignore except: bout = b"" berr = b"" @@ -3567,7 +3676,7 @@ def runihook( verbose: bool, cmd: str, vol: "VFS", - ups: list[tuple[str, int, int, str, str, str, int]], + ups: list[tuple[str, int, int, str, str, str, int, str]], ) -> bool: _, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd) bcmd = [sfsenc(x) for x in acmd] @@ -3834,7 +3943,7 @@ def runhook( txt: str, ) -> dict[str, Any]: assert broker or up2k # !rm - args = (broker or up2k).args + args = (broker or up2k).args # type: ignore verbose = args.hook_v vp = vp.replace("\\", "/") ret = {"rc": 0} @@ -3852,6 +3961,7 @@ def runhook( if broker: broker.say("up2k.hook_fx", k, v, vp) else: + assert up2k # !rm up2k.fx_backlog.append((k, v, vp)) elif k == "reloc" and v: # idk, just take the last one ig @@ -4011,6 +4121,8 @@ def termsize() -> tuple[int, int]: env = os.environ def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]: + assert fcntl # type: ignore # !rm + assert termios # type: ignore # !rm try: cr = sunpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA")) return cr[::-1] @@ -4140,7 +4252,7 @@ def _pkg_resource_exists(pkg: str, name: str) -> bool: def stat_resource(E: EnvParams, name: str): - path = os.path.join(E.mod, name) + path = E.mod_ + name if os.path.exists(path): return os.stat(fsenc(path)) return None @@ -4187,7 +4299,7 @@ def _has_resource(name: str): def has_resource(E: EnvParams, name: str): - return _has_resource(name) or os.path.exists(os.path.join(E.mod, name)) + return _has_resource(name) or os.path.exists(E.mod_ + name) def load_resource(E: EnvParams, name: str, mode="rb") -> IO[bytes]: @@ -4212,7 +4324,7 @@ def load_resource(E: EnvParams, name: str, mode="rb") -> IO[bytes]: stream = codecs.getreader(enc)(stream) return stream - ap = os.path.join(E.mod, name) + ap = E.mod_ + name if PY2: return codecs.open(ap, "r", encoding=enc) # type: ignore diff --git a/copyparty/web/baguettebox.js b/copyparty/web/baguettebox.js index adcf3239..0d18aa2a 100644 --- a/copyparty/web/baguettebox.js +++ b/copyparty/web/baguettebox.js @@ -44,10 +44,12 @@ window.baguetteBox = (function () { loopA = null, loopB = null, url_ts = null, + un_pp = 0, resume_mp = false; var onFSC = function (e) { isFullscreen = !!document.fullscreenElement; + clmod(document.documentElement, 'bb_fsc', isFullscreen); }; var overlayClickHandler = function (e) { @@ -251,7 +253,7 @@ window.baguetteBox = (function () { ['S', 'toggle file selection'], ['space, P, K', 'video: play / pause'], ['U', 'video: seek 10sec back'], - ['P', 'video: seek 10sec ahead'], + ['O', 'video: seek 10sec ahead'], ['0..9', 'video: seek 0%..90%'], ['M', 'video: toggle mute'], ['V', 'video: toggle loop'], @@ -277,23 +279,30 @@ window.baguetteBox = (function () { if (modal.busy) return; - if (e.key == '?') - return halp(); - if (anymod(e, true)) return; - var k = (e.code || e.key) + '', v = vid(), pos = -1; + var k = (e.key || e.code) + '', v = vid(); - if (k == "BracketLeft") + if (k.startsWith('Key')) + k = k.slice(3); + else if (k.startsWith('Digit')) + k = k.slice(5); + + var kl = k.toLowerCase(); + + if (k == '?') + return halp(); + + if (k == "[" || k == "BracketLeft") setloop(1); - else if (k == "BracketRight") + else if (k == "]" || k == "BracketRight") setloop(2); - else if (e.shiftKey && k != "KeyR" && k != "R") + else if (e.shiftKey && kl != "r") return; - else if (k == "ArrowLeft" || k == "KeyJ" || k == "Left" || k == "j") + else if (k == "ArrowLeft" || k == "Left" || kl == "j") showPreviousImage(); - else if (k == "ArrowRight" || k == "KeyL" || k == "Right" || k == "l") + else if (k == "ArrowRight" || k == "Right" || kl == "l") showNextImage(); else if (k == "Escape" || k == "Esc") hideOverlay(); @@ -301,34 +310,37 @@ window.baguetteBox = (function () { showFirstImage(e); else if (k == "End") showLastImage(e); - else if (k == "Space" || k == "KeyP" || k == "KeyK") + else if (k == "Space" || k == "Spacebar" || kl == " " || kl == "p" || kl == "k") playpause(); - else if (k == "KeyU" || k == "KeyO") - relseek(k == "KeyU" ? -10 : 10); - else if (k.indexOf('Digit') === 0 && v) - v.currentTime = v.duration * parseInt(k.slice(-1)) * 0.1; - else if (k == "KeyM" && v) { + else if (kl == "u" || kl == "o") + relseek(kl == "u" ? -10 : 10); + else if (v && /^[0-9]$/.test(k)) + v.currentTime = v.duration * parseInt(k) * 0.1; + else if (kl == "m" && v) { v.muted = vmute = !vmute; mp_ctl(); } - else if (k == "KeyV" && v) { + else if (kl == "v" && v) { vloop = !vloop; vnext = vnext && !vloop; setVmode(); } - else if (k == "KeyC" && v) { + else if (kl == "c" && v) { vnext = !vnext; vloop = vloop && !vnext; setVmode(); } - else if (k == "KeyF") + else if (kl == "f") tglfull(); - else if (k == "KeyS" || k == "s") + else if (kl == "s") tglsel(); - else if (k == "KeyR" || k == "r" || k == "R") + else if (kl == "r") rotn(e.shiftKey ? -1 : 1); - else if (k == "KeyY") + else if (kl == "y") dlpic(); + else + return; + return ev(e); } function anim() { @@ -402,7 +414,7 @@ window.baguetteBox = (function () { if (isFullscreen) document.exitFullscreen(); else - (vid() || ebi('bbox-overlay')).requestFullscreen(); + ebi('bbox-overlay').requestFullscreen(); } catch (ex) { if (IPHONE) @@ -449,10 +461,12 @@ window.baguetteBox = (function () { if (anymod(e)) return; - var k = e.code + ''; + var k = (e.key || e.code) + ''; - if (k == "Space") - ev(e); + if (k == "Space" || k == "Spacebar" || k == " ") { + un_pp = Date.now(); + return ev(e); + } } var passiveSupp = false; @@ -777,8 +791,7 @@ window.baguetteBox = (function () { image.setAttribute('playsinline', '1'); // ios ignores poster image.onended = vidEnd; - image.onplay = function () { show_buttons(1); }; - image.onpause = function () { show_buttons(); }; + image.onplay = image.onpause = ppHandler; } image.alt = thumbnailElement ? thumbnailElement.alt || '' : ''; if (options.titleTag && imageCaption) @@ -793,6 +806,15 @@ window.baguetteBox = (function () { callback(); } + function ppHandler() { + var now = Date.now(); + if (now - un_pp < 50) { + un_pp = 0; + return playpause(); // browser undid space hotkey + } + show_buttons(this.paused ? 1 : 0); + } + function showNextImage(e) { ev(e); return show(currentIndex + 1); diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 35dbeeac..4023b221 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -84,6 +84,13 @@ --sort-1: #fb0; --sort-2: #d09; + --sz-b: #aaa; + --sz-k: #4ff; + --sz-m: var(--tab-alt); + --sz-g: var(--a); + --sz-t: var(--sz-g); + --sz-p: var(--sz-t); + --srv-1: #aaa; --srv-2: #a73; --srv-3: #f4c; @@ -187,6 +194,9 @@ html.y { --sort-1: #059; --sort-2: #f5d; + --sz-b: #777; + --sz-k: #380; + --srv-1: #555; --srv-2: #c83; --srv-3: #c0a; @@ -344,6 +354,9 @@ html.cz { --btn-1-bb: .2em solid #e90; --btn-1-bs: 0 .1em .8em rgba(255,205,0,0.9); + --sz-b: #ddd; + --sz-k: #c9f; + --srv-3: #fff; --u2-tab-b1: var(--bg-d3); @@ -571,6 +584,7 @@ pre, code, tt, #doc, #doc>code { overflow: hidden; width: 0; height: 0; + left: -10em; color: var(--bg); } html .ayjump:focus { @@ -739,6 +753,15 @@ html.y #files tr.fade a { #files tbody tr td:last-child { white-space: nowrap; } +#files span.fsz_B { color: var(--sz-b); } +#files span.fsz_K { color: var(--sz-k); } +#files span.fsz_M { color: var(--sz-m); } +#files span.fsz_G { color: var(--sz-g); } +#files span.fsz_T { color: var(--sz-t); } +#files span.fsz_P { color: var(--sz-p); } +html.y #files span.fsz_G, +html.y #files span.fsz_T, +html.y #files span.fsz_P { font-weight: bold } #files thead th[style] { width: auto !important; } @@ -881,6 +904,9 @@ html.y #path a:hover { #flogout { display: inline; } +html.dz #flogout { + margin-left: 1em; +} #goh+span { color: var(--bg-u5); padding-left: .5em; @@ -1113,18 +1139,7 @@ html.y #widget.open { top: -.12em; } #wtico { - cursor: url(dd/4.png), pointer; - animation: cursor 500ms; -} -#wtico:hover { - animation: cursor 500ms infinite; -} -@keyframes cursor { - 0% {cursor: url(dd/2.png), pointer} - 30% {cursor: url(dd/3.png), pointer} - 50% {cursor: url(dd/4.png), pointer} - 75% {cursor: url(dd/5.png), pointer} - 85% {cursor: url(dd/4.png), pointer} + cursor: pointer; } @keyframes spin { 100% {transform: rotate(360deg)} @@ -1162,6 +1177,9 @@ html.y #widget.open { border: 1px solid var(--bg-u5); border-width: 0 .1em 0 0; } +#wzip1 { + margin-right: .2em; +} #wfm.act+#wzip1+#wzip, #wfm.act+#wzip1+#wzip+#wnp { margin-left: .2em; @@ -1385,6 +1403,7 @@ html.y #ops svg circle { #op_cfg input[type=text] { top: -.3em; } +.opview select, .opview input[type=text] { color: var(--fg); background: var(--txt-bg); @@ -1395,6 +1414,11 @@ html.y #ops svg circle { border-radius: .2em; padding: .2em .3em; } +.opview select { + padding: .3em; + margin: .2em .4em; + background: var(--bg-u3); +} .opview input.err { color: var(--err-fg); background: var(--err-bg); @@ -1559,11 +1583,13 @@ html { #treepar { z-index: 1; position: fixed; + background: #fff; background: var(--tree-bg); left: -.96em; width: calc(.3em + var(--nav-sz) - var(--sbw)); border-bottom: 1px solid var(--bg-u5); overflow: hidden; + border-right: .5em solid #999\9; } #treepar.off { display: none; @@ -1936,6 +1962,11 @@ html.y #tree.nowrap .ntree a+a:hover { padding: 0; font-size: 1.5em; } +#fs_abrt { + margin-top: 1em; + text-shadow: 0; + box-shadow: 1px 1px 0 var(--bg-d3); +} #doc { overflow: visible; background: #fff; @@ -1994,6 +2025,7 @@ html.y #doc .line-highlight { } #seldoc.sel { color: var(--fg2-max); + background: #f0f; background: var(--g-sel-b1); } #pvol, @@ -2013,6 +2045,7 @@ a.btn, user-select: none; } #hkhelp { + background: #fff; background: var(--bg); } #hkhelp table { @@ -2126,6 +2159,13 @@ html.noscroll .sbar::-webkit-scrollbar { vertical-align: middle; transition: transform .23s, left .23s, top .23s, width .23s, height .23s; } +html.bb_fsc .full-image img, +html.bb_fsc .full-image video { + max-height: 100%; +} +html.bb_fsc figcaption { + display: none; +} .full-image img.nt, .full-image video.nt { transition: none; @@ -3278,3 +3318,793 @@ html.d #treepar { transition: background-color .3s ease, color .3s ease; } } +html.ey { + --negative-space: 0em; /* Use this to change the global spacing of the 95 theme */ + --font-main: consolas; + --font-serif: consolas; + --font-mono: consolas; + --w: #fff; + --w2: #dfdfdf; + --w3: grey; + --fg: #000; + --fg-max: #0000ff; + --fg-weak: #0000ff; + --bg: #c6c3c6; + --bg-d3: #ff0; + --bg-d2: var(--w3); + --bg-d1: var(--bg); + --bg-u2: var(--bg); + --bg-u3: var(--bg); + --bg-u5: var(--shadow-color-2); + --tab-alt: #00f; + --g-fsel-bg: #00f; + --g-sel-bg: #00f; + --g-fsel-b1: #fff; + --row-alt: var(--w); + --scroll: var(--silver); + --f-sel-sh: transparent; + --a: #000; + --a-b: #fff; + --a-hil: #fff; + --a-h-bg: var(--bg); + --a-dark: var(--a); + --a-gray: var(--fg-weak); + --btn-fg: var(--fg); + --btn-bg: var(--bg); + --btn-h-fg: var(--fg); + --btn-h-bg: var(--bg); + --btn-1-fg: var(--fg); + --btn-1-bg: var(--bg); + --btn-1h-bg: var(--bg-d3); + --txt-sh: a; + --txt-bg: var(--white); + --u2-b1-bg: var(--w2); + --u2-b2-bg: var(--w2); + --u2-txt-bg: var(--w2); + --u2-tab-bg: a; + --u2-tab-1-bg: var(--w2); + --sort-1: var(--fg-weak); + --tree-bg: var(--w); + --g-b1: a; + --g-b2: a; + --g-f-bg: var(--w2); + --f-sh1: 0.1; + --f-sh2: 0.02; + --f-sh3: 0.1; + --f-h-b1: a; + --srv-1: var(--w); + --srv-3: var(--a); + --mp-sh: a; + --black: #000; + --white: #fff; + --grey: grey; + --silver: silver; + --transparent: transparent; + --shadow-color-1: #0a0a0a; + --shadow-color-2: #808080; + --border-dashed-black: 1px dashed var(--black); + --radius: 0; + --focus-outline: 1px dashed var(--black); + --hover-outline: 1px dotted var(--black); + --fm-off: var(--w3); + --ttlbar: linear-gradient(90deg, navy, #1084d0); + --inset-bg: var(--white); + --scroll-bkg: var(--white); + + /*All sides*/ + --shadow-outset: inset -1px -1px var(--shadow-color-1), + inset 1px 1px var(--white), inset -2px -2px var(--grey), + inset 2px 2px var(--w2); + + --shadow-inset: inset -1px -1px var(--white), + inset 1px 1px var(--shadow-color-1), inset -2px -2px var(--w2), + inset 2px 2px var(--shadow-color-2); + + --shadow-input: inset -1px -1px var(--white), inset 1px 1px var(--grey), + inset -2px -2px var(--w2), inset 2px 2px var(--shadow-color-1); + + /*Indiv sides*/ + --shadow-outset-bottom: inset 0 -1px var(--shadow-color-1), + inset 0 -2px var(--grey); + --shadow-outset-right: inset -1px 0 var(--shadow-color-1), + inset -2px 0 var(--grey); + --shadow-outset-left: inset 1px 0 var(--white), inset 2px 0 var(--w2); + --shadow-outset-top: inset 0 1px var(--white), inset 0 2px var(--w2); + + --shadow-inset-bottom: inset 0 -1px var(--white), inset 0 -2px var(--w2); + --shadow-inset-right: inset -1px 0 var(--white), inset -2px 0 var(--w2); + --shadow-inset-left: inset 1px 0 var(--shadow-color-1), + inset 2px 0 var(--shadow-color-2); + --shadow-inset-top: inset 0 1px var(--shadow-color-1), + inset 0 2px var(--shadow-color-2); +} +html.ez { + --negative-space: 0em; /* Use this to change the global spacing of your theme :) */ + --font-main: consolas; + --font-serif: consolas; + --font-mono: consolas; + --w: #fff; + --w2: var(--inset-bg); + --w3: grey; + --fg: #cfcfcf; + --fg-max: #47b8ff; + --fg-weak: #47b8ff; + --bg: #383838; + --bg-d3: #600000; + --bg-d2: var(--shadow-color-1); + --bg-d1: var(--bg); + --u2-tab-1-fg: #ff0; + --bg-u2: var(--bg); + --bg-u3: var(--bg); + --bg-u5: var(--shadow-color-2); + --tab-alt: #47b8ff; + --g-fsel-bg: #0000b7; + --g-sel-bg: #00f; + --g-fsel-b1: #fff; + --row-alt: #555555; + --scroll: #555555; + --f-sel-sh: transparent; + --a: var(--fg); + --a-b: var(--fg); + --a-hil: var(--fg); + --btn-1h-bg: var(--bg-d3); + --a-h-bg: var(--bg); + --a-dark: var(--a); + --a-gray: var(--fg-weak); + --btn-fg: var(--white); + --btn-bg: var(--bg); + --btn-h-fg: var(--white); + --btn-h-bg: var(--bg); + --btn-1-fg: var(--white); + --btn-1-bg: var(--bg); + --txt-sh: a; + --u2-b1-bg: var(--w2); + --u2-b2-bg: var(--w2); + --u2-txt-bg: var(--w2); + --u2-tab-bg: a; + --u2-tab-1-bg: var(--w2); + --sort-1: var(--fg-weak); + --g-b1: a; + --g-b2: a; + --g-f-bg: var(--w2); + --f-sh1: 0.1; + --f-sh2: 0.02; + --f-sh3: 0.1; + --f-h-b1: a; + --srv-1: var(--w); + --srv-3: var(--a); + --mp-sh: a; + --black: #000; + --white: #fff; + --grey: grey; + --silver: #858585; + --transparent: transparent; + --shadow-color-1: #101010; + --shadow-color-2: #1f1f1f; + --border-dashed-black: 1px dashed var(--shadow-color-1); + --radius: 0; + --focus-outline: 1px dashed var(--white); + --hover-outline: 1px dotted var(--white); + --fm-off: var(--w3); + --ttlbar: linear-gradient(90deg, var(--shadow-color-1) 20%, #888888); + --inset-bg: #3f3f3f; + --tree-bg: var(--inset-bg); + --txt-bg: var(--inset-bg); + --scroll-bkg: var(--black); + + /*All sides*/ + --shadow-outset: inset -1px -1px var(--shadow-color-1), inset 1px 1px #878787, + inset -2px -2px var(--shadow-color-2), inset 2px 2px #575757; + + --shadow-inset: inset -1px -1px #878787, inset 1px 1px var(--shadow-color-1), + inset -2px -2px #575757, inset 2px 2px var(--shadow-color-2); + + --shadow-input: inset -1px -1px var(--white), + inset 1px 1px var(--shadow-color-2), inset -2px -2px #575757, + inset 2px 2px var(--shadow-color-1); + + --shadow-outset-bottom: inset 0 -1px var(--shadow-color-1), + inset 0 -2px var(--shadow-color-2); + --shadow-outset-right: inset -1px 0 var(--shadow-color-1), + inset -2px 0 var(--shadow-color-2); + --shadow-outset-left: inset 1px 0 #878787, inset 2px 0 #575757; + --shadow-outset-top: inset 0 1px #878787, inset 0 2px #575757; + + --shadow-inset-bottom: inset 0 -1px #878787, inset 0 -2px #575757; + --shadow-inset-right: inset -1px 0 #878787, inset -2px 0 #575757; + --shadow-inset-left: inset 1px 0 var(--shadow-color-1), + inset 2px 0 var(--shadow-color-2); + --shadow-inset-top: inset 0 1px var(--shadow-color-1), + inset 0 2px var(--shadow-color-2); +} + +html.e { + text-shadow: none; +} +html.e #files, +html.e #u2conf input[type="checkbox"]:hover + label, +html.e .tgl.btn.on:hover, +html.e body { + background: var(--bg); +} +html.e #pctl a, +html.e #repl, +html.e #u2conf a, +html.e #u2conf input[type="checkbox"] + label, +html.e #wfp a, +html.e .btn, +html.e .eq_step, +html.e input[type="submit"] { + box-shadow: var(--shadow-outset); + border-radius: var(--radius); + background: var(--bg); + border: 0; +} +a.s0r, +html.e #ghead a.s0, +html.e #u2conf input[type="checkbox"]:checked + label, +html.e .tgl.btn.on, +html.e input[type="submit"]:active { + box-shadow: var(--shadow-inset) !important; +} +html.e #ops a:hover, +html.e #pctl a:hover, +html.e #repl:hover, +html.e #u2conf a:hover, +html.e #u2conf input[type="checkbox"]:hover + label, +html.e #wfp a:hover, +html.e .btn:hover, +html.e .eq_step:hover, +html.e input[type="submit"]:hover { + outline: var(--hover-outline); + outline-offset: -4px; +} +html.e .ntree a:hover, +html.e :focus, +html.e :focus + label, +html.e a:active, +html.e tr:focus, +input[type="text"]:focus { + outline: var(--focus-outline) !important; +} +html.e tr:focus { + box-shadow: none; +} +html.e #pctl a:focus, +html.e #repl:hover, +html.e #u2conf input[type="checkbox"]:focus + label, +html.e #wfp a:focus, +html.e .btn:focus, +html.e .eq_step:focus { + border: 0 !important; + outline: var(--focus-outline) !important; + outline-offset: 2px; + box-shadow: var(--shadow-outset) !important; +} +html.e #files tbody, +html.e #u2cards a.act { + box-shadow: var(--shadow-inset); +} +html.e #files { + border: 2px groove var(--transparent); + box-sizing: border-box; + width: 100%; + padding: 0.3em; + top: 0; + border: 0; +} +html.e #files tbody tr td, +html.e #files thead th { + border-radius: var(--radius); +} +#files td { + background: var(--w2); +} +html.e #files tr { + background-color: var(--black); +} +html.e #srv_info span, +html.e label { + color: var(--btn-fg) !important; +} +html.e #acc_info { + background: var(--transparent); + color: var(--white); + height: 2em; + left: 1em; + width: fit-content; +} +html.e #acc_info, +html.e #ops, +html.e #srv_info { + display: flex; + align-items: center; +} +html.e #acc_info span.warn, +html.e #acc_info a { + color: var(--white); +} +html.e #flogout:before { + padding-left: 0.2em; + padding-right: 0.4em; + content: " | "; +} +html.e #blogout { + color: var(--w); + box-shadow: none; + background: transparent; +} +html.e .opwide > div { + border-left: 1px solid var(--fg); +} +html.e #srv_info { + background: var(--transparent); + color: var(--white); + height: fit-content; + top: 3.2em; + left: 1em; + gap: 0.2em; +} +html.e #u2cards a.act { + padding: 0.2em 1em; +} +html.e #u2btn { + border: var(--border-dashed-black); + border-radius: var(--border-radius); + transform: translateY(30%); +} +html.e #ops, +html.e #ops a { + border-radius: var(--radius); +} +@media only screen and (max-width: 600px) { + html.e #acc_info { + background: var(--transparent); + color: var(--white); + height: fit-content; + align-items: center; + top: 3.2em; + right: 1em; + left: auto; + display: flex; + gap: 0.2em; + } + html.e #u2btn { + transform: none; + } +} +html.e #ops { + background: var(--ttlbar); + /*HC*/ + box-shadow: inset 0-1px grey, inset 0-2px var(--shadow-color-1); + height: 2em; + gap: 0.6em; + padding: 0.2em; + flex-direction: row-reverse; + margin-bottom: 1.2em; +} +html.e #srch_form, +html.e .opbox { + padding-bottom: 1em; + padding-top: 1em; + max-width: 100vw; +} +html.e #ghead, +html.e #ops a { + align-items: center; + display: flex; +} +html.e #ops a { + text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5); + height: 1.4em; + padding: 0; + box-shadow: var(--shadow-outset); + background: var(--bg); + aspect-ratio: 1/1; + justify-content: center; + font-size: 1.25em; + z-index: 4; +} +html.e #blogout:focus, +html.e #ops a:focus { + outline: 1px dashed var(--w) !important; +} + +html.e #blogout:hover { + text-decoration: underline; +} + +html.e #ops > a:not(:first-child).act { + height: 1.4em; + width: 1.4em; + padding-bottom: 0.3em; + margin-top: 0.3em; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + box-shadow: var(--shadow-inset-left), var(--shadow-inset-top), + var(--shadow-inset-right); + z-index: 6; +} +html.e #ops a.act { + box-shadow: var(--shadow-inset); + border-bottom: 0; +} +html.e a:active { + border: 0; +} +html.e :focus, +html.e :focus + label { + border: 0 !important; + outline-offset: 1px; + border-radius: var(--radius) !important; + box-shadow: inherit; +} +html.e #opa_x { + text-shadow: 0 0 0 var(--transparent) !important; + color: var(--bg) !important; + display: flex; +} +html.e #opa_x:before { + content: "⨯"; + color: var(--fg) !important; + margin-top: -0.1em; + font-size: 1.75em; + position: absolute; +} +html.e .opbox { + margin: -1.2em 0 0; + box-shadow: var(--shadow-inset-bottom), var(--shadow-inset-left), + var(--shadow-inset-right); + border-radius: var(--radius); + z-index: 5; + background: var(--bg); +} +html.e #srch_form { + margin: 0; + border-radius: var(--radius); +} +html.e #op_unpost { + max-width: 100vw; + margin: 0; +} +html.e label:focus { + box-shadow: 0 0; +} +html.e #tree { + box-shadow: none; + padding-right: 5px; +} +html.e #tt { + background: var(--w2); +} +html.e .mdo a { + background: 0 0; + text-decoration: underline; +} +html.e .mdo code, +html.e .mdo pre { + color: var(--white); + background: var(--w2); + border: 0; +} +html.e .mdo h1, +html.e .mdo h2 { + background: 0 0; + border-color: var(--w2); +} +html.e #tt, +html.e .mdo ol ol, +html.e .mdo ol ul, +html.e .mdo ul ol, +html.e .mdo ul ul { + border-color: var(--w2); +} +html.e .mdo li > em, +html.e .mdo p > em, +html.e .mdo td > em { + color: #fd0; +} +html.e input.txtbox, +html.e input[type="text"], +html.e select { + background-color: var(--txt-bg); + box-shadow: var(--shadow-input) !important; + box-sizing: border-box; + padding: 3px 4px; + border-radius: var(--radius); + border: 0; +} +html.e #gfiles { + box-shadow: var(--shadow-outset); + background: var(--bg); + padding: 0.4em; + display: flex; + flex-direction: column; + gap: 0.3em; +} +html.e #ggrid { + background-color: var(--inset-bg); + box-shadow: var(--shadow-input); + padding: 1.5em; + margin: 0; + overflow-x: scroll; +} +html.e #ghead { + margin: 0; + justify-content: flex-end; + gap: 0.4em; + padding: 0; + overflow: auto; + top: 0px; + border-radius: 0px; +} +html.e #ghead a { + margin: 0; + border-radius: var(--radius); +} +html.e ::-webkit-scrollbar, +html.e::-webkit-scrollbar { + width: 16px !important; + height: 16px !important; + background: var(--transparent) !important; +} +html.e ::-webkit-scrollbar-button, +html.e ::-webkit-scrollbar-thumb, +html.e::-webkit-scrollbar-button, +html.e::-webkit-scrollbar-thumb { + width: 16px !important; + height: 16px !important; + background: var(--scroll) !important; + /*HC*/ + box-shadow: var(--shadow-outset); + border: 1px solid !important; + border-color: var(--silver) var(--black) var(--black) var(--silver) !important; +} +html.e ::-webkit-scrollbar-track, +html.e::-webkit-scrollbar-track { + image-rendering: optimize-contrast !important; + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAyIDIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyI+CjxtZXRhZGF0YT5NYWRlIHdpdGggUGl4ZWxzIHRvIFN2ZyBodHRwczovL2NvZGVwZW4uaW8vc2hzaGF3L3Blbi9YYnh2Tmo8L21ldGFkYXRhPgo8cGF0aCBzdHJva2U9IiNjMGMwYzAiIGQ9Ik0wIDBoMU0xIDFoMSIgLz4KPC9zdmc+) !important; + background-position: 0 0 !important; + background-repeat: repeat !important; + background-size: 2px !important; + background: var(--scroll-bkg); +} +#tree::-webkit-scrollbar, +#tree::-webkit-scrollbar-track { + background: var(--scroll-bkg); +} +html.e ::-webkit-scrollbar-button, +html.e::-webkit-scrollbar-button { + background-repeat: no-repeat !important; + background-size: 16px !important; +} +html.e ::-webkit-scrollbar-button:single-button:vertical:decrement, +html.e::-webkit-scrollbar-button:single-button:vertical:decrement { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTcgNWgxTTYgNmgzTTUgN2g1TTQgOGg3IiAvPgo8L3N2Zz4=) !important; +} +html.e ::-webkit-scrollbar-button:single-button:vertical:increment, +html.e::-webkit-scrollbar-button:single-button:vertical:increment { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTQgNWg3TTUgNmg1TTYgN2gzTTcgOGgxIiAvPgo8L3N2Zz4=) !important; +} +html.e ::-webkit-scrollbar-button:single-button:horizontal:decrement, +html.e::-webkit-scrollbar-button:single-button:horizontal:decrement { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTggM2gxTTcgNGgyTTYgNWgzTTUgNmg0TTYgN2gzTTcgOGgyTTggOWgxIiAvPgo8L3N2Zz4=) !important; +} +html.e ::-webkit-scrollbar-button:single-button:horizontal:increment, +html.e::-webkit-scrollbar-button:single-button:horizontal:increment { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTAuNSAxNiAxNiIgc2hhcGUtcmVuZGVyaW5nPSJjcmlzcEVkZ2VzIj4KPG1ldGFkYXRhPk1hZGUgd2l0aCBQaXhlbHMgdG8gU3ZnIGh0dHBzOi8vY29kZXBlbi5pby9zaHNoYXcvcGVuL1hieHZOajwvbWV0YWRhdGE+CjxwYXRoIHN0cm9rZT0iIzAwMDAwMCIgZD0iTTYgM2gxTTYgNGgyTTYgNWgzTTYgNmg0TTYgN2gzTTYgOGgyTTYgOWgxIiAvPgo8L3N2Zz4=) !important; +} +html.e ::-webkit-scrollbar-corner, +html.e::-webkit-scrollbar-corner { + background: var(--silver) !important; +} +html, +html.e #tree { + scrollbar-color: inherit !important; +} +html.e #tree { + background: var(--bg); + padding-left: 0.4em; + padding-top: 0; + margin-left: var(--negative-space); +} +html.e.noscroll #tree { + /*HC*/ + box-shadow: 1px 1px var(--grey), 2px 2px var(--shadow-color-1), + var(--shadow-outset-bottom); +} +html.e #treeh { + background: var(--bg); + box-shadow: var(--shadow-outset-top), var(--shadow-outset-bottom); + width: calc(1.5em + var(--nav-sz) - var(--sbw)); + height: 2.4em; + border: none; + top: -2px; + display: flex; + align-items: center; + gap: 0.6em; +} +html.e #treeh .btn { + margin: 0px; + top: auto; +} +html.e #tree ul { + border-left: var(--border-dashed-black); + margin-left: 2.15em; +} +html.e .ntree a:first-child { + font-family: scp, monospace, monospace; + font-size: 1.2em; + line-height: 0; + background: var(--inset-bg); + aspect-ratio: 1/1; + text-align: center; + align-content: center; + border-radius: var(--radius) !important; + padding: 0.057em; + border: 1px solid var(--black); +} +html.e .ntree a:first-child:after { + content: "."; + position: absolute; + border-top: var(--border-dashed-black); + color: var(--transparent); + font-size: 0.9em; + margin-left: 0.13em; +} +html.e #treeul { + border: 0 !important; + position: static; + margin: 0 !important; + min-height: 100%; + height: max-content; +} +html.e .ntree a:last-of-type:before { + content: "📁"; + margin-left: 0.3em; +} +html.e .ntree { + padding-left: 1em !important; + padding-top: 0.3em !important; + background: var(--inset-bg); + box-shadow: var(--shadow-inset-left), var(--shadow-inset-bottom); +} +html.e #tree li { + margin-left: -0.5em; + border-top: 0; +} +html.e .ntree a:hover { + outline-offset: -2px; + color: var(--fg); + border-radius: var(--radius) !important; +} +html.e #treepar { + width: calc(-1em + var(--nav-sz) - var(--sbw)); + overflow: hidden; + left: -0.7em; + box-shadow: var(--shadow-inset-left), var(--shadow-inset-top); + border-left: 0 !important; + border-bottom: var(--border-dashed-black); + margin-left: calc(2.1em - (1em - var(--negative-space))) !important; +} +html.e #path, +html.e #widgeti, +html.e #wtoggle, +html.e #wtoggle a, +html.e #files, +html.e #files thead th, +html.e #ghead a, +html.e #tree { + box-shadow: var(--shadow-outset); +} +html.e.noscroll #treepar { + width: calc(var(--nav-sz) - 1em); +} +html.e #docul { + border-left: 0 !important; + margin-left: 0 !important; +} +html.e #wrap { + transform: translateX(calc((var(--negative-space) * 2) - 1.2em)); + padding-right: var(--negative-space); + position: relative; + margin-right: calc((var(--negative-space) * 2) - 1.2em); + margin-top: var(--negative-space); + margin-left: 1.2em; + /*overflow-x: auto; fix for OOB table when screen space is limited (mobile), but removes sticky header*/ +} +html.e input[type="radio"] { + accent-color: #232323; +} +html.e #path { + width: calc(100% - 0.4em); + display: flex; + align-items: center; + margin: 0; + padding: 0.2em; + overflow-x: auto; +} +html.e #path i { + border: 1px solid var(--w); + border-color: var(--w); + margin: 0; + border-width: 0.1em 0.1em 0 0; + height: 0.5em; + width: 0.5em; +}/* +html.e #hovertree:after { + color: red; + content: "BUGGY"; +html.ez #hovertree:after { + color: rgb(255 98 98); + content: "BUGGY"; +} +}*/ +html.e #widget { + box-shadow: 0 0; + border: 0 !important; +} +html.e #wtico, +html.e #zip1 { + box-shadow: 0 0 !important; +} +html.e #wtgrid { + top: -0.09em; +} +html.e #wfs, +html.e #wm3u, +html.e #wnp, +html.e #wzip { + border-width: 0 1px 0 0; +} +html.e #wfm.act + #wzip1 + #wzip, +html.e #wfm.act + #wzip1 + #wzip + #wnp { + border-left-width: 1px; +} +html.e #barpos { + /* border-radius: var(--radius); */ + box-shadow: var(--shadow-inset); +} +html.e #goh + span { + border-left: 0.1em solid var(--bg-u5); +} +html.e #wfp { + margin: var(--negative-space); + font-size: 0; + display: inline-block; +} +html.e #wfp a { + font-size: large; + display: inline-block; +} +html.e #repl { + font-size: large; + padding: 0.33em; + right: calc(var(--negative-space) * 0.89); + position: absolute; +} +html.e #epi { + text-align: center; + text-wrap-mode: nowrap; + margin: 0px; +} + +html.e #epi.logue:not(.mdo) { + padding: 0.8em; + box-shadow: var(--shadow-outset); +} + +html.e #epi.logue.mdo { + padding-left: 3px; +} + +html.e #doc { + box-shadow: var(--shadow-inset); + background: var(--inset-bg); + margin: 0.2em; + border-radius: var(--radius); +} + +html.e #detree { + padding: 0px; +} diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 02549c9f..82daa1ea 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -4,6 +4,7 @@ var XHR = XMLHttpRequest, img_re = /\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i; // please add translations in alphabetic order, but keep "eng" and "nor" first +// (lines ending with //m are machine translations) var Ls = { "eng": { "tt": "English", @@ -113,6 +114,7 @@ var Ls = { "gou": 'parent folder">up', "gon": 'next folder">next', "logout": "Logout ", + "login": "Login", "access": " access", "ot_close": "close submenu", "ot_search": "search for files by attributes, path / name, music tags, or any combination of those$N$N<code>foo bar</code> = must contain both «foo» and «bar»,$N<code>foo -bar</code> = must contain «foo» but not «bar»,$N<code>^yana .opus$</code> = start with «yana» and be an «opus» file$N<code>"try unite"</code> = contain exactly «try unite»$N$Nthe date format is iso-8601, like$N<code>2009-12-31</code> or <code>2020-09-12 23:30:00</code>", @@ -207,6 +209,7 @@ var Ls = { "u_nav_b": '<a href="#" id="modal-ok">Files</a><a href="#" id="modal-ng">One folder</a>', "cl_opts": "switches", + "cl_hfsz": "filesize", "cl_themes": "theme", "cl_langs": "language", "cl_ziptype": "folder download", @@ -299,13 +302,15 @@ var Ls = { "mt_mloop": "loop the open folder\">🔁 loop", "mt_mnext": "load the next folder and continue\">📂 next", "mt_mstop": "stop playback\">⏸ stop", - "mt_cflac": "convert flac / wav to opus\">flac", - "mt_caac": "convert aac / m4a to opus\">aac", - "mt_coth": "convert all others (not mp3) to opus\">oth", + "mt_cflac": "convert flac / wav to {0}\">flac", + "mt_caac": "convert aac / m4a to {0}\">aac", + "mt_coth": "convert all others (not mp3) to {0}\">oth", "mt_c2opus": "best choice for desktops, laptops, android\">opus", "mt_c2owa": "opus-weba, for iOS 17.5 and newer\">owa", "mt_c2caf": "opus-caf, for iOS 11 through 17\">caf", "mt_c2mp3": "use this on very old devices\">mp3", + "mt_c2flac": "best sound quality, but huge downloads\">flac", + "mt_c2wav": "uncompressed playback (even bigger)\">wav", "mt_c2ok": "nice, good choice", "mt_c2nd": "that's not the recommended output format for your device, but that's fine", "mt_c2ng": "your device does not seem to support this output format, but let's try anyways", @@ -424,6 +429,7 @@ var Ls = { "fcp_ok": "copy OK", "fp_busy": "moving {0} items...\n\n{1}", "fcp_busy": "copying {0} items...\n\n{1}", + "fp_abrt": "aborting...", "fp_err": "move failed:\n", "fcp_err": "copy failed:\n", "fp_confirm": "move these {0} items here?", @@ -741,6 +747,7 @@ var Ls = { "gou": 'naviger ett nivå opp">opp', "gon": 'naviger til mappen etter denne">neste', "logout": "Logg ut ", + "login": "Logg inn", "access": " tilgang", "ot_close": "lukk verktøy", "ot_search": "søk etter filer ved å angi filnavn, mappenavn, tid, størrelse, eller metadata som sangtittel / artist / osv.$N$N<code>foo bar</code> = inneholder både «foo» og «bar»,$N<code>foo -bar</code> = inneholder «foo» men ikke «bar»,$N<code>^yana .opus$</code> = starter med «yana», filtype «opus»$N<code>"try unite"</code> = «try unite» eksakt$N$Ndatoformat er iso-8601, så f.eks.$N<code>2009-12-31</code> eller <code>2020-09-12 23:30:00</code>", @@ -850,7 +857,7 @@ var Ls = { "cl_hcancel": "kolonne-skjuling avbrutt", "ct_grid": '田 ikoner', - "ct_ttips": 'hvis hjelpetekst ved å holde musen over ting">ℹ️ tips', + "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_ihop": 'bla ned til sist viste bilde når bildeviseren lukkes">g⮯', @@ -875,8 +882,8 @@ var Ls = { "cut_az": "last opp filer i alfabetisk rekkefølge, istedenfor minste-fil-først$N$Nalfabetisk kan gjøre det lettere å anslå om alt gikk bra, men er bittelitt tregere på fiber / LAN", - "cut_nag": "meldingsvarsel når opplastning er ferdig$N(kun on nettleserfanen ikke er synlig)", - "cut_sfx": "lydvarsel når opplastning er ferdig$N(kun on nettleserfanen ikke er synlig)", + "cut_nag": "meldingsvarsel når opplastning er ferdig$N(kun om nettleserfanen ikke er synlig)", + "cut_sfx": "lydvarsel når opplastning er ferdig$N(kun om nettleserfanen ikke er synlig)", "cut_mt": "raskere befaring ved å bruke hele CPU'en$N$Ndenne funksjonen anvender web-workers$Nog krever mer RAM (opptil 512 MiB ekstra)$N$Ngjør https 30% raskere, http 4.5x raskere\">mt", @@ -910,7 +917,7 @@ var Ls = { "mt_loop": "spill den samme sangen om og om igjen\">🔁", "mt_one": "spill kun én sang\">1️⃣", "mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀", - "mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URLen bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶", + "mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URL'en bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶", "mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles", "mt_prescan": "ved behov, bla til neste mappe$Nslik at nettleseren lar oss$Nfortsette å spille musikk\">bla", "mt_fullpre": "hent ned hele neste sang, ikke bare litt:$N✅ skru på hvis nettet ditt er <b>ustabilt</b>,$N❌ skru av hvis nettet ditt er <b>tregt</b>\">full", @@ -927,13 +934,15 @@ var Ls = { "mt_mloop": "repeter hele mappen\">🔁 gjenta", "mt_mnext": "hopp til neste mappe og fortsett\">📂 neste", "mt_mstop": "stopp avspilling\">⏸ stopp", - "mt_cflac": "konverter flac / wav-filer til opus\">flac", - "mt_caac": "konverter aac / m4a-filer til to opus\">aac", - "mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre", + "mt_cflac": "konverter flac / wav-filer til {0}\">flac", + "mt_caac": "konverter aac / m4a-filer til to {0}\">aac", + "mt_coth": "konverter alt annet (men ikke mp3) til {0}\">andre", "mt_c2opus": "det beste valget for alle PCer og Android\">opus", "mt_c2owa": "opus-weba, for iOS 17.5 og nyere\">owa", "mt_c2caf": "opus-caf, for iOS 11 tilogmed 17\">caf", "mt_c2mp3": "bra valg for steinalder-utstyr (slår aldri feil)\">mp3", + "mt_c2flac": "gir best lydkvalitet, men eter nettet ditt\">flac", + "mt_c2wav": "helt rå lydstrøm (bruker enda mere data enn flac)\">wav", "mt_c2ok": "bra valg!", "mt_c2nd": "ikke det foretrukne valget for din enhet, men funker sikkert greit", "mt_c2ng": "ser virkelig ikke ut som enheten din takler dette formatet... men ok, vi prøver", @@ -1052,6 +1061,7 @@ var Ls = { "fcp_ok": "kopiering OK", "fp_busy": "flytter {0} filer...\n\n{1}", "fcp_busy": "kopierer {0} filer...\n\n{1}", + "fp_abrt": "avbryter...", "fp_err": "flytting feilet:\n", "fcp_err": "kopiering feilet:\n", "fp_confirm": "flytt disse {0} filene hit?", @@ -1368,6 +1378,7 @@ var Ls = { "gou": '顶部">up', "gon": '下一项">next', "logout": " 登出", + "login": "登录", //m "access": " 访问", "ot_close": "关闭子菜单", "ot_search": "按属性、路径/名称、音乐标签或上述内容的任意组合搜索文件$N$N<code>foo bar</code> = 必须包含 «foo» 和 «bar»,$N<code>foo -bar</code> = 包含 «foo» 而不包含 «bar»,$N<code>^yana .opus$</code> = 以 «yama» 为开头的 «opus» 文件$N<code>"try unite"</code> = 正好包含 «try unite»$N$N时间格式为 iso-8601, 比如:$N<code>2009-12-31</code> or <code>2020-09-12 23:30:00</code>", @@ -1554,13 +1565,15 @@ var Ls = { "mt_mloop": "循环打开的文件夹\">🔁 循环", "mt_mnext": "加载下一个文件夹并继续\">📂 下一首", "mt_mstop": "停止播放\">⏸ 停止", //m - "mt_cflac": "将 flac / wav 转换为 opus\">flac", - "mt_caac": "将 aac / m4a 转换为 opus\">aac", - "mt_coth": "将所有其他(不是 mp3)转换为 opus\">oth", + "mt_cflac": "将 flac / wav 转换为 {0}\">flac", + "mt_caac": "将 aac / m4a 转换为 {0}\">aac", + "mt_coth": "将所有其他(不是 mp3)转换为 {0}\">oth", "mt_c2opus": "适合桌面电脑、笔记本电脑和安卓设备的最佳选择\">opus", //m "mt_c2owa": "opus-weba(适用于 iOS 17.5 及更新版本)\">owa", //m "mt_c2caf": "opus-caf(适用于 iOS 11 到 iOS 17)\">caf", //m "mt_c2mp3": "适用于非常旧的设备\">mp3", //m + "mt_c2flac": "最佳音质,但下载量很大\">flac", //m + "mt_c2wav": "无压缩播放(更占空间)\">wav", //m "mt_c2ok": "不错的选择!", //m "mt_c2nd": "这不是您的设备推荐的输出格式,但应该没问题。", //m "mt_c2ng": "您的设备似乎不支持此输出格式,不过我们还是试试看吧。", //m @@ -1604,7 +1617,7 @@ var Ls = { "f_dls": '当前文件夹中的文件链接已\n更改为下载链接', - "f_partial": "要安全下载正在上传的文件,请点击没有 <code>.PARTIAL</code> 文件扩展名的同名文件。请按取消或 Escape 执行此操作。\n\n按 OK / Enter 将忽略此警告并继续下载 <code>.PARTIAL</code> 临时文件,这几乎肯定会导致数据损坏。", + "f_partial": "要安全下载正在上传的文件,请点击没有 <code>.PARTIAL</code> 文件扩展名的同名文件。请按取消或 Escape 执行此操作。\n\n按 确定 / Enter 将忽略此警告并继续下载 <code>.PARTIAL</code> 临时文件,这几乎肯定会导致数据损坏。", "ft_paste": "粘贴 {0} 项$N快捷键: ctrl-V", "fr_eperm": '无法重命名:\n你在此文件夹中没有 “移动” 权限', @@ -1679,6 +1692,7 @@ var Ls = { "fcp_ok": "复制成功", //m "fp_busy": "正在移动 {0} 项...\n\n{1}", "fcp_busy": "正在复制 {0} 项...\n\n{1}", //m + "fp_abrt": "正在中止...", //m "fp_err": "移动失败:\n", "fcp_err": "复制失败:\n", //m "fp_confirm": "将这些 {0} 项移动到这里?", @@ -1887,6 +1901,641 @@ var Ls = { "lang_set": "刷新以使更改生效?", }, + "cze": { + "tt": "Čeština", + + "cols": { + "c": "tlačítka akcí", + "dur": "doba trvání", + "q": "kvalita / bitrate", + "Ac": "audio kodek", + "Vc": "video kodek", + "Fmt": "formát / kontejner", + "Ahash": "kontrolní součet audia", + "Vhash": "kontrolní součet videa", + "Res": "rozlišení", + "T": "typ souboru", + "aq": "kvalita zvuku / bitrate", + "vq": "kvalita videa / bitrate", + "pixfmt": "podvzorkování / struktura pixelů", + "resw": "horizontální rozlišení", + "resh": "vertikální rozlišení", + "chs": "audio kanály", + "hz": "vzorkovací frekvence" + }, + + "hks": [ + [ + "různé", + ["ESC", "zavřít různé věci"], + + "správce souborů", + ["G", "přepnout seznam / zobrazení mřížky"], + ["T", "přepnout náhledy / ikony"], + ["⇧ A/D", "velikost náhledů"], + ["ctrl-K", "smazat vybrané"], + ["ctrl-X", "vyjmout výběr do schránky"], + ["ctrl-C", "kopírovat výběr do schránky"], + ["ctrl-V", "vložit (přesunout/kopírovat) zde"], + ["Y", "stáhnout vybrané"], + ["F2", "přejmenovat vybrané"], + + "výběr souborů", + ["space", "přepnout výběr souboru"], + ["↑/↓", "posunout kurzor výběru"], + ["ctrl ↑/↓", "posunout kurzor a zobrazení"], + ["⇧ ↑/↓", "vybrat předchozí/následující soubor"], + ["ctrl-A", "vybrat všechny soubory / složky"], + ], [ + "navigace", + ["B", "přepnout drobečkovou navigaci / navigační panel"], + ["I/K", "předchozí/následující složka"], + ["M", "nadřazená složka (nebo sbalit aktuální)"], + ["V", "přepnout složky / textové soubory v navigačním panelu"], + ["A/D", "velikost navigačního panelu"], + ], [ + "audio přehrávač", + ["J/L", "předchozí/následující skladba"], + ["U/O", "přeskočit 10 sekund zpět/vpřed"], + ["0..9", "přejít na 0%..90%"], + ["P", "přehrát/pozastavit (také spustit)"], + ["S", "vybrat přehrávanou skladbu"], + ["Y", "stáhnout skladbu"], + ], [ + "prohlížeč obrázků", + ["J/L, ←/→", "předchozí/následující obrázek"], + ["Home/End", "první/poslední obrázek"], + ["F", "celá obrazovka"], + ["R", "otočit po směru hodinových ručiček"], + ["⇧ R", "otočit proti směru hodinových ručiček"], + ["S", "vybrat obrázek"], + ["Y", "stáhnout obrázek"], + ], [ + "video přehrávač", + ["U/O", "přeskočit 10 sekund zpět/vpřed"], + ["P/K/Space", "přehrát/pozastavit"], + ["C", "pokračovat přehráváním následující"], + ["V", "smyčka"], + ["M", "ztlumit"], + ["[ and ]", "nastavit interval smyčky"], + ], [ + "prohlížeč textových souborů", + ["I/K", "předchozí/následující soubor"], + ["M", "zavřít textový soubor"], + ["E", "upravit textový soubor"], + ["S", "vybrat soubor (pro vyjmutí/kopírování/přejmenování)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Zrušit", + + "enable": "Povolit", + "danger": "NEBEZPEČÍ", + "clipped": "zkopírováno do schránky", + + "ht_s1": "sekunda", + "ht_s2": "sekundy", + "ht_s5": "sekund", + "ht_m1": "minuta", + "ht_m2": "minuty", + "ht_m5": "minut", + "ht_h1": "hodina", + "ht_h2": "hodiny", + "ht_h5": "hodin", + "ht_d1": "den", + "ht_d2": "dny", + "ht_d5": "dní", + "ht_and": " a ", + + "goh": "ovládací panel", + "gop": 'předchozí sourozenec">předchozí', + "gou": 'nadřazená složka">nahoru', + "gon": 'následující složka">následující', + "logout": "Odhlásit ", + "login": "Přihlásit se", //m + "access": " přístup", + "ot_close": "zavřít podnabídku", + "ot_search": "hledat soubory podle atributů, cesty / názvu, hudebních tagů nebo jejich kombinace$N$N<code>foo bar</code> = musí obsahovat jak «foo» tak «bar»,$N<code>foo -bar</code> = musí obsahovat «foo» ale ne «bar»,$N<code>^yana .opus$</code> = začíná na «yana» a je to «opus» soubor$N<code>"try unite"</code> = obsahuje přesně «try unite»$N$Nformát data je iso-8601, jako$N<code>2009-12-31</code> nebo <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: smazat vaše nedávné nahrání nebo zrušit nedokončené", + "ot_bup": "bup: základní nahrávač, podporuje i netscape 4.0", + "ot_mkdir": "mkdir: vytvořit nový adresář", + "ot_md": "new-md: vytvořit nový markdown dokument", + "ot_msg": "msg: poslat zprávu do logu serveru", + "ot_mp": "možnosti přehrávače médií", + "ot_cfg": "možnosti konfigurace", + "ot_u2i": 'up2k: nahrát soubory (pokud máte oprávnění k zápisu) nebo přepnout do vyhledávacího režimu a podívat se, zda existují někde na serveru$N$Nnahrávání je obnovitelné, vícevláknové a časové značky souborů jsou zachovány, ale používá více CPU než [🎈]  (základní nahrávač)<br /><br />během nahrávání se tato ikona stává indikátorem průběhu!', + "ot_u2w": 'up2k: nahrát soubory s podporou obnovení (zavřete prohlížeč a stejné soubory přetáhněte později)$N$Nvícevláknové a časové značky souborů jsou zachovány, ale používá více CPU než [🎈]  (základní nahrávač)<br /><br />během nahrávání se tato ikona stává indikátorem průběhu!', + "ot_noie": 'Prosím použijte Chrome / Firefox / Edge', + + "ab_mkdir": "vytvořit adresář", + "ab_mkdoc": "nový markdown dokument", + "ab_msg": "poslat zprávu do logu serveru", + + "ay_path": "přejít na složky", + "ay_files": "přejít na soubory", + + "wt_ren": "přejmenovat vybrané položky$NKlávesová zkratka: F2", + "wt_del": "smazat vybrané položky$NKlávesová zkratka: ctrl-K", + "wt_cut": "vyjmout vybrané položky <small>(pak je vložit někam jinam)</small>$NKlávesová zkratka: ctrl-X", + "wt_cpy": "kopírovat vybrané položky do schránky$N(pro vložení někam jinam)$NKlávesová zkratka: ctrl-C", + "wt_pst": "vložit dříve vyjmutý / zkopírovaný výběr$NKlávesová zkratka: ctrl-V", + "wt_selall": "vybrat všechny soubory$NKlávesová zkratka: ctrl-A (když je zaměřen soubor)", + "wt_selinv": "invertovat výběr", + "wt_zip1": "stáhnout tuto složku jako archiv", + "wt_selzip": "stáhnout výběr jako archiv", + "wt_seldl": "stáhnout výběr jako samostatné soubory$NKlávesová zkratka: Y", + "wt_npirc": "kopírovat informace o stopě ve formátu irc", + "wt_nptxt": "kopírovat informace o stopě v prostém textu", + "wt_m3ua": "přidat do m3u playlistu (klikněte později na <code>📻kopírovat</code>)", + "wt_m3uc": "kopírovat m3u playlist do schránky", + "wt_grid": "přepnout zobrazení mřížky / seznamu$NKlávesová zkratka: G", + "wt_prev": "předchozí stopa$NKlávesová zkratka: J", + "wt_play": "přehrát / pozastavit$NKlávesová zkratka: P", + "wt_next": "následující stopa$NKlávesová zkratka: L", + + "ul_par": "paralelní nahrávání:", + "ut_rand": "náhodné názvy souborů", + "ut_u2ts": "kopírovat časovou značku poslední změny$Nz vašeho souborového systému na server\">📅", + "ut_ow": "přepsat existující soubory na serveru?$N🛡️: nikdy (místo toho vytvoří nový název souboru)$N🕒: přepsat pokud je soubor na serveru starší než váš$N♻️: vždy přepsat pokud se soubory liší", + "ut_mt": "pokračovat v hashování ostatních souborů během nahrávání$N$Nmožná zakázat pokud je vaše CPU nebo HDD bottleneckem", + "ut_ask": 'požádat o potvrzení před zahájením nahrávání">💭', + "ut_pot": "zlepšit rychlost nahrávání na pomalých zařízeních$Nzjednodušením UI", + "ut_srch": "skutečně nenahrávat, místo toho zkontrolovat zda soubory již $N existují na serveru (prohledá všechny složky které můžete číst)", + "ut_par": "pozastavit nahrávání nastavením na 0$N$Nzvýšit pokud je vaše připojení pomalé / vysoká latence$N$Nponechat na 1 v LAN nebo pokud je HDD serveru bottleneckem", + "ul_btn": "přetáhněte soubory / složky<br>sem (nebo sem klikněte)", + "ul_btnu": "N A H R Á T", + "ul_btns": "H L E D A T", + + "ul_hash": "hash", + "ul_send": "odeslat", + "ul_done": "hotovo", + "ul_idle1": "zatím nejsou zařazena žádná nahrávání", + "ut_etah": "průměrná rychlost <em>hashování</em> a odhadovaný čas do dokončení", + "ut_etau": "průměrná rychlost <em>nahrávání</em> a odhadovaný čas do dokončení", + "ut_etat": "průměrná <em>celková</em> rychlost a odhadovaný čas do dokončení", + + "uct_ok": "úspěšně dokončeno", + "uct_ng": "nedobré: selhalo / odmítnuto / nenalezeno", + "uct_done": "celkem", + "uct_bz": "hashování nebo nahrávání", + "uct_q": "nečinné, čekající", + + "utl_name": "název souboru", + "utl_ulist": "seznam", + "utl_ucopy": "kopírovat", + "utl_links": "odkazy", + "utl_stat": "stav", + "utl_prog": "průběh", + + // keep short: + "utl_404": "404", + "utl_err": "CHYBA", + "utl_oserr": "chyba OS", + "utl_found": "nalezeno", + "utl_defer": "odložit", + "utl_yolo": "YOLO", + "utl_done": "hotovo", + + "ul_flagblk": "soubory byly přidány do fronty</b><br>nicméně v jiné kartě prohlížeče běží up2k,<br>takže čekáme až skončí", + "ul_btnlk": "konfigurace serveru uzamkla tento přepínač v tomto stavu", + + "udt_up": "Nahrávání", + "udt_srch": "Hledání", + "udt_drop": "přetáhněte to sem", + + "u_nav_m": '<h6>jasnačka, co máte?</h6><code>Enter</code> = Soubory (jeden nebo více)\n<code>ESC</code> = Jednu složku (včetně podsložek)', + "u_nav_b": '<a href="#" id="modal-ok">Soubory</a><a href="#" id="modal-ng">Jedna složka</a>', + + "cl_opts": "přepínače", + "cl_themes": "téma", + "cl_langs": "jazyk", + "cl_ziptype": "stahování složky", + "cl_uopts": "up2k přepínače", + "cl_favico": "favicon", + "cl_bigdir": "velké adresáře", + "cl_hsort": "#řazení", + "cl_keytype": "notace kláves", + "cl_hiddenc": "skryté sloupce", + "cl_hidec": "skrýt", + "cl_reset": "resetovat", + "cl_hpick": "klepněte na záhlaví sloupců pro skrytí v tabulce níže", + "cl_hcancel": "skrývání sloupců zrušeno", + + "ct_grid": '田 mřížka', + "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_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', + "ct_qdel": 'při mazání souborů požádat o potvrzení jen jednou">rychlé mazání', + "ct_dir1st": 'řadit složky před soubory">📁 první', + "ct_nsort": 'přirozené řazení (pro názvy souborů s úvodními číslicemi)">přirozené řazení', + "ct_utc": 'zobrazit všechny časy v UTC">UTC', + "ct_readme": 'zobrazit README.md v seznamech složek">📜 readme', + "ct_idxh": 'zobrazit index.html místo seznamu složky">htm', + "ct_sbars": 'zobrazit posuvníky">⟊', + + "cut_umod": "pokud soubor na serveru již existuje, aktualizovat časovou značku posledního změny serveru tak, aby odpovídala vašemu lokálnímu souboru (vyžaduje oprávnění k zápisu+mazání)\">re📅", + + "cut_turbo": "yolo tlačítko, pravděpodobně to NECHCETE povolit:$N$Npoužijte to pokud jste nahrávali obrovské množství souborů a museli jste restartovat z nějakého důvodu a chcete pokračovat v nahrávání ASAP$N$Ntoto nahradí hash-kontrolu jednoduchým <em>"má to stejnou velikost souboru na serveru?"</em> takže pokud se obsah souborů liší, NEBUDE nahrán$N$Nměli byste to vypnout když nahrávání skončí a pak znovu "nahrát" stejné soubory aby je klient ověřil\">turbo", + + "cut_datechk": "nemá žádný efekt pokud není povoleno turbo tlačítko$N$Nsnižuje yolo faktor o trochu; kontroluje zda časové značky souborů na serveru odpovídají vašim$N$Nměl by <em>teoreticky</em> zachytit většinu nedokončených / poškozených nahrávání, ale není náhradou za ověřovací průchod s turbem vypnutým poté\">kontrola data", + + "cut_u2sz": "velikost (v MiB) každého kusu nahrávání; velké hodnoty lépe létají přes atlantik. Zkuste nízké hodnoty na velmi nespolehlivých připojeních", + + "cut_flag": "zajistit aby nahrávala jen jedna karta najednou $N -- ostatní karty to musí mít také povoleno $N -- ovlivňuje jen karty na stejné doméně", + + "cut_az": "nahrávat soubory v abecedním pořadí, spíše než nejmenší-soubor-první$N$Nabecední pořadí může usnadnit kontrolu zda se něco pokazilo na serveru, ale činí nahrávání mírně pomalejší na optice / LAN", + + "cut_nag": "notifikace OS když nahrávání skončí$N(jen pokud prohlížeč nebo karta není aktivní)", + "cut_sfx": "zvukové upozornění když nahrávání skončí$N(jen pokud prohlížeč nebo karta není aktivní)", + + "cut_mt": "použít vícevláknové zpracování pro zrychlení hashování souborů$N$Ntoto používá web-workers a vyžaduje$Nvíce RAM (až 512 MiB navíc)$N$Ndělá https o 30% rychlejší a http 4,5x rychlejší\">mt", + + "cut_wasm": "použijte wasm místo vestavěného hashování prohlížeče; zlepšuje rychlost na prohlížečích založených na chrome ale zvyšuje zátěž CPU, mnoho starších verzí chrome má chyby které způsobují že prohlížeč spotřebuje veškerou RAM a spadne pokud je toto povoleno\">wasm", + + "cft_text": "text favicon (prázdné a obnovte pro zakázání)", + "cft_fg": "barva popředí", + "cft_bg": "barva pozadí", + + "cdt_lim": "maximální počet souborů k zobrazení ve složce", + "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ě", + + "tt_entree": "zobrazit navigační panel (postranní strom adresářů)$NKlávesová zkratka: B", + "tt_detree": "zobrazit drobečkovou navigaci$NKlávesová zkratka: B", + "tt_visdir": "posunout k vybrané složce", + "tt_ftree": "přepnout strom složek / textové soubory$NKlávesová zkratka: V", + "tt_pdock": "zobrazit nadřazené složky v ukotveném panelu nahoře", + "tt_dynt": "automaticky rozrůstat jak se strom rozšiřuje", + "tt_wrap": "zalomení řádků", + "tt_hover": "odhalit přetékající řádky při najetí$N( ruší posun pokud kurzor myši $N  není v levém okraji )", + + "ml_pmode": "na konci složky...", + "ml_btns": "příkazy", + "ml_tcode": "transkódovat", + "ml_tcode2": "transkódovat na", + "ml_tint": "odstín", + "ml_eq": "audio ekvalizér", + "ml_drc": "kompresor dynamického rozsahu", + + "mt_loop": "smyčka/opakovat jednu skladbu\">🔁", + "mt_one": "zastavit po jedné skladbě\">1️⃣", + "mt_shuf": "zamíchat skladby v každé složce\">🔀", + "mt_aplay": "automatické přehrávání pokud je ID skladby v odkazu kterým jste přišli na server$N$Nzakázání toho také zastaví aktualizaci URL stránky s ID skladby při přehrávání hudby, aby se zabránilo automatickému přehrávání pokud se tato nastavení ztratí ale URL zůstane\">a▶", + "mt_preload": "začít načítat následující skladbu před koncem pro plynulé přehrávání\">přednahrání", + "mt_prescan": "přejít do následující složky před tím než$Nskončí poslední skladba, aby byl webprohlížeč$Nspokojen aby nezastavil přehrávání\">nav", + "mt_fullpre": "zkusit přednahrát celou skladbu;$N✅ povolit na <b>nespolehlivých</b> připojeních,$N❌ <b>zakázat</b> na pomalých připojeních pravděpodobně\">úplné", + "mt_fau": "na telefonech zabránit zastavení hudby, pokud se další píseň nenahraje dostatečně rychle (může způsobit chybné zobrazení tagů)\">☕️", + "mt_waves": "vlnový posuvník:$Nzobrazit amplitudu zvuku v posuvníku\">~s", + "mt_npclip": "zobrazit tlačítka pro kopírování aktuálně přehrávané písně do schránky\">/np", + "mt_m3u_c": "zobrazit tlačítka pro kopírování$Nvybraných písní jako položky m3u8 playlistu\">📻", + "mt_octl": "integrace s OS (mediální klávesy / osd)\">os-ctl", + "mt_oseek": "povolit posunování přes integraci s OS$N$Npoznámka: na některých zařízeních (iPhone),$Nto nahradí tlačítko další písně\">seek", + "mt_oscv": "zobrazit obal alba v osd\">art", + "mt_follow": "udržet přehrávanou stopu v zobrazení\">🎯", + "mt_compact": "kompaktní ovládání\">⟎", + "mt_uncache": "vymazat cache  (zkuste to, pokud váš prohlížeč uložil$Nporušenou kopii písně a odmítá ji přehrát)\">uncache", + "mt_mloop": "opakovat otevřenou složku\">🔁 loop", + "mt_mnext": "načíst další složku a pokračovat\">📂 next", + "mt_mstop": "zastavit přehrávání\">⏸ stop", + "mt_cflac": "převést flac / wav na {0}\">flac", + "mt_caac": "převést aac / m4a na {0}\">aac", + "mt_coth": "převést všechny ostatní (ne mp3) na {0}\">oth", + "mt_c2opus": "nejlepší volba pro desktopy, laptopy, android\">opus", + "mt_c2owa": "opus-weba, pro iOS 17.5 a novější\">owa", + "mt_c2caf": "opus-caf, pro iOS 11 až 17\">caf", + "mt_c2mp3": "použijte na velmi starých zařízeních\">mp3", + "mt_c2flac": "nejlepší kvalita zvuku, ale obrovské stahování\">flac", + "mt_c2wav": "nekomprimované přehrávání (ještě větší)\">wav", + "mt_c2ok": "výborně, dobrá volba", + "mt_c2nd": "to není doporučený výstupní formát pro vaše zařízení, ale v pořádku", + "mt_c2ng": "vaše zařízení, zdá se, nepodporuje tento výstupní formát, ale zkusíme to", + "mt_xowa": "v iOS jsou chyby bránící přehrávání na pozadí s tímto formátem; použijte prosím caf nebo mp3", + "mt_tint": "úroveň pozadí (0-100) na posuvníku$Nabyste učinili ukládání do vyrovnávací paměti méně rušivým", + "mt_eq": "povoluje ekvalizér a ovládání zisku;$N$Nboost <code>0</code> = standardní 100% hlasitost (nezměněno)$N$Nwidth <code>1  </code> = standardní stereo (nezměněno)$Nwidth <code>0.5</code> = 50% levý-pravý crossfeed$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = odstranění vokálů :^)$N$Npovolení ekvalizéru činí alba bez mezer zcela bez mezer, takže to nechte zapnuté se všemi hodnotami na nule (kromě width = 1), pokud vám na tom záleží", + "mt_drc": "povoluje kompresor dynamického rozsahu (vyrovnávač hlasitosti / brickwaller); také povolí EQ pro vyvážení špaget, takže nastavte všechna EQ pole kromě 'width' na 0, pokud to nechcete$N$Nsnižuje hlasitost zvuku nad THRESHOLD dB; pro každý RATIO dB za THRESHOLD je 1 dB výstupu, takže výchozí hodnoty tresh -24 a ratio 12 znamenají, že by nikdy nemělo být hlasitější než -22 dB a je bezpečné zvýšit boost ekvalizéru na 0.8, nebo dokonce 1.8 s ATK 0 a obrovským RLS jako 90 (funguje pouze ve firefoxu; RLS je max 1 v jiných prohlížečích)$N$N(viz wikipedia, vysvětlují to mnohem lépe)", + + "mb_play": "přehrát", + "mm_hashplay": "přehrát tento audio soubor?", + "mm_m3u": "stiskněte <code>Enter/OK</code> pro Přehrání\nstiskněte <code>ESC/Zrušit</code> pro Úpravu", + "mp_breq": "potřebujete firefox 82+ nebo chrome 73+ nebo iOS 15+", + "mm_bload": "nyní se načítá...", + "mm_bconv": "převádí se na {0}, čekejte prosím...", + "mm_opusen": "váš prohlížeč nemůže přehrát aac / m4a soubory;\ntranscoding na opus je nyní povolen", + "mm_playerr": "přehrávání selhalo: ", + "mm_eabrt": "Pokus o přehrávání byl zrušen", + "mm_enet": "Vaše internetové připojení je nestabilní", + "mm_edec": "Tento soubor je údajně poškozený??", + "mm_esupp": "Váš prohlížeč nerozumí tomuto audio formátu", + "mm_eunk": "Neznámá chyba", + "mm_e404": "Nelze přehrát audio; chyba 404: Soubor nenalezen.", + "mm_e403": "Nelze přehrát audio; chyba 403: Přístup odepřen.\n\nZkuste stisknout F5 pro obnovení, možná jste se odhlásili", + "mm_e500": "Nelze přehrát audio; chyba 500: Zkontrolujte logy serveru.", + "mm_e5xx": "Nelze přehrát audio; chyba serveru ", + "mm_nof": "žádné další audio soubory v okolí nenalezeny", + "mm_prescan": "Hledám hudbu k dalšímu přehrání...", + "mm_scank": "Další píseň nalezena:", + "mm_uncache": "cache vymazána; všechny písně se znovu stáhnou při dalším přehrávání", + "mm_hnf": "tato píseň již neexistuje", + + "im_hnf": "tento obrázek již neexistuje", + + "f_empty": 'tato složka je prázdná', + "f_chide": 'toto skryje sloupec «{0}»\n\nmůžete odkrýt sloupce v záložce nastavení', + "f_bigtxt": "tento soubor má {0} MiB -- opravdu zobrazit jako text?", + "f_bigtxt2": "zobrazit pouze konec souboru? to také povolí sledování/tailing, zobrazí nově přidané řádky textu v reálném čase", + "fbd_more": '<div id="blazy">zobrazuji <code>{0}</code> z <code>{1}</code> souborů; <a href="#" id="bd_more">zobraz {2}</a> nebo <a href="#" id="bd_all">zobraz všechny</a></div>', + "fbd_all": '<div id="blazy">zobrazuji <code>{0}</code> z <code>{1}</code> souborů; <a href="#" id="bd_all">zobraz všechny</a></div>', + "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_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 <code>.PARTIAL</code>. Stiskněte prosím Zrušit nebo Escape.\n\nStisknutím OK / Enter ignorujete toto varování a pokračujete ve stahování <code>.PARTIAL</code> dočasného souboru, což téměř jistě vyústí jako poškozená data.", + + "ft_paste": "vložit {0} položek$NKlávesová zkratka: ctrl-V", + "fr_eperm": 'nelze přejmenovat:\nnemáte oprávnění “přesunout” v této složce', + "fd_eperm": 'nelze smazat:\nnemáte oprávnění “smazat” v této složce', + "fc_eperm": 'nelze vyjmout:\nnemáte oprávnění “přesunout” v této složce', + "fp_eperm": 'nelze vložit:\nnemáte oprávnění “zapisovat” v této složce', + "fr_emore": "vyberte alespoň jednu položku k přejmenování", + "fd_emore": "vyberte alespoň jednu položku ke smazání", + "fc_emore": "vyberte alespoň jednu položku k vyjmutí", + "fcp_emore": "vyberte alespoň jednu položku k zkopírování do schránky", + + "fs_sc": "sdílet složku, ve které se nacházíte", + "fs_ss": "sdílet vybrané soubory", + "fs_just1d": "nelze vybrat více než jednu složku,\nnebo míchat soubory a složky v jednom výběru", + "fs_abrt": "❌ zrušit", + "fs_rand": "🎲 náhodný.název", + "fs_go": "✅ vytvořit sdílení", + "fs_name": "název", + "fs_src": "zdroj", + "fs_pwd": "heslo", + "fs_exp": "vypršení", + "fs_tmin": "min", + "fs_thrs": "hodin", + "fs_tdays": "dní", + "fs_never": "navždy", + "fs_pname": "volitelný název odkazu; bude náhodný, pokud je prázdný", + "fs_tsrc": "soubor nebo složka ke sdílení", + "fs_ppwd": "volitelné heslo", + "fs_w8": "vytváření sdílení...", + "fs_ok": "stiskněte <code>Enter/OK</code> pro zkopírování do schránky\nstiskněte <code>ESC/Zrušit</code> pro zavření", + + "frt_dec": "může opravit některé případy porušených názvů souborů\">url-decode", + "frt_rst": "resetovat změněné názvy souborů zpět na původní\">↺ reset", + "frt_abrt": "zrušit a zavřít toto okno\">❌ cancel", + "frb_apply": "PŘEJMENOVAT", + "fr_adv": "dávkové / metadata / přejmenování podle vzoru\">pokročilé", + "fr_case": "regex citlivý na velikost písmen\">velikost", + "fr_win": "názvy bezpečné pro windows; nahradit <code><>:"\\|?*</code> japonskými plnošířkovými znaky\">win", + "fr_slash": "nahradit <code>/</code> znakem který nezpůsobí vytvoření nových složek\">žádné /", + "fr_re": "vzor regex hledání k aplikaci na původní názvy souborů; zachycené skupiny mohou být odkazovány v poli formátu níže jako <code>(1)</code> a <code>(2)</code> atd.", + "fr_fmt": "inspirováno foobar2000:$N<code>(title)</code> je nahrazeno názvem skladby,$N<code>[(artist) - ](title)</code> přeskočí [tuto] část pokud je umělec prázdný$N<code>$lpad((tn),2,0)</code> doplní číslo stopy na 2 číslice", + "fr_pdel": "smazat", + "fr_pnew": "uložit jako", + "fr_pname": "zadejte název pro vaše nové přednastavení", + "fr_aborted": "zrušeno", + "fr_lold": "starý název", + "fr_lnew": "nový název", + "fr_tags": "tagy pro vybrané soubory (pouze pro čtení, jen pro referenci):", + "fr_busy": "přejmenovávám {0} položek...\n\n{1}", + "fr_efail": "přejmenování selhalo:\n", + "fr_nchg": "{0} z nových názvů bylo změněno kvůli <code>win</code> a/nebo <code>žádné /</code>\n\nPokračovat s těmito změněnými novými názvy?", + + "fd_ok": "mazání OK", + "fd_err": "mazání selhalo:\n", + "fd_none": "nic nebylo smazáno; možná blokováno konfigurací serveru (xbd)?", + "fd_busy": "mažu {0} položek...\n\n{1}", + "fd_warn1": "SMAZAT těchto {0} položek?", + "fd_warn2": "<b>Poslední šance!</b> Nelze vrátit zpět. Smazat?", + + "fc_ok": "vyjmout {0} položek", + "fc_warn": 'vyjmout {0} položek\n\nale: pouze <b>tato</b> karta prohlížeče je může vložit\n(protože výběr je tak absolutně masivní)', + + "fcc_ok": "zkopírováno {0} položek do schránky", + "fcc_warn": 'zkopírováno {0} položek do schránky\n\nale: pouze <b>tato</b> karta prohlížeče je může vložit\n(protože výběr je tak absolutně masivní)', + + "fp_apply": "použít tyto názvy", + "fp_ecut": "nejprve vyjměte nebo zkopírujte nějaké soubory / složky k vložení / přesunutí\n\npoznámka: můžete vyjmout / vložit přes různé karty prohlížeče", + "fp_ename": "{0} položek sem nelze přesunout protože názvy jsou již obsazené. Dejte jim nové názvy níže pro pokračování, nebo název nechte prázdný pro přeskočení:", + "fcp_ename": "{0} položek sem nelze zkopírovat protože názvy jsou již obsazené. Dejte jim nové názvy níže pro pokračování, nebo název nechte prázdný pro přeskočení:", + "fp_emore": "stále jsou některé kolize názvů souborů k opravě", + "fp_ok": "přesun OK", + "fcp_ok": "kopírování OK", + "fp_busy": "přesouvám {0} položek...\n\n{1}", + "fcp_busy": "kopíruji {0} položek...\n\n{1}", + "fp_abrt": "přerušuji...", //m + "fp_err": "přesun selhal:\n", + "fcp_err": "kopírování selhalo:\n", + "fp_confirm": "přesunout těchto {0} položek sem?", + "fcp_confirm": "zkopírovat těchto {0} položek sem?", + "fp_etab": 'selhalo čtení schránky z jiné karty prohlížeče', + "fp_name": "nahrávání souboru z vašeho zařízení. Dejte mu název:", + "fp_both_m": '<h6>vyberte co vložit</h6><code>Enter</code> = Přesunout {0} souborů z «{1}»\n<code>ESC</code> = Nahrát {2} souborů z vašeho zařízení', + "fcp_both_m": '<h6>vyberte co vložit</h6><code>Enter</code> = Kopírovat {0} souborů z «{1}»\n<code>ESC</code> = Nahrát {2} souborů z vašeho zařízení', + "fp_both_b": '<a href="#" id="modal-ok">Přesunout</a><a href="#" id="modal-ng">Nahrát</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopírovat</a><a href="#" id="modal-ng">Nahrát</a>', + + "mk_noname": "napište název do textového pole vlevo předtím než to uděláte :p", + + "tv_load": "Načítání textového dokumentu:\n\n{0}\n\n{1}% ({2} z {3} MiB načteno)", + "tv_xe1": "nelze načíst textový soubor:\n\nchyba ", + "tv_xe2": "404, soubor nenalezen", + "tv_lst": "seznam textových souborů v", + "tvt_close": "návrat do zobrazení složky$NKlávesová zkratka: M (nebo Esc)\">❌ zavřít", + "tvt_dl": "stáhnout tento soubor$NKlávesová zkratka: Y\">💾 stáhnout", + "tvt_prev": "zobrazit předchozí dokument$NKlávesová zkratka: i\">⬆ předchozí", + "tvt_next": "zobrazit následující dokument$NKlávesová zkratka: K\">⬇ další", + "tvt_sel": "vybrat soubor   ( pro vyjmutí / kopírování / mazání / ... )$NKlávesová zkratka: S\">výběr", + "tvt_edit": "otevřít soubor v textovém editoru$NKlávesová zkratka: E\">✏️ upravit", + "tvt_tail": "sledovat soubor pro změny; zobrazit nové řádky v reálném čase\">📡 sledovat", + "tvt_wrap": "zalamování slov\">↵", + "tvt_atail": "zamknout posun na konec stránky\">⚓", + "tvt_ctail": "dekódovat barvy terminálu (ansi escape kódy)\">🌈", + "tvt_ntail": "limit zpětného posouvání (kolik bajtů textu ponechat načtených)", + + "m3u_add1": "skladba přidána do m3u playlistu", + "m3u_addn": "{0} skladeb přidáno do m3u playlistu", + "m3u_clip": "m3u playlist nyní zkopírován do schránky\n\nměli byste vytvořit nový textový soubor pojmenovaný něco.m3u a vložit playlist do tohoto dokumentu; toto ho učiní přehratelným", + + "gt_vau": "nezobrazovat videa, jen přehrát zvuk\">🎧", + "gt_msel": "povolit výběr souborů; ctrl-klik na soubor pro přepsání$N$N<em>když aktivní: dvojklik na soubor / složku pro otevření</em>$N$NKlávesová zkratka: S\">výběr více", + "gt_crop": "ořez náhledů na střed\">ořez", + "gt_3x": "náhledy s vysokým rozlišením\">3x", + "gt_zoom": "zoom", + "gt_chop": "rozdělit", + "gt_sort": "řadit podle", + "gt_name": "název", + "gt_sz": "velikost", + "gt_ts": "datum", + "gt_ext": "typ", + "gt_c1": "více zkrátit názvy souborů (zobrazit méně)", + "gt_c2": "méně zkrátit názvy souborů (zobrazit více)", + + "sm_w8": "hledám...", + "sm_prev": "výsledky hledání níže jsou z předchozího dotazu:\n ", + "sl_close": "zavřít výsledky hledání", + "sl_hits": "zobrazuji {0} zásahů", + "sl_moar": "načíst více", + + "s_sz": "velikost", + "s_dt": "datum", + "s_rd": "cesta", + "s_fn": "název", + "s_ta": "tagy", + "s_ua": "nahráno@", + "s_ad": "pokročilé", + "s_s1": "minimum MiB", + "s_s2": "maximum MiB", + "s_d1": "min. iso8601", + "s_d2": "max. iso8601", + "s_u1": "nahráno po", + "s_u2": "a/nebo před", + "s_r1": "cesta obsahuje   (oddělené mezerami)", + "s_f1": "název obsahuje   (negace s -ne)", + "s_t1": "tagy obsahují   (^=začátek, konec=$)", + "s_a1": "specifické vlastnosti metadat", + + "md_eshow": "nelze vykreslit ", + "md_off": "[📜<em>readme</em>] zakázáno v [⚙️] -- dokument skryt", + + "badreply": "Selhalo parsování odpovědi ze serveru", + + "xhr403": "403: Přístup odepřen\n\nzkuste stisknout F5, možná jste se odhlásili", + "xhr0": "neznámý (pravděpodobně ztraceno spojení se serverem, nebo server je offline)", + "cf_ok": "omlouváme se za to -- DD" + wah + "oS ochrana se aktivovala\n\nvěci by se měly obnovit asi za 30 sekund\n\npokud se nic nestane, stiskněte F5 pro obnovení stránky", + "tl_xe1": "nelze vypsat podsložky:\n\nchyba ", + "tl_xe2": "404: Složka nenalezena", + "fl_xe1": "nelze vypsat soubory ve složce:\n\nchyba ", + "fl_xe2": "404: Složka nenalezena", + "fd_xe1": "nelze vytvořit podsložku:\n\nchyba ", + "fd_xe2": "404: Nadřazená složka nenalezena", + "fsm_xe1": "nelze odeslat zprávu:\n\nchyba ", + "fsm_xe2": "404: Nadřazená složka nenalezena", + "fu_xe1": "selhalo načtení unpost seznamu ze serveru:\n\nchyba ", + "fu_xe2": "404: Soubor nenalezen??", + + "fz_tar": "nekomprimovaný gnu-tar soubor (linux / mac)", + "fz_pax": "nekomprimovaný tar formátu pax (pomalejší)", + "fz_targz": "gnu-tar s gzip kompresí úrovně 3$N$Nto je obvykle velmi pomalé, takže$Npoužijte místo toho nekomprimovaný tar", + "fz_tarxz": "gnu-tar s xz kompresí úrovně 1$N$Nto je obvykle velmi pomalé, takže$Npoužijte místo toho nekomprimovaný tar", + "fz_zip8": "zip s utf8 názvy souborů (možná problematické na windows 7 a starších)", + "fz_zipd": "zip s tradičními cp437 názvy souborů, pro opravdu starý software", + "fz_zipc": "cp437 s crc32 vypočítaným brzy,$Npro MS-DOS PKZIP v2.04g (říjen 1993)$N(trvá déle zpracovat před začátkem stahování)", + + "un_m1": "můžete smazat vaše nedávné nahrání (nebo zrušit nedokončené) níže", + "un_upd": "obnovit", + "un_m4": "nebo sdílet soubory viditelné níže:", + "un_ulist": "zobrazit", + "un_ucopy": "kopírovat", + "un_flt": "volitelný filtr:  URL musí obsahovat", + "un_fclr": "vymazat filtr", + "un_derr": 'unpost-delete selhalo:\n', + "un_f5": 'něco se pokazilo, zkuste prosím obnovit, nebo stiskněte F5', + "un_uf5": "omlouváme se ale musíte obnovit stránku (například stisknutím F5 nebo CTRL-R) předtím než toto nahrávání může být zrušeno", + "un_nou": '<b>varování:</b> server je příliš zaneprázdněn pro zobrazení nedokončených nahrávání; za chvíli klikněte na odkaz "obnovit"', + "un_noc": '<b>varování:</b> unpost plně nahraných souborů není povoleno/dovoleno v konfiguraci serveru', + "un_max": "zobrazuji prvních 2000 souborů (použijte filtr)", + "un_avail": "{0} nedávných nahrávání může být smazáno<br />{1} nedokončených může být zrušeno", + "un_m2": "řazeno podle času nahrávání; nejnovější první:", + "un_no1": "počkej! žádná nahrávání nejsou dostatečně nedávná", + "un_no2": "počkej! žádná nahrávání odpovídající tomuto filtru nejsou dostatečně nedávná", + "un_next": "smazat dalších {0} souborů níže", + "un_abrt": "zrušit", + "un_del": "smazat", + "un_m3": "načítám vaše nedávné nahrání...", + "un_busy": "mažu {0} souborů...", + "un_clip": "{0} odkazů zkopírováno do schránky", + + "u_https1": "měli byste", + "u_https2": "přejít na https", + "u_https3": "pro lepší výkon", + "u_ancient": "váš prohlížeč je úctyhodně starý -- možná byste měli <a href=\"#\" onclick=\"goto('bup')\">použít bup</a>", + "u_nowork": "vyžadován firefox 53+ nebo chrome 57+ nebo iOS 11+", + "tail_2old": "vyžadován firefox 105+ nebo chrome 71+ nebo iOS 14.5+", + "u_nodrop": "váš prohlížeč je příliš starý pro nahrávání přetažením (drag-and-drop)", + "u_notdir": "toto není složka!\n\nváš prohlížeč je příliš starý,\nzkuste prosím soubory přetáhnout", + "u_uri": "pro přetažení obrázků z jiných oken prohlížeče,\nje prosím přetáhněte na velké tlačítko pro nahrávání", + "u_enpot": "přepnout na <a href=\"#\">potato UI</a> (může zrychlit nahrávání)", + "u_depot": "přepnout na <a href=\"#\">fancy UI</a> (může zpomalit nahrávání)", + "u_gotpot": "přepínám na potato UI pro zrychlení nahrávání,\n\npokud nesouhlasíte, klidně jej přepněte zpět!", + "u_pott": "<p>soubory:   <b>{0}</b> dokončeno,   <b>{1}</b> selhalo,   <b>{2}</b> nahrává se,   <b>{3}</b> ve frontě</p>", + "u_ever": "toto je základní nahrávání; up2k vyžaduje alespoň<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": "toto je základní nahrávání; <a href=\"#\" id=\"u2yea\">up2k</a> je lepší", + "u_uput": "optimalizovat pro rychlost (přeskočit kontrolní součet)", + "u_ewrite": "nemáte oprávnění k zápisu do této složky", + "u_eread": "nemáte oprávnění ke čtení této složky", + "u_enoi": "vyhledávání souborů není povoleno v konfiguraci serveru", + "u_enoow": "přepsání zde nebude fungovat; je vyžadováno oprávnění k mazání", + "u_badf": "Těchto {0} souborů (z celkem {1}) bylo přeskočeno, pravděpodobně kvůli oprávněním v souborovém systému:\n\n", + "u_blankf": "Těchto {0} souborů (z celkem {1}) je prázdných; přesto je nahrát?\n\n", + "u_applef": "Těchto {0} souborů (z celkem {1}) je pravděpodobně nežádoucích;\nStiskněte <code>OK/Enter</code> pro PŘESKOČENÍ následujících souborů,\nStiskněte <code>Zrušit/ESC</code> pro Zahrnutí a NAHRÁNÍ i těchto souborů:\n\n", + "u_just1": "\nMožná to bude fungovat lépe, když vyberete pouze jeden soubor", + "u_ff_many": "pokud používáte <b>Linux / MacOS / Android,</b> takové množství souborů <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>může</em> shodit Firefox!</a>\npokud se to stane, zkuste to prosím znovu (nebo použijte Chrome).", + "u_up_life": "Tento upload bude smazán ze serveru\n{0} po jeho dokončení", + "u_asku": "Nahrát {0} souborů do <code>{1}</code>", + "u_unpt": "toto nahrávání můžete vrátit zpět / smazat pomocí 🧯 vlevo nahoře", + "u_bigtab": "chystám se zobrazit {0} souborů\n\nto může shodit váš prohlížeč, jste si jisti?", + "u_scan": "Skenuji soubory...", + "u_dirstuck": "procházení adresáře se zaseklo při pokusu o přístup k následujícím {0} položkám; budou přeskočeny:", + "u_etadone": "Hotovo ({0}, {1} souborů)", + "u_etaprep": "(příprava na nahrávání)", + "u_hashdone": "hashování dokončeno", + "u_hashing": "hashování", + "u_hs": "navazuji spojení...", + "u_started": "soubory se nyní nahrávají; viz [🚀]", + "u_dupdefer": "duplikát; bude zpracován po všech ostatních souborech", + "u_actx": "klikněte na tento text, abyste zabránili ztrátě<br />výkonu při přepínání do jiných oken/záložek", + "u_fixed": "OK!  Opraveno 👍", + "u_cuerr": "nepodařilo se nahrát část {0} z {1};\npatrně neškodné, pokračuji\n\nsoubor: {2}", + "u_cuerr2": "server odmítl nahrání (část {0} z {1});\nzopakuji později\n\nsoubor: {2}\n\nchyba ", + "u_ehstmp": "zopakuji pokus; viz vpravo dole", + "u_ehsfin": "server odmítl požadavek na dokončení nahrávání; opakuji pokus...", + "u_ehssrch": "server odmítl požadavek na vyhledávání; opakuji pokus...", + "u_ehsinit": "server odmítl požadavek na zahájení nahrávání; opakuji pokus...", + "u_eneths": "síťová chyba při navazování spojení pro nahrávání; opakuji pokus...", + "u_enethd": "síťová chyba při ověřování existence cíle; opakuji pokus...", + "u_cbusy": "čekám, až nám server po síťovém problému začne znovu důvěřovat...", + "u_ehsdf": "na serveru došlo místo na disku!\n\nbudu to zkoušet dál, pro případ, že někdo\nuvolní dostatek místa pro pokračování", + "u_emtleak1": "vypadá to, že váš webový prohlížeč může mít únik paměti (memory leak);\nprosím", + "u_emtleak2": " <a href=\"{0}\">přejděte na https (doporučeno)</a> nebo ", + "u_emtleak3": " ", + "u_emtleakc": "zkuste následující:\n<ul><li>stiskněte <code>F5</code> pro obnovení stránky</li><li>poté vypněte tlačítko  <code>mt</code>  v  <code>⚙️ nastavení</code></li><li>a zkuste nahrávání znovu</li></ul>Nahrávání bude o něco pomalejší, ale co se dá dělat.\nOmlouváme se za potíže!\n\nPS: chrome v107 <a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=1354816\" target=\"_blank\">obsahuje opravu</a> pro tento problém", + "u_emtleakf": "zkuste následující:\n<ul><li>stiskněte <code>F5</code> pro obnovení stránky</li><li>poté zapněte <code>🥔</code> (potato) v rozhraní nahrávání<li>a zkuste nahrávání znovu</li></ul>\nPS: firefox snad <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\">bude mít opravu</a> v některé z příštích verzí", + "u_s404": "nenalezeno na serveru", + "u_expl": "vysvětlit", + "u_maxconn": "většina prohlížečů omezuje počet na 6, ale firefox umožňuje toto navýšit pomocí <code>connections-per-server</code> v <code>about:config</code>", + "u_tu": "<p class=\"warn\">VAROVÁNÍ: turbo zapnuto, <span> klient nemusí detekovat a obnovit nedokončené nahrávání; viz nápovědu u tlačítka turbo</span></p>", + "u_ts": "<p class=\"warn\">VAROVÁNÍ: turbo zapnuto, <span> výsledky vyhledávání mohou být nesprávné; viz nápovědu u tlačítka turbo</span></p>", + "u_turbo_c": "turbo je vypnuto v konfiguraci serveru", + "u_turbo_g": "vypínám turbo, protože nemáte oprávnění\nk výpisu adresářů na tomto svazku", + "u_life_cfg": 'automatické smazání po <input id="lifem" p="60" /> min (nebo <input id="lifeh" p="3600" /> hodinách)', + "u_life_est": 'nahrání bude smazáno <span id="lifew" tt="local time">---</span>', + "u_life_max": 'tato složka vynucuje\nmax. životnost {0}', + "u_unp_ok": 'unpost je povoleno pro {0}', + "u_unp_ng": 'unpost NEBUDE povoleno', + "ue_ro": 'váš přístup k této složce je pouze pro čtení\n\n', + "ue_nl": 'momentálně nejste přihlášeni', + "ue_la": 'momentálně jste přihlášeni jako "{0}"', + "ue_sr": 'momentálně jste v režimu vyhledávání souborů\n\npřepněte do režimu nahrávání kliknutím na lupu 🔎 (vedle velkého tlačítka HLEDAT) a zkuste nahrávání znovu\n\nomlouváme se', + "ue_ta": 'zkuste nahrávání znovu, nyní by to mělo fungovat', + "ue_ab": "tento soubor se již nahrává do jiné složky a toto nahrávání musí být dokončeno předtím, než může být soubor nahrán jinam.\n\nMůžete zrušit a zapomenout na původní nahrávání pomocí levého horního 🧯", + "ur_1uo": "OK: Soubor úspěšně nahrán", + "ur_auo": "OK: Všech {0} souborů úspěšně nahráno", + "ur_1so": "OK: Soubor nalezen na serveru", + "ur_aso": "OK: Všech {0} souborů nalezeno na serveru", + "ur_1un": "Nahrání selhalo, omlouváme se", + "ur_aun": "Všech {0} nahrání selhalo, omlouváme se", + "ur_1sn": "Soubor NEBYL nalezen na serveru", + "ur_asn": "{0} souborů NEBYLO nalezeno na serveru", + "ur_um": "Dokončeno;\n{0} nahrání OK,\n{1} nahrání selhalo, omlouváme se", + "ur_sm": "Dokončeno;\n{0} souborů nalezeno na serveru,\n{1} souborů NENALEZENO na serveru", + + "lang_set": "obnovit stránku, aby se změna projevila?", + }, "deu": { "tt": "Deutsch", @@ -1995,6 +2644,7 @@ var Ls = { "gou": 'zum übergeordneter Ordner springen">hoch', "gon": 'zum nächsten Ordner springen">nächst.', "logout": "Abmelden ", + "login": "Anmelden", //m "access": " Zugriff", "ot_close": "Submenu schliessen", "ot_search": "Dateien nach Attributen, Pfad/Name, Musiktags oder beliebiger Kombination suchen$N$N<code>foo bar</code> = muss «foo» und «bar» enthalten,$N<code>foo -bar</code> = muss «foo» aber nicht «bar» enthalten,$N<code>^yana .opus$</code> = beginnt mit «yana» und ist «opus»-Datei$N<code>"try unite"</code> = genau «try unite» enthalten$N$NDatumsformat ist iso-8601, z.B.$N<code>2009-12-31</code> oder <code>2020-09-12 23:30:00</code>", @@ -2181,13 +2831,15 @@ var Ls = { "mt_mloop": "offenen Ordner wiederholen\">🔁 Schleife", "mt_mnext": "nächsten Ordner laden und fortfahren\">📂 nächster", "mt_mstop": "Wiedergabe beenden\">⏸ Stop", - "mt_cflac": "FLAC / WAV zu OPUS konvertierebn\">flac", - "mt_caac": "AAC / M4A zu OPUS konvertieren\">aac", - "mt_coth": "Convertiere alle Dateien (die nicht MP3 sind) zu OPUS\">oth", + "mt_cflac": "FLAC / WAV zu {0} konvertierebn\">flac", + "mt_caac": "AAC / M4A zu {0} konvertieren\">aac", + "mt_coth": "Convertiere alle Dateien (die nicht MP3 sind) zu {0}\">oth", "mt_c2opus": "Beste Wahl für Desktops, Laptops, Android\">opus", "mt_c2owa": "opus-weba, für iOS 17.5 und neuer\">owa", "mt_c2caf": "opus-caf, für iOS 11 bis 17\">caf", "mt_c2mp3": "benutze dieses Format für ältere Geräte\">mp3", + "mt_c2flac": "beste Klangqualität, aber große Downloads\">flac", //m + "mt_c2wav": "unkomprimierte Wiedergabe (noch größer)\">wav", //m "mt_c2ok": "Gute Wahl, Chef!", "mt_c2nd": "Das ist nicht das empfohlene Ausgabeformat für dein Gerät, aber passt schon", "mt_c2ng": "Dein Gerät scheint dieses Ausgabeformat nicht zu unterstützen, aber lass trotzdem mal probieren", @@ -2306,6 +2958,7 @@ var Ls = { "fcp_ok": "Kopieren OK", "fp_busy": "Verschiebe {0} Elemente...\n\n{1}", "fcp_busy": "Kopiere {0} Elemente...\n\n{1}", + "fp_abrt": "Abbrechen...", //m "fp_err": "Verschieben fehlgeschlagen:\n", "fcp_err": "Kopieren fehlgeschlagen:\n", "fp_confirm": "Diese {0} Elemente hierher verschieben?", @@ -2430,7 +3083,7 @@ var Ls = { "u_https1": "für bessere Performance solltest du", "u_https2": "auf HTTPS wechseln", - "u_https3": "", + "u_https3": " ", "u_ancient": 'Dein Browser ist verdammt antik -- vielleicht solltest du <a href="#" onclick="goto(\'bup\')">stattdessen bup benutzen</a>', "u_nowork": "Benötigt Firefox 53+ oder Chrome 57+ oder iOS 11+", "tail_2old": "Benötigt Firefox 105+ oder Chrome 71+ oder iOS 14.5+", @@ -2480,7 +3133,7 @@ var Ls = { "u_ehsdf": "Server hat kein Speicherplatz mehr!\n\nwerde es erneut versuchen, falls jemand\ngenug Platz schafft um fortzufahren", "u_emtleak1": "scheint, als ob dein Browser ein Memory Leak hätte;\nbitte", "u_emtleak2": ' <a href="{0}">wechsle auf HTTPS (empfohlen)</a> oder ', - "u_emtleak3": '', + "u_emtleak3": ' ', "u_emtleakc": 'versuche folgendes:\n<ul><li>drücke <code>F5</code> um die Seite neu zu laden</li><li>deaktivere dann den  <code>mt</code>  Button in den  <code>⚙️ Einstellungen</code></li><li>und versuche den Upload nochmal.</li></ul>Uploads werden etwas langsamer sein, aber man kann ja nicht alles haben.\nSorry für die Umstände !\n\nPS: Chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">hat ein Bugfix</a> dafür', "u_emtleakf": 'versuche folgendes:\n<ul><li>drücke <code>F5</code> um die Seite neu zu laden</li><li>aktivere dann <code>🥔</code> (potato) im Upload UI<li>und versuche den Upload nochmal</li></ul>\nPS: Firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">hat hoffentlich irgendwann ein Bugfix</a>', "u_s404": "nicht auf dem Server gefunden", @@ -2514,6 +3167,637 @@ var Ls = { "lang_set": "Neuladen um Änderungen anzuwenden?", }, + "epo": { + "tt": "Esperanto", + + "cols": { + "c": "ago-butonoj", + "dur": "daŭro", + "q": "kvalito / bitrapido", + "Ac": "sonkodeko", + "Vc": "videokodeko", + "Fmt": "formato / ujo", + "Ahash": "kontrolsumo de aŭdio", + "Vhash": "kontrolsumo de video", + "Res": "distingivo", + "T": "dosiertipo", + "aq": "kvalito / bitrapido de aŭdio", + "vq": "kvalito / bitrapido de video", + "pixfmt": "specimenado / strukturo de bilderoj", + "resw": "horizontala distingivo", + "resh": "vertikala distingivo", + "chs": "nombro de aŭdio-kanaloj", + "hz": "sonpecrapido" + }, + + "hks": [ + [ + "misc", + ["ESK", "malfermi variajn aferojn"], + + "file-manager", + ["G", "baskuli inter lista kaj krada vido"], + ["T", "baskuli montradon de bildetoj"], + ["⇧ A/D", "grandeco de bildetoj"], + ["stir-K", "forigi elektitajn"], + ["stir-X", "eltondi elektaĵon al tondujo"], + ["stir-C", "kopii elektaĵon al tondujo"], + ["stir-V", "alglui (movi/kopii) ĉi tien"], + ["Y", "elŝuti elektitajn"], + ["F2", "alinomi elektitajn"], + + "file-list-sel", + ["spacoklavo", "baskuli elektadon de dosieroj"], + ["↑/↓", "movi elektado-kursoron"], + ["stir ↑/↓", "movi kursoron kaj vidujon"], + ["⇧ ↑/↓", "elekti (mal)sekvan dosieron"], + ["stir-A", "elekti ĉiujn dosier(uj)ojn"], + ], [ + "navigation", + ["B", "baskuli inter paĝnivela kaj arbovida navigo"], + ["I/K", "(mal-)sekva dosierujo"], + ["M", "parent folder (or unexpand current)"], + ["V", "baskuli inter montrado de dosierujoj aŭ tekst-dosieroj en toggle folders / textfiles en arbovida navig-panelo"], + ["A/D", "grandeco de arbovida navig-panelo"], + ], [ + "audio-player", + ["J/L", "(mal-)sekva kanto"], + ["U/O", "iri 10 sekundoj (mal)antaŭen"], + ["0..9", "iri al 0%..90%"], + ["P", "ludi/paŭzigi (ankaŭ komencas)"], + ["S", "elekti ludantan kanton"], + ["Y", "elŝuti kanton"], + ], [ + "image-viewer", + ["J/L, ←/→", "(mal)sekva bildo"], + ["Home/End", "unua/lasta bildo"], + ["F", "plenekrana vido"], + ["R", "turni dekstrumen"], + ["⇧ R", "turni maldekstrumen"], + ["S", "elekti bildon"], + ["Y", "elŝuti bildon"], + ], [ + "video-player", + ["U/O", "iri 10 sekundoj (mal)antaŭen"], + ["P/K/Spaco", "ludi/paŭzigi"], + ["C", "??continue playing next"], + ["V", "cikla ludado"], + ["M", "silentigi"], + ["[ and ]", "agordi intervalon de cikla ludado"], + ], [ + "textfile-viewer", + ["I/K", "(mal)sekva dosiero"], + ["M", "fermi dosieron"], + ["E", "redakti dosieron"], + ["S", "elekti dosieron (por eltondado/kopiado/alinomado)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Rezigni", + + "enable": "Ŝalti", + "danger": "DANĜERO", + "clipped": "kopiita al tondujo", + + "ht_s1": "sekundo", + "ht_s2": "sekundoj", + "ht_m1": "minuto", + "ht_m2": "minutoj", + "ht_h1": "horo", + "ht_h2": "horoj", + "ht_d1": "tago", + "ht_d2": "tagoj", + "ht_and": " kaj ", + + "goh": "stirpanelo", + "gop": 'malsekva dosierujo">malsekva', + "gou": 'supra dosierujo">supren', + "gon": 'sekva dosierujo">sekva', + "logout": "Adiaŭi kiel ", + "login": "Ensaluti", + "access": " atingo", + "ot_close": "fermi submenuon", + "ot_search": "serĉi dosierojn per atributoj, indiko / nomo, etikedoj de muziko aŭ ĉiu kombinaĵo de tiuj parametroj$N$N<code>foo bar</code> = devas enhavi ambaŭ «foo» kaj «bar»,$N<code>foo -bar</code> = devas enhavi «foo», sed ne «bar»,$N<code>^yana .opus$</code> = komenci per «yana» kaj esti dosieron de formato «opus»$N<code>"try unite"</code> = enhavi precipe «try unite»$N$Nformato de datoj estas iso-8601, ekzemple$N<code>2009-12-31</code> aŭ <code>2020-09-12 23:30:00</code>", + "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-md: krei novan markdown-dosieron", + "ot_msg": "msg: sendi mesaĝon al servila protokolo", + "ot_mp": "agordoj de medialudilo", + "ot_cfg": "aliaj agordoj", + "ot_u2i": 'up2k: alŝuti dosierojn (se vi havas skribo-atingon) aŭ ŝalti ŝerc-reĝimon por determini, ĉu dosieroj jam ekzistas ie ĉe la servilo$N$Nalŝutoj estas daŭrigeblaj, plurfadenaj, kaj prezervas tempindikojn, sed ĝi estas pli procesor-intensa ol [🎈]  (la fundamenta alŝutilo)<br /><br />dum alŝutado, ĉi tiu simbolo iĝas plenumindikilo!', + "ot_u2w": 'up2k: alŝuti dosierojn kun subteno de daŭrigeblo (fermu vian retumilon kaj demetu la samajn dosierojn poste)$N$Nalŝutoj estas daŭrigeblaj, plurfadenaj, kaj prezervas tempindikojn, sed ĝi estas pli procesor-intensa ol [🎈]  (la fundamenta alŝutilo)<br /><br />dum alŝutado, ĉi tiu simbolo iĝas plenumindikilo!', + "ot_noie": 'Bonvolu uzi retumilojn Chrome / Firefox / Edge', + + "ab_mkdir": "krei dosierujon", + "ab_mkdoc": "krei markdown-dosieron", + "ab_msg": "sendi mesaĝon al protokolo", + + "ay_path": "iri al dosierujoj", + "ay_files": "iri al dosieroj", + + "wt_ren": "alinomi elektitajn aĵojn$NFulmoklavo: F2", + "wt_del": "forigi elektitajn aĵojn$NFulmoklavo: stir-K", + "wt_cut": "eltondi elektitajn aĵojn <small>(do alglui ien aliloke)</small>$NFulmoklavo: stir-X", + "wt_cpy": "kopii elektitajn aĵojn al tondujo$N(por alglui ien aliloke)$NFulmoklavo: stir-C", + "wt_pst": "alglui antaŭe eltonditajn / kopiitajn aĵojn$NFulmoklavo: stir-V", + "wt_selall": "elekti ĉiujn dosierojn$NFulmoklavo: stir-A (se dosiero estas elektita)", + "wt_selinv": "inversigi elektaĵon", + "wt_zip1": "elŝuti dosierujon kiel arkivo", + "wt_selzip": "elŝuti elektaĵon kiel arkivo", + "wt_seldl": "elŝuti elektaĵon kiel apartaj dosieroj$NFulmoklavo: Y", + "wt_npirc": "kopii IRC-formatan muzikaĵ-informon", + "wt_nptxt": "kopii tekstan muzikaĵ-informon", + "wt_m3ua": "aldoni al m3u-ludliston (klaku butonon <code>📻copy</code> poste)", + "wt_m3uc": "kopii m3u-ludliston al tondujo", + "wt_grid": "baskuli kradan / listan vidon$NFulmoklavo: G", + "wt_prev": "malsekva muzikaĵo$NFulmoklavo: J", + "wt_play": "ludi / paŭzigi$NFulmoklavo: P", + "wt_next": "sekva muzikaĵo$NFulmoklavo: L", + + "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", + "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", + "ut_srch": "ne alŝuti ion ajn, nur kontroli, ke la dosieroj $N jam ekzistas ĉe la servilo (ĉiuj dosierujoj, kiuj vi povas legi, estos skanitaj)", + "ut_par": "paŭzi alŝutadon per agordado kiel 0$N$Npligrandigi, se via konekto estas malrapida aŭ malfruema$N$Nagordi kiel 1, se la loka reto aŭ servila disko ne estas sufiĉe rapidaj", + "ul_btn": "demeti dosier(uj)ojn<br>ĉi tien (aŭ alklaki ĉi tien)", + "ul_btnu": "A L Ŝ U T I", + "ul_btns": "S E R Ĉ I", + + "ul_hash": "k-sumado", + "ul_send": "sendado", + "ul_done": "finita", + "ul_idle1": "neniuj alŝutoj envicigitaj", + "ut_etah": "meza rapido de <em>kontrolsumado</em>, kaj pritaksita tempo ĝis la fino", + "ut_etau": "meza rapido de <em>alŝutado</em>, kaj pritaksita tempo ĝis la fino", + "ut_etat": "meza <em>tuta</em> rapido, kaj pritaksita tempo ĝis la fino", + + "uct_ok": "sukcese plenumita", + "uct_ng": "malbona: malsukceso / malakcepto / ne trovita (no good)", + "uct_done": "ambaŭ ok kaj ng", + "uct_bz": "kontrolsumado aŭ alŝutado (busy)", + "uct_q": "envicigita (queue)", + + "utl_name": "dosiernomo", + "utl_ulist": "listigi", + "utl_ucopy": "kopii", + "utl_links": "ligilojn", + "utl_stat": "stato", + "utl_prog": "progreso", + + // keep short: + "utl_404": "404", + "utl_err": "ERARO", + "utl_oserr": "OS-eraro", + "utl_found": "trovita", + "utl_defer": "postigita", + "utl_yolo": "rapidega", + "utl_done": "finita", + + "ul_flagblk": "la dosieroj estis aldonita al la vico,</b><br>sed alia langeto de retumilo jam alŝutas dosierojn per up2k,<br>do tiu alŝutado devas finiĝi unue", + "ul_btnlk": "la agordado de servilo ne permesas ŝanĝi tiun agordon", + + "udt_up": "Alŝuti", + "udt_srch": "Serĉi", + "udt_drop": "demetu ĉi tien", + + "u_nav_m": '<h6>do, kion vi havas ĉi tie?</h6><code>Enter</code> = Dosierojn (unu al multaj)\n<code>ESK</code> = Unu dosierujon (eble kun subdosierujoj)', + "u_nav_b": '<a href="#" id="modal-ok">Dosierojn</a><a href="#" id="modal-ng">Unu dosierujo</a>', + + "cl_opts": "ŝaltiloj", + "cl_themes": "etoso", + "cl_langs": "lingvo", + "cl_ziptype": "elŝutado de dosieroj", + "cl_uopts": "agordoj de up2k", + "cl_favico": "retpaĝsimbolo", + "cl_bigdir": "grandaj ujoj", + "cl_hsort": "#ordigo", + "cl_keytype": "skemo de fulmoklavoj", + "cl_hiddenc": "kaŝitaj kolumnoj", + "cl_hidec": "kaŝi", + "cl_reset": "restarigi", + "cl_hpick": "alklaki la kapojn de kolumnoj por kasi en la suban tabelon", + "cl_hcancel": "kaŝado de kolumno nuligita", + + "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_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.', + "ct_dir1st": 'ordigi dosierujojn antaŭ dosieroj">📁 unue', + "ct_nsort": 'numera ordigo de dosiernomoj (ekz. <code>2</code> antaŭ <code>11</code>)">№.ord', + "ct_utc": 'montri ĉiuj datoj kaj tempoj per UTC">UTC', + "ct_readme": 'montri enhavon de README.md en listaĵo de dosieroj">📜 readme', + "ct_idxh": 'montri paĝon index.html anstataŭ listaĵo de dosieroj">htm', + "ct_sbars": 'montri rulumskalojn">⟊', + + "cut_umod": "se dosiero jam ekzistas en la servilo, ŝanĝi la tempon de lasta modifo laŭ via dosiero (bezonas permesojn write+delete)\">re📅igi", + + "cut_turbo": "rapidigi alŝutojn KOSTE DE TUTA KONTROLADO:$N$Nuzinda, se vi alŝutis grandegajn dosierojn, devis haltigi la alŝutadon, kaj nun volas daŭrigi ĝin rapidege$N$Nse ĉi tiu agordo estas ŝaltita, anstataŭ kontrolsumado, la servilo nur kontrolas, ĉu la grando de via kaj servila dosieroj estas samaj, kaj ne realŝutas dosierojn kun samaj grandoj$N$Npost ĉio finiĝis, vi devus malŝalti ĉi tiun agordon, do provi "alŝuti" la tiuj samaj dosieroj — la kontrolsumado rekomencos kaj ne realŝutos ion ajn, se la alŝutado vere sukcesis\">rapidega", + + "cut_datechk": "efektas nur se "rapidega" alŝutado estas ŝaltita$N$Nete plibonigas fidindon de kontrolado, per kontrolado de modifo-tempoj aldone al grandoj$N$N<em>teorie</em> estas sufiĉe por detekti nefinigitajn aŭ difektitajn alŝutojn, sed ne estas kompleta alternativo por sen-"rapidega" kontrolado\">dato-kontrolo", + + "cut_u2sz": "grando (en MiBoj) de ĉiu alŝutanta ero; grandaj valoroj estas pli bonaj por longdistancaj konektoj, malgrandajn por malalt-kvalitaj konektoj", + + "cut_flag": "certigi, ke nur unu langeto alŝutas samttempe $N -- aliaj langetoj devas ankaŭ ŝalti ĉi tiun agordon $N -- nur funkcias por langetoj de sama domajno", + + "cut_az": "alŝuti dosierojn en alfabeta ordigo anstataŭ "plej malgrandaj unue"$N$Nalfabeta ordo igas pli simple vidi, ke okazis eraroj en la servilo, sed estas pli malrapida sur tre rapidaj konektoj (ekz. en la loka reto aŭ per fibrooptiko)", + + "cut_nag": "sciigi per operaciumo je fino de alŝutado$N(nur se ĉi tiu langeto de retumilo ne estas aktiva)", + "cut_sfx": "sciigi per sono je fino de alŝutado$N(nur se ĉi tiu langeto de retumilo ne estas aktiva)", + + "cut_mt": "kontrolsumi dosierojn pli rapide per multaj fadenoj$N$Nuzas teknologio Web Worker, bezonas pli da labormemoro (maksimume 512 MiB)$N$Nplirapidigas https je 30%, http je 4.5x\">mf", + + "cut_wasm": "uzi WASM-modulon anstataŭ kontrolsumilaj funkcioj de retumilo; plirapidigas kontrolsumadon sur Chrome-bazitaj retumiloj, sed ankaŭ pli procesor-intensa; malnovaj versioj de Chrome havas difektojn, kiuj misuzas la tutan labormemoron kaj kraŝas la retumilon, se ĉi tiu agordo estas ŝaltita\">wasm", + + "cft_text": "teksto de retpaĝsimbolo (blankigu kaj reŝargu la paĝon por malŝalti)", + "cft_fg": "teksta koloro", + "cft_bg": "fona koloro", + + "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", + + "tt_entree": "montri arbovidan navig-panelon$NFulmoklavo: B", + "tt_detree": "montri paĝnivelan navig-panelon$NFulmoklavo: B", + "tt_visdir": "rulumi al elektita dosierujo", + "tt_ftree": "baskuli dosieruj-arban aŭ teksto-dosieran vidon$NFulmoklavo: V", + "tt_pdock": "fiksi patrajn dosierojn sur supro de panelo", + "tt_dynt": "aŭtomate pligrandigi panelon", + "tt_wrap": "linifaldo", + "tt_hover": "montri kompletajn nomojn sur musumo$N( paneas rulumadon, se la kursoro de muso $N  ne estas en la maldekstra malplenaĵo )", + + "ml_pmode": "je la fino de dosierujo...", + "ml_btns": "komandoj", + "ml_tcode": "transkodi", + "ml_tcode2": "transkodi al", + "ml_tint": "kolorado", + "ml_eq": "ekvalizilo", + "ml_drc": "kompresoro", + + "mt_loop": "ripeti unu kanton\">🔁", + "mt_one": "haltigi post unu kanto\">1️⃣", + "mt_shuf": "ludi ĉiu dosierujo en hazarda ordo\">🔀", + "mt_aplay": "ludi aŭtomate, se ligilo enhavas identigilon de kanto$N$Nmalŝaltado de ĉi tiu agordo ankaŭ malŝaltas ĝisdatigadon de paĝ-adreso, por ke ludado ne rekomenciĝas, se la paĝo estos poste malfermita sen aliaj agordoj\">a▶", + "mt_preload": "komenci ŝargadon de sekva kanto antaŭ la fino de la nuna, por kontinua ludado\">antaŭŝarg.", + "mt_prescan": "eniri la sekvan dosierujon antaŭ la fino de la lasta kanto, $Npor ke la retumilo ne interrompis la ludadon\">nav", + "mt_fullpre": "antaŭŝargi la tutan kanton;$N✅ ŝalti por <b>malaltkvalitaj</b> konektoj,$N❌ eble <b>malŝalti</b> por malrapidaj konektoj\">full", + "mt_fau": "por poŝtelefonoj: komenci sekvan kanton, eĉ se ĝi ne estis tute ŝargita (povas difektigi la montradon de muzikaĵ-etikedoj)\">☕️", + "mt_waves": "bildigo:$Nmontri amplitudon de ludanta kanto en ludadbreto\">~", + "mt_npclip": "montri butonojn por kopiado de ludanta kanto\">/np", + "mt_m3u_c": "montri butonojn por kopiado de elektitaj kantoj kiel m3u8-ludlisto\">📻", + "mt_octl": "integrado kun operaciumo (medio-klavoj kaj montriloj)\">integr.", + "mt_oseek": "movi tra kanto per operaciumaj stiriloj$N$Nnoto: en iuj komputiloj (iPhone),$N ĉi tiu agordo anstataŭigas la butonon de sekva kanto\">movado", + "mt_oscv": "montri album-bildojn en montriloj\">bildo", + "mt_follow": "rulumi la pagon, por ke la ludanta kanto restas videbla\">🎯", + "mt_compact": "kompaktaj ruliloj\">⟎", + "mt_uncache": "malplenigi kaŝmemoron  (uzinda, se via retumilo kaŝmemoris$Ndifektitan kopion de kanto, kaj ne povas ludi ĝin)\">🗑️ kaŝmem.", + "mt_mloop": "ripeti la nunan dosierujon\">🔁 ripeti", + "mt_mnext": "ŝargi la sekvan dosierujon kaj daŭrigi\">📂 sekva", + "mt_mstop": "haltigi ludadon\">⏸ haltigi", + "mt_cflac": "konverti el flac / wav al {0}\">flac", + "mt_caac": "konverti el aac / m4a al {0}\">aac", + "mt_coth": "konverti aliajn (krom mp3) al {0}\">aliaj", + "mt_c2opus": "pli bona elekto por propraj komputiloj kaj Android\">opus", + "mt_c2owa": "opus-weba, por iOS 17.5 kaj pli novaj\">owa", + "mt_c2caf": "opus-caf, por iOS 11-17\">caf", + "mt_c2mp3": "por tre malnovaj iloj\">mp3", + "mt_c2flac": "plej bona sonkvalito, sed grandegaj elŝutoj\">flac", + "mt_c2wav": "sendensigita ludado (pli bonaj elŝutoj)\">wav", + "mt_c2ok": "bona elekto", + "mt_c2nd": "ĉi tiu formato ne estas rekomendita por via aparato, sed ĝi ankaŭ funkcios", + "mt_c2ng": "via aparato ŝajne ne subtenas ĉi tiun formaton, sed ni provu uzi ĝin malgraŭe", + "mt_xowa": "estas difektoj en iOS, kiuj preventas fonan ludadon per ĉi tiu formato; bonvolu uzi caf aŭ mp3 anstataŭe", + "mt_tint": "travideblo (0-100) de ludadbreto$Nvi povas ŝanĝi ĝin, se ĝi aspektas tro distre dum ŝargado", + "mt_eq": "ŝaltas ekvalizilon kaj stirilon de plifortigado;$N$Nboost (plifortigado) <code>0</code> = senmodifa 100%a laŭteco$N$Nwidth (larĝo) <code>1  </code> = senmodifa dukanala sono$Nwidth (larĝo) <code>0.5</code> = 50% miksado inter maldekstra kaj dekstra kanaloj$Nwidth (larĝo) <code>0  </code> = unukanala sono$N$Nboost <code>-0.8</code> & width <code>10</code> = senvokigo :^)$N$Nŝaltita ekvalizilo ankaŭ forigas paŭzojn inter muzikaĵoj en senpaŭzaj albumoj, agordi ĉion kiel 0 (sed 'width' kiel 1), se vi volas nur tion", + "mt_drc": "ŝaltas kompresoron de dinamiko (glatigas laŭtecon de muzikaĵoj); ankaŭ ŝaltas ekvalizilon, do agordu ĉion (sed 'width') kiel 0, se vi ne volas ĝin; $N$Nplimalgrandigas laŭtecon de aŭdio super sojlo-valoro ('tresh') da dB; ĉiu proporcio-valoro ('ratio') da dB post 'tresh' 1 dB estos eligita, do implicitaj valoroj (tresh = -24, ratio = 12) faras, ke laŭteco neniam pli grandas ol -22 dB; tiel estas sendanĝera agordi 'boost'on kiel 0.8 aŭ eĉ 1.8 dum ATK = 0 kaj grandega RLS, kiel 90 (funkcias nur en Firefox, RLS estas maksimume 1 en aliaj retumiloj)$N$N(rigardu vikipedion, ĝi klariĝas pli bone)", + + "mb_play": "ludi", + "mm_hashplay": "ludi ĉi tiun aŭdiodosieron?", + "mm_m3u": "premu <code>Enter/OK</code> por ludado \npremu <code>ESK/Rezigni</code> por redaktado", + "mp_breq": "bezonas Firefox 82+, Chrome 73+ aŭ iOS 15+", + "mm_bload": "ŝargado...", + "mm_bconv": "konvertado al formato {0}, bonvolu atendi...", + "mm_opusen": "via retumilo ne povas ludi dosierojn de formatoj aac / m4a;\ntranskodado al opus estas ŝaltigita", + "mm_playerr": "ludado malsukcesis: ", + "mm_eabrt": "Klopodo de ludado estis nuligita", + "mm_enet": "Via retkonekto estas nestabila", + "mm_edec": "Ĉi tiu dosiero estas ŝajne difektita??", + "mm_esupp": "Via retumilo ne komprenas ĉi tiun aŭdio-formaton", + "mm_eunk": "Nekonata eraro", + "mm_e404": "Ne povas ludi aŭdiaĵon; eraro 404: Dosiero ne trovita.", + "mm_e403": "Ne povas ludi aŭdiaĵon; eraro 403: Atingo malpermesita.\n\nKlopodu reŝargi paĝon per klavo F5, eble via seanco senvalidiĝis", + "mm_e500": "Ne povas ludi aŭdiaĵon; eraro 500: Rigardu la protokolojn de servilo.", + "mm_e5xx": "Ne povas ludi aŭdiaĵon; servila eraro ", + "mm_nof": "neniuj aŭdio-dosieroj trovitaj proksime", + "mm_prescan": "Serĉado por sekva aŭdiaĵo...", + "mm_scank": "Sekva muzikaĵo trovita:", + "mm_uncache": "kaŝmemoro malplenigita; ĉiuj muzikaĵoj estos reelŝutitaj dum sekva ludado", + "mm_hnf": "ĉi tiu muzikaĵo ne ekzistas plu", + + "im_hnf": "ĉi tiu bildo ne ekzistas plu", + + "f_empty": 'ĉi tiu dosierujo estas malplena', + "f_chide": 'ĉi tiu ago kaŝos kolumnon «{0}»\n\nvi povas malkaŝi kolumnojn en agordoj', + "f_bigtxt": "ĉi tiu dosiero estas {0}-MiB-granda -- ĉu vere malfermi kiel teksto?", + "f_bigtxt2": "ĉu malfermi nur la finon de dosiero? ĉi tiu reĝimo ankaŭ ŝaltos tujan ĝisdatigon, novaj linioj estos tuj montritaj", + "fbd_more": '<div id="blazy"><code>{0}</code> de <code>{1}</code> dosieroj montrataj; <a href="#" id="bd_more">montri {2}</a> aŭ <a href="#" id="bd_all">montri ĉiujn</a></div>', + "fbd_all": '<div id="blazy"><code>{0}</code> de <code>{1}</code> dosieroj montrataj; <a href="#" id="bd_all">montri ĉiujn</a></div>', + "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_partial": "Por sendifekta elŝuto de nune-alŝutata dosiero, elektu dosieron kun sama nomo, sed sen etendaĵo <code>.PARTIAL</code>. Bonvolu uzi la butonon \"Rezigni\" aŭ klavon ESK por fari tion.\n\nSe vi uzas OK / Enter, la provizora dosiero <code>.PARTIAL</code> estos elŝutita, kiu tre probable enhavas nekompletajn datumojn.", + + "ft_paste": "alglui {0} erojn$NFulmoklavo: stir-V", + "fr_eperm": 'ne povas alinomi:\nvi ne havas permeson “move” en ĉi tiu dosierujo', + "fd_eperm": 'ne povas forigi:\nvi ne havas permeson “delete” en ĉi tiu dosierujo', + "fc_eperm": 'ne povas eltondi:\nvi ne havas permeson “move” en ĉi tiu dosierujo', + "fp_eperm": 'ne povas alglui:\nvi ne havas permeson “write” en ĉi tiu dosierujo', + "fr_emore": "elekti almenaŭ unu aĵon por alinomi", + "fd_emore": "elekti almenaŭ unu aĵon por forigi", + "fc_emore": "elekti almenaŭ unu aĵon por eltondi", + "fcp_emore": "elekti almenaŭ unu aĵon por kopii al tondujo", + + "fs_sc": "kunhavigi la aktualan dosierujon", + "fs_ss": "kunhavigi la elektitajn dosierojn", + "fs_just1d": "vi ne povas elekti pli ol unu dosierujon\naŭ miksi dosierojn kaj dosierujojn en elektaĵo", + "fs_abrt": "❌ ĉesigi", + "fs_rand": "🎲 haz. nomo", + "fs_go": "✅ krei komunaĵon", + "fs_name": "nomo", + "fs_src": "indiko", + "fs_pwd": "pasvorto", + "fs_exp": "tempolimo", + "fs_tmin": "min", + "fs_thrs": "horoj", + "fs_tdays": "tagoj", + "fs_never": "eterna", + "fs_pname": "nomo de ligilo; estos hazarde kreita, se malplena", + "fs_tsrc": "dosier(uj)o por kunhavigi", + "fs_ppwd": "pasvorto (nedeviga)", + "fs_w8": "kreado de komunaĵo...", + "fs_ok": "premu <code>Enter/OK</code> por kopii al tondujo\npremu <code>ESK/Rezigni</code> por fermi", + + "frt_dec": "povas ripari difektitajn dosiernomojn\">url-malkodo", + "frt_rst": "reagordi modifitajn dosiernomojn al originalaj\">↺ malfari", + "frt_abrt": "ĉesigi operacion kaj fermi ĉi tiun fenestron\">❌ rezigni", + "frb_apply": "ALINOMI", + "fr_adv": "amasa / metadatuma / ŝablona alinomado\">altnivela", + "fr_case": "uskleciva regula esprimo\">uskleco", + "fr_win": "Windows-taŭgaj nomoj; signoj <code><>:"\\|?*</code> estos anstataŭigitaj per japanaj duobla-larĝaj signoj\">win", + "fr_slash": "anstataŭigi <code>/</code>n per signo, kiu ne devigas kreadon de novaj dosierujoj\">sen /", + "fr_re": "ŝablono de regula esprimo, kiu estos aplikita al originalaj dosiernomoj; kaptogrupoj povas esti referencita en formatkampo, ekz. <code>(1)</code>, <code>(2)</code> k.t.p.", + "fr_fmt": "inspirita de foobar2000:$N<code>(title)</code> anstataŭigitas per nomo de muzikaĵo,$N<code>[(artist) - ](title)</code> preterpasas [ĉi tiun] parton, se artisto ne estas specifita$N<code>$lpad((tn),2,0)</code> aldonas nulojn en trakonombro ĝis 2 ciferoj", + "fr_pdel": "forigi", + "fr_pnew": "konservi kiel", + "fr_pname": "nomu vian novan ŝablonon", + "fr_aborted": "ĉesigita", + "fr_lold": "malnova nomo", + "fr_lnew": "nova nomo", + "fr_tags": "etikedoj por elektitaj dosieroj (ne redakteblas, nur por referenco):", + "fr_busy": "alinomado de {0} aĵoj...\n\n{1}", + "fr_efail": "alinomado malsukcesis:\n", + "fr_nchg": "{0} da novaj nomoj estis modifita pro reguloj <code>win</code> kaj/aŭ <code>sen /</code>\n\nĈu daŭrigi kun modifitaj nomoj?", + + "fd_ok": "forigado sukcesis", + "fd_err": "forigado malsukcesis:\n", + "fd_none": "nenio estis forigita; eble servila eraro malpermesis ĝin (xbd)?", + "fd_busy": "forigado de {0} aĵoj...\n\n{1}", + "fd_warn1": "ĉu FORIGI ĉi tiujn {0} aĵojn?", + "fd_warn2": "<b>Averto!</b> Ĉi tiu ago ne malfareblas. Ĉu forigi?", + + "fc_ok": "{0} aĵoj eltonditaj", + "fc_warn": '{0} aĵoj eltonditaj\n\nnur <b>ĉi tiu</b> langeto de retumilo povas alglui ilin\n(pro la grando de elektaĵo)', + + "fcc_ok": "{0} aĵoj kopiitaj al tondujo", + "fcc_warn": '{0} aĵoj kopiitaj al tondujo\n\nnur <b>ĉi tiu</b> langeto de retumilo povas alglui ilin\n(pro la grando de elektaĵo)', + + "fp_apply": "uzi ĉi tiujn nomojn", + "fp_ecut": "unue eltondi aŭ kopii dosier(uj)ojn, do alglui ĝin poste\n\nnoto: tondujo ankaŭ funkcias inter aliaj langetoj de retumilo", + "fp_ename": "{0} aĵoj ne povas esti movitaj, ĉar iliaj nomoj estas jam uzataj. Alinomi ilin sube aŭ lasi la nomokampojn malplenaj por preterpasi:", + "fcp_ename": "{0} aĵoj ne povas esti kopiitaj, ĉar iliaj nomoj estas jam uzataj. Alinomi ilin sube aŭ lasi la nomokampojn malplenaj por preterpasi:", + "fp_emore": "ankoraŭ restas koincidoj de dosiernomoj, kiuj bezonas solvon", + "fp_ok": "movado sukcesis", + "fcp_ok": "kopiado sukcesis", + "fp_busy": "movado de {0} aĵoj...\n\n{1}", + "fcp_busy": "kopiado {0} aĵoj...\n\n{1}", + "fp_abrt": "ĉesigado...", + "fp_err": "movado malsukcesis:\n", + "fcp_err": "kopiado malsukcesis:\n", + "fp_confirm": "ĉu movi tiujn {0} aĵojn ĉi tien?", + "fcp_confirm": "ĉu kopii tiujn {0} aĵojn ĉi tien?", + "fp_etab": 'eraro dum legado de tondujo el alia langeto de retumilo', + "fp_name": "alŝutado de dosiero el via aparato. Nomi ĝin:", + "fp_both_m": '<h6>elektu, kion alglui</h6><code>Enter</code> = Movi {0} dosierojn al «{1}»\n<code>ESK</code> = Alŝuti {2} dosierojn el via aparato', + "fcp_both_m": '<h6>elektu, kion alglui</h6><code>Enter</code> = Kopii {0} dosierojn al «{1}»\n<code>ESK</code> = Alŝuti {2} dosierojn el via aparato', + "fp_both_b": '<a href="#" id="modal-ok">Movi</a><a href="#" id="modal-ng">Alŝuti</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopii</a><a href="#" id="modal-ng">Alŝuti</a>', + + "mk_noname": "tajpu nomon en tekstokampo maldekstre antaŭ vi faras ĉi tion :p", + + "tv_load": "Ŝargado de teksto-dokumento:\n\n{0}\n\n{1}% ({2} da {3} MiB ŝargita)", + "tv_xe1": "ne povas ŝargi teksto-dosieron:\n\neraro ", + "tv_xe2": "404, dosiero ne trovita", + "tv_lst": "listo de teksto-dosieroj en", + "tvt_close": "reveni al vido de dosierujo$NFulmoklavo: M (aŭ Esk)\">❌ fermi", + "tvt_dl": "elŝuti ĉi tiun dosieron$NFulmoklavo: Y\">💾 elŝuti", + "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_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\">↵", + "tvt_atail": "alpingli rulumadon al malsupro de paĝo\">⚓", + "tvt_ctail": "malkodi ANSI-kodojn de terminal-koloroj\">🌈", + "tvt_ntail": "limo de rulumado (kiom da bajtoj de teksto konservi en memoro)", + + "m3u_add1": "muzikaĵo aldonita al m3u-ludlisto", + "m3u_addn": "{0} muzikaĵoj aldonitaj al m3u-ludlisto", + "m3u_clip": "m3u-ludlisto kopiita al tondujo\n\nvi devus krei tekst-dosieron kun etendaĵo <code>.m3u</code> kaj alglui la tekston en ĝi por krei uzeblan ludliston", + + "gt_vau": "ne montri videojn, nur ludi muzikaĵojn\">🎧", + "gt_msel": "ŝalti elektado-reĝimon; stir-klaki dosieron por ne-elekta ago$N$N<em>kiam ŝaltita: duoblaklako por malfermi dosier(uj)on</em>$N$NFulmoklavo: S\">elektado", + "gt_crop": "stuci bildetojn\">stuci", + "gt_3x": "alt-kvalitaj bildetoj\">3x", + "gt_zoom": "grando", + "gt_chop": "nomlongo", + "gt_sort": "ordigi per", + "gt_name": "nomo", + "gt_sz": "grando", + "gt_ts": "dato", + "gt_ext": "tipo", + "gt_c1": "pli mallongaj dosiernomoj", + "gt_c2": "pli longaj dosiernomoj", + + "sm_w8": "serĉado...", + "sm_prev": "serĉrezultoj sube venas al la lasta informpeto:\n ", + "sl_close": "fermi serĉrezultojn", + "sl_hits": "montrado de {0} kongruaĵoj", + "sl_moar": "ŝargi pli", + + "s_sz": "grando", + "s_dt": "dato", + "s_rd": "vojo", + "s_fn": "nomo", + "s_ta": "etikedoj", + "s_ua": "alŝut📅", + "s_ad": "aliaj", + "s_s1": "minimuma MiB", + "s_s2": "maksimuma MiB", + "s_d1": "min. iso8601", + "s_d2": "maks. iso8601", + "s_u1": "alŝutita post", + "s_u2": "kaj/aŭ antaŭ", + "s_r1": "vojo enhavas   (apartigi per spacoj)", + "s_f1": "nomo enhavas   (negacii per minuso)", + "s_t1": "etikedoj enhavas   (^=komenco, fino=$)", + "s_a1": "ecoj de metadatumoj", + + "md_eshow": "ne povas montri ", + "md_off": "[📜<em>readme</em>] malŝaltita en [⚙️] -- dokumento kaŝita", + + "badreply": "Eraro dum legado de respondo al servilo", + + "xhr403": "403: Atingo malpermesita\n\nklopodu reŝargi paĝon per klavo F5, eble via seanco senvalidiĝis", + "xhr0": "nekonata (eble konekto al servilo estis perdita, aŭ servilo ne funkcias)", + "cf_ok": "pardonon -- DD" + wah + "oS-protekto aktiviĝis\n\nĉio devus esti bona 30 sekundoj post\n\nse nenio okazas, reŝargi per klavo F5", + "tl_xe1": "ne povas listigi subdosierujojn:\n\neraro ", + "tl_xe2": "404: Dosierujo ne trovita", + "fl_xe1": "ne povas listigi dosierojn en dosierujo:\n\neraro ", + "fl_xe2": "404: Dosierujo ne trovita", + "fd_xe1": "ne povas krei subdosierujon:\n\neraro ", + "fd_xe2": "404: Patra dosierujo ne trovita", + "fsm_xe1": "ne povas sendi mesaĝon:\n\neraro ", + "fsm_xe2": "404: Patra dosierujo ne trovita", + "fu_xe1": "ne povas ŝargi malsend-liston el servilo:\n\neraro ", + "fu_xe2": "404: Dosiero ne trovita??", + + "fz_tar": "nedensigita dosiero GNU TAR (linux / mac)", + "fz_pax": "nedensigita PAX-formata dosiero TAR (malpli rapide)", + "fz_targz": "GNU TAR, densigita per 3a nivelo de GZip$N$Nkutime tre malrapida, do$Nuzu nedensigitan TARon anstataŭe", + "fz_tarxz": "GNU TAR, densigita per 1a nivelo de XZ$N$Nkutime tre malrapida, do$Nuzu nedensigitan TARon anstataŭe", + "fz_zip8": "Zip kun dosiernomoj laŭ UTF-8 (eble difektiĝis je Windows 7 kaj pli malnovaj)", + "fz_zipd": "Zip kun dosiernomoj laŭ CP437, por tre malnovaj programoj (esperantaj diakritaĵoj ne funkcios)", + "fz_zipc": "Zip, cp437, CRC-kontrolsumoj prekalkulitaj,$Npor MS-DOS PKZIP v2.04g (oktobro 1993)$N(bezonas pli da tempo antaŭ komenco de elŝuto)", + + "un_m1": "vi povas forigi viajn lastajn alŝutojn (aŭ ĉesigi nefinigitajn) sube", + "un_upd": "reŝargi", + "un_m4": "aŭ kunhavigi la dosierojn sube:", + "un_ulist": "montri", + "un_ucopy": "kopii", + "un_flt": "nedeviga filtrilo:  URL devas enhavi", + "un_fclr": "vakigi filtrilon", + "un_derr": 'malalŝutado malsukcesis:\n', + "un_f5": 'io difektiĝis, bonvolu reŝargi aŭ uzi klavon F5', + "un_uf5": "pardonu, sed vi devas reŝargi la paĝon (F5 aŭ Stir+R) antaŭ ĉesigi ĉi tiun alŝuton", + "un_nou": '<b>averto:</b> servilo estas tro okupara por montri nefinigitajn alŝutojn; klaku la butonon "reŝargi" post kelkaj sekundoj', + "un_noc": '<b>averto:</b> malalŝutado de tute alŝutitaj dosieroj estas malpermesita laŭ agordoj de servilo', + "un_max": "unuaj 2000 dosieroj montritaj (uzi filtrilon por vidi aliajn)", + "un_avail": "{0} lastaj alŝutoj forigeblas<br />{1} nefinigitajn ĉesigeblas", + "un_m2": "ordigita per alŝuto-tempo, plej lastaj unue:", + "un_no1": "neniuj sufiĉe lastaj alŝutoj", + "un_no2": "neniuj sufiĉe lastaj alŝutoj, kongruaj laŭ filtrilo", + "un_next": "forigi la sekvajn {0} dosierojn sube", + "un_abrt": "ĉesigi", + "un_del": "forigi", + "un_m3": "ŝargado de viaj lastaj alŝutoj...", + "un_busy": "forigado de {0} dosieroj...", + "un_clip": "{0} ligiloj kopiitaj al tondujo", + + "u_https1": "vi devas", + "u_https2": "ŝalti HTTPS-protokolon", + "u_https3": "por pli bona rendimento", + "u_ancient": 'via retumilo estas vere antikva -- eble vi devus <a href="#" onclick="goto(\'bup\')">uzi alŝutilon bup anstataŭe</a>', + "u_nowork": "Firefox 53+ aŭ Chrome 57+ aŭ iOS 11+ necesas", + "tail_2old": "Firefox 105+ aŭ Chrome 71+ aŭ iOS 14.5+ necesas", + "u_nodrop": 'via retumilo estas tro malnova por ŝova-kaj-demeta alŝutado', + "u_notdir": "tio ne estas dosierujo!\n\nvia retumilo estas tro malnova,\nbonvolu ŝovu kaj demetu anstataŭe", + "u_uri": "por ŝovi-kaj-demeti bildon de aliaj fenestroj de retumiloj,\nbonvolu demeti ĝin sur la grandan alŝut-butonon", + "u_enpot": 'uzi <a href="#">simplan fasadon</a> (povas plirapidigi alŝutojn)', + "u_depot": 'uzi <a href="#">elegantan fasadon</a> (povas plimalrapidigi alŝutojn)', + "u_gotpot": 'ŝaltado de simpla fasado por pli rapidaj alŝutoj,\n\nvi povas malŝalti ĝin, se ĝi ne plaĉas al vi!', + "u_pott": "<p>dosieroj:   <b>{0}</b> finitaj,   <b>{1}</b> eraroj,   <b>{2}</b> alŝutataj,   <b>{3}</b> envicigitaj</p>", + "u_ever": "ĉi tiu estas fundamenta alŝutilo; up2k postulas almenaŭ retumilojn<br>Chrome 21 // Firefox 13 // Edge 12 // Opera 12 // Safari 5.1", + "u_su2k": 'ĉi tiu estas fundamenta alŝutilo; <a href="#" id="u2yea">up2k</a> estas pli bona', + "u_uput": 'optimumigi por rapideco (ne kalkuli kontrolsumojn)', + "u_ewrite": 'vi ne havas permeson skribi en ĉi tiun dosierujon', + "u_eread": 'vi ne havas permeson legi ĉi tiun dosierujon', + "u_enoi": 'serĉado de dosieroj estas malŝaltita en servilaj agordoj', + "u_enoow": "anstataŭigo ne funkcios ĉi tie; forigo-permeso necesas", + "u_badf": 'Ĉi tiuj {0} de {1} dosieroj estis preterpasitaj, eble pro permesoj de dosiersistemo:\n\n', + "u_blankf": 'Ĉi tiuj {0} de {1} dosieroj estas blankaj; ĉu alŝuti malgraŭ tio?\n\n', + "u_applef": 'Ĉi tiuj {0} de {1} dosieroj estas eble nedezirataj;\nPremu <code>OK/Enter</code> por PRETERPASI ilin,\nPremu <code>Rezigni/ESK</code> por ignori ĉi tiun mesaĝon kaj ALŜUTI ilin:\n\n', + "u_just1": '\nEble ĝi funkcios pli bone, se vi elektas nur unu dosieron', + "u_ff_many": "se vi uzas operaciumojn <b>Linux / MacOS / Android,</b> ĉi tiu kvanto de dosieroj <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>povas</em> kraŝi retumilon Firefox!</a>\nse ĉi tio okazos, klopodu denove (aŭ uzu Chrome-on).", + "u_up_life": "Ĉi tiu alŝuto estos forigita de servilo\n{0} post kompletado", + "u_asku": 'alŝuti ĉi tiujn {0} dosierojn al <code>{1}</code>', + "u_unpt": "vi povas malalŝuti / forigi ĉi tiun alŝuton per 🧯 supre-maldesktre", + "u_bigtab": '{0} dosieroj montrotaj\n\nĉi tiu povas kraŝi vian retumilon, ĉu daŭrigi?', + "u_scan": 'Skanado de dosieroj...', + "u_dirstuck": 'skanilo haltis dum atingado de sekvaj {0} dosieroj; ili estos preterpasitaj:', + "u_etadone": 'Finita ({0}, {1} dosieroj)', + "u_etaprep": '(preparado por alŝutado)', + "u_hashdone": 'kontrolsumita', + "u_hashing": 'k-sumado', + "u_hs": 'kvitanco...', + "u_started": "la dosieroj estas alŝutataj; rigardu [🚀]", + "u_dupdefer": "duplikatoj; estos traktita post ĉiuj aliaj dosieroj", + "u_actx": "alklaku ĉi tiun tekston por eviti malrapidigon<br />dum uzado de aliaj fenestroj/langetoj", + "u_fixed": "Bone!  Riparita 👍", + "u_cuerr": "alŝutado de ero {0} de {1} malsukcesis;\neble malgravas, alŝutado daŭrigas\n\ndosiero: {2}", + "u_cuerr2": "servilo rifuzis alŝutadon (ero {0} de {1});\nprovos denove poste\n\ndosiero: {2}\n\neraro ", + "u_ehstmp": "reprovos poste; rigardu sube-dekstre", + "u_ehsfin": "servilo rifuzis peton por finigi alŝutadon; reprovado...", + "u_ehssrch": "servilo rifuzis peton por ŝerco; reprovado...", + "u_ehsinit": "servilo rifuzis peton por komenco de alŝutado; reprovado...", + "u_eneths": "reta eraro dum alŝutada kvitanco; reprovado...", + "u_enethd": "reta eraro dum kontrolo de ekzistado de cela dosiero; reprovado...", + "u_cbusy": "atendado, por ke la servilo estas atingebla post reta eraro...", + "u_ehsdf": "servilo ne havas sufiĉe da diskospaco!\n\nla alŝutado reprovos daŭre, okaze de iu liberigas\nsufiĉe da diskospaco", + "u_emtleak1": "ŝajnas, ke via retumilo likas memoron; \nbonvolu", + "u_emtleak2": ' <a href="{0}">uzi protokolon HTTPS (rekomendita)</a> aŭ ', + "u_emtleak3": ' ', + "u_emtleakc": 'provu fari tiel:\n<ul><li>reŝargu la paĝon per klavo <code>F5</code></li><li>, do malŝalti la butonon  <code>mt</code>  en la  <code>⚙️ agordoj</code></li><li>kaj reprovu alŝuton</li></ul>Alŝuto estos pli malrapida, sed nenio fareblas.\nPardonon por la ĝenaĵo!\n\nPS: chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">korektis ĉi tion</a>', + "u_emtleakf": 'provu fari tiel:\n<ul><li>reŝargu la paĝon per klavo <code>F5</code></li><li>, do malŝalti la butonon  <code>mt</code>  en la  <code>⚙️ agordoj</code></li><li>kaj reprovu alŝuton</li></ul>\nPS: firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">espereble korektos ĉi tion</a> baldaŭ', + "u_s404": "ne trovita ĉe la servilo", + "u_expl": "klarigi", + "u_maxconn": "plimulto da retumiloj ne permesas uzi pli ol 6, sed Firefox havas agordon <code>connections-per-server</code> en <code>about:config</code>, per kiu la limo ŝanĝeblas", + "u_tu": '<p class="warn">AVERTO: rapidega reĝimo ŝaltita, <span> kliento povas ne detekti kaj daŭrigi nefinigitajn alŝutojn; rigardu la ŝpruchelpilon de rapidega-butono</span></p>', + "u_ts": '<p class="warn">AVERTO: rapidega reĝimo ŝaltita, <span> serĉrezultoj povas esti eraraj; rigardu la ŝpruchelpilon de rapidega-butono</span></p>', + "u_turbo_c": "rapidega reĝimo estas malpermesita laŭ servilaj agordoj", + "u_turbo_g": "rapidega reĝimo estas malpermesita, ĉar vi\nne rajtas listigi dosierujojn en ĉi tiu datumportilo", + "u_life_cfg": 'aŭtoforigado post <input id="lifem" p="60" /> minutoj (aŭ <input id="lifeh" p="3600" /> horoj)', + "u_life_est": 'alŝuto estos forigita je <span id="lifew" tt="local time">---</span>', + "u_life_max": 'ĉi tiu dosierujo postulas \naŭtoforigadon de dosieroj post {0}', + "u_unp_ok": 'malalŝuto permesitas por {0}', + "u_unp_ng": 'malalŝuto NE estos permesita', + "ue_ro": 'via atingo de ĉi tiu dosierujo estas nur-lega\n\n', + "ue_nl": 'vi ne estas ensalutita', + "ue_la": 'vi estas ensalutita kiel "{0}"', + "ue_sr": 'vi estas en serĉo-reĝimo nun\n\nŝanĝi al alŝuto-reĝimo per lupeo-simbolo 🔎 (apude de la grandega SERĈO-butono), kaj provu alŝuti denove\n\npardonon', + "ue_ta": 'provu alŝuti denove, ĝi devus funkcii nun', + "ue_ab": "ĉi tiu dosiero estas jam alŝutata en alian dosierujon, tiu alŝutado devas esti finigita antaŭ alŝutado de la sama dosiero en alian lokon.\n\nVi povas ĉesigi la nunan alŝutadon per butonon 🧯 supre-maldekstre", + "ur_1uo": "OK: Dosiero sukcese alŝutita", + "ur_auo": "OK: Ĉiuj {0} dosieroj sukcese alŝutitaj", + "ur_1so": "OK: Dosiero trovita ĉe la servilo", + "ur_aso": "OK: Ĉiuj {0} dosieroj trovitaj ĉe la serviloj", + "ur_1un": "Alŝutado malsukcesis, pardonon", + "ur_aun": "Ĉiuj {0} alŝutadoj malsukcesis, pardonon", + "ur_1sn": "Dosiero estis NE trovita ĉe la servilo", + "ur_asn": "La {0} dosieroj estis NE trovitaj ĉe la servilo", + "ur_um": "Finita;\n{0} alŝutoj sukcesis,\n{1} alŝutoj malsukcesis, pardonon", + "ur_sm": "Finita;\n{0} dosieroj trovitaj ĉe la servilo,\n{1} dosieroj NE trovitaj ĉe la servilo", + + "lang_set": "ĉu reŝargi paĝon por efektivigi lingvo-ŝanĝon?", + }, "fin": { "tt": "Suomi", @@ -2543,7 +3827,7 @@ var Ls = { ["ESC", "sulje asioita"], "file-manager", - ["G", "vaihda lista/ruudukkonäkymään"], + ["G", "vaihda lista/kuvanäkymään"], ["T", "vaihda pienoiskuviin/kuvakkeisiin"], ["⇧ A/D", "pienoiskuvien koko"], ["ctrl-K", "poista valitut"], @@ -2558,13 +3842,13 @@ var Ls = { ["↑/↓", "siirrä valintaosoitinta"], ["ctrl ↑/↓", "siirrä osoitinta ja näkymää"], ["⇧ ↑/↓", "valitse edellinen/seuraava tiedosto"], - ["ctrl-A", "valitse kaikki tiedostot / kansiot"], + ["ctrl-A", "valitse kaikki tiedostot / hakemistot"], ], [ "navigation", ["B", "näytä linkkipolku"], ["I/K", "siirry edelliseen/seuraavaan hakemistoon"], ["M", "siirry ylähakemistoon/supista nykyinen hakemisto"], - ["V", "näytä kansiot/tekstitiedostot navigointipaneelissa"], + ["V", "näytä hakemistot/tekstitiedostot navigointipaneelissa"], ["A/D", "navigointipaneelin koko"], ], [ "audio-player", @@ -2617,16 +3901,17 @@ var Ls = { "ht_d2": "päivää", "ht_and": " ja ", - "goh": "ohjauspaneeli", + "goh": "hallintapaneeli", "gop": 'viereinen hakemisto">edell', "gou": 'ylempi hakemisto">ylös', "gon": 'seuraava hakemisto">seur', "logout": "Kirjaudu ulos ", + "login": "Kirjaudu sisään", //m "access": " -oikeudet", "ot_close": "sulje alavalikko", "ot_search": "etsi tiedostoja ominaisuuksien, tiedostopolun tai -nimen, musiikkitägien tai näiden yhdistelmän perusteella$N$N<code>foo bar</code> = täytyy sisältää sekä «foo» että «bar»,$N<code>foo -bar</code> = täytyy sisältää «foo» mutta ei «bar»,$N<code>^yana .opus$</code> = alkaa «yana» ja on «opus»-tiedosto$N<code>"try unite"</code> = sisältää täsmälleen «try unite»$N$Npäivämäärän muoto on iso-8601, kuten$N<code>2009-12-31</code> tai <code>2020-09-12 23:30:00</code>", "ot_unpost": "unpost: poista viimeaikaiset tai keskeytä keskeneräiset lataukset", - "ot_bup": "bup: tiedostojen 'perus'lähetysohjelma, tukee jopa netscape 4.0:aa", + "ot_bup": "bup: tiedostojen 'perus'lähetysohjelma, tukee jopa netscape 4.0", "ot_mkdir": "mkdir: luo uusi hakemisto", "ot_md": "new-md: luo uusi markdown-dokumentti", "ot_msg": "msg: lähetä viesti palvelinlokiin", @@ -2640,7 +3925,7 @@ var Ls = { "ab_mkdoc": "luo markdown-tiedosto", "ab_msg": "lähetä viesti palvelinlokiin", - "ay_path": "siirry kansioihin", + "ay_path": "siirry hakemistoihin", "ay_files": "siirry tiedostoihin", "wt_ren": "uudelleennimeä valitut kohteet$NPikanäppäin: F2", @@ -2650,29 +3935,29 @@ var Ls = { "wt_pst": "liitä aiemmin leikatut / kopioidut valinnat$NPikanäppäin: ctrl-V", "wt_selall": "valitse kaikki tiedostot$NPikanäppäin: ctrl-A (kun tiedosto on kohdistettu)", "wt_selinv": "valitse vastakkaiset tiedostot", - "wt_zip1": "lataa tämä kansio pakattuna", + "wt_zip1": "lataa tämä hakemisto pakattuna", "wt_selzip": "lataa valitut kohteet pakattuna", "wt_seldl": "lataa valitut kohteet paketoimatta$NPikanäppäin: Y", "wt_npirc": "kopioi kappaletiedot IRC-muotoilulla", "wt_nptxt": "kopioi kappaletiedot ilman muotoilua", "wt_m3ua": "lisää m3u-soittolistaan (klikkaa <code>📻kopioi</code> myöhemmin)", "wt_m3uc": "kopioi m3u-soittolista leikepöydälle", - "wt_grid": "vaihda ruudukko- ja listanäkymän välillä$NPikanäppäin: G", + "wt_grid": "vaihda kuva- ja listanäkymän välillä$NPikanäppäin: G", "wt_prev": "edellinen kappale$NPikanäppäin: J", "wt_play": "toista / pysäytä$NPikanäppäin: P", "wt_next": "seuraava kappale$NPikanäppäin: L", - "ul_par": "rinnakkaisten latausten lkm:", + "ul_par": "rinnakkaislatausten lkm:", "ut_rand": "satunnaisgeneroidut tiedostonimet", "ut_u2ts": "kopioi viimeksi muokattu aikaleima$Ntiedostojärjestelmästäsi palvelimelle\">📅", "ut_ow": "korvaa olemassa olevat tiedostot palvelimella?$N🛡️: ei koskaan (luo sen sijaan uuden tiedostonimen)$N🕒: korvaa jos palvelintiedosto on vanhempi kuin omasi$N♻️: korvaa aina jos tiedostot ovat erilaisia", - "ut_mt": "jatka muiden tiedostojen tiivisteiden laskemista latauksen aikana$N$kannattanee poistaa käytöstä, mikäli prosessori tai kovalevy on vanhempaa mallia", + "ut_mt": "jatka muiden tiedostojen tiivisteiden laskemista latauksen aikana$N$Nkannattanee poistaa käytöstä, mikäli prosessori tai kovalevy on vanhempaa mallia", "ut_ask": 'kysy vahvistusta ennen latauksen aloittamista">💭', - "ut_pot": "paranna latausnopeutta hitailla laitteilla$Ntekemällä käyttöliittymäästä vähemmän monimutkaisen", + "ut_pot": "paranna latausnopeutta hitailla laitteilla$Nvähentämällä käyttöliittymän monimutkaisuutta", "ut_srch": "lataamisen sijaan tarkista, ovatko tiedostot jo $N olemassa palvelimella (käy läpi kaikki hakemistot, joihin sinulla on read-oikeudet)", - "ut_par": "keskeytä lataukset asettamalla se 0:aan$N$Nnosta, jos yhteytesi on hidas tai viive on suuri$N$Npidä se 1:ssä lähiverkossa tai jos palvelimen kovalevy on pullonkaula", - "ul_btn": "vedä tiedostoja / kansioita<br>tähän (tai napsauta tätä)", - "ul_btnu": "L A T A A", + "ut_par": "keskeytä lataukset asettamalla se nollaan$N$Nnosta, jos yhteytesi on hidas tai viive on suuri$N$Npidä se 1:ssä lähiverkossa tai jos palvelimen kovalevy on pullonkaula", + "ul_btn": "vedä tiedostoja / hakemistoja tähän<br>(tai klikkaa minua)", + "ul_btnu": "L Ä H E T Ä", "ul_btns": "E T S I", "ul_hash": "tiiviste", @@ -2712,31 +3997,31 @@ var Ls = { "udt_srch": "Etsi", "udt_drop": "pudota se tähän", - "u_nav_m": '<h6>selvä, mitäs sulla on?</h6><code>Enter</code> = Tiedostoja (yksi tai useampi)\n<code>ESC</code> = Yksi kansio (mukaan lukien alikansiot)', - "u_nav_b": '<a href="#" id="modal-ok">Tiedostoja</a><a href="#" id="modal-ng">Yksi kansio</a>', + "u_nav_m": '<h6>selvä, mitäs sulla on?</h6><code>Enter</code> = Tiedostoja (yksi tai useampi)\n<code>ESC</code> = Yksi hakemisto (mukaan lukien alihakemistot)', + "u_nav_b": '<a href="#" id="modal-ok">Tiedostoja</a><a href="#" id="modal-ng">Yksi hakemisto</a>', - "cl_opts": "kytkimet", + "cl_opts": "asetukset", "cl_themes": "teema", "cl_langs": "kieli", - "cl_ziptype": "kansion lataus", + "cl_ziptype": "hakemiston pakkaustyyppi", "cl_uopts": "up2k-kytkimet", "cl_favico": "favicon", "cl_bigdir": "suuret hakemistot", - "cl_hsort": "#lajittelu", - "cl_keytype": "sävellajin notaatio", + "cl_hsort": "#sort", + "cl_keytype": "sävellajin notaatiotyyppi", "cl_hiddenc": "piilotetut sarakkeet", "cl_hidec": "piilota", "cl_reset": "palauta", "cl_hpick": "napauta sarakeotsikoita piilottaaksesi alla olevassa taulukossa", "cl_hcancel": "sarakkeiden piilotus peruttu", - "ct_grid": '田 RUUDUKKO', - "ct_ttips": '◔ ◡ ◔">ℹ️ ammattilaisvinkit', - "ct_thumb": 'RUUDUKKOnäkymässä, vaihda kuvakkeihin tai pienoiskuviin$NPikanäppäin: T">🖼️ pienoiskuvat', - "ct_csel": 'käytä CTRL ja SHIFT tiedostojen valintaan RUUDUKKOnäkymässä">val', + "ct_grid": '田 kuvanäkymä', + "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_ihop": 'kun kuvakatselin suljetaan, vieritä alas viimeksi katsottuun tiedostoon">g⮯', "ct_dots": 'näytä piilotetut tiedostot (jos palvelin sallii)">piilotiedostot', - "ct_qdel": 'tiedostoja poistaessa, kysy vahvistusta vain kerran">qdel', + "ct_qdel": 'kysy vahvistusta vain kerran tiedostoja poistaessa">qdel', "ct_dir1st": 'lajittele hakemistot ennen tiedostoja">📁 ensin', "ct_nsort": 'luonnollinen lajittelu (tiedostonimille jotka ovat numeroalkuisia)">nsort', "ct_utc": 'näytä kaikki aikaleimat UTC-ajassa">UTC', @@ -2746,9 +4031,9 @@ var Ls = { "cut_umod": "jos tiedosto on jo olemassa palvelimella, päivitä palvelimen viimeksi muokattu aikaleima vastaamaan paikallista tiedostoasi (vaatii write- ja delete-oikeudet)\">re📅", - "cut_turbo": "yolo-painike, ET todennäköisesti halua ottaa tätä käyttöön:$N$Nkäytä tätä jos latasit valtavan määrän tiedostoja ja jouduit käynnistämään uudelleen jostain syystä, ja haluat jatkaa latausta ASAP$N$Ntämä korvaa tiivistetarkistuksen yksinkertaisella <em>"onko tällä sama tiedostokoko palvelimella?"</em> joten jos tiedoston sisältö on erilainen sitä EI ladata$N$Nsinun pitäisi poistaa tämä käytöstä kun lataus on valmis, ja sitten "ladata" samat tiedostot uudelleen antaaksesi asiakkaan varmistaa ne\">turbo", + "cut_turbo": "yolo-painike -- et todennäköisesti halua ottaa tätä käyttöön:$N$Nkäytä tätä jos latasit valtavan määrän tiedostoja ja jouduit käynnistämään uudelleen jostain syystä, ja haluat jatkaa latausta välittömästi$N$Ntämä korvaa tiivistetarkistuksen yksinkertaisella <em>"onko tällä sama tiedostokoko palvelimella?"</em> joten jos tiedoston sisältö on erilainen sitä EI ladata$N$Nsinun pitäisi poistaa tämä käytöstä kun lataus on valmis, ja sitten "ladata" samat tiedostot uudelleen antaaksesi selaimesi varmistaa ne\">turbo", - "cut_datechk": "ei vaikutusta ellei turbo-painike ole käytössä$N$Nvähentää yolo-tekijää hieman; tarkistaa vastaavatko tiedostojen aikaleimat palvelimella omia$N$Npitäisi <em>teoriassa</em> napata useimmat keskeneräiset / vioittuneet lataukset, mutta ei ole korvike varmistuskierrokselle turbo poistettuna käytöstä jälkeenpäin\">päiväys-tark", + "cut_datechk": "ei vaikutusta ellei turbo-painike ole käytössä$N$Nvähentää yolo-tekijää hieman; tarkistaa vastaavatko tiedostojen aikaleimat palvelimella omia$N$Npitäisi <em>teoriassa</em> napata useimmat keskeneräiset / vioittuneet lataukset, mutta ei ole korvike varmistuskierrokselle turbo poistettuna käytöstä jälkeenpäin\">päiväysvarmistin", "cut_u2sz": "kunkin lähetyspalan koko (MiB:ssä); suuret arvot lentävät paremmin atlantin yli. kokeile pieniä arvoja erittäin heikoilla yhteyksillä", @@ -2767,61 +4052,63 @@ var Ls = { "cft_fg": "edustaväri", "cft_bg": "taustaväri", - "cdt_lim": "tiedostojen enimmäismäärä näytettäväksi kansiossa", - "cdt_ask": "sivun lopussa,$Nsen sijaan että lataa automaattisesti lisää tiedostoja,$Nkysy mitä tehdä", + "cdt_lim": "tiedostojen enimmäismäärä näytettäväksi hakemistossa", + "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ä", "tt_entree": "näytä navigointipaneeli$NPikanäppäin: B", "tt_detree": "näytä linkkipolku$NPikanäppäin: B", - "tt_visdir": "näytä valittu kansio", + "tt_visdir": "näytä valittu hakemisto", "tt_ftree": "vaihda linkkipolku- / tekstitiedostonäkymään$NPikanäppäin: V", - "tt_pdock": "näytä yläkansiot telakoitussa paneelissa ylhäällä", + "tt_pdock": "näytä ylähakemistot telakoitussa paneelissa ylhäällä", "tt_dynt": "kasvata automaattisesti hakemistosyvyyden kasvaessa", "tt_wrap": "rivitys", "tt_hover": "paljasta ylivuotavat rivit leijutettaessa$N( rikkoo vierityksen ellei hiiri $N  ole vasemmassa marginaalissa )", - "ml_pmode": "kansion lopussa...", + "ml_pmode": "hakemiston lopussa...", "ml_btns": "komennot", - "ml_tcode": "transkoodaa", - "ml_tcode2": "transkoodaa muotoon", + "ml_tcode": "muunna nämä", + "ml_tcode2": "tähän muotoon", "ml_tint": "sävy", "ml_eq": "taajuuskorjain", "ml_drc": "dynaaminen alueen kompressori", "mt_loop": "toista samaa kappaletta\">🔁", - "mt_one": "lopeta yhden kappaleen jälkeen\">1️⃣", - "mt_shuf": "satunnaistoisto\">🔀", + "mt_one": "lopeta yhden toiston jälkeen\">1️⃣", + "mt_shuf": "aktivoi satunnaistoisto\">🔀", "mt_aplay": "automaattitoisto jos linkissä jolla pääsit palvelimelle oli kappale-ID$N$Ntämän poistaminen käytöstä pysäyttää myös sivun URL:n päivittämisen kappale-ID:lla musiikkia toistettaessa, estääksesi automaattitoiston jos nämä asetukset menetetään mutta URL säilyy\">a▶", "mt_preload": "aloita seuraavan kappaleen lataaminen lähellä loppua, mahdollistaen saumattoman toiston\">esilataus", - "mt_prescan": "siirry seuraavaan kansioon ennen viimeisen kappaleen$Nloppumista, pitäen verkkoselaimen tyytyväisenä$Njotta se ei pysäytä toistoa\">nav", - "mt_fullpre": "yritä esiladata koko kappale;$N✅ ota käyttöön <b>heikoilla</b> yhteyksillä,$N❌ <b>poista käytöstä</b> hitailla yhteyksillä\">täysi", + "mt_prescan": "siirry seuraavaan hakemistoon ennen viimeisen kappaleen$Nloppumista, pitäen verkkoselaimen tyytyväisenä$Njotta se ei pysäytä toistoa\">nav", + "mt_fullpre": "yritä esiladata koko kappale;$N✅ ota käyttöön <b>heikoilla</b> yhteyksillä,$N❌ <b>poista käytöstä</b> hitailla yhteyksillä\">esi+", "mt_fau": "puhelimissa: estä musiikin pysähtyminen jos seuraava kappale ei esilataudu tarpeeksi nopeasti (voi aiheuttaa ongelmia kappaletietojen näyttämisessä)\">☕️", "mt_waves": "aaltomuoto-hakupalkki:$Nnäytä äänenvahvuus selaimessa\">~s", "mt_npclip": "näytä painikkeet parhaillaan soivan kappaleen leikepöydälle kopioimiseen\">/np", "mt_m3u_c": "näytä painikkeet valittujen$Nkappaleiden kopioimiseen m3u8-soittolistana leikepöydälle\">📻", "mt_octl": "käyttöjärjestelmäintegraatio (medianäppäimet / osd)\">os-ctl", - "mt_oseek": "salli haku käyttöjärjestelmäintegraation kautta$N$Nhuom: joissakin laitteissa (iPhonet),$Ntämä korvaa seuraava-kappale-painikkeen\">kelaus", - "mt_oscv": "näytä albumin kansi osd:ssä\">taide", + "mt_oseek": "salli haku käyttöjärjestelmäintegraation kautta$N$Nhuom: joissakin laitteissa (iPhonet),$Ntämä korvaa 'seuraava kappale' -painikkeen\">kelaus", + "mt_oscv": "näytä albumin kansi osd:ssä\">kansikuvat", "mt_follow": "pidä soiva kappale näkyvissä\">🎯", "mt_compact": "kompaktit säätimet\">⟎", "mt_uncache": "tyhjennä välimuisti  (kokeile tätä jos selaimesi välimuistissa on$Nrikkinäinen kopio kappaleesta)\">uncache", - "mt_mloop": "toista avoinna olevaa hakemistoa loputtomasti\">🔁 silmukka", - "mt_mnext": "lataa seuraava kansio ja jatka\">📂 seuraava", + "mt_mloop": "toista avoinna olevaa hakemistoa loputtomasti\">🔁 alkuun", + "mt_mnext": "lataa seuraava hakemisto ja jatka\">📂 seuraava", "mt_mstop": "pysäytä toisto\">⏸ pysäytä", - "mt_cflac": "muunna flac / wav opus-muotoon\">flac", - "mt_caac": "muunna aac / m4a opus-muotoon\">aac", - "mt_coth": "muunna kaikki muut paitsi mp3 opus-muotoon\">muut", + "mt_cflac": "muunna flac / wav {0}-muotoon\">flac", + "mt_caac": "muunna aac / m4a {0}-muotoon\">aac", + "mt_coth": "muunna kaikki muut paitsi mp3 {0}-muotoon\">muut", "mt_c2opus": "paras valinta pöytäkoneille, kannettaville, androidille\">opus", "mt_c2owa": "opus-weba, iOS 17.5:lle ja uudemmille\">owa", "mt_c2caf": "opus-caf, iOS 11:lle - 17:lle\">caf", "mt_c2mp3": "käytä tätä erittäin vanhoissa laitteissa\">mp3", + "mt_c2flac": "paras äänenlaatu, mutta isot lataukset\">flac", //m + "mt_c2wav": "pakkaamaton toisto (vielä suurempi tiedosto)\">wav", //m "mt_c2ok": "hienoa, hyvä valinta", "mt_c2nd": "tuo ei ole suositeltu formaatti laitteellesi, mutta tee miten lystäät", "mt_c2ng": "laitteesi ei näytä tukevan tätä formaattia, mutta yritetään nyt silti", "mt_xowa": "iOS:ssä on bugeja jotka estävät taustatoiston tällä formaatilla; käytä caf:ia tai mp3:a sen sijaan", "mt_tint": "taustan taso (0-100) liukupalkissa$Ntehden puskuroinnista vähemmän häiritsevän", "mt_eq": "aktivoi taajuuskorjaimen ja vahvistussäätimen;$N$Nvahvistus <code>0</code> = normaali 100% äänenvoimakkuus (muokkaamaton)$N$Nleveys <code>1  </code> = normaali stereo (muokkaamaton)$Nleveys <code>0.5</code> = 50% vasen-oikea ristisyöttö$Nleveys <code>0  </code> = mono$N$Nvahvistus <code>-0.8</code> & leveys <code>10</code> = laulun poisto :^)$N$Nequalizerin käyttöönotto tekee saumattomista albumeista täysin saumattomia, joten jätä se päälle kaikilla arvoilla nollassa (paitsi leveys = 1) jos välität siitä", - "mt_drc": "aktivoi dynaamisen alueen kompressorin (äänenvoimakkuuden tasoittaja / tiiliseinättäjä); ottaa myös käyttöön EQ:n tasapainottamaan spagettia, joten aseta kaikki EQ-kentät paitsi 'leveys' 0:aan jos et halua sitä$N$Nalentaa äänenvoimakkuutta KYNNYS dB:n yläpuolella; jokaisesta SUHDE dB:stä KYNNYKSEN yli tulee 1 dB ulos, joten oletusarvot kynnys -24 ja suhde 12 tarkoittaa ettei sen pitäisi koskaan tulla kovempaa kuin -22 dB ja on turvallista nostaa equalizerin vahvistus 0.8:aan, tai jopa 1.8:aan ATK 0:lla ja valtavalla RLS:llä kuten 90 (toimii vain firefoxissa; RLS on max 1 muissa selaimissa)$N$N(katso wikipedia, he selittävät sen paljon paremmin)", + "mt_drc": "aktivoi dynaamisen alueen kompressorin; ottaa myös käyttöön taajuuskorjaimen tasapainottamaan spagettia, joten aseta kaikki EQ-kentät paitsi 'leveys' nollaan jos et halua sitä$N$Nalentaa äänenvoimakkuutta KYNNYS dB:n yläpuolella; jokaisesta SUHDE dB:stä KYNNYKSEN yli tulee 1 dB ulos, joten oletusarvot kynnys -24 ja suhde 12 tarkoittaa ettei sen pitäisi koskaan tulla kovempaa kuin -22 dB ja on turvallista nostaa equalizerin vahvistus 0.8:aan, tai jopa 1.8:aan ATK 0:lla ja valtavalla RLS:llä kuten 90 (toimii vain firefoxissa; RLS on max 1 muissa selaimissa)$N$N(katso wikipedia, he selittävät sen paljon paremmin)", "mb_play": "toista", "mm_hashplay": "soita tämä äänitiedosto?", @@ -2846,33 +4133,33 @@ var Ls = { "mm_uncache": "välimuisti tyhjennetty; kaikki kappaleet ladataan uudelleen seuraavalla toistolla", "mm_hnf": "tuota kappaletta ei enää ole olemassa", -" im_hnf": "tuota kuvaa ei enää ole olemassa", + "im_hnf": "tuota kuvaa ei enää ole olemassa", - "f_empty": 'tämä kansio on tyhjä', + "f_empty": 'tämä hakemisto on tyhjä', "f_chide": 'tämä piilottaa sarakkeen «{0}»\n\nvoit palauttaa sarakkeet asetuksista', "f_bigtxt": "tämä tiedosto on {0} Mt kokoinen -- näytetäänkö silti tekstinä?", "f_bigtxt2": "näytetäänkö vain tiedoston loppu? tämä myös mahdollistaa seuraamisen/tailing, näyttäen uudet tekstirivit reaaliaikaisesti", "fbd_more": '<div id="blazy">näytetään <code>{0}</code> / <code>{1}</code> tiedostoa; <a href="#" id="bd_more">näytä {2}</a> tai <a href="#" id="bd_all">näytä kaikki</a></div>', "fbd_all": '<div id="blazy">näytetään <code>{0}</code> / <code>{1}</code> tiedostoa; <a href="#" id="bd_all">näytä kaikki</a></div>', - "f_anota": "vain {0} / {1} kohdetta valittiin;\nvalitaksesi koko kansion, vieritä ensin loppuun", + "f_anota": "vain {0} / {1} kohdetta valittiin;\nvalitaksesi koko hakemiston, vieritä ensin loppuun", - "f_dls": 'nykyisen kansion tiedostolinkit on\nvaihdettu latauslinkeiksi', + "f_dls": 'nykyisen hakemiston tiedostolinkit on\nvaihdettu latauslinkeiksi', "f_partial": "Ladataksesi turvallisesti tiedoston joka on parhaillaan latautumassa, klikkaa tiedostoa jolla on sama nimi mutta ilman <code>.PARTIAL</code> päätettä. Paina PERUUTA tai Escape tehdäksesi tämän.\n\nOK / Enter painaminen sivuuttaa tämän varoituksen ja jatkaa <code>.PARTIAL</code> väliaikaistiedoston lataamista, mikä todennäköisesti antaa sinulle vioittunutta dataa.", "ft_paste": "liitä {0} kohdetta$NPikanäppäin: ctrl-V", - "fr_eperm": 'ei voida nimetä uudelleen:\nsinulla ei ole “move”-oikeutta tässä kansiossa', - "fd_eperm": 'ei voida poistaa:\nsinulla ei ole “delete” oikeutta tässä kansiossa', - "fc_eperm": 'ei voida leikata:\nsinulla ei ole “move” oikeutta tässä kansiossa', - "fp_eperm": 'ei voida liittää:\nsinulla ei ole “write” oikeutta tässä kansiossa', + "fr_eperm": 'ei voida nimetä uudelleen:\nsinulla ei ole “move”-oikeutta tässä hakemistossa', + "fd_eperm": 'ei voida poistaa:\nsinulla ei ole “delete” oikeutta tässä hakemistossa', + "fc_eperm": 'ei voida leikata:\nsinulla ei ole “move” oikeutta tässä hakemistossa', + "fp_eperm": 'ei voida liittää:\nsinulla ei ole “write” oikeutta tässä hakemistossa', "fr_emore": "valitse vähintään yksi kohde uudelleennimettäväksi", "fd_emore": "valitse vähintään yksi kohde poistettavaksi", "fc_emore": "valitse vähintään yksi kohde leikattavaksi", "fcp_emore": "valitse vähintään yksi kohde kopioitavaksi leikepöydälle", - "fs_sc": "jaa kansio jossa olet", + "fs_sc": "jaa hakemisto jossa olet", "fs_ss": "jaa valitut tiedostot", - "fs_just1d": "et voi valita useampaa kuin yhtä kansiota,\ntai sekoittaa tiedostoja ja kansioita yhdessä valinnassa", + "fs_just1d": "et voi valita useampaa kuin yhtä,\ntai sekoittaa tiedostoja ja hakemistoja yhdessä valinnassa", "fs_abrt": "❌ keskeytä", "fs_rand": "🎲 joku.nimi", "fs_go": "✅ luo share", @@ -2885,10 +4172,10 @@ var Ls = { "fs_tdays": "päivää", "fs_never": "ikuinen", "fs_pname": "valinnainen linkin nimi; on satunnainen jos tyhjä", - "fs_tsrc": "jaettava tiedosto tai kansio", + "fs_tsrc": "jaettava tiedosto tai hakemisto", "fs_ppwd": "valinnainen salasana", "fs_w8": "luodaan sharea...", - "fs_ok": "paina <code>Enter/OK</code> lisätäksesi leikepöydälle\npaina <code>ESC/Cancel</code> sulkeaksesi", + "fs_ok": "paina <code>Enter/OK</code> lisätäksesi leikepöydälle\npaina <code>ESC/Peruuta</code> sulkeaksesi", "frt_dec": "saattaa korjata joitakin rikkinäisiä tiedostonimiä\">url-decode", "frt_rst": "palauta muokatut tiedostonimet takaisin alkuperäisiksi\">↺ palauta", @@ -2897,7 +4184,7 @@ var Ls = { "fr_adv": "erä / liitännäistiedot / kaava uudelleennimeäminen\">lisäasetukset", "fr_case": "isot ja pienet kirjaimet erottava regex\">kirjainkoko", "fr_win": "windows-yhteensopivat nimet; korvaa <code><>:"\\|?*</code> japanilaisilla leveillä merkeillä\">win", - "fr_slash": "korvaa <code>/</code> merkillä joka ei aiheuta uusien kansioiden luomista\">ei /", + "fr_slash": "korvaa <code>/</code> merkillä joka ei aiheuta uusien hakemistoiden luomista\">ei /", "fr_re": "regex hakukuvio jota käytetään alkuperäisiin tiedostonimiin; kaappausryhmiin voi viitata alla olevassa muotoilukentässä kuten <code>(1)</code> ja <code>(2)</code> ja niin edelleen", "fr_fmt": "foobar2000 innoittama:$N<code>(title)</code> korvataan kappaleen nimellä,$N<code>[(artist) - ](title)</code> sivuuttaa [tämän] osan jos artisti on tyhjä$N<code>$lpad((tn),2,0)</code> ", "fr_pdel": "poista", @@ -2925,7 +4212,7 @@ var Ls = { "fcc_warn": 'kopioitiin {0} kohdetta leikepöydälle\n\nmutta: vain <b>tämä</b> selain-välilehti voi liittää ne\n(koska valinta on niin valtavan suuri)', "fp_apply": "käytä näitä nimiä", - "fp_ecut": "leikkaa tai kopioi ensin joitakin tiedostoja / kansioita liitettäväksi / siirrettäväksi\n\nhuom: voit leikata / liittää eri selain-välilehtien välillä", + "fp_ecut": "leikkaa tai kopioi ensin joitakin tiedostoja / hakemistoja liitettäväksi / siirrettäväksi\n\nhuom: voit leikata / liittää eri selain-välilehtien välillä", "fp_ename": "{0} kohdetta ei voida siirtää tänne koska nimet ovat jo käytössä. Anna niille uudet nimet alla jatkaaksesi, tai tyhjennä nimi ohittaaksesi ne:", "fcp_ename": "{0} kohdetta ei voida kopioida tänne koska nimet ovat jo käytössä. Anna niille uudet nimet alla jatkaaksesi, tai tyhjennä nimi ohittaaksesi ne:", "fp_emore": "tiedostonimien törmäyksiä on vielä korjaamatta", @@ -2933,6 +4220,7 @@ var Ls = { "fcp_ok": "kopiointi OK", "fp_busy": "siirretään {0} kohdetta...\n\n{1}", "fcp_busy": "kopioidaan {0} kohdetta...\n\n{1}", + "fp_abrt": "keskeytetään...", //m "fp_err": "siirto epäonnistui:\n", "fcp_err": "kopiointi epäonnistui:\n", "fp_confirm": "siirrä nämä {0} kohdetta tänne?", @@ -2949,8 +4237,8 @@ var Ls = { "tv_load": "Ladataan tekstidokumenttia:\n\n{0}\n\n{1}% ({2} / {3} Mt ladattu)", "tv_xe1": "tekstitiedoston lataaminen epäonnistui:\n\nvirhe ", "tv_xe2": "404, tiedostoa ei löydy", - "tv_lst": "tekstitiedostojen lista kansiossa", - "tvt_close": "palaa kansionäkymään$NPikanäppäin: M (tai Esc)\">❌ sulje", + "tv_lst": "tekstitiedostojen lista hakemistossa", + "tvt_close": "palaa hakemistonäkymään$NPikanäppäin: M (tai Esc)\">❌ sulje", "tvt_dl": "lataa tämä tiedosto$NPikanäppäin: Y\">💾 lataa", "tvt_prev": "näytä edellinen dokumentti$NPikanäppäin: i\">⬆ edell", "tvt_next": "näytä seuraava dokumentti$NPikanäppäin: K\">⬇ seur", @@ -2967,8 +4255,8 @@ var Ls = { "m3u_clip": "m3u soittolista nyt kopioitu leikepöydälle\n\nsinun tulisi luoda uusi tekstitiedosto nimeltä jotain.m3u ja liittää soittolista siihen dokumenttiin; tämä tekee siitä soitettavan", "gt_vau": "älä näytä videoita, toista vain ääni\">🎧", - "gt_msel": "ota käyttöön tiedostojen valinta; ctrl-klikkaa tiedostoa ohittaaksesi$N$N<em>kun aktiivinen: tuplaklikkaa tiedostoa / kansiota avataksesi sen</em>$N$NPikanäppäin: S\">monivalinta", - "gt_crop": "keskitä-rajaa pienoiskuvat\">rajaa", + "gt_msel": "aktivoi tiedostonvalintatila; ctrl-klikkaa ohittaaksesi valitsemisen väliaikaisesti$N$N<em>tuplaklikkaa tiedostoa / hakemistoa avataksesi sen</em>$N$NPikanäppäin: S\">valitsin", + "gt_crop": "rajaa pienoiskuvat keskeltä\">rajaa", "gt_3x": "korkearesoluutioiset pienoiskuvat\">3x", "gt_zoom": "zoomaa", "gt_chop": "pilko", @@ -3012,28 +4300,28 @@ var Ls = { "xhr403": "403: Pääsy kielletty\n\nkokeile painaa F5, ehkä sinut kirjattiin ulos", "xhr0": "tuntematon (todennäköisesti yhteys palvelimeen katosi, tai palvelin on pois päältä)", "cf_ok": "sori siitä -- DD" + wah + "oS suojaus aktivoitui\n\nasioiden pitäisi jatkua noin 30 sekunnissa\n\njos mitään ei tapahdu, paina F5 ladataksesi sivun uudelleen", - "tl_xe1": "alikansioiden listaaminen epäonnistui:\n\nvirhe ", - "tl_xe2": "404: Kansiota ei löydy", - "fl_xe1": "kansion tiedostojen listaaminen epäonnistui:\n\nvirhe ", - "fl_xe2": "404: Kansiota ei löydy", - "fd_xe1": "alikansion luominen epäonnistui:\n\nvirhe ", - "fd_xe2": "404: Yläkansiota ei löydy", + "tl_xe1": "alihakemistojen listaaminen epäonnistui:\n\nvirhe ", + "tl_xe2": "404: hakemistoa ei löydy", + "fl_xe1": "hakemiston tiedostojen listaaminen epäonnistui:\n\nvirhe ", + "fl_xe2": "404: hakemistoa ei löydy", + "fd_xe1": "alihakemiston luominen epäonnistui:\n\nvirhe ", + "fd_xe2": "404: Ylähakemistoa ei löydy", "fsm_xe1": "viestin lähettäminen epäonnistui:\n\nvirhe ", - "fsm_xe2": "404: Yläkansiota ei löydy", + "fsm_xe2": "404: Ylähakemistoa ei löydy", "fu_xe1": "unpost-listan lataaminen palvelimelta epäonnistui:\n\nvirhe ", "fu_xe2": "404: Tiedostoa ei löydy??", "fz_tar": "pakkaamaton gnu-tar tiedosto (linux / mac)", "fz_pax": "pakkaamaton pax-formaatin tar (hitaampi)", - "fz_targz": "gnu-tar gzip tason 3 pakkauksella$N$NTämä on yleensä hyvin hidasta, $Nkäytä pakkamatonta tar:ia sen sijaan", - "fz_tarxz": "gnu-tar xz tason 1 pakkauksella$N$NTämä on yleensä hyvin hidasta, $Nkäytä pakkamatonta tar:ia sen sijaan", + "fz_targz": "gnu-tar gzip tason 3 pakkauksella$N$Nyleensä hyvin hidas, $Nkäytä pakkamatonta tar:ia tämän sijasta", + "fz_tarxz": "gnu-tar xz tason 1 pakkauksella$N$Nyleensä hyvin hidas, $Nkäytä pakkamatonta tar:ia tämän sijasta", "fz_zip8": "zip utf8-tiedostonimillä (suattaapi olla epävakaa windows 7:ssa ja vanhemmissa)", "fz_zipd": "zip perinteisillä cp437 tiedostonimillä esihistoriallisille ohjelmistoille", "fz_zipc": "cp437, jossa crc32 laskettu aikaisin,$NMS-DOS PKZIP v2.04g:lle (lokakuu 1993)$N(kestää kauemmin käsitellä ennen latauksen alkua)", - "un_m1": "voit poistaa viimeaikaiset latauksesi (tai keskeyttää keskeneräiset) alla", + "un_m1": "voit poistaa tuoreet tai keskeyttää keskeneräiset latauksesi alta", "un_upd": "päivitä", - "un_m4": "tai jaa alla näkyvät tiedostot:", + "un_m4": "tai jakaa alla näkyvät tiedostot:", "un_ulist": "näytä", "un_ucopy": "kopioi", "un_flt": "valinnainen suodatin:  URL:n täytyy sisältää", @@ -3041,8 +4329,8 @@ var Ls = { "un_derr": 'unpost-poisto epäonnistui:\n', "un_f5": 'jotain hajosi, kokeile päivitystä tai paina F5', "un_uf5": "pahoittelen mutta sinun täytyy päivittää sivu (esimerkiksi painamalla F5 tai CTRL-R) ennen kuin tämä lataus voidaan keskeyttää", - "un_nou": '<b>varoitus:</b> palvelin liian kiireinen näyttääkseen keskeneräiset lataukset; klikkaa "päivitä" linkkiä hetken kuluttua', - "un_noc": '<b>varoitus:</b> täysin ladattujen tiedostojen unpost ei ole käytössä/sallittu palvelimen asetuksissa', + "un_nou": '<b>huom!</b> palvelin liian kiireinen näyttääkseen keskeneräiset lataukset; klikkaa "päivitä" linkkiä hetken kuluttua', + "un_noc": '<b>huom!</b> täysin ladattujen tiedostojen unpost ei ole käytössä/sallittu palvelimen asetuksissa', "un_max": "näytetään ensimmäiset 2000 tiedostoa (käytä suodatinta)", "un_avail": "{0} viimeaikaista latausta voidaan poistaa<br />{1} keskeneräistä voidaan keskeyttää", "un_m2": "järjestetty latausajan mukaan; viimeisimmät ensin:", @@ -3069,15 +4357,15 @@ var Ls = { "u_gotpot": 'vaihdetaan peruna UI:hin paremman latausnopeuden vuoksi,\n\ntee miten lystäät, jos ei kelpaa!', "u_pott": "<p>tiedostot:   <b>{0}</b> valmis,   <b>{1}</b> epäonnistui,   <b>{2}</b> kiireinen,   <b>{3}</b> jonossa</p>", "u_ever": "tämä on peruslatain; up2k tarvitsee vähintään<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", - "u_su2k": 'tämä on peruslatain; <a href="#" id="u2yea">up2k</a> on parempi', - "u_uput": 'optimoi nopeuteen (ohita tarkistussumma)', - "u_ewrite": 'sinulla ei ole move-oikeutta tähän kansioon', - "u_eread": 'sinulla ei ole read-oikeutta tähän kansioon', + "u_su2k": 'peruslatain; <a href="#" id="u2yea">up2k</a> on parempi', + "u_uput": 'optimoi latausnopeus (älä laske tarkistussummia)', + "u_ewrite": 'sinulla ei ole move-oikeutta tähän hakemistoon', + "u_eread": 'sinulla ei ole read-oikeutta tähän hakemistoon', "u_enoi": 'tiedostohaku ei ole käytössä palvelimen asetuksissa', "u_enoow": "ylikirjoitus ei toimi täällä; tarvitaan “Delete”-oikeus", "u_badf": 'Nämä {0} tiedostoa ({1} yhteensä) ohitettiin, mahdollisesti tiedostojärjestelmän oikeuksien vuoksi:\n\n', "u_blankf": 'Nämä {0} tiedostoa ({1} yhteensä) ovat tyhjiä; ladataanko ne silti?\n\n', - "u_applef": 'Nämä {0} tiedostoa ({1} yhteensä) ovat todennäköisesti ei-toivottuja;\nPaina <code>OK/Enter</code> OHITTAAKSESI seuraavat tiedostot,\nPaina <code>Cancel/ESC</code> jos ET halua sulkea pois, ja LATAA nekin:\n\n', + "u_applef": 'Nämä {0} tiedostoa ({1} yhteensä) ovat todennäköisesti ei-toivottuja;\nPaina <code>OK/Enter</code> OHITTAAKSESI seuraavat tiedostot,\nPaina <code>Peruuta/ESC</code> jos ET halua sulkea pois, ja LATAA nekin:\n\n', "u_just1": '\nEhkä toimii paremmin jos valitset vain yhden tiedoston', "u_ff_many": "jos käytät <b>Linux / MacOS / Android,</b> niin tämä määrä tiedostoja <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>saattaa</em> kaataa Firefoxin!</a>\njos niin käy, kokeile uudelleen (tai käytä Chromea).", "u_up_life": "Tämä lataus poistetaan palvelimelta\n{0} sen valmistumisen jälkeen", @@ -3119,15 +4407,15 @@ var Ls = { "u_turbo_g": "poistetaan turbo käytöstä koska sinulla ei ole\nhakemistolistausoikeuksia tässä asemassa", "u_life_cfg": 'automaattinen poisto <input id="lifem" p="60" /> min kuluttua (tai <input id="lifeh" p="3600" /> tuntia)', "u_life_est": 'lataus poistetaan <span id="lifew" tt="paikallinen aika">---</span>', - "u_life_max": 'tämä kansio pakottaa\nmaksimi elinajan {0}', + "u_life_max": 'tämä hakemisto pakottaa\nmaksimi elinajan {0}', "u_unp_ok": 'unpost on sallittu {0}', "u_unp_ng": 'unpost EI ole sallittu', - "ue_ro": 'sinulla on vain read-oikeus tähän kansioon\n\n', + "ue_ro": 'sinulla on vain read-oikeus tähän hakemistoon\n\n', "ue_nl": 'et ole tällä hetkellä kirjautunut sisään', "ue_la": 'olet tällä hetkellä kirjautunut sisään nimellä "{0}"', "ue_sr": 'olet tällä hetkellä tiedostohaku-tilassa\n\nvaihda lataus-tilaan klikkaamalla suurennuslasia 🔎 (suuren HAKU napin vieressä), ja yritä latausta uudelleen\n\npahoittelen', "ue_ta": 'yritä latausta uudelleen, sen pitäisi toimia nyt', - "ue_ab": "tätä tiedostoa ladataan jo toiseen kansioon, ja se lataus täytyy suorittaa loppuun ennen kuin tiedostoa voidaan ladata muualle.\n\nVoit keskeyttää ja unohtaa alkuperäisen latauksen käyttämällä vasemmalla ylhäällä olevaa 🧯", + "ue_ab": "tätä tiedostoa ladataan jo toiseen hakemistoon, ja se lataus täytyy suorittaa loppuun ennen kuin tiedostoa voidaan ladata muualle.\n\nVoit keskeyttää ja unohtaa alkuperäisen latauksen käyttämällä vasemmalla ylhäällä olevaa 🧯", "ur_1uo": "OK: Tiedosto ladattu onnistuneesti", "ur_auo": "OK: Kaikki {0} tiedostoa ladattu onnistuneesti", "ur_1so": "OK: Tiedosto löytyi palvelimelta", @@ -3139,7 +4427,5058 @@ var Ls = { "ur_um": "Valmis;\n{0} latausta OK,\n{1} latausta epäonnistui, pahoittelen", "ur_sm": "Valmis;\n{0} tiedostoa löytyi palvelimelta,\n{1} tiedostoa EI löytynyt palvelimelta", - "lang_set": "päivitetäänkö sivu muutoksen voimaansaattamiseksi?", + "lang_set": "ladataanko sivu uudestaan kielen vaihtamiseksi?", + }, + "fra": { + "tt": "français", + + "cols": { + "c": "bouton d'action", + "dur": "durée", + "q": "qualité / débit binaire", + "Ac": "codec audio", + "Vc": "codec vidéo", + "Fmt": "format / conteneur", + "Ahash": "somme de contrôle audio", + "Vhash": "somme de contrôle vidéo", + "Res": "résolution", + "T": "type de fichier", + "aq": "qualité audio / débit binaire", + "vq": "qualité vidéo / débit binaire", + "pixfmt": "sous-échantillonnage / structure de pixel", + "resw": "résolution horizontale", + "resh": "résolution verticale", + "chs": "canaux audio", + "hz": "fréquence" + }, + + "hks": [ + [ + "misc", + ["Échap", "ferme divers menus"], + + "gestionaire de fichiers", + ["G", "activer vue en liste / vue en grille"], + ["T", "activer les miniatures / icônes"], + ["⇧ A/D", "taille des miniatures"], + ["ctrl-K", "suprimer la sélection"], + ["ctrl-X", "couper la sélection au presse-papier"], + ["ctrl-C", "copier la sélection au presse-papier"], + ["ctrl-V", "coller (déplacer/copier) ici"], + ["Y", "télécharger la sélection"], + ["F2", "renomer la sélection"], + + "file-list-sel", + ["Espace", "activer la sélection de fichiers"], + ["↑/↓", "déplacer le selecteur"], + ["ctrl ↑/↓", "déplacer le curseur et la zone d'affichage"], + ["⇧ ↑/↓", "sélectioner le fichier précédent/suivant"], + ["ctrl-A", "sélectionner tout les fichiers / dossiers"], + ], [ + "navigation", + ["B", "basculer la vue en fil d'Ariane / panneau de navigation"], + ["I/K", "dossier précédent/suivant"], + ["M", "dossier parent (ou réduire le dossier actuel)"], + ["V", "activer les dossiers / fichiers texte dans le volet de navigation"], + ["A/D", "taille du volet de navigation"], + ], [ + "lecteur-audio", + ["J/L", "chanson précédente/suivante"], + ["U/O", "sauter 10s en arrière/avant"], + ["0..9", "sauter à 0%..90%"], + ["P", "lecture/pause (démarre également la lecture)"], + ["S", "sélectionner la chanson en cours"], + ["Y", "télécharger le morceau"], + ], [ + "visionneuse d'image", + ["J/L, ←/→", "image précédente/suivante"], + ["Début/Fin, ⭦/Fin", "première/dernière image"], + ["F", "plein écran"], + ["R", "rotation horaire"], + ["⇧ R", "rotation antihoraire"], + ["S", "sélectionner l'image"], + ["Y", "télécharger l'image"], + ], [ + "lecteur vidéo", + ["U/O", "sauter 10s en arrière/avant"], + ["P/K/Espace", "lecture/pause"], + ["C", "continuer de lire la suivante"], + ["V", "lire en boucle"], + ["M", "couper le son"], + ["[ and ]", "définir l'intervalle de boucle"], + ], [ + "visionneuse de texte", + ["I/K", "fichier précédent/suivant"], + ["M", "fermer le fichier texte"], + ["E", "modifier le fichier texte"], + ["S", "sélectioner le fichier (pour le couper/copier/renommer)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Annuler", + + "enable": "Activer", + "danger": "DANGER", + "clipped": "copié dans le presse-papier", + + "ht_s1": "seconde", + "ht_s2": "secondes", + "ht_m1": "minute", + "ht_m2": "minutes", + "ht_h1": "heure", + "ht_h2": "heures", + "ht_d1": "jour", + "ht_d2": "jours", + "ht_and": " et ", + + "goh": "panneau-de-commande", + "gop": 'élément "frère" précédent">précédent', + "gou": 'dossier parent">haut', + "gon": 'dossier suivant">suivant', + "logout": "Déconnexion ", + "login": "Se connecter", //m + "access": " accès", + "ot_close": "fermer le sous-menu", + "ot_search": "chercher des fichiers par leurs attributs, chemin / nom, tag musicaux, ou nimporte quelle combinaison de ces options$N$N<code>foo bar</code> = doit contenir à la fois «foo» et «bar»,$N<code>foo -bar</code> = doit contenir «foo» mais pas «bar»,$N<code>^yana .opus$</code> = commence par «yana» et est un fichier «opus»$N<code>"try unite"</code> = contient exactement «try unite»$N$Nle format de date est iso-8601, comme$N<code>2009-12-31</code> ou <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: supprimer vos téléchargements récents, ou annuler ceux en cours", + "ot_bup": "bup: téléverseur de base, prend même en charge netscape 4.0", + "ot_mkdir": "mkdir: créer un nouveau répertoire", + "ot_md": "new-md: créer un nouveau document markdown", + "ot_msg": "msg: envoyer un message au journal du serveur", + "ot_mp": "options du lecteur multimedia", + "ot_cfg": "options de configuration", + "ot_u2i": 'up2k : téléverser des fichiers (si vous avez un accès en écriture) ou basculer en mode recherche pour voir s\'ils existent quelque part sur le serveur$N$Nles téléversements peuvent être repris, ils sont multithreadé, et les horodatages des fichiers sont préservés, mais cela utilise plus de CPU que [🎈]  (le téléverseur de base)<br /><br />pendant les téléversements, cette icône devient un indicateur de progression!', + "ot_u2w": 'up2k : téléverser des fichiers avec prise en charge de la reprise (fermez votre navigateur et déposez les mêmes fichiers plus tard)$N$multithreadé, et les horodatages des fichiers sont préservés, mais cela utilise plus de CPU que [🎈]  (le téléverseur de base)<br /><br />pendant les téléversements, cette icône devient un indicateur de progression!', + "ot_noie": 'Utilisez Chrome / Firefox / Edge', + + "ab_mkdir": "créer un nouveau répertoire", + "ab_mkdoc": "faire un nouveau document markdown", + "ab_msg": "envoyer un message au journal du serveur", + + "ay_path": "passer aux dossiers", + "ay_files": "passer aux fichiers", + + "wt_ren": "renommer les éléments sélectionnés$NHotkey: F2", + "wt_del": "supprimer les éléments sélectionnés$NHotkey: ctrl-K", + "wt_cut": "couper les éléments sélectionnés <small>(puis coller ailleurs)</small>$NHotkey: ctrl-X", + "wt_cpy": "copier les éléments sélectionnés dans le presse-papiers$N(pour les coller ailleurs)$NHotkey: ctrl-C", + "wt_pst": "coller une sélection précédemment coupée / copiée$NHotkey: ctrl-V", + "wt_selall": "sélectionner tous les fichiers$NHotkey: ctrl-A (lorsque le fichier est sélectionné)", + "wt_selinv": "inverser la sélection", + "wt_zip1": "télécharger ce dossier en tant qu'archive", + "wt_selzip": "télécharger la sélection en tant qu'archive", + "wt_seldl": "télécharger la sélection en tant que fichiers séparés$NHotkey: Y", + "wt_npirc": "copier les informations de la musique au format irc", + "wt_nptxt": "copier les informations de la musique en texte brut", + "wt_m3ua": "ajouter à la playlist m3u (cliquez sur <code>📻copier</code> plus tard)", + "wt_m3uc": "copier la playlist m3u dans le presse-papiers", + "wt_grid": "basculer entre la vue en grille / liste$NHotkey: G", + "wt_prev": "musique précédente$NHotkey: J", + "wt_play": "lecture / pause$NHotkey: P", + "wt_next": "musique suivante$NHotkey: L", + + "ul_par": "téléversements parallèles:", + "ut_rand": "attribution de noms de fichiers aléatoires", + "ut_u2ts": "copier l'horodatage de dernière modification$Nde votre système de fichiers vers le serveur\">📅", + "ut_ow": "écraser les fichiers existants sur le serveur?$N🛡️: jamais (générera un nouveau nom de fichier à la place)$N🕒: écraser si le fichier sur le serveur est plus ancien que le vôtre$N♻️: toujours écraser si les fichiers sont différents", + "ut_mt": "continuer à calculer la somme de contrôle d'autres fichiers pendant le téléversement$N$Npeut-être désactiver si votre CPU ou HDD est la cause de perte de performances", + "ut_ask": 'demander confirmation avant le début du téléversement">💭', + "ut_pot": "améliorer la vitesse de téléversement sur les appareils lents$Nen simplifiant l'interface utilisateur", + "ut_srch": "ne pas réellement téléverser, mais vérifier si les fichiers existent déjà$N sur le serveur (scannera tous les dossiers que vous pouvez lire)", + "ut_par": "mettre en pause les téléversements en le réglant sur 0$N$Naugmenter si votre connexion est lente / à forte latence$N$Nle garder à 1 sur le LAN ou si le HDD du serveur est un goulot d'étranglement", + "ul_btn": "déposer des fichiers / dossiers<br>ici (ou cliquez sur moi)", + "ul_btnu": "T É L É V E R S E R", + "ul_btns": "C H E R C H E R", + + "ul_hash": "somme de contrôle", + "ul_send": "envoyer", + "ul_done": "terminé", + "ul_idle1": "aucun téléversement n'est encore dans la file d'attente", + "ut_etah": "moyenne <em>hashing</em> vitesse, et temps estimé jusqu'à la fin", + "ut_etau": "moyenne <em>upload</em> vitesse et temps estimé jusqu'à la fin", + "ut_etat": "moyenne <em>total</em> vitesse et temps estimé jusqu'à la fin", + + "uct_ok": "terminé avec succès", + "uct_ng": "non réussi : échoué / rejeté / non trouvé", + "uct_done": "terminés et échoué combinés", + "uct_bz": "hachage ou téléversement", + "uct_q": "inactif, en attente", + + "utl_name": "nom de fichier", + "utl_ulist": "liste", + "utl_ucopy": "copie", + "utl_links": "liens", + "utl_stat": "état", + "utl_prog": "progrès", + + // keep short: + "utl_404": "404", + "utl_err": "ERREUR", + "utl_oserr": "OS-ERREUR", + "utl_found": "trouvé", + "utl_defer": "état", + "utl_yolo": "YOLO", + "utl_done": "terminé", + + "ul_flagblk": "les fichiers ont été ajoutés à la file d'attente</b><br>cependant, il y a un processus up2k actif dans un autre onglet du navigateur,<br>en attente qu'il finisse d'abord", + "ul_btnlk": "la configuration du serveur a verrouillé cette options dans cet état", + + "udt_up": "Téléverser", + "udt_srch": "Chercher", + "udt_drop": "déposer ici", + + "u_nav_m": '<h6>aight, ques-que tu à ?</h6><code>Enter</code> = Fichiers (un ou plus)\n<code>ESC</code> = Un dossier (sous-dossiers inclus)', + "u_nav_b": '<a href="#" id="modal-ok">Fichiers</a><a href="#" id="modal-ng">Un dossier</a>', + + "cl_opts": "options", + "cl_themes": "thème", + "cl_langs": "langue", + "cl_ziptype": "téléchargement de dossier", + "cl_uopts": "up2k", + "cl_favico": "favicon", + "cl_bigdir": "gros dossiers", + "cl_hsort": "#sort", + "cl_keytype": "notation des touches", + "cl_hiddenc": "colonnes masquées", + "cl_hidec": "masquer", + "cl_reset": "réinitialiser", + "cl_hpick": "cliquez sur les en-têtes de colonnes pour les masquer dans le tableau ci-dessous", + "cl_hcancel": "masquage des colonnes annulé", + + "ct_grid": '田 grille', + "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_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', + "ct_qdel": 'ne demander qu\'une confirmation lors de la suppression de fichiers>qdel', + "ct_dir1st": 'trier les dossiers avant les fichiers">📁 first', + "ct_nsort": 'triage par numérotation (pour les nom de fichiers qui sont numérotés)">nsort', + "ct_utc": 'voir tout les horodatage en format UTC">UTC', + "ct_readme": 'voir le fichier README.md dans le listage des dossiers">📜 readme', + "ct_idxh": 'voir une version html (index.html) au-lieu du listage des dossiers normal">htm', + "ct_sbars": 'montrer la barre de defilement">⟊', + + "cut_umod": "si un fichier existe déjà sur le server, mettre à jour l'horodatage de dernière modification du serveur pour qu'il corresponde à votre fichier local (nécessite des autorisations d'écriture et de suppression)\">re📅", + + "cut_turbo": "le bouton yolo, vous ne voulez probablement PAS activer ceci:$N$Nutilisez ceci si vous téléchargez une grande quantité de fichiers et que vous devez redémarrer pour une raison quelconque, et que vous souhaitez continuer le téléchargement dès que possible$N$Ncela remplace la vérification de hachage par une simple <em>"est-ce que cela a la même taille de fichier sur le serveur?"</em> donc si le contenu du fichier est différent, il ne sera PAS téléchargé$N$Nvous devriez désactiver cela lorsque le téléchargement est terminé, puis "télécharger" les mêmes fichiers à nouveau pour laisser le client les vérifier\">turbo", + + "cut_datechk": "n'a aucun effet à moins que le bouton turbo ne soit activé$N$Nréduit le facteur yolo d'un tout petit peu ; vérifie si les horodatages des fichiers sur le serveur correspondent aux vôtres$N$Ndevrait <em>théoriquement</em> attraper la plupart des téléchargements inachevés / corrompus, mais n'est pas un substitut à un passage de vérification avec turbo désactivé par la suite\">date-chk", + + "cut_u2sz": "taille (en MiB) de chaque morceau de téléversement; des grosse valeurs vont mieux passer si la distance entre le serveur et vous est trés grande. Si vous avez une connection trés instable, essayer de plus petites valeurs", + + "cut_flag": "s'assurer qu'un seul onglet est entrain de mettre un fichier en ligne a la fois $N -- les autres onglets doivent avoir cette option activé aussi $N -- affecte seulement les onglets qui sont sur le même domaine", + + "cut_az": "mettre en ligne les fichiers dans l'ordre alphabétique, plutôt que le plus petit fichier en premier$N$Nl'ordre alphabétique peut rendre la lecture plus douce sur pour les yeux si quelque chose s'est mal passé sur le serveur, mais cela rend le téléversement légèrement plus lent sur fibre / LAN", + + "cut_nag": "recevoir une notification via l'OS quand un téléversement finit$N(seulement si le navigateur ou l'onglet n'est pas actif)", + "cut_sfx": "alerte audible quand le téléversement finit$N(seulement si le navigateur ou l'onglet n'est pas actif)", + + "cut_mt": "utiliser le calcul de somme de contrôle multithreadé pour accelerer le processus$N$Ncela utilise des web-workers et nécessite$Nplus de RAM (jusqu'à 512 MiB supplémentaires)$N$NCela rend https 30% plus rapide, http 4.5x plus rapide\">mt", + + "cut_wasm": "utiliser wasm au lieu du hachage intégré du navigateur; améliore la vitesse sur les navigateurs basés sur chrome mais augmente la charge CPU, et de nombreuses anciennes versions de chrome ont des bugs qui font que le navigateur consomme toute la RAM et plante si cela est activé\">wasm", + + "cft_text": "text favicon (laisser vide et rafraîchir pour désactiver)", + "cft_fg": "couleur de premier plan", + "cft_bg": "couleur d'arrière-plan", + + "cdt_lim": "nombre maximum de fichiers à afficher dans un dossier", + "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.", + + "tt_entree": "afficher le panneau de navigation (arborescence des dossiers)$NHotkey: B", + "tt_detree": "afficher le fil d’Ariane$NHotkey: B", + "tt_visdir": "faire défiler jusqu'au dossier sélectionné", + "tt_ftree": "basculer l'arborescence des dossiers / fichiers texte$NHotkey: V", + "tt_pdock": "afficher les dossiers parents dans un panneau ancré en haut", + "tt_dynt": "croissance automatique à mesure que l'arborescence s'étend", + "tt_wrap": "retour à la ligne", + "tt_hover": "révéler les lignes débordantes au survol$N( interrompt le défilement à moins que le curseur de la souris ne soit dans la gouttière gauche )", + + "ml_pmode": "à la fin du dossier…", + "ml_btns": "cmds", + "ml_tcode": "transcoder", + "ml_tcode2": "transcoder vers", + "ml_tint": "teinte", + "ml_eq": "égaliseur audio", + "ml_drc": "compresseur de plage dynamique", + + "mt_loop": "répéter en boucle une musique\">🔁", + "mt_one": "stopper après une musique\">1️⃣", + "mt_shuf": "mélanger les musiques dans chaque dossiers\">🔀", + "mt_aplay": "jouer automatiquement si le lien utilisé pour accéder au serveur a un song-ID $N$N, désactiver cela arrêtera également la mise à jour de l'URL de la page avec les song-IDs lors de la lecture de la musique, pour éviter la lecture automatique si ces paramètres sont perdus mais que l'URL reste\">a▶", + "mt_preload": "commencer à charger la prochaine chanson près de la fin pour une lecture sans interruption\">preload", + "mt_prescan": "explorer le dossier suivant avant la dernière musique$Nne finisse, pour garder le navigateur content$Npour qu'il n'arrête pas la lecture\">nav", + "mt_fullpre": "essayer de pré-charger la musique entière;$N✅ activer en cas de connection instable,$N❌ désactiver en revanche sur une connection lente va probablement être mieux\">full", + "mt_fau": "sur téléphone, empêche la musique de s'arrêter de jouer si la prochaine n'est pas pré-chargée assez rapidement (peut rendre l'affichage des tags buggé)\">☕️", + "mt_waves": "barre de progression en spectrograme:$Nmontrer l'amplitude audio dans la miniature\">~s", + "mt_npclip": "montrer les boutons pour copier le morceau en cours de lecture\">/np", + "mt_m3u_c": "montrer les boutons pour copier les$morceaux sélectionnées en tant qu'entrées de playlist m3u8\">📻", + "mt_octl": "intégration os (touches de raccourci multimédia / osd)\">os-ctl", + "mt_oseek": "permettre la recherche via l'intégration os$N$Nremarque : sur certains appareils (iPhones),$Ncela remplace le bouton de la chanson suivante\">seek", + "mt_oscv": "montrer la couverture de l'album dans l'osd\">art", + "mt_follow": "garder la piste en cours défilée dans la vue\">🎯", + "mt_compact": "contrôles compacts\">⟎", + "mt_uncache": "effacer le cache  (essayez ceci si votre navigateur a mis en cache$Nun copie défectueuse d'une chanson, ce qui empêche sa lecture)\">uncache", + "mt_mloop": "lire en boucle le dossier ouvert\">🔁 loop", + "mt_mnext": "charger le dossier suivant et continuer\">📂 next", + "mt_mstop": "arrêter la lecture\">⏸ stop", + "mt_cflac": "convertir flac / wav en {0}\">flac", + "mt_caac": "convertir aac / m4a en {0}\">aac", + "mt_coth": "convertir tout les autres (pas mp3) en {0}\">oth", + "mt_c2opus": "meilleur choix pour PC fixe, PC portable, android\">opus", + "mt_c2owa": "opus-weba, pour iOS 17.5 et supérieur\">owa", + "mt_c2caf": "opus-caf, pour iOS 11 à 17\">caf", + "mt_c2mp3": "utilisez ceci sur des appareils très anciens\">mp3", + "mt_c2flac": "meilleure qualité sonore, mais téléchargements énormes\">flac", + "mt_c2wav": "lecture non compressée (encore plus gros)\">wav", + "mt_c2ok": "bien, bon choix", + "mt_c2nd": "ce n'est pas le format de sortie recommandé pour votre appareil, mais ça devrait aller", + "mt_c2ng": "votre appareil ne semble pas prendre en charge ce format de sortie, mais essayons quand même", + "mt_xowa": "il y a des bugs dans iOS qui empeche d'avoir une lecture en ariere plan en utilisant ce format; utilisez caf ou mp3 à la place", + "mt_tint": "niveau d’arrière-plan (0–100) de la barre de progression$Npour rendre la mise en mémoire tampon moins gênante", + "mt_eq": "active l'égaliseur et le contrôle de gain;$N$Nboost <code>0</code> = volume standard 100% (non modifié)$N$Nwidth <code>1  </code> = stéréo standard (non modifié)$Nwidth <code>0.5</code> = 50% de crossfeed gauche-droite$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = suppression vocale :^)$N$Nl'activation de l'égaliseur rend les albums gapless entièrement gapless, alors laissez-le activé avec toutes les valeurs à zéro (sauf largeur = 1) si vous vous en souciez", + "mt_drc": "active le compresseur de plage dynamique (aplanisseur de volume / brickwaller); activera également l'EQ pour équilibrer les choses, donc définissez tous les champs EQ sauf 'width' sur 0 si vous ne le voulez pas$N$Ndiminue le volume de l'audio au-dessus de THRESHOLD dB; pour chaque RATIO dB au-delà de THRESHOLD, il y a 1 dB de sortie, donc des valeurs par défaut de tresh -24 et ratio 12 signifient qu'il ne devrait jamais être plus fort que -22 dB et qu'il est sûr d'augmenter le boost de l'égaliseur à 0.8, ou même 1.8 avec ATK 0 et un énorme RLS comme 90 (ne fonctionne que dans firefox; RLS est max 1 dans les autres navigateurs)$N$N(voir wikipedia, ils expliquent cela beaucoup mieux)", + + "mb_play": "lecture", + "mm_hashplay": "lire ce fichier audio ?", + "mm_m3u": "appuyez sur <code>Entrée/OK</code> pour lire\nappuyez sur <code>Échap/Annuler</code> pour modifier", + "mp_breq": "nécessite firefox 82+ ou chrome 73+ ou iOS 15+", + "mm_bload": "chargement en cours…", + "mm_bconv": "conversion en {0}, veuillez patienter…", + "mm_opusen": "votre navigateur ne peut pas lire les fichiers aac / m4a ;\nle transcodage en opus est maintenant activé", + "mm_playerr": "échec de la lecture : ", + "mm_eabrt": "La tentative de lecture a été annulée", + "mm_enet": "Votre connexion internet est instable ou inexistante", + "mm_edec": "Ce fichier est supposément corrompu??", + "mm_esupp": "Votre navigateur ne comprend pas ce format audio", + "mm_eunk": "Erreur inconnue", + "mm_e404": "Impossible de lire l'audio ; erreur 404 : fichier introuvable.", + "mm_e403": "Impossible de lire l'audio ; erreur 403 : accès refusé.\n\nEssayez d'appuyer sur F5 pour recharger, peut-être que vous avez été déconnecté", + "mm_e500": "Impossible de lire l'audio ; erreur 500 : vérifiez les journaux du serveur.", + "mm_e5xx": "Impossible de lire l'audio ; erreur serveur ", + "mm_nof": "Pas d'autres fichiers audio trouvés par ici", + "mm_prescan": "En recherche d'une autre musique à lire…", + "mm_scank": "Prochaine musique trouvée :", + "mm_uncache": "cache vidé ; toutes les chansons seront retéléchargées lors de la prochaine lecture", + "mm_hnf": "cette chanson n'existe plus", + + "im_hnf": "cette image n'existe plus", + + "f_empty": 'ce dossier est vide', + "f_chide": 'ceci va cacher les colonnes «{0}»\n\ntu peut les réafficher dans les options', + "f_bigtxt": "ce fichier fait {0} MiB -- tu veut vraiment le voir en tant que texte ?", + "f_bigtxt2": "voir seulement la fin du fichier à la place ? ceci activera aussi le suivi en temps réel, affichant les nouvelles lignes de texte au fur et à mesure", + "fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>', + "fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>', + "f_anota": "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_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 <code>.PARTIAL</code>. 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 <code>.PARTIAL</code> à la place, ce qui donnera presque certainement des données corrompues.", + + "ft_paste": "coller {0} éléments$NHotkey: ctrl-V", + "fr_eperm": 'impossible de renommer:\n vous n\'avez pas la permission “move” dans ce dossier', + "fd_eperm": 'impossible de supprimer:\nvous n\'avez pas la permission “delete” dans ce dossier', + "fc_eperm": 'impossible de couper:\nvous n\'avez pas la permission “move” dans ce dossier', + "fp_eperm": 'impossible de coller:\nvous n\'avez pas la permission “write” dans ce dossier', + "fr_emore": "sélectionnez au moins un élément à renommer", + "fd_emore": "sélectionnez au moins un élément à supprimer", + "fc_emore": "sélectionnez au moins un élément à couper", + "fcp_emore": "sélectionnez au moins un élément à copier dans le presse-papiers", + + "fs_sc": "partager le dossier dans lequel vous vous trouvez", + "fs_ss": "partager les fichiers sélectionnés", + "fs_just1d": "vous ne pouvez pas sélectionner plus d'un dossier,\nou mélanger des fichiers et des dossiers dans une seule sélection", + "fs_abrt": "❌ abandonner", + "fs_rand": "🎲 nom.aleatoire", + "fs_go": "✅ créer partage", + "fs_name": "nom", + "fs_src": "source", + "fs_pwd": "mdp", + "fs_exp": "expiration", + "fs_tmin": "min", + "fs_thrs": "heures", + "fs_tdays": "jours", + "fs_never": "éternel", + "fs_pname": "nom de lien optionnel ; sera aléatoire si vide", + "fs_tsrc": "le fichier ou le dossier à partager", + "fs_ppwd": "mot de passe optionnel", + "fs_w8": "création du partage…", + "fs_ok": "appuyez sur <code>Entrée/OK</code> pour le Presse-papiers\nappuyez sur <code>Échap/Annuler</code> pour fermer", + + "frt_dec": "peut potentiellement réparer certaines instances de noms de fichiers cassés\">url-decode", + "frt_rst": "réinitialiser les noms de fichiers modifiés à leurs originaux\">↺ reset", + "frt_abrt": "abandonner et fermer cette fenêtre\">❌ cancel", + "frb_apply": "APPLIQUER RENOMMER", + "fr_adv": "renommage par lot / métadonnées / motif\">advanced", + "fr_case": "regex sensible à la casse\">case", + "fr_win": "noms windows-safe; remplacer <code><>:"\\|?*</code> par des caractères japonais en pleine largeur\">win", + "fr_slash": "remplacer <code>/</code> par un caractère qui ne provoque pas la création de nouveaux dossiers\">no /", + "fr_re": "modèle de recherche regex à appliquer aux noms de fichiers originaux ; les groupes capturés peuvent être référencés dans le champ de format ci-dessous comme <code>(1)</code> et <code>(2)</code> et ainsi de suite", + "fr_fmt": "inspiré par foobar2000 : $N<code>(title)</code> est remplacé par le titre de la chanson, $N<code>[(artist) - ](title)</code> saute [cette] partie si l'artiste est vide, $N<code>$lpad((tn),2,0)</code> remplit le numéro de piste à 2 chiffres", + "fr_pdel": "supprimer", + "fr_pnew": "enregistrer sous", + "fr_pname": "donnez un nom pour le nouveau preset", + "fr_aborted": "abandonné", + "fr_lold": "ancien nom", + "fr_lnew": "nouveau nom", + "fr_tags": "tags pour les fichier selectioné (lecture-seule, juste pour référence):", + "fr_busy": "renomage de {0} items…\n\n{1}", + "fr_efail": "renomage a échoué:\n", + "fr_nchg": "{0} des nouveaux noms ont été modifiés en raison de <code>win</code> et/ou <code>no /</code>\n\nOK pour continuer avec ces nouveaux noms modifiés ?", + + "fd_ok": "suppression réussie", + "fd_err": "impossible de supprimer:\n", + "fd_none": "rien n'a été supprimé ; peut-être bloqué par la configuration du serveur (xbd) ?", + "fd_busy": "suppression de {0} éléments…\n\n{1}", + "fd_warn1": "SUPPRIMER ces {0} éléments ?", + "fd_warn2": "<b>Dernière chance !</b> Impossible de revenir en arrière. Supprimer ?", + + "fc_ok": "couper {0} éléments", + "fc_warn": 'couper {0} éléments\n\nmais : seul <b>cet</b> onglets peut les coller\n(puisque la sélection est si absolument massive)', + + "fcc_ok": "copié {0} éléments dans le presse-papiers", + "fcc_warn": 'copié {0} éléments dans le presse-papiers\n\nmais : seul <b>cet</b> onglet peut les coller\n(puisque la sélection est si absolument massive)', + + "fp_apply": "utiliser ces noms", + "fp_ecut": "en premier, coupez ou copiez quelques fichiers / dossiers à coller / déplacer\n\nnote: vous pouvez couper / coller a travers different onglets", + "fp_ename": "{0} éléments ne peuvent pas être déplacés ici parceque leurs noms sont déjà pris. Donnez leurs un nouveau nom ci-dessous pour continuer, ou laissez les vides pour les sauter:", + "fcp_ename": "{0} éléments ne peuvent pas être copiés ici parce que les noms sont déjà pris. Donnez-leur un nouveau nom ci-dessous pour continuer, ou laissez-les vides pour les sauter :", + "fp_emore": "il reste encore des collisions de noms de fichiers à corriger", + "fp_ok": "déplacement OK", + "fcp_ok": "copie OK", + "fp_busy": "déplacement de {0} éléments…\n\n{1}", + "fcp_busy": "copie de {0} éléments…\n\n{1}", + "fp_abrt": "abandon en cours...", //m + "fp_err": "deplacement échoué:\n", + "fcp_err": "copie échouée:\n", + "fp_confirm": "déplacer ces {0} éléments ici ?", + "fcp_confirm": "copier ces {0} éléments ici ?", + "fp_etab": 'lecture du presse-papier venant d\'un autre onglet échoué', + "fp_name": "téléversement d'un fichier de votre apareil. Donnez lui un nom:", + "fp_both_m": '<h6>choisisez ce qu\'il faut coller</h6><code>Entrer</code> = Déplacer {0} fichiers de «{1}»\n<code>ESC</code> = Téléverser {2} fichiers de votre appareil', + "fcp_both_m": '<h6>choisissez ce qu\'il faut coller</h6><code>Entrer</code> = Copier {0} fichiers de «{1}»\n<code>ESC</code> = Téléverser {2} fichiers de votre appareil', + "fp_both_b": '<a href="#" id="modal-ok">Déplacer</a><a href="#" id="modal-ng">Téléverser</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Copier</a><a href="#" id="modal-ng">Téléverser</a>', + + "mk_noname": "entrez un nom dans le champ de texte à gauche avant de faire ça :p", + + "tv_load": "Chargement du document texte:\n\n{0}\n\n{1}% ({2} de {3} MiB chargés)", + "tv_xe1": "impossible de charger le fichier texte:\n\nerreur", + "tv_xe2": "404, fichier introuvable", + "tv_lst": "liste des fichiers texte dans", + "tvt_close": "retour a la vue de dossier$NHotkey: M (ou Échap)\">❌ fermer", + "tvt_dl": "télécharger ce fichier$NHotkey: Y\">💾 télécharger", + "tvt_prev": "montrer le document précédent$NHotkey: i\">⬆ précédent", + "tvt_next": "montrer le document suivant$NHotkey: K\">⬇ suivant", + "tvt_sel": "sélectionner le fichier   ( pour couper / copier / supprimer / … )$NHotkey: S\">sel", + "tvt_edit": "ouvrir le fichier dans l'éditeur de texte$NHotkey: E\">✏️ modifier", + "tvt_tail": "surveiller le fichier pour les changements; montrer les nouvelles lignes en temps réel\">📡 suivre", + "tvt_wrap": "retour à la ligne\">↵", + "tvt_atail": "ancrer le défilement au fond de la page\">⚓", + "tvt_ctail": "décoder les couleurs du terminal (ansi escape codes)\">🌈", + "tvt_ntail": "limite de défilement en arrière (combien d'octets de texte à garder chargé)", + + "m3u_add1": "musique ajoutée à la playlist m3u", + "m3u_addn": "{0} musiques ajoutées à la playlist m3u", + "m3u_clip": "la playlist m3u est maintenant copiée dans le presse-papier\n\nvous devriez créer un nouveau fichier texte nommé par exemple playlist.m3u et coller la playlist dans ce fichier ; cela la rendra lisible en tant que playlist", + + "gt_vau": "ne pas voir les vidéos, juste jouer l'audio\">🎧", + "gt_msel": "activer la séléction de fichiers ; ctrl-clic sur un fichier pour override écraser$N$N<em>quand actif : double-cliquer sur un fichier / dossier pour l'ouvrir</em>$N$NHotkey: S\">multiséléction", + "gt_crop": "rogner les miniatures au centre\">rogner", + "gt_3x": "miniatures haute résolution\">3x", + "gt_zoom": "zoomer", + "gt_chop": "rogner", + "gt_sort": "trier par", + "gt_name": "nom", + "gt_sz": "taille", + "gt_ts": "date", + "gt_ext": "type", + "gt_c1": "tronquer les noms de fichiers (montrer moins)", + "gt_c2": "tronquer les noms de fichiers (montrer plus)", + + "sm_w8": "recherche…", + "sm_prev": "les résultats de recherche ci-dessous proviennent d'une requête précédente:\n ", + "sl_close": "fermer les résultats de recherche", + "sl_hits": "affichage de {0} résultats", + "sl_moar": "chercher plus", + + "s_sz": "taille", + "s_dt": "date", + "s_rd": "chemin", + "s_fn": "nom", + "s_ta": "tags", + "s_ua": "up@", + "s_ad": "adv.", + "s_s1": "minimum MiB", + "s_s2": "maximum MiB", + "s_d1": "min. iso8601", + "s_d2": "max. iso8601", + "s_u1": "téléverser après", + "s_u2": "et/ou avant", + "s_r1": "le chemin contient   (séparé par des espaces)", + "s_f1": "le nom contient   (négation avec -nope)", + "s_t1": "les tags contiennent   (^=début, fin=$)", + "s_a1": "propriétés de métadonnées spécifiques", + + "md_eshow": "impossible d'afficher le rendu ", + "md_off": "[📜<em>readme</em>] disabled in [⚙️] -- document caché", + + "badreply": "Échec de l'analyse de la réponse du serveur", + + "xhr403": "403: Accès refusé\n\nessayez d'appuyer sur F5, peut-être que vous avez été déconnecté", + "xhr0": "inconnu (vous avez probablement perdu la connexion au serveur, ou le serveur est hors ligne)", + "cf_ok": "désolé pour cela -- la protection DD" + wah + "oS a été déclenché\n\nles choses devraient reprendre dans environ 30 secondes\n\nsi rien ne se passe, appuyez sur F5 pour recharger la page", + "tl_xe1": "impossible de lister les sous-dossiers:\n\nerreur ", + "tl_xe2": "404: Dossier introuvable", + "fl_xe1": "impossible de lister les fichiers dans le dossier:\n\nerreur ", + "fl_xe2": "404: Dossier introuvable", + "fd_xe1": "impossible de créer le sous-dossier:\n\nerreur ", + "fd_xe2": "404: Dossier parent introuvable", + "fsm_xe1": "impossible d'envoyer le message:\n\nerreur ", + "fsm_xe2": "404: Dossier parent introuvable", + "fu_xe1": "échec du chargement de la liste des unpost du serveur:\n\nerreur ", + "fu_xe2": "404: Fichier introuvable??", + + "fz_tar": "fichier gnu-tar non compressé (linux / mac)", + "fz_pax": "tar au format pax non compressé (plus lent)", + "fz_targz": "gnu-tar avec compression gzip niveau 3$N$Ncela est généralement très lent, donc$Nutilisez plutôt tar non compressé", + "fz_tarxz": "gnu-tar avec compression xz niveau 1$N$Ncela est généralement très lent, donc$Nutilisez plutôt tar non compressé", + "fz_zip8": "zip avec noms de fichiers utf8 (peut être instable sur windows 7 et versions antérieures)", + "fz_zipd": "zip avec noms de fichiers cp437 traditionnels, pour les très anciens logiciels", + "fz_zipc": "cp437 avec crc32 calculé tôt,$Nfor MS-DOS PKZIP v2.04g (octobre 1993)$N(prend plus de temps à charger avant que le téléchargement ne commence)", + + "un_m1": "vous pouvez supprimer vos téléchargements récents (ou annuler ceux en cours) ci-dessous", + "un_upd": "rafraîchir", + "un_m4": "ou partager les fichiers visibles ci-dessous:", + "un_ulist": "montrer", + "un_ucopy": "copier", + "un_flt": "filtre optionnel:  l'URL doit contenir", + "un_fclr": "effacer le filtre", + "un_derr": 'échec de l\'unpost-delete:\n', + "un_f5": 'quelque chose a cassé, veuillez essayer de rafraîchir ou d\'appuyer sur F5', + "un_uf5": "désolé mais vous devez rafraîchir la page (par exemple en appuyant sur F5 ou CTRL-R) avant que ce téléchargement puisse être annulé", + "un_nou": '<b>warning:</b> serveur trop occupé pour afficher les téléversements non finis; cliquez sur le lien "rafraîchir" dans un instant', + "un_noc": '<b>warning:</b> unpost des fichiers entièrement téléchargés n\'est pas activé/permis dans la configuration du serveur', + "un_max": "affichage des 2000 premiers fichiers (utilisez le filtre)", + "un_avail": "{0} téléchargements récents peuvent être supprimés<br />{1} ceux en cours peuvent être annulés", + "un_m2": "triés par date de téléchargement; les plus récents en premier:", + "un_no1": "sike! aucun téléchargement n'est suffisamment récent", + "un_no2": "sike! aucun téléchargement correspondant à ce filtre n'est suffisamment récent", + "un_next": "supprimer les {0} fichiers suivants ci-dessous", + "un_abrt": "abandonner", + "un_del": "supprimer", + "un_m3": "chargement de vos téléchargements récents…", + "un_busy": "suppression de {0} fichiers…", + "un_clip": "{0} liens copiés dans le presse-papiers", + + "u_https1": "vous devriez", + "u_https2": "passer à https", + "u_https3": "pour de meilleure performances", + "u_ancient": 'votre navigateur est impressionnamment ancien -- vous devriez peut-être <a href="#" onclick="goto(\'bup\')">utiliser bup à la place</a>', + "u_nowork": "nécessite firefox 53+ ou chrome 57+ ou iOS 11+", + "tail_2old": "nécessite firefox 105+ ou chrome 71+ ou iOS 14.5+", + "u_nodrop": 'votre navigateur est trop ancien pour le téléversement par glisser-déposer', + "u_notdir": "ce n'est pas un dossier!\n\nvotre navigateur est trop ancien,\nveuillez essayer le glisser-déposer à la place", + "u_uri": "pour glisser-déposer des images depuis d'autres fenêtres de navigateur,\nveuillez les déposer sur le gros bouton de téléversement", + "u_enpot": 'passer à <a href="#">l\'interface utilisateur potato</a> (peut améliorer la vitesse de téléversement)', + "u_depot": 'passer à <a href="#">l\'interface utilisateur fancy</a> (peut réduire la vitesse de téléversement)', + "u_gotpot": 'passage à l\'interface utilisateur potato pour une vitesse de téléversement améliorée,\n\nn\'hésitez pas à revenir en arrière si ça ne vous plaît pas !', + "u_pott": "<p>fichiers:   <b>{0}</b> fini,   <b>{1}</b> échoué,   <b>{2}</b> en cours,   <b>{3}</b> en attente</p>", + "u_ever": "ceci est le téléverseur de base ; up2k nécessite au moins chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'ceci est le téléverseur de base; <a href="#" id="u2yea">up2k</a> est meilleur', + "u_uput": 'optimiser pour la vitesse (ignorer la somme de contrôle)', + "u_ewrite": 'vous n\'avez pas accès en écriture à ce dossier', + "u_eread": 'vous n\'avez pas accès en lecture à ce dossier', + "u_enoi": 'la recherche de fichiers n\'est pas activée dans la configuration du serveur', + "u_enoow": "l'écrasage ne fonctionnera pas ici; besoin de permissions de suppression", + "u_badf": 'Ces {0} fichiers (sur {1} au total) ont été ignorés, probablement en raison de permissions système de fichiers:\n\n', + "u_blankf": 'Ces {0} fichiers (sur {1} au total) sont vides; les téléverser quand même ?\n\n', + "u_applef": 'Ces {0} fichiers (sur {1} au total) sont probablement indésirables;\nAppuyez sur <code>OK/Enter</code> pour IGNORER les fichiers suivants,\nAppuyez sur <code>Annuler/Échap</code> pour NE PAS exclure, et TÉLÉVERSER ceux-ci également:\n\n', + "u_just1": '\nPeut-être que cela fonctionne mieux si vous sélectionnez juste un fichier', + "u_ff_many": "si vous utilisez <b>Linux / MacOS / Android,</b> alors ce nombre de fichiers <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>peut</em> faire planter Firefox!</a>\nSi cela se produit, veuillez réessayer (ou utiliser Chrome).", + "u_up_life": "Ce téléversement va être supprimé du serveur\n{0} après son achèvement", + "u_asku": 'téléverser ces {0} fichiers vers <code>{1}</code>', + "u_unpt": "vous pouvez défaire / supprimer ce téléversement en utilisant le 🧯 en haut à gauche", + "u_bigtab": 'sur le point d\'afficher {0} fichiers\n\ncela peut faire planter votre navigateur, êtes-vous sûr ?', + "u_scan": 'Analyse des fichiers…', + "u_dirstuck": 'l\'itérateur de répertoire est bloqué en essayant d\'accéder aux {0} éléments suivants ; il sera ignoré :', + "u_etadone": 'Terminé ({0}, {1} fichiers)', + "u_etaprep": '(préparation au téléversement)', + "u_hashdone": 'calcul de la somme de contrôle terminé', + "u_hashing": 'calcul de la somme de contrôle', + "u_hs": 'établissement d\'une liaison…', + "u_started": "les fichiers sont maintenant en cours de téléversement ; voir [🚀]", + "u_dupdefer": "dupliqué ; sera traité après tous les autres fichiers", + "u_actx": "cliquez sur ce texte pour éviter la perte de<br />performance lors du passage à d'autres fenêtres/onglets", + "u_fixed": "OK!  Résolu 👍", + "u_cuerr": "echec du téléversement du morceau {0} de {1};\nprobablement inoffensif, poursuite\n\nfichier : {2}", + "u_cuerr2": "le serveur a rejeté le téléversement (morceau {0} de {1});\nréessaiera plus tard\n\nfichier : {2}\n\nerreur ", + "u_ehstmp": "réessaiera ; voir en bas à droite", + "u_ehsfin": "le serveur a rejeté la demande de finalisation du téléversement ; nouvelle tentative…", + "u_ehssrch": "le serveur a rejeté la demande d'effectuer une recherche ; nouvelle tentative…", + "u_ehsinit": "le serveur a rejeté la demande d'initier le téléversement ; nouvelle tentative…", + "u_eneths": "erreur réseau lors de l'exécution de l'initialisation du téléversement ; nouvelle tentative…", + "u_enethd": "erreur réseau lors du test de l'existence de la cible ; nouvelle tentative…", + "u_cbusy": "attente que le serveur nous fasse à nouveau confiance après un problème réseau…", + "u_ehsdf": "le serveur est à court d'espace disque !\n\nil va continuer de réessayer, au cas où quelqu'un\nlibérerait suffisamment d'espace pour continuer", + "u_emtleak1": "il semble que votre navigateur web ait une fuite de mémoire ;\nveuillez", + "u_emtleak2": ' <a href="{0}">passer à https (recommandé)</a> ou ', + "u_emtleak3": ' ', + "u_emtleakc": 'essayez la solution suivante:\n<ul><li>appuyez sur <code>F5</code> pour rafraîchir la page</li><li>ensuite désactivez le bouton  <code>mt</code>  dans les  <code>⚙️ paramètres</code></li><li>et réessayez ce téléversement</li></ul>Les téléversements seront un peu plus lents, mais tant pis.\nDésolé pour le dérangement !\n\nPS : chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">a un correctif</a> pour cela', + "u_emtleakf": 'essayez la solution suivante:\n<ul><li>appuyez sur <code>F5</code> pour rafraîchir la page</li><li>ensuite activez <code>🥔</code> (pomme de terre) dans l\'interface de téléversement</li><li>et réessayez ce téléversement</li></ul>\nPS : firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">aura probablement un correctif</a> à un moment donné', + "u_s404": "pas trouvé sur le serveur", + "u_expl": "expliquer", + "u_maxconn": "la plupart des navigateur limite ceci à 6, mais firefox vous permet de l'augmenter avec <code>connections-per-server</code> dans <code>about:config</code>", + "u_tu": '<p class="warn">WARNING: turbo enclenché, <span> le client peut ne pas détecter et reprendre les téléversements incomplets ; voir l\'info-bulle du bouton turbo</span></p>', + "u_ts": '<p class="warn">WARNING: turbo enclenché, <span> les résultats de recherche peuvent être incorrects ; voir l\'info-bulle du bouton turbo</span></p>', + "u_turbo_c": "turbo est désactivé dans la configuration du serveur", + "u_turbo_g": "désactivation de turbo car vous n'avez pas de\nprivilèges de listing de répertoires dans ce volume", + "u_life_cfg": 'suppression automatique après <input id="lifem" p="60" /> min (ou <input id="lifeh" p="3600" /> heures)', + "u_life_est": 'le téléversement sera supprimé <span id="lifew" tt="local time">---</span>', + "u_life_max": 'ce dossier impose une\ndurée de vie maximale de {0}', + "u_unp_ok": 'unpost est autorisé pour {0}', + "u_unp_ng": 'unpost ne sera PAS autorisé', + "ue_ro": 'votre accès à ce dossier est en lecture seule\n\n', + "ue_nl": 'vous n\'êtes actuellement pas connecté', + "ue_la": 'vous êtes actuellement connecté en tant que "{0}"', + "ue_sr": 'vous êtes actuellement en mode recherche de fichiers\n\nchangez en mode téléversement en cliquant sur la loupe 🔎 (à côté du grand bouton RECHERCHER), et essayez de téléverser à nouveau\n\ndésolé', + "ue_ta": 'essayez de téléverser à nouveau, cela devrait fonctionner maintenant', + "ue_ab": "ce fichier a déjà été téléversé dans un autre dossier, et ce téléversement doit être terminé avant que le fichier puisse être téléversé ailleurs.\n\nVous pouvez annuler et oublier le téléversement initial en utilisant le bouton 🧯 en haut à gauche.", + "ur_1uo": "OK: Fichier téléversé avec succès", + "ur_auo": "OK: Tous les {0} fichiers téléversés avec succès", + "ur_1so": "OK: Fichier trouvé sur le serveur", + "ur_aso": "OK: Tous les {0} fichiers trouvés sur le serveur", + "ur_1un": "Échec du téléversement, désolé", + "ur_aun": "Tous les {0} téléversements ont échoué, désolé", + "ur_1sn": "Fichier NON trouvé sur le serveur", + "ur_asn": "Les {0} fichiers n'ont PAS ÉTÉ trouvés sur le serveur", + "ur_um": "Terminé;\n{0} téléversements OK,\n{1} téléversements échoués, désolé", + "ur_sm": "Terminé;\n{0} fichiers trouvés sur le serveur,\n{1} fichiers NON trouvés sur le serveur", + + "lang_set": "rafraîchir pour que les changements prennent effet ?", + }, + "grc": { + "tt": "Ελληνικά", + + "cols": { + "c": "κουμπιά ενεργειών", + "dur": "διάρκεια", + "q": "ποιότητα / bitrate", + "Ac": "κωδικοποιητής ήχου", + "Vc": "κωδικοποιητής βίντεο", + "Fmt": "μορφή / container", + "Ahash": "checksum ήχου", + "Vhash": "checksum βίντεο", + "Res": "ανάλυση", + "T": "τύπος αρχείου", + "aq": "ποιότητα ήχου / bitrate", + "vq": "ποιότητα βίντεο / bitrate", + "pixfmt": "subsampling / δομή εικονοστοιχείων", + "resw": "οριζόντια ανάλυση", + "resh": "κάθετη ανάλυση", + "chs": "κανάλια ήχου", + "hz": "συχνότητα δειγματοληψίας" + }, + + "hks": [ + [ + "διάφορα", + ["ESC", "κλείσιμο διαφόρων λειτουργιών"], + + "διαχειριστής αρχείων", + ["G", "εναλλαγή λίστας / πλέγματος"], + ["T", "εναλλαγή μικρογραφιών / εικονιδίων"], + ["⇧ A/D", "μέγεθος μικρογραφιών"], + ["ctrl-K", "διαγραφή επιλεγμένων"], + ["ctrl-X", "αποκοπή επιλογής στο πρόχειρο"], + ["ctrl-C", "αντιγραφή επιλογής στο πρόχειρο"], + ["ctrl-V", "επικόλληση (μετακίνηση/αντιγραφή) εδώ"], + ["Y", "λήψη επιλεγμένων"], + ["F2", "μετονομασία επιλεγμένων"], + + "λίστα αρχείων", + ["space", "εναλλαγή επιλογής αρχείου"], + ["↑/↓", "μετακίνηση δείκτη επιλογής"], + ["ctrl ↑/↓", "μετακίνηση δείκτη και προβολής"], + ["⇧ ↑/↓", "επιλογή προηγούμενου/επόμενου αρχείου"], + ["ctrl-A", "επιλογή όλων των αρχείων / φακέλων"] + ], [ + "πλοήγηση", + ["B", "εναλλαγή σε καρτέλες διαδρομών / δέντρο διαδρομών"], + ["I/K", "προηγούμενος/επόμενος φάκελος"], + ["M", "γονικός φάκελος (ή σμίκρυνση τρέχοντος)"], + ["V", "εναλλαγή φακέλων / δέντρο αρχείων κειμένου"], + ["A/D", "μέγεθος πίνακα πλοήγησης"] + ], [ + "μουσική", + ["J/L", "προηγούμενο/επόμενο τραγούδι"], + ["U/O", "μετάβαση 10δευτ πίσω/μπροστά"], + ["0..9", "μετάβαση στο 0%..90%"], + ["P", "αναπαραγωγή/παύση (ξεκινάει κιόλας)"], + ["S", "επιλογή αναπαραγόμενου τραγουδιού"], + ["Y", "λήψη τραγουδιού"] + ], [ + "εικόνες", + ["J/L, ←/→", "προηγούμενη/επόμενη εικόνα"], + ["Home/End", "πρώτη/τελευταία εικόνα"], + ["F", "πλήρης οθόνη"], + ["R", "περιστροφή δεξιόστροφα"], + ["⇧ R", "περιστροφή αριστερόστροφα"], + ["S", "επιλογή εικόνας"], + ["Y", "λήψη εικόνας"] + ], [ + "βίντεο", + ["U/O", "μετάβαση 10δευτ πίσω/μπροστά"], + ["P/K/Space", "αναπαραγωγή/παύση"], + ["C", "συνέχεια στο επόμενο"], + ["V", "επανάληψη"], + ["M", "σίγαση"], + ["[ και ]", "ορισμός διαστήματος επανάληψης"] + ], [ + "αρχεία κειμένου", + ["I/K", "προηγούμενο/επόμενο αρχείο"], + ["M", "κλείσιμο αρχείου"], + ["E", "επεξεργασία αρχείου"], + ["S", "επιλογή αρχείου (για αποκοπή/αντιγραφή/μετονομασία)"] + ] + ], + + "m_ok": "Εντάξει", + "m_ng": "Άκυρο", + + "enable": "Ενεργοποίηση", + "danger": "ΚΙΝΔΥΝΟΣ", + "clipped": "αντιγράφηκε στο πρόχειρο", + + "ht_s1": "δευτερόλεπτο", + "ht_s2": "δευτερόλεπτα", + "ht_m1": "λεπτό", + "ht_m2": "λεπτά", + "ht_h1": "ώρα", + "ht_h2": "ώρες", + "ht_d1": "μέρα", + "ht_d2": "μέρες", + "ht_and": " και ", + + "goh": "πίνακας ελέγχου", + "gop": 'προηγούμενος φάκελος στο ίδιο επίπεδο">προηγούμενο', + "gou": 'γονικός φάκελος">πάνω', + "gon": 'επόμενος φάκελος">επόμενο', + "logout": "Αποσύνδεση ", + "login": "Σύνδεση", //m + "access": " πρόσβαση", + "ot_close": "κλείσιμο υπομενού", + "ot_search": "αναζήτηση αρχείων με βάση χαρακτηριστικά, διαδρομή / όνομα, μουσικά tags ή οποιονδήποτε συνδυασμό$N$N<code>foo bar</code> = πρέπει να περιέχει και τα «foo» και «bar»,$N<code>foo -bar</code> = πρέπει να περιέχει το «foo» αλλά όχι το «bar»,$N<code>^yana .opus$</code> = να ξεκινά με «yana» και να είναι αρχείο «opus»$N<code>"try unite"</code> = να περιέχει ακριβώς «try unite»$N$Nη μορφή ημερομηνίας είναι iso-8601, όπως$N<code>2009-12-31</code> ή <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: διαγραφή πρόσφατων μεταφορτώσεων ή ακύρωση ανολοκλήρωτων", + "ot_bup": "bup: βασικός uploader, υποστηρίζει μέχρι και netscape 4.0", + "ot_mkdir": "mkdir: δημιουργία νέου φακέλου", + "ot_md": "new-md: δημιουργία νέου markdown εγγράφου", + "ot_msg": "msg: αποστολή μηνύματος στο server log", + "ot_mp": "επιλογές media player", + "ot_cfg": "επιλογές ρυθμίσεων", + "ot_u2i": 'up2k: ανέβασε αρχεία (αν έχεις δικαίωμα εγγραφής) ή ενεργοποίησε τη λειτουργία αναζήτησης για να δεις αν υπάρχουν ήδη στο server$N$Nοι μεταφορτώσεις συνεχίζονται αν διακοπούν, είναι πολυνηματικές και διατηρούν τις χρονοσφραγίδες, αλλά καταναλώνουν περισσότερο CPU από τον [🎈]  (βασικός uploader)<br /><br />κατά τη διάρκεια της μεταφόρτωσης, αυτό το εικονίδιο δείχνει την πρόοδό της!', + "ot_u2w": 'up2k: ανέβασε αρχεία με υποστήριξη συνέχισης (κλείσε τον browser και ρίξε τα ίδια αρχεία ξανά μετά)$N$Nπολυνηματικό, διατηρεί τις χρονοσφραγίδες, αλλά καταναλώνει περισσότερο CPU από τον [🎈]  (βασικός uploader)<br /><br />κατά τη διάρκεια της μεταφόρτωσης, αυτό το εικονίδιο δείχνει την πρόοδό της!', + "ot_noie": 'Χρησιμοποίησε Chrome / Firefox / Edge', + + "ab_mkdir": "δημιουργία φακέλου", + "ab_mkdoc": "νέο markdown έγγραφο", + "ab_msg": "στείλε μήνυμα στο server log", + + "ay_path": "πήγαινε σε φακέλους", + "ay_files": "πήγαινε σε αρχεία", + + "wt_ren": "μετονομασία επιλεγμένων$NΣυντόμευση: F2", + "wt_del": "διαγραφή επιλεγμένων$NΣυντόμευση: ctrl-K", + "wt_cut": "αποκοπή επιλεγμένων <small>(και επικόλληση αλλού)</small>$NΣυντόμευση: ctrl-X", + "wt_cpy": "αντιγραφή επιλεγμένων στο πρόχειρο$N(για επικόλληση αλλού)$NΣυντόμευση: ctrl-C", + "wt_pst": "επικόλληση αποκομμένων / αντεγραμμένων$NΣυντόμευση: ctrl-V", + "wt_selall": "επιλογή όλων$NΣυντόμευση: ctrl-A (με το αρχείο επιλεγμένο)", + "wt_selinv": "αντιστροφή επιλογής", + "wt_zip1": "κατέβασμα φακέλου ως συμπιεσμένο αρχείο", + "wt_selzip": "κατέβασμα επιλογής ως συμπιεσμένο αρχείο", + "wt_seldl": "κατέβασμα επιλογής ως μεμονωμένα αρχεία$NΣυντόμευση: Y", + "wt_npirc": "αντιγραφή πληροφοριών τραγουδιού σε μορφή irc", + "wt_nptxt": "αντιγραφή πληροφοριών τραγουδιού ως κείμενο", + "wt_m3ua": "προσθήκη σε m3u λίστα αναπαραγωγής (μετά πάτησε <code>📻αντιγραφή</code>)", + "wt_m3uc": "αντιγραφή m3u λίστας αναπαραγωγής στο πρόχειρο", + "wt_grid": "εναλλαγή πλέγματος / λίστας$NΣυντόμευση: G", + "wt_prev": "προηγούμενο κομμάτι$NΣυντόμευση: J", + "wt_play": "αναπαραγωγή / παύση$NΣυντόμευση: P", + "wt_next": "επόμενο κομμάτι$NΣυντόμευση: L", + + "ul_par": "παράλληλες μεταφορτώσεις:", + "ut_rand": "τυχαιοποίηση ονομάτων αρχείων", + "ut_u2ts": "αντιγραφή της τελευταίας τροποποιημένης χρονοσφραγίδας αλλαγής$Nαπό το σύστημά σου στον server\">📅", + "ut_ow": "αντικατάσταση σε ήδη υπάρχοντα αρχεία του server?$N🛡️: ποτέ (θα δημιουργηθεί νέο όνομα)$N🕒: αν το αρχείο του server είναι παλαιότερο$N♻️: πάντα να αντικαθίστανται αν διαφέρουν", + "ut_mt": "συνέχιση υπολογισμού hash για άλλα αρχεία κατά τη μεταφόρτωση$N$Nαπενεργοποίησέ το αν η CPU ή ο δίσκος σου ζορίζονται", + "ut_ask": 'επιβεβαίωση πριν ξεκινήσει η μεταφόρτωση">💭', + "ut_pot": "βελτίωση ταχύτητας μεταφόρτωσης σε αργές συσκευές$Nμε απλοποίηση του UI", + "ut_srch": "μην ανεβάζεις, έλεγξε αν τα αρχεία$Nυπάρχουν ήδη στον server (ψάχνει σε όλους τους φακέλους που έχεις πρόσβαση)", + "ut_par": "κάνε παύση στις μεταφορτώσεις βάζοντάς το 0$N$Nαύξησε το αν έχεις αργή/μεγάλη καθυστέρηση σύνδεσης$N$Nκράτα το 1 σε LAN ή αν ο server έχει αργό δίσκο", + "ul_btn": "ρίξε αρχεία / φακέλους<br>εδώ (ή κάνε κλικ σε μένα)", + "ul_btnu": "Μ Ε Τ Α Φ Ο Ρ Τ Ω Σ Η", + "ul_btns": "Α Ν Α Ζ Η Τ Η Σ Η", + + "ul_hash": "υπολογισμός hash", + "ul_send": "αποστολή", + "ul_done": "ολοκληρώθηκε", + "ul_idle1": "καμία μεταφόρτωση στην ουρά για την ώρα", + "ut_etah": "μέση ταχύτητα <em>υπολογισμού hash</em> και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + "ut_etau": "μέση ταχύτητα <em>μεταφόρτωσης</em> και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + "ut_etat": "μέση <em>συνολική</em> ταχύτητα και εκτίμηση χρόνου μέχρι την ολοκλήρωση", + + "uct_ok": "ολοκληρώθηκε επιτυχώς", + "uct_ng": "no-good: απέτυχε / απορρίφθηκε / δεν βρέθηκε", + "uct_done": "ολοκληρωμένα και αποτυχημένα", + "uct_bz": "κάνει hash ή μεταφορτώνει", + "uct_q": "σε αναμονή, εκκρεμεί", + + "utl_name": "όνομα αρχείου", + "utl_ulist": "λίστα", + "utl_ucopy": "αντιγραφή", + "utl_links": "σύνδεσμοι", + "utl_stat": "κατάσταση", + "utl_prog": "πρόοδος", + + // keep short: + "utl_404": "404", + "utl_err": "ΣΦΑΛΜΑ", + "utl_oserr": "ΣΦ-ΛΕ", + "utl_found": "βρέθηκε", + "utl_defer": "αναβολή", + "utl_yolo": "YOLO", + "utl_done": "έγινε", + + "ul_flagblk": "τα αρχεία προστέθηκαν στην ουρά</b><br>αλλά υπάρχει άλλη ενεργή μεταφόρτωση σε άλλη καρτέλα,<br>οπότε περίμενε να τελειώσει αυτό πρώτα", + "ul_btnlk": "ο διακομιστής έχει κλειδώσει αυτήν την επιλογή σε αυτήν την κατάσταση", + + "udt_up": "Μεταφόρτωση", + "udt_srch": "Αναζήτηση", + "udt_drop": "ρίξ' το εδώ", + + "u_nav_m": '<h6>οκ, τι έχουμε εδώ;</h6><code>Enter</code> = Αρχεία (ένα ή περισσότερα)\n<code>ESC</code> = Ένας φάκελος (μαζί με υποφακέλους)', + "u_nav_b": '<a href="#" id="modal-ok">Αρχεία</a><a href="#" id="modal-ng">Ένας φάκελος</a>', + + "cl_opts": "διακόπτες", + "cl_themes": "θέμα", + "cl_langs": "γλώσσα", + "cl_ziptype": "λήψη φακέλου", + "cl_uopts": "διακόπτες μεταφόρτωσης", + "cl_favico": "favicon", + "cl_bigdir": "μεγάλοι φάκελοι", + "cl_hsort": "#ταξινόμηση", + "cl_keytype": "σημείωση πλήκτρων", + "cl_hiddenc": "κρυφές στήλες", + "cl_hidec": "κρύψε", + "cl_reset": "επανεκκίνηση", + "cl_hpick": "πάτησε στις κεφαλίδες στηλών για να τις κρύψεις στον πίνακα παρακάτω", + "cl_hcancel": "η απόκρυψη στηλών ακυρώθηκε", + + "ct_grid": '田 το πλέγμα', + "ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων', + "ct_thumb": 'σε προβολή πλέγματος, εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες', + "ct_csel": 'χρησιμοποίησε CTRL και SHIFT για επιλογή αρχείων σε προβολή πλέγματος">επιλογή', + "ct_ihop": 'όταν η προβολή εικόνων κλείνει, κάνε scroll στο τελευταίο προβαλλόμενο αρχείο">g⮯', + "ct_dots": 'εμφάνιση κρυφών αρχείων (αν το επιτρέπει ο server)">dotfiles', + "ct_qdel": 'όταν διαγράφεις αρχεία, ζήτα επιβεβαίωση μόνο μία φορά">γρήγορη διαγραφή', + "ct_dir1st": 'ταξινόμηση φακέλων πριν από τα αρχεία">📁 πρώτα', + "ct_nsort": 'φυσική ταξινόμηση (για ονόματα αρχείων με αριθμούς στην αρχή)">φυσική ταξινόμηση', + "ct_utc": 'εμφάνιση όλων των ημερομηνιών σε UTC">UTC', + "ct_readme": 'εμφάνιση README.md στις λίστες φακέλων">📜 πληροφορίες', + "ct_idxh": 'εμφάνιση index.html αντί για λίστα φακέλων">html', + "ct_sbars": 'εμφάνιση μπαρών κύλισης">⟊', + + "cut_umod": "αν το αρχείο υπάρχει ήδη στον server, ενημέρωσέ το με την τελευταία χρονοσφραγίδα τροποποίησης για να ταιριάζει με το τοπικό αρχείο (απαιτεί δικαιώματα εγγραφής+διαγραφής)\">re📅", + + "cut_turbo": "το κουμπί yolo, πιθανόν να ΜΗΝ ΘΕΛΕΙΣ να το ενεργοποιήσεις:$N$Nχρησιμοποίησέ το αν μεταφορτώνεις πολλά αρχεία και χρειάστηκε να ξαναρχίσεις, και θες να συνεχίσεις τη μεταφότρωση όσο το δυνατόν πιο γρήγορα$N$Nαντικαθιστά τον έλεγχο hash με απλό <em>"έχει το ίδιο μέγεθος αρχείου στον server?"</em> οπότε αν το περιεχόμενο είναι διαφορετικό, ΔΕΝ θα ανέβει$N$Nπρέπει να το κλείσεις όταν τελειώσει η μεταφόρτωση και μετά να "μεταφορτώσεις" πάλι τα ίδια αρχεία για να τα επιβεβαιώσει το τοπικό σου πρόγραμμα\">turbo", + + "cut_datechk": "δεν επηρεάζει τίποτα εκτός αν το turbo είναι ενεργοποιημένο$N$Nμειώνει λίγο τον παράγοντα yolo; ελέγχει αν οι χρονοσφραγίδες στο διακομιστή ταιριάζουν με τα δικά σου$N$Nπιάνει <em>θεωρητικά</em> τις περισσότερες μισοτελειωμένες/κατεστραμμένες μεταφορτώσεις, αλλά δεν αντικαθιστά τον έλεγχο με το turbo απενεργοποιημένο μετέπειτα\">έλεγχος ημερομηνίας", + + "cut_u2sz": "μέγεθος (σε MiB) κάθε κομματιού μεταφόρτωσης; μεγάλες τιμές λειτουργούν καλύτερα σε μεγαλύτερες αποστάσεις διακομιστή-πελάτη. Δοκίμασε μικρές τιμές σε πολύ άστατες συνδέσεις", + + "cut_flag": "εξασφαλίζει ότι μόνο μία καρτέλα μεταφορτώνει κάθε φορά $N -- οι άλλες καρτέλες πρέπει να το έχουν κι αυτές ενεργό $N -- επηρεάζει μόνο τις καρτέλες που βρίσκονται στο ίδιο διεύθυνση", + + "cut_az": "μεταφόρτωσε τα αρχεία αλφαβητικά, αντί για το μικρότερο αρχείο, πρώτα$N$Nη αλφαβητική σειρά βοηθά να καταλάβεις αν κάτι χάλασε στο διακομιστή αλλά κάνει το ανέβασμα λίγο πιο αργό σε fiber / LAN", + + "cut_nag": "ειδοποίηση λειτουργικού συστήματος όταν τελειώσει η μεταφόρτωση$N(μόνο αν ο browser ή η καρτέλα δεν είναι ενεργά)", + "cut_sfx": "ηχητική ειδοποίηση όταν τελειώσει η μεταφόρτωση$N(μόνο αν ο browser ή η καρτέλα δεν είναι ενεργά)", + + "cut_mt": "χρησιμοποίησε multithreading για να επιταχύνεις το hashing των αρχείων$N$Nχρησιμοποιεί web-workers και χρειάζεται$Nπερισσότερη RAM (μέχρι 512 MiB επιπλέον)$N$Nκάνει το https 30% πιο γρήγορο, το http 4.5x πιο γρήγορο\">mt", + + "cut_wasm": "χρησιμοποίησε wasm αντί για τον ενσωματωμένο hasher του browser; βελτιώνει την ταχύτητα σε chrome-based browsers αλλά αυξάνει το φορτίο της CPU, και παλιές εκδόσεις chrome έχουν bugs που κάνουν το browser να τρώει όλη τη RAM και να κρασάρει αν ενεργοποιηθεί\">wasm", + + "cft_text": "κείμενο favicon (κενό και ανανέωση για απενεργοποίηση)", + "cft_fg": "χρώμα προσκηνίου", + "cft_bg": "χρώμα παρασκηνίου", + + "cdt_lim": "μέγιστος αριθμός αρχείων προς εμφάνιση σε ένα φάκελο", + "cdt_ask": "όταν φτάνεις στο τέλος,$Nαντί να φορτώσει περισσότερα αρχεία,$Nρωτά τι να κάνει", + "cdt_hsort": "πόσους κανόνες ταξινόμησης (<code>,sorthref</code>) να συμπεριλάβει σε URLs πολυμέσων. Αν το βάλεις 0 αγνοεί και κανόνες ταξινόμησης στους συνδέσμους πολυμέσων", + + "tt_entree": "εμφάνιση navpane (δέντρο διαδρομών)$NΠλήκτρο συντόμευσης: B", + "tt_detree": "εμφάνιση breadcrumbs (καρτέλες διαδρομών)$NΠλήκτρο συντόμευσης: B", + "tt_visdir": "κύλιση στον επιλεγμένο φάκελο", + "tt_ftree": "εναλλαγή δέντρου διαδρομών / αρχείων κειμένου$NΠλήκτρο συντόμευσης: V", + "tt_pdock": "εμφάνιση γονικών φακέλων σε σταθερή μπάρα επάνω", + "tt_dynt": "αυτόματη επέκταση καθώς επεκτείνεται το δέντρο διαδρομών", + "tt_wrap": "αναδίπλωση λέξεων", + "tt_hover": "αποκάλυψη των γραμμών που ξεπερνούν το πλάτος με το ποντίκι πάνω τους$N( σπάει το scroll εκτός αν το ποντίκι $N  είναι στην αριστερή στήλη )", + + "ml_pmode": "στο τέλος του φακέλου...", + "ml_btns": "εντολές", + "ml_tcode": "μετακωδικοποίηση", + "ml_tcode2": "μετακωδικοποίηση σε", + "ml_tint": "φίλτρο χρώματος", + "ml_eq": "ισοσταθμιστής ήχου", + "ml_drc": "συμπιεστής δυναμικής εμβέλειας", + + "mt_loop": "επανάληψη ενός τραγουδιού\">🔁", + "mt_one": "σταμάτα μετά από ένα τραγούδι\">1️⃣", + "mt_shuf": "τυχαία σειρά τραγουδιών σε κάθε φάκελο\">🔀", + "mt_aplay": "αυτόματη αναπαραγωγή αν υπάρχει song-ID στη διεύθυνση που μπήκες στο διακομιστή$N$Nη απενεργοποίηση αυτού, σταματά το URL από το να ενημερώνεται με τα song-ID ενώ παίζει η μουσική για να αποτραπεί η αυτόματη αναπαραγωγή αν χαθούν αυτές οι ρυθμίσεις αλλά το URL παραμείνει το ίδιο\">a▶", + "mt_preload": "ξεκίνα τη φόρτωση του επόμενου τραγουδιού κοντά στο τέλος για συνεχόμενη ακρόαση\">προφόρτωση", + "mt_prescan": "πήγαινε στον επόμενο φάκελο πριν τελειώσει το τελευταίο τραγούδι$Nγια να μη σταματήσει το πρόγραμμα περιήγησης να παίζει μουσική\">nav", + "mt_fullpre": "προσπάθησε να προφορτώσεις ολόκληρο το τραγούδι;$N✅ ενεργό σε <b>αναξιόπιστες</b> συνδέσεις,$N❌ πιθανότατα απενεργοποιημένο σε αργές συνδέσεις\">πλήρες", + "mt_fau": "σε κινητά, πρόλαβε να μην σταματήσει η μουσική αν το επόμενο τραγούδι δεν προφορτώθηκε γρήγορα (μπορεί να προκαλέσει πρόβλημα στην εμφάνιση των ετικετών)\">☕️", + "mt_waves": "γραμμή αναζήτησης κυματομορφής:$Nεμφάνιση έντασης ήχου στην μπάρα αναζήτησης\">~s", + "mt_npclip": "εμφάνισε κουμπιά για αντιγραφή του τρέχοντος τραγουδιού\">/np", + "mt_m3u_c": "εμφάνισε κουμπιά για αντιγραφή των$Nεπιλεγμένων τραγουδιών ως καταχωρήσεις λίστας m3u8\">📻", + "mt_octl": "ενσωμάτωση στο λειτουργικό σύστημα (σηντομεύσεις πλήκτρων πολυμέσων / osd)\">έλεγχος-OS", + "mt_oseek": "επιτρέπει την αναζήτηση μέσω ενσωμάτωσης του λειτουργικού συστήματος$N$Nσημείωση: σε μερικές συσκευές (iPhones),$Nαντικαθιστά το κουμπί επόμενου τραγουδιού\">αναζήτηση", + "mt_oscv": "εμφάνιση εξωφύλλου άλμπουμ σε osd\">εξώφυλλο", + "mt_follow": "κρατά το τρέχον κομμάτι ορατό κατά την κύλιση\">🎯", + "mt_compact": "συμπαγή κουμπιά ελέγχου\">⟎", + "mt_uncache": "καθάρισε την προσωρινή μνήμη  (δοκίμασε αυτό αν ο browser έχει αποθηκεύσει$Nχαλασμένο αντίγραφο τραγουδιού και αρνείται να παίξει)\">εκκαθάριση", + "mt_mloop": "τυχαία αναπαραγωγή στον ανοικτό φάκελο\">🔁 τυχαία αναπαραγωγή", + "mt_mnext": "φόρτωση επόμενου φακέλου και συνέχιση\">📂 επόμενο", + "mt_mstop": "σταμάτησε την αναπαραγωγή\">⏸ σταμάτημα", + "mt_cflac": "μετατροπή flac / wav σε {0}\">flac", + "mt_caac": "μετατροπή aac / m4a σε {0}\">aac", + "mt_coth": "μετατροπή όλων των άλλων (εκτός των mp3) σε {0}\">άλλο", + "mt_c2opus": "καλύτερη επιλογή για desktop, laptop, android\">opus", + "mt_c2owa": "opus-weba, για iOS 17.5 και νεότερα\">owa", + "mt_c2caf": "opus-caf, για iOS 11 έως 17\">caf", + "mt_c2mp3": "χρησιμοποίησε αυτό σε πολύ παλιές συσκευές\">mp3", + "mt_c2flac": "βέλτιστη ποιότητα ήχου αλλά τεράστιο αρχείο για μεταφόρτωση\">flac", + "mt_c2wav": "ασυμπίεστη αναπαραγωγή (ακόμα μεγαλύτερο αρχείο)\">wav", + "mt_c2ok": "μια χαρά, σοφή επιλογή", + "mt_c2nd": "δεν είναι η προτεινόμενη μορφή εξόδου για τη συσκευή σου, αλλά αυτό είναι ok", + "mt_c2ng": "η συσκευή σου φαίνεται να μην υποστηρίζει αυτήν τη μορφή εξόδου, αλλά ας το δοκιμάσουμε ούτως ή άλλως", + "mt_xowa": "υπάρχουν bugs σε iOS που εμποδίζουν την αναπαραγωγή στο παρασκήνιο με αυτήν τη μορφή· χρησιμοποίησε caf ή mp3 αντ’ αυτού", + "mt_tint": "επίπεδο φόντου (0-100) στην μπάρα αναζήτησης$Nγια να κάνεις το buffering λιγότερο ενοχλητικό", + "mt_eq": "ενεργοποιεί τον ισοσταθμιστή και τον έλεγχο ενίσχυσης;$N$Nενίσχυση <code>0</code> = στάνταρ 100% ένταση (απαράλλαχτη)$N$Nεύρος <code>1  </code> = στάνταρ στερεοφωνικό (απαράλλαχτο)$Nεύρος <code>0.5</code> = 50% αριστερά-δεξιά μίξη ήχου$Nεύρος <code>0  </code> = μονοφωνικό$N$Nενίσχυση <code>-0.8</code> & εύρος <code>10</code> = αφαίρεση φωνής :^)$N$Nη ενεργοποίηση του ισοσταθμιστή κάνει τα άλμπουμ χωρίς κενά, να παίζουν χωρίς καθόλου κενά, οπότε άφησέ το ενεργό με όλες τις τιμές στο μηδέν (εκτός από εύρος = 1) αν σε νοιάζει", + "mt_drc": "ενεργοποιεί τον συμπιεστή δυναμικής εμβέλειας (εξομάλυνση έντασης / ακραία συμπίεση έντασης); θα ενεργοποιήσει και τον ισοσταθμιστή για να ισορροπήσει τον ήχο, οπότε βάλε όλα τα πεδία ισοσταθμιστή εκτός από το 'εύρος' στο 0 αν δεν το θες$N$Nχαμηλώνει την ένταση του ήχου πάνω από το όριο (THRESHOLD) dB; για κάθε RATIO dB πέρα από το όριο υπάρχει 1 dB εξόδου, οπότε οι προεπιλεγμένες τιμές όριο -24 και 'λόγος' 12 σημαίνουν ότι δεν θα ξεπεράσει ποτέ τα -22 dB και είναι ασφαλές να αυξήσεις την ενίσχυση ισοσταθμιστή σε 0.8, ή και 1.8 με ATK 0 και μεγάλο RLS όπως 90 (δουλεύει μόνο σε firefox· το RLS είναι max 1 σε άλλους browsers)$N$N(δες wikipedia, το εξηγούν καλύτερα)", + + "mb_play": "παίξε", + "mm_hashplay": "να παίξω αυτό το αρχείο ήχου;", + "mm_m3u": "πάτα <code>Enter/Εντάξει</code> για Αναπαραγωγή\nπάτα <code>ESC/Άκυρο</code> για Επεξεργασία", + "mp_breq": "χρειάζεται firefox 82+ ή chrome 73+ ή iOS 15+", + "mm_bload": "φορτώνει...", + "mm_bconv": "μετατροπή σε {0}, περίμενε...", + "mm_opusen": "ο browser σου δεν παίζει αρχεία aac / m4a;\nη μετατροπή σε opus είναι τώρα ενεργή", + "mm_playerr": "η αναπαραγωγή, απέτυχε: ", + "mm_eabrt": "Η προσπάθεια αναπαραγωγής ακυρώθηκε", + "mm_enet": "Η σύνδεση του ίντερνέτ σου είναι χάλια", + "mm_edec": "Το αρχείο αυτό είναι μάλλον κατεστραμμένο;;", + "mm_esupp": "Ο browser σου δεν καταλαβαίνει αυτή τη μορφή ήχου", + "mm_eunk": "Άγνωστο σφάλμα", + "mm_e404": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 404: Το αρχείο δεν βρέθηκε.", + "mm_e403": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 403: Άρνηση πρόσβασης.\n\nΔοκίμασε F5 για επαναφόρτωση, ίσως να έχεις αποσυνδεθεί", + "mm_e500": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα 500: Έλεγξε τα logs του διακομιστή.", + "mm_e5xx": "Αδύνατη η αναπαραγωγή ήχου; σφάλμα διακομιστή", + "mm_nof": "δεν βρέθηκαν άλλα αρχεία ήχου τριγύρω", + "mm_prescan": "Αναζήτηση μουσικής για επόμενο τραγούδι...", + "mm_scank": "Βρέθηκε το επόμενο τραγούδι:", + "mm_uncache": "κρυφή μνήμη καθαρίστηκε· όλα τα τραγούδια θα ξανακατεβούν στην επόμενη αναπαραγωγή", + "mm_hnf": "το τραγούδι αυτό πλέον δεν υπάρχει", + + "im_hnf": "η εικόνα αυτή πλέον δεν υπάρχει", + + "f_empty": "αυτός ο φάκελος είναι άδειος", + "f_chide": "αυτό θα κρύψει τη στήλη «{0}»\n\nμπορείς να εμφανίσεις τις στήλες από τις ρυθμίσεις", + "f_bigtxt": "αυτό το αρχείο είναι {0} MiB σε μέγεθος — σίγουρα θέλεις να το δεις ως κείμενο;", + "f_bigtxt2": "να δεις μόνο το τέλος του αρχείου αντί για όλο; αυτό ενεργοποιεί και το following/tailing, που δείχνει νέες γραμμές που προστίθενται ζωντανά", + "fbd_more": '<div id="blazy">εμφανίζονται <code>{0}</code> από <code>{1}</code> αρχεία; <a href="#" id="bd_more">δείξε {2}</a> ή <a href="#" id="bd_all">δείξε τα όλα</a></div>', + "fbd_all": '<div id="blazy">εμφανίζονται <code>{0}</code> από <code>{1}</code> αρχεία; <a href="#" id="bd_all">δείξε όλα</a></div>', + "f_anota": "μόνο {0} από τα {1} αντικείμενα επιλέχθηκαν;\nγια να επιλέξεις ολόκληρο το φάκελο, κύλησε πρώτα μέχρι κάτω", + + "f_dls": 'οι σύνδεσμοι αρχείων στον τρέχοντα φάκελο έχουν\nμετατραπεί σε συνδέσμους λήψης', + + "f_partial": "Για να κατεβάσεις με ασφάλεια ένα αρχείο που ανεβαίνει, κλίκαρε το αρχείο με το ίδιο όνομα, αλλά χωρίς την κατάληξη <code>.PARTIAL</code>. Πάτα Άκυρο ή Escape για να σταματήσεις.\n\nΠάτα Εντάξει / Enter αν αγνοείς την προειδοποίηση και κατέβασε το <code>.PARTIAL</code> αρχείο, που σχεδόν σίγουρα θα είναι κατεστραμμένο.", + + "ft_paste": "επικόλλησε {0} αντικείμενα$NΠλήκτρο συντόμευσης: ctrl-V", + "fr_eperm": 'δεν μπορεί να μετονομαστεί:\nδεν έχεις δικαίωμα “μετακίνησης” σε αυτόν το φάκελο', + "fd_eperm": 'δεν μπορεί να διαγραφεί:\nδεν έχεις δικαίωμα “διαγραφής” σε αυτόν το φάκελο', + "fc_eperm": 'δεν μπορεί να κοπεί:\nδεν έχεις δικαίωμα “μετακίνησης” σε αυτόν το φάκελο', + "fp_eperm": 'δεν μπορεί να επικολληθεί:\nδεν έχεις δικαίωμα “εγγραφής” σε αυτόν το φάκελο', + "fr_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για μετονομασία", + "fd_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για διαγραφή", + "fc_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για αποκοπή", + "fcp_emore": "επίλεξε τουλάχιστον ένα αντικείμενο για αντιγραφή στο πρόχειρο", + + "fs_sc": "μοιράσου το φάκελο που βρίσκεσαι", + "fs_ss": "μοιράσου τα επιλεγμένα αρχεία", + "fs_just1d": "δεν μπορείς να επιλέξεις περισσότερους από έναν φακέλους,\nή να αναμείξεις αρχεία και φακέλους στην ίδια επιλογή", + "fs_abrt": "❌ ακύρωση", + "fs_rand": "🎲 τυχαίο όνομα", + "fs_go": "✅ δημιούργησε κοινή χρήση", + "fs_name": "όνομα", + "fs_src": "πηγή", + "fs_pwd": "κωδικός", + "fs_exp": "λήξη", + "fs_tmin": "λεπτά", + "fs_thrs": "ώρες", + "fs_tdays": "ημέρες", + "fs_never": "αιώνιο", + "fs_pname": "προαιρετικό όνομα συνδέσμου; αν είναι κενό, θα είναι τυχαίο", + "fs_tsrc": "το αρχείο ή ο φάκελος προς κοινή χρήση", + "fs_ppwd": "προαιρετικός κωδικός", + "fs_w8": "δημιουργία κοινής χρήσης...", + "fs_ok": "πάτα <code>Enter/Εντάξει</code> για Πρόχειρο\nπάτα <code>ESC/Άκυρο</code> για Κλείσιμο", + + "frt_dec": "μπορεί να διορθώσει μερικές περιπτώσεις κατεστραμμένων ονομάτων αρχείων\">αποκωδικοποίηση url", + "frt_rst": "επανέφερε τα ονόματα αρχείων στα αρχικά τους\">↺ επαναφορά", + "frt_abrt": "ακύρωσε και κλείσε αυτό το παράθυρο\">❌ ακύρωση", + "frb_apply": "ΕΦΑΡΜΟΓΗ ΜΕΤΟΝΟΜΑΣΙΑΣ", + "fr_adv": "μαζική / μεταδεδομένα / μετονομασία με πρότυπα\">προχωρημένη", + "fr_case": "regex με διάκριση πεζών/κεφαλαίων\">case", + "fr_win": "ασφαλή ονόματα για windows; αντικαθιστά <code><>:"\\|?*</code> με ιαπωνικούς χαρακτήρες πλήρους πλάτους\">win", + "fr_slash": "αντικαθίσταται <code>/</code> με χαρακτήρα που δεν δημιουργεί νέους φακέλους\">όχι /", + "fr_re": "μοτίβα αναζήτησης (regex) για αναζήτηση στα αρχικά ονόματα; τα καταγραφόμενα groups μπορούν να χρησιμοποιηθούν στο πεδίο μορφοποίησης παρακάτω όπως <code>(1)</code> και <code>(2)</code> και ούτω καθεξής", + "fr_fmt": "εμπνευσμένο από foobar2000:$N<code>(title)</code> αντικαθίσταται από τίτλο τραγουδιού,$N<code>[(artist) - ](title)</code> παραλείπει το [this] αν το artist είναι κενό$N<code>$lpad((tn),2,0)</code> γεμίζει τον αριθμό κομματιού σε 2 ψηφία", + "fr_pdel": "διαγραφή", + "fr_pnew": "αποθήκευση ως", + "fr_pname": "δώσε όνομα για τη νέα προεπιλογή", + "fr_aborted": "ακυρώθηκε", + "fr_lold": "παλιό όνομα", + "fr_lnew": "νέο όνομα", + "fr_tags": "ετικέτες για τα επιλεγμένα αρχεία (μόνο για ανάγνωση):", + "fr_busy": "μετονομασία {0} αντικειμένων...\n\n{1}", + "fr_efail": "αποτυχία μετονομασίας:\n", + "fr_nchg": "{0} από τα νέα ονόματα άλλαξαν λόγω <code>win</code> και/ή <code>όχι /</code>\n\nΕίναι ΟΚ να συνεχίσουμε με αυτά τα ονόματα;", + + "fd_ok": "διαγραφή OK", + "fd_err": "αποτυχία διαγραφής:\n", + "fd_none": "δεν διαγράφηκε τίποτα; ίσως μπλοκαρισμένο από τις ρυθμίσεις του διακομιστή (xbd);", + "fd_busy": "διαγραφή {0} αντικειμένων...\n\n{1}", + "fd_warn1": "ΔΙΑΓΡΑΦΗ αυτών των {0} αντικειμένων;", + "fd_warn2": "<b>Τελευταία ευκαιρία!</b> Δεν υπάρχει αναίρεση. Διαγραφή;", + + "fc_ok": "αποκοπή {0} αντικειμένων", + "fc_warn": 'αποκοπή {0} αντικειμένων\n\nαλλά: μόνο <b>αυτή</b> η καρτέλα browser μπορεί να τα επικολλήσει\n(λόγω πολύ μεγάλης επιλογής)', + + "fcc_ok": "αντιγράφηκαν {0} αντικείμενα στο πρόχειρο", + "fcc_warn": "αντιγράφηκαν {0} αντικείμενα στο πρόχειρο\n\nαλλά: μόνο <b>αυτή</b> η καρτέλα browser μπορεί να τα επικολλήσει\n(λόγω πολύ μεγάλης επιλογής)", + + "fp_apply": "χρησιμοποίησε αυτά τα ονόματα", + "fp_ecut": "πρώτα κάνε αποκοπή ή αντιγραφή κάποιων αρχείων / φακέλων για επικόλληση / μετακίνηση\n\nσημείωση: μπορείς να αποκόπτεις / επικολλάς ανάμεσα σε διαφορετικές καρτέλες browser", + "fp_ename": "τα {0} αντικείμενα δεν μπορούν να μετακινηθούν εδώ γιατί τα ονόματα υπάρχουν ήδη. Δώσε νέα ονόματα παρακάτω για να συνεχίσεις, ή άφησε κενό για να τα αγνοήσεις:", + "fcp_ename": "τα {0} αντικείμενα δεν μπορούν να αντιγραφούν εδώ γιατί τα ονόματα υπάρχουν ήδη. Δώσε νέα ονόματα παρακάτω για να συνεχίσεις, ή άφησε κενό για να τα αγνοήσεις:", + "fp_emore": "υπάρχουν ακόμα συγκρούσεις ονομάτων που πρέπει να διορθωθούν", + "fp_ok": "μετακίνηση OK", + "fcp_ok": "αντιγραφή OK", + "fp_busy": "μετακίνηση {0} αντικειμένων...\n\n{1}", + "fcp_busy": "αντιγραφή {0} αντικειμένων...\n\n{1}", + "fp_abrt": "γίνεται ακύρωση...", //m + "fp_err": "αποτυχία μετακίνησης:\n", + "fcp_err": "αποτυχία αντιγραφής:\n", + "fp_confirm": "να μετακινηθούν αυτά τα {0} αντικείμενα εδώ;", + "fcp_confirm": "να αντιγραφούν αυτά τα {0} αντικείμενα εδώ;", + "fp_etab": "αποτυχία ανάγνωσης πρόχειρου από άλλη καρτέλα browser", + "fp_name": "μεταφόρτωση αρχείου από τη συσκευή σου. Δώσε του όνομα:", + "fp_both_m": '<h6>διάλεξε τι θα επικολλήσεις</h6><code>Enter</code> = Μετακίνηση {0} αρχείων από «{1}»\n<code>ESC</code> = Μεταφόρτωση {2} αρχείων από τη συσκευή σου', + "fcp_both_m": '<h6>διάλεξε τι θα επικολλήσεις</h6><code>Enter</code> = Αντιγραφή {0} αρχείων από «{1}»\n<code>ESC</code> = Μεταφόρτωση {2} αρχείων από τη συσκευή σου', + "fp_both_b": '<a href="#" id="modal-ok">Μετακίνηση</a><a href="#" id="modal-ng">Μεταφόρτωση</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Αντιγραφή</a><a href="#" id="modal-ng">Μεταφόρτωση</a>', + + "mk_noname": "γράψε ένα όνομα στο πεδίο κειμένου αριστερά πριν το κάνεις :p", + + "tv_load": "Φόρτωση αρχείου κειμένου:\n\n{0}\n\n{1}% ({2} από {3} MiB φορτωμένα)", + "tv_xe1": "αδυναμία φόρτωσης αρχείου κειμένου:\n\nσφάλμα ", + "tv_xe2": "404, αρχείο δεν βρέθηκε", + "tv_lst": "λίστα αρχείων κειμένου σε", + "tvt_close": "επιστροφή στην προβολή φακέλου$NΣυντόμευση: M (ή Esc)\">❌ κλείσιμο", + "tvt_dl": "κατέβασε αυτό το αρχείο$NΣυντόμευση: Y\">💾 λήψη", + "tvt_prev": "προβολή προηγούμενου εγγράφου$NΣυντόμευση: i\">⬆ προηγούμενο", + "tvt_next": "προβολή επόμενου εγγράφου$NΣυντόμευση: K\">⬇ επόμενο", + "tvt_sel": "επέλεξε αρχείο   (για αποκοπή / αντιγραφή / διαγραφή / ...)$NΣυντόμευση: S\">επιλογή", + "tvt_edit": "άνοιγμα αρχείου στον επεξεργαστή κειμένου$NΣυντόμευση: E\">✏️ επεξεργασία", + "tvt_tail": "παρακολούθηση αρχείου για αλλαγές; εμφάνιση νέων γραμμών σε πραγματικό χρόνο\">📡 παρακολούθηση", + "tvt_wrap": "αναδίπλωση λέξεων\">↵", + "tvt_atail": "κλείδωμα κύλισης στο κάτω μέρος\">⚓", + "tvt_ctail": "αποκωδικοποίηση χρωμάτων τερματικού (ansi escape codes)\">🌈", + "tvt_ntail": "όριο κύλισης (πόσα bytes κειμένου να κρατούνται φορτωμένα)", + + "m3u_add1": "το τραγούδι προστέθηκε στη λίστα m3u", + "m3u_addn": "προστέθηκαν {0} τραγούδια στη λίστα m3u", + "m3u_clip": "η λίστα m3u αντιγράφηκε στο πρόχειρο\n\nπρέπει να φτιάξεις ένα νέο αρχείο κειμένου με όνομα η_λίστα_μου.m3u και να επικολλήσεις τη λίστα μέσα· αυτό θα το καταστήσει αναπαράξιμο", + + "gt_vau": "μην δείχνεις το βίντεο, παίξε μόνο τον ήχο\">🎧", + "gt_msel": "ενεργοποίηση επιλογής αρχείων; ctrl-κλικ σε αρχείο για παράκαμψη$N$N<em>όταν είναι ενεργό: διπλό κλικ σε αρχείο / φάκελο το ανοίγει</em>$N$NΣυντόμευση: S\">πολλαπλή επιλογή", + "gt_crop": "κεντραρισμένη περικοπή μικρογραφιών\">περικοπή", + "gt_3x": "μικρογραφίες υψηλής ανάλυσης\">3x", + "gt_zoom": "ζουμ", + "gt_chop": "κόψε", + "gt_sort": "ταξινόμηση κατά", + "gt_name": "όνομα", + "gt_sz": "μέγεθος", + "gt_ts": "ημερομηνία", + "gt_ext": "τύπος", + "gt_c1": "μεγαλύτερη περικοπή ονομάτων αρχείων (δείξε λιγότερα)", + "gt_c2": "μικρότερη περικοπή ονομάτων αρχείων (δείξε περισσότερα)", + + "sm_w8": "αναζήτηση...", + "sm_prev": "τα παρακάτω αποτελέσματα αναζήτησης προέρχονται από προηγούμενη αναζήτηση:\n ", + "sl_close": "κλείσιμο αποτελεσμάτων αναζήτησης", + "sl_hits": "εμφανίζονται {0} αποτελέσματα", + "sl_moar": "φόρτωσε περισσότερα", + + "s_sz": "μέγεθος", + "s_dt": "ημερομηνία", + "s_rd": "μονοπάτι", + "s_fn": "όνομα", + "s_ta": "ετικέτες", + "s_ua": "ανέβηκε@", + "s_ad": "προχωρ.", + "s_s1": "ελάχιστο σε MiB", + "s_s2": "μέγιστο σε MiB", + "s_d1": "ελάχιστο iso8601", + "s_d2": "μέγιστο iso8601", + "s_u1": "μεταφορτώθηκε αργότερα", + "s_u2": "και/ή πριν", + "s_r1": "το μονοπάτι περιέχει   (χωρισμένα με κενό)", + "s_f1": "το όνομα περιέχει   (άρνηση με -nope)", + "s_t1": "οι ετικέτες περιέχουν   (^=αρχή, τέλος=$)", + "s_a1": "συγκεκριμένες ιδιότητες μεταδεδομένων", + + "md_eshow": "δεν μπορεί να εμφανιστεί ", + "md_off": "[📜<em>readme</em>] απενεργοποιημένο στο [⚙️] -- κρυμμένο έγγραφο", + + "badreply": "Αποτυχία ανάλυσης απάντησης από το διακομιστή", + + "xhr403": "403: Πρόσβαση αρνήθηκε\n\nδοκίμασε το F5, ίσως αποσυνδέθηκες", + "xhr0": "άγνωστο (πιθανόν αποσύνδεση από το διακομιστή ή ο διακομιστής είναι εκτός σύνδεσης)", + "cf_ok": "συγγνώμη γι' αυτό -- η προστασία DD" + wah + "oS ενεργοποιήθηκε\n\nοι διαδικασίες θα συνεχιστούν σε περίπου 30 δευτερόλεπτα\n\nαν δεν γίνει τίποτα, πάτα F5 για επαναφόρτωση", + "tl_xe1": "αδύνατη η λίστα υποφακέλων:\n\nσφάλμα ", + "tl_xe2": "404: Ο φάκελος δεν βρέθηκε", + "fl_xe1": "αδύνατη η λίστα αρχείων σε φάκελο:\n\nσφάλμα ", + "fl_xe2": "404: Ο φάκελος δεν βρέθηκε", + "fd_xe1": "αδύνατη η δημιουργία υποφακέλου:\n\nσφάλμα ", + "fd_xe2": "404: Ο γονικός φάκελος δεν βρέθηκε", + "fsm_xe1": "αδύνατη η αποστολή μηνύματος:\n\nσφάλμα ", + "fsm_xe2": "404: Ο γονικός φάκελος δεν βρέθηκε", + "fu_xe1": "αποτυχία φόρτωσης λίστας unpost από το διακομιστή:\n\nσφάλμα ", + "fu_xe2": "404: Το αρχείο δεν βρέθηκε??", + + "fz_tar": "μη συμπιεσμένο αρχείο gnu-tar (linux / mac)", + "fz_pax": "μη συμπιεσμένο pax-format tar (πιο αργό)", + "fz_targz": "gnu-tar με συμπίεση gzip επίπεδο 3$N$Nσυνήθως πολύ αργό, οπότε$Nχρησιμοποίησε καλύτερα μη συμπιεσμένο tar", + "fz_tarxz": "gnu-tar με συμπίεση xz επίπεδο 1$N$Nσυνήθως πολύ αργό, οπότε$Nχρησιμοποίησε καλύτερα μη συμπιεσμένο tar", + "fz_zip8": "zip με ονόματα αρχείων utf8 (ίσως να κολλάει σε windows 7 και παλιότερα)", + "fz_zipd": "zip με παραδοσιακά ονόματα cp437, για πολύ παλιό λογισμικό", + "fz_zipc": "cp437 με crc32 υπολογισμένο νωρίτερα,$Nγια MS-DOS PKZIP v2.04g (οκτώβριος 1993)$N(παίρνει παραπάνω χρόνο πριν ξεκινήσει η μεταφόρτωση)", + + "un_m1": "μπορείς να διαγράψεις τα πρόσφατα αρχεία που μεταφόρτωσες (ή να ακυρώσεις τα μισοτελειωμένα) παρακάτω", + "un_upd": "ανανέωση", + "un_m4": "ή μοιράσου τα αρχεία που βλέπεις παρακάτω:", + "un_ulist": "εμφάνιση", + "un_ucopy": "αντιγραφή", + "un_flt": "προαιρετικό φίλτρο:  η διεύθυνση πρέπει να περιέχει", + "un_fclr": "καθαρισμός φίλτρου", + "un_derr": "αποτυχία διαγραφής unpost:\n", + "un_f5": "κάτι χάλασε, δοκίμασε την ανανέωση ή πάτα F5", + "un_uf5": "συγγνώμη αλλά πρέπει να ανανεώσεις τη σελίδα (πχ με F5 ή CTRL-R) πριν ακυρώσεις αυτήν την αποστολή", + "un_nou": "<b>προσοχή:</b> ο διακομιστής είναι πολύ φορτωμένος για να δείξει μισοτελειωμένες αποστολές· πάτα την ανανέωση, σε λίγο", + "un_noc": "<b>προσοχή:</b> το unpost των ολοκληρωμένων αρχείων δεν επιτρέπεται από τη ρύθμιση του διακομιστή", + "un_max": "εμφανίζονται τα πρώτα 2000 αρχεία (χρησιμοποίησε φίλτρο)", + "un_avail": "μπορείς να διαγράψεις {0} πρόσφατα αρχεία<br />μπορείς να ακυρώσεις {1} μισοτελειωμένες αποστολές", + "un_m2": "ταξινομημένα κατά χρόνο μεταφόρτωσης; τα πιο πρόσφατα πρώτα:", + "un_no1": "άκυρο! καμία μεταφόρτωση δεν είναι αρκετά πρόσφατη", + "un_no2": "άκυρο! καμία μεταφόρτωση με αυτό το φίλτρο δεν είναι αρκετά πρόσφατη", + "un_next": "διάγραψε τα επόμενα {0} αρχεία παρακάτω", + "un_abrt": "άκυρο", + "un_del": "διαγραφή", + "un_m3": "φορτώνω τις πρόσφατες μεταφορτώσεις σου...", + "un_busy": "διαγράφω {0} αρχεία...", + "un_clip": "αντιγράφηκαν {0} σύνδεσμοι στο πρόχειρο", + + "u_https1": "πρέπει", + "u_https2": "μετάβαση σε https", + "u_https3": "για καλύτερη απόδοση", + "u_ancient": 'ο browser σου είναι εντυπωσιακά απαρχαιωμένος — ίσως να <a href="#" onclick="goto(\'bup\')">χρησιμοποιήσεις το bup αντί γι\' αυτό</a>', + "u_nowork": "χρειάζεται firefox 53+ ή chrome 57+ ή iOS 11+", + "tail_2old": "χρειάζεται firefox 105+ ή chrome 71+ ή iOS 14.5+", + "u_nodrop": "ο browser σου είναι πολύ παλιός για drag&drop μεταφορτώσεις", + "u_notdir": "αυτός δεν είναι φάκελος!\n\nο browser σου είναι πολύ παλιός,\nδοκίμασε drag&drop αντ' αυτού", + "u_uri": "για να κάνεις drag&drop εικόνων από άλλα παράθυρα browser,\nρίξ' τες πάνω στο μεγάλο κουμπί μεταφόρτωσης", + "u_enpot": 'άλλαξε στο <a href="#">potato UI</a> (ίσως ανεβάζει πιο γρήγορα)', + "u_depot": 'άλλαξε στο <a href="#">fancy UI</a> (ίσως ανεβάζει πιο αργά)', + "u_gotpot": "αλλάζω στο potato UI για πιο γρήγορη μεταφόρτωση,\n\nμπορείς να το αλλάξεις πάλι αν θες!", + "u_pott": "<p>αρχεία:   <b>{0}</b> ολοκληρωμένα,   <b>{1}</b> αποτυχημένα,   <b>{2}</b> σε εξέλιξη,   <b>{3}</b> σε ουρά</p>", + "u_ever": "αυτός είναι ο βασικός uploader; το up2k θέλει τουλάχιστον<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'αυτός είναι ο βασικός uploader; το <a href="#" id="u2yea">up2k</a> είναι καλύτερο', + "u_uput": "βελτιστοποίηση για ταχύτητα (παράλειψη ελέγχου ακεραιότητας)", + "u_ewrite": "δεν έχεις δικαίωμα εγγραφής σε αυτόν τον φάκελο", + "u_eread": "δεν έχεις δικαίωμα ανάγνωσης σε αυτόν τον φάκελο", + "u_enoi": "η αναζήτηση αρχείων δεν είναι ενεργοποιημένη στο αρχείο ρυθμίσεων του διακομιστή", + "u_enoow": "δεν μπορείς να κάνεις αντικατάσταση εδώ· χρειάζεται δικαίωμα Διαγραφής", + "u_badf": "Αυτά τα {0} αρχεία (από {1} συνολικά) παραλείφθηκαν, πιθανώς λόγω δικαιωμάτων συστήματος αρχείων:\n\n", + "u_blankf": "Αυτά τα {0} αρχεία (από {1} συνολικά) είναι άδεια / κενά· να τα μεταφορτώσω έτσι κι αλλιώς;\n\n", + "u_applef": "Αυτά τα {0} αρχεία (από {1} συνολικά) πιθανώς δεν είναι επιθυμητά;\nΠάτα <code>Εντάξει/Enter</code> για ΝΑ ΑΓΝΟΗΘΟΥΝ τα παρακάτω αρχεία,\nΠάτα <code>Άκυρο/ESC</code> για ΝΑ ΜΗΝ ΑΠΟΚΛΕΙΣΤΟΥΝ και να ΜΕΤΑΦΟΡΤΩΘΟΎΝ κι αυτά:\n\n", + "u_just1": "\nΊσως δουλέψει καλύτερα αν επιλέξεις μόνο ένα αρχείο", + "u_ff_many": "αν χρησιμοποιείς <b>Linux / MacOS / Android,</b> τότε τόσα αρχεία <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>μπορεί</em> να κατάρρευση του Firefox!</a>\nαν γίνει αυτό, δοκίμασε ξανά (ή χρησιμοποίησε τον Chrome).", + "u_up_life": "Αυτή η μεταφόρτωση θα διαγραφεί από το διακομιστή\n{0} μετά την ολοκλήρωσή της", + "u_asku": "μεταφόρτωση αυτών των {0} αρχείων στο <code>{1}</code>", + "u_unpt": "μπορείς να αναιρέσεις / διαγράψεις αυτήν τη μεταφόρτωση χρησιμοποιώντας το 🧯, πάνω αριστερά", + "u_bigtab": "θα εμφανιστούν {0} αρχεία\n\nαυτό μπορεί να κάνει τον browser σου να κολλήσει, είσαι σίγουρος;", + "u_scan": "Σάρωση αρχείων...", + "u_dirstuck": "ο επεξεργαστής φακέλων κόλλησε προσπαθώντας να προσπελάσει τα εξής {0} αντικείμενα· θα τα παραλείψει:", + "u_etadone": "Ολοκληρώθηκε ({0}, {1} αρχεία)", + "u_etaprep": "(προετοιμασία για μεταφόρτωση)", + "u_hashdone": "το hashing ολοκληρώθηκε", + "u_hashing": "υπολογισμός hash", + "u_hs": "handshaking...", + "u_started": "τα αρχεία ανεβαίνουν τώρα· δες τα στο [🚀]", + "u_dupdefer": "διπλότυπο; θα επεξεργαστεί μετά από όλα τα άλλα αρχεία", + "u_actx": "πάτα αυτό το κείμενο για να μην χάσεις<br />απόδοση όταν αλλάζεις παράθυρα/καρτέλες", + "u_fixed": "ΟΚ!  Το διόρθωσα 👍", + "u_cuerr": "αποτυχία μεταφόρτωσης τμήματοςς {0} από {1};\nπιθανώς ακίνδυνο, συνεχίζω\n\nαρχείο: {2}", + "u_cuerr2": "ο διακομιστής απέρριψε τη μεταφόρτωση (τμήμα {0} από {1});\nθα ξαναδοκιμάσει αργότερα\n\nαρχείο: {2}\n\nσφάλμα ", + "u_ehstmp": "θα ξαναδοκιμάσει; δες κάτω δεξιά", + "u_ehsfin": "ο διακομιστής απέρριψε το αίτημα ολοκλήρωσης της μεταφόρτωσης; ξαναδοκιμάζει...", + "u_ehssrch": "ο διακομιστής απέρριψε το αίτημα αναζήτησης; ξαναδοκιμάζει...", + "u_ehsinit": "ο διακομιστής απέρριψε το αίτημα για εκκίνηση μεταφόρτωσης; ξαναδοκιμάζει...", + "u_eneths": "σφάλμα δικτύου κατά το handshake μεταφόρτωσης; ξαναδοκιμάζει...", + "u_enethd": "σφάλμα δικτύου κατά τον έλεγχο ύπαρξης στόχου; ξαναδοκιμάζει...", + "u_cbusy": "ο διακομιστής περιμένει να μας εμπιστευτεί ξανά μετά από πρόβλημα δικτύου...", + "u_ehsdf": "ο διακομιστής έμεινε από χώρο στο δίσκο!\n\nθα συνεχίσει να ξαναδοκιμάζει,\nσε περίπτωση που κάποιος\nελευθερώσει αρκετό χώρο για συνέχεια", + "u_emtleak1": "φαίνεται πως ο browser σου έχει διαρροή μνήμης;\nπαρακαλώ", + "u_emtleak2": ' <a href="{0}">αλλαγή σε https (συνιστάται)</a> ή ', + "u_emtleak3": ' ', + "u_emtleakc": 'δοκίμασε τα εξής:\n<ul><li>πάτα <code>F5</code> για ανανέωση σελίδας</li><li>μετά απενεργοποίησε το  <code>mt</code>  κουμπί στις  <code>⚙️ ρυθμίσεις</code></li><li>και δοκίμασε ξανά τη μεταφόρτωση</li></ul>Οι μεταφορτώσιες θα είναι λίγο πιο αργές, αλλά ok.\nΣυγγνώμη για την ταλαιπωρία!\n\nPS: το chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">έχει διόρθωση γι\' αυτό</a>', + "u_emtleakf": 'δοκίμασε τα εξής:\n<ul><li>πάτα <code>F5</code> για ανανέωση σελίδας</li><li>μετά άνοιξε το <code>🥔</code> (potato) στο UI μεταφόρτωσης<li>και δοκίμασε ξανά τη μεταφόρτωση</li></ul>\nPS: ο firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">ελπίζει να φτιάξει αυτό το bug</a> κάποια στιγμή', + "u_s404": "δεν βρέθηκε στο διακομιστή", + "u_expl": "επεξήγηση", + "u_maxconn": "οι περισσότεροι browser το περιορίζουν στα 6, αλλά ο firefox σου επιτρέπει να το αυξήσεις με <code>connections-per-server</code> στο <code>about:config</code>", + "u_tu": '<p class="warn">ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το turbo είναι ενεργοποιημένο, <span> το πρόγραμμα πελάτη ίσως να μην ανιχνεύσει και να μην ξαναεκκινήσει μισοτελειωμένες μεταφορτώσεις; δες τα tooltip του κουμπιού turbo</span></p>', + "u_ts": '<p class="warn">ΠΡΟΕΙΔΟΠΟΙΗΣΗ: το turbo είναι ενεργοποιημένο, <span> τα αποτελέσματα αναζήτησης μπορεί να είναι λάθος; δες τα tooltip του κουμπιού turbo</span></p>', + "u_turbo_c": "το turbo είναι απενεργοποιημένο στο αρχείο ρυθμίσεων του διακομιστή", + "u_turbo_g": "απενεργοποιώ το turbo επειδή δεν έχεις δικαίωμα\nγια τη λίστα φακέλων σε αυτόν τον τόμο", + "u_life_cfg": 'αυτόματη διαγραφή μετά από <input id="lifem" p="60" /> λεπτά (ή <input id="lifeh" p="3600" /> ώρες)', + "u_life_est": 'η μεταφόρτωση θα διαγραφεί <span id="lifew" tt="τοπική ώρα">---</span>', + "u_life_max": 'αυτός ο φάκελος επιβάλλει\nμέγιστη διάρκεια ζωής {0}', + "u_unp_ok": "επιτρέπεται το unpost για {0}", + "u_unp_ng": "δεν επιτρέπεται το unpost", + "ue_ro": "έχεις μόνο δικαίωμα ανάγνωσης σε αυτόν το φάκελο\n\n", + "ue_nl": "δεν είσαι συνδεδεμένος τώρα", + "ue_la": 'είσαι συνδεδεμένος ως "{0}"', + "ue_sr": "είσαι σε λειτουργία αναζήτησης αρχείων\n\nπήγαινε σε λειτουργία μεταφόρτωσης πατώντας το 🔎 (δίπλα στο μεγάλο κουμπί ΑΝΑΖΗΤΗΣΗΣ) και δοκίμασε πάλι\n\nσυγγνώμη", + "ue_ta": "δοκίμασε να μεταφορτώσεις εκ νέου, θα πρέπει να δουλέψει τώρα", + "ue_ab": "αυτό το αρχείο ανεβαίνει σε άλλο φάκελο και η μεταφόρτωση πρέπει να ολοκληρωθεί πριν ανέβει αλλού.\n\nΜπορείς να ακυρώσεις και να ξεχάσεις την αρχική μεταφόρτωση με το κουμπί 🧯 πάνω αριστερά", + "ur_1uo": "ΟΚ: Το αρχείο ανέβηκε επιτυχώς", + "ur_auo": "ΟΚ: Και τα {0} αρχεία ανέβηκαν επιτυχώς", + "ur_1so": "ΟΚ: Το αρχείο βρέθηκε στο διακομιστή", + "ur_aso": "ΟΚ: Και τα {0} αρχεία βρέθηκαν στο διακομιστή", + "ur_1un": "Η μεταφόρτωση απέτυχε, συγγνώμη", + "ur_aun": "Και οι {0} μεταφορτώσεις απέτυχαν, συγγνώμη", + "ur_1sn": "Το αρχείο ΔΕΝ βρέθηκε στο διακομιστή", + "ur_asn": "Τα {0} αρχεία ΔΕΝ βρέθηκαν στο διακομιστή", + "ur_um": "Ολοκληρώθηκε;\n{0} μεταφορτώσεις είναι OK,\n{1} μεταφορτώσεις απέτυχαν, συγγνώμη", + "ur_sm": "Ολοκληρώθηκε;\n{0} αρχεία βρέθηκαν στο διακομιστή,\n{1} αρχεία ΔΕΝ βρέθηκαν στο διακομιστή", + + "lang_set": "ανανέωση σελίδας για εφαρμογή της αλλαγής;" + }, + "ita": { + "tt": "Italiano", + + "cols": { + "c": "pulsanti azione", + "dur": "durata", + "q": "qualità / bitrate", + "Ac": "codec audio", + "Vc": "codec video", + "Fmt": "formato / container", + "Ahash": "checksum audio", + "Vhash": "checksum video", + "Res": "risoluzione", + "T": "tipo file", + "aq": "qualità audio / bitrate", + "vq": "qualità video / bitrate", + "pixfmt": "subsampling / struttura pixel", + "resw": "risoluzione orizzontale", + "resh": "risoluzione verticale", + "chs": "canali audio", + "hz": "frequenza di campionamento" + }, + + "hks": [ + [ + "varie", + ["ESC", "chiudi vari elementi"], + + "file-manager", + ["G", "alterna vista lista / griglia"], + ["T", "alterna miniature / icone"], + ["⇧ A/D", "dimensione miniature"], + ["ctrl-K", "elimina selezionati"], + ["ctrl-X", "taglia selezione negli appunti"], + ["ctrl-C", "copia selezione negli appunti"], + ["ctrl-V", "incolla (sposta/copia) qui"], + ["Y", "scarica selezionati"], + ["F2", "rinomina selezionati"], + + "file-list-sel", + ["spazio", "alterna selezione file"], + ["↑/↓", "sposta cursore selezione"], + ["ctrl ↑/↓", "sposta cursore e viewport"], + ["⇧ ↑/↓", "seleziona file prec/succ"], + ["ctrl-A", "seleziona tutti i file / cartelle"], + ], [ + "navigation", + ["B", "alterna breadcrumb / pannello nav"], + ["I/K", "cartella prec/succ"], + ["M", "cartella genitore (o comprimi corrente)"], + ["V", "alterna cartelle / file di testo nel pannello nav"], + ["A/D", "dimensione pannello nav"], + ], [ + "audio-player", + ["J/L", "brano prec/succ"], + ["U/O", "salta 10sec indietro/avanti"], + ["0..9", "salta a 0%..90%"], + ["P", "play/pausa (avvia anche)"], + ["S", "seleziona brano in riproduzione"], + ["Y", "scarica brano"], + ], [ + "image-viewer", + ["J/L, ←/→", "immagine prec/succ"], + ["Home/End", "prima/ultima immagine"], + ["F", "schermo intero"], + ["R", "ruota in senso orario"], + ["⇧ R", "ruota in senso antiorario"], + ["S", "seleziona immagine"], + ["Y", "scarica immagine"], + ], [ + "video.player", + ["U/O", "salta 10sec indietro/avanti"], + ["P/K/Spazio", "play/pausa"], + ["C", "continua riproduzione successivo"], + ["V", "loop"], + ["M", "muto"], + ["[ e ]", "imposta intervallo loop"], + ], [ + "textfile-viewer", + ["I/K", "file prec/succ"], + ["M", "chiudi file di testo"], + ["E", "modifica file di testo"], + ["S", "seleziona file (per taglia/copia/rinomina)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Annulla", + + "enable": "Abilita", + "danger": "PERICOLO", + "clipped": "copiato negli appunti", + + "ht_s1": "secondo", + "ht_s2": "secondi", + "ht_m1": "minuto", + "ht_m2": "minuti", + "ht_h1": "ora", + "ht_h2": "ore", + "ht_d1": "giorno", + "ht_d2": "giorni", + "ht_and": " e ", + + "goh": "control-panel", + "gop": 'cartella sorella precedente">prec', + "gou": 'cartella genitore">su', + "gon": 'prossima cartella">succ', + "logout": "Logout ", + "login": "Accedi", //m + "access": " accesso", + "ot_close": "chiudi sottomenu", + "ot_search": "cerca file per attributi, percorso / nome, tag musicali, o qualsiasi combinazione di questi$N$N<code>foo bar</code> = deve contenere sia «foo» che «bar»,$N<code>foo -bar</code> = deve contenere «foo» ma non «bar»,$N<code>^yana .opus$</code> = inizia con «yana» ed è un file «opus»$N<code>"try unite"</code> = contiene esattamente «try unite»$N$Nil formato data è iso-8601, come$N<code>2009-12-31</code> o <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: elimina i tuoi caricamenti recenti, o interrompi quelli non completati", + "ot_bup": "bup: uploader di base, supporta anche netscape 4.0", + "ot_mkdir": "mkdir: crea una nuova directory", + "ot_md": "new-md: crea un nuovo documento markdown", + "ot_msg": "msg: invia un messaggio al log del server", + "ot_mp": "opzioni lettore multimediale", + "ot_cfg": "opzioni di configurazione", + "ot_u2i": 'up2k: carica file (se hai accesso in scrittura) o attiva la modalità ricerca per vedere se esistono già da qualche parte sul server$N$NI caricamenti sono ripristinabili, multithreaded, e i timestamp dei file vengono preservati, ma usa più CPU di [🎈]  (l\'uploader di base)<br /><br />durante i caricamenti, questa icona diventa un indicatore di progresso!', + "ot_u2w": 'up2k: carica file con supporto per il ripristino (chiudi il browser e trascina gli stessi file più tardi)$N$NMultithreaded, e i timestamp dei file vengono preservati, ma usa più CPU di [🎈]  (l\'uploader di base)<br /><br />durante i caricamenti, questa icona diventa un indicatore di progresso!', + "ot_noie": 'Perfavore usa Chrome / Firefox / Edge', + + "ab_mkdir": "crea directory", + "ab_mkdoc": "nuovo doc markdown", + "ab_msg": "invia msg al log srv", + + "ay_path": "salta alle cartelle", + "ay_files": "salta ai file", + + "wt_ren": "rinomina elementi selezionati$NTasto rapido: F2", + "wt_del": "elimina elementi selezionati$NTasto rapido: ctrl-K", + "wt_cut": "taglia elementi selezionati <small>(poi incolla altrove)</small>$NTasto rapido: ctrl-X", + "wt_cpy": "copia elementi selezionati negli appunti$N(per incollarli altrove)$NTasto rapido: ctrl-C", + "wt_pst": "incolla una selezione precedentemente tagliata / copiata$NTasto rapido: ctrl-V", + "wt_selall": "seleziona tutti i file$NTasto rapido: ctrl-A (quando il file è focalizzato)", + "wt_selinv": "inverti selezione", + "wt_zip1": "scarica questa cartella come archivio", + "wt_selzip": "scarica selezione come archivio", + "wt_seldl": "scarica selezione come file separati$NTasto rapido: Y", + "wt_npirc": "copia info traccia formato irc", + "wt_nptxt": "copia info traccia testo semplice", + "wt_m3ua": "aggiungi alla playlist m3u (clicca <code>📻copia</code> dopo)", + "wt_m3uc": "copia playlist m3u negli appunti", + "wt_grid": "alterna vista griglia / lista$NTasto rapido: G", + "wt_prev": "traccia precedente$NTasto rapido: J", + "wt_play": "play / pausa$NTasto rapido: P", + "wt_next": "traccia successiva$NTasto rapido: L", + + "ul_par": "caricamenti paralleli:", + "ut_rand": "randomizza nomi file", + "ut_u2ts": "copia il timestamp di ultima modifica$Ndal tuo filesystem al server\">📅", + "ut_ow": "sovrascrivere file esistenti sul server?$N🛡️: mai (genererà un nuovo nome file)$N🕒: sovrascrivi se il file del server è più vecchio del tuo$N♻️: sovrascrivi sempre se i file sono diversi", + "ut_mt": "continua l'hashing di altri file durante il caricamento$N$NProva a disabilitare se la tua CPU o HDD è un collo di bottiglia", + "ut_ask": 'chiedi conferma prima che inizi il caricamento">💭', + "ut_pot": "migliora la velocità di caricamento su dispositivi lenti$Nrendendo l'interfaccia meno complessa", + "ut_srch": "non caricare realmente, invece controlla se i file esistono già $N sul server (scansionerà tutte le cartelle che puoi leggere)", + "ut_par": "metti in pausa i caricamenti impostandolo a 0$N$NAumenta se la tua connessione è lenta / alta latenza$N$NMantienilo a 1 su LAN o se l'HDD del server è un collo di bottiglia", + "ul_btn": "trascina file / cartelle<br>qui (o cliccami)", + "ul_btnu": "C A R I C A", + "ul_btns": "C E R C A", + + "ul_hash": "hash", + "ul_send": "invia", + "ul_done": "fatto", + "ul_idle1": "nessun caricamento ancora in coda", + "ut_etah": "velocità media di <em>hashing</em>, e tempo stimato al completamento", + "ut_etau": "velocità media di <em>caricamento</em> e tempo stimato al completamento", + "ut_etat": "velocità <em>totale</em> media e tempo stimato al completamento", + + "uct_ok": "completato con successo", + "uct_ng": "non-valido: fallito / rifiutato / non-trovato", + "uct_done": "ok e ng combinati", + "uct_bz": "hashing o caricamento", + "uct_q": "inattivo, in attesa", + + "utl_name": "nome file", + "utl_ulist": "lista", + "utl_ucopy": "copia", + "utl_links": "link", + "utl_stat": "stato", + "utl_prog": "progresso", + + // keep short: + "utl_404": "404", + "utl_err": "ERRORE", + "utl_oserr": "Errore-SO", + "utl_found": "trovato", + "utl_defer": "rinvia", + "utl_yolo": "YOLO", + "utl_done": "finito", + + "ul_flagblk": "i file sono stati aggiunti alla coda</b><br>tuttavia c'è un up2k occupato in un'altra scheda del browser,<br>quindi aspetto che quello finisca prima", + "ul_btnlk": "la configurazione del server ha bloccato questo interruttore in questo stato", + + "udt_up": "Carica", + "udt_srch": "Cerca", + "udt_drop": "lascialo qui", + + "u_nav_m": '<h6>ok, cosa hai?</h6><code>Invio</code> = File (uno o più)\n<code>ESC</code> = Una cartella (incluse sottocartelle)', + "u_nav_b": '<a href="#" id="modal-ok">File</a><a href="#" id="modal-ng">Una cartella</a>', + + "cl_opts": "opzioni", + "cl_themes": "tema", + "cl_langs": "lingua", + "cl_ziptype": "download cartella", + "cl_uopts": "opzioni up2k", + "cl_favico": "favicon", + "cl_bigdir": "cartelle grandi", + "cl_hsort": "#ordinamento", + "cl_keytype": "notazione tasti", + "cl_hiddenc": "colonne nascoste", + "cl_hidec": "nascondi", + "cl_reset": "reset", + "cl_hpick": "tocca le intestazioni delle colonne per nascondere nella tabella sottostante", + "cl_hcancel": "nascondere colonne annullato", + + "ct_grid": '田 griglia', + "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_ihop": 'quando il visualizzatore immagini è chiuso, scorri fino all\'ultimo file visualizzato">g⮯', + "ct_dots": 'mostra file nascosti (se il server lo permette)">dotfile', + "ct_qdel": 'quando elimini file, chiedi conferma solo una volta">qdel', + "ct_dir1st": 'ordina cartelle prima dei file">📁 prima', + "ct_nsort": 'ordinamento naturale (per nomi file con cifre iniziali)">nsort', + "ct_utc": 'mostra tutte le date/ore in UTC">UTC', + "ct_readme": 'mostra README.md negli elenchi cartelle">📜 readme', + "ct_idxh": 'mostra index.html invece dell\'elenco cartelle">htm', + "ct_sbars": 'mostra barre di scorrimento">⟊', + + "cut_umod": "se un file esiste già sul server, aggiorna il timestamp di ultima modifica del server per farlo coincidere con il tuo file locale (richiede permessi di scrittura+eliminazione)\">re📅", + + "cut_turbo": "il pulsante yolo, probabilmente NON lo vuoi abilitare:$N$NUsalo se stavi caricando una grande quantità di file e hai dovuto riavviare per qualche motivo, e vuoi continuare il caricamento il prima possibile$N$NQuesto sostituisce il controllo hash con un semplice <em>"questo ha la stessa dimensione file sul server?"</em> quindi se il contenuto del file è diverso NON verrà caricato$N$NDovresti spegnere questo quando il caricamento è finito, e poi "caricare" di nuovo gli stessi file per far verificare al client\">turbo", + + "cut_datechk": "non ha effetto a meno che il pulsante turbo sia abilitato$N$NRiduce il fattore yolo di una piccola quantità; controlla se i timestamp dei file sul server corrispondono ai tuoi$N$NDovrebbe <em>teoricamente</em> catturare la maggior parte dei caricamenti non finiti / corrotti, ma non è un sostituto per fare un passaggio di verifica con turbo disabilitato dopo\">date-chk", + + "cut_u2sz": "dimensione (in MiB) di ogni chunk di caricamento; valori grandi volano meglio attraverso l'atlantico. Prova valori bassi su connessioni molto inaffidabili", + + "cut_flag": "assicura che solo una scheda stia caricando alla volta $N -- anche le altre schede devono avere questo abilitato $N -- influisce solo sulle schede dello stesso dominio", + + "cut_az": "carica file in ordine alfabetico, invece che dal file più piccolo prima$N$NL'ordine alfabetico può rendere più facile controllare a occhio se qualcosa è andato storto sul server, ma rende il caricamento leggermente più lento su fibra / LAN", + + "cut_nag": "notifica SO quando il caricamento si completa$N(solo se il browser o la scheda non è attiva)", + "cut_sfx": "allarme sonoro quando il caricamento si completa$N(solo se il browser o la scheda non è attiva)", + + "cut_mt": "usa multithreading per accelerare l'hashing dei file$N$NQuesto usa web-worker e richiede$Npiù RAM (fino a 512 MiB extra)$N$NRende https 30% più veloce, http 4.5x più veloce\">mt", + + "cut_wasm": "usa wasm invece dell'hasher integrato del browser; migliora la velocità sui browser basati su chrome ma aumenta il carico CPU, e molte versioni vecchie di chrome hanno bug che fanno consumare tutta la RAM al browser e crashare se questo è abilitato\">wasm", + + "cft_text": "testo favicon (vuoto e aggiorna per disabilitare)", + "cft_fg": "colore primo piano", + "cft_bg": "colore sfondo", + + "cdt_lim": "numero massimo di file da mostrare in una cartella", + "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", + + "tt_entree": "mostra pannello nav (barra laterale albero directory)$NTasto rapido: B", + "tt_detree": "mostra breadcrumb$NTasto rapido: B", + "tt_visdir": "scorri alla cartella selezionata", + "tt_ftree": "alterna albero cartelle / file di testo$NTasto rapido: V", + "tt_pdock": "mostra cartelle genitore in un pannello ancorato in alto", + "tt_dynt": "crescita automatica mentre l'albero si espande", + "tt_wrap": "a capo parola", + "tt_hover": "rivela righe che traboccano al passaggio del mouse$N( interrompe lo scorrimento a meno che il cursore $N  del mouse non sia nella grondaia sinistra )", + + "ml_pmode": "alla fine della cartella...", + "ml_btns": "comandi", + "ml_tcode": "transcodifica", + "ml_tcode2": "transcodifica in", + "ml_tint": "tinta", + "ml_eq": "equalizzatore audio", + "ml_drc": "compressore gamma dinamica", + + "mt_loop": "loop/ripeti una canzone\">🔁", + "mt_one": "fermati dopo una canzone\">1️⃣", + "mt_shuf": "mescola le canzoni in ogni cartella\">🔀", + "mt_aplay": "autoplay se c'è un song-ID nel link che hai cliccato per accedere al server$N$NDisabilitando questo fermerà anche l'aggiornamento dell'URL della pagina con song-ID quando riproduci musica, per prevenire autoplay se queste impostazioni vengono perse ma l'URL rimane\">a▶", + "mt_preload": "inizia a caricare la prossima canzone verso la fine per riproduzione senza interruzioni\">preload", + "mt_prescan": "vai alla prossima cartella prima che finisca l'ultima canzone$Nmantenendo felice il browser web$Ncosì non si ferma la riproduzione\">nav", + "mt_fullpre": "prova a precaricare l'intera canzone;$N✅ abilita su connessioni <b>inaffidabili</b>,$N❌ <b>disabilita</b> su connessioni lente probabilmente\">full", + "mt_fau": "sui telefoni, previeni che la musica si fermi se la prossima canzone non si precarica abbastanza velocemente (può rendere glitchy la visualizzazione dei tag)\">☕️", + "mt_waves": "barra di ricerca forma d'onda:$Nmostra ampiezza audio nello scrubber\">~s", + "mt_npclip": "mostra pulsanti per copiare negli appunti la canzone attualmente in riproduzione\">/np", + "mt_m3u_c": "mostra pulsanti per copiare negli appunti le$Ncanzoni selezionate come voci playlist m3u8\">📻", + "mt_octl": "integrazione so (tasti multimediali / osd)\">os-ctl", + "mt_oseek": "permetti ricerca attraverso integrazione so$N$Nnota: su alcuni dispositivi (iPhone),$Nquesto sostituisce il pulsante canzone successiva\">seek", + "mt_oscv": "mostra copertina album in osd\">art", + "mt_follow": "mantieni la traccia in riproduzione scorrevole nella vista\">🎯", + "mt_compact": "controlli compatti\">⟎", + "mt_uncache": "pulisci cache  (prova ad attivare se il tuo browser ha messo in cache$Nuna copia rotta di una canzone e si rifiuta di riprodurla)\">uncache", + "mt_mloop": "loop della cartella aperta\">🔁 loop", + "mt_mnext": "carica la prossima cartella e continua\">📂 succ", + "mt_mstop": "ferma riproduzione\">⏸ stop", + "mt_cflac": "converti flac / wav in {0}\">flac", + "mt_caac": "converti aac / m4a in {0}\">aac", + "mt_coth": "converti tutti gli altri (non mp3) in {0}\">oth", + "mt_c2opus": "scelta migliore per desktop, laptop, android\">opus", + "mt_c2owa": "opus-weba, per iOS 17.5 e più recenti\">owa", + "mt_c2caf": "opus-caf, per iOS 11 fino a 17\">caf", + "mt_c2mp3": "usa questo su dispositivi molto vecchi\">mp3", + "mt_c2flac": "qualità audio migliore, ma download pesanti\">flac", //m + "mt_c2wav": "riproduzione non compressa (ancora più grande)\">wav", //m + "mt_c2ok": "bene, buona scelta", + "mt_c2nd": "quello non è il formato di output raccomandato per il tuo dispositivo, ma va bene", + "mt_c2ng": "il tuo dispositivo non sembra supportare questo formato di output, ma proviamo comunque", + "mt_xowa": "ci sono bug in iOS che prevengono la riproduzione in background usando questo formato; usa caf o mp3 invece", + "mt_tint": "livello sfondo (0-100) sulla barra di ricerca$Nper rendere il buffering meno distraente", + "mt_eq": "abilita l'equalizzatore e controllo guadagno;$N$Nboost <code>0</code> = volume standard 100% (non modificato)$N$Nwidth <code>1  </code> = stereo standard (non modificato)$Nwidth <code>0.5</code> = 50% crossfeed sinistra-destra$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = rimozione vocale :^)$N$Nabilitando l'equalizzatore rende gli album senza interruzioni completamente senza interruzioni, quindi lascialo acceso con tutti i valori a zero (eccetto width = 1) se ti importa di quello", + "mt_drc": "abilita il compressore gamma dinamica (appiattitore volume / brickwaller); abiliterà anche EQ per bilanciare gli spaghetti, quindi imposta tutti i campi EQ eccetto 'width' a 0 se non lo vuoi$N$NAbbassa il volume dell'audio sopra THRESHOLD dB; per ogni RATIO dB oltre THRESHOLD c'è 1 dB di output, quindi i valori di default di tresh -24 e ratio 12 significa che non dovrebbe mai diventare più forte di -22 dB ed è sicuro aumentare il boost equalizzatore a 0.8, o anche 1.8 con ATK 0 e un RLS enorme come 90 (funziona solo in firefox; RLS è max 1 in altri browser)$N$N(vedi wikipedia, lo spiegano molto meglio)", + + "mb_play": "riproduci", + "mm_hashplay": "riprodurre questo file audio?", + "mm_m3u": "premi <code>Invio/OK</code> per Riprodurre\npremi <code>ESC/Annulla</code> per Modificare", + "mp_breq": "serve firefox 82+ o chrome 73+ o iOS 15+", + "mm_bload": "ora caricando...", + "mm_bconv": "convertendo in {0}, attendi...", + "mm_opusen": "il tuo browser non può riprodurre file aac / m4a;\ntranscodifica in opus ora abilitata", + "mm_playerr": "riproduzione fallita: ", + "mm_eabrt": "Il tentativo di riproduzione è stato cancellato", + "mm_enet": "La tua connessione internet è instabile", + "mm_edec": "Questo file è presumibilmente corrotto??", + "mm_esupp": "Il tuo browser non capisce questo formato audio", + "mm_eunk": "Errore Sconosciuto", + "mm_e404": "Non è stato possibile riprodurre audio; errore 404: File non trovato.", + "mm_e403": "Non è stato possibile riprodurre audio; errore 403: Accesso negato.\n\nProva a premere F5 per ricaricare, forse sei stato disconnesso", + "mm_e500": "Non è stato possibile riprodurre audio; errore 500: Controlla i log del server.", + "mm_e5xx": "Non è stato possibile riprodurre audio; errore server ", + "mm_nof": "non trovo altri file audio nelle vicinanze", + "mm_prescan": "Cercando musica da riprodurre dopo...", + "mm_scank": "Trovata la prossima canzone:", + "mm_uncache": "cache pulita; tutte le canzoni si riscaricheranno alla prossima riproduzione", + "mm_hnf": "quella canzone non esiste più", + + "im_hnf": "quell'immagine non esiste più", + + "f_empty": 'questa cartella è vuota', + "f_chide": 'questo nasconderà la colonna «{0}»\n\npuoi mostrare le colonne nella scheda impostazioni', + "f_bigtxt": "questo file è {0} MiB grande -- visualizzare davvero come testo?", + "f_bigtxt2": "visualizzare solo la fine del file invece? questo abiliterà anche following/tailing, mostrando righe di testo appena aggiunte in tempo reale", + "fbd_more": '<div id="blazy">mostrando <code>{0}</code> di <code>{1}</code> file; <a href="#" id="bd_more">mostra {2}</a> o <a href="#" id="bd_all">mostra tutti</a></div>', + "fbd_all": '<div id="blazy">mostrando <code>{0}</code> di <code>{1}</code> file; <a href="#" id="bd_all">mostra tutti</a></div>', + "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_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 <code>.PARTIAL</code>. Premi ANNULLA o Escape per farlo.\n\nPremendo OK / Invio ignorerai questo avviso e continuerai a scaricare il file <code>.PARTIAL</code> scratch, che quasi sicuramente ti darà dati corrotti.", + + "ft_paste": "incolla {0} elementi$NTasto rapido: ctrl-V", + "fr_eperm": 'impossibile rinominare:\nnon hai il permesso “sposta” in questa cartella', + "fd_eperm": 'impossibile eliminare:\nnon hai il permesso “elimina” in questa cartella', + "fc_eperm": 'impossibile tagliare:\nnon hai il permesso “sposta” in questa cartella', + "fp_eperm": 'impossibile incollare:\nnon hai il permesso “scrivi” in questa cartella', + "fr_emore": "seleziona almeno un elemento da rinominare", + "fd_emore": "seleziona almeno un elemento da eliminare", + "fc_emore": "seleziona almeno un elemento da tagliare", + "fcp_emore": "seleziona almeno un elemento da copiare negli appunti", + + "fs_sc": "condividi la cartella in cui ti trovi", + "fs_ss": "condividi i file selezionati", + "fs_just1d": "non puoi selezionare più di una cartella,\no mescolare file e cartelle in una selezione", + "fs_abrt": "❌ interrompi", + "fs_rand": "🎲 nome.casuale", + "fs_go": "✅ crea condivisione", + "fs_name": "nome", + "fs_src": "sorgente", + "fs_pwd": "password", + "fs_exp": "scadenza", + "fs_tmin": "min", + "fs_thrs": "ore", + "fs_tdays": "giorni", + "fs_never": "eterno", + "fs_pname": "nome link opzionale; sarà casuale se vuoto", + "fs_tsrc": "il file o cartella da condividere", + "fs_ppwd": "password opzionale", + "fs_w8": "creando condivisione...", + "fs_ok": "premi <code>Invio/OK</code> per Appunti\npremi <code>ESC/Annulla</code> per Chiudere", + + "frt_dec": "può risolvere alcuni casi di nomi file corrotti\">url-decode", + "frt_rst": "ripristina nomi file modificati a quelli originali\">↺ reset", + "frt_abrt": "interrompi e chiudi questa finestra\">❌ annulla", + "frb_apply": "APPLICA RINOMINA", + "fr_adv": "rinomina batch / metadata / pattern\">avanzato", + "fr_case": "regex case-sensitive\">maiusc", + "fr_win": "nomi sicuri per windows; sostituisce <code><>:"\\|?*</code> con caratteri giapponesi fullwidth\">win", + "fr_slash": "sostituisce <code>/</code> con un carattere che non causa la creazione di nuove cartelle\">no /", + "fr_re": "pattern di ricerca regex da applicare ai nomi file originali; i gruppi di cattura possono essere referenziati nel campo formato sottostante come <code>(1)</code> e <code>(2)</code> e così via", + "fr_fmt": "ispirato da foobar2000:$N<code>(title)</code> è sostituito dal titolo della canzone,$N<code>[(artist) - ](title)</code> salta [questa] parte se artista è vuoto$N<code>$lpad((tn),2,0)</code> aggiunge padding al numero traccia a 2 cifre", + "fr_pdel": "elimina", + "fr_pnew": "salva come", + "fr_pname": "fornisci un nome per il tuo nuovo preset", + "fr_aborted": "interrotto", + "fr_lold": "nome vecchio", + "fr_lnew": "nome nuovo", + "fr_tags": "tag per i file selezionati (sola lettura, solo per riferimento):", + "fr_busy": "rinominando {0} elementi...\n\n{1}", + "fr_efail": "rinomina fallita:\n", + "fr_nchg": "{0} dei nuovi nomi sono stati alterati a causa di <code>win</code> e/o <code>no /</code>\n\nOK per continuare con questi nuovi nomi alterati?", + + "fd_ok": "eliminazione OK", + "fd_err": "eliminazione fallita:\n", + "fd_none": "niente è stato eliminato; forse bloccato dalla configurazione server (xbd)?", + "fd_busy": "eliminando {0} elementi...\n\n{1}", + "fd_warn1": "ELIMINARE questi {0} elementi?", + "fd_warn2": "<b>Ultima possibilità!</b> Nessun modo per annullare. Eliminare?", + + "fc_ok": "tagliati {0} elementi", + "fc_warn": 'tagliati {0} elementi\n\nma: solo <b>questa</b> scheda-browser può incollarli\n(dato che la selezione è così assolutamente massiva)', + + "fcc_ok": "copiati {0} elementi negli appunti", + "fcc_warn": 'copiati {0} elementi negli appunti\n\nma: solo <b>questa</b> scheda-browser può incollarli\n(dato che la selezione è così assolutamente massiva)', + + "fp_apply": "usa questi nomi", + "fp_ecut": "prima taglia o copia alcuni file / cartelle da incollare / spostare\n\nnota: puoi tagliare / incollare attraverso diverse schede del browser", + "fp_ename": "{0} elementi non possono essere spostati qui perché i nomi sono già presi. Dai loro nuovi nomi qui sotto per continuare, o lascia vuoto il nome per saltarli:", + "fcp_ename": "{0} elementi non possono essere copiati qui perché i nomi sono già presi. Dai loro nuovi nomi qui sotto per continuare, o lascia vuoto il nome per saltarli:", + "fp_emore": "ci sono ancora alcune collisioni di nomi file rimaste da risolvere", + "fp_ok": "spostamento OK", + "fcp_ok": "copia OK", + "fp_busy": "spostando {0} elementi...\n\n{1}", + "fcp_busy": "copiando {0} elementi...\n\n{1}", + "fp_abrt": "annullamento in corso...", //m + "fp_err": "spostamento fallito:\n", + "fcp_err": "copia fallita:\n", + "fp_confirm": "spostare questi {0} elementi qui?", + "fcp_confirm": "copiare questi {0} elementi qui?", + "fp_etab": 'fallito leggere appunti da altra scheda browser', + "fp_name": "caricando un file dal tuo dispositivo. Dagli un nome:", + "fp_both_m": '<h6>scegli cosa incollare</h6><code>Invio</code> = Sposta {0} file da «{1}»\n<code>ESC</code> = Carica {2} file dal tuo dispositivo', + "fcp_both_m": '<h6>scegli cosa incollare</h6><code>Invio</code> = Copia {0} file da «{1}»\n<code>ESC</code> = Carica {2} file dal tuo dispositivo', + "fp_both_b": '<a href="#" id="modal-ok">Sposta</a><a href="#" id="modal-ng">Carica</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Copia</a><a href="#" id="modal-ng">Carica</a>', + + "mk_noname": "scrivi un nome nel campo di testo a sinistra prima di farlo :p", + + "tv_load": "Caricando documento di testo:\n\n{0}\n\n{1}% ({2} di {3} MiB caricati)", + "tv_xe1": "impossibile caricare file di testo:\n\nerrore ", + "tv_xe2": "404, file non trovato", + "tv_lst": "lista di file di testo in", + "tvt_close": "torna alla vista cartella$NTasto rapido: M (o Esc)\">❌ chiudi", + "tvt_dl": "scarica questo file$NTasto rapido: Y\">💾 scarica", + "tvt_prev": "mostra documento precedente$NTasto rapido: i\">⬆ prec", + "tvt_next": "mostra documento successivo$NTasto rapido: K\">⬇ succ", + "tvt_sel": "seleziona file   ( per taglia / copia / elimina / ... )$NTasto rapido: S\">sel", + "tvt_edit": "apri file nell'editor di testo$NTasto rapido: E\">✏️ modifica", + "tvt_tail": "monitora file per cambiamenti; mostra nuove righe in tempo reale\">📡 segui", + "tvt_wrap": "a capo parola\">↵", + "tvt_atail": "blocca scorrimento in fondo alla pagina\">⚓", + "tvt_ctail": "decodifica colori terminale (codici escape ansi)\">🌈", + "tvt_ntail": "limite scrollback (quanti byte di testo mantenere caricati)", + + "m3u_add1": "canzone aggiunta alla playlist m3u", + "m3u_addn": "{0} canzoni aggiunte alla playlist m3u", + "m3u_clip": "playlist m3u ora copiata negli appunti\n\ndovresti creare un nuovo file di testo chiamato qualcosa.m3u e incollare la playlist in quel documento; questo la renderà riproducibile", + + "gt_vau": "non mostrare video, riproduci solo l'audio\">🎧", + "gt_msel": "abilita selezione file; ctrl-click un file per sovrascrivere$N$N<em>quando attivo: doppio-click un file / cartella per aprirlo</em>$N$NTasto rapido: S\">multiselezione", + "gt_crop": "ritaglia miniature al centro\">ritaglia", + "gt_3x": "miniature hi-res\">3x", + "gt_zoom": "zoom", + "gt_chop": "taglia", + "gt_sort": "ordina per", + "gt_name": "nome", + "gt_sz": "dimensione", + "gt_ts": "data", + "gt_ext": "tipo", + "gt_c1": "tronca nomi file di più (mostra meno)", + "gt_c2": "tronca nomi file di meno (mostra di più)", + + "sm_w8": "cercando...", + "sm_prev": "i risultati di ricerca qui sotto sono da una query precedente:\n ", + "sl_close": "chiudi risultati ricerca", + "sl_hits": "mostrando {0} risultati", + "sl_moar": "carica altro", + + "s_sz": "dimensione", + "s_dt": "data", + "s_rd": "percorso", + "s_fn": "nome", + "s_ta": "tag", + "s_ua": "car@", + "s_ad": "avanz.", + "s_s1": "MiB minimo", + "s_s2": "MiB massimo", + "s_d1": "iso8601 min.", + "s_d2": "iso8601 max.", + "s_u1": "caricato dopo", + "s_u2": "e/o prima", + "s_r1": "percorso contiene   (separato da spazi)", + "s_f1": "nome contiene   (nega con -nope)", + "s_t1": "tag contiene   (^=inizio, fine=$)", + "s_a1": "proprietà metadata specifiche", + + "md_eshow": "impossibile renderizzare ", + "md_off": "[📜<em>readme</em>] disabilitato in [⚙️] -- documento nascosto", + + "badreply": "Fallito nel parsare risposta dal server", + + "xhr403": "403: Accesso negato\n\nprova a premere F5, forse sei stato disconnesso", + "xhr0": "sconosciuto (probabilmente persa connessione al server, o server offline)", + "cf_ok": "scusa per quello -- la protezione DD" + wah + "oS è entrata in azione\n\nle cose dovrebbero riprendere in circa 30 sec\n\nse non succede niente, premi F5 per ricaricare la pagina", + "tl_xe1": "impossibile elencare sottocartelle:\n\nerrore ", + "tl_xe2": "404: Cartella non trovata", + "fl_xe1": "impossibile elencare file nella cartella:\n\nerrore ", + "fl_xe2": "404: Cartella non trovata", + "fd_xe1": "impossibile creare sottocartella:\n\nerrore ", + "fd_xe2": "404: Cartella genitore non trovata", + "fsm_xe1": "impossibile inviare messaggio:\n\nerrore ", + "fsm_xe2": "404: Cartella genitore non trovata", + "fu_xe1": "fcaricamento fallito per la lista unpost dal server:\n\nerrore ", + "fu_xe2": "404: File non trovato??", + + "fz_tar": "file gnu-tar non compresso (linux / mac)", + "fz_pax": "tar formato pax non compresso (più lento)", + "fz_targz": "gnu-tar con compressione gzip livello 3$N$NSolitamente è molto lento, quindi$Nusa tar non compresso", + "fz_tarxz": "gnu-tar con compressione xz livello 1$N$NQuesto è solitamente molto lento, quindi$Nusa tar non compresso", + "fz_zip8": "zip con nomi file utf8 (forse instabile su windows 7 e precedenti)", + "fz_zipd": "zip con nomi file cp437 tradizionali, per software molto vecchio", + "fz_zipc": "cp437 con crc32 calcolato presto,$Nper MS-DOS PKZIP v2.04g (ottobre 1993)$N(ci vuole più tempo per elaborare prima che possa iniziare il download)", + + "un_m1": "puoi eliminare i tuoi caricamenti recenti (o interrompere quelli non finiti) qui sotto", + "un_upd": "aggiorna", + "un_m4": "o condividi i file visibili qui sotto:", + "un_ulist": "mostra", + "un_ucopy": "copia", + "un_flt": "filtro opzionale:  URL deve contenere", + "un_fclr": "resetta filtro", + "un_derr": 'unpost-delete fallito:\n', + "un_f5": 'qualcosa si è rotto, prova un aggiornamento o premi F5', + "un_uf5": "scusa ma devi aggiornare la pagina (per esempio premendo F5 o CTRL-R) prima che questo caricamento possa essere interrotto", + "un_nou": '<b>avviso:</b> server troppo occupato per mostrare caricamenti non finiti; clicca il link "aggiorna" tra un po\'', + "un_noc": '<b>avviso:</b> unpost di file completamente caricati non è abilitato/permesso nella configurazione server', + "un_max": "mostrando primi 2000 file (usa il filtro)", + "un_avail": "{0} caricamenti recenti possono essere eliminati<br />{1} non finiti possono essere interrotti", + "un_m2": "ordinati per tempo di caricamento; più recenti prima:", + "un_no1": "scherzo! nessun caricamento è abbastanza recente", + "un_no2": "scherzo! nessun caricamento che corrisponde a quel filtro è abbastanza recente", + "un_next": "elimina i prossimi {0} file qui sotto", + "un_abrt": "interrompi", + "un_del": "elimina", + "un_m3": "caricando i tuoi caricamenti recenti...", + "un_busy": "eliminando {0} file...", + "un_clip": "{0} link copiati negli appunti", + + "u_https1": "dovresti", + "u_https2": "passare a https", + "u_https3": "per prestazioni migliori", + "u_ancient": 'il tuo browser è incredibilmente antico -- forse dovresti <a href="#" onclick="goto(\'bup\')">usare bup invece</a>', + "u_nowork": "serve firefox 53+ o chrome 57+ o iOS 11+", + "tail_2old": "serve firefox 105+ o chrome 71+ o iOS 14.5+", + "u_nodrop": 'il tuo browser è troppo vecchio per il caricamento drag-and-drop', + "u_notdir": "quella non è una cartella!\n\nil tuo browser è troppo vecchio,\nprova dragdrop invece", + "u_uri": "per trascinare immagini da altre finestre del browser,\nrilasciale sul pulsante upload grande", + "u_enpot": 'passa alla <a href="#">UI patata</a> (può migliorare velocità upload)', + "u_depot": 'passa alla <a href="#">UI elegante</a> (può ridurre velocità upload)', + "u_gotpot": 'passando alla UI patata per migliorare velocità upload,\n\nsentiti libero di non essere d\'accordo e tornare indietro!', + "u_pott": "<p>file:   <b>{0}</b> finiti,   <b>{1}</b> falliti,   <b>{2}</b> occupati,   <b>{3}</b> in coda</p>", + "u_ever": "questo è l'uploader di base; up2k necessita almeno<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'questo è l\'uploader di base; <a href="#" id="u2yea">up2k</a> è migliore', + "u_uput": 'velocizza (salta checksum)', + "u_ewrite": 'non hai accesso in scrittura a questa cartella', + "u_eread": 'non hai accesso in lettura a questa cartella', + "u_enoi": 'file-search non è abilitato nella configurazione server', + "u_enoow": "non puoi sovrascrivere qui; serve permesso Elimina", + "u_badf": 'Questi {0} file (di {1} totali) sono stati saltati, probabilmente a causa di permessi filesystem:\n\n', + "u_blankf": 'Questi {0} file (di {1} totali) sono vuoti; caricarli comunque?\n\n', + "u_applef": 'Questi {0} file (di {1} totali) sono probabilmente indesiderabili;\nPremi <code>OK/Invio</code> per SALTARE i seguenti file,\nPremi <code>Annulla/ESC</code> per NON escludere, e CARICARE anche quelli:\n\n', + "u_just1": '\nForse funziona meglio se selezioni solo un file', + "u_ff_many": "se stai usando <b>Linux / MacOS / Android,</b> allora questa quantità di file <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>potrebbe</em> far crashare Firefox!</a>\nse succede, riprova (o usa Chrome).", + "u_up_life": "Questo caricamento sarà eliminato dal server\n{0} dopo che si completa", + "u_asku": 'caricare questi {0} file in <code>{1}</code>', + "u_unpt": "puoi annullare / eliminare questo caricamento usando 🧯 in alto a sinistra", + "u_bigtab": 'sto per mostrare {0} file\n\nquesto potrebbe far crashare il tuo browser, sei sicuro?', + "u_scan": 'Scansionando file...', + "u_dirstuck": 'iteratore directory si è bloccato tentando di accedere ai seguenti {0} elementi; salterò:', + "u_etadone": 'Fatto ({0}, {1} file)', + "u_etaprep": '(preparando per caricare)', + "u_hashdone": 'hashing completato', + "u_hashing": 'hash', + "u_hs": 'handshaking...', + "u_started": "i file ora sono in caricamento; vedi [🚀]", + "u_dupdefer": "duplicato; sarà processato dopo tutti gli altri file", + "u_actx": "clicca questo testo per prevenire perdita di<br />prestazioni quando cambi ad altre finestre/schede", + "u_fixed": "OK!  Risolto 👍", + "u_cuerr": "caricamento fallito del chunk {0} di {1};\nprobabilmente innocuo, continuo\n\nfile: {2}", + "u_cuerr2": "il server ha rifiutato il caricamento (chunk {0} di {1});\nriproverò più tardi\n\nfile: {2}\n\nerrore ", + "u_ehstmp": "riproverò; vedi in basso a destra", + "u_ehsfin": "il server ha rifiutato la richiesta di finalizzare caricamento; riprovando...", + "u_ehssrch": "il server ha rifiutato la richiesta di eseguire ricerca; riprovando...", + "u_ehsinit": "il server ha rifiutato la richiesta di iniziare caricamento; riprovando...", + "u_eneths": "errore di rete durante handshake per upload; riprovando...", + "u_enethd": "errore di rete durante test esistenza target; riprovando...", + "u_cbusy": "aspettando che il server si fidi di noi di nuovo dopo un problema di rete...", + "u_ehsdf": "il server ha finito lo spazio su disco!\n\ncontinuerò a riprovare, nel caso qualcuno\nliberi abbastanza spazio per continuare", + "u_emtleak1": "sembra che il tuo browser possa avere un memory leak;\nper favore", + "u_emtleak2": ' <a href="{0}">passa a https (raccomandato)</a> o ', + "u_emtleak3": ' ', + "u_emtleakc": 'prova quanto segue:\n<ul><li>premi <code>F5</code> per aggiornare la pagina</li><li>poi disabilita il pulsante  <code>mt</code>  nelle  <code>⚙️ impostazioni</code></li><li>e riprova quel caricamento</li></ul>I caricamenti saranno un po\' più lenti, ma pazienza.\nScusa per il disturbo !\n\nPS: chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">ha un bugfix</a> per questo', + "u_emtleakf": 'prova quanto segue:\n<ul><li>premi <code>F5</code> per aggiornare la pagina</li><li>poi abilita <code>🥔</code> (patata) nell\'UI caricamento<li>e riprova quel caricamento</li></ul>\nPS: firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">avrà sperabilmente un bugfix</a> ad un certo punto', + "u_s404": "non trovato sul server", + "u_expl": "spiega", + "u_maxconn": "la maggior parte dei browser limita questo a 6, ma firefox ti permette di alzarlo con <code>connections-per-server</code> in <code>about:config</code>", + "u_tu": '<p class="warn">AVVISO: turbo abilitato, <span> client potrebbe non rilevare e riprendere caricamenti incompleti; vedi tooltip pulsante turbo</span></p>', + "u_ts": '<p class="warn">AVVISO: turbo abilitato, <span> risultati ricerca possono essere incorretti; vedi tooltip pulsante turbo</span></p>', + "u_turbo_c": "turbo è disabilitato nella configurazione server", + "u_turbo_g": "disabilitando turbo perché non hai\nprivilegi di elenco directory all'interno di questo volume", + "u_life_cfg": 'auto-elimina dopo <input id="lifem" p="60" /> min (o <input id="lifeh" p="3600" /> ore)', + "u_life_est": 'caricamento sarà eliminato <span id="lifew" tt="ora locale">---</span>', + "u_life_max": 'questa cartella impone una\nvita massima di {0}', + "u_unp_ok": 'unpost è permesso per {0}', + "u_unp_ng": 'unpost NON sarà permesso', + "ue_ro": 'il tuo accesso a questa cartella è solo-Lettura\n\n', + "ue_nl": 'attualmente non sei loggato', + "ue_la": 'attualmente sei loggato come "{0}"', + "ue_sr": 'attualmente sei in modalità file-search\n\npassa alla modalità upload cliccando la lente d\'ingrandimento 🔎 (accanto al grande pulsante CERCA), e prova a caricare di nuovo\n\nscusa', + "ue_ta": 'prova a caricare di nuovo, dovrebbe funzionare ora', + "ue_ab": "questo file è già in caricamento in un'altra cartella, e quel caricamento deve essere completato prima che il file possa essere caricato altrove.\n\nPuoi interrompere e dimenticare il caricamento iniziale usando l'🧯 in alto a sinistra", + "ur_1uo": "OK: File caricato con successo", + "ur_auo": "OK: Tutti i {0} file caricati con successo", + "ur_1so": "OK: File trovato sul server", + "ur_aso": "OK: Tutti i {0} file trovati sul server", + "ur_1un": "Caricamento fallito, scusa", + "ur_aun": "Tutti i {0} caricamenti falliti, scusa", + "ur_1sn": "File NON trovato sul server", + "ur_asn": "I {0} file NON sono stati trovati sul server", + "ur_um": "Finito;\n{0} caricamenti OK,\n{1} caricamenti falliti, scusa", + "ur_sm": "Finito;\n{0} file trovati sul server,\n{1} file NON trovati sul server", + + "lang_set": "aggiornare per rendere effettivo il cambiamento?", + }, + "kor": { + "tt": "한국어", + + "cols": { + "c": "작업 버튼", + "dur": "길이", + "q": "품질/비트레이트", + "Ac": "오디오 코덱", + "Vc": "비디오 코덱", + "Fmt": "형식/컨테이너", + "Ahash": "오디오 체크섬", + "Vhash": "비디오 체크섬", + "Res": "해상도", + "T": "파일 유형", + "aq": "오디오 품질/비트레이트", + "vq": "비디오 품질/비트레이트", + "pixfmt": "서브샘플링/픽셀 구조", + "resw": "가로 해상도", + "resh": "세로 해상도", + "chs": "오디오 채널", + "hz": "샘플레이트" + }, + + "hks": [ + [ + "기타", + ["ESC", "다양한 창 닫기"], + + "파일 관리자", + ["G", "목록/그리드 보기 전환"], + ["T", "썸네일/아이콘 전환"], + ["⇧ A/D", "썸네일 이미지 크기"], + ["ctrl-K", "선택 항목 삭제"], + ["ctrl-X", "선택 항목 잘라내기"], + ["ctrl-C", "선택 항목 복사"], + ["ctrl-V", "여기에 붙여넣기 (이동/복사)"], + ["Y", "선택 항목 다운로드"], + ["F2", "선택 항목 이름 바꾸기"], + + "파일 목록 선택", + ["space", "파일 선택/해제"], + ["↑/↓", "선택 커서 이동"], + ["ctrl ↑/↓", "커서와 뷰포트 동시 이동"], + ["⇧ ↑/↓", "이전/다음 파일 선택"], + ["ctrl-A", "모든 파일/폴더 선택"], + ], [ + "탐색", + ["B", "브레드크럼/탐색창 전환"], + ["I/K", "이전/다음 폴더"], + ["M", "상위 폴더 (또는 현재 항목 닫기)"], + ["V", "탐색창에 폴더/텍스트 파일 표시 전환"], + ["A/D", "탐색창 크기"], + ], [ + "오디오 플레이어", + ["J/L", "이전/다음 곡"], + ["U/O", "10초 뒤로/앞으로 건너뛰기"], + ["0..9", "0%..90% 지점으로 이동"], + ["P", "재생/일시정지 (시작 포함)"], + ["S", "재생 중인 곡 선택"], + ["Y", "곡 다운로드"], + ], [ + "이미지 뷰어", + ["J/L, ←/→", "이전/다음 이미지"], + ["Home/End", "첫/마지막 이미지"], + ["F", "전체 화면"], + ["R", "시계 방향으로 회전"], + ["⇧ R", "반시계 방향으로 회전"], + ["S", "이미지 선택"], + ["Y", "이미지 다운로드"], + ], [ + "비디오 플레이어", + ["U/O", "10초 뒤로/앞으로 건너뛰기"], + ["P/K/Space", "재생/일시정지"], + ["C", "다음 파일 계속 재생"], + ["V", "반복"], + ["M", "음소거"], + ["[ 와 ]", "반복 구간 설정"], + ], [ + "텍스트 파일 뷰어", + ["I/K", "이전/다음 파일"], + ["M", "텍스트 파일 닫기"], + ["E", "텍스트 파일 편집"], + ["S", "파일 선택 (잘라내기/복사/이름 바꾸기용)"], + ] + ], + + "m_ok": "확인", + "m_ng": "취소", + + "enable": "활성화", + "danger": "위험", + "clipped": "클립보드에 복사되었습니다", + + "ht_s1": "초", + "ht_s2": "초", + "ht_m1": "분", + "ht_m2": "분", + "ht_h1": "시간", + "ht_h2": "시간", + "ht_d1": "일", + "ht_d2": "일", + "ht_and": " ", + + "goh": "제어판", + "gop": '이전 형제 폴더">이전', + "gou": '상위 폴더">위로', + "gon": '다음 폴더">다음', + "logout": "로그아웃 ", + "login": "로그인", //m + "access": " 액세스", + "ot_close": "하위 메뉴 닫기", + "ot_search": "속성, 경로/이름, 음악 태그 또는 이들의 조합으로 파일을 검색합니다.$N$N<code>foo bar</code> = «foo»와 «bar»를 모두 포함해야 함,$N<code>foo -bar</code> = «foo»는 포함하지만 «bar»는 포함하지 않아야 함,$N<code>^yana .opus$</code> = «yana»로 시작하고 «opus» 파일이어야 함$N<code>"try unite"</code> = 정확히 «try unite»를 포함해야 함$N$N날짜 형식은 ISO-8601입니다. 예:$N<code>2009-12-31</code> 또는 <code>2020-09-12 23:30:00</code>", + "ot_unpost": "주워담기: 최근 업로드한 항목을 삭제하거나 미완료된 업로드를 중단합니다", + "ot_bup": "bup: 기본 업로더. 넷스케이프 4.0도 지원합니다", + "ot_mkdir": "mkdir: 새 디렉터리를 만듭니다", + "ot_md": "new-md: 새 마크다운 문서를 만듭니다", + "ot_msg": "msg: 서버 로그에 메시지를 보냅니다", + "ot_mp": "미디어 플레이어 옵션", + "ot_cfg": "구성 옵션", + "ot_u2i": 'up2k: (쓰기 권한이 있는 경우) 파일을 업로드하거나, 검색 모드로 전환하여 서버 어딘가에 파일이 있는지 확인합니다.$N$N업로드는 재개 가능하고, 멀티스레드로 작동하며, 파일 타임스탬프가 보존되지만, [🎈] (기본 업로더)보다 CPU를 더 많이 사용합니다.<br /><br />업로드 중에는 이 아이콘이 진행률 표시창이 됩니다!', + "ot_u2w": 'up2k: 이어올리기 기능을 지원하는 파일 업로더입니다 (브라우저를 닫았다가 나중에 동일한 파일을 끌어다 놓으세요).$N$N멀티스레드로 작동하며, 파일 타임스탬프가 보존되지만, [🎈] (기본 업로더)보다 CPU를 더 많이 사용합니다.<br /><br />업로드 중에는 이 아이콘이 진행률 표시창이 됩니다!', + "ot_noie": 'Chrome / Firefox / Edge를 사용해주세요', + + "ab_mkdir": "디렉터리 만들기", + "ab_mkdoc": "새 마크다운 문서", + "ab_msg": "서버 로그에 메시지 보내기", + + "ay_path": "폴더로 건너뛰기", + "ay_files": "파일로 건너뛰기", + + "wt_ren": "선택한 항목 이름 바꾸기$N단축키: F2", + "wt_del": "선택한 항목 삭제$N단축키: ctrl-K", + "wt_cut": "선택한 항목 잘라내기 <small>(다른 곳에 붙여넣기용)</small>$N단축키: ctrl-X", + "wt_cpy": "선택한 항목 클립보드에 복사$N(다른 곳에 붙여넣기용)$N단축키: ctrl-C", + "wt_pst": "이전에 잘라내거나 복사한 항목 붙여넣기$N단축키: ctrl-V", + "wt_selall": "모든 파일 선택$N단축키: ctrl-A (파일에 포커스된 경우)", + "wt_selinv": "선택 반전", + "wt_zip1": "이 폴더를 압축 파일로 다운로드", + "wt_selzip": "선택 항목을 압축 파일로 다운로드", + "wt_seldl": "선택 항목을 개별 파일로 다운로드$N단축키: Y", + "wt_npirc": "IRC 형식 트랙 정보 복사", + "wt_nptxt": "일반 텍스트 트랙 정보 복사", + "wt_m3ua": "m3u 재생 목록에 추가 (나중에 <code>📻복사</code> 클릭)", + "wt_m3uc": "m3u 재생 목록을 클립보드에 복사", + "wt_grid": "그리드/목록 보기 전환$N단축키: G", + "wt_prev": "이전 트랙$N단축키: J", + "wt_play": "재생/일시정지$N단축키: P", + "wt_next": "다음 트랙$N단축키: L", + + "ul_par": "동시 업로드:", + "ut_rand": "파일명 무작위로 만들기", + "ut_u2ts": "사용자 파일 시스템의 마지막 수정 타임스탬프를$N서버에 복사\">📅", + "ut_ow": "서버에 있는 기존 파일을 덮어쓸까요?$N🛡️: 안 함 (대신 새 파일 이름 생성)$N🕒: 서버 파일이 더 오래된 경우 덮어쓰기$N♻️: 파일이 다르면 항상 덮어쓰기", + "ut_mt": "업로드 중 다른 파일 해싱 계속하기$N$NCPU 또는 HDD가 병목 현상을 일으키는 경우 비활성화하세요", + "ut_ask": '업로드 시작 전 확인 요청">💭', + "ut_pot": "느린 기기에서 UI를 단순화하여$N업로드 속도 향상", + "ut_srch": "실제로 업로드하는 대신, 파일이 이미 서버에 있는지 확인합니다$N(읽을 수 있는 모든 폴더를 스캔합니다)", + "ut_par": "0으로 설정하여 업로드 일시정지$N$N연결이 느리거나 지연 시간이 길면 늘리세요$N$NLAN 환경이거나 서버 HDD가 병목 현상을 일으키면 1로 유지하세요", + "ul_btn": "파일/폴더를 여기에<br>끌어다 놓거나 클릭하세요", + "ul_btnu": "업 로 드", + "ul_btns": "검 색", + + "ul_hash": "해싱", + "ul_send": "전송", + "ul_done": "완료", + "ul_idle1": "대기 중인 업로드가 없습니다", + "ut_etah": "평균 <em>해싱</em> 속도 및 예상 완료 시간", + "ut_etau": "평균 <em>업로드</em> 속도 및 예상 완료 시간", + "ut_etat": "평균 <em>총</em> 속도 및 예상 완료 시간", + + "uct_ok": "성공적으로 완료됨", + "uct_ng": "문제 발생: 실패/거부/찾을 수 없음", + "uct_done": "완료됨 (성공 및 문제 발생 포함)", + "uct_bz": "해싱 또는 업로드 중", + "uct_q": "대기 중, 보류 중", + + "utl_name": "파일명", + "utl_ulist": "목록", + "utl_ucopy": "복사", + "utl_links": "링크", + "utl_stat": "상태", + "utl_prog": "진행률", + + // keep short: + "utl_404": "404", + "utl_err": "오류", + "utl_oserr": "OS 오류", + "utl_found": "찾음", + "utl_defer": "보류", + "utl_yolo": "YOLO", + "utl_done": "완료", + + "ul_flagblk": "파일이 대기열에 추가되었습니다.</b><br>하지만 다른 브라우저 탭에서 up2k가 실행 중이므로,<br>해당 작업이 끝날 때까지 기다립니다.", + "ul_btnlk": "서버 구성에서 이 스위치를 현재 상태로 잠갔습니다.", + + "udt_up": "업로드", + "udt_srch": "검색", + "udt_drop": "여기에 놓으세요", + + "u_nav_m": '<h6>자, 갖고 있는 게 무엇인가?</h6><code>Enter</code> = 파일 (하나 이상)\n<code>ESC</code> = 폴더 하나 (하위 폴더 포함)', + "u_nav_b": '<a href="#" id="modal-ok">파일</a><a href="#" id="modal-ng">폴더 하나</a>', + + "cl_opts": "스위치", + "cl_themes": "테마", + "cl_langs": "언어", + "cl_ziptype": "폴더 다운로드", + "cl_uopts": "up2k 스위치", + "cl_favico": "파비콘", + "cl_bigdir": "큰 디렉터리", + "cl_hsort": "#sort", + "cl_keytype": "조성 표기법", + "cl_hiddenc": "숨겨진 열", + "cl_hidec": "숨기기", + "cl_reset": "초기화", + "cl_hpick": "아래 테이블에서 숨기고 싶은 열의 헤더를 탭하세요", + "cl_hcancel": "열 숨기기가 중단되었습니다", + + "ct_grid": "田 그리드", + "ct_ttips": '◔ ◡ ◔">ℹ️ 도움말', + "ct_thumb": '그리드 보기에서 아이콘 또는 미리보기 이미지 전환$N단축키: T">🖼️ 미리보기', + "ct_csel": '그리드 보기에서 CTRL과 SHIFT를 사용하여 파일 선택">선택', + "ct_ihop": '이미지 뷰어를 닫으면 마지막으로 본 파일로 스크롤">g⮯', + "ct_dots": '숨김 파일 표시 (서버가 허용하는 경우)">숨김파일', + "ct_qdel": '파일 삭제 시 한 번만 확인 요청">빠른삭제', + "ct_dir1st": '폴더를 파일보다 먼저 정렬">📁 먼저', + "ct_nsort": '자연어 정렬 (파일명의 숫자를 인식)">자연어정렬', + "ct_utc": '모든 날짜/시간을 UTC로 표시">UTC', + "ct_readme": '폴더 목록에 README.md 표시">📜 readme', + "ct_idxh": '폴더 목록 대신 index.html 표시">htm', + "ct_sbars": '스크롤바 표시">⟊', + + "cut_umod": '파일이 서버에 이미 있는 경우, 서버의 마지막 수정 타임스탬프를 로컬 파일과 일치하도록 업데이트합니다 (쓰기+삭제 권한 필요).\">re📅', + + "cut_turbo": 'YOLO 버튼. 아마 활성화하고 싶지 않으실 겁니다.$N$N대량의 파일을 업로드하다가 어떤 이유로 재시작해야 할 때, 최대한 빨리 업로드를 계속하고 싶을 때 사용하세요.$N$N이 옵션은 해시 확인을 단순히 <em>"서버에 동일한 파일 크기를 가진 파일이 있는가?"</em>로 대체하므로, 파일 내용만 다를 경우 업로드되지 않습니다.$N$N업로드가 끝나면 이 옵션을 끄고, 동일한 파일을 다시 \"업로드\"하여 클라이언트가 검증하도록 해야 합니다.\">turbo', + + "cut_datechk": '터보 버튼이 활성화되어 있지 않으면 효과가 없습니다.$N$NYOLO의 위험성을 약간 줄여줍니다. 서버의 파일 타임스탬프가 사용자의 것과 일치하는지 확인합니다.$N$N<em>이론적으로는</em> 대부분의 미완료/손상된 업로드를 잡아내지만, 터보를 비활성화하고 검증 과정을 거치는 것을 대체할 수는 없습니다.\">날짜확인', + + "cut_u2sz": "각 업로드 청크의 크기 (MiB)입니다. 큰 값은 태평양을 건너는 데 더 유리합니다. 매우 불안정한 연결에서는 낮은 값을 시도해보세요.", + + "cut_flag": '한 번에 하나의 탭만 업로드하도록 보장합니다.$N-- 다른 탭도 이 옵션을 활성화해야 합니다.$N-- 동일한 도메인의 탭에만 영향을 미칩니다.', + + "cut_az": '가장 작은 파일 우선이 아닌 알파벳 순서로 파일을 업로드합니다.$N$N알파벳 순서는 서버에서 문제가 발생했는지 눈으로 확인하기 쉽게 해주지만, 광랜/LAN 환경에서는 업로드 속도가 약간 느려집니다.', + + "cut_nag": '업로드 완료 시 OS 알림$N(브라우저나 탭이 활성화되지 않은 경우에만)', + "cut_sfx": '업로드 완료 시 소리 알림$N(브라우저나 탭이 활성화되지 않은 경우에만)', + + "cut_mt": '멀티스레딩을 사용하여 파일 해싱 속도를 높입니다.$N$N이 기능은 웹 워커를 사용하며$N더 많은 RAM이 필요합니다 (추가적으로 최대 512 MiB).$N$Nhttps는 30% 더 빠르게, http는 4.5배 더 빠르게 만듭니다.\">mt', + + "cut_wasm": '브라우저 내장 해셔 대신 wasm을 사용합니다. 크롬 기반 브라우저에서 속도를 향상시키지만 CPU 부하를 증가시키며, 많은 구버전 크롬에는 이 기능을 활성화하면 모든 RAM을 소모하고 충돌하는 버그가 있습니다.\">wasm', + + "cft_text": "파비콘 텍스트 (비워두고 새로고침하면 비활성화됨)", + "cft_fg": "전경색", + "cft_bg": "배경색", + + "cdt_lim": "폴더에 표시할 최대 파일 수", + "cdt_ask": "맨 아래로 스크롤할 때$N더 많은 파일을 불러오는 대신$N무엇을 할지 묻기", + "cdt_hsort": "미디어 URL에 포함할 정렬 규칙 (<code>,sorthref</code>)의 수. 0으로 설정하면 미디어 링크를 클릭할 때 포함된 정렬 규칙도 무시됩니다.", + + "tt_entree": "탐색 창 (디렉터리 트리 사이드바) 표시$N단축키: B", + "tt_detree": "이동 경로 표시$N단축키: B", + "tt_visdir": "선택한 폴더로 스크롤하기", + "tt_ftree": "폴더 트리/텍스트 파일 전환$N단축키: V", + "tt_pdock": "상위 폴더를 상단에 고정된 창에 표시", + "tt_dynt": "트리가 확장될 때 자동으로 너비 증가", + "tt_wrap": "자동 줄 바꿈", + "tt_hover": "마우스를 올리면 넘어가는 줄 표시$N(마우스 커서가 왼쪽 여백에$N  있지 않으면 스크롤이 깨짐)", + + "ml_pmode": "폴더 끝에서...", + "ml_btns": "명령", + "ml_tcode": "트랜스코딩", + "ml_tcode2": "다음으로 트랜스코딩", + "ml_tint": "틴트", + "ml_eq": "오디오 이퀄라이저", + "ml_drc": "다이내믹 레인지 압축기", + + "mt_loop": "한 곡 반복 재생\">🔁", + "mt_one": "한 곡 재생 후 중지\">1️⃣", + "mt_shuf": "각 폴더의 곡을 무작위 재생\">🔀", + "mt_aplay": "서버에 접속한 링크에 곡 ID가 있으면 자동 재생$N$N이것을 비활성화하면 음악 재생 시 페이지 URL이 곡 ID로 업데이트되지 않아, 이 설정이 손실되고 URL이 남아있을 경우 자동 재생되는 것을 방지합니다.\">a▶", + "mt_preload": "끊김 없는 재생을 위해 다음 곡을 미리 불러오기 시작\">미리로드", + "mt_prescan": "마지막 곡이 끝나기 전에 다음 폴더로 이동하여$N웹브라우저가 재생을 멈추지 않도록 합니다.\">탐색", + "mt_fullpre": "전체 곡을 미리 불러오기 시도;$N✅ <b>불안정한</b> 연결에서 활성화,$N❌ <b>느린</b> 연결에서는 아마도 비활성화\">전체", + "mt_fau": "폰에서 다음 곡이 충분히 빨리 미리 불러오지 않아 음악이 멈추는 것을 방지합니다 (태그 표시가 불안정해질 수 있음).\">☕️", + "mt_waves": "파형 탐색 바:$N탐색 바에 오디오 진폭 표시\">~s", + "mt_npclip": "현재 재생 중인 곡을 클립보드에 복사하는 버튼 표시\">/np", + "mt_m3u_c": "선택한 곡을 m3u8 재생 목록 항목으로$N클립보드에 복사하는 버튼 표시\">📻", + "mt_octl": "OS 통합 (미디어 단축키/OSD)\">os-ctl", + "mt_oseek": "OS 통합을 통해 탐색 허용$N$N참고: 일부 기기 (iPhone)에서는$N이것이 다음 곡 버튼을 대체합니다.\">탐색", + "mt_oscv": "OSD에 앨범 커버 표시\">아트", + "mt_follow": "재생 중인 트랙이 보이도록 스크롤 유지\">🎯", + "mt_compact": "컴팩트 컨트롤\">⟎", + "mt_uncache": "캐시 지우기 (브라우저가 곡의 깨진 사본을 캐시하여$N재생이 안되는 경우 시도해보세요)\">캐시삭제", + "mt_mloop": "열린 폴더 반복\">🔁 반복", + "mt_mnext": "다음 폴더 불러오고 계속\">📂 다음", + "mt_mstop": "재생 중지\">⏸ 중지", + "mt_cflac": "flac/wav를 {0}로 변환\">flac", + "mt_caac": "aac/m4a를 {0}로 변환\">aac", + "mt_coth": "다른 모든 것 (mp3 제외)을 {0}로 변환\">기타", + "mt_c2opus": "데스크톱, 노트북, 안드로이드 환경에 최적\">opus", + "mt_c2owa": "iOS 17.5 이상용 opus-weba\">owa", + "mt_c2caf": "iOS 11부터 17까지용 opus-caf\">caf", + "mt_c2mp3": "매우 오래된 기기에서 사용\">mp3", + "mt_c2flac": "최고 음질이지만 다운로드 용량이 큼\">flac", + "mt_c2wav": "비압축 재생 (더 큼)\">wav", + "mt_c2ok": "네, 좋은 선택입니다", + "mt_c2nd": "기기에 권장되는 출력 형식이 아니지만 괜찮습니다", + "mt_c2ng": "기기가 이 출력 형식을 지원하지 않는 것 같지만, 시도해 보겠습니다", + "mt_xowa": "iOS에서 이 형식의 백그라운드 재생이 안되는 버그가 있습니다. 대신 caf나 mp3를 사용해주세요.", + "mt_tint": "탐색 바의 배경 레벨 (0-100)$N버퍼링이 덜 눈시리게 만듦", + "mt_eq": "이퀄라이저 및 게인 제어 활성화;$N$Nboost <code>0</code> = 표준 100% 볼륨 (수정 없음)$N$Nwidth <code>1  </code> = 표준 스테레오 (수정 없음)$Nwidth <code>0.5</code> = 50% 좌우 크로스피드$Nwidth <code>0  </code> = 모노$N$Nboost <code>-0.8</code> & width <code>10</code> = 보컬 제거 :^)$N$N이퀄라이저를 활성화하면 끊김 없는 앨범이 온전히 끊김 없이 재생되므로, 그 점이 중요하다면 모든 값을 0으로 두고 (width=1 제외) 켜두세요.", + "mt_drc": "다이내믹 레인지 컴프레서(볼륨 평탄화/벽돌화)를 활성화합니다. 스파게티의 균형을 맞추기 위해 EQ도 활성화되므로, 원하지 않으면 'width'를 제외한 모든 EQ 필드를 0으로 설정하세요.$N$NTHRESHOLD dB 이상의 오디오 볼륨을 낮춥니다. THRESHOLD를 초과하는 모든 RATIO dB에 대해 1dB의 출력이 있으므로, 기본값인 tresh -24 및 ratio 12는 볼륨이 -22dB보다 커지지 않음을 의미하며, 이퀄라이저 부스트를 0.8 또는 ATK 0과 큰 RLS (예: 90)를 사용하여 1.8까지 안전하게 높일 수 있습니다 (firefox에서만 작동, 다른 브라우저에서는 RLS 최대 1).$N$N(위키백과를 참조하세요, 훨씬 더 잘 설명되어 있습니다)", + + "mb_play": "재생", + "mm_hashplay": "이 오디오 파일을 재생할까요?", + "mm_m3u": "<code>Enter/확인</code>을 눌러 재생\n<code>ESC/취소</code>를 눌러 편집", + "mp_breq": "Firefox 82+, Chrome 73+ 또는 iOS 15+ 필요", + "mm_bload": "불러오는 중...", + "mm_bconv": "{0}(으)로 변환 중, 잠시만 기다려주세요...", + "mm_opusen": "브라우저가 aac/m4a 파일을 재생할 수 없습니다.\nopus로의 트랜스코딩이 활성화되었습니다.", + "mm_playerr": "재생 실패: ", + "mm_eabrt": "재생 시도가 취소되었습니다", + "mm_enet": "인터넷 연결이 불안정합니다", + "mm_edec": "이 파일이 손상된 것 같습니다??", + "mm_esupp": "브라우저가 이 오디오 형식을 이해하지 못합니다", + "mm_eunk": "알 수 없는 오류", + "mm_e404": "오디오를 재생할 수 없습니다; 오류 404: 파일을 찾을 수 없습니다.", + "mm_e403": "오디오를 재생할 수 없습니다; 오류 403: 접근이 거부되었습니다.\n\nF5를 눌러 새로고침 해보세요, 로그아웃되었을 수 있습니다", + "mm_e500": "오디오를 재생할 수 없습니다; 오류 500: 서버 로그를 확인하세요.", + "mm_e5xx": "오디오를 재생할 수 없습니다; 서버 오류 ", + "mm_nof": "주변에서 더 이상 오디오 파일을 찾을 수 없습니다", + "mm_prescan": "다음에 재생할 음악을 찾는 중...", + "mm_scank": "다음 곡을 찾았습니다:", + "mm_uncache": "캐시가 지워졌습니다. 모든 곡은 다음 재생 시 다시 다운로드됩니다.", + "mm_hnf": "그 곡이 더 이상 존재하지 않습니다", + + "im_hnf": "그 이미지가 더 이상 존재하지 않습니다", + + "f_empty": '이 폴더는 비어 있습니다', + "f_chide": '«{0}» 열을 숨깁니다.\n\n설정 탭에서 열을 다시 표시할 수 있습니다.', + "f_bigtxt": "이 파일은 {0} MiB입니다 -- 정말 텍스트로 보시겠습니까?", + "f_bigtxt2": "대신 파일의 끝부분만 보시겠습니까? 이렇게 하면 실시간으로 새로 추가되는 텍스트 줄을 보여주는 팔로잉/테일링 기능도 활성화됩니다.", + "fbd_more": '<div id="blazy"><code>{1}</code>개 파일 중 <code>{0}</code>개 표시 중; <a href="#" id="bd_more">{2}개 더 보기</a> 또는 <a href="#" id="bd_all">모두 보기</a></div>', + "fbd_all": '<div id="blazy"><code>{1}</code>개 파일 중 <code>{0}</code>개 표시 중; <a href="#" id="bd_all">모두 보기</a></div>', + "f_anota": "{1}개 항목 중 {0}개만 선택되었습니다.\n전체 폴더를 선택하려면 먼저 맨 아래로 스크롤하세요.", + + "f_dls": '현재 폴더의 파일 링크가\n다운로드 링크로 변경되었습니다', + + "f_partial": "현재 업로드 중인 파일을 안전하게 다운로드하려면, 파일 이름이 같지만 <code>.PARTIAL</code> 확장자가 없는 파일을 클릭하세요. 이 경고를 무시하려면 \"취소\" 또는 ESC를 누르세요.\n\n\"확인\"/Enter를 누르면 이 경고를 무시하고 <code>.PARTIAL</code> 임시 파일을 계속 다운로드하며, 이 경우 거의 확실히 손상된 데이터를 받게 됩니다.", + + "ft_paste": "{0}개 항목 붙여넣기$N단축키: ctrl-V", + "fr_eperm": "이름을 바꿀 수 없습니다:\n이 폴더에 \"이동\" 권한이 없습니다", + "fd_eperm": "삭제할 수 없습니다:\n이 폴더에 \"삭제\" 권한이 없습니다", + "fc_eperm": "잘라낼 수 없습니다:\n이 폴더에 \"이동\" 권한이 없습니다", + "fp_eperm": "붙여넣을 수 없습니다\n이 폴더에 \"쓰기\" 권한이 없습니다", + "fr_emore": "이름을 바꿀 항목을 하나 이상 선택하세요", + "fd_emore": "삭제할 항목을 하나 이상 선택하세요", + "fc_emore": "잘라낼 항목을 하나 이상 선택하세요", + "fcp_emore": "클립보드에 복사할 항목을 하나 이상 선택하세요", + + "fs_sc": "현재 폴더 공유", + "fs_ss": "선택한 파일 공유", + "fs_just1d": "하나 이상의 폴더를 선택하거나,\n파일과 폴더를 한 번에 섞어 선택할 수 없습니다", + "fs_abrt": "❌ 중단", + "fs_rand": "🎲 무작위 이름", + "fs_go": "✅ 공유 생성", + "fs_name": "이름", + "fs_src": "소스", + "fs_pwd": "비밀번호", + "fs_exp": "만료", + "fs_tmin": "분", + "fs_thrs": "시간", + "fs_tdays": "일", + "fs_never": "영원", + "fs_pname": "선택적 링크 이름; 비워두면 무작위로 생성", + "fs_tsrc": "공유할 파일 또는 폴더", + "fs_ppwd": "비밀번호 (선택사항)", + "fs_w8": "공유 생성 중...", + "fs_ok": "<code>Enter/OK</code>를 눌러 클립보드에 복사\n<code>ESC/Cancel</code>를 눌러 닫기", + + "frt_dec": "깨진 파일 이름의 일부 경우를 수정할 수 있습니다\">url-디코드", + "frt_rst": "수정된 파일 이름을 원래대로 되돌립니다\">↺ 초기화", + "frt_abrt": "이 창을 중단하고 닫습니다\">❌ 취소", + "frb_apply": "이름 바꾸기 적용", + "fr_adv": "배치/메타데이터/패턴 이름 바꾸기\">고급", + "fr_case": "대소문자 구분 정규식\">대소문자", + "fr_win": "Windows 안전 이름; <code><>:"\\|?*</code>를 일본어 전각 문자로 바꿉니다\">win", + "fr_slash": "<code>/</code>를 새 폴더를 만들지 않는 문자로 바꿉니다\">/ 없음", + "fr_re": "원본 파일 이름에 적용할 정규식 검색 패턴; 캡처링 그룹은 아래 형식 필드에서 <code>(1)</code>, <code>(2)</code> 등으로 참조할 수 있습니다", + "fr_fmt": "foobar2000에서 영감을 받음:$N<code>(title)</code>은(는) 곡 제목으로 대체됨,$N<code>[(artist) - ](title)</code>은(는) 아티스트가 비어 있으면 [이] 부분을 건너뜀$N<code>$lpad((tn),2,0)</code>은(는) 트랙 번호를 2자리로 채움", + "fr_pdel": "삭제", + "fr_pnew": "다른 이름으로 저장", + "fr_pname": "새 프리셋의 이름을 입력하세요", + "fr_aborted": "중단됨", + "fr_lold": "이전 이름", + "fr_lnew": "새 이름", + "fr_tags": "선택한 파일의 태그 (읽기 전용, 참조용):", + "fr_busy": "{0}개 항목 이름 바꾸는 중...\n\n{1}", + "fr_efail": "이름 바꾸기 실패:\n", + "fr_nchg": "<code>win</code> 및/또는 <code>/ 없음</code>으로 인해 새 이름 중 {0}개가 변경되었습니다.\n\n이 변경된 새 이름으로 계속하시겠습니까?", + + "fd_ok": "삭제 확인", + "fd_err": "삭제 실패:\n", + "fd_none": "아무것도 삭제되지 않았습니다. 서버 구성 (xbd)에 의해 차단되었을 수 있습니다.", + "fd_busy": "삭제 중 {0}개 항목...\n\n{1}", + "fd_warn1": "이 {0}개 항목을 삭제하시겠습니까?", + "fd_warn2": "<b>마지막 기회입니다!</b> 되돌릴 수 없습니다. 삭제하시겠습니까?", + + "fc_ok": "{0}개 항목 잘라내기 완료", + "fc_warn": "{0}개 항목 잘라내기 완료\n\n하지만: 선택 항목이 너무 커서 <b>이</b> 브라우저 탭에서만 붙여넣을 수 있습니다", + + "fcc_ok": "{0}개 항목을 클립보드에 복사했습니다", + "fcc_warn": "{0}개 항목을 클립보드에 복사했습니다\n\n하지만: 선택 항목이 너무 커서 <b>이</b> 브라우저 탭에서만 붙여넣을 수 있습니다", + + "fp_apply": "이 이름 사용", + "fp_ecut": "붙여넣거나 이동하려면 먼저 파일/폴더를 잘라내거나 복사하세요\n\n참고: 다른 브라우저 탭 간에 잘라내기/붙여넣기를 할 수 있습니다", + "fp_ename": "이름이 이미 사용 중이므로 {0}개 항목을 여기로 이동할 수 없습니다. 계속하려면 아래에 새 이름을 지정하거나, 이름을 비워두면 건너뜁니다:", + "fcp_ename": "이름이 이미 사용 중이므로 {0}개 항목을 여기로 복사할 수 없습니다. 계속하려면 아래에 새 이름을 지정하거나, 이름을 비워두면 건너뜁니다:", + "fp_emore": "아직 해결해야 할 파일 이름 충돌이 남아 있습니다", + "fp_ok": "이동 완료", + "fcp_ok": "복사 완료", + "fp_busy": "{0}개 항목 이동 중...\n\n{1}", + "fcp_busy": "{0}개 항목 복사 중...\n\n{1}", + "fp_abrt": "취소 중...", + "fp_err": "이동 실패:\n", + "fcp_err": "복사 실패:\n", + "fp_confirm": "이 {0}개 항목을 여기로 이동하시겠습니까?", + "fcp_confirm": "이 {0}개 항목을 여기로 복사하시겠습니까?", + "fp_etab": '다른 브라우저 탭에서 클립보드를 읽지 못했습니다', + "fp_name": "기기에서 파일을 업로드합니다. 이름을 지정하세요:", + "fp_both_m": '<h6>붙여넣을 항목 선택</h6><code>Enter</code> = «{1}»에서 파일 {0}개 이동\n<code>ESC</code> = 기기에서 파일 {2}개 업로드', + "fcp_both_m": '<h6>붙여넣을 항목 선택</h6><code>Enter</code> = «{1}»에서 파일 {0}개 복사\n<code>ESC</code> = 기기에서 파일 {2}개 업로드', + "fp_both_b": '<a href="#" id="modal-ok">이동</a><a href="#" id="modal-ng">업로드</a>', + "fcp_both_b": '<a href="#" id="modal-ok">복사</a><a href="#" id="modal-ng">업로드</a>', + + "mk_noname": "왼쪽 텍스트 필드에 이름을 먼저 입력해주세요 :p", + + "tv_load": "텍스트 문서 불러오는 중:\n\n{0}\n\n{1}% ({3} MiB 중 {2} MiB 로드됨)", + "tv_xe1": "텍스트 파일을 불러올 수 없습니다:\n\n오류 ", + "tv_xe2": "404, 파일을 찾을 수 없음", + "tv_lst": "텍스트 파일 목록", + "tvt_close": "폴더 보기로 돌아가기$N단축키: M (또는 Esc)\">❌ 닫기", + "tvt_dl": "이 파일 다운로드$N단축키: Y\">💾 다운로드", + "tvt_prev": "이전 문서 보기$N단축키: i\">⬆ 이전", + "tvt_next": "다음 문서 보기$N단축키: K\">⬇ 다음", + "tvt_sel": "파일 선택   (잘라내기/복사/삭제/...용)$N단축키: S\">선택", + "tvt_edit": "텍스트 편집기에서 파일 열기$N단축키: E\">✏️ 편집", + "tvt_tail": "파일 변경 사항 모니터링; 실시간으로 새 줄 표시\">📡 팔로우", + "tvt_wrap": "자동 줄 바꿈\">↵", + "tvt_atail": "페이지 하단으로 스크롤 고정\">⚓", + "tvt_ctail": "터미널 색상 디코딩 (ANSI 이스케이프 코드)\">🌈", + "tvt_ntail": "스크롤백 제한 (불러온 상태로 유지할 텍스트 바이트 수)", + + "m3u_add1": "m3u 재생 목록에 곡이 추가되었습니다", + "m3u_addn": "{0}개의 곡이 m3u 재생 목록에 추가되었습니다", + "m3u_clip": "m3u 재생 목록이 클립보드에 복사되었습니다\n\n something.m3u와 같은 이름의 새 텍스트 파일을 만들고 그 문서에 재생 목록을 붙여넣으면 재생할 수 있습니다.", + + "gt_vau": "비디오를 표시하지 않고 오디오만 재생\">🎧", + "gt_msel": "파일 선택 활성화; ctrl-클릭하여 파일 재정의$N$N<em>활성 시: 파일/폴더를 두 번 클릭하여 열기</em>$N$N단축키: S\">다중선택", + "gt_crop": "썸네일 중앙 자르기\">자르기", + "gt_3x": "고해상도 썸네일\">3x", + "gt_zoom": "확대/축소", + "gt_chop": "자르기", + "gt_sort": "정렬 기준", + "gt_name": "이름", + "gt_sz": "크기", + "gt_ts": "날짜", + "gt_ext": "유형", + "gt_c1": "파일명 더 많이 생략하기 (더 적게 표시)", + "gt_c2": "파일명 덜 생략하기 (더 많이 표시)", + + "sm_w8": "검색 중...", + "sm_prev": "아래 검색 결과는 이전 검색어에 대한 결과입니다:\n  ", + "sl_close": "검색 결과 닫기", + "sl_hits": "{0}개 결과 표시 중", + "sl_moar": "더 불러오기", + + "s_sz": "크기", + "s_dt": "날짜", + "s_rd": "경로", + "s_fn": "이름", + "s_ta": "태그", + "s_ua": "업로드 시점", + "s_ad": "고급", + "s_s1": "최소 MiB", + "s_s2": "최대 MiB", + "s_d1": "최소 ISO-8601", + "s_d2": "최대 ISO-8601", + "s_u1": "이후", + "s_u2": "이전", + "s_r1": "경로에 포함   (공백으로 구분)", + "s_f1": "이름에 포함   (-로 제외)", + "s_t1": "태그에 포함   (^=시작, 끝=$)", + "s_a1": "특정 메타데이터 속성", + + "md_eshow": "렌더링할 수 없음 ", + "md_off": "[📜<em>readme</em>]가 [⚙️]에서 비활성화됨 -- 문서 숨김", + + "badreply": "서버로부터의 응답을 구문 분석하지 못했습니다", + + "xhr403": "403: 접근 거부됨\n\nF5를 눌러보세요, 로그아웃되었을 수 있습니다", + "xhr0": "알 수 없음 (서버와의 연결이 끊겼거나 서버가 오프라인일 수 있습니다)", + "cf_ok": "죄송합니다 -- DD" + wah + "oS 보호 기능이 작동했습니다\n\n약 30초 후에 다시 정상적으로 작동할 것입니다\n\n아무 일도 일어나지 않으면 F5를 눌러 페이지를 새로고침하세요", + "tl_xe1": "하위 폴더를 나열할 수 없습니다:\n\n오류 ", + "tl_xe2": "404: 폴더를 찾을 수 없음", + "fl_xe1": "폴더의 파일을 나열할 수 없습니다:\n\n오류 ", + "fl_xe2": "404: 폴더를 찾을 수 없음", + "fd_xe1": "하위 폴더를 만들 수 없습니다:\n\n오류 ", + "fd_xe2": "404: 상위 폴더를 찾을 수 없음", + "fsm_xe1": "메시지를 보낼 수 없습니다:\n\n오류 ", + "fsm_xe2": "404: 상위 폴더를 찾을 수 없음", + "fu_xe1": "서버에서 주워담기 목록을 불러오지 못했습니다:\n\n오류 ", + "fu_xe2": "404: 파일을 찾을 수 없음??", + + "fz_tar": "압축되지 않은 gnu-tar 파일 (linux / mac)", + "fz_pax": "압축되지 않은 pax 형식 tar (느림)", + "fz_targz": "gzip 레벨 3 압축이 적용된 gnu-tar$N$N이것은 보통 매우 느리므로$N압축되지 않은 tar를 대신 사용하세요", + "fz_tarxz": "xz 레벨 1 압축이 적용된 gnu-tar$N$N이것은 보통 매우 느리므로$N압축되지 않은 tar를 대신 사용하세요", + "fz_zip8": "utf8 파일 이름이 포함된 zip (windows 7 및 이전 버전에서 문제가 있을 수 있음)", + "fz_zipd": "정말 오래된 소프트웨어를 위한 전통적인 cp437 파일 이름이 포함된 zip", + "fz_zipc": "MS-DOS PKZIP v2.04g (1993년 10월)용으로$Ncrc32가 미리 계산된 cp437$N(다운로드 시작 전 처리 시간이 더 걸림)", + + "un_m1": "아래에서 최근 업로드를 삭제하거나 미완료된 업로드를 중단할 수 있습니다", + "un_upd": "새로고침", + "un_m4": "또는 아래에 보이는 파일을 공유할 수 있습니다:", + "un_ulist": "보기", + "un_ucopy": "복사", + "un_flt": "선택적 필터:  URL에 포함되어야 함", + "un_fclr": "필터 지우기", + "un_derr": '주워담기-삭제 실패:\n', + "un_f5": '문제가 발생했습니다, 새로고침하거나 F5를 눌러보세요', + "un_uf5": "죄송하지만, 이 업로드를 중단하기 전에 페이지를 새로고침해야 합니다 (예: F5 또는 CTRL-R 누르기).", + "un_nou": '<b>경고:</b> 서버가 너무 바빠서 미완료 업로드를 표시할 수 없습니다; 잠시 후 "새로고침" 링크를 클릭하세요', + "un_noc": '<b>경고:</b> 완전히 업로드된 파일의 주워담기가 서버 구성에서 활성화/허용되지 않았습니다', + "un_max": "처음 2000개 파일을 표시합니다 (필터를 사용하세요)", + "un_avail": "{0}개의 최근 업로드를 삭제할 수 있습니다<br />{1}개의 미완료 업로드를 중단할 수 있습니다", + "un_m2": "업로드 시간순으로 정렬됨. 가장 최근 항목이 먼저 표시:", + "un_no1": "아쉽다! 충분히 최근인 업로드가 없습니다.", + "un_no2": "아쉽다! 해당 필터와 일치하는 최근 업로드가 없습니다.", + "un_next": "아래의 다음 {0}개 파일 삭제", + "un_abrt": "중단", + "un_del": "삭제", + "un_m3": "최근 업로드 로드 중...", + "un_busy": "{0}개 파일 삭제 중...", + "un_clip": "{0}개의 링크가 클립보드에 복사되었습니다", + + "u_https1": "더 나은 성능을 위해", + "u_https2": "https로 전환", + "u_https3": "하는 것이 좋습니다", + "u_ancient": '브라우저가 정말 오래되었네요 -- 아마도 <a href="#" onclick="goto(\'bup\')">bup을 대신 사용</a>해야 할 것 같습니다', + "u_nowork": "Firefox 53+, Chrome 57+ 또는 iOS 11+가 필요합니다", + "tail_2old": "Firefox 105+, Chrome 71+ 또는 iOS 14.5+가 필요합니다", + "u_nodrop": '브라우저가 너무 오래되어 드래그 앤 드롭 업로드를 지원하지 않습니다', + "u_notdir": '폴더가 아닙니다!\n\n브라우저가 너무 오래되었습니다,\n대신 드래그드롭을 시도해보세요', + "u_uri": '다른 브라우저 창에서 이미지를 드래그드롭하려면,\n큰 업로드 버튼 위로 떨어뜨려주세요', + "u_enpot": '<a href="#">단순 UI로 전환</a> (업로드 속도가 향상될 수 있음)', + "u_depot": '<a href="#">화려한 UI로 전환</a> (업로드 속도가 감소할 수 있음)', + "u_gotpot": '업로드 속도 향상을 위해 단순 UI로 전환합니다,\n\n언제든지 다시 전환하셔도 좋습니다!', + "u_pott": "<p>파일:   <b>{0}</b> 완료,   <b>{1}</b> 실패,   <b>{2}</b> 처리 중,   <b>{3}</b> 대기 중</p>", + "u_ever": "이것은 기본 업로더입니다. up2k는 최소한 다음 버전이 필요합니다:<br>Chrome 21 // Firefox 13 // Edge 12 // Opera 12 // Safari 5.1", + "u_su2k": '이것은 기본 업로더입니다. <a href="#" id="u2yea">up2k</a>가 더 좋습니다', + "u_uput": '속도 최적화 (체크섬 건너뛰기)', + "u_ewrite": '이 폴더에 쓰기 권한이 없습니다', + "u_eread": '이 폴더에 읽기 권한이 없습니다', + "u_enoi": '파일 검색이 서버 구성에서 활성화되지 않았습니다', + "u_enoow": '여기서는 덮어쓰기가 작동하지 않습니다. 삭제 권한이 필요합니다', + "u_badf": '총 {1}개 중 다음 {0}개의 파일은 파일 시스템 권한 문제 등으로 건너뛰었습니다:\n\n', + "u_blankf": '총 {1}개 중 다음 {0}개의 파일은 비어있습니다. 그래도 업로드하시겠습니까?\n\n', + "u_applef": '총 {1}개 중 다음 {0}개의 파일은 아마도 불필요한 파일일 것입니다.\n다음 파일을 건너뛰려면 <code>확인/Enter</code>를 누르세요,\n해당 파일도 업로드하려면 <code>취소/ESC</code>를 누르세요:\n\n', + "u_just1": '\n파일을 하나만 선택하면 더 잘 작동할 수 있습니다', + "u_ff_many": '<b>리눅스/macOS/안드로이드</b>를 사용 중이라면, 이 정도의 파일 수는 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">Firefox를 <em>충돌시킬 수 있습니다!</em></a>\n만약 그런 일이 발생하면 다시 시도하거나 Chrome을 사용해주세요.', + "u_up_life": '이 업로드는 완료 후 {0} 뒤에\n서버에서 삭제됩니다', + "u_asku": '이 {0}개의 파일을 <code>{1}</code>(으)로 업로드하시겠습니까?', + "u_unpt": '왼쪽 상단의 🧯를 사용하여 이 업로드를 취소/삭제할 수 있습니다', + "u_bigtab": '{0}개의 파일을 표시하려고 합니다\n\n브라우저가 충돌할 수 있습니다, 계속 진행합니까?', + "u_scan": '파일 스캔 중...', + "u_dirstuck": '디렉터리 반복자가 다음 {0}개 항목에 접근하는 데 실패하여 건너뜁니다:', + "u_etadone": '완료 ({0}, {1}개 파일)', + "u_etaprep": '(업로드 준비 중)', + "u_hashdone": '해싱 완료', + "u_hashing": '해시', + "u_hs": '핸드셰이킹 중...', + "u_started": '파일이 현재 업로드 중입니다. [🚀] 참조', + "u_dupdefer": '중복됨. 다른 모든 파일 처리 후 처리됩니다', + "u_actx": "다른 창/탭으로 전환 시 성능 저하를<br />방지하려면 이 텍스트를 클릭하세요", + "u_fixed": "OK!  해결됐습니다 👍", + "u_cuerr": "{1} 중 청크 {0} 업로드 실패;\n아마 문제 없을 겁니다. 계속 진행합니다\n\n파일: {2}", + "u_cuerr2": "서버가 업로드를 거부했습니다 (청크 {0}/{1});\n나중에 다시 시도합니다\n\n파일: {2}\n\n오류 ", + "u_ehstmp": "다시 시도합니다; 오른쪽 하단 참조", + "u_ehsfin": "서버가 업로드 완료 요청을 거부했습니다. 재시도 중...", + "u_ehssrch": "서버가 검색 수행 요청을 거부했습니다. 재시도 중...", + "u_ehsinit": "서버가 업로드 시작 요청을 거부했습니다. 재시도 중...", + "u_eneths": "업로드 핸드셰이크 중 네트워크 오류 발생. 재시도 중...", + "u_enethd": "대상 존재 여부 테스트 중 네트워크 오류 발생; 재시도 중...", + "u_cbusy": '네트워크 문제 후 서버가 다시 우리를 신뢰할 때까지 기다리는 중...', + "u_ehsdf": '서버 디스크 공간이 부족합니다!\n\n누군가 계속할 수 있을 만큼의 공간을\n비워줄 경우를 대비해 계속 재시도합니다', + "u_emtleak1": "웹 브라우저에 메모리 누수가 있는 것 같습니다.\n", + "u_emtleak2": ' <a href="{0}">https로 전환 (권장)</a>하거나 ', + "u_emtleak3": ' ', + "u_emtleakc": '다음을 시도해보세요:\n<ul><li><code>F5</code>를 눌러 페이지를 새로고침하세요</li><li>그런 다음 <code>⚙️ 설정</code>에서  <code>mt</code>  버튼을 비활성화하세요</li><li>그리고 다시 그 업로드를 시도해보세요</li></ul>업로드가 조금 느려지겠지만, 어쩔 수 없죠.\n불편을 드려 죄송합니다!\n\nPS: 이 버그는 Chrome v107에서 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">수정되었습니다</a>.', + "u_emtleakf": '다음을 시도해보세요:\n<ul><li><code>F5</code>를 눌러 페이지를 새로고침하세요</li><li>그런 다음 업로드 UI에서 <code>🥔</code>(단순 UI)를 활성화하세요<li>그리고 다시 그 업로드를 시도해보세요</li></ul>\nPS: Firefox에서 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">언젠가 이 버그가 수정될 거라 믿습니다</a>.', + "u_s404": '서버에서 찾을 수 없음', + "u_expl": '설명', + "u_maxconn": "대부분의 브라우저는 이를 6으로 제한하지만, Firefox에서는 <code>about:config</code>에서 <code>connections-per-server</code> 설정값으로 높일 수 있습니다.", + "u_tu": '<p class="warn">경고: 터보가 활성화되어 <span>클라이언트가 불완전한 업로드를 감지하고 재개하지 못할 수 있습니다. 터보 버튼의 툴팁을 참조하세요</span></p>', + "u_ts": '<p class="warn">경고: 터보가 활성화되어 <span>검색 결과가 부정확할 수 있습니다. 터보 버튼의 툴팁을 참조하세요</span></p>', + "u_turbo_c": "터보가 서버 구성에서 비활성화되었습니다", + "u_turbo_g": '이 볼륨 내에서 디렉터리 목록 권한이 없으므로\n터보를 비활성화합니다', + "u_life_cfg": '자동 삭제 시간 <input id="lifem" p="60" /> 분 (또는 <input id="lifeh" p="3600" /> 시간)', + "u_life_est": '업로드가 <span id="lifew" tt="현지 시간">---</span>에 삭제됩니다', + "u_life_max": '이 폴더는 최대 수명을\n{0}(으)로 강제합니다', + "u_unp_ok": '주워담기는 {0} 동안 허용됩니다', + "u_unp_ng": '주워담기는 허용되지 않습니다', + "ue_ro": '이 폴더에 대한 접근은 읽기 전용입니다\n\n', + "ue_nl": '현재 로그인되어 있지 않습니다', + "ue_la": '현재 \'{0}\'(으)로 로그인되어 있습니다', + "ue_sr": '현재 파일 검색 모드입니다\n\n큰 "검색" 버튼 옆의 돋보기 🔎를 클릭하여 업로드 모드로 전환한 후 다시 업로드해보세요\n\n죄송합니다', + "ue_ta": '다시 업로드해보세요, 이제 작동할 겁니다', + "ue_ab": '이 파일은 이미 다른 폴더로 업로드 중이며, 파일이 다른 곳에 업로드되기 전에 해당 업로드가 완료되어야 합니다.\n\n왼쪽 상단의 🧯를 사용하여 초기 업로드를 중단하고 잊을 수 있습니다.', + "ur_1uo": "OK: 파일이 성공적으로 업로드되었습니다", + "ur_auo": "OK: 모든 {0}개의 파일이 성공적으로 업로드되었습니다", + "ur_1so": "OK: 서버에서 파일을 찾았습니다", + "ur_aso": "OK: 서버에서 모든 {0}개의 파일을 찾았습니다", + "ur_1un": "업로드에 실패했습니다, 죄송", + "ur_aun": "모든 {0}개의 업로드에 실패했습니다, 죄송", + "ur_1sn": "서버에서 파일을 찾지 못했습니다", + "ur_asn": "서버에서 {0}개의 파일을 찾지 못했습니다", + "ur_um": "완료;\n{0}개 업로드 성공,\n{1}개 업로드 실패, 죄송", + "ur_sm": "완료;\n서버에서 {0}개 파일 찾음,\n서버에서 {1}개 파일 찾지 못함", + + "lang_set": '변경 사항을 적용하기 위해 새로고침하시겠습니까?' + }, + "nld": { + "tt": "Nederlands", + + "cols": { + "c": "Action knoppen", + "dur": "Duratie", + "q": "Kwaliteit / bitrate", + "Ac": "Audio codec", + "Vc": "Video codec", + "Fmt": "Formaat / container", + "Ahash": "Audio checksum", + "Vhash": "Video checksum", + "Res": "Resolution", + "T": "Bestandstype", + "aq": "Audio kwaliteit / bitrate", + "vq": "Video kwaliteit / bitrate", + "pixfmt": "Subsampling / pixel structure", + "resw": "Horizontale resolutie", + "resh": "Verticale resolutie", + "chs": "Audiokanalen", + "hz": "Samplefrequentie" + }, + + "hks": [ + [ + "diversen", + ["ESC", "Sluit verschillende dingen"], + + "bestand beheer", + ["G", "Verwissel tussen list / grid weergave"], + ["T", "Verwissel tussen miniaturen / iconen"], + ["⇧ A/D", "Thumbnail formaat"], + ["ctrl-K", "Verwijder geselecteerde"], + ["ctrl-X", "Knip selectie naar klembord"], + ["ctrl-C", "Kopieer selectie naar klembord"], + ["ctrl-V", "Hier plakken (verplaatsen/kopieëren)"], + ["Y", "Download geselecteerde"], + ["F2", "Hernoem geselecteerde"], + + "bestand-lijst-selectie", + ["space", "wissel bestand selectie"], + ["↑/↓", "verplaats selectie cursor"], + ["ctrl ↑/↓", "verplaats cursor en scherm"], + ["⇧ ↑/↓", "select vorige/volgende bestand"], + ["ctrl-A", "selecteer alle bestanden / mappen"], + ], [ + "navigatie", + ["B", "verwissel breadcrumbs / navpane"], + ["I/K", "Vorige/volgende map"], + ["M", "Bovenliggende map (of huidige uitvouwen)"], + ["V", "Berwissel map / tekstbestand in navpane"], + ["A/D", "Navpane formaat"], + ], [ + "muziek-speler", + ["J/L", "Vorige/volgende song"], + ["U/O", "Skip 10sec terug/vooruit"], + ["0..9", "Spring naar 0%..90%"], + ["P", "Speel/pauzeer (start ook)"], + ["S", "Selecteer afspelende song"], + ["Y", "Download song"], + ], [ + "afbeelding viewer", + ["J/L, ←/→", "Vorige/volgende afbeelding"], + ["Home/End", "Eerste/laatste afbeelding"], + ["F", "Volledig scherm"], + ["R", "Draai rechtsom"], + ["⇧ R", "Draai linksom"], + ["S", "Selecteer afbeelding"], + ["Y", "Download afbeelding"], + ], [ + "video-speler", + ["U/O", "Skip 10sec terug/vooruit"], + ["P/K/Space", "Speel/pauze"], + ["C", "Verder met volgende"], + ["V", "herhaal"], + ["M", "stil"], + ["[ and ]", "zet herhaal interval"], + ], [ + "tekstbestand-viewer", + ["I/K", "vorige/volgende bestand"], + ["M", "sluit tekst bestand"], + ["E", "bewerk tekst bestand"], + ["S", "selecteer bestand (voor knip/kopie/hernoem)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Annuleren", + + "enable": "Inschakelen", + "danger": "GEVAARLIJK", + "clipped": "Gekopieërd naar klembord", + + "ht_s1": "seconde", + "ht_s2": "secondes", + "ht_m1": "minuut", + "ht_m2": "minuten", + "ht_h1": "uur", + "ht_h2": "uur", + "ht_d1": "dag", + "ht_d2": "dagen", + "ht_and": " en ", + + "goh": "Beheer-paneel", + "gop": 'Vorige map">Vorige', + "gou": 'Bovenligende map">Omhoog', + "gon": 'Volgende map">Volgende', + "logout": "Uitloggen ", + "login": "Inloggen", //m + "access": " Toegang", + "ot_close": "Sluit onder-menu", + "ot_search": "Zoek voor bestanden bij attributes, pad / naam, muziek tags, of elk andere combinatie tussen$N$N<code>foo bar</code> = moet beide «foo» en «bar» bevatten,$N<code>foo -bar</code> = moet «foo» bevatten maar geen «bar»,$N<code>^yana .opus$</code> = start met «yana» en moet een «opus» bestand zijn$N<code>"try unite"</code> = moet precies «try unite» bevatten$N$Nde datum formaat is iso-8601, zoals$N<code>2009-12-31</code> of <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: verwijder je recente uploads, of onvoltooide uploads afbreken", + "ot_bup": "bup: Basisuploader, supports zelfs netscape 4.0", + "ot_mkdir": "mkdir: Maak een nieuwe map", + "ot_md": "new-md: Maak een nieuwe markdown bestand", + "ot_msg": "msg: Verstuur een bericht naar de server logs", + "ot_mp": "Media speler opties", + "ot_cfg": "Configuratie opties", + "ot_u2i": 'up2k: upload bestanden (als je schrijf toegang hebt) of verwissel naar zoek-mode om te zien of ze ergens bestaan op de server$N$Nuploads zijn hervatbaar, multithreaded, en bestandstijdstempels blijven behouden, maar het gebruikt meer CPU dan [🎈]  (de basic uploader)<br /><br />tijdens het uploaden, dit icoon word dan een progress indicatie!', + "ot_u2w": 'up2k: upload bestanden met hervattings ondersteuning (sluit je webbrowser en selecteer dezelfde bestand later opnieuw)$N$Nmultithreaded, en bestandstijdstempels blijven behouden, maar het gebruikt meer CPU dan [🎈]  (de basic uploader)<br /><br />tijdens het uploaden, dit icoon word dan een progress indicatie!', + "ot_noie": 'Gebruik alstublieft Chrome / Firefox / Edge', + + "ab_mkdir": "maak map", + "ab_mkdoc": "nieuw markdown doc", + "ab_msg": "verstuur msg naar srv log", + + "ay_path": "skip naar mappen", + "ay_files": "skip naar bestanden", + + "wt_ren": "Hernoem geselecteerde items$NHotkey: F2", + "wt_del": "Berwijder geselecteerde items$NHotkey: ctrl-K", + "wt_cut": "Knip geselecteerde items <small>(en plak het ergens anders)</small>$NHotkey: ctrl-X", + "wt_cpy": "Kopieer geselecteerde items naar klembord$N(om te plakken ergens anders)$NHotkey: ctrl-C", + "wt_pst": "Plak eeen laatst geknipte / gekopieërde selectie$NHotkey: ctrl-V", + "wt_selall": "Selecteer alle bestanden$NHotkey: ctrl-A (wanneer bestand gefocused is)", + "wt_selinv": "Selectie omkeren", + "wt_zip1": "Download deze map als archief", + "wt_selzip": "Download selectie als archief", + "wt_seldl": "Download selectie als losse bestanden$NHotkey: Y", + "wt_npirc": "Kopieer irc-geformarteerde track info", + "wt_nptxt": "Kopieer platte tekst track info", + "wt_m3ua": "Aan m3u afspeellijst toevoegen (klik <code>📻kopieer</code> later)", + "wt_m3uc": "Kopieer m3u playlist naar klembord", + "wt_grid": "Verwissel grid / lijst weergave$NHotkey: G", + "wt_prev": "Vorig nummer$NHotkey: J", + "wt_play": "Afspelen / pauzeer$NHotkey: P", + "wt_next": "Volgend nummer$NHotkey: L", + + "ul_par": "Parallel uploads:", + "ut_rand": "Willekeurige bestandsnaam", + "ut_u2ts": "Kopieer de laatste-gewijzigde tijdstamp$Nvan je bestandsysteem naar de server\">📅", + "ut_ow": "Overschrijf bestaande bestanden op de server?$N🛡️: nooit (zal in plaats daarvan een nieuwe bestandsnaam genereren)$N🕒: overschrijven als de server-bestand ouder is dan het geüploade bestand$N♻️: altijd overschrijven als de bestanden verschillend zijn", + "ut_mt": "Ga door met hashen van andere bestanden tijdens het uploaden$N$Moet je misschien uitschakelen als je CPU of HDD het niet aan kan", + "ut_ask": 'Vraag voor bevestiging voordat het uploaden start">💭', + "ut_pot": "Verbeter de uploadsnelheid voor langzame apparaten$Ndoor de interface minder complex te maken", + "ut_srch": "Niet uploaden, maar check of de bestanden als op de server bestaan$N (checkt alle mappen die waar jij toegang op hebt)", + "ut_par": "Pauzeer bij zetten het op 0$N$Nverhoog als je verbinding traag is$N$Nhou het op 1 als je netwerk of server HDD het niet aankan", + "ul_btn": "Drop bestanden / mappen<br>hier (of klik mij)", + "ul_btnu": "U P L O A D", + "ul_btns": "Z O E K E N", + + "ul_hash": "Hashing", + "ul_send": "Versturen", + "ul_done": "Klaar", + "ul_idle1": "Geen uploads in wachtrij", + "ut_etah": "Gemiddelde <em>hashing</em> snelheid en geschatte tijd tot de voltooiing", + "ut_etau": "Gemiddelde <em>verzend</em> snelheid en geschatte tijd tot voltooiing", + "ut_etat": "Gemiddelde <em>totale</em> snelheid en geschatte tijd tot voltooiing", + + "uct_ok": "Succesvol afgerond", + "uct_ng": "Niet goed: gefaald / geweigerd / niet gevonden", + "uct_done": "ok en ng gecombineerd", + "uct_bz": "Hashing van uploads", + "uct_q": "Inactief, in afwachting", + + "utl_name": "Bestandsnaam", + "utl_ulist": "Lijst", + "utl_ucopy": "Kopieer", + "utl_links": "Links", + "utl_stat": "Status", + "utl_prog": "Vooruitgang", + + // keep short: + "utl_404": "404", + "utl_err": "FOUT", + "utl_oserr": "OS-FOUT", + "utl_found": "gevonden", + "utl_defer": "Uitgesteld", + "utl_yolo": "YOLO", + "utl_done": "klaar", + + "ul_flagblk": "De bestanden zijn toegevoegd aan de wachtrij</b><br>maar er is een drukke up2k bezig in een andere tabblad,<br>wachten totdat die eerst klaar is", + "ul_btnlk": "De server configuratie heeft deze schakelaar versleuteld in deze staat", + + "udt_up": "Upload", + "udt_srch": "Zoeken", + "udt_drop": "Laat hier los", + + "u_nav_m": '<h6>Hey, wat heb jij daar?</h6><code>Enter</code> = Bestanden (een of meer)\n<code>ESC</code> = Een map (inclusief submappen)', + "u_nav_b": '<a href="#" id="modal-ok">Bestanden</a><a href="#" id="modal-ng">Een map</a>', + + "cl_opts": "Switches", + "cl_themes": "Thema", + "cl_langs": "Taal", + "cl_ziptype": "Download map als", + "cl_uopts": "up2k switches", + "cl_favico": "Favicon", + "cl_bigdir": "Item limiet in map", + "cl_hsort": "#sorteer", + "cl_keytype": "Key notaties", + "cl_hiddenc": "Verborgen kolomen", + "cl_hidec": "Verborgen", + "cl_reset": "Reset", + "cl_hpick": "Tik op de kolomkoppen om ze in de onderstaande tabel te verbergen", + "cl_hcancel": "Kolumn verbergen geannuleerd", + + "ct_grid": '田 grid', + "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_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', + "ct_qdel": 'Waneeer je een bestand verwijderd, vraag eenmalig om bevestiging">qdel', + "ct_dir1st": 'Sorteer mappen eerst en dan de bestanden">📁 first', + "ct_nsort": 'Natural sort (voor bestandsnamen dat beginnen met getallen)">nsort', + "ct_readme": 'Laat README.md in mappen lijst zien">📜 readme', + "ct_utc": 'Toon alle datums en tijden in UTC">UTC', + "ct_idxh": 'Laat index.html zien in plaats van de map overzicht">htm', + "ct_sbars": 'Laat scrollbars zien">⟊', + + "cut_umod": "Als een bestand al bestaat op de server, update de 'gewijzigd' waarde op het bestand wat op de server staat met het bestand wat je geupload hebt (vereist schrijf+verwijder rechten)\">re📅", + + "cut_turbo": "De yolo knop, die wil jij waarschijnlijk NIET actief wilt hebben:$N$Ngebruik dit als je heel veel bestanden gaat uploaden EN je moest het herstarten voor een reden en je wilt doorgaan met uploaden ASAP$N$Ndit vervangt de hash-check met een simpele <em>"heeft dit dezelfde bestands groote op de server?"</em>, zo als de bestands inhoud verschillend is, dan worden ze NIET geupload$N$NJe zou deze optie weer uit moeten zetten als de upload klaar is en dan "upload" de zelfde bestanden opnieuw uploaden zo de client het kan verifieren\">turbo", + + "cut_datechk": "Heeft geen effect tenzij de turbo knop actief is$N$Nverminder de yolo factor (een klein beetje); controlleert of de bestand tijdstamp op de server hetzelfde is met het geuploade bestand$N$Ndit zou <em>in theorie</em> de meest onvoltooide/onvoledige uploads, maar dit is geen vervaning voor de verificatie-check met de turbo knop uitgeschakeld daarna\">date-chk", + + "cut_u2sz": "Grote (in MiB) voor elk geuploade stuk; grote waardes vliegen beter over de Atlantische Oceaan. Probeer lage waardes op zeer onstabiele verbindingen", + + "cut_flag": "Alleen een tabblad kan bestanden uploaden $N -- andere tabbladen moeten deze optie ook actief hebben $N -- dit heeft alleen effect op de tabbladen die op hetzelfde domain zijn", + + "cut_az": "Bestanden uploaden in alfabetische volgorde, in plaats van kleinste bestanden eerst$N$Nalfabetische volgorde kan het makkelijker maken om te zien of er wat fout is gegaan op de server, dit maakt het uploaden ietsjes trager op fiber / LAN", + + "cut_nag": "Systeem notificatie weergeven als een upload voltooid is$N(alleen als de browser of tabblad niet actief is)", + "cut_sfx": "Geluid waarschuwing afspelen als een upload voltooid is$N(alleen als de browser of tabblad niet actief is)", + + "cut_mt": "Gebruik multithreading om bestands-hashing te versnellen$N$Ndit gebruikt web-workers en vereist$Nmeer geheugen (tot wel 512 MiB extra)$N$Nmaakt https 30% sneller en http 4.5x sneller\">mt", + + "cut_wasm": "Gebruik wasm in plaats van de webbrowser ingebouwde hasher; verbetert de snelheid op chrome-gebaseerde webbrowsers maar verhoogd CPU gebruik, veel oude versie van chrome hebben een bug dat een geheugen lek heeft, dat kan alle geheugen in gebruik nemen en crashen als dit actief is\">wasm", + + "cft_text": "Favicon tekst (laat leeg en vernieuw om uit te schakelen)", + "cft_fg": "Voorgrondkleur", + "cft_bg": "Achtergrondkleur", + + "cdt_lim": "Max aantal bestanden laten zien in een map", + "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.", + + "tt_entree": "Laat navpane zien (directoryboom zijbalk)$NHotkey: B", + "tt_detree": "Laat breadcrumbs zien$NHotkey: B", + "tt_visdir": "Scroll naar geselecteerde map", + "tt_ftree": "Verwissel tussen directoryboom / tekst bestanden$NHotkey: V", + "tt_pdock": "Laat bovenliggende mappen zien in een vastgezet deelvenster bovenaan", + "tt_dynt": "Automatisch groeien naarmate de directoryboom zich uitbreidt", + "tt_wrap": "Automatische terugloop", + "tt_hover": "Laat overlopenden lijnen zien bij zweven$N(stopt het scrollen tenzij de muis in de linker gedeelte van het scherm is)", + + "ml_pmode": "Aan het einde van de map...", + "ml_btns": "Cmds", + "ml_tcode": "Transcode", + "ml_tcode2": "Transcode naar", + "ml_tint": "Tint", + "ml_eq": "Audio-equalizer", + "ml_drc": "Dynamisch bereikcompressor", + + "mt_loop": "Loop/herhaal een nummer\">🔁", + "mt_one": "Stop na een nummer\">1️⃣", + "mt_shuf": "Shuffle alle muziek in alle mappen\">🔀", + "mt_aplay": "Autoplay als er een song-ID staat in de link waarop je hebt geklikt om naar de server te gaan$N$NAls u dit uitschakelt, wordt de pagina-URL ook niet meer bijgewerkt met nummer-ID's tijdens het afspelen van muziek. Dit voorkomt automatisch afspelen als deze instellingen verloren gaan, maar de URL behouden blijft.\">a▶", + "mt_preload": "Begin het laden van de volgende nummer vlak voordat de huidige nummer het einde bereikt voor gapless playback\">preload", + "mt_prescan": "Ga naar de volgende map voordat de laatste nummer eindigd$NMaakt de webbrower blij$NZo het afspelen van muziek niet gestopt word\">nav", + "mt_fullpre": "Probeer het hele nummer vooraf te laden;$N✅ activeer dit op <b>onstabiele</b> verbindingen,$N❌ <b>zet uit</b> als je waarschijnlijk een trage verbinding hebt\">full", + "mt_fau": "Op telefoons, voorkom muziek van stoppen als de volgende nummer niet snel genoeg voorgeladen is (kan de weergave van tags glitchy maken)\">☕️", + "mt_waves": "Waveform zoekbar:$NToon audio-amplitude in de zoekbar\">~s", + "mt_npclip": "Knoppen tonen voor het clipboarden van het nummer dat op dat moment wordt afgespeeld\">/np", + "mt_m3u_c": "Knoppen tonen om de geselecteerde nummers als m3u8-afspeellijstitems te clipboarden\">📻", + "mt_octl": "OS-integratie (media hotkeys / osd)\">os-ctl", + "mt_oseek": "Zoeken via os-integratie mogelijk maken$N$NNotitie: op sommige toestellen (iPhones) dit vervcangt de volgende-nummer knop\">seek", + "mt_oscv": "Albumhoes weergeven in osd\">art", + "mt_follow": "Het afgespeelde nummer in beeld houden\">🎯", + "mt_compact": "Compacte bedieningselementen\">⟎", + "mt_uncache": "Cache wissen  (Probeer dit als uw browser een kapotte kopie van een nummer heeft gecached, waardoor het niet afgespeeld kan worden)\">uncache", + "mt_mloop": "De open map herhalen\">🔁 loop", + "mt_mnext": "Laad de volgende map en ga verder\">📂 next", + "mt_mstop": "Stoppen met afspelen\">⏸ stop", + "mt_cflac": "flac / wav omzetten naar {0}\">flac", + "mt_caac": "aac / m4a omzetten naar {0}\">aac", + "mt_coth": "Alle andere bestanden (geen mp3) converteren naar {0}\">oth", + "mt_c2opus": "Beste keuze voor computers, laptops, android\">opus", + "mt_c2owa": "opus-weba, voor iOS 17.5 en nieuwer\">owa", + "mt_c2caf": "opus-caf, voor iOS 11 tot en met iOS 17\">caf", + "mt_c2mp3": "Gebruik dit hele oude toestellen\">mp3", + "mt_c2flac": "Beste geluidskwaliteit, maar grote downloads\">flac", //m + "mt_c2wav": "Ongemprimeerde weergave (nog groter)\">wav", //m + "mt_c2ok": "Mooi, goede keuze", + "mt_c2nd": "Dat is niet het aanbevolen uitvoerformaat voor uw apparaat, maar dat is prima", + "mt_c2ng": "Uw apparaat lijkt dit uitvoerformaat niet te ondersteunen, maar we gaan het toch proberen", + "mt_xowa": "iOS bevat bugs waardoor dit formaat niet op de achtergrond kan worden afgespeeld; gebruik in plaats daarvan caf of mp3.", + "mt_tint": "Achtergrond helderheid (0-100) op de zoekbalk om bufferen minder storend te maken", + "mt_eq": "Schakelt de equalizer en gain-control in;$N$Nboost <code>0</code> = standaard 100% volume (ongeweijzigd)$N$Nwidth <code>1  </code> = standaard stereo (ongeweijzigd)$Nwidth <code>0.5</code> = 50% links-rechts crossfeed$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = stemverwijdering :^)$N$NDoor de equalizer in te schakelen, worden gapless albums volledig gapless. Laat hem dus aanstaan met alle waarden op nul (behalve width = 1) als je dat belangrijk vindt.", + "mt_drc": "Schakelt de dynamic range compressor in (volume flattener / brickwaller); schakelt ook EQ in om de spaghetti te balanceren, dus zet alle EQ velden behalve ‘width’ op 0 als je dat niet wilt.$N$Nverlaagt het volume van audio boven THRESHOLD dB; voor elke RATIO dB voorbij THRESHOLD is er 1 dB output, dus standaardwaarden van tresh -24 en ratio 12 betekenen dat het nooit luider dan -22 dB zou moeten worden en het is veilig om de equalizer boost te verhogen tot 0.8, of zelfs 1.8 met ATK 0 en een enorme RLS zoals 90 (werkt alleen in firefox; RLS is max 1 in andere browsers)$N$N(zie wikipedia, die legt het veel beter uit)", + + "mb_play": "Afspelen", + "mm_hashplay": "Deze audio bestand afspelen?", + "mm_m3u": "Druk op <code>Enter/OK</code> om af te spelen\nDruk op <code>ESC/Annuleren</code> om te bewerken", + "mp_breq": "Heeft firefox 82+ of chrome 73+ of iOS 15+", + "mm_bload": "Aan het laden...", + "mm_bconv": "Opmzetten naar {0}, even geduld...", + "mm_opusen": "Uw browser kan geen aac / m4a-bestanden afspelen;\ntranscodering naar opus is nu ingeschakeld", + "mm_playerr": "Afspelen mislukt: ", + "mm_eabrt": "De afspeelpoging is geannuleerd", + "mm_enet": "Je internetverbinding is onstabiel", + "mm_edec": "Dit bestand is vermoedelijk beschadigd??", + "mm_esupp": "Uw browser begrijpt deze audio-formaat niet", + "mm_eunk": "Onbekende fout", + "mm_e404": "Kan audio niet afspelen; fout 404: Bestand niet gevonden..", + "mm_e403": "Kan audio niet afspelen; fout 403: Toegang geweigerd.\n\nProbeer op F5 te drukken om opnieuw te laden, misschien ben je uitgelogd", + "mm_e500": "Kan geen audio afspelen; fout 500: Controleer serverlogs.", + "mm_e5xx": "Kan geen audio afspelen; serverfout ", + "mm_nof": "Geen audiobestanden meer vinden in de buurt", + "mm_prescan": "Op zoek naar muziek om als volgende te spelen...", + "mm_scank": "Het volgende nummer gevonden:", + "mm_uncache": "Cache gewist; alle nummers worden opnieuw gedownload bij de volgende keer afspelen", + "mm_hnf": "Dat liedje bestaat niet meer", + + "im_hnf": "Deze afbeelding bestaat niet meer", + + "f_empty": 'Deze map is leeg', + "f_chide": 'Dit verbergt kolom «{0}»\n\nje kunt kolommen verbergen op de instellingen tabblad', + "f_bigtxt": "Dit bestand is {0} MiB groot -- echt bekijken als tekst?", + "f_bigtxt2": "Wilt u alleen het einde van het bestand bekijken? Dit maakt ook volgen/tailen mogelijk, waarbij nieuw toegevoegde tekstregels in realtime worden weergegeven.", + "fbd_more": '<div id="blazy"><code>{0}</code> van de <code>{1}</code> bestanden weergegeven; <a href="#" id="bd_more">Toon {2}</a> of <a href="#" id="bd_all">Laat alles zien</a></div>', + "fbd_all": '<div id="blazy"><code>{0}</code> van de <code>{1}</code> bestanden weergegeven; <a href="#" id="bd_all">Laat alles zien</a></div>', + "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_partial": "Om een bestand dat momenteel wordt geüpload veilig te downloaden, klikt u op het bestand met dezelfde bestandsnaam, maar zonder de bestandsextensie <code>.PARTIAL</code>. 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 <code>.PARTIAL</code> scratchbestand, waardoor u vrijwel zeker beschadigde gegevens krijgt.", + + "ft_paste": "plakken {0} items$NHotkey: ctrl-V", + "fr_eperm": 'kan de naam niet wijzigen:\nje hebt geen “move” rechten in deze map', + "fd_eperm": 'kan niet verwijderen:\nje hebt geen “delete” rechten in deze map', + "fc_eperm": 'kan niet knippen:\nje hebt geen “move” rechten in deze map', + "fp_eperm": 'kan niet plakken:\nje hebt geen “schrijf” rechten in deze map', + "fr_emore": "selecteer ten minste één item om te hernoemen", + "fd_emore": "selecteer minstens één item om te verwijderen", + "fc_emore": "selecteer ten minste één item om te knippen", + "fcp_emore": "selecteer ten minste één item om naar het klembord te kopiëren", + + "fs_sc": "Deel de map waarin je je bevindt", + "fs_ss": "De geselecteerde bestand(en) delen", + "fs_just1d": "U kunt niet meer dan één map selecteren\nof mix bestanden en mappen in één selectie", + "fs_abrt": "❌ Afbreken", + "fs_rand": "🎲 rand.naam", + "fs_go": "✅ Maak share", + "fs_name": "Naam", + "fs_src": "Bron", + "fs_pwd": "Wachtwoord", + "fs_exp": "Verloopt", + "fs_tmin": "min", + "fs_thrs": "uur", + "fs_tdays": "dag(en)", + "fs_never": "eeuwig", + "fs_pname": "Optionele linknaam; is willekeurig als deze leeg is", + "fs_tsrc": "Het bestand of de map die u wilt delen", + "fs_ppwd": "Optioneel wachtwoord", + "fs_w8": "Delen...", + "fs_ok": "Druk op <code>Enter/OK</code> naar klembord te zetten\Druk op <code>ESC/Annuleren</code> om te sluiten", + + "frt_dec": "Kan sommige gevallen van gebroken bestandsnamen oplossen\">url-decode", + "frt_rst": "Gewijzigde bestandsnamen terugzetten naar de oorspronkelijke namen\">↺ reset", + "frt_abrt": "Afbreken en dit venster sluiten\">❌ Annuleren", + "frb_apply": "HERNOEMEN TOEPASSEN", + "fr_adv": "Batch / metadata / patroon hernoemen\">Geavanceerd", + "fr_case": "Hoofdlettergevoelige regex\">case", + "fr_win": "Windows-veilige namen; vervangen <code><>:"\\|?*</code> met japanse tekens over de volledige breedte\">win", + "fr_slash": "Vervang <code>/</code> met een teken waardoor er geen nieuwe mappen worden gemaakt\">geen /", + "fr_re": "Regex zoekpatroon om toe te passen op originele bestandsnamen; naar capturing groups kan worden verwezen in het onderstaande opmaakveld zoals <code>(1)</code> en <code>(2)</code> enzovoort", + "fr_fmt": "Geïnspireerd door foobar2000 :$N<code>(titel)</code> wordt vervangen door de titel van het nummer,$N<code>[(artiest) - ](titel)</code> sla [dit] gedeelte over als artiest leeg is$N<code>$lpad((tn),2,0)</code> vult tracknummer op tot 2 cijfers (0X)", + "fr_pdel": "Verwijderen", + "fr_pnew": "Opslaan als", + "fr_pname": "Geef een naam op voor je nieuwe preset", + "fr_aborted": "Afgebroken", + "fr_lold": "Oude naam", + "fr_lnew": "Nieuwe naam", + "fr_tags": "Tags voor de geselecteerde bestanden (alleen-lezen, alleen ter referentie):", + "fr_busy": "Hernoemen van {0} items...\n\n{1}", + "fr_efail": "Hernoemen mislukt:\n", + "fr_nchg": "{0} van de nieuwe namen zijn gewijzigd als gevolg van <cod>win</code> en/of <code>geen /</code>\n\nOK om door te gaan met deze gewijzigde nieuwe namen?", + + "fd_ok": "Verwijderen OK", + "fd_err": "Verwijderen mislukt:\n", + "fd_none": "Er is niets verwijderd; misschien geblokkeerd door serverconfiguratie (xbd)?", + "fd_busy": "{0} items verwijderen...\n\n{1}", + "fd_warn1": "VERWIJDER deze {0} items?", + "fd_warn2": "<b>LAATSTE KANS!</b> Geen manier om ongedaan te maken. Verwijderen?", + + "fc_ok": "Knip {0} items", + "fc_warn": 'Knip {0} items\n\nmaar: alleen <b>deze</b> browser-tabblad kan weer plakken\n(omdat de selectie zo enorm is)', + + "fcc_ok": "{0} items naar klembord gekopieerd", + "fcc_warn": '{0} items naar klembord gekopieerd\n\maar: alleen <b>deze</b> browser-tabblad kan weer plakken\n(omdat de selectie zo enorm is)', + + "fp_apply": "Gebruik deze namen", + "fp_ecut": "Knip of kopieer eerst enkele bestanden/mappen om te verplaatsen/plakken\n\nnotitie: je kunt knippen/plakken in verschillende browsertabbladen", + "fp_ename": "{0} items kunnen hier niet worden verplaatst omdat de namen al in gebruik zijn. Geef ze hieronder een nieuwe naam om verder te gaan, of verwijder de naam om ze over te slaan:", + "fcp_ename": "{0} items kunnen hier niet worden gekopieerd omdat de namen al in gebruik zijn. Geef ze hieronder een nieuwe naam om verder te gaan, of verwijder de naam om ze over te slaan:", + "fp_emore": "Er zijn nog enkele bestandsnaambotsingen die moeten worden opgelost", + "fp_ok": "Verplaatsen OK", + "fcp_ok": "Kopiëren OK", + "fp_busy": "{0} items verplaatsen...\n\n{1}", + "fcp_busy": "{0} items kopiëren...\n\n{1}", + "fp_abrt": "afbreken...", //m + "fp_err": "Verplaatsen mislukt:\n", + "fcp_err": "Kopieëren mislukt:\n", + "fp_confirm": "Verplaats deze {0} items hierheen?", + "fcp_confirm": "Kopieer deze {0} items hier?", + "fp_etab": 'Kan klembord van ander browsertabblad niet lezen', + "fp_name": "Een bestand uploaden vanaf uw apparaat. Geef het een naam:", + "fp_both_m": '<h6>Kies wat je wilt plakken</h6><code>Enter</code> = Verplaatsen {0} bestanden van «{1}»\n<code>ESC</code> = Upload {2} bestanden van je apparaat', + "fcp_both_m": '<h6>Kies wat je wilt plakken</h6><code>Enter</code> = Kopieer {0} bestanden van «{1}»\n<code>ESC</code> = Upload {2} bestanden van je apparaat', + "fp_both_b": '<a href="#" id="modal-ok">Verplaats</a><a href="#" id="modal-ng">Upload</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopieer</a><a href="#" id="modal-ng">Upload</a>', + + "mk_noname": "Voer een naam in het tekstveld aan de linkerkant voordat je verder gaat :p", + + "tv_load": "Tekstdocument laden:\n\n{0}\n\n{1}% ({2} van de {3} MiB geladen)", + "tv_xe1": "Kon tekstbestand niet laden:\n\nfout ", + "tv_xe2": "404, bestand niet gevonden", + "tv_lst": "Lijst met tekstbestanden in", + "tvt_close": "Terugkeren naar mapweergave$NHotkey: M (of Esc)\">❌ Sluiten", + "tvt_dl": "Download dit bestand$NHotkey: Y\">💾 download", + "tvt_prev": "Vorig document tonen$NHotkey: i\">⬆ prev", + "tvt_next": "Volgende document tonen$NHotkey: K\">⬇ next", + "tvt_sel": "Selecteer bestand   ( voor knip / verplaats / verwijder / ... )$NHotkey: S\">sel", + "tvt_edit": "Bestand openen in teksteditor$NHotkey: E\">✏️ bewerk", + "tvt_tail": "Bestand controleren op wijzigingen; nieuwe regels in realtime weergeven\">📡 volgen", + "tvt_wrap": "Automatische terugloop\">↵", + "tvt_atail": "Vergrendelen scroll naar onderkant van pagina\">⚓", + "tvt_ctail": "Kleuren van terminals decoderen (ansi escape codes)\">🌈", + "tvt_ntail": "Terugrollimiet (hoeveel tekst geladen moeten blijven)", + + "m3u_add1": "Nummer toegevoegd aan m3u afspeellijst", + "m3u_addn": "{0} nummers toegevoegd aan m3u-afspeellijst", + "m3u_clip": "m3u-afspeellijst nu gekopieerd naar klembord\n\nje moet een nieuw tekstbestand maken met de naam iets.m3u en de afspeellijst in dat document plakken; dit maakt het afspeelbaar", + + "gt_vau": "Laat geen video's zien, speel alleen de audio af\">🎧", + "gt_msel": "Schakel bestandsselectie in; ctrl-klik op een bestand om te openen$N$N<em>indien actief: dubbelklik op een bestand / map om het te openen</em>$N$NHotkey: S\">multiselect", + "gt_crop": "Gecentreerde miniaturen\">crop", + "gt_3x": "Hi-res miniaturen\">3x", + "gt_zoom": "Zoom", + "gt_chop": "Verkorten", + "gt_sort": "Sorteer bij", + "gt_name": "naam", + "gt_sz": "grootte", + "gt_ts": "datum", + "gt_ext": "type", + "gt_c1": "Bestandsnamen meer inkorten (minder tonen)", + "gt_c2": "Bestandsnamen minder inkorten (meer tonen)", + + "sm_w8": "Zoeken...", + "sm_prev": "Onderstaande zoekresultaten zijn afkomstig van een eerdere zoekopdracht:\n ", + "sl_close": "Zoekresultaten sluiten", + "sl_hits": "Toont {0} treffers", + "sl_moar": "Laad meer", + + "s_sz": "grootte", + "s_dt": "datum", + "s_rd": "pad", + "s_fn": "naam", + "s_ta": "tags", + "s_ua": "op@", + "s_ad": "adv.", + "s_s1": "Minimaal MiB", + "s_s2": "Maximaal MiB", + "s_d1": "Min. iso8601", + "s_d2": "Max. iso8601", + "s_u1": "Uploaded na", + "s_u2": "en/of voor", + "s_r1": "Pad bevad   (spatie-gescheiden)", + "s_f1": "Naam bevat   (ontkennen met -nope)", + "s_t1": "Tags bevat   (^=start, einde=$)", + "s_a1": "Specifieke metadata-eigenschappen", + + "md_eshow": "Kan niet weergeven ", + "md_off": "[📜<em>readme</em>] uitgeschakeld in [⚙️] -- document verborgen", + + "badreply": "Mislukt om antwoord van server te parsen", + + "xhr403": "403: Toegang geweigerd\n\nprobeer F5 in te drukken, misschien ben je uitgelogd", + "xhr0": "Onbekend (waarschijnlijk verbinding met server verloren of server is offline)", + "cf_ok": "Sorry daarvoor -- DD" + wah + "OS-bescherming ingeschakeld\n\nalles zou binnen ongeveer 30 seconden moeten hervatten\n\nals er niets gebeurt, druk dan op F5 om de pagina opnieuw te laden", + "tl_xe1": "Kon submappen niet weergeven:\n\nfout ", + "tl_xe2": "404: Map niet gevonden", + "fl_xe1": "Kon bestanden in map niet weergeven:\n\nfout ", + "fl_xe2": "404: Map niet gevonden", + "fd_xe1": "Kon submap niet aanmaken:\n\nfout ", + "fd_xe2": "404: Bovenliggende map niet gevonden", + "fsm_xe1": "Kon bericht niet verzenden:\n\nfout ", + "fsm_xe2": "404: Bovenliggende map niet gevonden", + "fu_xe1": "Mislukt om unpost lijst van server te laden:\n\nfout ", + "fu_xe2": "404: Bestand niet gevonden??", + + "fz_tar": "gnu-tar bestand uitpakken (linux / mac)", + "fz_pax": "pax-formaat tar uitpakken (trager)", + "fz_targz": "gnu-tar met gzip niveau 3 compressie$N$Ndit is meestal erg langzaam, dus gebruik in plaats daarvan ongecomprimeerde tar", + "fz_tarxz": "gnu-tar met xz-niveau 1 compressie$N$Ndit is meestal erg langzaam, dus gebruik in plaats daarvan ongecomprimeerde tar", + "fz_zip8": "Zip met utf8 bestandsnamen (misschien onhandig op windows 7 en ouder)", + "fz_zipd": "Zip met traditionele cp437-bestandsnamen, voor echt oude software", + "fz_zipc": "cp437 met crc32 vroeg berekend$Nvoor MS-DOS PKZIP v2.04g (oktober 1993)$N(het duurt langer voordat het downloaden kan beginnen)", + + "un_m1": "Hieronder kunt u uw recente uploads verwijderen (of onvoltooide uploads afbreken)", + "un_upd": "Vernieuwen", + "un_m4": "of deel de bestanden die hieronder zichtbaar zijn:", + "un_ulist": "Toon", + "un_ucopy": "Kopieer", + "un_flt": "Optionele filter:  URL moet het volgende bevatten", + "un_fclr": "Reset filter", + "un_derr": 'unpost-verwijderen mislukt:\n', + "un_f5": 'Er is iets kapot, probeer te verversen of druk op F5', + "un_uf5": "Sorry, maar u moet de pagina vernieuwen (bijvoorbeeld door op F5 of CTRL-R te drukken) voordat deze upload kan worden afgebroken.", + "un_nou": '<b>Waarschuwing:</b> server te druk om onvoltooide uploads weer te geven; klik straks op de "refresh" link', + "un_noc": '<b>Waarschuwing:</b> unpost van volledig geüploade bestanden is niet ingeschakeld/toegestaan in de serverconfiguratie', + "un_max": "Toont de eerste 2000 bestanden (gebruik de filter)", + "un_avail": "{0} recente uploads kunnen worden verwijderd<br />{1} onvoltooide kunnen worden afgebroken", + "un_m2": "Gesorteerd op uploadtijd; meest recente eerst:", + "un_no1": "sike! geen enkele upload is recent genoeg", + "un_no2": "sike! geen uploads die aan dat filter voldoen zijn voldoende recent", + "un_next": "Verwijder de volgende {0} bestanden", + "un_abrt": "Afbreken", + "un_del": "Verwijderen", + "un_m3": "Je recente uploads laden...", + "un_busy": "Verwijderen van {0} bestanden...", + "un_clip": "{0} links gekopieerd naar klembord", + + "u_https1": "Je moet", + "u_https2": "overschakelen naar https", + "u_https3": "voor betere prestaties", + "u_ancient": 'Je browser is indrukwekkend oud -- misschien moet je <a href="#" onclick="goto(\'bup\')">in plaats daarvan bup gebruiken</a>', + "u_nowork": "Je moet firefox 53+ of chrome 57+ of iOS 11+ hebben", + "tail_2old": "Je moet firefox 105+ of chrome 71+ of iOS 14.5+ hebben", + "u_nodrop": 'Je browser is te oud voor uploaden via slepen en neerzetten', + "u_notdir": "Dat is geen map!\n\nuw browser is te oud,\nprobeer in plaats daarvan sleep en neerzetten", + "u_uri": "Om afbeeldingen te slepen vanuit andere browser tabblad,\nplaats deze dan op de grote uploadknop", + "u_enpot": 'Overschakelen naar <a href="#">potato UI</a> (kan uploadsnelheid verbeteren)', + "u_depot": 'Overschakelen naar <a href="#">fancy UI</a> (kan uploadsnelheid verminderen)', + "u_gotpot": 'Overschakelen naar de potato UI voor verbeterde uploadsnelheid,\n\nVoel je vrij om het er niet mee eens te zijn en schakel terug!', + "u_pott": "<p>Bestanden:   <b>{0}</b> klaar,   <b>{1}</b> mislukt,   <b>{2}</b> bezig,   <b>{3}</b> in de wachtrij</p>", + "u_ever": "Dit is de basis uploader; up2k heeft minstens het volgende nodig<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'Dit is de basis uploader; <a href="#" id="u2yea">up2k</a> is beter', + "u_uput": 'Optimaliseren voor snelheid (checksum overslaan)', + "u_ewrite": 'Je hebt geen schrijftoegang tot deze map', + "u_eread": 'Je hebt geen leestoegang tot deze map', + "u_enoi": 'Zoeken naar bestanden is niet ingeschakeld in de serverconfiguratie', + "u_enoow": "Overschrijven zal hier niet werken; je heb verwijder toestemming nodig", + "u_badf": 'Deze {0} bestanden (van {1} totaal) zijn overgeslagen, mogelijk door bestandssysteemmachtigingen:\n\n', + "u_blankf": 'Deze {0} bestanden (van {1} totaal) zijn leeg; alsnog uploaden?\n\n', + "u_applef": 'Deze {0} bestanden (van {1} totaal) zijn waarschijnlijk ongewenst;\nKlik op <code>OK/Enter</code> om de volgende bestanden over te slaan,\Klik op <code>Annuleren/ESC</code> niet uit te sluiten en deze ook te uploaden:\n\n', + "u_just1": '\nMisschien werkt het beter als je slechts één bestand selecteert', + "u_ff_many": "Als je <b>Linux / MacOS / Android,</b> gebruikt dan <em>kan</em> deze hoeveelheid bestanden <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\">Firefox crashen!</a>\nals dat gebeurt, probeer het dan opnieuw (of gebruik Chrome).", + "u_up_life": "Deze upload wordt verwijderd van de server\n{0} nadat het is voltooid", + "u_asku": 'Upload deze {0} bestanden naar <code>{1}</code>', + "u_unpt": "Je kunt deze upload ongedaan maken / verwijderen met de linkerbovenhoek 🧯", + "u_bigtab": 'We staan op het punt om {0} bestanden te tonen\n\nDit kan uw browser laten crashen, weet je het zeker??', + "u_scan": 'Bestanden scannen...', + "u_dirstuck": 'Directory iterator liep vast bij het benaderen van het volgende {0} items; zal het volgende overslaan:', + "u_etadone": 'Klaar ({0}, {1} bestanden)', + "u_etaprep": '(klaarmaken om te uploaden)', + "u_hashdone": 'hashing klaar', + "u_hashing": 'Hash', + "u_hs": 'Hallo zeggen...', + "u_started": "De bestanden worden nu geüpload; zie [🚀]", + "u_dupdefer": "Duplicaat; wordt verwerkt na alle andere bestanden", + "u_actx": "klik op deze tekst om prestatieverlies</br>bij het overschakelen naar andere vensters/tabbladen te voorkomen", + "u_fixed": "OK!  Fixed it 👍", + "u_cuerr": "Mislukt bij het uploaden van stuk {0} van {1};\nwaarschijnlijk ongevaarlijk, doorgaan\n\nbestand: {2}", + "u_cuerr2": "Upload door server geweigerd (stuk {0} van {1});\nzal later opnieuw proberen\n\nbestand: {2}\n\nfout ", + "u_ehstmp": "Zal opnieuw proberen; zie rechtsonder", + "u_ehsfin": "Server heeft het verzoek om de upload te finaliseren afgewezen; opnieuw proberen...", + "u_ehssrch": "Server heeft de zoekaanvraag afgewezen; opnieuw proberen...", + "u_ehsinit": "Server heeft het verzoek om het uploaden te starten afgewezen; opnieuw proberen...", + "u_eneths": "Netwerkfout tijdens het uitvoeren van de uploadhanddruk; opnieuw proberen...", + "u_enethd": "Netwerkfout tijdens het testen van het bestaan van het doel; opnieuw proberen...", + "u_cbusy": "Wachten tot de server ons weer vertrouwt na een netwerkstoring...", + "u_ehsdf": "Server heeft geen schijfruimte meer!\n\nzal blijven proberen, voor het geval iemand genoeg ruimte vrijmaakt om door te gaan", + "u_emtleak1": "Het lijkt erop dat uw webbrowser een geheugenlek heeft;\nprobeer", + "u_emtleak2": ' <a href="{0}">over te schakel over naar https (aanbevolen)</a> of ', + "u_emtleak3": ' ', + "u_emtleakc": 'Probeer het volgende:\n<ul><li>druk op <code>F5</code> om de pagina te verversen</li><li>dan schakel de  <code>mt</code>  uit, deze knop staat in  <code>⚙️ instellingen</code></li><li>en probeer de upload opnieuw</li></ul>Uploaden zal wat langzamer gaan, maar ja.\nSorry voor de problemen!\n\nPS: chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">heeft een bugfix</a> voor dit', + "u_emtleakf": '{robeer het volgende:\n<ul><li>druk op <code>F5</code> om de pagina te verversen</li><li>dan activeer <code>🥔</code> (aardappel) in de upload scherm<li>en probeer de upload opnieuw</li></ul>\nPS: firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">heeft mogelijk een fix</a> op een gegeven moment', + "u_s404": "Niet gevonden op server", + "u_expl": "Leg uit", + "u_maxconn": "De meeste browsers beperken dit tot 6, maar firefox laat je dit verhogen met <code>network.http.max-persistent-connections-per-server</code> in <code>about:config</code>", + "u_tu": '<p class="warn">WAARSCHUWING: turbo ingeschakeld, <span> webbrowser detecteert en hervat onvolledige uploads mogelijk niet; zie de tooltip van de turboknop</span></p>', + "u_ts": '<p class="warn">WAARSCHUWING: turbo ingeschakeld, <span> zoekresultaten kunnen onjuist zijn; zie turbo-knop tooltip</span></p>', + "u_turbo_c": "Turbo is uitgeschakeld in serverconfiguratie", + "u_turbo_g": "Turbo uitgeschakeld, je geen recht om mappen in deze volume te tonen", + "u_life_cfg": 'Automatisch verwijderen na <input id="lifem" p="60" /> minuten (of <input id="lifeh" p="3600" /> uur)', + "u_life_est": 'Upload wordt verwijderd <span id="lifew" tt="local time">---</span>', + "u_life_max": 'Deze map dwingt een\nmaximale levensduur van {0} af', + "u_unp_ok": 'unpost is toegestaan voor {0}', + "u_unp_ng": 'unpost zijn NIET toegestaan', + "ue_ro": 'Je toegang tot deze map is alleen-lezen\n\n', + "ue_nl": 'Je bent momenteel niet ingelogd', + "ue_la": 'Je bent momenteel aangemeld als "{0}"', + "ue_sr": 'U bevindt zich momenteel in de bestandszoekmodus\n\nschakel over naar uploadmodus door op het vergrootglas te klikken 🔎 (naast de grote ZOEK-knop), en probeer opnieuw te uploaden\n\nsorry', + "ue_ta": 'Probeer opnieuw te uploaden, het zou nu moeten werken', + "ue_ab": "Dit bestand wordt al geüpload naar een andere map en die upload moet worden voltooid voordat het bestand naar een andere map kan worden geüpload.\n\nU kunt de eerste upload afbreken en laten vergeten met de linkerbovenhoek 🧯", + "ur_1uo": "OK: Bestand succesvol geüpload", + "ur_auo": "OK: Alle {0} bestanden succesvol geüpload", + "ur_1so": "OK: Bestand gevonden op server", + "ur_aso": "OK: Alle {0} bestanden gevonden op server", + "ur_1un": "Uploaden mislukt, sorry", + "ur_aun": "Alle {0} uploads mislukt, sorry", + "ur_1sn": "Bestand NIET gevonden op server", + "ur_asn": "De {0} bestanden zijn NIET gevonden op de server", + "ur_um": "Voltooid;\n{0} upload(s) OK,\n{1} upload(s) mislukt, sorry", + "ur_sm": "Voltooid;\n{0} bestand(en) gevonden op de server,\n{1} bestand(en) NIET gevonden op de server", + + "lang_set": "Vernieuw de pagina om de wijziging door te voeren?", + }, + "nno": { + "tt": "Nynorsk", + + "cols": { + "c": "handlingsknappar", + "dur": "varigheit", + "q": "kvalitet / bitrate", + "Ac": "lydformat", + "Vc": "videoformat", + "Fmt": "format / innpakning", + "Ahash": "lydkontrollsum", + "Vhash": "videokontrollsum", + "Res": "oppløysing", + "T": "filtype", + "aq": "lydkvalitet / bitrate", + "vq": "videokvalitet / bitrate", + "pixfmt": "fargekoding / detaljnivå", + "resw": "horisontal oppløysing", + "resh": "vertikal oppløysing", + "chs": "lydkanaler", + "hz": "lydoppløsing" + }, + + "hks": [ + [ + "ymse", + ["ESC", "lukk saker og ting"], + + "filbehandlar", + ["G", "listevisning eller ikon"], + ["T", "miniatyrbilder på/av"], + ["⇧ A/D", "ikonstorleik"], + ["ctrl-K", "slett valde"], + ["ctrl-X", "klipp ut valde"], + ["ctrl-C", "kopiér åt utklippstavle"], + ["ctrl-V", "lim inn (flytt/kopiér)"], + ["Y", "last ned valde"], + ["F2", "endre namn på valde"], + + "filmarkering", + ["space", "markér fil"], + ["↑/↓", "flytt markør"], + ["ctrl ↑/↓", "flytt markør og scroll"], + ["⇧ ↑/↓", "velg forr./neste fil"], + ["ctrl-A", "velg alle filer / mapper"], + ], [ + "navigering", + ["B", "mappehierarki eller filsti"], + ["I/K", "forr./neste mappe"], + ["M", "eitt nivå opp (eller lukk)"], + ["V", "vis mapper eller tekstfiler"], + ["A/D", "panelstorleik"], + ], [ + "musikkspelar", + ["J/L", "forr./neste song"], + ["U/O", "hopp 10sek bak/fram"], + ["0..9", "hopp åt 0%..90%"], + ["P", "pause, eller start / fortsett"], + ["S", "marker spelande song"], + ["Y", "last ned song"], + ], [ + "bildevisar", + ["J/L, ←/→", "forr./neste bilde"], + ["Home/End", "første/siste bilde"], + ["F", "fullskjermvisning"], + ["R", "rotér åt høyre"], + ["⇧ R", "rotér åt venstre"], + ["S", "markér bilde"], + ["Y", "last ned bilde"], + ], [ + "videospelar", + ["U/O", "hopp 10sek bak/fram"], + ["P/K/Space", "pause / fortsett"], + ["C", "fortsett åt neste fil"], + ["V", "gjenta avspeling"], + ["M", "lyd av/på"], + ["[ og ]", "gjentaksintervall"], + ], [ + "dokumentvisar", + ["I/K", "forr./neste fil"], + ["M", "lukk tekstdokument"], + ["E", "redigér tekstdokument"], + ["S", "markér fil (for F2/ctrl-x/...)"], + ["Y", "last ned tekstfil"], + ] + ], + + "m_ok": "OK", + "m_ng": "Avbryt", + + "enable": "Aktiv", + "danger": "VARSKU", + "clipped": "kopiert åt utklippstavla", + + "ht_s1": "sekund", + "ht_s2": "sekund", + "ht_m1": "minutt", + "ht_m2": "minutt", + "ht_h1": "time", + "ht_h2": "timar", + "ht_d1": "dag", + "ht_d2": "dagar", + "ht_and": " og ", + + "goh": "kontrollpanel", + "gop": 'navigér åt mappa før den her">forr.', + "gou": 'navigér eitt nivå opp">opp', + "gon": 'navigér åt mappa etter den her">neste', + "logout": "Logg ut ", + "login": "Logg inn", + "access": " åtgang", + "ot_close": "lukk reiskap", + "ot_search": "søk etter filer ved å angje filnamn, mappenamn, tid, storleik, eller metadata som songtittel / artist / osv.$N$N<code>foo bar</code> = inneheld båe «foo» og «bar»,$N<code>foo -bar</code> = innehold «foo» men ikkje «bar»,$N<code>^yana .opus$</code> = startar med «yana», filtype «opus»$N<code>"try unite"</code> = «try unite» eksakt$N$Ndatoformat er iso-8601, så f.eks.$N<code>2009-12-31</code> eller <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: slett filer som du nyleg har lastet opp; «angre-knappen»", + "ot_bup": "bup: tradisjonell / primitiv filopplasting,$N$Nfungerar i om lag samtlege nettlesarar", + "ot_mkdir": "mkdir: lag ei ny mappe", + "ot_md": "new-md: lag eit nytt markdown-dokument", + "ot_msg": "msg: send ein beskjed åt serverloggen", + "ot_mp": "musikkspelarinstillinger", + "ot_cfg": "andre innstillinger", + "ot_u2i": 'up2k: last opp filer (viss du har skriveåtgang) eller bytt åt søkemodus for å sjekke om filene finnast ein eller annan plass på serveren$N$Nopplastinger kan startast opp att etter avbrot, skjer stykkevis for potensielt høgare ytelse, og ivaretek datostempling -- men bruker litt meir prosessorkraft enn [🎈]  (den primitive opplastaren "bup")<br /><br />mens opplastinger føregår så visast framdrifta her oppe!', + "ot_u2w": 'up2k: filopplasting med støtte for å starte opp att avbrotne opplastinger -- steng ned nettlesaren og drage dei same filene inn i nettlesaren igjen for å plukke opp att der du slapp$N$Nopplastinger skjer stykkevis for potensielt høgare ytelse, og ivaretek datostempling -- men bruker litt meir prosessorkraft enn [🎈]  (den primitive opplastaren "bup")<br /><br />mens opplastinger føregår så visast framdrifta her oppe!', + "ot_noie": 'Fungerer mye betre i Chrome / Firefox / Edge', + + "ab_mkdir": "lag mappe", + "ab_mkdoc": "nytt dokument", + "ab_msg": "send melding", + + "ay_path": "gå videre åt mapper", + "ay_files": "gå videre åt filer", + + "wt_ren": "gje nye namn åt dei valde filene$NSnarvei: F2", + "wt_del": "slett dei valde filene$NSnarvei: ctrl-K", + "wt_cut": "klipp ut dei valde filene <small>(for å lime inn ein annan plass)</small>$NSnarvei: ctrl-X", + "wt_cpy": "kopiér dei valde filene åt utklippstavla$N(for å lime inn ein annan plass)$NSnarvei: ctrl-C", + "wt_pst": "lim inn filer (som tidligare blei klipt ut / kopiert ein annan plass)$NSnarvei: ctrl-V", + "wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på ei fil)", + "wt_selinv": "invertér utval", + "wt_zip1": "last ned denne mappa som eit arkiv", + "wt_selzip": "last ned dei valde filene som eit arkiv", + "wt_seldl": "last ned dei valde filene$NSnarvei: Y", + "wt_npirc": "kopiér songinfo (irc-formatert)", + "wt_nptxt": "kopiér songinfo", + "wt_m3ua": "legg song åt i m3u-speleliste$N(husk å klikk på <code>📻copy</code> senere)", + "wt_m3uc": "kopiér m3u-spelelista åt utklippstavla", + "wt_grid": "bytt mellom ikon og listevising$NSnarvei: G", + "wt_prev": "førre song$NSnarvei: J", + "wt_play": "play / pause$NSnarvei: P", + "wt_next": "neste song$NSnarvei: L", + + "ul_par": "samtidige handl.:", + "ut_rand": "finn opp nye tilfeldige filnamn", + "ut_u2ts": "gje fila på serveren same$Ntidsstempel som lokalt hos deg\">📅", + "ut_ow": "overskrive eksisterande filer på serveren?$N🛡️: aldri (finn på eit nytt filnamn i staden for)$N🕒: overskriv viss fila åt serveren er eldre$N♻️: alltid, gitt at innholdet er annleis", + "ut_mt": "fortsett å synfare køa mens opplasting føregår$N$Nskru denne av dersom du har ein$Ntreig prosessor eller harddisk", + "ut_ask": 'bekreft filutvalg før opplasting startar">💭', + "ut_pot": "forbetre ytinga på treige einheiter ved å$Nforenkle brukergrensesnittet", + "ut_srch": "gjer eit søk i staden for å laste opp --$Nleitar gjennom alle mappane du har lov åt å sjå", + "ut_par": "sett åt 0 for å midlertidig stoppe opplasting$N$Nhøge verdier (4 eller 8) kan gje betre yting,$Nspesielt på treige internettlinjer$N$Nbør ikkje vere høgare enn 1 på LAN$Neller viss serveren sin harddisk er treig", + "ul_btn": "slepp filer / mapper<br>her (eller klikk meg)", + "ul_btnu": "L A S T   O P P", + "ul_btns": "F I L S Ø K", + + "ul_hash": "synfar", + "ul_send": " send", + "ul_done": "total", + "ul_idle1": "ingen handlinger i køen", + "ut_etah": "snitthastigheit for <em>synfaring</em> samt gjenståande tid", + "ut_etau": "snitthastigheit for <em>opplasting</em> samt gjenståande tid", + "ut_etat": "<em>total</em> snitthastigheit og gjenståande tid", + + "uct_ok": "fullført uten problem", + "uct_ng": "fullført under tvil (duplikat, ikkje funne, ...)", + "uct_done": "fullført (enten <em>ok</em> eller <em>ng</em>)", + "uct_bz": "aktive handlinger (synfaring / opplasting)", + "uct_q": "køa", + + "utl_name": "filnamn", + "utl_ulist": "vis", + "utl_ucopy": "kopiér", + "utl_links": "lenker", + "utl_stat": "status", + "utl_prog": "fremdrift", + + // må vere korte: + "utl_404": "404", + "utl_err": "FEIL!", + "utl_oserr": "OS-feil", + "utl_found": "funnet", + "utl_defer": "seinare", + "utl_yolo": "YOLO", + "utl_done": "ferdig", + + "ul_flagblk": "filene har blitt lagd i køa</b><br>men det er ein anna nettlesarfane som held på med synfaring eller opplasting akkurat no,<br>så venter åt den er ferdig først", + "ul_btnlk": "brytaren har blitt låst åt denne tilstanden i serverens konfigurasjon", + + "udt_up": "Last opp", + "udt_srch": "Søk", + "udt_drop": "Slepp filene her", + + "u_nav_m": '<h6>kva har du?</h6><code>Enter</code> = Filer (éin eller fleire)\n<code>ESC</code> = Éi mappe (inkludert undermapper)', + "u_nav_b": '<a href="#" id="modal-ok">Filer</a><a href="#" id="modal-ng">Éi mappe</a>', + + "cl_opts": "brytarar", + "cl_themes": "utsjånad", + "cl_langs": "språk", + "cl_ziptype": "nedlasting av mapper", + "cl_uopts": "up2k-brytarar", + "cl_favico": "favicon", + "cl_bigdir": "store mapper", + "cl_hsort": "#sort", + "cl_keytype": "notasjon for musikalsk dur", + "cl_hiddenc": "skjulte kolonner", + "cl_hidec": "skjul", + "cl_reset": "nullstill", + "cl_hpick": "klikk på overskrifta åt kolonnene du ønskjer å skjule i tabellen nedanfor", + "cl_hcancel": "kolonne-skjuling avbrote", + + "ct_grid": '田 ikon', + "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_ihop": 'bla ned åt sist viste bilde når bildevisaren lukkast">g⮯', + "ct_dots": 'vis skjulte filer (gitt at serveren tillèt det)">.synlig', + "ct_qdel": 'sletteknappen spør berre éin gong om stadfesting">hurtig🗑️', + "ct_dir1st": 'sortér slik at mapper kjem framanfor filer">📁 først', + "ct_nsort": 'naturlig sortering (skjønar tal i filnamn)">nsort', + "ct_utc": 'bruk UTC for alle klokkeslett">UTC', + "ct_readme": 'vis README.md nedanfor filene">📜 readme', + "ct_idxh": 'vis index.html i staden for filliste">htm', + "ct_sbars": 'vis rullgardiner / skrollefelt">⟊', + + "cut_umod": 'i tilfelle ei fil du lastar opp alt finnast på serveren, så skal tidsstempelet åt serveren oppdaterast slik at det stemmer overeins med din lokale fil (krev rettigheitene write+delete)">re📅', + + "cut_turbo": "forenkla synfaring ved opplasting; bør etter alt å døme <em>ikkje</em> skruast på:$N$Nnyttig dersom du var midt i ei svær opplasting som måtte startast på nytt av ein eller annan grunn, og du vil komme i gang igjen så raskt som i det heile mulig.$N$Nnår denne er skrudd på så forenklast synfaringa kraftig; i staden for å utføre ein trygg sjekk på om filene finnast på serveren i god stand, så sjekkast det kun om <em>filstorleiken</em> stemmer. Så dersom ein korrupt fil vere på serveren allerede, på same plass, med same storleik og namn, så blir det <em>ikkje oppdaga</em>.$N$Ndet anbefalast å kun benytte denne funksjonen for å komme seg raskt gjennom sjølve opplastinga, for så å skru den av, og åt slutt "laste opp" dei same filene éin gong åt -- slik at integriteten kan verifiserast\">turbo", + + "cut_datechk": "har ingen effekt dersom turbo er skrudd av$N$Ngjer turbo bittelitt tryggare ved å sjekke datostemplinga på filene (i tillegg åt filstorleik)$N$N<em>burde</em> oppdage og gjenoppta dei fleste ufullstendige opplastinger, men er <em>ikkje</em> ein fullverdig erstatning for å deaktivere turbo og gjere ein skikkeleg sjekk\">date-chk", + + "cut_u2sz": "storleik i megabyte for kvart bruddstykke for opplasting. Store verdiar flyg betre over atlanteren. Små verdiar kan vere betre på flettande ustabile samband", + + "cut_flag": "samkøyrer nettlesarfaner slik at berre éin $N kan holde på med synfaring / opplasting $N -- andre faner må óg ha denne skrudd på $N -- fungerar kun innanom same domene", + + "cut_az": "last opp filer i alfabetisk rekkefølge, i staden for minste-fil-først$N$Nalfabetisk kan gjere det lettare å anslå om alt gjekk bra, men er bittelitt treigare på fiber / LAN", + + "cut_nag": "meldingsvarsel når opplasting er ferdig$N(kun om nettlesarfana ikkje er synlig)", + "cut_sfx": "lydvarsel når opplasting er ferdig$N(kun om nettlesarfanen ikkje er synlig)", + + "cut_mt": "raskere synfaring ved å bruke heile CPU'en$N$Ndenne funksjonen nytter web-workers$Nog krev meir RAM (opptil 512 MiB ekstra)$N$Ngjer https 30% raskare, http 4.5x raskare\">mt", + + "cut_wasm": "bruk wasm i staden for nettlesaren sin sha512-funksjon; gjev betre yting på chrome-baserte nettlesarar, men brukar meir CPU, og eldre versjoner av chrome toler det ikkje (et opp all RAM og kræsjer)\">wasm", + + "cft_text": "ikontekst (blank ut og last siden på nytt for å deaktivere)", + "cft_fg": "farge", + "cft_bg": "bakgrunnsfarge", + + "cdt_lim": "maks mengd filer å vise per mappe", + "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", + + "tt_entree": "bytt åt mappehierarki$NSnarvei: B", + "tt_detree": "bytt åt tradisjonell stivising$NSnarvei: B", + "tt_visdir": "bla ned åt den åpne mappa", + "tt_ftree": "bytt mellom filstruktur og tekstfiler$NSnarvei: V", + "tt_pdock": "vis dei overordna mappane i eit panel", + "tt_dynt": "øk bredda på panelet ettersom treet utvider seg", + "tt_wrap": "linjebryting", + "tt_hover": "vis heile mappenamnet når musepeikaren treff mappa$N( gjer diverre at scrollhjulet fusker dersom musepeikaren ikkje finn seg i grøfta )", + + "ml_pmode": "ved enden av mappa", + "ml_btns": "knapper", + "ml_tcode": "konvertering", + "ml_tcode2": "konvertér til", + "ml_tint": "tint", + "ml_eq": "audio equalizer (tonejustering)", + "ml_drc": "compressor (volumutjevning)", + + "mt_loop": "spel den same songen om og om igjen\">🔁", + "mt_one": "spel kun éin song\">1️⃣", + "mt_shuf": "songane i kvar mappe$Nspelast i tilfeldig rekkefølge\">🔀", + "mt_aplay": "prøv å starte avspeling viss linken du trykte på for å åpne nettsida inneheld ein song-ID$N$Nviss denne deaktiverast så vil heller ikkje nettside-URL'en bli oppdatert med song-ID'er når musikk spelast, i tilfelle innstillingane skulle gå tapt og nettsida lastast på ny\">a▶", + "mt_preload": "hent ned litt av neste song i forkant,$Nslik at pausa i overgangen blir mindre\">forsyn", + "mt_prescan": "ved behov, bla åt neste mappe$Nslik at nettlesaren lar oss$Nfortsetja å spele musikk\">bla", + "mt_fullpre": "hent ned heile neste song, ikkje berre litt:$N✅ skru på viss nettet ditt er <b>ustabilt</b>,$N❌ skru av viss nettet ditt er <b>treigt</b>\">full", + "mt_fau": "for telefoner: forhindre at avspeling stoppar viss nettet er for treigt åt å laste neste song i tide. Viss påskrudd kan det forårsake at songinfo ikkje visast korrekt i OS'et\">☕️", + "mt_waves": "waveform seekbar:$Nvis volumkurve i avspelingsfeltet\">~s", + "mt_npclip": "vis knappar for å kopiere info om songen du høyrer på\">/np", + "mt_m3u_c": "vis knapper for å kopiere dei valde$Nsongene som innslag i ei m3u8-speleliste\">📻", + "mt_octl": "integrering med operativsystemet (fjernkontroll, infoskjerm)\">os-ctl", + "mt_oseek": "gje løyve åt spoling med fjernkontroll$N$Nmerk: på nokon eininger (iPhones) så vil$Ndette erstatte knappen for neste song\">spoling", + "mt_oscv": "vis albumcover på infoskjermen\">bilde", + "mt_follow": "bla slik at songen som spelast alltid er synleg\">🎯", + "mt_compact": "tettpakka spelarpanel\">⟎", + "mt_uncache": "prøv denne viss ein song ikkje spelar riktig\">oppfrisk", + "mt_mloop": "repetér heile mappa\">🔁 gjenta", + "mt_mnext": "hopp åt neste mappe og fortsett\">📂 neste", + "mt_mstop": "stopp avspeling\">⏸ stopp", + "mt_cflac": "konvertér flac / wav-filer åt {0}\">flac", + "mt_caac": "konvertér aac / m4a-filer åt to {0}\">aac", + "mt_coth": "konvertér alt anna (men ikkje mp3) åt {0}\">andre", + "mt_c2opus": "det beste valget for alle PCar og Android\">opus", + "mt_c2owa": "opus-weba, for iOS 17.5 og nyare\">owa", + "mt_c2caf": "opus-caf, for iOS 11 åt og med 17\">caf", + "mt_c2mp3": "bra valg for steinalder-utstyr (slår aldri feil)\">mp3", + "mt_c2flac": "gir best lydkvalitet, men et nettet ditt\">flac", + "mt_c2wav": "heilt rå lydstrøm (bruker enda meir data enn flac)\">wav", + "mt_c2ok": "bra valg!", + "mt_c2nd": "ikkje det føretrekte valget for din einheit, men funker sikkert greit", + "mt_c2ng": "ser verkelig ikkje ut som enheiten din taklar dette formatet... men ok, vi prøver", + "mt_xowa": "iOS har fortsatt problem med avspeling av owa-musikk i bakgrunnen. Bruk caf eller mp3 i staden for", + "mt_tint": "nivå av bakgrunnsfarge på søkestripa (0-100),$Ngjer oppdateringer mindre distraherande", + "mt_eq": "aktivér tonekontroll og forsterker;$N$Nboost <code>0</code> = normal volumskala$N$Nwidth <code>1  </code> = normal stereo$Nwidth <code>0.5</code> = 50% blanding venstre-høgre$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = instrumental :^)$N$Nreduserer óg daudtid mellom songfiler", + "mt_drc": "aktivér volum-utjevning (dynamic range compressor); vil óg aktivere tonejustering, så sett alle EQ-feltene bortsett frå 'width' åt 0 viss du ikkje vil ha nokon EQ$N$Nfilteret vil dempe volumet på alt som er høgare enn TRESH dB; for kvar RATIO dB over grensa er det 1dB som treff høgtalarane, så standardverdiane tresh -24 og ratio 12 skal bety at volumet ikkje gjeng høgare enn -22 dB, slik at ein trygt kan øke boost-verdien i equalizeren åt rundt 0.8, eller 1.8 kombinert med ATK 0 og RLS 90 (berre mulig i firefox; andre nettlesarar tek ikkje høgare RLS enn 1)$N$Nwikipedia forklarar dette mykje betre forresten", + + "mb_play": "lytt", + "mm_hashplay": "spel denne songen?", + "mm_m3u": "trykk <code>Enter/OK</code> for å spele\ntrykk <code>ESC/Avbryt</code> for å redigere", + "mp_breq": "krev firefox 82+, chrome 73+, eller iOS 15+", + "mm_bload": "lastar inn...", + "mm_bconv": "konverterer åt {0}, vent litt...", + "mm_opusen": "nettlesaren din skjønar ikkje aac / m4a;\nkonvertering åt opus er no aktivert", + "mm_playerr": "avspeling feilet: ", + "mm_eabrt": "Avspelingsforespørselen blei avbroten", + "mm_enet": "Nettet ditt er ustabilt", + "mm_edec": "Noko er galt med musikkfila", + "mm_esupp": "Nettleseren din skjønar ikkje filtypen", + "mm_eunk": "Ukjent feil", + "mm_e404": "Avspeling feilet: Fil ikkje funnet.", + "mm_e403": "Avspeling feilet: Høve nekta.\n\nKanskje du blei logget ut?\nPrøv å trykk F5 for å laste sida på nytt.", + "mm_e500": "Avspeling feilet: Rusk i maskineriet, sjekk serverloggen.", + "mm_e5xx": "Avspeling feilet: ", + "mm_nof": "finn ikkje flere songer i nærheita", + "mm_prescan": "Leitar etter neste song...", + "mm_scank": "Fann neste song:", + "mm_uncache": "alle songer vil lastast på nytt ved neste avspeling", + "mm_hnf": "songen finnast ikkje lenger", + + "im_hnf": "bildet finnast ikkje lenger", + + "f_empty": 'denne mappa er tom', + "f_chide": 'dette vil skjule kolonna «{0}»\n\nfana for "andre innstillinger" let deg vise kolonna igjen', + "f_bigtxt": "denne fila er heeile {0} MiB -- vis som tekst?", + "f_bigtxt2": "vil du sjå bunnen av filen i staden for? du vil da óg sjå nye linjer som blir lagd åt på slutten av filen i sanntid", + "fbd_more": '<div id="blazy">visar <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_more">vis {2}</a> eller <a href="#" id="bd_all">vis alle</a></div>', + "fbd_all": '<div id="blazy">visar <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_all">vis alle</a></div>', + "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_partial": "For å laste ned ei fil som enda ikkje er ferdig opplasta, klikk på filen som har same filnamn som denne, men uten <code>.PARTIAL</code> 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 <code>.PARTIAL</code>-filen på ein ukontrollert måte, trykk OK / Enter for å ignorere denne advarselen. Slik vil du høgst sannsynleg motta korrupt data.", + + "ft_paste": "Lim inn {0} filer$NSnarvei: ctrl-V", + "fr_eperm": 'kan ikkje endre namn:\ndu har ikkje høve åt “move” i denne mappa', + "fd_eperm": 'kan ikkje slette:\ndu har ikkje høve åt “delete” i denne mappa', + "fc_eperm": 'kan ikkje klippe ut:\ndu har ikkje høve åt “move” i denne mappa', + "fp_eperm": 'kan ikkje lime inn:\ndu har ikkje høve åt “write” i denne mappa', + "fr_emore": "vel minst éi fil som skal få nytt namn", + "fd_emore": "vel minst éi fil som skal slettast", + "fc_emore": "vel minst éi fil som skal klippast ut", + "fcp_emore": "vel minst éi fil som skal kopierast åt utklippstavla", + + "fs_sc": "del mappa du er i no", + "fs_ss": "del dei valde filene", + "fs_just1d": "du kan ikkje markere flere mapper samtidig,\neller kombinere mapper og filer", + "fs_abrt": "❌ avbryt", + "fs_rand": "🎲 tilfeldig namn", + "fs_go": "✅ opprett deling", + "fs_name": "namn", + "fs_src": "kjelde", + "fs_pwd": "passord", + "fs_exp": "varigheit", + "fs_tmin": "min", + "fs_thrs": "timar", + "fs_tdays": "dagar", + "fs_never": "for evig", + "fs_pname": "valfri namn (blir litt tilfeldig ellers)", + "fs_tsrc": "fil/mappe som skal delast", + "fs_ppwd": "valfri passord", + "fs_w8": "opprettar deling...", + "fs_ok": "trykk <code>Enter/OK</code> for å kopiere lenka (for CTRL-V)\ntrykk <code>ESC/Avbryt</code> for å kun bekrefta", + + "frt_dec": "kan korrigere visse ødelagte filnamn\">url-decode", + "frt_rst": "nullstillar endringar (tilbake åt dei originale filnamna)\">↺ reset", + "frt_abrt": "avbryt og lukk dette vindauget\">❌ avbryt", + "frb_apply": "IVERKSETT", + "fr_adv": "automasjon basert på metadata<br>og / eller mønster (regulære uttrykk)\">avansert", + "fr_case": "versalfølsomme uttrykk\">Aa", + "fr_win": "bytt ut bokstavane <code><>:"\\|?*</code> med$Ntilsvarande som windows ikkje får panikk av\">win", + "fr_slash": "bytt ut bokstaven <code>/</code> slik at den ikkje forårsakar at nye mapper opprettes\">ikke /", + "fr_re": "regex-mønster som køyrast på kvart filnamn. Grupper kan leses ut i format-feltet nedanfor, f.eks. <code>(1)</code> og <code>(2)</code> osv.", + "fr_fmt": "inspirert av foobar2000:$N<code>(title)</code> byttast ut med songtittel,$N<code>[(artist) - ](title)</code> dropper [dette] viss artist er blank$N<code>$lpad((tn),2,0)</code> visar songnr. med 2 siffer", + "fr_pdel": "slett", + "fr_pnew": "lagre som", + "fr_pname": "gje innstillingane dine eit namn", + "fr_aborted": "avbrote", + "fr_lold": "gamalt namn", + "fr_lnew": "nytt namn", + "fr_tags": "metadata for dei valde filene (kun for referanse):", + "fr_busy": "endrar namn på {0} filer...\n\n{1}", + "fr_efail": "endring av namn feila:\n", + "fr_nchg": "{0} av namna blei justert pga. <code>win</code> og/eller <code>ikkje /</code>\n\nvil du fortsetja med dei nye namna som blei valde?", + + "fd_ok": "sletting OK", + "fd_err": "sletting feila:\n", + "fd_none": "ingenting blei sletta; kanskje avvist av serverkonfigurasjon (xbd)?", + "fd_busy": "slettar {0} filer...\n\n{1}", + "fd_warn1": "SLETT disse {0} filene?", + "fd_warn2": "<b>Siste sjanse!</b> Dette kan ikkje angrast. Slett?", + + "fc_ok": "klipte ut {0} filer", + "fc_warn": 'klipte ut {0} filer\n\nmen: kun <b>denne</b> nettlesarfana har muligheit åt å lime dei inn ein annan plass, siden antallet filer er helt hinsides', + + "fcc_ok": "kopierte {0} filer åt utklippstavla", + "fcc_warn": 'kopierte {0} filer åt utklippstavla\n\nmen: kun <b>denne</b> nettlesarfana har muligheit åt å lime dei inn ein annan plass, sidan antallet filer er heilt på hi sida', + + "fp_apply": "bekreft og lim inn no", + "fp_ecut": "du må klippe ut eller kopiere nokre filer / mapper først\n\nmerk: du kan gjerne jobbe på kryss av nettlesarfaner; klippe ut i éi fane, lime inn i ei anna", + "fp_ename": "{0} filer kan ikkje flyttast åt målmappa fordi det allereie finnast filer med same namn. Gi dei nye namn nedanfor, eller gje dei eit blankt namn for å hoppe over dei:", + "fcp_ename": "{0} filer kan ikkje kopierast åt målmappa fordi det allereie finnast filer med same namn. Gi dei nye namn nedanfor, eller gje dei eit blankt namn for å hoppe over dei:", + "fp_emore": "det er fortsatt fleire namn som må endrast", + "fp_ok": "flytting OK", + "fcp_ok": "kopiering OK", + "fp_busy": "flyttar {0} filer...\n\n{1}", + "fcp_busy": "kopierar {0} filer...\n\n{1}", + "fp_abrt": "avbryt...", + "fp_err": "flytting feila:\n", + "fcp_err": "kopiering feila:\n", + "fp_confirm": "flytt disse {0} filene hit?", + "fcp_confirm": "kopiér disse {0} filene hit?", + "fp_etab": 'kunne ikkje lese lista med filer frå den andre nettlesarfana', + "fp_name": "Lastar opp éi fil frå einheita di. Velg filnamn:", + "fp_both_m": '<h6>kva skal limast inn her?</h6><code>Enter</code> = Flytt {0} filer frå «{1}»\n<code>ESC</code> = Last opp {2} filer frå einheita din', + "fcp_both_m": '<h6>kva skal limes inn her?</h6><code>Enter</code> = Kopiér {0} filer frå «{1}»\n<code>ESC</code> = Last opp {2} filer frå einheita din', + "fp_both_b": '<a href="#" id="modal-ok">Flytt</a><a href="#" id="modal-ng">Last opp</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopiér</a><a href="#" id="modal-ng">Last opp</a>', + + "mk_noname": "skriv inn eit namn i tekstboksa åt venstre først :p", + + "tv_load": "Lastar inn tekstfil:\n\n{0}\n\n{1}% ({2} av {3} MiB lasta ned)", + "tv_xe1": "kunne ikkje laste tekstfil:\n\nfeil ", + "tv_xe2": "404, Fil ikkje funne", + "tv_lst": "tekstfiler i mappa", + "tvt_close": "gå tilbake åt mappa$NSnarvei: M (eller Esc)\">❌ lukk", + "tvt_dl": "last ned denne fila$NSnarvei: Y\">💾 last ned", + "tvt_prev": "vis førre dokument$NSnarvei: i\">⬆ forr.", + "tvt_next": "vis neste dokument$NSnarvei: K\">⬇ neste", + "tvt_sel": "markér fila   ( for utklipp / sletting / ... )$NSnarvei: S\">merk", + "tvt_edit": "redigér fila$NSnarvei: E\">✏️ endre", + "tvt_tail": "overvak fila for endringar og vis nye linjer i sanntid\">📡 følg", + "tvt_wrap": "tekstbryting\">↵", + "tvt_atail": "hald dei nyaste linjene synlege (lås åt botnen av sida)\">⚓", + "tvt_ctail": "skjøn og vis terminalfargar (ansi-sekvensar)\">🌈", + "tvt_ntail": "maksgrense for antal bokstavar som skal visast i vindauget", + + "m3u_add1": "songen blei lagd åt i m3u-spelelista", + "m3u_addn": "{0} songer blei lagde åt i m3u-spelelista", + "m3u_clip": "m3u-spelelista blei kopiert åt utklippstavla\n\nneste steg er å oppretta eit tekstdokument med filnamn som sluttar på <code>.m3u</code> og lime inn spelelista der", + + "gt_vau": "ikkje vis videofiler, berre spel lyden\">🎧", + "gt_msel": "markér filer i staden for å åpne dei; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk ei fil / mappe for å åpne</em>$N$NSnarvei: S\">markering", + "gt_crop": "skjer ikona slik at dei passar betre\">✂", + "gt_3x": "høgare oppløysing på ikon\">3x", + "gt_zoom": "zoom", + "gt_chop": "trim", + "gt_sort": "sortér", + "gt_name": "namn", + "gt_sz": "størr.", + "gt_ts": "dato", + "gt_ext": "type", + "gt_c1": "redusér makslengde på filnamn", + "gt_c2": "auk makslengde på filnamn", + + "sm_w8": "søker...", + "sm_prev": "søkeresultata er frå eit tidlegare søk:\n ", + "sl_close": "lukk søkeresultat", + "sl_hits": "visar {0} treff", + "sl_moar": "hent fleire", + + "s_sz": "størr.", + "s_dt": "dato", + "s_rd": "sti", + "s_fn": "namn", + "s_ta": "meta", + "s_ua": "up@", + "s_ad": "avns.", + "s_s1": "større enn ↓ MiB", + "s_s2": "mindre enn ↓ MiB", + "s_d1": "nyare enn <dato>", + "s_d2": "eldre enn", + "s_u1": "lasta opp etter", + "s_u2": "og/eller før", + "s_r1": "mappaamn inneheld", + "s_f1": "filnamn inneheld", + "s_t1": "song-info inneheld", + "s_a1": "konkrete eigenskapar", + + "md_eshow": "visar forenkla ", + "md_off": "[📜<em>readme</em>] er skrudd av i [⚙️] -- dokument skjult", + + "badreply": "Ugyldig svar frå serveren", + + "xhr403": "403: Høve nekta\n\nkanskje du blei logga ut? prøv å trykk F5", + "xhr0": "ukjend (enten nettverksproblem eller serverkræsj)", + "cf_ok": "om orsak -- liten tilfeldig kontroll, alt OK\n\nting skal fortsetja om ca. 30 sekund\n\nviss ikkje noko skjer, trykk F5 for å laste sida på nytt", + "tl_xe1": "kunne ikkje hente undermapper:\n\nfeil ", + "tl_xe2": "404: Mappa finnast ikkje", + "fl_xe1": "kunne ikkje hente filer i mappa:\n\nfeil ", + "fl_xe2": "404: Mappa finnast ikkje", + "fd_xe1": "kan ikkje opprette ny mappe:\n\nfeil ", + "fd_xe2": "404: Den overordna mappa finnast ikkje", + "fsm_xe1": "kunne ikkje sende melding:\n\nfeil ", + "fsm_xe2": "404: Den overordna mappa finnast ikkje", + "fu_xe1": "kunne ikkje hente lista med nyleg opplastede filer frå serveren:\n\nfeil ", + "fu_xe2": "404: Fila finnast ikkje??", + + "fz_tar": "ukomprimert gnu-tar arkiv, for linux og mac", + "fz_pax": "ukomprimert pax-tar arkiv, litt treigare", + "fz_targz": "gnu-tar pakket med gzip (nivå 3)$N$NNB: denne er veldig treig;$Nukomprimert tar er betre", + "fz_tarxz": "gnu-tar pakket med xz (nivå 1)$N$NNB: denne er veldig treig;$Nukomprimert tar er betre", + "fz_zip8": "zip med filnamn i utf8 (noko problematisk på windows 7 og eldre)", + "fz_zipd": "zip med filnamn i cp437, for høggamle maskiner", + "fz_zipc": "cp437 med tidlig crc32,$Nfor MS-DOS PKZIP v2.04g (oktober 1993)$N(øker behandlingstid på server)", + + "un_m1": "nedanfor kan du angre / slette filer som du nyleg har lastet opp, eller avbryte ufullstendige opplastinger", + "un_upd": "oppdater", + "un_m4": "eller viss du vil dele nedlastings-lenkene:", + "un_ulist": "vis", + "un_ucopy": "kopiér", + "un_flt": "valgfritt filter:  filnamn / filsti må inneholde", + "un_fclr": "nullstill filter", + "un_derr": 'unpost-sletting feilet:\n', + "un_f5": 'noko gjekk galt, prøv å oppdatere lista eller trykk F5', + "un_uf5": "om orsak, men du må laste sida på nytt (f.eks. ved å trykkje F5 eller CTRL-R) før denne opplastinga kan avbrytast", + "un_nou": '<b>advarsel:</b> kan ikkje vise ufullstendige opplastingar akkurat no; klikk på oppdater-lenka om litt', + "un_noc": '<b>advarsel:</b> angring av fullførte opplastingar er deaktivert i serverkonfigurasjonen', + "un_max": "visar dei første 2000 filene (bruk filteret for å snevre inn)", + "un_avail": "{0} nyleg opplasta filer kan slettast<br />{1} ufullstendige opplastingar kan avbrytast", + "un_m2": "sortert etter opplastingstid; nyaste først:", + "un_no1": "men nei, her var det jaggu ikkje noko som slettast kan", + "un_no2": "men nei, her var det jaggu ingenting som passa overens med filteret", + "un_next": "slett dei neste {0} filene nedanfor", + "un_abrt": "avbryt", + "un_del": "slett", + "un_m3": "hentar lista med nyleg opplasta filer...", + "un_busy": "slettar {0} filer...", + "un_clip": "{0} lenkar kopiert åt utklippstavla", + + "u_https1": "du burde", + "u_https2": "bytte åt https", + "u_https3": "for høgare hastigheit", + "u_ancient": 'nettlesaren din er prehistorisk -- mulig du burde <a href="#" onclick="goto(\'bup\')">bruke bup i staden for</a>', + "u_nowork": "krev firefox 53+, chrome 57+, eller iOS 11+", + "tail_2old": "krev firefox 105+, chrome 71+, eller iOS 14.5+", + "u_nodrop": 'nettlesaren din er for gamal åt å laste opp filer ved å drage dei inn i vindauget', + "u_notdir": "mottok ikkje mappa!\n\nnettlesaren din er for gamal,\nprøv å drage mappa inn i vindauget i staden for", + "u_uri": "for å laste opp bilder frå andre nettlesarvindauge,\nslipp bildet rett på den store last-opp-knappen", + "u_enpot": 'bytt åt <a href="#">enkelt UI</a> (gir sannsynleg raskere opplasting)', + "u_depot": 'bytt åt <a href="#">snæsent UI</a> (gir sannsynleg treigare opplasting)', + "u_gotpot": 'bytta åt eit enklare UI for å laste opp raskere,\n\ndu kan gjerne bytte tilbake altså!', + "u_pott": "<p>filer:   <b>{0}</b> ferdig,   <b>{1}</b> feilet,   <b>{2}</b> behandlast,   <b>{3}</b> i kø</p>", + "u_ever": "dette er den primitive opplastaren; up2k krev minst:<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'dette er den primitive opplastaren; <a href="#" id="u2yea">up2k</a> er betre', + "u_uput": 'litt raskare (uten sha512)', + "u_ewrite": 'du har ikkje høve til å skrive i denne mappa', + "u_eread": 'du har ikkje høve til å lese i denne mappa', + "u_enoi": 'filsøk er deaktivert i serverkonfigurasjonen', + "u_enoow": "kan ikkje overskrive filer her (Delete-rettigheiten er nødvendig)", + "u_badf": 'Disse {0} filene (av totalt {1}) kan ikkje leses, kanskje pga rettigheitsproblem i filsystemet på datamaskinen din:\n\n', + "u_blankf": 'Disse {0} filene (av totalt {1}) er blanke / uten innhald; ønskjer du å laste dei opp uansett?\n\n', + "u_applef": 'Disse {0} filene (av totalt {1}) er antakeleg uønska;\nTrykk <code>OK/Enter</code> for å HOPPE OVER disse filene,\nTrykk <code>Avbryt/ESC</code> for å LASTE OPP disse filene óg:\n\n', + "u_just1": '\nFunkar kanskje betre viss du berre tar éi fil om gangen', + "u_ff_many": 'Viss du bruker <b>Linux / MacOS / Android,</b> så kan dette antalet filer<br /><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank"><em>kanskje</em> kræsje Firefox!</a> Viss det skjer, så prøv igjen (eller bruk Chrome).', + "u_up_life": "Filene slettast frå serveren {0}\netter at opplastingen er fullført", + "u_asku": 'Laste opp disse {0} filene åt <code>{1}</code>', + "u_unpt": "Du kan angre / slette opplastinga med 🧯 oppe åt venstre", + "u_bigtab": 'Vil no vise {0} filer...\n\nDette kan kræsje nettlesaren din. Fortsette?', + "u_scan": 'Les mappane...', + "u_dirstuck": 'Nettleseren din fekk ikkje høve åt å lese følgande {0} filer/mapper, så dei blir hoppa over:', + "u_etadone": 'Ferdig ({0}, {1} filer)', + "u_etaprep": '(forberedar opplasting)', + "u_hashdone": 'synfaring ferdig', + "u_hashing": 'les', + "u_hs": 'serveren tenkjer...', + "u_started": "filene blir no lasta opp 🚀", + "u_dupdefer": "duplikat; vil bli håndtert åt slutt", + "u_actx": "klikk her for å forhindre tap av<br />yting ved bytte åt andre vindauge/faner", + "u_fixed": "OK!  Løyste seg 👍", + "u_cuerr": "kunne ikkje laste opp del {0} av {1};\nsikkert greit, fortsetjar\n\nfil: {2}", + "u_cuerr2": "server nekta opplastinga (del {0} av {1});\nprøver igjen senere\n\nfil: {2}\n\nerror ", + "u_ehstmp": "prøver igjen; se mld nederst", + "u_ehsfin": "server nekta forespørselen om å ferdigstille filen; prøver igjen...", + "u_ehssrch": "server nekta forespørselen om å utføre søk; prøver igjen...", + "u_ehsinit": "server nekta forespørselen om å begynne ei ny opplasting; prøver igjen...", + "u_eneths": "eit problem med nettverket gjorde at avtale om opplasting ikkje kunne inngås; prøver igjen...", + "u_enethd": "eit problem med nettverket gjorde at filsjekk ikkje kunne utførast; prøver igjen...", + "u_cbusy": "ventar på klarering frå server etter eit lite nettverksglipp...", + "u_ehsdf": "serveren er full!\n\nprøver igjen regelmessig,\ni tilfelle nokon ryddar litt...", + "u_emtleak1": "uff, det er mulig at nettlesaren din har ei minnelekkasje...\nForeslår", + "u_emtleak2": ' helst at du <a href="{0}">byttar åt https</a>, eller ', + "u_emtleak3": ' at du ', + "u_emtleakc": 'prøver følgande:\n<ul><li>trykk F5 for å laste sida på nytt</li><li>så skru av  <code>mt</code>  brytaren under  <code>⚙️ innstillinger</code></li><li>og prøv den same opplastinga igjen</li></ul>Opplasting vil gå litt treigare, men det får så vere.\nBeklager bryderiet!\n\nPS: feilen <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">skal vere fikset</a> i chrome v107', + "u_emtleakf": 'prøver følgende:\n<ul><li>trykk F5 for å laste sida på nytt</li><li>så skru på <code>🥔</code> ("enkelt UI") i opplastaren</li><li>og prøv den same opplastingen igjen</li></ul>\nPS: Firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">fiksar forhåpentligvis feilen</a> ein eller annen gong', + "u_s404": "ikkje funne på serveren", + "u_expl": "forklar", + "u_maxconn": "dei fleste nettlesarar tillet ikkje meir enn 6, men firefox lar deg øke grensen med <code>connections-per-server</code> i <code>about:config</code>", + "u_tu": '<p class="warn">ADVARSEL: turbo er på, <span> avbrotne opplastingar vil muligens ikkje oppdagast og gjenopptakast; hald musepeikaren over turbo-knappen for meir info</span></p>', + "u_ts": '<p class="warn">ADVARSEL: turbo er på, <span> søkeresultat kan vere feil; hold musepeikaren over turbo-knappen for meir info</span></p>', + "u_turbo_c": "turbo er deaktivert i serverkonfigurasjonen", + "u_turbo_g": 'turbo blei deaktivert fordi du ikkje har\nhøve åt å sjå mappeinnhold i dette volumet', + "u_life_cfg": 'slett opplasting etter <input id="lifem" p="60" /> min (eller <input id="lifeh" p="3600" /> timar)', + "u_life_est": 'opplastingen slettast <span id="lifew" tt="lokal tid">---</span>', + "u_life_max": 'denne mappa tillet ikkje å \noppbevare filer i meir enn {0}', + "u_unp_ok": 'opplasting kan angrast i {0}', + "u_unp_ng": 'opplasting kan IKKE angrast', + "ue_ro": 'du har ikkje høve åt skriving i denne mappa\n\n', + "ue_nl": 'du er ikkje logga inn', + "ue_la": 'du er logga inn som "{0}"', + "ue_sr": 'du er i filsøk-modus\n\nbytt åt opplasting ved å klikke på forstørringsglaset 🔎 (ved siden av den store FILSØK-knappen) og prøv igjen\n\nsorry', + "ue_ta": 'prøv å last opp igjen, det burde fungere no', + "ue_ab": "den same filen er under opplasting åt ei anna mappe, og den må fullførast der før fila kan lastast opp andre plassar.\n\nDu kan avbryte og gløyme den påbegynte opplastinga ved hjelp av 🧯 oppe åt venstre", + "ur_1uo": "OK: Fila blei lastet opp", + "ur_auo": "OK: Alle {0} filene blei lastet opp", + "ur_1so": "OK: Fila blei funne på serveren", + "ur_aso": "OK: Alle {0} filene blei funne på serveren", + "ur_1un": "Opplasting feila!", + "ur_aun": "Alle {0} opplastingene gjekk feil!", + "ur_1sn": "Fila finnast IKKE på serveren", + "ur_asn": "Fann INGEN av dei {0} filene på serveren", + "ur_um": "Ferdig;\n{0} opplastingar gjekk bra,\n{1} opplastingar gjekk feil", + "ur_sm": "Ferdig;\n{0} filer blei funne,\n{1} filer finnast IKKJE på serveren", + + "lang_set": "passar det å laste sida på nytt?", + }, + "pol": { + "tt": "Polski", + "cols": { + "c": "przyciski akcji", + "dur": "czas trwania", + "q": "jakość / bitrate", + "Ac": "kodek audio", + "Vc": "kodek wideo", + "Fmt": "format / kontener", + "Ahash": "suma kontrolna audio", + "Vhash": "suma kontrolna wideo", + "Res": "rozdzielczość", + "T": "rodzaj pliku", + "aq": "jakość / bitrate audio", + "vq": "jakość / bitrate wideo", + "pixfmt": "podpróbkowanie / struktura pikseli", + "resw": "rozdzielczość pozioma", + "resh": "rozdzielczość pionowa", + "chs": "kanały audio", + "hz": "częstotliwość próbkowania" + }, + + "hks": [ + [ + "misc", + ["ESC", "zamknij różne rzeczy"], + + "file-manager", + ["G", "przełącz widok lista / siatka"], + ["T", "przełącz miniaturki / ikony"], + ["⇧ A/D", "wielkość miniaturki"], + ["ctrl-K", "usuń zaznaczone"], + ["ctrl-X", "wytnij zaznaczone do schowka"], + ["ctrl-C", "skopiuj zaznaczone do schowka"], + ["ctrl-V", "wklej (przenieś/skopiuj) tutaj"], + ["Y", "pobierz zaznaczone"], + ["F2", "zmień nazwę zaznaczonych"], + + "file-list-sel", + ["spacja", "przełącz zaznaczanie plików"], + ["↑/↓", "przenieś kursor zaznaczenia"], + ["ctrl ↑/↓", "przenieś kursor i widok"], + ["⇧ ↑/↓", "wybierz poprzedni/następny plik"], + ["ctrl-A", "wybierz wszystkie pliki/foldery"], + ], [ + "navigation", + ["B", "przełącz ścieżkę nawigacyjną / panel nawigacyjny"], + ["I/K", "poprzedni/następny folder"], + ["M", "folder nadrzędny (lub zwiń aktualny)"], + ["V", "przełącz foldery / pliki tekstowe w panelu nawigacyjnym"], + ["A/D", "rozmiar panelu nawigacyjnego"], + ], [ + "audio-player", + ["J/L", "poprzedni/następny utwór"], + ["U/O", "przejdź 10 sek. do tyłu/przodu"], + ["0..9", "przeskocz do 0%..90%"], + ["P", "odtwórz/pauza (również rozpoczyna)"], + ["S", "wybierz odtwarzany utwór"], + ["Y", "pobierz utwór"], + ], [ + "image-viewer", + ["J/L, ←/→", "poprzednie/następne zdjęcie"], + ["Home/End", "pierwsze/ostatnie zdjęcie"], + ["F", "pełny ekran"], + ["R", "obróć zgodnie ze wskaz. zegara"], + ["⇧ R", "obróć przeciwnie do ruchu wskaz. zegara"], + ["S", "wybierz zdjęcie"], + ["Y", "pobierz zdjęcie"], + ], [ + "video-player", + ["U/O", "przejdź 10 sek. do tyłu/przodu"], + ["P/K/Spacja", "odtwórz/pauza"], + ["C", "odtwarzaj następne po zakończeniu"], + ["V", "odtwarzaj w pętli"], + ["M", "wycisz"], + ["[ i ]", "ustaw opóźnienie pętli"], + ], [ + "textfile-viewer", + ["I/K", "poprzedni/następny plik"], + ["M", "zamknij plik"], + ["E", "edytuj plik"], + ["S", "wybierz plik (do wycięcia/skopiowania/zmiany nazwy)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Anuluj", + + "enable": "Włącz", + "danger": "NIEBEZPIECZEŃSTWO", + "clipped": "skopiowano do schowka", + + "ht_s1": "sekunda", + "ht_s2": "sekundy", + "ht_s5": "sekund", + "ht_m1": "minuta", + "ht_m2": "minuty", + "ht_m5": "minut", + "ht_h1": "godzina", + "ht_h2": "godziny", + "ht_h5": "godzin", + "ht_d1": "dzień", + "ht_d2": "dni", + "ht_and": " i ", + + "goh": "panel sterowania", + "gop": 'poprzedni plik/folder">poprzedni', + "gou": 'nadrzędny folder">w górę', + "gon": 'następny folder">następny', + "logout": "Wyloguj ", + "login": "Zaloguj się", //m + "access": " dostęp", + "ot_close": "zamknij pod-menu", + "ot_search": "szukaj plików po atrybutach, ścieżce / nazwie, tagach muzyki, bądź dowolnej ich kombinacji$N$N<code>foo bar</code> = musi zawierać «foo» oraz «bar»,$N<code>foo -bar</code> = musi zawierać «foo», lecz nie «bar»,$N<code>^yana .opus$</code> = musi zaczynać się od «yana» i być plikiem «opus»$N<code>"try unite"</code> = zawierać dokładnie «try unite»$N$Nformatem daty jest iso-8601, czyli$N<code>2009-12-31</code> lub <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: usuń ostatnio przesłane pliki lub przerwij przesyłanie", + "ot_bup": "bup: podstawowe przesyłanie danych, wspiera nawet netscape 4.0", + "ot_mkdir": "mkdir: tworzy nowy folder", + "ot_md": "new-md: tworzy nowy dokument markdown", + "ot_msg": "msg: wysyła wiadomość do loga serwera", + "ot_mp": "opcje odtwarzacza multimediów", + "ot_cfg": "opcje konfiguracji", + "ot_u2i": 'up2k: przesyła pliki (jeżeli masz dostęp do zapisu) lub uruchomia tryb wyszukiwania, aby sprawdzić czy już istnieją na serwerze$N$Nprzesyłanie można wznowić, jest wielowątkowe i znaczniki czasu są zachowywane, lecz zużywa więcej procesora niż [🎈]  (podstawowe przesyłanie)<br /><br />podczas przesyłania ta ikona zamienia się w wskaźnik postępu!', + "ot_u2w": 'up2k: przesyła pliki z możliwością wznowienia (można zamknąć przeglądarkę i dokończyć przesyłanie plików później)$N$Njest wielowątkowy i zachowuje znaczniki czasu plików, lecz zużywa więcej procesora od [🎈]  (podstawowego przesyłania)<br /><br />podczas przesyłania ta ikona zamienia się w wskaźnik postępu!', + "ot_noie": 'Użyj przeglądarki Chrome / Firefox / Edge', + + "ab_mkdir": "stwórz folder", + "ab_mkdoc": "stwórz dok. markdown", + "ab_msg": "wyślij wiad. do logów serwera", + + "ay_path": "przejdź do folderów", + "ay_files": "przejdź do plików", + + "wt_ren": "zmień nazwę zaznaczonych elementów$NSkrót: F2", + "wt_del": "usuń zaznaczone elementy$NSkrót: ctrl-K", + "wt_cut": "wytnij zaznaczone elementy <small>(aby wkleić gdzie indziej)</small>$NSkrót: ctrl-X", + "wt_cpy": "skopiuj zaznaczone pliki do schowka$N(aby wkleić gdzie indziej)$NSkrót: ctrl-C", + "wt_pst": "wklej wcześniej wycięte/skopiowane zaznaczenie$NSkrót: ctrl-V", + "wt_selall": "zaznacz wszystko$NHotkey: ctrl-A (when file focused)", + "wt_selinv": "odwróć zaznaczenie", + "wt_zip1": "pobierz folder jako archiwum", + "wt_selzip": "pobierz zaznaczone jako archiwum", + "wt_seldl": "pobierz zaznaczenie jako oddzielne pliki$NSkrót: Y", + "wt_npirc": "skopiuj informacje o utworze w formacie irc", + "wt_nptxt": "skopiuj informacje o utworze jako zwykły tekst", + "wt_m3ua": "dodaj to playlisty m3u (kliknij <code>📻copy</code> kliknij)", + "wt_m3uc": "skopiuj playlistę m3u do schowka", + "wt_grid": "przełącz widok siatki / listy$NSkrót: G", + "wt_prev": "poprzeni utwór$NSkrót: J", + "wt_play": "odtwórz / pauza$NSkrót: P", + "wt_next": "następny utwór$NSkrót: L", + + "ul_par": "przesyłane równolegle:", + "ut_rand": "losuj nazwy plików", + "ut_u2ts": "kopiuj znacznik ostatniej modyfikacji$Nz twojego systemu plików na serwer\">📅", + "ut_ow": "nadpisywać istniejące pliki na serwerzę?$N🛡️: nigdy (wygeneruje nową nazwę)$N🕒: nadpisz jeśli pliki na serwerze są starsze niż przesyłane$N♻️: zawsze nadpisuj jeśli zawartość plików się różni", + "ut_mt": "hashuj inne pliki podczas przesyłania$N$Nmożna wyłączyć w przypadku wystąpienia wąskiego gardła na CPU lub HDD", + "ut_ask": 'pytaj o potwierdzenie rozpoczęcia przesyłania">💭', + "ut_pot": "przyspiesz przesyłanie na słabszych urządzeniach,$Nupraszczając interfejs", + "ut_srch": "nie przesyłaj plików, jedynie sprawdź czy istnieją$Njuż na serwerze (przeskanuje wszystkie foldery dostępne do odczytu)", + "ut_par": "zatrzymuje przesyłanie jeśli wynosi 0$N$Nzwiększ w przypadku jeśli twoja sieć jest wolna / ma duże opóźnienia$N$Nustaw wartość 1 w sieci lokalnej lub w przypadku wolnego dysku serwerowego", + "ul_btn": "upuść pliki / foldery<br>tutaj (lub kliknij mnie)", + "ul_btnu": "P R Z E Ś L I J", + "ul_btns": "S Z U K A J", + + "ul_hash": "hashowanie", + "ul_send": "przesyłanie", + "ul_done": "gotowe", + "ul_idle1": "nic się jeszcze nie przesyła", + "ut_etah": "średnia prędkość <em>hashowania</em> i przewidywany czas do końca", + "ut_etau": "średnia prędkość <em>przesyłania</em> i przewidywany czas do końca", + "ut_etat": "średnia prędkość <em>ogólna</em> i przewidywany czas do końca", + + "uct_ok": "zakończone pomyślnie", + "uct_ng": "zakończono niepowodzeniem (odrzucono, nie znaleziono, itp.)", + "uct_done": "zakończono z błędami", + "uct_bz": "w trakcie (oblicznie sumy kontrolnej, przesyłanie)", + "uct_q": "oczekujące", + + "utl_name": "nazwa pliku", + "utl_ulist": "lista", + "utl_ucopy": "kopia", + "utl_links": "linki", + "utl_stat": "status", + "utl_prog": "postęp", + + // keep short: + "utl_404": "404", + "utl_err": "BŁĄD", + "utl_oserr": "błąd OS", + "utl_found": "znaleziono", + "utl_defer": "opóźnij", + "utl_yolo": "YOLO", + "utl_done": "gotowe", + + "ul_flagblk": "pliki zostały zakolejkowane,</b><br>lecz przesyłanie up2k już trwa (w innej zakładce),<br>oczekuję na zakończenie", + "ul_btnlk": "przełącznik zablokowany przez konfigurację serwera", + + "udt_up": "Prześlij", + "udt_srch": "Szukaj", + "udt_drop": "upuść tutaj", + + "u_nav_m": '<h6>co my tu mamy?</h6><code>Enter</code> = Pliki (jeden lub wiecej)\n<code>ESC</code> = Jeden folder (włącznie z podfolderami)', + "u_nav_b": '<a href="#" id="modal-ok">Pliki</a><a href="#" id="modal-ng">Jeden folder</a>', + + "cl_opts": "przełączniki", + "cl_themes": "motyw", + "cl_langs": "język", + "cl_ziptype": "pobieranie folderów", + "cl_uopts": "przełączniki przesyłania (up2k)", + "cl_favico": "favicon (ikona w przeglądarce)", + "cl_bigdir": "duże foldery", + "cl_hsort": "#sortowanie", + "cl_keytype": "notacja klucza", // not sure + "cl_hiddenc": "ukryte kolumny", + "cl_hidec": "ukryj", + "cl_reset": "zresetuj", + "cl_hpick": "kliknij nagłówki kolumn, aby ukryć je w tabeli niżej", + "cl_hcancel": "ukrywanie kolumn przerwane", + + "ct_grid": '田 siatka', + "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_ihop": 'przejdź do ostatniego pliku po zamknięciu przeglądarki obrazów">g⮯', + "ct_dots": 'pokaż ukryte pliki (jeśli pozwala serwer)">ukryte', + "ct_qdel": 'pytaj o potwierdzenie przy usuwaniu tylko raz">pyt. us.', + "ct_dir1st": 'pokazuj foldery na początku">📁 najpierw', + "ct_nsort": 'naturalne sortowanie (dla numerowanych plików)">nsort', + "ct_utc": 'pokaż wszystkie daty/czas w UTC">UTC', + "ct_readme": 'pokazuj README.md w folderach">📜 readme', + "ct_idxh": 'pokazuj plik index.html zamiast zawartości folderu">htm', + "ct_sbars": 'pokazuj paski przewijania">⟊', + + "cut_umod": "uaktualnij znacznik ostatniej modyfikacji pliku, tak aby pasował do pliku lokalnego jeżeli plik już istnieje na serwerze (wymaga dostępu zapisu i usuwania)\">📅 ponownie", + + "cut_turbo": "przycisk „raz się żyje”, raczej NIE POWINIENEŚ tego włączać:$N$Nużywaj jeśli przesyłano ogromną liczbę plików i z jakiegoś powodu musisz przesłać pliki ponownie, kontynuując jak najszybciej$N$Nzamienia sprawdzanie sumy kontrolnej plików prostym <em>"czy ten plik jest tego samego rozmiaru jak ten na serwerze?"</em> więc jeśli pliki różnią się zawartością, ale są tego samego rozmiaru, NIE ZOSTANĄ przesłane ponownie$N$Nta opcja powinna zostać wyłączona po zakończeniu przesyłania, i potem "przesłać" te same pliki ponownie w celu weryfikacji\">turbo", + + "cut_datechk": "przy wyłączonym przycisku turbo nic nie robi$N$Nleciutko zmniejsza czynnik „raz się żyje”; dodatkowo sprawdza czy znaczniki modyfikacji pliku przesyłanego zgadzają się z serwerem$N$N<em>teorytycznie</em> powinno złapać to większość niedokończonych / uszkodzonych plików, lecz nie jest zamiennikiem wykonania ponownego sprawdzenia bez włączonego trybu turbo\">spr-daty", + + "cut_u2sz": "rozmiar (w MiB) każdego kawałka do przesłania; większe wartości szybciej latają po Atlantyku. Mniejsze wartości działają lepiej na bardzo niestabilnych połączeniach (neostrada?)", + + "cut_flag": "zapewnia, że tylko jedna karta przesyła dane w danym momencie$N -- opcja musi być włączona na innych kartach $N - dotyczy tylko kart w tej samej domenie", + + "cut_az": "przesyła pliki w kolejności alfabetycznej, zamiast rozpocząć od najmniejszego pliku$N$Nkolejność alfabetyczna może ułatwić oszacowanie, co mogło pójść nie tak na serwerze, lecz lekko spowalnia przesyłanie po światłowodzie lub w sieci lokalnej", + + "cut_nag": "powiadomienie systemowe po zakończeniu przesyłania$N(tylko jeśli przeglądarka lub karta nie jest aktywna)", + "cut_sfx": "sygnał dźwiękowy po zakończeniu przesyłania$N(tylko jeśli przeklądarka lub karta nie jest aktywna)", + + "cut_mt": "używaj wielowątkowości, aby przyspieszyć obliczanie sumy kontrolnej plików$N$Nużywa web workerów i wymaga$Nwięcej pamięci RAM (do 512 MiB)$N$Nprzyspiesza https o 30% i http 4,5-krotnie\">ww", + + "cut_wasm": "używaj WASM zamiast wbudowanego hashera przeglądarki; zwiększa prędkość na Chrome'o-pochodnych przeglądarkach, zwiększając zużycie procesora, ponadto wiele starszych wersji Chrome'a zawiera błędy powodujące zeżarcie całej pamięci RAM komputera i przymusowe zamknięcie przeglądarki jeżeli ta opcja jest włączona\">wasm", + + "cft_text": "tekst favicon (aby wyłączyć, usuń zawartość i przeładuj stronę)", + "cft_fg": "kolor tekstu", + "cft_bg": "kolor tła", + + "cdt_lim": "maksymalna liczba plików do pokazania na raz w folderze", + "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", + + "tt_entree": "pokaż panel nawigacyjny (panel boczny z drzewem folderów)$NSkrót: B", + "tt_detree": "pokaż ślad nawigacyjny$NSkrót: B", + "tt_visdir": "przewiń do wybranego folderu", + "tt_ftree": "przełącz drzewo folderów / pliki tekstowe$NSkrót: V", + "tt_pdock": "pokaż foldery nadrzędne w przypiętym u góry panelu", + "tt_dynt": "rozszerzaj panel wraz z drzewem", + "tt_wrap": "zawijaj tekst", + "tt_hover": "pokazuj za długie linie po najechaniu kursorem$N( psuje przewijanie gdy $N  kursor nie jest w lewym marginesie )", + + "ml_pmode": "na końcu folderu...", + "ml_btns": "komendy", + "ml_tcode": "transkoduj", + "ml_tcode2": "transkoduj do", + "ml_tint": "odcień", + "ml_eq": "korektor dźwięku (equalizer)", + "ml_drc": "kompresor zasięgu dynamiki", + + "mt_loop": "pętla/powtarzaj jeden utwór\">🔁", + "mt_one": "zatrzymaj po jednym utworze\">1️⃣", + "mt_shuf": "odtwarzaj losowo w każdym folderze\">🔀", + "mt_aplay": "autoodtwarzanie po kliknięciu linku do tego serwera, zawierającego identyfikator utworu$N$Nwyłączenie tej opcji zapobiegnie aktualizowaniu adresu strony podczas odtwarzania muzyki, aby zapobiec autoodtwarzaniu przy utracie ustawień\">a▶", + "mt_preload": "rozpocznij ładowanie kolejnego utworu blisko końca aktualnego w celu uzyskania odtwarzania bez przerw\">preload", + "mt_prescan": "przechodzi do następnego folderu przed zakończeniem ostatniego utworu,$Naby udobruchać przeglądarkę,$Nżeby nie zatrzymała odtwarzania\">naw", + "mt_fullpre": "próbuj zbuforować cały utwór;$N✅ włącz na <b>niestabilnych</b> połączeniach,$N❌ <b>wyłącz</b> na wolnych połączeniach\">pełnebuf", + "mt_fau": "nie zatrzymuj muzyki jeśli następna piosenka będzie się zbyt wolno buforować na telefonach (może sprawić, że tagi będą się niepoprawnie wyświetlać)\">☕️", + "mt_waves": "falisty pasek:$Npokazuj amplitudę dźwięku w pasku utworu\">~s", + "mt_npclip": "pokaż przyciski kopiowania aktualnie odtwarzanego utworu\">/np", + "mt_m3u_c": "pokaż przyciski kopiowania$Nwybranych piosenek jako playlista m3u8\">📻", + "mt_octl": "integracja z systemem operacyjnym (przyciski multimedialne / informacje o utworze)\">os-int", + "mt_oseek": "zezwól na przewijanie utworu poprzez integrację z systemem$N$Nuwaga: na niektórych urządzeniach (iPhone'y),$Nzamienia przycisk następnej piosenki\">seek", + "mt_oscv": "pokaż okładkę albumu w widoku systemu\">okładka", + "mt_follow": "podążaj za odtwarzanym utworem przewijając widok\">🎯", + "mt_compact": "kompaktowe sterowanie\">⟎", + "mt_uncache": "wyczyść pamięć podręczną  (spróbuj jeśli przeglądarka$Nzachowała zepsutą kopię utworu, przez co nie odtwarza się ona)\">uncache", + "mt_mloop": "odtwarzaj utwory w folderze w pętli\">🔁 loop", + "mt_mnext": "wczytaj następny folder i kontynuuj\">📂 next", + "mt_mstop": "zatrzymaj odtwarzanie\">⏸ stop", + "mt_cflac": "przekonwertuj format flac / wav na {0}\">flac", + "mt_caac": "przekonwertuj format aac / m4a na {0}\">aac", + "mt_coth": "przekonwertuj wszystkie inne formaty (nie będące mp3) na {0}\">oth", + "mt_c2opus": "najlepszy wybór dla komputerów, laptopów i urządzeń z androidem\">opus", + "mt_c2owa": "opus-weba, dla iOS 17.5 i nowszych\">owa", + "mt_c2caf": "opus-caf, dla iOS 11 do 17\">caf", + "mt_c2mp3": "używaj na bardzo starych urządzeniach\">mp3", + "mt_c2flac": "najlepsza jakość dźwięku, ale ogromne pliki do pobrania\">flac", //m + "mt_c2wav": "nieskompresowane odtwarzanie (jeszcze większe)\">wav", //m + "mt_c2ok": "cudownie, dobry wybór", + "mt_c2nd": "ten format nie jest rekomendowany dla twojego urządzenia, ale nadal jest w porządku", + "mt_c2ng": "wygląda na to, że to urządzenie nie wspiera tego formatu, lecz spróbujmy i tak", + "mt_xowa": "iOS zawiera błędy uniemożliwiające odtwarzanie w tle używając tego formatu; wybierz caf lub mp3", + "mt_tint": "jasność tła (0-100) paska,$Naby zmniejszyć widoczność buforowania", + "mt_eq": "włącza korektor dźwięku (equalizer) i kontrolę wzmocnienia dźwięku;$N$Nboost <code>0</code> = standardowa głośność 100% (niezmodyfikowana)$N$Nwidth <code>1  </code> = standardowe stereo (niezmodyfikowane)$Nwidth <code>0.5</code> = 50% crossfeed lewo-prawo$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = usuwanie wokalu :^)$N$Nwłączenie korektora sprawia, że albumy bezprzerwowe są w pełni bez przerw, więc jeśli jest to dla ciebie ważne, zostaw wszystko na 0 (poza width = 1)", + "mt_drc": "włącza kompresor zakresu dynamiki (normalizacja głośności); włącza również korektor w celu zbalansowania tego spaghetti, więc ustaw wszystkie opcje korektora, oprócz 'width',na 0, jeśli go nie chcesz$N$Nobniża głośność audio nad THRESHOLD (próg) dB; dla każdego RATIO (współczynnika) dB, będącego ponad THRESHOLDem jest 1 dB wyjścia, więc domyślne wartości progu -24 i współczynnika 12 znaczą, że nigdy nie powinno być głośniej niż -22 dB i bezpieczne jest zwiększenie wzmocnienia korektora do 0.8, lub nawet 1.8 z ATK 0 i ogromnym RLS, jak 90 (działa tylko na firefoxie, inne przeglądarki mają limit RLS 1)$N$N(na wikipedii tłumaczą to dużo lepiej)", + + "mb_play": "odtwórz", + "mm_hashplay": "odtworzyć ten plik audio?", + "mm_m3u": "naciśnij <code>Enter/OK</code>, aby odtworzyć\nnaciśnij <code>ESC/Anuluj</code>, aby edytować", + "mp_breq": "wymagany jest Firefox 82+, Chrome 73+ lub iOS 15+", + "mm_bload": "wczytywanie...", + "mm_bconv": "konwertowanie do {0}, proszę czekać...", + "mm_opusen": "ta przeglądarka nie może odtwarzać plików aac / m4a;\ntranskodowanie do formatu opus włączone", + "mm_playerr": "odtwarzanie nie powiodło się: ", + "mm_eabrt": "Odtwarzanie zostało przerwane", + "mm_enet": "Połączenie z internetem jest słabe", + "mm_edec": "Ten plik wydaje się uszkodzony??", + "mm_esupp": "Twoja przeglądarka nie rozumie tego formatu audio", + "mm_eunk": "Nieznany błąd", + "mm_e404": "Nie można odtworzyć; błąd 404: Nie znaleziono pliku.", + "mm_e403": "Nie można odtworzyć; błąd 403: Odmowa dostępu.\n\nSpróbuj przeładować stronę (F5), może cię wylogowało", + "mm_e500": "Nie można odtworzyć; błąd 500: Sprawdź logi serwera.", + "mm_e5xx": "Nie można odtworzyć; błąd serwera", + "mm_nof": "nie znaleziono więcej plików audio", + "mm_prescan": "Szukanie kolejnego utworu...", + "mm_scank": "Znaleziono następną piosenkę:", + "mm_uncache": "wyczyszczono pamięć podręczną; wszystkie utwory zostaną pobrane ponownie przy następnym odtworzeniu", + "mm_hnf": "ten utwór już nie istnieje", + + "im_hnf": "ten obraz już nie istnieje", + + "f_empty": 'ten folder jest pusty', + "f_chide": 'schowa kolumnę «{0}»\n\nkolumny można ponownie pokazać w zakładce ustwaień', + "f_bigtxt": "ten plik waży {0} MiB -- na pewno pokazać jako tekst?", + "f_bigtxt2": "odczytać jedynie koniec pliku? włączy również śledzenie, pokazując nowo-dodane linie tekstu w czasie rzeczywistym", + "fbd_more": '<div id="blazy">pokazuję <code>{0}</code> z <code>{1}</code> plików; <a href="#" id="bd_more">pokaż {2}</a> lub <a href="#" id="bd_all">pokaż wszystko</a></div>', + "fbd_all": '<div id="blazy">pokazuję <code>{0}</code> z <code>{1}</code> files; <a href="#" id="bd_all">pokaż wszystko</a></div>', + "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_partial": "Aby bezpiecznie pobrać plik, który aktualnie jest przesyłany, wybierz plik o tej samej nazwie, lecz bez rozszerzenia <code>.PARTIAL</code>. Żeby to zrobić, naciśnij ANULUJ lub klawisz ESC.\n\nWciśnięcie OK / Enter zignoruje to ostrzeżenie i pobierze plik tymczasowy <code>.PARTIAL</code>, który prawie z pewnością będzie zepsuty", + + "ft_paste": "wklej {0} elementów$NSkrót: ctrl-V", + "fr_eperm": 'nie można zmienić nazwy:\nnie posiadasz uprawnienia „move” w tym folderze', + "fd_eperm": 'nie można usunąć:\nnie posiadasz uprawnienia „delete” w tym folderze', + "fc_eperm": 'nie można wyciąć:\nnie posiadasz uprawnienia „move” w tym folderze', + "fp_eperm": 'nie można wkleić:\nnie posiadasz uprawnienia „write” w tym folderze', + "fr_emore": "wybierz przynajmniej jeden element do zmiany nazwy", + "fd_emore": "wybierz przynajmniej jeden element do usunięcia", + "fc_emore": "wybierz przynajmniej jeden element do wycięcia", + "fcp_emore": "wybierz przynajmniej jeden element do skopiowania", + + "fs_sc": "udostępnij ten folder", + "fs_ss": "udostępnij zaznaczone pliki", + "fs_just1d": "nie można wybrać więcej niż jednego folderu,\nani mieszać plików i folderów w jednym zaznaczeniu", + "fs_abrt": "❌ przerwij", + "fs_rand": "🎲 losuj nazwę", + "fs_go": "✅ stwórz udostępnienie", + "fs_name": "nazwa", + "fs_src": "źródło", + "fs_pwd": "hasło", + "fs_exp": "wygaśnięcie", + "fs_tmin": "min", + "fs_thrs": "godz.", + "fs_tdays": "dni", + "fs_never": "na zawsze", + "fs_pname": "opcjonalna nazwa linku; zostanie wylosowana jeśli pusta", + "fs_tsrc": "plik lub folder do udostępnienia", + "fs_ppwd": "hasło (opcjonalnie)", + "fs_w8": "udostępnianie...", + "fs_ok": "naciśnij <code>Enter/OK</code>, aby skopiować do schowka\nnaciśnij <code>ESC/Anuluj</code>, aby zamknąć", + + "frt_dec": "może naprawić niektóre zepsute nazwy plików\">dekoduj-url", + "frt_rst": "zresetuj zmodyfikowane nazwy plików do oryginalnych\">↺ zresetuj", + "frt_abrt": "przerwij i zamknij to okno\">❌ anuluj", + "frb_apply": "ZASTOSUJ ZMIANĘ NAZWY", + "fr_adv": "zmiana nazwy hurtowa / metadanych / wzorcem\">zaawansowane", + "fr_case": "rozróżnianie wielkości liter w regex\">wlit", + "fr_win": "nazwy bezpieczne dla systemu Windows; zamienia symbole <code><>:"\\|?*</code> na japońskie odpowiedniki\">win", + "fr_slash": "zamienia <code>/</code> symbolem, który nie tworzy nowych folderów\">brak /", + "fr_re": "wzorzec wyszukiwania regex stosowany do oryginalnych nazw plików; do grup przechwytywania można się odwołać w polu formatu poniżej, np. <code>(1)</code> i <code>(2)</code> itd.", + "fr_fmt": "inspirowane programem foobar2000:$N<code>(title)</code> zostaje zamienione na tytuł utworu,$N<code>[(artist) - ](title)</code> pomija [tą] część jeśli pole artysty jest puste$N<code>$lpad((tn),2,0)</code> wyrównuje numer utworu do 2 cyfr (np. 01, 06, 09, 16)", + "fr_pdel": "usuń", + "fr_pnew": "zapisz jako", + "fr_pname": "podaj nazwę nowego szablonu", + "fr_aborted": "anulowano", + "fr_lold": "poprzednia nazwa", + "fr_lnew": "nowa nazwa", + "fr_tags": "znaczniki dla wybranych plików (tylko do odczytu, w celach informacyjnych):", + "fr_busy": "zmienianie nazwy {0} plików...\n\n{1}", + "fr_efail": "zmiana nazwy zakończona niepowodzeniem:\n", + "fr_nchg": "{0} nowych nazw zostało zmienionych przez opcje <code>win</code> i/lub <code>brak /</code>\n\nKontynuować ze zmienionymi nazwami?", + + "fd_ok": "usunięto", + "fd_err": "usuwanie zakończone niepowodzeniem:\n", + "fd_none": "nie usunięto nic; usunięcie mogło zostać zablokowane przez konfigurację serwera (xbd)?", + "fd_busy": "usuwanie {0} elementów...\n\n{1}", + "fd_warn1": "USUNĄĆ {0} elementów?", + "fd_warn2": "<b>OSTATNIA SZANSA!</b> Tej operacji nie da się cofnąć. Usunąć?", + + "fc_ok": "wycięto {0} elementów", + "fc_warn": 'wycięto {0} elementów,\n\nlecz można je wkleić tylko w <b>tej</b> karcie\n(ze względu na ogromną ilość wybranych elementów)', + + "fcc_ok": "skopiowano {0} elementów do schowka", + "fcc_warn": 'skopiowano {0} elementów,\n\nlecz można je wkleić tylko w <b>tej</b> karcie\n(ze względu na ogromną ilość wybranych elementów)', + + "fp_apply": "zastosuj te nazwy", + "fp_ecut": "najpierw wytnij lub skopiuj pliki / foldery, aby je wkleić / przenieść\n\nuwaga: można wycinać / wklejać pomiędzy różnymi kartami przeglądarki", + "fp_ename": "Nie udało się przenieść {0} elementów, gdyż ich nazwy już istnieją w tym folderze. Nadaj im nowe nazwy poniżej, bądź zostaw pole nazwy puste, aby je pominąć:", + "fcp_ename": "Nie udało się przekopiować {0} elementów, gdyż ich nazwy już istnieją w tym folderze. Nadaj im nowe nazwy poniżej, bądź zostaw pole nazwy puste, aby je pominąć:", + "fp_emore": "pozostało jeszcze kilka kolizji nazw plików do poprawy", + "fp_ok": "przeniesiono", + "fcp_ok": "przekopiowano", + "fp_busy": "przenoszenie {0} elementów...\n\n{1}", + "fcp_busy": "kopiowanie {0} elementów...\n\n{1}", + "fp_abrt": "przerywanie...", //m + "fp_err": "nie udało się przenieść:\n", + "fcp_err": "nie udało się skopiować:\n", + "fp_confirm": "przenieść tutaj {0} elementy(ów)?", + "fcp_confirm": "skopiować tutaj {0} elementy(ów)?", + "fp_etab": 'nie udało się odczytać schowka z innej karty przeglądarki', + "fp_name": "przesyłanie pliku z twojego urządzenia. Nadaj nazwę:", + "fp_both_m": '<h6>wybierz metodę wklejenia</h6><code>Enter</code> = Przenieś {0} pliki(ów) z «{1}»\n<code>ESC</code> = Prześlij {2} pliki(ów) z twojego urządzenia', + "fcp_both_m": '<h6>wybierz metodę wklejenia</h6><code>Enter</code> = Skopiuj {0} pliki(ów) z «{1}»\n<code>ESC</code> = Prześlij {2} pliki(ów) z twojego urządzenia', + "fp_both_b": '<a href="#" id="modal-ok">Przenieś</a><a href="#" id="modal-ng">Prześlij</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopiuj</a><a href="#" id="modal-ng">Prześlij</a>', + + "mk_noname": "wpisz nazwę do pola po lewej zanim to zrobisz :p", + + "tv_load": "Wczytywanie pliku tekstowego:\n\n{0}\n\n{1}% (wczytano {2} z {3} MiB)", + "tv_xe1": "nie udało się wczytać pliku:\n\nbłąd ", + "tv_xe2": "404, nie znaleziono pliku", + "tv_lst": "lista plików tekstowych w", + "tvt_close": "powróć do widoku folderów$NSkrót: M (lub Esc)\">❌ zamknij", + "tvt_dl": "pobierz ten plik$NHotkey: Y\">💾 pobierz", + "tvt_prev": "pokaż poprzedni dokument$NSkrót: i\">⬆ poprzedni", + "tvt_next": "pokaż następny dokument$NSkrót: K\">⬇ następny", + "tvt_sel": "wybierz plik   ( do wycięcia / skopiowania / usunięcia / itp. )$NSkrót: S\">wyb", + "tvt_edit": "otwórz plik w edytorze tekstu$NSkrót: E\">✏️ edytuj", + "tvt_tail": "śledź zmiany w pliku; pokazuj nowe linie w czasie rzeczywistym\">📡 śledź", + "tvt_wrap": "zawijaj tekst\">↵", + "tvt_atail": "utrzymuj widok na dole strony\">⚓", + "tvt_ctail": "dekoduj kolory terminala (sekwencje sterujące ANSI)\">🌈", + "tvt_ntail": "limit przewijania (ile bajtów tekstu przechowywać w pamięci)", + + "m3u_add1": "dodano utwór do playlisty m3u", + "m3u_addn": "dodano {0} utwory(ów) do playlisty m3u", + "m3u_clip": "skopiowano playlistę m3u do schowka\n\nutwórz", + + "gt_vau": "nie pokazuj obrazu, odtwarzaj tylko dźwięk\">🎧", + "gt_msel": "wybierz pliki; kliknij plik z wciśniętym klawiszem CTRL, aby zastąpić$N$N<em>gdy tryb jest aktywny, kliknij dwukrotnie na plik / folder, żeby go otworzyć</em>$N$NSkrót: S\">wybierz wiele", + "gt_crop": "kadruj miniaturki do środka\">kadruj", + "gt_3x": "miniaturki w wysokiej rozdzielczości\">3x", + "gt_zoom": "przybliż", + "gt_chop": "przytnij", + "gt_sort": "sortuj według", + "gt_name": "nazwa", + "gt_sz": "rozmiar", + "gt_ts": "data", + "gt_ext": "typ", + "gt_c1": "przycinaj większą część nazw plików (pokazuj mniej)", + "gt_c2": "przycinaj mniejszą część nazw plików (pokazuj więcej)", + + "sm_w8": "wyszukiwanie...", + "sm_prev": "wyniki wyszukiwania poniżej pochodzą z poprzedniego zapytania:\n ", + "sl_close": "zamknij wyniki wyszukiwania", + "sl_hits": "pokazuję {0} wyniki(ów)", + "sl_moar": "pokaż więcej", + + "s_sz": "rozmiar", + "s_dt": "data", + "s_rd": "ścieżka", + "s_fn": "nazwa", + "s_ta": "znaczniki", + "s_ua": "data przesłania", + "s_ad": "zaawansowane", + "s_s1": "min. rozmiar (MiB)", + "s_s2": "maks. rozmiar (MiB)", + "s_d1": "min. data iso8601", + "s_d2": "maks. data iso8601", + "s_u1": "przesłane po", + "s_u2": "i/lub przed", + "s_r1": "ścieżka zawiera   (oddzielone spacją)", + "s_f1": "nazwa zawiera   (odwróć za pomocą -nope)", + "s_t1": "znaczniki zawierają   (^=start, koniec=$)", + "s_a1": "dokładne właściwości metadanych", + + "md_eshow": "nie można wyświetlić ", + "md_off": "[📜<em>readme</em>] wyłączone w [⚙️] -- dokument ukryty", + + "badreply": "Nie udało się przeanalizować odpowiedzi serwera", + + "xhr403": "403: Odmowa dostępu.\n\nSpróbuj przeładować stronę (F5), możliwe, że cię wylogowano", + "xhr0": "nieznany (być może utracono połączenie z serwerem, lub jest on nieaktywny)", + "cf_ok": "przepraszamy, włączyła się ochrona przed DD" + wah + "oS\n\nwszystko powinno wrócić do normy za około 30 sekund\n\njeśli nic się nie zmieni, naciśnij klawisz F5, aby przeładować stronę", + "tl_xe1": "nie można wyświetlić podfolderów:\n\nbłąd ", + "tl_xe2": "404: Nie znaleziono folderu", + "fl_xe1": "nie można wyświetlić plików w folderze:\n\nbłąd ", + "fl_xe2": "404: Nie znaleziono folderu", + "fd_xe1": "nie można stworzyć podfolderu:\n\nbłąd ", + "fd_xe2": "404: Nie znaleziono folderu nadrzędnego", + "fsm_xe1": "nie można wysłać wiadomości:\n\nbłąd ", + "fsm_xe2": "404: Nie znaleziono folderu nadrzędnego", + "fu_xe1": "nie udało się wczytać listy unpost z serwera:\n\nbłąd ", + "fu_xe2": "404: Nie znaleziono pliku??", + + "fz_tar": "nieskompresowane archiwum gnu-tar (linux / mac)", + "fz_pax": "nieskompresowane archiwum tar w formacie pax (wolniejsze)", + "fz_targz": "gnu-tar z kompresją gzip poziomu 3.,$N$Nzazwyczaj bardzo wolne, używaj nieskompresowanego tar", + "fz_tarxz": "gnu-tar z kompresją xz poziomu 3.$N$Nzazwyczaj bardzo wolne, używaj nieksompresowanego tar", + "fz_zip8": "zip z nazwami plików UTF-8 (może działać nieprawidłowo na systemie Windows 7 i starszych)", + "fz_zipd": "zip z nazwami plików cp437, dobre dla bardzo starego oprogramowania", + "fz_zipc": "cp437 z CRC32 obliczonym wcześniej,$Ndla MS-DOS PKZIP v2.04g (październik 1993)$N(przetwarzanie do pobrania trwa dłużej)", + + "un_m1": "można usunąć ostatnio przesłane pliki (lub przerwać trwające) poniżej", + "un_upd": "odśwież", + "un_m4": "lub udostępnij pliki widoczne poniżej:", + "un_ulist": "pokaż", + "un_ucopy": "kopiuj", + "un_flt": "filtruj (opcjonalnie):  URL musi zawierać", + "un_fclr": "wyczyść kryteria filtrowania", + "un_derr": 'nie udało się usunąć unpost:\n', + "un_f5": 'coś poszło nie tak, spróbuj odświeżyć lub wciśnij F5', + "un_uf5": "przed przerwaniem przesyłania trzeba odświeżyć stronę (za pomocą CTRL-R lub F5)", + "un_nou": '<b>ostrzeżenie:</b> serwer jest aktualnie zbyt obciążony, żeby pokazać niedokończone przesłania; kliknij link "odśwież" za chwilę', + "un_noc": '<b>ostrzeżenie:</b> unpost w pełni przesłanych plików jest wyłączone/zabronione w konfiguracji serwera', + "un_max": "pokazuję pierwsze 2000 plików (użyj filtrowania)", + "un_avail": "{0} ostatnio przesłanych elementów może zostać usunięte<br />{1} niedokończonych można przerwać", + "un_m2": "przesortowano po czasie przesłania; najnowsze elementy pierwsze: ", + "un_no1": "cholibka! żaden przesłany element nie jest wystarczająco niedawny", + "un_no2": "cholibka! żaden przesłany element pasujący do filtra nie jest wystarczająco niedawny", + "un_next": "usuń następne {0} pliki(ów) poniżej", + "un_abrt": "przerwij", + "un_del": "usuń", + "un_m3": "wczytywanie ostatnio przesłanych elementów...", + "un_busy": "usuwanie {0} plików...", + "un_clip": "skopiowano {0} linków do schowka", + + "u_https1": "powinieneś przejść", + "u_https2": "na HTTPS w celu", + "u_https3": "uzyskania lepszej wydajności", + "u_ancient": 'twoja przeglądarka jest niezwykle zabytkowa -- powinieneś zamiast tego <a href="#" onclick="goto(\'bup\')">użyć bup</a>', + "u_nowork": "wymaga Firefox 53+, Chrome 57+ lub iOS 11+", + "tail_2old": "wymaga Firefox 105+, Chrome 71+ lub iOS 14.5+", + "u_nodrop": 'ta przeglądarka jest za stara, nie wspiera przesyłania "przeciągnij i upuść"', + "u_notdir": "to nie jest folder!\n\nta przeglądarka jest za stara\nspróbuj przeciągnąć i upuścić", + "u_uri": "aby przeciągnąć i upuścić obrazy z innych okien przeglądarki,\nupuść je na duży przycisk przesyłania", + "u_enpot": 'przełącz na <a href="#">lekki interfejs</a> (może zwiększyć prędkość przesyłania)', + "u_depot": 'przełącz na <a href="#">ładny interfejs</a> (może zmniejszyć prędkośc przesyłania)', + "u_gotpot": 'przełączanie na lekki interfejs w celu poprawy prędkości przesyłania,\n\nzawsze można przełączyć się na ładny interfejs!', + "u_pott": "<p>pliki:   <b>{0}</b> ukończonych,   <b>{1}</b> nie powiodło się,   <b>{2}</b> w trakcie,   <b>{3}</b> oczekujących</p>", + "u_ever": "podstawowe przesyłanie; up2k wymaga minimalnie przeglądarek:<br>Chrome 21 // Firefox 13 // Edge 12 // Opera 12 // Safari 5.1", + "u_su2k": 'podstawowe przesyłanie; <a href="#" id="u2yea">up2k</a> jest lepszy', + "u_uput": 'optymalizuj dla prędkości (pomijając spr. sum kontrolnych)', + "u_ewrite": 'nie masz dostępu do zapisu (write) w tym folderze', + "u_eread": 'nie masz dostępu do odczytu (read) tego folderu', + "u_enoi": 'wyszukiwanie plików jest wyłączone w konfiguracji serwera', + "u_enoow": "nadpisanie nie zadziała, wymagany dostęp do usuwania (delete)", + "u_badf": '{0} (z {1}) plików zostało pominiętych, prawdopodobnie przez opcje dostępu systemu plików:\n\n', + "u_blankf": '{0} (z {1}) plików jest pustych; przesłać mimo to?\n\n', + "u_applef": '{0} (z {1}) plików może być niepożądane;\nNaciśnij <code>OK/Enter</code>, aby pominąć je (wypisane poniżej);\nNaciśnij <code>Anuluj/ESC</code>, by je przesłać mimo to:\n\n', + "u_just1": '\nTa funkcja może działać lepiej z wybranym jednym plikiem', + "u_ff_many": "na systemach <b>Linux / MacOS / Android,</b> ta ilośc plików <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>może</em> spowodować przymusowe zamknięcie przeglądarki Firefox</a>\nw takim przypadku, spróbuj ponownie (lub użyj Chrome'a).", + "u_up_life": "Ten przesyłany plik zostanie usunięty z serwera\n{0} po zakończeniu przesyłania", + "u_asku": 'prześlij {0} pliki(ów) do <code>{1}</code>', + "u_unpt": "można cofnąć / usunąć ten przesłany plik za pomocą 🧯 w lewym górnym rogu", + "u_bigtab": 'zaraz pokażę {0} plików\n\nta operacja może zawiesić twoją przeglądarkę, na pewno kontynuować?', + "u_scan": 'Skanowanie plików...', + "u_dirstuck": 'iterator katalogów utknął podczas próby dostępu poniższych {0} elementów, pominięto:', + "u_etadone": 'Ukończono ({0}, {1} plików)', + "u_etaprep": '(przygotowywanie do przesłania)', + "u_hashdone": 'obliczono sumę kontrolną', + "u_hashing": 'obliczanie sumy kontrolnej', + "u_hs": 'nawiązywanie połączenia...', + "u_started": "rozpoczęto przesyłanie; zobacz w [🚀]", + "u_dupdefer": "duplikat; zostanie przetworzony na końcu", + "u_actx": "kliknij ten napis, aby zapobiec spadkowi <br />wydajności po zmianie aktywnego okna/karty przeglądarki", + "u_fixed": "OK!  Naprawiono 👍", + "u_cuerr": "nie udało się przesłać fragmentu {0} z {1};\nprawdopodobnie niegroźne, kontynuowanie\n\nplik: {2}", + "u_cuerr2": "serwer odrzucił przesyłanie (kawałek {0} z {1});\nspróbuję ponownie później\n\nplik: {2}\n\nbłąd ", + "u_ehstmp": "spróbuję ponownie; więcej informacji w prawym dolnym rogu", + "u_ehsfin": "serwer odrzucił prośbę o zakończenie przesyłania; próbuję ponownie...", + "u_ehssrch": "serwer odrzucił prośbę o wykonanie wyszukania; próbuję ponownie...", + "u_ehsinit": "serwer odrzucił prośbę o rozpoczęcie przesyłania; próbuję ponownie...", + "u_eneths": "błąd sieci podczas negocjacji warunków przesyłania; próbuję ponownie...", + "u_enethd": "błąd sieci podczas sprawdzania istnienia celu; próbuję ponownie...", + "u_cbusy": "oczekiwanie na ponowne zaufanie serwera po błędzie sieci...", + "u_ehsdf": "brak miejsca na dysku serwera!\n\npróby będą ponawiane na wypadek\nzwolnienia wystarczająco dużo miejsca aby kontynuować", + "u_emtleak1": "wygląda na to, że twoja przeglądarka może mieć wyciek pamięci;\n", + "u_emtleak2": ' <a href="{0}">przejdź na HTTPS (zalecane)</a> lub ', + "u_emtleak3": ' ', + "u_emtleakc": 'spróbuj:\n<ul><li>wciśnij <code>F5</code>, aby odświeżyć stronę</li><li>wyłącz przycisk  <code>ww</code>  w menu <code>⚙️ ustawienia</code></li><li>i spróbuj przesłać ponownie</li></ul>Prędkość przesyłania będzie niższa, ale cóż zrobisz.\nPrzepraszamy za problemu!\n\nPS: Chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">ma poprawkę tego błędu</a>.', + "u_emtleakf": 'spróbuj:\n<ul><li>wciśnij <code>F5</code>, aby odświeżyć stronę</li><li>włącz tryb <code>🥔</code> (lekkiego interfejsu) w interfejsie przesyłania<li>i spróbuj przesłać ponownie</li></ul>\nPS: Firefox może kiedyś mieć <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">poprawkę tego błędu</a>', + "u_s404": "nie znaleziono na serwerze", + "u_expl": "wytłumacz", + "u_maxconn": "większość przeglądarek ogranicza to do 6, ale Firefox pozwala zwiększyć tą wartość, ustawiając <code>connections-per-server</code> w <code>about:config</code>", + "u_tu": '<p class="warn">UWAGA: tryb turbo włączony, <span> klient może nie wykryć i nie kontynuować niedokończonych przesłań; patrz wskazówka przycisku turbo</span></p>', + "u_ts": '<p class="warn">UWAGA: tryb turbo włączony, <span> wyniki wyszukiwania mogą być niepoprawne; patrz wskazówka przycisku turbo</span></p>', + "u_turbo_c": "tryb turbo jest wyłączony w konfiguracji serwera", + "u_turbo_g": "wyłączanie trybu turbo, nie posiadasz dostępu\ndo listy katalogu w tym wolumenie", + "u_life_cfg": 'autousuwanie po <input id="lifem" p="60" /> min (lub <input id="lifeh" p="3600" /> godz.)', + "u_life_est": 'przesłany plik zostanie usunięty <span id="lifew" tt="local time">---</span>', + "u_life_max": 'ten folder wymaga\nmaks. czasu do usunięcia równego {0}', + "u_unp_ok": 'unpost jest dozwolony przez {0}', + "u_unp_ng": 'unpost NIE jest dozwolony', + "ue_ro": 'dostęp tylko-do-odczytu\n\n', + "ue_nl": 'nie jesteś zalogowany', + "ue_la": 'zalogowano jako "{0}"', + "ue_sr": 'jesteś w trybie wyszukiwania\n\nprzełącz się na tryb przesyłania, klikając lupę 🔎 (obok przycisku Szukaj), i spróbuj ponownie', + "ue_ta": 'spróbuj przesłać ponownie, wszystko powinno być w porządku', + "ue_ab": "ten plik już jest przesyłany do innego folderu, przesyłanie musi się zakończyć, zanim będzie mógł być on przesłany gdzie indziej.\n\nMożna przerwać pierwsze przesyłanie za pomocą 🧯 w lewym górnym rogu", + "ur_1uo": "OK: Plik przesłany pomyślnie", + "ur_auo": "OK: Wszystkie ({0}) pliki zostały przesłane pomyślnie", + "ur_1so": "OK: Znaleziono plik na serwerze", + "ur_aso": "OK: Znaleziono wszystkie ({0}) pliki na serwerze", + "ur_1un": "Przesyłanie nie powiodło się", + "ur_aun": "Wszystkie ({0}) przesłania nie powiodły się", + "ur_1sn": "NIE znaleziono pliku na serwerze", + "ur_asn": "NIE znaleziono {0} plików na serwerze", + "ur_um": "Zakończono;\n{0} przesłań OK,\n{1} przesłań nie powiodło się", + "ur_sm": "Zakończono;\nznaleziono {0} pliki(ów),\nnie znaleziono {1} pliki(ów) na serwerze", + + "lang_set": "odśwież stronę (F5), aby zastosować zmianę.", + }, + "por": { + "tt": "Português", + + "cols": { + "c": "botões de ação", + "dur": "duração", + "q": "qualidade / bitrate", + "Ac": "codec de áudio", + "Vc": "codec de vídeo", + "Fmt": "formato / contêiner", + "Ahash": "checksum de áudio", + "Vhash": "checksum de vídeo", + "Res": "resolução", + "T": "tipo de arquivo", + "aq": "qualidade / bitrate de áudio", + "vq": "qualidade / bitrate de vídeo", + "pixfmt": "subamostragem / estrutura de pixel", + "resw": "resolução horizontal", + "resh": "resolução vertical", + "chs": "canais de áudio", + "hz": "taxa de amostragem" + }, + + "hks": [ + [ + "diversos", + ["ESC", "fechar várias coisas"], + + "gerenciador de arquivos", + ["G", "alternar entre visualização de lista / grade"], + ["T", "alternar entre miniaturas / ícones"], + ["⇧ A/D", "tamanho da miniatura"], + ["ctrl-K", "excluir selecionados"], + ["ctrl-X", "recortar seleção para a área de transferência"], + ["ctrl-C", "copiar seleção para a área de transferência"], + ["ctrl-V", "colar (mover/copiar) aqui"], + ["Y", "baixar selecionado"], + ["F2", "renomear selecionado"], + + "seleção de lista de arquivos", + ["espaço", "alternar seleção de arquivo"], + ["↑/↓", "mover cursor de seleção"], + ["ctrl ↑/↓", "mover cursor e visualização"], + ["⇧ ↑/↓", "selecionar arquivo anterior/próximo"], + ["ctrl-A", "selecionar todos os arquivos / pastas"], + ], [ + "navegação", + ["B", "alternar entre breadcrumbs / painel de navegação"], + ["I/K", "pasta anterior/próxima"], + ["M", "pasta pai (ou desexpandir a atual)"], + ["V", "alternar entre pastas / arquivos de texto no painel de navegação"], + ["A/D", "tamanho do painel de navegação"], + ], [ + "reprodutor de áudio", + ["J/L", "música anterior/próxima"], + ["U/O", "pular 10 segundos para trás/frente"], + ["0..9", "pular para 0%..90%"], + ["P", "reproduzir/pausar (também inicia)"], + ["S", "selecionar a música que está tocando"], + ["Y", "baixar música"], + ], [ + "visualizador de imagens", + ["J/L, ←/→", "imagem anterior/próxima"], + ["Home/End", "primeira/última imagem"], + ["F", "tela cheia"], + ["R", "girar no sentido horário"], + ["⇧ R", "girar no sentido anti-horário"], + ["S", "selecionar imagem"], + ["Y", "baixar imagem"], + ], [ + "reprodutor de vídeo", + ["U/O", "pular 10 segundos para trás/frente"], + ["P/K/Espaço", "reproduzir/pausar"], + ["C", "continuar reproduzindo o próximo"], + ["V", "loop"], + ["M", "mudo"], + ["[ e ]", "definir intervalo de loop"], + ], [ + "visualizador de arquivos de texto", + ["I/K", "arquivo anterior/próximo"], + ["M", "fechar arquivo de texto"], + ["E", "editar arquivo de texto"], + ["S", "selecionar arquivo (para recortar/copiar/renomear)"], + ] + ], + + "m_ok": "OK", + "m_ng": "Cancelar", + + "enable": "Ativar", + "danger": "PERIGO", + "clipped": "copiado para a área de transferência", + + "ht_s1": "segundo", + "ht_s2": "segundos", + "ht_m1": "minuto", + "ht_m2": "minutos", + "ht_h1": "hora", + "ht_h2": "horas", + "ht_d1": "dia", + "ht_d2": "dias", + "ht_and": " e ", + + "goh": "painel de controle", + "gop": 'pai anterior">anterior', + "gou": 'pasta pai">acima', + "gon": 'próxima pasta">próximo', + "logout": "Sair ", + "login": "Fazer login", + "access": " acesso", + "ot_close": "fechar submenu", + "ot_search": "procurar arquivos por atributos, caminho / nome, tags de música ou qualquer combinação deles$N$N<code>foo bar</code> = deve conter ambos «foo» e «bar»,$N<code>foo -bar</code> = deve conter «foo» mas não «bar»,$N<code>^yana .opus$</code> = começar com «yana» e ser um arquivo «opus»$N<code>"try unite"</code> = conter exatamente «try unite»$N$No formato de data é iso-8601, como$N<code>2009-12-31</code> ou <code>2020-09-12 23:30:00</code>", + "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-md: criar um novo documento markdown", + "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", + "ot_u2i": 'up2k: fazer upload de arquivos (se você tiver acesso de escrita) ou alternar para o modo de busca para ver se eles já existem em algum lugar no servidor$N$Nuploads são reiniciáveis, multithread, e os carimbos de data/hora dos arquivos são preservados, mas usa mais CPU que [🎈]  (o uploader básico)<br /><br />durante os uploads, este ícone se torna um indicador de progresso!', + "ot_u2w": 'up2k: fazer upload de arquivos com suporte a retomada (feche seu navegador e solte os mesmos arquivos mais tarde)$N$Nmultithread, e os carimbos de data/hora dos arquivos são preservados, mas usa mais CPU que [🎈]  (o uploader básico)<br /><br />durante os uploads, este ícone se torna um indicador de progresso!', + "ot_noie": 'Por favor, use Chrome / Firefox / Edge', + + "ab_mkdir": "criar diretório", + "ab_mkdoc": "novo documento markdown", + "ab_msg": "enviar msg para o log do srv", + + "ay_path": "pular para pastas", + "ay_files": "pular para arquivos", + + "wt_ren": "renomear itens selecionados$NHotkey: F2", + "wt_del": "excluir itens selecionados$NHotkey: ctrl-K", + "wt_cut": "recortar itens selecionados <small>(depois colar em outro lugar)</small>$NHotkey: ctrl-X", + "wt_cpy": "copiar itens selecionados para a área de transferência$N(para colá-los em outro lugar)$NHotkey: ctrl-C", + "wt_pst": "colar uma seleção previamente recortada / copiada$NHotkey: ctrl-V", + "wt_selall": "selecionar todos os arquivos$NHotkey: ctrl-A (quando o arquivo estiver em foco)", + "wt_selinv": "inverter seleção", + "wt_zip1": "baixar esta pasta como um arquivo compactado", + "wt_selzip": "baixar seleção como um arquivo compactado", + "wt_seldl": "baixar seleção como arquivos separados$NHotkey: Y", + "wt_npirc": "copiar informações da faixa em formato irc", + "wt_nptxt": "copiar informações da faixa em texto simples", + "wt_m3ua": "adicionar à playlist m3u (clique em <code>📻copiar</code> depois)", + "wt_m3uc": "copiar playlist m3u para a área de transferência", + "wt_grid": "alternar entre visualização de grade / lista$NHotkey: G", + "wt_prev": "faixa anterior$NHotkey: J", + "wt_play": "reproduzir / pausar$NHotkey: P", + "wt_next": "próxima faixa$NHotkey: L", + + "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", + "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", + "ul_btn": "soltar arquivos / pastas<br>aqui (ou clique em mim)", + "ul_btnu": "U P L O A D", + "ul_btns": "B U S C A R", + + "ul_hash": "hash", + "ul_send": "enviar", + "ul_done": "feito", + "ul_idle1": "nenhum upload está na fila ainda", + "ut_etah": "velocidade média de <em>hash</em>, e tempo estimado até o fim", + "ut_etau": "velocidade média de <em>upload</em> e tempo estimado até o fim", + "ut_etat": "velocidade média <em>total</em> e tempo estimado até o fim", + + "uct_ok": "concluído com sucesso", + "uct_ng": "ruim: falhou / rejeitado / não encontrado", + "uct_done": "ok e ruim combinados", + "uct_bz": "fazendo hash ou upload", + "uct_q": "ocioso, pendente", + + "utl_name": "nome do arquivo", + "utl_ulist": "lista", + "utl_ucopy": "copiar", + "utl_links": "links", + "utl_stat": "status", + "utl_prog": "progresso", + + // mantenha curto: + "utl_404": "404", + "utl_err": "ERRO", + "utl_oserr": "Erro-SO", + "utl_found": "encontrado", + "utl_defer": "adiar", + "utl_yolo": "YOLO", + "utl_done": "feito", + + "ul_flagblk": "os arquivos foram adicionados à fila</b><br>no entanto, há um up2k ocupado em outra aba do navegador,<br>então esperando que ele termine primeiro", + "ul_btnlk": "a configuração do servidor bloqueou este interruptor neste estado", + + "udt_up": "Upload", + "udt_srch": "Buscar", + "udt_drop": "solte aqui", + + "u_nav_m": '<h6>certo, o que você tem?</h6><code>Enter</code> = Arquivos (um ou mais)\n<code>ESC</code> = Uma pasta (incluindo subpastas)', + "u_nav_b": '<a href="#" id="modal-ok">Arquivos</a><a href="#" id="modal-ng">Uma pasta</a>', + + "cl_opts": "interruptores", + "cl_themes": "tema", + "cl_langs": "idioma", + "cl_ziptype": "download de pasta", + "cl_uopts": "interruptores up2k", + "cl_favico": "favicon", + "cl_bigdir": "grandes dirs", + "cl_hsort": "#sort", + "cl_keytype": "notação de tecla", + "cl_hiddenc": "colunas ocultas", + "cl_hidec": "ocultar", + "cl_reset": "resetar", + "cl_hpick": "toque nos cabeçalhos das colunas para ocultá-los na tabela abaixo", + "cl_hcancel": "ocultar coluna abortado", + + "ct_grid": '田 a grade', + "ct_ttips": '◔ ◡ ◔">ℹ️ dicas de ferramentas', + "ct_thumb": 'na visualização de grade, alternar entre ícones ou miniaturas$NHotkey: T">🖼️ miniaturas', + "ct_csel": 'usar CTRL e SHIFT para seleção de arquivo na visualização de grade">sel', + "ct_ihop": 'quando o visualizador de imagens for fechado, rolar para o último arquivo visualizado">g⮯', + "ct_dots": 'mostrar arquivos ocultos (se o servidor permitir)">dotfiles', + "ct_qdel": 'ao excluir arquivos, pedir confirmação apenas uma vez">qdel', + "ct_dir1st": 'ordenar pastas antes de arquivos">📁 primeiro', + "ct_nsort": 'ordem natural (para nomes de arquivos com dígitos iniciais)">nsort', + "ct_utc": 'mostrar todas as datas/horas em UTC">UTC', + "ct_readme": 'mostrar README.md nas listas de pastas">📜 readme', + "ct_idxh": 'mostrar index.html em vez de lista de pastas">htm', + "ct_sbars": 'mostrar barras de rolagem">⟊', + + "cut_umod": "se um arquivo já existe no servidor, atualizar o carimbo de data/hora de última modificação do servidor para corresponder ao seu arquivo local (requer permissões de escrita+exclusão)\">re📅", + + "cut_turbo": "o botão yolo, você provavelmente NÃO quer habilitar isso:$N$Nuse isto se você estava fazendo upload de uma enorme quantidade de arquivos e teve que reiniciar por algum motivo, e quer continuar o upload o mais rápido possível$N$Nisto substitui a verificação de hash por uma simples <em>"este arquivo tem o mesmo tamanho no servidor?"</em> então se o conteúdo do arquivo for diferente ele NÃO será enviado$N$Nvocê deve desativar isso quando o upload estiver concluído, e então "enviar" os mesmos arquivos novamente para permitir que o cliente os verifique\">turbo", + + "cut_datechk": "não tem efeito a menos que o botão turbo esteja ativado$N$Nreduz o fator yolo por uma pequena quantidade; verifica se os carimbos de data/hora dos arquivos no servidor correspondem aos seus$N$ndeve <em>teoricamente</em> pegar a maioria dos uploads incompletos / corrompidos, mas não é um substituto para fazer uma verificação com o turbo desativado depois\">date-chk", + + "cut_u2sz": "tamanho (em MiB) de cada bloco de upload; valores grandes voam melhor pelo atlântico. Tente valores baixos em conexões muito não confiáveis", + + "cut_flag": "garantir que apenas uma aba esteja fazendo upload por vez $N -- outras abas devem ter isso ativado também $N -- só afeta abas no mesmo domínio", + + "cut_az": "enviar arquivos em ordem alfabética, em vez de o menor primeiro$N$na ordem alfabética pode tornar mais fácil de verificar se algo deu errado no servidor, mas torna o upload um pouco mais lento em fibra / LAN", + + "cut_nag": "notificação do SO quando o upload for concluído$N(somente se o navegador ou aba não estiver ativo)", + "cut_sfx": "alerta audível quando o upload for concluído$N(somente se o navegador ou aba não estiver ativo)", + + "cut_mt": "usar multithreading para acelerar o hash de arquivos$N$nisto usa web-workers e requer$Nmais RAM (até 512 MiB extras)$N$ntorna https 30% mais rápido, http 4.5x mais rápido\">mt", + + "cut_wasm": "usar wasm em vez do hasher embutido do navegador; melhora a velocidade em navegadores baseados em chrome mas aumenta a carga da CPU, e muitas versões antigas do chrome têm bugs que fazem o navegador consumir toda a RAM e travar se isso for ativado\">wasm", + + "cft_text": "texto do favicon (deixe em branco e atualize para desativar)", + "cft_fg": "cor do primeiro plano", + "cft_bg": "cor do fundo", + + "cdt_lim": "número máximo de arquivos para mostrar em uma pasta", + "cdt_ask": "ao rolar para o final,$nem vez de carregar mais arquivos,$nperguntar o que fazer", + "cdt_hsort": "quantas regras de ordenação (<code>,sorthref</code>) incluir em URLs de mídia. Definir isso para 0 também ignorará as regras de ordenação incluídas em links de mídia quando você clicar neles", + + "tt_entree": "mostrar painel de navegação (árvore de diretórios)$NHotkey: B", + "tt_detree": "mostrar breadcrumbs$NHotkey: B", + "tt_visdir": "rolar para a pasta selecionada", + "tt_ftree": "alternar entre árvore de pastas / arquivos de texto$NHotkey: V", + "tt_pdock": "mostrar pastas pai em um painel acoplado no topo", + "tt_dynt": "crescer automaticamente à medida que a árvore se expande", + "tt_wrap": "quebra de linha", + "tt_hover": "revelar linhas transbordando ao passar o mouse$N( quebra a rolagem a menos que o cursor do mouse $N  esteja na margem esquerda )", + + "ml_pmode": "ao final da pasta...", + "ml_btns": "comandos", + "ml_tcode": "transcodificar", + "ml_tcode2": "transcodificar para", + "ml_tint": "matiz", + "ml_eq": "equalizador de áudio", + "ml_drc": "compressor de faixa dinâmica", + + "mt_loop": "loop/repetir uma música\">🔁", + "mt_one": "parar depois de uma música\">1️⃣", + "mt_shuf": "embaralhar as músicas em cada pasta\">🔀", + "mt_aplay": "reproduzir automaticamente se houver um ID de música no link que você clicou para acessar o servidor$N$ndesativar isso também impedirá que a URL da página seja atualizada com IDs de música ao tocar música, para evitar a reprodução automática se essas configurações forem perdidas mas a URL permanecer\">a▶", + "mt_preload": "começar a carregar a próxima música perto do final para uma reprodução sem interrupções\">preload", + "mt_prescan": "ir para a próxima pasta antes que a última música$Ntermine, mantendo o navegador feliz$Npara que ele não pare a reprodução\">nav", + "mt_fullpre": "tentar pré-carregar a música inteira;$N✅ ativar em conexões <b>não confiáveis</b>,$N❌ <b>desativar</b> em conexões lentas provavelmente\">full", + "mt_fau": "em telefones, evitar que a música pare se a próxima música não pré-carregar rápido o suficiente (pode fazer as tags aparecerem com falhas)\">☕️", + "mt_waves": "barra de busca de forma de onda:$Nmostrar amplitude de áudio no scrubber\">~s", + "mt_npclip": "mostrar botões para copiar a música que está tocando para a área de transferência\">/np", + "mt_m3u_c": "mostrar botões para copiar as$Nmúsicas selecionadas como entradas de playlist m3u8 para a área de transferência\">📻", + "mt_octl": "integração com o SO (atalhos de mídia / osd)\">os-ctl", + "mt_oseek": "permitir busca através da integração com o SO$N$nnota: em alguns dispositivos (iPhones),$nisto substitui o botão de próxima música\">seek", + "mt_oscv": "mostrar capa do álbum no osd\">art", + "mt_follow": "manter a faixa que está tocando rolando à vista\">🎯", + "mt_compact": "controles compactos\">⟎", + "mt_uncache": "limpar cache  (tente isso se seu navegador armazenou em cache$Numa cópia quebrada de uma música e se recusa a tocar)\">uncache", + "mt_mloop": "loop na pasta aberta\">🔁 loop", + "mt_mnext": "carregar a próxima pasta e continuar\">📂 próximo", + "mt_mstop": "parar reprodução\">⏸ parar", + "mt_cflac": "converter flac / wav para {0}\">flac", + "mt_caac": "converter aac / m4a para {0}\">aac", + "mt_coth": "converter todos os outros (não mp3) para {0}\">oth", + "mt_c2opus": "melhor escolha para desktops, laptops, android\">opus", + "mt_c2owa": "opus-weba, para iOS 17.5 e mais recentes\">owa", + "mt_c2caf": "opus-caf, para iOS 11 a 17\">caf", + "mt_c2mp3": "use isso em dispositivos muito antigos\">mp3", + "mt_c2flac": "melhor qualidade de som, mas downloads enormes\">flac", + "mt_c2wav": "reprodução não comprimida (ainda maior)\">wav", + "mt_c2ok": "legal, boa escolha", + "mt_c2nd": "esse não é o formato de saída recomendado para o seu dispositivo, mas tudo bem", + "mt_c2ng": "seu dispositivo não parece suportar este formato de saída, mas vamos tentar mesmo assim", + "mt_xowa": "existem bugs no iOS que impedem a reprodução em segundo plano usando este formato; por favor, use caf ou mp3 em vez disso", + "mt_tint": "nível de fundo (0-100) na barra de busca$Npara tornar o buffer menos distrativo", + "mt_eq": "ativa o equalizador e o controle de ganho;$N$nimpulsão <code>0</code> = volume padrão de 100% (não modificado)$N$nlargura <code>1  </code> = estéreo padrão (não modificado)$nlargura <code>0.5</code> = 50% de crossfeed esquerda-direita$nlargura <code>0  </code> = mono$N$nimpulsão <code>-0.8</code> & largura <code>10</code> = remoção de vocal :^)$N$natvar o equalizador torna os álbuns sem interrupções totalmente sem interrupções, então deixe-o ligado com todos os valores em zero (exceto largura = 1) se você se importa com isso", + "mt_drc": "ativa o compressor de faixa dinâmica (nivelador de volume / brickwaller); também ativará o EQ para equilibrar o spaghetti, então defina todos os campos EQ exceto 'width' para 0 se você não quiser$N$nabaixa o volume do áudio acima do THRESHOLD dB; para cada RATIO dB após o THRESHOLD há 1 dB de saída, então os valores padrão de tresh -24 e ratio 12 significam que nunca deve ficar mais alto que -22 dB e é seguro aumentar o impulso do equalizador para 0.8, ou até 1.8 com ATK 0 e um enorme RLS como 90 (só funciona no firefox; RLS é no máximo 1 em outros navegadores)$N$n(veja a wikipedia, eles explicam muito melhor)", + + "mb_play": "reproduzir", + "mm_hashplay": "reproduzir este arquivo de áudio?", + "mm_m3u": "pressione <code>Enter/OK</code> para Reproduzir\npressione <code>ESC/Cancelar</code> para Editar", + "mp_breq": "precisa do firefox 82+ ou chrome 73+ ou iOS 15+", + "mm_bload": "carregando...", + "mm_bconv": "convertendo para {0}, por favor, espere...", + "mm_opusen": "seu navegador não pode reproduzir arquivos aac / m4a;\na transcodificação para opus agora está ativada", + "mm_playerr": "reprodução falhou: ", + "mm_eabrt": "A tentativa de reprodução foi cancelada", + "mm_enet": "Sua conexão de internet está instável", + "mm_edec": "Este arquivo está supostamente corrompido??", + "mm_esupp": "Seu navegador não entende este formato de áudio", + "mm_eunk": "Erro Desconhecido", + "mm_e404": "Não foi possível reproduzir áudio; erro 404: Arquivo não encontrado.", + "mm_e403": "Não foi possível reproduzir áudio; erro 403: Acesso negado.\n\nTente pressionar F5 para recarregar, talvez você tenha saído da conta", + "mm_e500": "Não foi possível reproduzir áudio; erro 500: Verifique os logs do servidor.", + "mm_e5xx": "Não foi possível reproduzir áudio; erro do servidor ", + "mm_nof": "não encontrando mais arquivos de áudio por perto", + "mm_prescan": "Procurando música para tocar a seguir...", + "mm_scank": "Encontrei a próxima música:", + "mm_uncache": "cache limpo; todas as músicas serão baixadas novamente na próxima reprodução", + "mm_hnf": "essa música não existe mais", + + "im_hnf": "essa imagem não existe mais", + + "f_empty": 'esta pasta está vazia', + "f_chide": 'isso irá ocultar a coluna «{0}»\n\nvocê pode reexibir as colunas na aba de configurações', + "f_bigtxt": "este arquivo tem {0} MiB de tamanho -- realmente ver como texto?", + "f_bigtxt2": "ver apenas o final do arquivo em vez disso? isso também ativará o acompanhamento/tailing, mostrando linhas de texto recém-adicionadas em tempo real", + "fbd_more": '<div id="blazy">mostrando <code>{0}</code> de <code>{1}</code> arquivos; <a href="#" id="bd_more">mostrar {2}</a> ou <a href="#" id="bd_all">mostrar todos</a></div>', + "fbd_all": '<div id="blazy">mostrando <code>{0}</code> de <code>{1}</code> arquivos; <a href="#" id="bd_all">mostrar todos</a></div>', + "f_anota": "apenas {0} dos {1} itens foram selecionados;\npara selecionar a pasta inteira, primeiro role para o final", + + "f_dls": 'os links de arquivo na pasta atual foram\nalterados para links de download', + + "f_partial": "Para baixar com segurança um arquivo que está sendo enviado, por favor, clique no arquivo que tem o mesmo nome, mas sem a extensão <code>.PARTIAL</code>. Por favor, pressione CANCELAR ou Escape para fazer isso.\n\nPressionar OK / Enter irá ignorar este aviso e continuar baixando o arquivo temporário <code>.PARTIAL</code>, o que quase certamente lhe dará dados corrompidos.", + + "ft_paste": "colar {0} itens$NHotkey: ctrl-V", + "fr_eperm": 'não é possível renomear:\nvocê não tem permissão de “mover” nesta pasta', + "fd_eperm": 'não é possível excluir:\nvocê não tem permissão de “excluir” nesta pasta', + "fc_eperm": 'não é possível recortar:\nvocê não tem permissão de “mover” nesta pasta', + "fp_eperm": 'não é possível colar:\nvocê não tem permissão de “escrever” nesta pasta', + "fr_emore": "selecione pelo menos um item para renomear", + "fd_emore": "selecione pelo menos um item para excluir", + "fc_emore": "selecione pelo menos um item para recortar", + "fcp_emore": "selecione pelo menos um item para copiar para a área de transferência", + + "fs_sc": "compartilhar a pasta em que você está", + "fs_ss": "compartilhar os arquivos selecionados", + "fs_just1d": "você não pode selecionar mais de uma pasta,\nou misturar arquivos e pastas em uma seleção", + "fs_abrt": "❌ abortar", + "fs_rand": "🎲 nome aleatório", + "fs_go": "✅ criar compartilhamento", + "fs_name": "nome", + "fs_src": "fonte", + "fs_pwd": "senha", + "fs_exp": "expira", + "fs_tmin": "min", + "fs_thrs": "horas", + "fs_tdays": "dias", + "fs_never": "eterno", + "fs_pname": "nome do link opcional; será aleatório se em branco", + "fs_tsrc": "o arquivo ou pasta a ser compartilhado", + "fs_ppwd": "senha opcional", + "fs_w8": "criando compartilhamento...", + "fs_ok": "pressione <code>Enter/OK</code> para Copiar para a Área de Transferência\npressione <code>ESC/Cancelar</code> para Fechar", + + "frt_dec": "pode consertar alguns casos de nomes de arquivos quebrados\">url-decode", + "frt_rst": "resetar nomes de arquivos modificados de volta para os originais\">↺ resetar", + "frt_abrt": "abortar e fechar esta janela\">❌ cancelar", + "frb_apply": "APLICAR RENOMEAÇÃO", + "fr_adv": "renomeação em lote / metadados / padrão\">avançado", + "fr_case": "regex sensível a maiúsculas e minúsculas\">case", + "fr_win": "nomes seguros para windows; substituir <code><>:"\\|?*</code> por caracteres japoneses de largura total\">win", + "fr_slash": "substituir <code>/</code> por um caractere que não cause a criação de novas pastas\">no /", + "fr_re": "padrão de busca regex para aplicar aos nomes de arquivos originais; grupos de captura podem ser referenciados no campo de formato abaixo como <code>(1)</code> e <code>(2)</code> e assim por diante", + "fr_fmt": "inspirado por foobar2000:$N<code>(título)</code> é substituído pelo título da música,$N<code>[(artista) - ](título)</code> pula esta parte se o artista estiver em branco$N<code>$lpad((tn),2,0)</code> preenche o número da faixa com 2 dígitos", + "fr_pdel": "excluir", + "fr_pnew": "salvar como", + "fr_pname": "forneça um nome para seu novo preset", + "fr_aborted": "abortado", + "fr_lold": "nome antigo", + "fr_lnew": "novo nome", + "fr_tags": "tags para os arquivos selecionados (somente leitura, apenas para referência):", + "fr_busy": "renomeando {0} itens...\n\n{1}", + "fr_efail": "renomeação falhou:\n", + "fr_nchg": "{0} dos novos nomes foram alterados devido a <code>win</code> e/ou <code>no /</code>\n\nOK para continuar com estes novos nomes alterados?", + + "fd_ok": "exclusão OK", + "fd_err": "exclusão falhou:\n", + "fd_none": "nada foi excluído; talvez bloqueado pela configuração do servidor (xbd)?", + "fd_busy": "excluindo {0} itens...\n\n{1}", + "fd_warn1": "EXCLUIR estes {0} itens?", + "fd_warn2": "<b>Última chance!</b> Não há como desfazer. Excluir?", + + "fc_ok": "recortar {0} itens", + "fc_warn": 'recortar {0} itens\n\nmas: apenas <b>esta</b> aba do navegador pode colá-los\n(já que a seleção é tão absolutamente massiva)', + + "fcc_ok": "copiado {0} itens para a área de transferência", + "fcc_warn": 'copiado {0} itens para a área de transferência\n\nmas: apenas <b>esta</b> aba do navegador pode colá-los\n(já que a seleção é tão absolutamente massiva)', + + "fp_apply": "usar estes nomes", + "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 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 para pular:", + "fp_emore": "ainda há algumas colisões de nome de arquivo para consertar", + "fp_ok": "movimento OK", + "fcp_ok": "cópia OK", + "fp_busy": "movendo {0} itens...\n\n{1}", + "fcp_busy": "copiando {0} itens...\n\n{1}", + "fp_abrt": "abortando...", + "fp_err": "movimento falhou:\n", + "fcp_err": "cópia falhou:\n", + "fp_confirm": "mover estes {0} itens para cá?", + "fcp_confirm": "copiar estes {0} itens para cá?", + "fp_etab": 'falha ao ler a área de transferência de outra aba do navegador', + "fp_name": "enviando um arquivo do seu dispositivo. Dê-lhe um nome:", + "fp_both_m": '<h6>escolha o que colar</h6><code>Enter</code> = Mover {0} arquivos de «{1}»\n<code>ESC</code> = Enviar {2} arquivos do seu dispositivo', + "fcp_both_m": '<h6>escolha o que colar</h6><code>Enter</code> = Copiar {0} arquivos de «{1}»\n<code>ESC</code> = Enviar {2} arquivos do seu dispositivo', + "fp_both_b": '<a href="#" id="modal-ok">Mover</a><a href="#" id="modal-ng">Enviar</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Copiar</a><a href="#" id="modal-ng">Enviar</a>', + + "mk_noname": "digite um nome no campo de texto à esquerda antes de fazer isso :p", + + "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 ", + "tv_xe2": "404, arquivo não encontrado", + "tv_lst": "lista de arquivos de texto em", + "tvt_close": "voltar para a visualização da pasta$NHotkey: M (ou Esc)\">❌ fechar", + "tvt_dl": "baixar este arquivo$NHotkey: Y\">💾 baixar", + "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_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\">↵", + "tvt_atail": "fixar rolagem no final da página\">⚓", + "tvt_ctail": "decodificar cores do terminal (códigos de escape ansi)\">🌈", + "tvt_ntail": "limite de rolagem para trás (quantos bytes de texto manter carregados)", + + "m3u_add1": "música adicionada à playlist m3u", + "m3u_addn": "{0} músicas adicionadas à playlist m3u", + "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_crop": "cortar miniaturas ao centro\">cortar", + "gt_3x": "miniaturas de alta resolução\">3x", + "gt_zoom": "zoom", + "gt_chop": "picar", + "gt_sort": "ordenar por", + "gt_name": "nome", + "gt_sz": "tamanho", + "gt_ts": "data", + "gt_ext": "tipo", + "gt_c1": "truncar nomes de arquivos mais (mostrar menos)", + "gt_c2": "truncar nomes de arquivos menos (mostrar mais)", + + "sm_w8": "buscando...", + "sm_prev": "os resultados da busca abaixo são de uma consulta anterior:\n ", + "sl_close": "fechar resultados da busca", + "sl_hits": "mostrando {0} resultados", + "sl_moar": "carregar mais", + + "s_sz": "tamanho", + "s_dt": "data", + "s_rd": "caminho", + "s_fn": "nome", + "s_ta": "tags", + "s_ua": "up@", + "s_ad": "adv.", + "s_s1": "MiB mínimo", + "s_s2": "MiB máximo", + "s_d1": "iso8601 min.", + "s_d2": "iso8601 max.", + "s_u1": "enviado depois de", + "s_u2": "e/ou antes de", + "s_r1": "caminho contém   (separado por espaço)", + "s_f1": "nome contém   (negar com -nope)", + "s_t1": "tags contém   (^=início, fim=$)", + "s_a1": "propriedades de metadados específicas", + + "md_eshow": "não é possível renderizar ", + "md_off": "[📜<em>readme</em>] desativado em [⚙️] -- documento oculto", + + "badreply": "Falha ao analisar a resposta do servidor", + + "xhr403": "403: Acesso negado\n\ntente pressionar F5, talvez você tenha saído da conta", + "xhr0": "desconhecido (provavelmente perdeu a conexão com o servidor, ou o servidor está offline)", + "cf_ok": "desculpe por isso -- a proteção DD" + "oS foi ativada\n\nas coisas devem ser retomadas em cerca de 30 segundos\n\nse nada acontecer, pressione F5 para recarregar a página", + "tl_xe1": "não foi possível listar as subpastas:\n\nerro ", + "tl_xe2": "404: Pasta não encontrada", + "fl_xe1": "não foi possível listar os arquivos na pasta:\n\nerro ", + "fl_xe2": "404: Pasta não encontrada", + "fd_xe1": "não foi possível criar a subpasta:\n\nerro ", + "fd_xe2": "404: Pasta pai não encontrada", + "fsm_xe1": "não foi possível enviar a mensagem:\n\nerro ", + "fsm_xe2": "404: Pasta pai não encontrada", + "fu_xe1": "falha ao carregar a lista de despublicação do servidor:\n\nerro ", + "fu_xe2": "404: Arquivo não encontrado??", + + "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_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)", + + "un_m1": "você pode excluir seus uploads recentes (ou abortar os que não foram concluídos) abaixo", + "un_upd": "atualizar", + "un_m4": "ou compartilhar os arquivos visíveis abaixo:", + "un_ulist": "mostrar", + "un_ucopy": "copiar", + "un_flt": "filtro opcional:  a URL deve conter", + "un_fclr": "limpar filtro", + "un_derr": 'a exclusão da despublicação falhou:\n', + "un_f5": 'algo quebrou, por favor, tente uma atualização ou pressione F5', + "un_uf5": "desculpe, mas você tem que atualizar a página (por exemplo, pressionando F5 ou CTRL-R) antes que este upload possa ser abortado", + "un_nou": "<b>aviso:</b> o servidor está muito ocupado para mostrar uploads incompletos; clique no link \"atualizar\" em um momento", + "un_noc": "<b>aviso:</b> a despublicação de arquivos totalmente enviados não está ativada/permitida na configuração do servidor", + "un_max": "mostrando os primeiros 2000 arquivos (use o filtro)", + "un_avail": "{0} uploads recentes podem ser excluídos<br />{1} incompletos podem ser abortados", + "un_m2": "ordenado por tempo de upload; o mais recente primeiro:", + "un_no1": "sike! nenhum upload é suficientemente recente", + "un_no2": "sike! nenhum upload que corresponda a esse filtro é suficientemente recente", + "un_next": "excluir os próximos {0} arquivos abaixo", + "un_abrt": "abortar", + "un_del": "excluir", + "un_m3": "carregando seus uploads recentes...", + "un_busy": "excluindo {0} arquivos...", + "un_clip": "{0} links copiados para a área de transferência", + + "u_https1": "você deveria", + "u_https2": "mudar para https", + "u_https3": "para um melhor desempenho", + "u_ancient": 'seu navegador é impressionantemente antigo -- talvez você devesse <a href="#" onclick="goto(\'bup\')">usar o bup em vez disso</a>', + "u_nowork": "precisa do firefox 53+ ou chrome 57+ ou iOS 11+", + "tail_2old": "precisa do firefox 105+ ou chrome 71+ ou iOS 14.5+", + "u_nodrop": 'seu navegador é muito antigo para upload de arrastar e soltar', + "u_notdir": "isso não é uma pasta!\n\nseu navegador é muito antigo,\npor favor, tente arrastar e soltar em vez disso", + "u_uri": "para arrastar e soltar imagens de outras janelas do navegador,\npor favor, solte-as no grande botão de upload", + "u_enpot": 'mudar para <a href="#">UI batata</a> (pode melhorar a velocidade de upload)', + "u_depot": 'mudar para <a href="#">UI chique</a> (pode reduzir a velocidade de upload)', + "u_gotpot": 'mudando para a UI batata para uma velocidade de upload melhorada,\n\nsinta-se à vontade para discordar e voltar!', + "u_pott": "<p>arquivos:   <b>{0}</b> concluídos,   <b>{1}</b> falhados,   <b>{2}</b> ocupados,   <b>{3}</b> na fila</p>", + "u_ever": "este é o uploader básico; up2k precisa de pelo menos<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'este é o uploader básico; <a href="#" id="u2yea">up2k</a> é melhor', + "u_uput": 'otimizar para velocidade (pular checksum)', + "u_ewrite": 'você não tem acesso de escrita a esta pasta', + "u_eread": 'você não tem acesso de leitura a esta pasta', + "u_enoi": 'a busca de arquivos não está ativada na configuração do servidor', + "u_enoow": "substituir não funcionará aqui; precisa de permissão de Excluir", + "u_badf": 'Estes {0} arquivos (de um total de {1}) foram ignorados, possivelmente devido a permissões do sistema de arquivos:\n\n', + "u_blankf": 'Estes {0} arquivos (de um total de {1}) estão em branco / vazios; enviá-los de qualquer maneira?\n\n', + "u_applef": 'Estes {0} arquivos (de um total de {1}) são provavelmente indesejáveis;\nPressione <code>OK/Enter</code> para PULAR os seguintes arquivos,\nPressione <code>Cancelar/ESC</code> para NÃO excluir, e ENVIAR esses também:\n\n', + "u_just1": '\nTalvez funcione melhor se você selecionar apenas um arquivo', + "u_ff_many": "se você estiver usando <b>Linux / MacOS / Android,</b> então essa quantidade de arquivos <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>pode</em> travar o Firefox!</a>\nse isso acontecer, por favor, tente novamente (ou use o Chrome).", + "u_up_life": "Este upload será excluído do servidor\n{0} após ser concluído", + "u_asku": 'enviar estes {0} arquivos para <code>{1}</code>', + "u_unpt": "você pode desfazer / excluir este upload usando o 🧯 no canto superior esquerdo", + "u_bigtab": 'prestes a mostrar {0} arquivos\n\nisto pode travar seu navegador, tem certeza?', + "u_scan": 'Escaneando arquivos...', + "u_dirstuck": 'o iterador de diretório travou ao tentar acessar os seguintes {0} itens; irá pular:', + "u_etadone": 'Concluído ({0}, {1} arquivos)', + "u_etaprep": '(preparando para enviar)', + "u_hashdone": 'hash concluído', + "u_hashing": 'hash', + "u_hs": 'handshaking...', + "u_started": "os arquivos estão sendo enviados agora; veja [🚀]", + "u_dupdefer": "duplicado; será processado após todos os outros arquivos", + "u_actx": "clique neste texto para evitar perda de<br />desempenho ao mudar para outras janelas/abas", + "u_fixed": "OK!  Consertado 👍", + "u_cuerr": "falha ao enviar o bloco {0} de {1};\nprovavelmente inofensivo, continuando\n\narquivo: {2}", + "u_cuerr2": "o servidor rejeitou o upload (bloco {0} de {1});\ntentará novamente mais tarde\n\narquivo: {2}\n\nerro ", + "u_ehstmp": "tentará novamente; veja no canto inferior direito", + "u_ehsfin": "o servidor rejeitou a solicitação para finalizar o upload; tentando novamente...", + "u_ehssrch": "o servidor rejeitou a solicitação para realizar a busca; tentando novamente...", + "u_ehsinit": "o servidor rejeitou a solicitação para iniciar o upload; tentando novamente...", + "u_eneths": "erro de rede ao realizar o handshake de upload; tentando novamente...", + "u_enethd": "erro de rede ao testar a existência do alvo; tentando novamente...", + "u_cbusy": "esperando o servidor confiar em nós novamente após uma falha de rede...", + "u_ehsdf": "o servidor ficou sem espaço em disco!\n\ncontinuará tentando novamente, caso alguém\nlibere espaço suficiente para continuar", + "u_emtleak1": "parece que seu navegador pode ter um vazamento de memória;\npor favor,", + "u_emtleak2": ' <a href="{0}">mude para https (recomendado)</a> ou ', + "u_emtleak3": ' ', + "u_emtleakc": 'tente o seguinte:\n<ul><li>pressione <code>F5</code> para atualizar a página</li><li>depois desative o botão  <code>mt</code>  nas  <code>⚙️ configurações</code></li><li>e tente o upload novamente</li></ul>Os uploads serão um pouco mais lentos, mas tudo bem.\nDesculpe pelo problema !\n\nPS: chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">tem uma correção de bug</a> para isso', + "u_emtleakf": 'tente o seguinte:\n<ul><li>pressione <code>F5</code> para atualizar a página</li><li>depois ative <code>🥔</code> (batata) na UI de upload<li>e tente o upload novamente</li></ul>\nPS: o firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">esperançosamente terá uma correção de bug</a> em algum momento', + "u_s404": "não encontrado no servidor", + "u_expl": "explicar", + "u_maxconn": "a maioria dos navegadores limita isso a 6, mas o firefox permite que você aumente com <code>connections-per-server</code> em <code>about:config</code>", + "u_tu": '<p class="warn">AVISO: turbo ativado, <span> o cliente pode não detectar e retomar uploads incompletos; veja a dica de ferramenta do botão turbo</span></p>', + "u_ts": '<p class="warn">AVISO: turbo ativado, <span> os resultados da busca podem estar incorretos; veja a dica de ferramenta do botão turbo</span></p>', + "u_turbo_c": "o turbo está desativado na configuração do servidor", + "u_turbo_g": "desativando o turbo porque você não tem\n privilégios de listagem de diretório neste volume", + "u_life_cfg": 'excluir automaticamente depois de <input id="lifem" p="60" /> min (ou <input id="lifeh" p="3600" /> horas)', + "u_life_est": 'o upload será excluído <span id="lifew" tt="local time">---</span>', + "u_life_max": 'esta pasta impõe um\n tempo de vida máximo de {0}', + "u_unp_ok": 'a despublicação é permitida para {0}', + "u_unp_ng": 'a despublicação NÃO será permitida', + "ue_ro": 'seu acesso a esta pasta é Somente Leitura\n\n', + "ue_nl": 'você não está logado no momento', + "ue_la": 'você está logado no momento como "{0}"', + "ue_sr": 'você está no modo de busca de arquivos no momento\n\nmude para o modo de upload clicando na lupa 🔎 (ao lado do grande botão BUSCAR), e tente enviar novamente\n\ndesculpe', + "ue_ta": 'tente enviar novamente, deve funcionar agora', + "ue_ab": "este arquivo já está sendo enviado para outra pasta, e esse upload deve ser concluído antes que o arquivo possa ser enviado para outro lugar.\n\nVocê pode abortar e esquecer o upload inicial usando o 🧯 no canto superior esquerdo", + "ur_1uo": "OK: Arquivo enviado com sucesso", + "ur_auo": "OK: Todos os {0} arquivos enviados com sucesso", + "ur_1so": "OK: Arquivo encontrado no servidor", + "ur_aso": "OK: Todos os {0} arquivos encontrados no servidor", + "ur_1un": "O upload falhou, desculpe", + "ur_aun": "Todos os {0} uploads falharam, desculpe", + "ur_1sn": "O arquivo NÃO foi encontrado no servidor", + "ur_asn": "Os {0} arquivos NÃO foram encontrados no servidor", + "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", + + "lang_set": "atualizar para a mudança ter efeito?" }, "rus": { "tt": "Русский", @@ -3249,6 +9588,7 @@ var Ls = { "gou": 'родительская папка">вверх', "gon": 'следующая папка">след', "logout": "Выйти ", + "login": "Войти", //m "access": " доступ", "ot_close": "закрыть подменю", "ot_search": "искать файлы по атрибутам, пути / имени, музыкальным тегам или любой другой комбинации из следующих конструкций$N$N<code>foo bar</code> = обязано содержать «foo» И «bar»,$N<code>foo -bar</code> = обязано содержать «foo», но не «bar»,$N<code>^yana .opus$</code> = начинается с «yana» и имеет расширение «opus»$N<code>"try unite"</code> = содержит именно «try unite»$N$Nформат времени задаётся по стандарту iso-8601, например$N<code>2009-12-31</code> или <code>2020-09-12 23:30:00</code>", @@ -3435,13 +9775,15 @@ var Ls = { "mt_mloop": "повторять треки в папке\">🔁 цикл", "mt_mnext": "загрузить следующую папку и продолжить в ней\">📂 след.", "mt_mstop": "приостановить воспроизведение\">⏸ стоп", - "mt_cflac": "конвертировать flac / wav в opus\">flac", - "mt_caac": "конвертировать aac / m4a в opus\">aac", - "mt_coth": "конвертировать всё остальное (кроме mp3) в opus\">др.", + "mt_cflac": "конвертировать flac / wav в {0}\">flac", + "mt_caac": "конвертировать aac / m4a в {0}\">aac", + "mt_coth": "конвертировать всё остальное (кроме mp3) в {0}\">др.", "mt_c2opus": "лучший вариант для компьютеров и устройств на Android\">opus", "mt_c2owa": "opus-weba, для iOS 17.5 и выше\">owa", "mt_c2caf": "opus-caf, для iOS 11-17\">caf", "mt_c2mp3": "для очень старых устройств\">mp3", + "mt_c2flac": "лучшее качество звука, но большие файлы\">flac", //m + "mt_c2wav": "не сжатое воспроизведение (ещё больше)\">wav", //m "mt_c2ok": "хороший выбор", "mt_c2nd": "это не рекомендованный вариант формата для вашего устройства, но сойдёт", "mt_c2ng": "не похоже, что ваше устройство поддерживает этот формат, но давайте попробуем и узнаем наверняка", @@ -3515,7 +9857,7 @@ var Ls = { "fs_tsrc": "путь к файлу или папке, которыми нужно поделиться", "fs_ppwd": "пароль (необязательно)", "fs_w8": "создаю доступ...", - "fs_ok": "нажмите <code>Enter/OK</code>, чтобы скопировать\nнажмите <code>ESC/Cancel</code>, чтобы закрыть", + "fs_ok": "нажмите <code>Enter/OK</code>, чтобы скопировать\nнажмите <code>ESC/Отмена</code>, чтобы закрыть", "frt_dec": "может исправить некоторые случаи с некорректными именами файлов\">декодировать url", "frt_rst": "сбросить изменённые имена обратно к оригинальным\">↺ сброс", @@ -3560,6 +9902,7 @@ var Ls = { "fcp_ok": "успешно скопировано", "fp_busy": "перемещаю {0} файлов...\n\n{1}", "fcp_busy": "копирую {0} файлов...\n\n{1}", + "fp_abrt": "прерывание...", //m "fp_err": "ошибка перемещения:\n", "fcp_err": "ошибка копирования:\n", "fp_confirm": "переместить эти {0} файлов сюда?", @@ -3876,6 +10219,7 @@ var Ls = { "gou": 'carpeta de nivel superior">subir', "gon": 'siguiente carpeta">siguiente', "logout": "Cerrar sesión ", + "login": "Iniciar sesión", //m "access": " acceso", "ot_close": "cerrar submenú", "ot_search": "buscar archivos por atributos, ruta / nombre, etiquetas de música, o cualquier combinación$N$N<code>foo bar</code> = debe contener «foo» y «bar»,$N<code>foo -bar</code> = debe contener «foo» pero no «bar»,$N<code>^yana .opus$</code> = empieza con «yana» y es un archivo «opus»$N<code>"try unite"</code> = contiene exactamente «try unite»$N$Nel formato de fecha es iso-8601, como$N<code>2009-12-31</code> o <code>2020-09-12 23:30:00</code>", @@ -4061,13 +10405,15 @@ var Ls = { "mt_mloop": 'repetir la carpeta actual">🔁 bucle', "mt_mnext": 'cargar la siguiente carpeta y continuar">📂 sig', "mt_mstop": 'detener reproducción">⏸ parar', - "mt_cflac": 'convertir flac / wav a opus">flac', - "mt_caac": 'convertir aac / m4a a opus">aac', - "mt_coth": 'convertir todos los demás (no mp3) a opus">oth', + "mt_cflac": 'convertir flac / wav a {0}">flac', + "mt_caac": 'convertir aac / m4a a {0}">aac', + "mt_coth": 'convertir todos los demás (no mp3) a {0}">oth', "mt_c2opus": 'la mejor opción para ordenadores, portátiles, android">opus', "mt_c2owa": 'opus-weba, para iOS 17.5 y superior">owa', "mt_c2caf": 'opus-caf, para iOS 11 a 17">caf', "mt_c2mp3": 'usar en dispositivos muy antiguos">mp3', + "mt_c2flac": "la mejor calidad de sonido,$Npero descargas muy grandes\">flac", //m + "mt_c2wav": "reproducción sin comprimir (aún más grande)\">wav", //m "mt_c2ok": "bien, buena elección", "mt_c2nd": "ese no es el formato de salida recomendado para tu dispositivo, pero está bien", "mt_c2ng": "tu dispositivo no parece soportar este formato de salida, pero intentémoslo de todas formas", @@ -4186,6 +10532,7 @@ var Ls = { "fcp_ok": "copia correcta", "fp_busy": "moviendo {0} elementos...\n\n{1}", "fcp_busy": "copiando {0} elementos...\n\n{1}", + "fp_abrt": "cancelando...", //m "fp_err": "fallo al mover:\n", "fcp_err": "fallo al copiar:\n", "fp_confirm": "¿mover estos {0} elementos aquí?", @@ -4394,6 +10741,1268 @@ var Ls = { "lang_set": "¿refrescar para que el cambio surta efecto?" }, + "swe": { + "tt": "Svenska", + + "cols": { + "c": "aktion", + "dur": "längd", + "q": "kvalitet / bitrate", + "Ac": "ljudkodek", + "Vc": "videokodek", + "Fmt": "format / container", + "Ahash": "ljudchecksumma", + "Vhash": "videochecksumma", + "Res": "upplösning", + "T": "filtyp", + "aq": "ljudkvalitet / bitrate", + "vq": "videokvalitet / bitrate", + "pixfmt": "subsampling / pixelstruktur", + "resw": "horisontell upplösning", + "resh": "vertikal upplösning", + "chs": "antal ljudkanaler", + "hz": "samplingsfrekvens" + }, + + "hks": [ + [ + "övrigt", + ["ESC", "stäng diverse paneler"], + + "filhanterare", + ["G", "växla mellan listvy / rutnät"], + ["T", "växla mellan miniatyrer / ikoner"], + ["⇧ A/D", "miniatyrstorlek"], + ["ctrl-K", "radera urval"], + ["ctrl-X", "klipp urval till urklipp"], + ["ctrl-C", "kopiera urval till urklipp"], + ["ctrl-V", "klistra in (kopiera/flytta) hit"], + ["Y", "ladda ner urval"], + ["F2", "byt namn på urval"], + + "välja filer", + ["Blanksteg", "växla val av fil"], + ["↑/↓", "flytta filvalsmarkör"], + ["ctrl ↑/↓", "flytta markör och vy"], + ["⇧ ↑/↓", "välj föregående/nästa fil"], + ["ctrl-A", "välj alla filer / mappar"], + ], [ + "navigation", + ["B", "växla mellan brödsmulor / trädvy"], + ["I/K", "föregående/nästa mapp"], + ["M", "hoppa till överordnad mapp (eller kollapsa närvarande)"], + ["V", "växla mellan mappar / textfiler i trädvy"], + ["A/D", "trädvystorlek"], + ], [ + "ljudspelare", + ["J/L", "föregående/nästa låt"], + ["U/O", "hoppa 10sek bakåt/framåt"], + ["0..9", "hoppa till 0%..90%"], + ["P", "play/paus (startar även uppspelning)"], + ["S", "välj låten som spelas"], + ["Y", "ladda ner låt"], + ], [ + "bildvisare", + ["J/L, ←/→", "föregående/nästa bild"], + ["Hem/End", "första/sista bilden"], + ["F", "helskärm"], + ["R", "rotera medsols"], + ["⇧ R", "rotera motsols"], + ["S", "välj bild"], + ["Y", "ladda ner bild"], + ], [ + "videospelare", + ["U/O", "hoppa 10sek bakåt/framåt"], + ["P/K/Blanksteg", "play/paus"], + ["C", "fortsätt med nästa"], + ["V", "loopa"], + ["M", "stäng av ljud"], + ["[ och ]", "ställ in loopintervall"], + ], [ + "textfilsvisare", + ["I/K", "föregående/nästa fil"], + ["M", "stäng textfil"], + ["E", "redigera textfil"], + ["S", "välj fil"], + ] + ], + + "m_ok": "OK", + "m_ng": "Avbryt", + + "enable": "Aktivera", + "danger": "VARNING", + "clipped": "kopierat till urklipp", + + "ht_s1": "sekund", + "ht_s2": "sekunder", + "ht_m1": "minut", + "ht_m2": "minuter", + "ht_h1": "timme", + "ht_h2": "timmar", + "ht_d1": "dag", + "ht_d2": "dagar", + "ht_and": " och ", + + "goh": "kontrollpanel", + "gop": 'föregående mapp">föreg.', + "gou": 'överordnad mapp">upp', + "gon": 'nästa mapp">nästa', + "logout": "Logga ut ", + "login": "Logga in", //m + "access": "-rättighet", + "ot_close": "stäng undermeny", + "ot_search": "sök efter filer via attribut, sökväg / namn, musiktaggar, eller någon kombination av dessa$N$N<code>foo bar</code> = måste innehålla både «foo» och «bar»,$N<code>foo -bar</code> = måste innehålla «foo» men inte «bar»,$N<code>^yana .opus$</code> = måste börja med «yana» och vara en «opus»-fil$N<code>"try unite"</code> = måste innehålla exakt «try unite»$N$Ndatumformatet är iso-8601, t.ex.$N<code>2009-12-31</code> eller <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: radera dina senaste uppladdningar, eller avbryt pågående sådana", + "ot_bup": "bup: enkel uppladdare, stödjer t o m netscape 4.0", + "ot_mkdir": "mkdir: skapa en ny mapp", + "ot_md": "new-md: skapa ett nytt markdown-dokument", + "ot_msg": "msg: skicka ett meddelande till serverloggen", + "ot_mp": "mediaspelarinställningar", + "ot_cfg": "konfigurationsinställningar", + "ot_u2i": 'up2k: ladda upp filer (om du har skrivrättigheter) eller byt till sökläge för att se om de finns någonstans på servern$N$Nuppladdningarna är återupptagbara, multitrådade och filernas tidsstämpel bevaras, men den använder mer CPU än [🎈]  (den enkla uppladdaren)<br /><br />under uppladdningens gång blir denna ikon en förloppsindikator!', + "ot_u2w": 'up2k: ladda upp filer med stöd för återupptagning (stäng din webbläsare och dra in samma filer senare)$N$Nmultitrådad och filernas tidsstämpel bevaras, men den använder mer CPU än [🎈]  (den enkla uppladdaren)<br /><br />under uppladdningens gång blir denna ikon en förloppsindikator!', + "ot_noie": 'Var vänlig använd Chrome / Firefox / Edge', + + "ab_mkdir": "skapa mapp", + "ab_mkdoc": "nytt markdown-dokument", + "ab_msg": "skicka medd. till serverlogg", + + "ay_path": "hoppa till mappar", + "ay_files": "hoppa till filer", + + "wt_ren": "byt namn på urval$NSnabbtangent: F2", + "wt_del": "radera urval$NSnabbtangent: ctrl-K", + "wt_cut": "klipp urval<small>(för att klistra in någonstans)</small>$NSnabbtangent: ctrl-X", + "wt_cpy": "kopiera urval till urklipp$N(för att klistra in någonstans)$NSnabbtangent: ctrl-C", + "wt_pst": "klistra in tidigare urval$NSnabbtangent: ctrl-V", + "wt_selall": "välj alla filer$NSnabbtangent: ctrl-A (när en fil har fokus)", + "wt_selinv": "invertera urval", + "wt_zip1": "ladda ner denna mapp som ett arkiv", + "wt_selzip": "ladda ner urval som ett arkiv", + "wt_seldl": "ladda ner urval som separata filer$NSnabbtangent: Y", + "wt_npirc": "kopiera IRC-formatterad låtinfo", + "wt_nptxt": "kopiera låtinfo i klartext", + "wt_m3ua": "lägg till i m3u-spellista (klicka på <code>📻copy</code> senare)", + "wt_m3uc": "kopiera m3u-spellista till urklipp", + "wt_grid": "växla mellan rutnät och listvy$NSnabbtangent: G", + "wt_prev": "föregående låt$NSnabbtangent: J", + "wt_play": "play / paus$NSnabbtangent: P", + "wt_next": "nästa låt$NSnabbtangent: L", + + "ul_par": "samtidiga uppladdningar:", + "ut_rand": "slumpa filnamn", + "ut_u2ts": "bevara tidsstämpeln för senaste ändring$Nfrån ditt filsystem till servern\">📅", + "ut_ow": "skriv över existerande filer på servern?$N🛡️: aldrig (skapar ett nytt filnamn istället)$N🕒: skriv över om serverns fil är äldre än din$N♻️: skriv alltid över om filerna skiljer sig", + "ut_mt": "fortsätt hasha filer under uppladdningens gång$N$Nstäng av om din CPU eller disk är en flaskhals", + "ut_ask": 'bekräfta innan uppladdningar påbörjas">💭', + "ut_pot": "förbättra uppladdningshastigheten på långsamma enheter$Ngenom att förenkla användargränssnittet", + "ut_srch": "ladda inte upp; kolla istället om filerna redan existerar på $N servern (detta kommer att skanna alla mappar med läsrättighet)", + "ut_par": "du kan pausa all uppladdning genom att sätta detta till 0$N$Nöka denna om din uppkoppling är långsam eller har hög latens$N$Nsätt till 1 över lokala nätverk eller om serverns disk är en flaskhals", + "ul_btn": "släpp filer / mappar<br>här (eller klicka)", + "ul_btnu": "L A D D A U P P", + "ul_btns": "S Ö K", + + "ul_hash": "hashar", + "ul_send": "skickar", + "ul_done": "klar", + "ul_idle1": "inga uppladdningar har köats", + "ut_etah": "medelhastighet för <em>hashning</em>, och uppskattad återstående tid", + "ut_etau": "medelhastighet för <em>överföring</em>, och uppskattad återstående tid", + "ut_etat": "<em>total</em> medelhastighet, och uppskattad återstående tid", + + "uct_ok": "lyckade", + "uct_ng": "no-good: misslyckade / avvisade / ej funna", + "uct_done": "ok och ng kombinerat", + "uct_bz": "pågående", + "uct_q": "köade", + + "utl_name": "filnamn", + "utl_ulist": "visa", + "utl_ucopy": "kopiera", + "utl_links": "länkar", + "utl_stat": "status", + "utl_prog": "förlopp", + + // keep short: + "utl_404": "404", + "utl_err": "FEL", + "utl_oserr": "OS-fel", + "utl_found": "hittad", + "utl_defer": "väntar", + "utl_yolo": "YOLO", + "utl_done": "klar", + + "ul_flagblk": "filerna lades till i kön,</b><br>men det finns en upptagen up2k i en annan webbläsarflik,<br>så vi väntar på den först", + "ul_btnlk": "serverkonfigurationen har låst denna inställning", + + "udt_up": "Ladda upp", + "udt_srch": "Sök", + "udt_drop": "släpp här", + + "u_nav_m": '<h6>jaha, vad har du då?</h6><code>Enter</code> = Filer (en eller flera)\n<code>ESC</code> = En mapp (inklusive undermappar)', + "u_nav_b": '<a href="#" id="modal-ok">Filer</a><a href="#" id="modal-ng">En mapp</a>', + + "cl_opts": "växlar", + "cl_themes": "tema", + "cl_langs": "språk", + "cl_ziptype": "mappnedladdning", + "cl_uopts": "up2k-inställningar", + "cl_favico": "favikon", + "cl_bigdir": "stora mappar", + "cl_hsort": "#sort.", + "cl_keytype": "tonartsnotering", + "cl_hiddenc": "dolda kolumner", + "cl_hidec": "dölj", + "cl_reset": "återställ", + "cl_hpick": "tryck på en kolumntitel för att dölja den i filvyn", + "cl_hcancel": "kolumndöljning avbruten", + + "ct_grid": '田 rutnätet', + "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_ihop": 'skrolla till den senast visade filen när bildvisaren stängs">g⮯', + "ct_dots": 'visa dolda filer (om servern tillåter detta)">dolda', + "ct_qdel": 'bekräfta endast en gång när filer raderas">srad', + "ct_dir1st": 'sortera mappar före filer">📁 först', + "ct_nsort": 'naturlig sortering (för filnamn med ledande siffror)">nsort', + "ct_utc": 'visa alla datum och tider i UTC">UTC', + "ct_readme": 'visa README.md i listvyn">📜 läsmig', + "ct_idxh": 'visa index.html istället för listvyn">htm', + "ct_sbars": 'visa rullningslister">⟊', + + "cut_umod": "om en fil redan existerar på servern, uppdatera serverns senast modifierade tidsstämpel till att matcha din lokala fil (kräver skriv+radera-rättighet)\">re📅", + + "cut_turbo": "yolo-knappen, du vill förmodligen INTE aktivera denna:$N$Nanvänd denna om du höll på att ladda upp en stor mängd filer och var tvungen att stänga webbläsaren, och du vill fortsätta uppladdningen så fort som möjligt$N$Ndetta ersätter hash-checken med en enkel <em>"har denna fil samma filstorlek på servern?"</em>, så om filinnehållet skiljer sig kommer den INTE att laddas upp$N$Ndu bör stänga av denna när uppladdningen är klar och sedan "ladda upp" samma filer igen för att låta klienten verifiera dem\">turbo", + + "cut_datechk": "har endast effekt med turbo-växeln påslagen$N$Nminskar yolo-faktorn lite grann; kollar om filtidsstämplarna på servern matchar dina$N$Ndetta <em>bör</em> fånga de flesta ofärdiga / korrumperade uppladdningarna, men kan inte ersätta ett fullständigt verifieringspass med turbo avstängt\">date-chk", + + "cut_u2sz": "storlek (i MiB) för varje uppladdnings-chunk; stora värden flyger bättre över atlanten. Prova lägre värden på mycket opålitliga uppkopplingar", + + "cut_flag": "garantera att endast en flik laddar upp samtidigt $N -- andra flikar måste också ha denna påslagen -- $N påverkar endast flikar på samma domän", + + "cut_az": "ladda upp filer i alfabetisk ordning, snarare än mindre filer först$N$Nalfabetisk ordning kan göra det enklare att se var något har gått fel, men uppladdningen blir lite långsammare över fiber / lokala nätverk", + + "cut_nag": "skicka en systemnotifikation när uppladdningar blir klara$N(endast om webbläsaren eller fliken inte är fokuserade)", + "cut_sfx": "ljudnotifikation när uppladdningar blir klara$N(endast om webbläsaren eller fliken inte är fokuserade)", + + "cut_mt": "använd multitrådning för att accelerera filhashningen$N$Ndetta använder web-workers och kräver$Nmer RAM (upp till 512 MiB extra)$N$Nuppladdningar över https blir 30% snabbare, över http 4.5x snabbare\">mt", + + "cut_wasm": "använd wasm istället för webbläsarens inbyggda hashare; förbättrar hastigheten i chrome-baserade webbläsare men ökar CPU-lasten, och många äldre versioner av chrome har buggar som får webbläsaren att konsumera allt RAM-minne och krascha om detta är påslaget\">wasm", + + "cft_text": "favikon-text (låt stå tom och uppdatera sidan för att stänga av)", + "cft_fg": "förgrundsfärg", + "cft_bg": "bakgrundsfärg", + + "cdt_lim": "högsta antal filer att visa in en mapp", + "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", + + "tt_entree": "visa trädvy$NSnabbtangent: B", + "tt_detree": "visa brödsmulor$NSnabbtangent: B", + "tt_visdir": "skrolla till öppnad mapp", + "tt_ftree": "växla mellan trädvy och textfiler$NHotkey: V", + "tt_pdock": "visa överordnade mappar i en panel längst upp i vyn", + "tt_dynt": "väx vyn när trädet expanderar", + "tt_wrap": "automatisk radbrytning", + "tt_hover": "visa överlånga rader när muspekaren hovrar över dem$N( skrollhjulet fungerar ej såvida inte pekaren$Nstår till vänster )", + + "ml_pmode": "vid mappens slut...", + "ml_btns": "komm.", + "ml_tcode": "konvertera", + "ml_tcode2": "konvertera till", + "ml_tint": "hy", + "ml_eq": "ljudutjämnare", + "ml_drc": "dynamikkompressor", + + "mt_loop": "upprepa en låt\">🔁", + "mt_one": "stoppa uppspelningen efter en låt\">1️⃣", + "mt_shuf": "blanda låtarna i varje mapp\">🔀", + "mt_aplay": "spela automatiskt om det finns en låt-ID i länkar du har klickat på för att öppna sidan$N$Nom detta är avstängt kommer sidans adress inte att bli uppdaterad med en låt-ID om du spelar musik, för att förhindra automatisk uppspelning om dessa inställningar går förlorade men webbadressen återstår\">a▶", + "mt_preload": "påbörja nedladdning av nästa låt i förväg för gapfri uppspelning\">ladda", + "mt_prescan": "hoppa till nästa mapp i förväg så att webbläsaren$Nförblir glad och inte avbryter uppspelningen\">nav", + "mt_fullpre": "försök att ladda ner hela låten i förväg;$N✅ aktivera på <b>opålitliga</b> uppkopplingar,$N❌ <b>avaktivera</b> kanske på långsamma uppkopplingar\">full", + "mt_fau": "förhindra att uppspelningen avstannar på telefoner om nästa låt inte laddas tillräckligt snabbt i förväg (kan ge upphov till buggiga musiktaggar)\">☕️", + "mt_waves": "vågformsreglage:$Nvisa ljudstyrkan i uppspelningsreglaget\">~s", + "mt_npclip": "visa knappar för att kopiera låtinfo till urklippet\">/np", + "mt_m3u_c": "visa knappar för att kopiera de valda$Nlåtarna som en m3u8-spellista\">📻", + "mt_octl": "systemintegration (mediaknappar / skärmdisplay)\">os-ctl", + "mt_oseek": "tillåt fram- och bakåtspolning via systemintegrationen$N$Nobs.: på vissa enheter (iPhone)$Nersätter detta knappen för nästa låt\">spola", + "mt_oscv": "visa skivomslag i skärmdisplayen\">omslag", + "mt_follow": "skrolla vyn till den spelande låten\">🎯", + "mt_compact": "kompakt kontrollpanel\">⟎", + "mt_uncache": "rensa cachen  (prova detta om din webbläsare har cachat$Nen trasig kopia av en låt och den vägrar spela upp den)\">rensa", + "mt_mloop": "upprepa den öppna mappen\">🔁 upprepa", + "mt_mnext": "ladda nästa mapp och fortsätt\">📂 nästa", + "mt_mstop": "stoppa uppspelningen\">⏸ stopp", + "mt_cflac": "konvertera flac / wav till {0}\">flac", + "mt_caac": "konvertera aac / m4a till {0}\">aac", + "mt_coth": "konvertera allt annat (förutom mp3) till {0}\">annat", + "mt_c2opus": "bäst val för pc, laptop, android\">opus", + "mt_c2owa": "opus-weba, för iOS 17.5 och senare\">owa", + "mt_c2caf": "opus-caf, för iOS 11 till 17\">caf", + "mt_c2mp3": "använd detta på mycket gamla enheter\">mp3", + "mt_c2flac": "bäst ljudkvalitet, men enorma nedladdningar\">flac", + "mt_c2wav": "okomprimerad uppspelning (ännu större)\">wav", + "mt_c2ok": "snyggt, bra val", + "mt_c2nd": "det är inte det rekommenderade formatet för din enhet, men det är lungt", + "mt_c2ng": "din enhet verkar inte stödja det här formatet, men vi provar ändå", + "mt_xowa": "det finns buggar i iOS som hindrar uppspelning i bakgrunden med detta format; vänligen använd caf eller mp3 istället", + "mt_tint": "nivå på bakgrundsfärg (0-100) på uppspelningsreglaget;$Ngör buffring mindre distraherande", + "mt_eq": "aktiverar utjämning och förstärkning;$N$Nboost <code>0</code> = standard 100%-volym (omodifierad)$N$Nwidth <code>1  </code> = standard stereo (omodifierad)$Nwidth <code>0.5</code> = 50% vänster-höger crossfeed$Nwidth <code>0  </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = tar bort sång :^)$N$Nnär utjämningen är aktiverad blir gaplösa album verkligen gaplösa, så låt den stå påslagen med alla värden satta till 0 (förutom width = 1) om du bryr dig om det", + "mt_drc": "aktiverar dynamikkompressorn (volymtillplattning / brickwaller); aktiverar även utjämnaren för att balansera röran, så sätt alla fält i utjämnaren förutom 'width' till 0 om du inte vill ha den$N$Nsänker all volym över THRESHOLD dB; för varje RATIO dB över THRESHOLD blir det 1 dB av output, så standardvärdena tresh = -24 och ratio = 12 innebär att volymen aldrig bör bli högre än -22 dB och det är säkert att höja utjämnarens boost till 0.8, eller t.o.m. 1.8 med ATK 0 och ett högt RLS-värde t.ex. 90 (fungerar endast i firefox; RLS är låst till högst 1 i andra webbläsare)$N$N(se wikipedia för en bättre förklaring)", + + "mb_play": "play", + "mm_hashplay": "spela upp den här ljudfilen?", + "mm_m3u": "tryck <code>Enter/OK</code> för att spela\ntryck <code>ESC/Avbryt</code> to Edit", + "mp_breq": "firefox 82+ eller chrome 73+ eller iOS 15+ krävs", + "mm_bload": "laddar...", + "mm_bconv": "konverterar till {0}, vänligen vänta...", + "mm_opusen": "din webbläsare kan inte spela upp aac- eller m4a-filer;\nkonvertering till opus är nu påslaget", + "mm_playerr": "uppspelning misslyckades: ", + "mm_eabrt": "Uppspelningen avbröts", + "mm_enet": "Din uppkoppling är skum", + "mm_edec": "Filen är korrumperad??", + "mm_esupp": "Din webbläsare förstår inte detta format", + "mm_eunk": "Okänt Fel", + "mm_e404": "Kunde inte spela upp ljudfil; fel 404: Filen hittades inte.", + "mm_e403": "Kunde inte spela upp ljudfil; fel 403: Åtkomst nekad.\n\nProva att ladda om sidan med F5, du kanske blev utloggad", + "mm_e500": "Kunde inte spela upp ljudfil; fel 500: Kolla serverloggen.", + "mm_e5xx": "Kunde inte spela upp ljudfil; serverfel ", + "mm_nof": "hittade inga fler låtar i närheten", + "mm_prescan": "Letar efter fler låtar...", + "mm_scank": "Hittade nästa låt:", + "mm_uncache": "cachen rensad; alla låtar kommer att laddas ner igen vid uppspelning", + "mm_hnf": "den låten finns inte längre", + + "im_hnf": "den bilden finns inte längre", + + "f_empty": 'mappen är tom', + "f_chide": 'detta kommer att dölja kolumnen «{0}»\n\ndu kan visa kolumner igen i inställningarna', + "f_bigtxt": "den här filen är {0} MiB stor -- vill du verkligen visa den som text?", + "f_bigtxt2": "visa endast slutet på filen? detta aktiverar även övervakning, vilket visar nya rader i filen i realtid", + "fbd_more": '<div id="blazy"><code>{0}</code> av <code>{1}</code> filer visas; <a href="#" id="bd_more">visa {2}</a> eller <a href="#" id="bd_all">visa alla</a></div>', + "fbd_all": '<div id="blazy"><code>{0}</code> av <code>{1}</code> filer visas; <a href="#" id="bd_all">visa alla</a></div>', + "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_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 <code>.PARTIAL</code>-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 <code>.PARTIAL</code>-filen istället att laddas ner, vilket är nästan garanterat att ge dig korrumperad data.", + + "ft_paste": "klistra in {0} objekt$NSnabbtangent: ctrl-V", + "fr_eperm": 'kan ej byta namn:\ndu har inte flytträttighet i denna mapp', + "fd_eperm": 'kan ej radera:\ndu har inte raderingsrättighet i denna mapp', + "fc_eperm": 'kan ej klippa:\ndu har inte flytträttighet i denna mapp', + "fp_eperm": 'kan ej klistra in:\ndu har inte skrivrättighet i denna mapp', + "fr_emore": "välj minst en fil att byta namn på", + "fd_emore": "välj minst en fil att radera", + "fc_emore": "välj minst en fil att klippa", + "fcp_emore": "välj minst en fil att kopiera till urklippet", + + "fs_sc": "dela den öppna mappen", + "fs_ss": "dela de urvalda filerna", + "fs_just1d": "du kan inte välja mer än en mapp\neller blanda filer och mappar i samma urval", + "fs_abrt": "❌ avbryt", + "fs_rand": "🎲 slump.namn", + "fs_go": "✅ skapa utdelning", + "fs_name": "namn", + "fs_src": "källa", + "fs_pwd": "lösen", + "fs_exp": "utgång", + "fs_tmin": "min", + "fs_thrs": "timmar", + "fs_tdays": "dagar", + "fs_never": "oändlig", + "fs_pname": "valfritt länknamn; slumpas fram om detta står tomt", + "fs_tsrc": "filen eller mappen att dela", + "fs_ppwd": "valfritt lösenord", + "fs_w8": "skapar utdelning...", + "fs_ok": "tryck <code>Enter/OK</code> för att kopiera länken till urklipp\ntryck <code>ESC/Avbryt</code> för att stänga", + + "frt_dec": "kan laga vissa typer av trasiga filnamn\">avkoda-url", + "frt_rst": "återställ modifierade filnamn till de ursprungliga\">↺ återställ", + "frt_abrt": "avbryt och stäng denna panel\">❌ avbryt", + "frb_apply": "BYT NAMN", + "fr_adv": "batch-, metadata- och mönsteromskrivning\">avancerat", + "fr_case": "skiftlägeskänsligt reguljärt uttryck\">skift", + "fr_win": "windows-säkra namn; ersätt <code><>:"\\|?*</code> med japanska fullbreddtecken\">win", + "fr_slash": "ersätt <code>/</code> med ett tecken som inte skapar nya mappar\">ingen /", + "fr_re": "reguljärt sökuttryck att tillämpa på ursprungliga filnamn; grupper kan hänvisas till i formatfältet nedan via <code>(1)</code> och <code>(2)</code> osv.", + "fr_fmt": "inspirerat av foobar2000:$N<code>(title)</code> ersätts av låttitel,$N<code>[(artist) - ](title)</code> skippar [detta] om artisten är tom$N<code>$lpad((tn),2,0)</code> fyller i spårnumret till 2 siffror", + "fr_pdel": "ta bort", + "fr_pnew": "spara som", + "fr_pname": "ge ett nytt namn på din inställning", + "fr_aborted": "avbrutet", + "fr_lold": "gammalt namn", + "fr_lnew": "nytt namn", + "fr_tags": "taggar för de valda filerna (skrivskyddat, endast som referens):", + "fr_busy": "byter namn på {0} objekt...\n\n{1}", + "fr_efail": "namnbyte misslyckades:\n", + "fr_nchg": "{0} av de nya namnen ändrades p g a <code>win</code> och/eller <code>ingen /</code>\n\nÄr det okej att fortsätta med de nya namnen?", + + "fd_ok": "radering lyckades", + "fd_err": "radering misslyckades:\n", + "fd_none": "inget raderades; kanske blockerat av serverkonfigurationen (xbd)?", + "fd_busy": "raderar {0} objekt...\n\n{1}", + "fd_warn1": "RADERA dessa {0} objekt?", + "fd_warn2": "<b>Sista chansen!</b> Det finns inget sätt att ångra detta. Radera?", + + "fc_ok": "klippte {0} objekt", + "fc_warn": 'klippte {0} objekt, men:\n\nendast <b>denna</b> webbläsarflik kan klistra in dem\n(eftersom urvalet är så enormt stort)', + + "fcc_ok": "kopierade {0} objekt till urklippet", + "fcc_warn": 'kopierade {0} objekt till urklippet, men:\n\nendast <b>denna</b> webbläsarflik kan klistra in dem\n(eftersom urvalet är så enormt stort)', + + "fp_apply": "använd dessa namn", + "fp_ecut": "klipp eller kopiera filer / mappar först för att klistra / flytta dem\n\nobs.: du kan klippa och klistra mellan webbläsarflikar", + "fp_ename": "{0} objekt kan ej flyttas hit eftersom filnamnen redan är tagna. Ge dem nya namn nedan för att fortsätta, eller lämna fältet tomt för att skippa:", + "fcp_ename": "{0} objekt kan ej kopieras hit eftersom filnamnen redan är tagna. Ge dem nya namn nedan för att fortsätta, eller lämna fältet tomt för att skippa:", + "fp_emore": "det finns fortfarande filnamnskrockar att fixa", + "fp_ok": "flytt lyckades", + "fcp_ok": "kopiering lyckades", + "fp_busy": "flyttar {0} objekt...\n\n{1}", + "fcp_busy": "kopierar {0} objekt...\n\n{1}", + "fp_abrt": "avbryter...", + "fp_err": "flytt misslyckades:\n", + "fcp_err": "kopiering misslyckades:\n", + "fp_confirm": "flytta dessa {0} objekt hit?", + "fcp_confirm": "kopiera dessa {0} objekt hit?", + "fp_etab": 'lyckades ej läsa urklippet från en annan webbläsarflik', + "fp_name": "laddar upp en fil från din enhet. Ge den ett namn:", + "fp_both_m": '<h6>välj vad som ska klistras in</h6><code>Enter</code> = Flytta {0} objekt från «{1}»\n<code>ESC</code> = Ladda upp {2} filer från din enhet', + "fcp_both_m": '<h6>välj vad som ska klistras in</h6><code>Enter</code> = Kopiera {0} objekt från «{1}»\n<code>ESC</code> = Ladda upp {2} filer från din enhet', + "fp_both_b": '<a href="#" id="modal-ok">Flytta</a><a href="#" id="modal-ng">Ladda upp</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopiera</a><a href="#" id="modal-ng">Ladda upp</a>', + + "mk_noname": "skriv ett namn i fältet till vänster först :p", + + "tv_load": "Laddar textfil:\n\n{0}\n\n{1}% ({2} av {3} MiB laddat)", + "tv_xe1": "kunde ej ladda textfil:\n\nfel ", + "tv_xe2": "404, filen hittades inte", + "tv_lst": "lista av textfiler i", + "tvt_close": "återvänd till mapp$NSnabbtangent: M (eller Esc)\">❌ stäng", + "tvt_dl": "ladda ner denna fil$NSnabbtangent: Y\">💾 ladda ner", + "tvt_prev": "visa föregående fil$NSnabbtangent: i\">⬆ föreg.", + "tvt_next": "visa nästa fil$NSnabbtangent: K\">⬇ nästa", + "tvt_sel": "välj fil   ( för klipp / kopiera / radera / ... )$NSnabbtangent: S\">välj", + "tvt_edit": "öppna fil i textredigerare$NSnabbtangent: E\">✏️ redigera", + "tvt_tail": "övervaka filen; visa nya rader i realtid\">📡 övervaka", + "tvt_wrap": "automatisk radbrytning\">↵", + "tvt_atail": "lås vyn till sidans botten\">⚓", + "tvt_ctail": "avkoda terminalfärger (ansi-escapesekvenser)\">🌈", + "tvt_ntail": "gräns för scrollback (hur många byte ska behållas laddade)", + + "m3u_add1": "låt tillagd till m3u-spellista", + "m3u_addn": "{0} låtar tillagda till m3u-spellista", + "m3u_clip": "m3u-spellista kopierad till urklippet\n\ndu bör skapa en ny textfil som heter någonting.m3u och klistra in spellistan i det dokumentet; detta gör den uppspelbar", + + "gt_vau": "visa inte videor, spela endast ljudet\">🎧", + "gt_msel": "urval av filer; ctrl-klicka en fil för standardbeteende$N$N<em>när detta är aktiverat: dubbelklicka en fil / mapp för att öppna den</em>$N$NSnabbtangent: S\">urval", + "gt_crop": "centrera och beskär miniatyrbilder\">beskär", + "gt_3x": "högupplösta miniatyrbilder\">3x", + "gt_zoom": "zoom", + "gt_chop": "klipp", + "gt_sort": "sortera efter", + "gt_name": "namn", + "gt_sz": "storlek", + "gt_ts": "datum", + "gt_ext": "typ", + "gt_c1": "förkorta filnamn (visa mindre)", + "gt_c2": "förläng filnamn (visa mer)", + + "sm_w8": "söker...", + "sm_prev": "sökresultaten nedan är från en tidigare sökning:\n ", + "sl_close": "stäng sökresultaten", + "sl_hits": "visar {0} träffar", + "sl_moar": "ladda fler", + + "s_sz": "storlek", + "s_dt": "datum", + "s_rd": "sökväg", + "s_fn": "namn", + "s_ta": "taggar", + "s_ua": "uppl.", + "s_ad": "avanc.", + "s_s1": "minimum MiB", + "s_s2": "maximum MiB", + "s_d1": "min. iso8601", + "s_d2": "max. iso8601", + "s_u1": "uppladdad efter", + "s_u2": "och/eller före", + "s_r1": "sökvägen innehåller   (blankstegsseparerat)", + "s_f1": "filnamnet innehåller   (invertera med -intedetta)", + "s_t1": "taggar innehåller   (^=start, slut=$)", + "s_a1": "specifika metadataegenskaper", + + "md_eshow": "kan ej visa ", + "md_off": "[📜<em>läsmig</em>] avstängt i [⚙️] -- dokumentet är dolt", + + "badreply": "Kunde ej tolka svaret från servern", + + "xhr403": "403: Åtkomst nekad\n\nProva att ladda om sidan med F5, du kanske blev utloggad", + "xhr0": "okänt (tappade förmodligen kontakt med servern, eller så är den nere)", + "cf_ok": "ledsen -- DD" + wah + "oS-skyddet har aktiverats\n\nsaker bör fungera igen om 30 sekunder\n\nom inget händer, ladda om sidan med F5", + "tl_xe1": "kunde inte visa undermappar:\n\nfel ", + "tl_xe2": "404: Mappen hittades inte", + "fl_xe1": "kunde inte visa filer i mapp:\n\nfel ", + "fl_xe2": "404: Mappen hittades inte", + "fd_xe1": "kunde inte skapa mapp:\n\nfel ", + "fd_xe2": "404: Överordnad mapp hittades inte", + "fsm_xe1": "kunde inte skicka meddelande:\n\ndel ", + "fsm_xe2": "404: Överordnad mapp hittades inte", + "fu_xe1": "kunde inte ladda unpost-listan från servern:\n\nfel ", + "fu_xe2": "404: Filen hittades inte??", + + "fz_tar": "okomprimerad tar-fil i gnu-format (linux / mac)", + "fz_pax": "okomprimerad tar-fil i pax-format (långsammare)", + "fz_targz": "gnu-tar komprimerad med gzip-nivå 3$N$Ndetta är vanligtvis mycket långsamt,$Nanvänd okomprimerad tar istället", + "fz_tarxz": "gnu-tar komprimerad med xz-nivå 1$N$Ndetta är vanligtvis mycket långsamt,$Nanvänd okomprimerad tar istället", + "fz_zip8": "zip-fil med utf8-filnman (kan vara skum i windows 7 och äldre)", + "fz_zipd": "zip-fil med standard cp437-filnamn, för riktigt gammal mjukvara", + "fz_zipc": "cp437 med crc32 uträknad i förväg,$Nför MS-DOS PKZIP v2.04g (oktober 1993)$N(tar längre tid att behandla innan nedladdningen kan påbörjas)", + + "un_m1": "du kan radera dina senaste uppladdningar (eller avbryta pågående sådana) nedan", + "un_upd": "uppdatera", + "un_m4": "eller, dela filerna som syns nedan:", + "un_ulist": "visa", + "un_ucopy": "kopiera", + "un_flt": "filter:  sökvägen måste innehålla", + "un_fclr": "rensa filtret", + "un_derr": 'unpost-radering misslyckades:\n', + "un_f5": 'något gick sönder, prova att uppdatera eller tryck på F5', + "un_uf5": "ledsen men du måste uppdatera sidan (t.ex. genom att trycka på F5 eller CTRL-R) innan du kan avbryta den här uppladdningen", + "un_nou": '<b>varning:</b> servern är för upptagen för att visa pågående uppladdningar; klicka på "uppdatera" om en stund', + "un_noc": '<b>varning:</b> serverkonfigurationen tillåter inte unpost:ning av uppladdade filer', + "un_max": "visar de första 2000 filerna (använd filtret)", + "un_avail": "{0} av de senaste uppladdningarna kan raderas<br />{1} pågående uppladdningar kan avbrytas", + "un_m2": "sorterat efter uppladdningstid; senast uppladdad först:", + "un_no1": "tjosan! inga uppladdningar är tillräckligt nya", + "un_no2": "tjosan! inga uppladdningar som matchar filtret är tillräckligt nya", + "un_next": "radera de {0} nästkommande filerna", + "un_abrt": "avbryt", + "un_del": "radera", + "un_m3": "laddar dina senaste uppladdningar...", + "un_busy": "raderar {0} filer...", + "un_clip": "{0} länkar kopierade till urklippet", + + "u_https1": "du bör", + "u_https2": "byta till https", + "u_https3": "för bättre prestanda", + "u_ancient": 'din webbläsare är imponerande uråldrig -- du kanske borde <a href="#" onclick="goto(\'bup\')">använda bup istället</a>', + "u_nowork": "firefox 53+ eller chrome 57+ eller iOS 11+ krävs", + "tail_2old": "firefox 105+ eller chrome 71+ eller iOS 14.5+ krävs", + "u_nodrop": 'din webbläsare är för gammal för dra-och-släpp-uppladdning', + "u_notdir": "det där är ingen mapp!\n\ndin webbläsare är för gammal,\nprova dra-och-släpp istället", + "u_uri": "släpp bilder från andra webbläsarfönster på den stora\nuppladdningsknappen för att ladda upp dem", + "u_enpot": 'byt till <a href="#">potatisgränssnittet</a> (kan förbättra uppladdningshastigheten)', + "u_depot": 'byt till <a href="#">det snygga gränssnittet</a> (kan försämra uppladdningshastigheten)', + "u_gotpot": 'byter till potatisgränssnittet för förbättrad uppladdningshastighet,\n\nbyt gärna tillbaka om du vill!', + "u_pott": "<p>filer:   <b>{0}</b> färdiga,   <b>{1}</b> misslyckade,   <b>{2}</b> pågående,   <b>{3}</b> köade</p>", + "u_ever": "detta är den enkla uppladdaren; up2k kräver minst<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1", + "u_su2k": 'detta är den enkla uppladdaren; <a href="#" id="u2yea">up2k</a> är bättre', + "u_uput": 'optimera hastigheten (skippa checksumman)', + "u_ewrite": 'du har inte skrivrättighet i denna mapp', + "u_eread": 'du har inte läsrättighet i denna mapp', + "u_enoi": 'serverkonfigurationen har inte slagit på sökning', + "u_enoow": "du kan inte skriva över här; raderingsrättighet krävs", + "u_badf": 'Dessa {0} filer (av totalt {1}) skippades, möjligtvis p.g.a. filsystemsrättigheter:\n\n', + "u_blankf": 'Dessa {0} filer (av totalt {1}) är tomma; ladda upp dem ändå?\n\n', + "u_applef": 'Dessa {0} filer (av totalt {1}) är förmodligen oönskade;\nTryck <code>OK/Enter</code> för att SKIPPA de följande filerna,\nTryck <code>Avbryt/ESC</code> för att INKLUDERA och LADDA UPP dem:\n\n', + "u_just1": '\nDet kanske fungerar om du endast väljer en fil', + "u_ff_many": "om du använder <b>Linux / MacOS / Android,</b> så <em>kan</em> denna mängd filer <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\">krascha Firefox!</a>\nom detta händer, vänligen försök igen (eller använd Chrome).", + "u_up_life": "Denna uppladdning kommer att raderas från servern om\n{0} efter att den har blivit uppladdad", + "u_asku": 'ladda upp dessa {0} filer till <code>{1}</code>', + "u_unpt": "du kan ångra / radera denna uppladdning med 🧯 uppe till vänster", + "u_bigtab": 'försöker att visa {0} filer\n\ndetta kan krascha din webbläsare, är du säker?', + "u_scan": 'Scannar filer...', + "u_dirstuck": 'katalogskannern fastnade när den försökte komma åt de följande {0} objekten; dessa kommer att skippas:', + "u_etadone": 'Klar ({0}, {1} filer)', + "u_etaprep": '(förbereder uppladdning)', + "u_hashdone": 'hashning klar', + "u_hashing": 'hashar', + "u_hs": 'skakar hand...', + "u_started": "filerna laddas nu upp; se [🚀]", + "u_dupdefer": "duplikat; kommer att behandlas efter alla andra filer", + "u_actx": "klicka här för att undvika prestandaförlust<br />när du byter till andra fönster/flikar", + "u_fixed": "Okej!  Fixat 👍", + "u_cuerr": "misslyckades att ladda upp chunk {0} av {1};\nförmodligen harmlöst, fortsätter\n\nfil: {2}", + "u_cuerr2": "servern avvisade uppladdningen (chunk {0} av {1});\nprovar igen senare\n\nfil: {2}\n\nfel ", + "u_ehstmp": "provar igen; see nedåt till höger", + "u_ehsfin": "servern avvisade förfrågan att färdigställa uppladdningen; provar igen...", + "u_ehssrch": "servern avvisade förfrågan att söka; provar igen...", + "u_ehsinit": "servern avvisade förfrågan att påbörja uppladdningen; provar igen...", + "u_eneths": "nätverksfel vid handskakning; provar igen...", + "u_enethd": "nätverksfel när destinationens existens testades; provar igen...", + "u_cbusy": "väntar på att servern ska lita på oss igen efter nätverksfel...", + "u_ehsdf": "servern fick slut på diskutrymme!\n\nprovar igen, ifall någon rensar upp\ntillräckligt med utrymme för att fortsätta", + "u_emtleak1": "det verkar som att din webbläsare kanske har en minnesläcka;\nvänligen", + "u_emtleak2": ' <a href="{0}">byt till https (rekommenderat)</a> eller ', + "u_emtleak3": ' ', + "u_emtleakc": 'prova följande:\n<ul><li>tryck <code>F5</code> för att uppdatera sidan</li><li>avaktivera sedan  <code>mt</code> -växeln i  <code>⚙️-inställningarna</code></li><li>och prova att ladda upp igen</li></ul>Uppladdningar kommer att vara lite långsammare, men aja.\nBeklagar problemet!\n\nPS: chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">har en buggfix</a> för detta', + "u_emtleakf": 'prova följande:\n<ul><li>tryck <code>F5</code> för att uppdatera sidan</li><li>aktivera sedan <code>🥔</code> (potatis) i uppladdningsgränssnittet<li>och prova att ladda upp igen</li></ul>\nPS: firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">kommer förhoppningsvis få en buggfix</a> vid något tillfälle', + "u_s404": "hittades ej på servern", + "u_expl": "förklara", + "u_maxconn": "de flesta webbläsare begränsar detta till 6, men firefox låter dig höja gränsen med <code>connections-per-server</code> i <code>about:config</code>", + "u_tu": '<p class="warn">VARNING: turbo är aktiverat, <span> det är möjligt att klienten inte upptäcker och återupptar ofärdiga uppladdningar; se tipset för turbo-växeln</span></p>', + "u_ts": '<p class="warn">VARNING: turbo är aktiverat, <span> sökresultat kan vara felaktiga; se tipset för turbo-växeln</span></p>', + "u_turbo_c": "serverkonfigurationen har avaktiverat turbo", + "u_turbo_g": "avaktiverar turbo eftersom du inte har rättigheten\natt se mappars innehåll i den här volymen", + "u_life_cfg": 'radera automatiskt efter <input id="lifem" p="60" /> min (eller <input id="lifeh" p="3600" /> timmar)', + "u_life_est": 'uppladdningen kommer att raderas vid <span id="lifew" tt="local time">---</span>', + "u_life_max": 'denna mapp tvingar en\nhögsta livstid på {0}', + "u_unp_ok": 'unpost är tillåten för {0}', + "u_unp_ng": 'unpost är INTE tillåten', + "ue_ro": 'du har endast läsrättighet till denna mapp\n\n', + "ue_nl": 'du är inte inloggad', + "ue_la": 'du är inloggad som "{0}"', + "ue_sr": 'du är i filsökläge\n\nbyt till uppladdningsläge genom att klicka på förstoringsglaset 🔎 (bredvid den stora SÖK-knappen), och försök ladda upp igen\n\nledsen', + "ue_ta": 'prova att ladda upp igen nu, det bör fungera', + "ue_ab": "denna fil laddas redan upp till en annan mapp, och den uppladdningen måste färdigställas innan filen kan laddas upp någon annanstans.\n\nDu kan avbryta och glömma bort den uppladdningen med 🧯 uppe till vänster", + "ur_1uo": "Okej: Filen laddades upp med framgång", + "ur_auo": "Okej: Alla {0} filer laddades upp med framgång", + "ur_1so": "Okej: Filen fanns på servern", + "ur_aso": "Okej: Alla {0} filer fanns på servern", + "ur_1un": "Uppladdningen misslyckades, ledsen", + "ur_aun": "Alla {0} uppladdningar misslyckades, ledsen", + "ur_1sn": "Filen hittades INTE på servern", + "ur_asn": "De {0} filerna hittades INTE på servern", + "ur_um": "Klar;\n{0} uppladdningar gick okej,\n{1} uppladdningar misslyckades, ledsen", + "ur_sm": "Klar;\n{0} filer hittades på servern,\n{1} filer hittades INTE på servern", + + "lang_set": "uppdatera för att ändringen ska ta effekt?", + }, + "tur": { + "tt": "Türkçe", + + "cols": { + "c": "işlem butonları", + "dur": "süre", + "q": "kalite / bitrate", + "Ac": "ses kodlaması", + "Vc": "video kodlaması", + "Fmt": "format / yapı", + "Ahash": "ses denetim toplamı", + "Vhash": "video denetim toplamı", + "Res": "çözünürlük", + "T": "dosya türü", + "aq": "ses kalitesi / bitrate", + "vq": "video kalitesi / bitrate", + "pixfmt": "subsampling / pixel yapısı", + "resw": "yatay çözünürlük", + "resh": "dikey çözünürlük", + "chs": "ses kanalları", + "hz": "örnekleme hızı" + }, + + "hks": [ + [ + "diğer", + ["ESC", "kapat"], + + "dosya yönetimi", + ["G", "liste / ızgara görünümü arasında geçiş yap"], + ["T", "küçük resimler / simgeler arasında geçiş yap"], + ["⇧ A/D", "küçük resim boyutu"], + ["ctrl-K", "seçileni sil"], + ["ctrl-X", "seçimi panoya kes"], + ["ctrl-C", "seçimi panoya kopyala"], + ["ctrl-V", "buraya yapıştır (taşı/kopyala)"], + ["Y", "seçileni indir"], + ["F2", "seçileni yeniden adlandır"], + + "dosya yönetimi seçimleri", + ["boşluk", "seçimi değiştir"], + ["↑/↓", "seçim imlecini hareket ettir"], + ["ctrl ↑/↓", "imleci ve görünümü hareket ettir"], + ["⇧ ↑/↓", "önceki/sonraki dosyayı seç"], + ["ctrl-A", "tüm dosyaları / klasörleri seç"], + ], [ + "navigasyon", + ["B", "içerik haritası / navigasyon paneli arasında geçiş yap"], + ["I/K", "önceki/sonraki klasör"], + ["M", "üst klasör (veya mevcut olanı daralt)"], + ["V", "navigasyon panelinde klasörler / metin dosyaları arasında geçiş yap"], + ["A/D", "navigasyon paneli boyutu"], + ], [ + "ses oynatıcı", + ["J/L", "önceki/sonraki şarkı"], + ["U/O", "10sn geri/ileri atla"], + ["0..9", "%0..%90 atla"], + ["P", "oynat/duraklat (aynı zamanda oynatıcıyı aç)"], + ["S", "şarkıyı seç"], + ["Y", "şarkıyı indir"], + ], [ + "resim görüntüleyici", + ["J/L, ←/→", "önceki/sonraki resim"], + ["Home/End", "ilk/son resim"], + ["F", "tam ekran"], + ["R", "sağa döndür"], + ["⇧ R", "sola döndür"], + ["S", "resmi seç"], + ["Y", "resmi indir"], + ], [ + "video oynatıcı", + ["U/O", "10sn geri/ileri atla"], + ["P/K/Space", "oynat/duraklat"], + ["C", "sıradakinden devam et"], + ["V", "döngü"], + ["M", "sessiz"], + ["[ ve ]", "döngü aralığını ayarla"], + ], [ + "metin dosyası görüntüleyici", + ["I/K", "önceki/sonraki dosya"], + ["M", "metin dosyasını kapat"], + ["E", "metin dosyasını düzenle"], + ["S", "dosyayı seç (kes/kopyala/yeniden adlandır)"], + ] + ], + + "m_ok": "Tamam", + "m_ng": "İptal", + + "enable": "Etkinleştir", + "danger": "TEHLİKE", + "clipped": "panoya kopyalandı", + + "ht_s1": "saniye", + "ht_s2": "saniye", + "ht_m1": "dakika", + "ht_m2": "dakika", + "ht_h1": "saat", + "ht_h2": "saat", + "ht_d1": "gün", + "ht_d2": "gün", + "ht_and": " ve ", + + "goh": "kontrol paneli", + "gop": 'önceki kardeş">önceki', + "gou": 'üst klasör">üst', + "gon": 'sonraki klasör">sonraki', + "logout": "Çıkış ", + "login": "Giriş", + "access": " erişim", + "ot_close": "alt menüyü kapat", + "ot_search": "dosyaları özniteliklere, yol / ad, müzik etiketlerine veya bunların herhangi bir kombinasyonuna göre arayın$N$N<code>foo bar</code> = hem «foo» hem de «bar» içermelidir,$N<code>foo -bar</code> = «foo» içermeli ancak «bar» içermemelidir,$N<code>^yana .opus$</code> = «yana» ile başlamalı ve bir «opus» dosyası olmalıdır$N<code>"try unite"</code> = tam olarak «try unite» içermelidir$N$N tarih formatı iso-8601'dir, gibi$N<code>2009-12-31</code> veya <code>2020-09-12 23:30:00</code>", + "ot_unpost": "unpost: son yüklemelerinizi silin veya tamamlanmamış olanları iptal edin", + "ot_bup": "bup: temel yükleyici, hatta netscape 4.0'ı destekler", + "ot_mkdir": "mkdir: yeni bir dizin oluştur", + "ot_md": "new-md: yeni bir markdown belgesi oluştur", + "ot_msg": "msg: sunucu günlüğüne bir mesaj gönder", + "ot_mp": "medya oynatıcı seçenekleri", + "ot_cfg": "konfigürasyon seçenekleri", + "ot_u2i": 'up2k: dosyaları yükle (yazma erişiminiz varsa) veya sunucuda bir yerde var olup olmadıklarını görmek için arama moduna geçiş yap$N$Nyüklemeler devam ettirilebilir, çok iş parçacıklı ve dosya zaman damgaları korunur, ancak [🎈]  (temel yükleyici) ile karşılaştırıldığında daha fazla CPU kullanır<br /><br />yükleme sırasında, bu simge bir ilerleme göstergesi haline gelir!', + "ot_u2w": 'up2k: devam desteği ile dosyaları yükle (tarayıcınızı kapatın ve daha sonra aynı dosyaları bırakın)$N$Nçok iş parçacıklı ve dosya zaman damgaları korunur, ancak [🎈]  (temel yükleyici) ile karşılaştırıldığında daha fazla CPU kullanır<br /><br />yükleme sırasında, bu simge bir ilerleme göstergesi haline gelir!', + "ot_noie": 'Lütfen Chrome / Firefox / Edge kullanın', + + "ab_mkdir": "dizin oluştur", + "ab_mkdoc": "yeni markdown belgesi", + "ab_msg": "sunucu günlüğüne mesaj gönder", + + "ay_path": "klasörlere atla", + "ay_files": "dosyalara atla", + + "wt_ren": "seçilenleri yeniden adlandır$NKısa yol: F2", + "wt_del": "Seçilen ögeleri sil$Kısa yol: ctrl-K", + "wt_cut": "seçilen ögeleri kes <small></small>$NKısa yol: ctrl-X", + "wt_cpy": "seçilen ögeleri panoya kopyala$N$NKısa yol: ctrl-C", + "wt_pst": "daha önce kesilmiş / kopyalanmış bir seçimi yapıştır$NKısa yol: ctrl-V", + "wt_selall": "tüm dosyaları seç$NKısa yol: ctrl-A (dosya odaklandığında)", + "wt_selinv": "seçimi tersine çevir", + "wt_zip1": "seçilenleri sıkıştırılmış bir arşiv olarak indir", + "wt_selzip": "seçimi arşiv olarak indir", + "wt_seldl": "seçimi ayrı dosyalar olarak indir$NHotkey: Y", + "wt_npirc": "irc biçiminde parça bilgilerini kopyala", + "wt_nptxt": "düz metin parça bilgilerini kopyala", + "wt_m3ua": "m3u çalma listesine ekle (daha sonra <code>📻kopyala</code>ya tıklayın)", + "wt_m3uc": "m3u çalma listesini panoya kopyala", + "wt_grid": "ızgara / liste görünümünü değiştir$NHotkey: G", + "wt_prev": "önceki parça$NHotkey: J", + "wt_play": "oynat / duraklat$NHotkey: P", + "wt_next": "sonraki parça$NHotkey: L", + + "ul_par": "paralel yüklemeler:", + "ut_rand": "dosya adlarını rastgeleleştir", + "ut_u2ts": "kendi dosyalarınızdan sunucuya$Nzaman damgasını kopyala\">📅", + "ut_ow": "sunucudaki mevcut dosyaları üzerine yazmak mı?$N🛡️: asla (yerine yeni bir dosya adı oluşturur)$N🕒: sunucu dosyası sizinkinden daha eskiyse üzerine yaz$N♻️: dosyalar farklıysa her zaman üzerine yaz", + "ut_mt": "yükleme yaparken diğer dosyaların hash'lenmesini durdur$N$kötü bir CPU veya HDD'ye sahipseniz kullanabilirsiniz.", + "ut_ask": 'yüklemeye başlamadan önce doğrulama mesajı göster">💭', + "ut_pot": "arayüzü daha az karmaşık hale getirerek$Nyükleme hızını yavaş cihazlarda artır", + "ut_srch": "gerçekten yükleme yapma, bunun yerine dosyaların $N sunucuda var olup olmadığını kontrol et (okuma izniniz olan tüm klasörleri tarar)", + "ut_par": "0'a ayarlayarak yüklemeleri durdur$N$Nbağlantınız yavaşsa değeri artırın$N$NLAN'daysanız veya sunucu HDD'si darboğaz yapıyorsa 1'de tutun", + "ul_btn": "dosyaları / klasörleri <br>buraya sürükleyin (veya bana tıklayın)", + "ul_btnu": "Y Ü K L E", + "ul_btns": "A R A", + + "ul_hash": "hash", + "ul_send": "gönder", + "ul_done": "tamamlandı", + "ul_idle1": "henüz bekleyen yükleme yok", + "ut_etah": "ortalama <em>hash</em> hızı ve bitişe kadar tahmini süre", + "ut_etau": "ortalama <em>yükleme</em> hızı ve bitişe kadar tahmini süre", + "ut_etat": "ortalama <em>toplam</em> hızı ve bitişe kadar tahmini süre", + + "uct_ok": "başarıyla tamamlandı", + "uct_ng": "başarısız: başarısız / reddedildi / bulunamadı", + "uct_done": "hem başarılı hem de başarısız", + "uct_bz": "hash'liyor veya yüklüyor", + "uct_q": "beklemede, gönderiliyor", + + "utl_name": "dosya adı", + "utl_ulist": "liste", + "utl_ucopy": "kopyala", + "utl_links": "bağlantılar", + "utl_stat": "durum", + "utl_prog": "ilerleme", + + // kısa tutun: + "utl_404": "404", + "utl_err": "HATA", + "utl_oserr": "OS-hatası", + "utl_found": "bulundu", + "utl_defer": "ertele", + "utl_yolo": "YOLO", + "utl_done": "tamamlandı", + + "ul_flagblk": "dosyalar sıraya alındı</b><br>ancak başka bir tarayıcı sekmesinde meşgul bir up2k var,<br>bu nedenle önce onun bitmesini bekliyor", + "ul_btnlk": "sunucu yapılandırması bu anahtarı bu duruma kilitledi", + + "udt_up": "Yükle", + "udt_srch": "Ara", + "udt_drop": "buraya bırakın", + + "u_nav_m": '<h6>Pekâlâ, bakalım neyin varmnış?</h6><code>Enter</code> = Dosyalar (bir veya birden fazla)\n<code>ESC</code> = Bir klasör (alt klasörler dahil)', + "u_nav_b": '<a href="#" id="modal-ok">Dosyalar</a><a href="#" id="modal-ng">Tek klasör</a>', + + "cl_opts": "aç / kapat", + "cl_themes": "tema", + "cl_langs": "dil", + "cl_ziptype": "klasör indirme", + "cl_uopts": "up2k aç / kapat", + "cl_favico": "favicon", + "cl_bigdir": "big dirs", + "cl_hsort": "#sort", + "cl_keytype": "anahtar notasyonu", + "cl_hiddenc": "gizli sütunlar", + "cl_hidec": "gizle", + "cl_reset": "sıfırla", + "cl_hpick": "aşağıdaki tabloda gizlemek için sütun başlıklarına dokunun", + "cl_hcancel": "sütun gizleme iptal edildi", + + "ct_grid": '田 ızgara', + "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_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ı', + "ct_qdel": 'dosyaları silerken yalnız bir kez onay isteyin">qdel', + "ct_dir1st": 'sıralamada klasörleri dosyalardan önceye koy">ilk 📁', + "ct_nsort": 'doğal sıralama (başında sayı bulunan isimler için)">dsort', + "ct_utc": 'tüm zaman damgalarını UTC diliminde göster">UTC', + "ct_readme": 'README.md\'yi klasör listelemelerinde göster">📜 readme', + "ct_idxh": 'index.html\'i klasör listelemeleri yerine göster">htm', + "ct_sbars": 'kaydırma çubuklarını göster">⟊', + + "cut_umod": "eğer bir dosya sunucuda mevcutsa sunucudaki dosyanın son-değiştirilme zaman damgasını yereldekiyle değiştir (yazma+silme izinlerini gerektirir)\">re📅", + + "cut_turbo": "yolo butonu, bunu muhtemelen etkinleştirmek itemezsiniz:$N$Nyalnızca aynı anda çok fazla sayıda dosya yüklerken harhangi bir sebepten yüklemeyi durdurup anında yeniden başlatmanız gerekirse kullanın$N$Nbu, hash fonksiyonunu basit bir <em> ile değiştirir"sunucudaki boyutu da aynı mı?"</em> dosya içerikleri farklıysa yükleme devam ETMEYECEKTİR!$N$Nyükleme tamamlandığında bu ayarı kapatın ve sonra aynı dosyaları yeniden "yüklemeyi" deneyin ki sunucu bu dosyaları doğrulayabilsin\">turbo", + + "cut_datechk": "turbo modu açıksa hiçbir etkisi yoktur$N$Nyolo faktörünü az bir miktar düşürür; sunucudaki dosya zaman damgalarının sizinkiyle eşleşip eşleşmediğini kontrol eder$N$Nteorik olarak tamamlanmamış / bozuk yüklemelerin çoğunu yakalaması gerekir ancak turbo devre dışı bırakıldıktan sonra yapılan doğrulama geçişinin yerini tutamaz\">date-chk", + + "cut_u2sz": "her yükleme parçasının boyutu (MiB cinsinden); büyük değerler Atlantik boyunca daha iyi iletilir. Çok güvenilmez bağlantılarda düşük değerler deneyin", + + "cut_flag": "aynı anda sadece bir sekmenin yükleme yapabilmesini sağlayın $N -- diğer sekmelerde de bu ayar açık olmalıdır $N -- sadece aynı alan adı altındaki sekmeleri etkiler", + + "cut_az": "dosyaları en küçüğünden başlamak yerine alfabetik sırayla yükleyin$N$Nalefabetik sıralama, sunucuda bir şeylerin yanlış gidip gitmediğini gözlemlemenizi daha kolay hale getirebilir ancak fiber / LAN üzerinde yüklemeyi biraz yavaşlatır", + + "cut_nag": "yükleme tamamlandığında işletim sistemi bildirimi$N(sadece tarayıcı veya serkme aktif değilse)", + "cut_sfx": "yükleme tamamlandığında sesli bildirim$N(sadece tarayıcı veya serkme aktif değilse)", + + "cut_mt": "dosya hash'lemesini hızlandırmak için çoklu izlekleri kullan$N$Nbu ayar web işlerini kullanır ve daha fazla$Nmore RAM (fazladan 512 MiB'a kadar) kullanır$N$https bağlantısını %30, http'yi ise 4.5 kat hızlandırır\">mt", + + "cut_wasm": "tarayıcının varsayılan hash fonksiyonu yerine WASM'ı kullan; Chrome tabanlı tarayıcılarda performansla birlikte CPU kullanımını artırır, ayrıca Chrome'un birçok eski sürümünde bu ayar etkinleştiğinde tarayıcının tüm RAM'i yemesine ve çökmesine sebep olan hatalar bulunur\">wasm", + + "cft_text": "favicon yazısı (devre dışı bırakmak için boş bırakıp yenileyin)", + "cft_fg": "ön plan rengi", + "cft_bg": "arka plan rengi", + + "cdt_lim": "bir klasörde gösterilecek maksimum dosya sayısı", + "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", + + "tt_entree": "navigasyon panosunu göster (yan dizin panosu)$NHotkey: B", + "tt_detree": "içerik haritasını göster$Kısayol: B", + "tt_visdir": "seçili klasöre kaydır", + "tt_ftree": "klasör ağacını / metin dosyalarını aç/kapat$Kısayol: V", + "tt_pdock": "üstteki bir pencerede üst klasörleri göster", + "tt_dynt": "ağaç genişledikçe otomatik büyüt", + "tt_wrap": "kelime sarma", + "tt_hover": "fare ile üzerine gelindiğinde taşan satırları göster$N( fare imleci sol kenarda değilse kaydırmayı bozar )", + + "ml_pmode": "klasör sonunda...", + "ml_btns": "komutlar", + "ml_tcode": "dönüştür", + "ml_tcode2": "dönüştür", + "ml_tint": "tonlama", + "ml_eq": "ses eşitleyici", + "ml_drc": "dinamik aralık sıkıştırıcı", + + "mt_loop": "bir şarkıyı döngüye al / tekrar et\">🔁", + "mt_one": "bir şarkıdan sonra dur\">1️⃣", + "mt_shuf": "klasörlerdeki şarkıları karıştır\">🔀", + "mt_aplay": "sunucuya erişmek için kullandığın bağlantıda geçerli bir şarkı varsa otomatik oynat$N$Nbunu etkisiz kılmak aynı zamanda müzik oynatıldığında sayfa URL'nin değişmesini de engeller\">a▶", + "mt_preload": "aralıksız oynatma için sıradaki şarkıyı önceden yüklemeye başla\">ön yükleme", + "mt_prescan": "son şarkı bitmeden önce bir sonraki klasöre git$Nweb tarayıcısını mutlu tutar$Nbu nedenle oynatmayı durdurmaz\">nav", + "mt_fullpre": "tüm şarkıyı ön yüklemeye çalış;$N✅ <b>güvenilmez</b> bağlantılarda etkinleştir,$N❌ <b>yavaş</b> bağlantılarda devre dışı bırak\">full", + "mt_fau": "telefonlarda, bir sonraki şarkı yeterince hızlı ön yüklenmezse müziğin durmasını önle (etiketlerin bozuk görünmesine neden olabilir)\">☕️", + "mt_waves": "dalga formu kaydırıcı:$Nses genliğini kaydırıcıda göster\">~s", + "mt_npclip": "şu anda çalan şarkıyı kopyalamak için düğmeleri göster\">/np", + "mt_m3u_c": "seçilen şarkıları m3u8 çalma listesi girişleri olarak kopyalamak için düğmeleri göster\">📻", + "mt_octl": "os entegrasyonu (medya kısayolları / osd)\">os-ctl", + "mt_oseek": "OS entegrasyonuyla şarkı ilerletmeye izin ver$N$Nnot: bazı cihazlarda (iPhone'lar)$Nbu, sıradaki şarkı düğmesini değiştirir\">ilerletme", + "mt_oscv": "ekranda albüm kapaklarını göster\">görsel", + "mt_follow": "oynatılan müzik ibaresini görünümde tut\">🎯", + "mt_compact": "kompakt kontroller\">⟎", + "mt_uncache": "önbelleği temizle  (bunu, tarayıcınızın bozuk bir şarkı kopyasını önbelleğe alması nedeniyle çalmayı reddettiğinde deneyin)\">önbelleği temizle", + "mt_mloop": "açık klasörü döngüye al\">🔁 döngü", + "mt_mnext": "bir sonraki klasörü yükle ve devam et\">📂 sonraki", + "mt_mstop": "oynatmayı durdur\">⏸ durdur", + "mt_cflac": "flac / wav'ı {0}'a dönüştür\">flac", + "mt_caac": "aac / m4a'yı {0}'a dönüştür\">aac", + "mt_coth": "diğer tüm formatları (mp3 hariç) {0}'a dönüştür\">oth", + "mt_c2opus": "masaüstü, dizüstü bilgisayarlar, android için en iyi seçim\">opus", + "mt_c2owa": "opus-weba, iOS 17.5 ve üzeri için\">owa", + "mt_c2caf": "opus-caf, iOS 11 ile 17 arasında için\">caf", + "mt_c2mp3": "çok eski cihazlarda bunu kullanın\">mp3", + "mt_c2flac": "en iyi ses kalitesi, ancak büyük indirmeler\">flac", + "mt_c2wav": "sıkıştırılmamış oynatma (daha da büyük)\">wav", + "mt_c2ok": "güzel, iyi seçim", + "mt_c2nd": "bu, cihazınız için önerilen çıkış formatı değil, ama sorun değil", + "mt_c2ng": "cihazınız bu çıkış formatını desteklemiyor gibi görünüyor, ama yine de deneyelim", + "mt_xowa": "iOS'ta bu formatta arka plan oynatımını engelleyen hatalar var; lütfen bunun yerine caf veya mp3 kullanın", + "mt_tint": "seekbar'da arka plan seviyesi (0-100)$ön belleğin daha az dikkat dağıtıcı olmasını sağlar", + "mt_eq": "ekolayzer ve kazanç kontrolünü aktifleştirir;$N$Nboost <code>0</code> = standart %100 ses (varsayılan)$N$Ngenişlik <code>1  </code> = standart çift kanal (varsayılan)$Ngenişlik <code>0.5</code> = %50 sol-sağ crossfeed$Ngenişlik <code>0  </code> = mono$N$Nartış <code>-0.8</code> & artış <code>10</code> = vokal kaldırma :^)$N$Nekolayzeri aktifleştirmek, aralıksız albümleri gerçekten aralıksız yapar, bu yüzden tüm değerleri 0'da bırakın (width = 1 hariç) tabii bunu umursuyorsnaız", + "mt_drc": "dinamik aralık sıkıştırıcıyı (ses düzleştiriciyi) aktifleştirir; spagettiyi dengelemek için aynı zamanda EQ'yu da açar, bunun olmasını istemiyorsanız EQ'daki 'genişlik' hariç tüm alanları 0 yapın$N$Nsınır dB'inin üstündeki tüm sesleri kısar; sınırı geçen her 1 dB için ancak 1 dB ses verilir, yani varsayılan eşik -24 ve oran 12 değerleri sesin -22 dB'den yükseğe çıkmayacağını ve EQ artışının 0,8'e, hatta ATK 0 ve hayvan gibi RLS (örneğin 90, bu sadece Firefox'ta çalışır; diğer tarayıcılarda RLS en fazla 1'dir) gibi değerlerle 1.,8'e kadar güvenle çıkabileceğini gösteriyor$N$N(git vikipediye bak, orada daha iyi açıklanıyor)", + + "mb_play": "oynat", + "mm_hashplay": "bu ses dosyası oynatılsın mı?", + "mm_m3u": "Oynatmak için <code>Enter/Tamam</code> tuşuna basın\nDüzenlemek için <code>ESC/İptal</code> tuşuna basın", + "mp_breq": "firefox 82+ veya chrome 73+ veya iOS 15+ gerektirir", + "mm_bload": "şu anda yükleniyor...", + "mm_bconv": "{0} formatına dönüştürülüyor, lütfen bekleyin...", + "mm_opusen": "tarayıcınız aac / m4a dosyalarını oynatamıyor;\nopus'a dönüştürme etkinleştirildi", + "mm_playerr": "oynatma hatası: ", + "mm_eabrt": "Oynatma denemesi iptal edildi", + "mm_enet": "İnternet bağlantınızda bir bokluk var", + "mm_edec": "Bu dosya muhtemelen bozuk!", + "mm_esupp": "Tarayıcınız salak, bu ses formatını anlamıyor", + "mm_eunk": "Bilinmeyen Hata", + "mm_e404": "Ses oynatılamadı; hata 404: Dosya bulunamadı.", + "mm_e403": "Ses oynatılamadı; hata 403: Erişim reddedildi.\n\nYeniden yüklemek için F5 tuşuna basın, oturumunuz kapanmış olabilir.", + "mm_e500": "Ses oynatılamadı; hata 500: Sunucu günlüklerini kontrol edin.", + "mm_e5xx": "Ses oynatılamadı; sunucu hatası ", + "mm_nof": "yakınlarda başka ses dosyası bulunamadı", + "mm_prescan": "Sonraki şarkı aranıyor...", + "mm_scank": "Sonraki şarkı bulundu:", + "mm_uncache": "önbellek temizlendi; tüm şarkılar bir sonraki çalınmada yeniden indirilecek", + "mm_hnf": "bu şarkı artık mevcut değil", + + "im_hnf": "bu resim artık mevcut değil", + + "f_empty": 'bu klasör boş', + "f_chide": 'bu, «{0}» sütununu gizleyecektir\n\nsütunları ayarlar sekmesinden yeniden açabilirsiniz', + "f_bigtxt": "bu dosya {0} MiB boyutunda -- Cidden saf yazı olarak mı görüntülemek istiyo'n?", + "f_bigtxt2": "dosyanın sadece sonunu görüntülemek ister misin? bu, takip etme/sonlandırma işlemini de etkinleştirecek ve gerçek zamanlı olarak yeni eklenen metin satırlarını gösterecektir.", + "fbd_more": '<div id="blazy"><code>{1}</code> dosyadan <code>{0}</code> tanesi gösteriliyor; <a href="#" id="bd_more">{2}</a> tanesini ya da <a href="#" id="bd_all">tümünü göster</a></div>', + "fbd_all": '<div id="blazy"><code>{1}</code> dosyadan <code>{0}</code> tanesi gösteriliyor; <a href="#" id="bd_all">tümünü göster</a></div>', + "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_partial": "Mevcutta yüklenen bir dosyayı güvenli bir şekilde indirmek için lütfen aynı adlı ama <code>.PARTIAL</code> 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 <code>.PARTIAL</code> geçici dosyasını indirmeye devam edecektir ki bu da elinize bozuk veriler sunacaktır.", + + "ft_paste": "{0} ögeyi yapıştır$Kısayol: ctrl-V", + "fr_eperm": 'yeniden adlandırılamıyor:\nbu klasörü “taşıma” izniniz yok', + "fd_eperm": 'silinemiyor:\nbu klasörde “silme” izniniz yok', + "fc_eperm": 'kesilemiyor:\nbu klasörde “taşıma“ izniniz yok', + "fp_eperm": 'yapıştırılamıyor:\nbu klasörde “yazma“ izniniz yok', + "fr_emore": "yeniden adlandırmak için en az bir öge seçin", + "fd_emore": "silmek için en az bir öge seçin", + "fc_emore": "kesmek için en az bir öge seçin", + "fcp_emore": "panoya kopyalamak için en az bir öge seçin", + + "fs_sc": "bulunduğunuz klasörü paylaşın", + "fs_ss": "seçilen dosyaları paylaşın", + "fs_just1d": "birden fazla klasör seçemezsiniz\nveya bir seçimde dosyaları ve klasörleri karıştıramazsınız", + "fs_abrt": "❌ iptal", + "fs_rand": "🎲 rastgele.ad", + "fs_go": "✅ paylaşımı oluştur", + "fs_name": "isim", + "fs_src": "kaynak", + "fs_pwd": "şifre", + "fs_exp": "son kullanma", + "fs_tmin": "dakika", + "fs_thrs": "saat", + "fs_tdays": "gün", + "fs_never": "sonsuz", + "fs_pname": "isteğe bağlı bağlantı adı; boşsa rastgele olacaktır", + "fs_tsrc": "paylaşılacak dosya veya klasör", + "fs_ppwd": "isteğe bağlı şifre", + "fs_w8": "paylaşım oluşturuluyor...", + "fs_ok": "panoya kopyalamak için <code>Enter/Tamam</code> tuşuna basın\nkapatmak için <code>ESC/İptal</code> tuşuna basın", + + "frt_dec": "bazı bozuk dosya adlarını düzeltebilir\">url-decode", + "frt_rst": "değiştirilen dosya adlarını orijinal haline döndür\">↺ sıfırla", + "frt_abrt": "iptal et ve bu pencereyi kapat\">❌ iptal", + "frb_apply": "YENİ ADI UYGULA", + "fr_adv": "toplu / meta veri / desen yeniden adlandırma\">gelişmiş", + "fr_case": "büyük/küçük harf duyarlı regex\">büyük/küçük harf", + "fr_win": "windows'a uygun adlar; <code><>:"\\|?*</code> karakterlerini Japonca tam genişlik karakterleriyle değiştir\">win", + "fr_slash": "<code>/</code> karakterini yeni klasörlerin oluşturulmasına neden olmayan bir karakterle değiştir\">/ yok", + "fr_re": "orijinal dosya adlarına uygulanacak regex arama deseni; yakalama grupları aşağıdaki format alanında <code>(1)</code> ve <code>(2)</code> gibi belirtilebilir", + "fr_fmt": "foobar2000'den esinlenilmiştir:$N<code>(title)</code> şarkı başlığıyla değiştirilir,$N<code>[(artist) - ](title)</code> sanatçı boşsa [bu] kısmı atlar$N<code>$lpad((tn),2,0)</code> parça numarasını 2 basamağa doldurur", + "fr_pdel": "sil", + "fr_pnew": "farklı kaydet", + "fr_pname": "yeni ayarınızı adlandırın", + "fr_aborted": "iptal edildi", + "fr_lold": "eski ad", + "fr_lnew": "yeni ad", + "fr_tags": "seçilen dosyalar için etiketler (salt okunur, sadece referans için):", + "fr_busy": "{0} öğe yeniden adlandırılıyor...\n\n{1}", + "fr_efail": "yeniden adlandırma başarısız:\n", + "fr_nchg": "{0} yeni adlardan bazıları <code>win</code> ve/veya <code>/ yok</code> nedeniyle değiştirildi\n\nBu değiştirilmiş yeni adlarla devam etmek için Tamam'a basın.", + + "fd_ok": "silme tamam", + "fd_err": "silme başarısız:\n", + "fd_none": "hiçbir şey silinmedi; belki sunucu yapılandırması (xbd) tarafından engellenmiştir?", + "fd_busy": "{0} öğe siliniyor...\n\n{1}", + "fd_warn1": "Bu {0} öge silinsin mi?", + "fd_warn2": "<b>Son şans!</b> Geri alma imkanın yok. Silinsin mi?", + + "fc_ok": "{0} öge kesildi", + "fc_warn": '{0} öge kesildi\n\namma velakin: sadece <b>bu</b> tarayıcı penceresine yapıştırılabilirler\n(çünkü seçiminin boyutu hayvan gibi)', + + "fcc_ok": "{0} öge panoya kopyalandı", + "fcc_warn": '{0} öge panoya kopyalandı\n\namma velakin: sadece <b>bu</b> tarayıcı penceresine yapıştırılabilirler\n(çünkü seçiminin boyutu hayvan gibi)', + + "fp_apply": "bu adları kullan", + "fp_ecut": "önce bazı dosyaları / klasörleri kes veya kopyala, sonra yapıştır / taşı\n\nnot: farklı tarayıcı sekmeleri arasında kes / yapıştır yapabilirsiniz", + "fp_ename": "{0} öğe buraya taşınamaz çünkü adlar zaten alınmış. Devam etmek için aşağıda yeni adlar verin veya atlamak için adları boş bırakın:", + "fcp_ename": "{0} öğe buraya kopyalanamaz çünkü adlar zaten alınmış. Devam etmek için aşağıda yeni adlar verin veya atlamak için adları boş bırakın:", + "fp_emore": "hâlâ düzeltilmesi gereken bazı dosya adı çakışmaları var", + "fp_ok": "taşıma tamam", + "fcp_ok": "kopyalama tamam", + "fp_busy": "{0} öğe taşınıyor...\n\n{1}", + "fcp_busy": "{0} öğe kopyalanıyor...\n\n{1}", + "fp_abrt": "İptal ediliyor...", + "fp_err": "taşıma başarısız:\n", + "fcp_err": "kopyalama başarısız:\n", + "fp_confirm": "bu {0} öğeyi buraya taşımak istiyor musunuz?", + "fcp_confirm": "bu {0} öğeyi buraya kopyalamak istiyor musunuz?", + "fp_etab": 'diğer tarayıcı sekmesinden pano okunamadı', + "fp_name": "cihazınızdan bir dosya yüklüyorsunuz. Bir ad verin:", + "fp_both_m": '<h6>yapıştırılacak şeyi seçin</h6><code>Enter</code> = «{1}» içinden {0} dosyayı taşı\n<code>ESC</code> = cihazınızdan {2} dosyayı yükle', + "fcp_both_m": '<h6>yapıştırılacak şeyi seçin</h6><code>Enter</code> = «{1}» içinden {0} dosyayı kopyala\n<code>ESC</code> = cihazınızdan {2} dosyayı yükle', + "fp_both_b": '<a href="#" id="modal-ok">Taşı</a><a href="#" id="modal-ng">Yükle</a>', + "fcp_both_b": '<a href="#" id="modal-ok">Kopyala</a><a href="#" id="modal-ng">Yükle</a>', + + "mk_noname": "bunu yapmadan önce soldaki boşluğa bir şeyler yazsana :p", + + "tv_load": "Metin belgesi yükleniyor:\n\n{0}\n\n{1}% ({2} of {3} MiB yüklendi)", + "tv_xe1": "metin dosyası yüklenemedi:\n\nhata ", + "tv_xe2": "404, dosya bulunamadı", + "tv_lst": "metin dosyalarının listesi", + "tvt_close": "klasör görünümüne dön$NKısayol: M (veya Esc)\">❌ kapat", + "tvt_dl": "bu dosyayı indir$NKısayol: Y\">💾 indir", + "tvt_prev": "önceki belgeyi göster$NKısayol: i\">⬆ önceki", + "tvt_next": "sonraki belgeyi göster$NKısayol: K\">⬇ sonraki", + "tvt_sel": "dosyayı seç$NKısayol: S\">seç", + "tvt_edit": "dosyayı metin düzenleyicisinde aç$NKısayol: E\">✏️ düzenle", + "tvt_tail": "dosyalardaki değişiklikleri izle; yeni satırları gerçek zamanlı göster\">📡 takip", + "tvt_wrap": "kelime sarma\">↵", + "tvt_atail": "sayfanın altına sabitle\">⚓", + "tvt_ctail": "terminal renklerini çöz (ansi kaçış kodları)\">🌈", + "tvt_ntail": "önbellek limiti (kaç byte yüklenmiş metin tutulacağı)", + + "m3u_add1": "şarkı m3u çalma listesine eklendi", + "m3u_addn": "{0} şarkı m3u çalma listesine eklendi", + "m3u_clip": "m3u çalma listesi şimdi panoya kopyalandı\n\nyeni bir metin dosyası oluşturmalı ve adını dosya.m3u koymalısınız ve çalma listesini o belgeye yapıştırmalısınız; bu sayede oynatılabilir hale gelecektir", + + "gt_vau": "videoları gösterme, sadece sesi oynat\">🎧", + "gt_msel": "dosya seçimlerini etkinleştir; bir dosyayı geçersiz kılmak için ctrl-tıkla$N$N<em>etkin olduğunda: bir dosyayı / klasörü açmak için çift tıkla</em>$N$NKısayol: S\">çoklu seçim", + "gt_crop": "küçültülmüş önizlemeleri ortala\">kırp", + "gt_3x": "yüksek çözünürlüklü önizlemeler\">3x", + "gt_zoom": "yakınlaştır", + "gt_chop": "kes", + "gt_sort": "sırala", + "gt_name": "isim", + "gt_sz": "boyut", + "gt_ts": "tarih", + "gt_ext": "tip", + "gt_c1": "dosya adlarını daha fazla kısalt (daha az göster)", + "gt_c2": "dosya adlarını daha az kısalt (daha fazla göster)", + + "sm_w8": "aranıyor...", + "sm_prev": "aşağıdaki arama sonuçları önceki bir sorgudan alınmıştır:\n ", + "sl_close": "arama sonuçlarını kapat", + "sl_hits": "gösterilen {0} sonuç", + "sl_moar": "daha fazla yükle", + + "s_sz": "boyut", + "s_dt": "tarih", + "s_rd": "yol", + "s_fn": "isim", + "s_ta": "etiketler", + "s_ua": "yükleme@", + "s_ad": "gel.", + "s_s1": "en az MiB", + "s_s2": "en fazla MiB", + "s_d1": "en az iso8601", + "s_d2": "en fazla. iso8601", + "s_u1": "yüklendi", + "s_u2": "veya önce", + "s_r1": "dosya yolu   içerir; (boşlukla ayrılmış)", + "s_f1": "isim   içerir; (-nope kullan)", + "s_t1": "etiketler   içerir; (^=baş, son=$)", + "s_a1": "özel metadata özellikleri", + + "md_eshow": "görüntülenemiyor: ", + "md_off": "[📜<em>beni-oku</em>], [⚙️] içinde kapalıdır -- belge gizli", + + "badreply": "Sunucudan gelen yanıt çözümlenemedi", + + "xhr403": "403: Erişim reddedildi\n\nF5'e basmayı deneyin, oturumunuz kapanmış olabilir", + "xhr0": "bilinmiyor (muhtemelen sunucuya bağlantı kaybedildi veya sunucu çevrimdışı)", + "cf_ok": "pardon yahu -- DD" + wah + "oS koruması şey etti\n\n30 saniyeye düzelmiş olurum\n\nbi' halt olmazsa f5'e basarak sayfayı yenile", + "tl_xe1": "alt klasörler listelenemiyor:\n\nhata ", + "tl_xe2": "404: klasör bulunamadı", + "fl_xe1": "dosyalar listelenemiyor:\n\nhata ", + "fl_xe2": "404: klasör bulunamadı", + "fd_xe1": "alt klasör oluşturulamadı:\n\nhata ", + "fd_xe2": "404: Üst klasör bulunamadı", + "fsm_xe1": "mesaj gönderilemedi:\n\nhata ", + "fsm_xe2": "404: Üst klasör bulunamadı", + "fu_xe1": "sunucudan unpost(?) listesini yüklemek başarısız oldu:\n\nhata ", + "fu_xe2": "404: Dosya bulunamadı!!", + + "fz_tar": "sıkıştırılmamış gnu-tar dosyası (linux / mac)", + "fz_pax": "sıkıştırılmamış pax-format tar (daha yavaş)", + "fz_targz": "gnu-tar ile gzip seviye 3 sıkıştırma$N$Nbu genellikle çok yavaştır, bu yüzden$Nsıkıştırılmamış tar kullanın", + "fz_tarxz": "gnu-tar ile xz seviye 1 sıkıştırma$N$Nbu genellikle çok yavaştır, bu yüzden$Nsıkıştırılmamış tar kullanın", + "fz_zip8": "utf8 dosya adları ile zip (windows 7 ve daha eski sürümlerde sorunlu olabilir)", + "fz_zipd": "gerçekten eski yazılımlar için geleneksel cp437 dosya adları ile zip", + "fz_zipc": "erken hesaplanan crc32 ile cp437,$NMS-DOS PKZIP v2.04g (ekim 1993) için$N(indirmenin başlamasından önce işlenmesi daha uzun sürer)", + + "un_m1": "aşağıdan son yüklemelerinizi (veya tamamlanmamış olanları) silebilirsiniz", + "un_upd": "yenile", + "un_m4": "veya aşağıda görünen dosyaları paylaşın:", + "un_ulist": "göster", + "un_ucopy": "kopyala", + "un_flt": "isteğe bağlı filtre:  URL şunu içermelidir:", + "un_fclr": "filtreyi temizle", + "un_derr": 'unpost-delete failed:\n', + "un_f5": 'bir şeyler bozuldu, lütfen yenilemeyi deneyin veya F5 tuşuna basın', + "un_uf5": "üzgünüm ama bu yükleme iptal edilmeden önce sayfayı yenilemeniz gerekiyor (F5 veya CTRL-R tuşlarına basarak)", + "un_nou": '<b>uyarı:</b> sunucu tamamlanmamış yüklemeleri göstermek için çok meşgul; birazdan "yenile" bağlantısına tıklayın', + "un_noc": '<b>uyarı:</b> tamamen yüklenmiş dosyaların unpost edilmesi sunucu yapılandırmasında etkinleştirilmemiştir/izin verilmemiştir', + "un_max": "ilk 2000 dosya gösteriliyor (filtreyi kullan)", + "un_avail": "son yüklemelerin {0} tanesi silinebilir<br />tamamlanmayanların {1} tanesi iptal edilebilir", + "un_m2": "yükleme zamanına göre sıralandı; en son önce:", + "un_no1": "Hayda! yeterince güncel yükleme yok", + "un_no2": "Hayda! o filtreye uyan yeterince güncel yükleme yok", + "un_next": "aşağıdaki {0} dosyayı sil", + "un_abrt": "iptal", + "un_del": "sil", + "un_m3": "son yüklemelerinizi hazırlanıyor...", + "un_busy": "{0} dosya siliniyor...", + "un_clip": "{0} bağlantı panoya kopyalandı", + + "u_https1": "daha iyi performans", + "u_https2": "için https'i", + "u_https3": "kullanın", + "u_ancient": 'tarayıcınız resmen fosilleşmiş -- belki de <a href="#" onclick="goto(\'bup\')">bup kullanmalısınız</a>', + "u_nowork": "firefox 53+ veya chrome 57+ veya iOS 11+ gerekiyor", + "tail_2old": "firefox 105+ veya chrome 71+ veya iOS 14.5+ gerekiyor", + "u_nodrop": 'tarayıcınız sürükleyip bırakmak için çok eski', + "u_notdir": "bu bir klasör değil!\n\ntarayıcınız çok eski,\nlütfen bunun yerine sürükleyip bırakmayı deneyin", + "u_uri": "başka tarayıcı pencerelerinden sürükle-bırak ile yükleme yapmak için,\nlütfen büyük yükleme düğmesinin üstüne bırakın", + "u_enpot": '<a href="#">Patates arayüze</a> geçiş yap (yükleme hızını artırabilir)', + "u_depot": '<a href="#">Havalı arayüze</a> geçiş yap (yükleme hızını azaltabilir)', + "u_gotpot": 'yükleme hızını artırmak için patates arayüzüne geçiliyor,\n\nkatılmıyorsanız geri dönmekte özgürsünüz!', + "u_pott": "<p>dosyalar:   <b>{0}</b> tamamlandı,   <b>{1}</b> başarısız,   <b>{2}</b> meşgul,   <b>{3}</b> sırada bekliyor</p>", + "u_ever": "bu temel yükleyici; up2k en az<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1 gerektirir", + "u_su2k": 'bu temel yükleyici; <a href="#" id="u2yea">up2k</a> daha iyidir', + "u_uput": 'hız için optimize et (kontrol toplamını atla)', + "u_ewrite": 'bu klasöre yazma izniniz yok', + "u_eread": 'bu klasörü okuma izniniz yok', + "u_enoi": 'dosya arama sunucu yapılandırmasında etkin değil', + "u_enoow": "üstüne yazma burada çalışmayacak; Silme izni gerekiyor", + "u_badf": 'Bu {0} dosya (toplam {1} arasında) atlandı, muhtemelen dosya sistemi izinleri nedeniyle:\n\n', + "u_blankf": 'Bu {0} dosya (toplam {1} arasında) boş / boş; yine de yüklemek ister misiniz?\n\n', + "u_applef": 'Bu {0} dosya (toplam {1} arasında) muhtemelen istenmiyor;\nAşağıdaki dosyaları ATLAMAK için <code>TAMAM/Enter</code> tuşuna basın,\nyüklemek için <code>İptal/ESC</code> tuşuna basın\n\n', + "u_just1": '\nBelki sadece bir dosya seçerseniz daha iyi çalışır', + "u_ff_many": "eğer <b>Linux / MacOS / Android</b> kullanıyorsanız bu kadar çok dosya yüklemenin <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>Firefox'u</em> çökertme ihtimali var!</a>\nEğer çökerse lütfen tekrar deneyin (veya Chrome kullanın).", + "u_up_life": "Bu yükleme, tamamlandıktan {0} süre sonra sunucudan silinecek\n", + "u_asku": 'bu {0} dosya <code>{1}</code> içerisine yükleniyor', + "u_unpt": "bu yüklemeyi sol üstteki 🧯 ile geri alabilir / silebilirsiniz", + "u_bigtab": '{0} dosya görüntülenmek üzere\n\nBu kadar fazlası tarayıcınızı çökertebilir, emin misiniz?', + "u_scan": 'Dsyalar tarıyor...', + "u_dirstuck": 'dizin yineleyicisi aşağıdaki {0} öğeye erişmeye çalışırken takıldı; atlanacak:', + "u_etadone": 'Tamamlandı ({0}, {1} dosya)', + "u_etaprep": '(yüklemeye hazırlanıyor)', + "u_hashdone": 'hash\'leme tamamlandı', + "u_hashing": 'hash\'leniyor', + "u_hs": 'el sıkılıyor...', + "u_started": "dosyalar şu anda yükleniyor; [🚀]'i gör", + "u_dupdefer": "başka bir dosyanın kopyası; diğer tüm dosyalardan sonra işlenecek", + "u_actx": "diğer pencereler/sekmeler arasında geçiş yaparken <br /> performansın düşmemesi için bu yazıya tıklayın", + "u_fixed": "Timam!  Hallettim 👍", + "u_cuerr": "parça {0} / {1} yüklenemedi;\nmuhtemelen zararsız, devam ediliyor\n\ndosya: {2}", + "u_cuerr2": "sunucu yüklemeyi reddetti (parça {0} / {1});\nsonra tekrar denenecek\n\ndosya: {2}\n\nhata ", + "u_ehstmp": "tekrar denenecek; sağ alt köşeye mak", + "u_ehsfin": "sunucu yüklemeyi tamamlama isteğini reddetti; tekrar deniyor...", + "u_ehssrch": "sunucu arama yapma isteğini reddetti; tekrar deniyor...", + "u_ehsinit": "sunucu yüklemeyi başlatma isteğini reddetti; tekrar deniyor...", + "u_eneths": "yükleme el sıkışması sırasında ağ hatası; tekrar deniyor...", + "u_enethd": "hedef varlığını test ederken ağ hatası; tekrar deniyor...", + "u_cbusy": "ağ kesintisinden sonra sunucunun bize tekrar güvenmesini bekliyoruz...", + "u_ehsdf": "sunucuda disk alanı kalmadı!\n\ndevam edebilmek için birinin\nyeterli alan açmasını bekleyeceğiz", + "u_emtleak1": "görünüşe göre web tarayıcınızda bir bellek sızıntısı var;\nlütfen", + "u_emtleak2": ' <a href="{0}">https\'ye geçin (önerilir)</a> veya ', + "u_emtleak3": ' ', + "u_emtleakc": 'sırayla şunları deneyin:\n<ul><li>sayfayı yenilemek için <code>F5</code>\'e basın</li><li>sonra  <code>mt</code>  düğmesini  <code>⚙️ ayarlar</code> menüsünde devre dışı bırakın</li><li>ve o yüklemeyi tekrar deneyin</li></ul>Yüklemeler biraz daha yavaş olacaktır, ama salla gitsin.\nSorun için özür dileriz!\n\nDipnot: chrome v107 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1354816" target="_blank">için bir hata düzeltmesi</a>', + "u_emtleakf": 'şunları deneyin:\n<ul><li>sayfayı yenilemek için <code>F5</code>\'e basın</li><li>sonra yükleme arayüzünde <code>🥔</code> (patates) seçeneğini etkinleştirin<li>ve yüklemeyi tekrar deneyin</li></ul>\nDipnot: firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1790500" target="_blank">için muhtemel bir hata düzeltmesi</a>', + "u_s404": "sunucuda bulunamadı", + "u_expl": "açıklama", + "u_maxconn": "çoğu tarayıcı bunu 6 ile sınırlar, ancak firefox bunu <code>about:config</code> içinde <code>connections-per-server</code> ile artırmanıza izin verir", + "u_tu": '<p class="warn">UYARI: turbo etkin, <span> istemci tamamlanmamış yüklemeleri algılayamayabilir ve devam ettiremeyebilir; turbo düğmesinden ipuçlarına bakabilirsiniz</span></p>', + "u_ts": '<p class="warn">UYARI: turbo etkin, <span> arama sonuçları yanlış olabilir; turbo düğmesi ipuçlarına bakın</span></p>', + "u_turbo_c": "turbo sunucu yapılandırmasında devre dışı bırakıldı", + "u_turbo_g": "turbo devre dışı bırakılıyor çünkü bu alanda\ndizin listeleme ayrıcalıklarınız yok", + "u_life_cfg": 'yüklemeleri <input id="lifem" p="60" /> dk (veya <input id="lifeh" p="3600" /> saat) sonra otomatik olarak sil', + "u_life_est": 'yükleme <span id="lifew" tt="local time">---</span> sonra silinecek', + "u_life_max": 'bu klasör bir\nmaksimum ömür {0} uygular', + "u_unp_ok": 'unpost {0} için izin verildi', + "u_unp_ng": 'unpost izin verilmeyecek', + "ue_ro": 'bu klasöre erişiminiz Salt Okuma\n\n', + "ue_nl": 'şu anda oturum açmamışsınız', + "ue_la": 'şu anda "{0}" olarak oturum açmışsınız', + "ue_sr": 'şu anda dosya arama modundasınız\n\nbüyüteç simgesine tıklayarak yükleme moduna geçin 🔎 (büyük ARAMA düğmesinin yanındaki) ve tekrar yüklemeyi deneyin\n\nyeter be', + "ue_ta": 'tekrar yüklemeyi deneyin, artık çalışsana be', + "ue_ab": "bu dosya zaten başka bir klasöre yükleniyor ve o yükleme tamamlanmadan dosya başka bir yere yüklenemez.\n\nİlk yüklemeyi iptal edip unutmak için sol üstteki 🧯 simgesini kullanabilirsiniz.", + "ur_1uo": "OK: Dosya başarıyla yüklendi", + "ur_auo": "OK: {0} dosyanın tamamı başarıyla yüklendi", + "ur_1so": "OK: Dosya sunucuda bulundu", + "ur_aso": "OK: {0} dosyanın tamamı sunucuda bulundu", + "ur_1un": "Yükleme başarısız oldu, üzgünüm", + "ur_aun": "{0} yüklemenin tamamı başarısız oldu, üzgünüm", + "ur_1sn": "Dosya sunucuda bulunamadı", + "ur_asn": "{0} dosyas sunucuda bulunamadı", + "ur_um": "Tamamlandı;\n{0} yükleme başarılı,\n{1} yükleme başarısız oldu, üzgünüm", + "ur_sm": "Tamamlandı;\n{0} dosya sunucuda bulundu,\n{1} dosya sunucuda bulunamadı", + + "lang_set": "Değişikliklerin etki göstermesi için sayfa yenilensin mi?", + }, "ukr": { "tt": "Українська", @@ -4502,6 +12111,7 @@ var Ls = { "gou": 'батьківська папка">вгору', "gon": 'наступна папка">далі', "logout": "Вийти ", + "login": "увійти", //m "access": " доступ", "ot_close": "закрити підменю", "ot_search": "пошук файлів за атрибутами, шляхом / іменем, музичними тегами, або будь-якою комбінацією$N$N<code>foo bar</code> = має містити «foo» і «bar»,$N<code>foo -bar</code> = має містити «foo», але не «bar»,$N<code>^yana .opus$</code> = починатися з «yana» і бути файлом «opus»$N<code>"try unite"</code> = містити точно «try unite»$N$Nформат дати - iso-8601, наприклад$N<code>2009-12-31</code> або <code>2020-09-12 23:30:00</code>", @@ -4552,7 +12162,7 @@ var Ls = { "ut_srch": "не завантажувати, а перевірити, чи файли вже $N існують на сервері (сканує всі папки, які ви можете читати)", "ut_par": "призупинити завантаження, встановивши 0$N$Nзбільшіть, якщо ваше з'єднання повільне / висока затримка$N$Nзалишіть 1 в локальній мережі або якщо HDD сервера є вузьким місцем", "ul_btn": "перетягніть файли / папки<br> (або клацніть сюди)", - "ul_btnu": "З А В А Н Т А Ж И Т И", + "ul_btnu": "ЗАВАНТАЖИТИ", "ul_btns": "П О Ш У К", "ul_hash": "хеш", @@ -4688,13 +12298,15 @@ var Ls = { "mt_mloop": "зациклити відкриту папку\">🔁 loop", "mt_mnext": "завантажити наступну папку і продовжити\">📂 next", "mt_mstop": "зупинити відтворення\">⏸ stop", - "mt_cflac": "конвертувати flac / wav в opus\">flac", - "mt_caac": "конвертувати aac / m4a в opus\">aac", - "mt_coth": "конвертувати всі інші (не mp3) в opus\">oth", + "mt_cflac": "конвертувати flac / wav в {0}\">flac", + "mt_caac": "конвертувати aac / m4a в {0}\">aac", + "mt_coth": "конвертувати всі інші (не mp3) в {0}\">oth", "mt_c2opus": "найкращий вибір для робочих столів, ноутбуків, android\">opus", "mt_c2owa": "opus-weba, для iOS 17.5 і новіших\">owa", "mt_c2caf": "opus-caf, для iOS 11 до 17\">caf", "mt_c2mp3": "використовуйте це на дуже старих пристроях\">mp3", + "mt_c2flac": "найкраща якість звуку, але великі завантаження\">flac", //m + "mt_c2wav": "відтворення без стиснення (ще більше)\">wav", //m "mt_c2ok": "гарно, хороший вибір", "mt_c2nd": "це не рекомендований вихідний формат для вашого пристрою, але це нормально", "mt_c2ng": "ваш пристрій, здається, не підтримує цей вихідний формат, але давайте все одно спробуємо", @@ -4705,7 +12317,7 @@ var Ls = { "mb_play": "відтворити", "mm_hashplay": "відтворити цей аудіо файл?", - "mm_m3u": "натисніть <code>Enter/OK</code> для відтворення\nнатисніть <code>ESC/Cancel</code> для редагування", + "mm_m3u": "натисніть <code>Enter/Гаразд</code> для відтворення\nнатисніть <code>ESC/Скасувати</code> для редагування", "mp_breq": "потрібен firefox 82+ або chrome 73+ або iOS 15+", "mm_bload": "зараз завантажується...", "mm_bconv": "конвертується в {0}, будь ласка, зачекайте...", @@ -4738,7 +12350,7 @@ var Ls = { "f_dls": 'посилання на файли в поточній папці були\nзмінені на посилання для завантаження', - "f_partial": "Щоб безпечно завантажити файл, який зараз завантажується, будь ласка, клацніть на файл, який має таке саме ім'я, але без розширення <code>.PARTIAL</code>. Будь ласка, натисніть CANCEL або Escape, щоб зробити це.\n\nНатиснення OK / Enter проігнорує це попередження і продовжить завантаження <code>.PARTIAL</code> робочого файлу замість цього, що майже напевно дасть вам пошкоджені дані.", + "f_partial": "Щоб безпечно завантажити файл, який зараз завантажується, будь ласка, клацніть на файл, який має таке саме ім'я, але без розширення <code>.PARTIAL</code>. Будь ласка, натисніть Скасувати або Escape, щоб зробити це.\n\nНатиснення Гаразд / Enter проігнорує це попередження і продовжить завантаження <code>.PARTIAL</code> робочого файлу замість цього, що майже напевно дасть вам пошкоджені дані.", "ft_paste": "вставити {0} елементів$NГаряча клавіша: ctrl-V", "fr_eperm": 'не можу перейменувати:\nу вас немає дозволу “переміщення“ в цій папці', @@ -4768,7 +12380,7 @@ var Ls = { "fs_tsrc": "файл або папка для спільного доступу", "fs_ppwd": "необов'язковий пароль", "fs_w8": "створення спільного доступу...", - "fs_ok": "натисніть <code>Enter/OK</code> для копіювання до буфера\nнатисніть <code>ESC/Cancel</code> для закриття", + "fs_ok": "натисніть <code>Enter/Гаразд</code> для копіювання до буфера\nнатисніть <code>ESC/Скасувати</code> для закриття", "frt_dec": "може виправити деякі випадки пошкоджених імен файлів\">url-decode", "frt_rst": "скинути змінені імена файлів назад до оригінальних\">↺ reset", @@ -4813,6 +12425,7 @@ var Ls = { "fcp_ok": "копіювання OK", "fp_busy": "переміщення {0} елементів...\n\n{1}", "fcp_busy": "копіювання {0} елементів...\n\n{1}", + "fp_abrt": "переривання...", //m "fp_err": "переміщення невдале:\n", "fcp_err": "копіювання невдале:\n", "fp_confirm": "перемістити ці {0} елементи сюди?", @@ -4891,7 +12504,7 @@ var Ls = { "xhr403": "403: Доступ заборонено\n\nспробуйте натиснути F5, можливо ви вийшли з системи", "xhr0": "невідома (ймовірно втрачено з'єднання з сервером, або сервер офлайн)", - "cf_ok": "вибачте за це -- захист від DD" + "oS спрацював\n\nречі повинні відновитися приблизно через 30 сек\n\nякщо нічого не відбувається, натисніть F5 для перезавантаження сторінки", + "cf_ok": "вибачте за це -- захист від DD" + wah + "oS спрацював\n\nречі повинні відновитися приблизно через 30 сек\n\nякщо нічого не відбувається, натисніть F5 для перезавантаження сторінки", "tl_xe1": "не вдалося перелічити підпапки:\n\nпомилка ", "tl_xe2": "404: Папка не знайдена", "fl_xe1": "не вдалося перелічити файли в папці:\n\nпомилка ", @@ -4957,7 +12570,7 @@ var Ls = { "u_enoow": "перезапис не працюватиме тут; потрібен дозвіл на видалення", "u_badf": 'Ці {0} файли (з {1} загальних) були пропущені, можливо, через дозволи файлової системи:\n\n', "u_blankf": 'Ці {0} файли (з {1} загальних) порожні; все одно завантажити їх?\n\n', - "u_applef": 'Ці {0} файли (з {1} загальних), ймовірно, небажані;\nНатисніть <code>OK/Enter</code> щоб ПРОПУСТИТИ наступні файли,\nНатисніть <code>Cancel/ESC</code> щоб НЕ виключати, і ЗАВАНТАЖИТИ їх також:\n\n', + "u_applef": 'Ці {0} файли (з {1} загальних), ймовірно, небажані;\nНатисніть <code>Гаразд/Enter</code> щоб ПРОПУСТИТИ наступні файли,\nНатисніть <code>Скасувати/ESC</code> щоб НЕ виключати, і ЗАВАНТАЖИТИ їх також:\n\n', "u_just1": '\nМожливо, це спрацює краще, якщо ви виберете лише один файл', "u_ff_many": "якщо ви використовуєте <b>Linux / MacOS / Android,</b> то така кількість файлів <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>може</em> завісити Firefox!</a>\nякщо це станеться, будь ласка, спробуйте знову (або використовуйте Chrome).", "u_up_life": "Це завантаження буде видалено з сервера\n{0} після його завершення", @@ -5023,7 +12636,7 @@ var Ls = { }, }; -var LANGS = ["eng", "nor", "chi", "deu", "fin", "rus", "spa", "ukr"]; +var LANGS = ["eng", "nor", "chi", "cze", "deu", "epo", "fin", "fra", "grc", "ita", "kor", "nld", "nno", "pol", "por", "rus", "spa", "swe", "tur", "ukr"]; if (window.langmod) langmod(); @@ -5043,7 +12656,7 @@ for (var a = 0; a < LANGS.length; a++) { t2 = Ls[LANGS[i2]]; for (var k in t1) - if (!t2[k]) { + if (!t2[k] && !/^ht_.5$/.test(k)) { console.log("E missing TL", LANGS[i2], k); t2[k] = t1[k]; } @@ -5060,7 +12673,7 @@ modal.load(); ebi('ops').innerHTML = ( '<a href="#" id="opa_x" data-dest="" tt="' + L.ot_close + '">--</a>' + '<a href="#" id="opa_srch" data-perm="read" data-dep="idx" data-dest="search" tt="' + L.ot_search + '">🔎</a>' + - (have_del ? '<a href="#" id="opa_del" data-dest="unpost" tt="' + L.ot_unpost + '">🧯</a>' : '') + + (have_del ? '<a href="#" id="opa_del" data-perm="write" data-dest="unpost" tt="' + L.ot_unpost + '">🧯</a>' : '') + '<a href="#" id="opa_up" data-dest="up2k">🚀</a>' + '<a href="#" id="opa_bup" data-perm="write" data-dest="bup" tt="' + L.ot_bup + '">🎈</a>' + '<a href="#" id="opa_mkd" data-perm="write" data-dest="mkdir" tt="' + L.ot_mkdir + '">📂</a>' + @@ -5246,14 +12859,29 @@ ebi('op_cfg').innerHTML = ( ' </div>\n' + '</div>\n' + '<div>\n' + + ' <h3>' + L.cl_hfsz + '</h3>\n' + + ' <div><select id="fszfmt">\n' + + ' <option value="0">0 ┃ 1234567</option>\n' + + ' <option value="1">1 ┃ 1 234 567</option>\n' + + ' <option value="2">2- ┃ 1.18 M</option>\n' + + ' <option value="2c">2c ┃ 1.18 M</option>\n' + + ' <option value="3">3- ┃ 1.2 M</option>\n' + + ' <option value="3c">3c ┃ 1.2 M</option>\n' + + ' <option value="4">4- ┃ 1.18 MB</option>\n' + + ' <option value="4c">4c ┃ 1.18 MB</option>\n' + + ' <option value="5">5- ┃ 1.2 MB</option>\n' + + ' <option value="5c">5c ┃ 1.2 MB</option>\n' + + ' <option value="fuzzy">fuzzy</option>\n' + + ' </select></div>\n' + + '</div>\n' + + '<div>\n' + ' <h3>' + L.cl_themes + '</h3>\n' + - ' <div id="themes">\n' + + ' <div><select id="themes"></select></div>\n' + ' </div>\n' + '</div>\n' + '<div>\n' + ' <h3>' + L.cl_langs + '</h3>\n' + - ' <div id="langs">\n' + - ' </div>\n' + + ' <div><select id="langs"></select></div>\n' + '</div>\n' + (have_zip ? ( '<div><h3>' + L.cl_ziptype + '</h3><div id="arc_fmt"></div></div>\n' @@ -5300,7 +12928,7 @@ ebi('op_cfg').innerHTML = ( ' </td>\n' + ' </div>\n' + '</div>\n' + - '<div><h3>' + L.cl_keytype + '</h3><div id="key_notation"></div></div>\n' + + '<div><h3>' + L.cl_keytype + '</h3><div><select id="key_notation"></select></div></div>\n' + '<div><h3>' + L.cl_hiddenc + '  ' + (MOBILE ? '<a href="#" id="hcolsh">' + L.cl_hidec + '</a> / ' : '') + '<a href="#" id="hcolsr">' + L.cl_reset + '</a></h3><div id="hcols"></div></div>' ); @@ -5461,12 +13089,19 @@ var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext), hash0 = location.hash, sloc0 = '' + location, noih = /[?&]v\b/.exec(sloc0), + dbg_kbd = /[?&]dbgkbd\b/.exec(sloc0), + abrt_key = "", + can_shr = false, rtt = null, ldks = [], dks = {}, dk, mp; +if (location.pathname.indexOf('//') === 0) + hist_replace(location.pathname.replace(/^\/+/, '/')); + + if (window.og_fn) { hash0 = 1; hist_replace(vsplit(get_evpath())[0]); @@ -5548,6 +13183,8 @@ var mpl = (function () { '<a href="#" id="ac2owa" class="tgl btn" tt="' + L.mt_c2owa + '</a>' + '<a href="#" id="ac2caf" class="tgl btn" tt="' + L.mt_c2caf + '</a>' + '<a href="#" id="ac2mp3" class="tgl btn" tt="' + L.mt_c2mp3 + '</a>' + + '<a href="#" id="ac2flac" class="tgl btn" tt="' + L.mt_c2flac + '</a>' + + '<a href="#" id="ac2wav" class="tgl btn" tt="' + L.mt_c2wav + '</a>' + '</div></div>' ) : '') + @@ -5672,6 +13309,7 @@ var mpl = (function () { else if (re_au_native.exec(cs)) c = false; + // allow flac->flac (bitstream fixup) if (!c) return url; @@ -5688,8 +13326,9 @@ var mpl = (function () { return; } - var dv = can_ogg ? 'opus' : can_caf ? 'caf' : 'mp3', - fmts = ['opus', 'owa', 'caf', 'mp3'], + var dv = can_ogg ? 'opus' : + can_caf ? 'caf' : 'mp3', + fmts = ['opus', 'owa', 'caf', 'mp3', 'flac', 'wav'], btns = []; if (v === dv) @@ -5699,7 +13338,8 @@ var mpl = (function () { if ((v == 'opus' && !can_ogg) || (v == 'caf' && !can_caf) || - (v == 'owa' && !can_owa)) + (v == 'owa' && !can_owa) || + (v == 'flac' && !can_flac)) toast.warn(15, L.mt_c2ng); if (v == 'owa' && IPHONE) @@ -5714,6 +13354,8 @@ var mpl = (function () { } if (!IPHONE) btns[1].style.display = btns[2].style.display = 'none'; + btns[4].style.display = have_c2flac ? '' : 'none'; + btns[5].style.display = have_c2wav ? '' : 'none'; if (v) swrite('acode2', v); @@ -5725,6 +13367,9 @@ var mpl = (function () { clmod(btns[a], 'on', fmts[a] == v) r.ac2 = v; + ebi('ac_flac').setAttribute('tt', L.mt_cflac.split('"')[0].format(v)); + ebi('ac_aac').setAttribute('tt', L.mt_caac.split('"')[0].format(v)); + ebi('ac_oth').setAttribute('tt', L.mt_coth.split('"')[0].format(v)); }; r.pp = function () { @@ -5837,11 +13482,13 @@ var mpl = (function () { var za, can_ogg = true, can_owa = false, + can_flac = false, can_caf = APPLE && !/ OS ([1-9]|1[01])_/.test(UA); try { za = new Audio(); can_ogg = za.canPlayType('audio/ogg; codecs=opus') === 'probably'; can_owa = za.canPlayType('audio/webm; codecs=opus') === 'probably'; + can_flac = za.canPlayType('audio/flac') === 'probably'; can_caf = za.canPlayType('audio/x-caf') && can_caf; //'maybe' } catch (ex) { } @@ -5855,6 +13502,7 @@ mpl.init_ac2(); var re_m3u = /\.(m3u8?)$/i; var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|oga|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i, + re_au_vid = /\.(3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i, re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4a|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|oga|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i; @@ -5883,10 +13531,11 @@ function MPlayer() { fn = url.split('?')[0]; if (re_audio.exec(fn)) { - var tid = link.getAttribute('id'); + var tid = link.getAttribute('id'), + txt = re_au_vid.exec(fn) ? '(🎧)' : L.mb_play; r.order.push(tid); r.tracks[tid] = url; - tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">' + L.mb_play + '</a></td>'; + tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">' + txt + '</a></td>'; ebi('a' + tid).onclick = ev_play; clmod(trs[a], 'au', 1); } @@ -6623,6 +14272,10 @@ function song_skip(n, dirskip) { } function next_song(e) { ev(e); + if (QS('.dumb_loader_thing')) { + treectl.ls_cb = next_song; + return; + } if (mp.order.length) { var dirskip = mpl.traversals; mpl.traversals = 0; @@ -6656,6 +14309,10 @@ function prev_song(e) { if (mp.au && !mp.au.paused && mp.au.currentTime > 3) return seek_au_sec(0); + if (QS('.dumb_loader_thing')) { + treectl.ls_cb = function () { song_skip(-1); }; + return; + } return song_skip(-1); } function dl_song() { @@ -7705,9 +15362,11 @@ function eval_hash() { d.onclick = function (e) { ev(e); if (a) - QS(treectl.hidden ? '#path a:nth-last-child(2)' : '#treeul a.hl').focus(); + d = QS(treectl.hidden ? '#path a:nth-last-child(2)' : '#treeul a.hl'); else - QS(thegrid.en ? '#ggrid a' : '#files tbody tr[tabindex]').focus(); + d = QS(thegrid.en ? '#ggrid a' : '#files tbody tr[tabindex]'); + if (d) + d.focus(); }; })(a); @@ -7975,6 +15634,16 @@ function fmt_ren(re, md, fmt) { } +function fs_abrt() { + toast.inf(30, L.fp_abrt); + fileman.sn++; + fileman.f.length = 0; + var xhr = new XHR(); + xhr.open('POST', '/?fs_abrt=' + abrt_key, true); + xhr.send(); +} + + var fileman = (function () { var bren = ebi('fren'), bdel = ebi('fdel'), @@ -7985,6 +15654,8 @@ var fileman = (function () { t_paste, r = {}; + r.f = []; + r.sn = 1; r.clip = null; try { r.bus = new BroadcastChannel("fileman_bus"); @@ -8010,7 +15681,7 @@ var fileman = (function () { hdel = !(have_del && has(perms, 'delete')), hcut = !(have_mv && has(perms, 'move')), hpst = !(have_mv && has(perms, 'write')), - hshr = !(have_shr && acct != '*' && (has(perms, 'read') || has(perms, 'write'))); + hshr = !can_shr; if (!(enren || endel || encut || enpst)) hren = hdel = hcut = hpst = true; @@ -8171,7 +15842,7 @@ var fileman = (function () { exm.onkeydown = exh.onkeydown = exd.onkeydown = sh_k.onkeydown = sh_pw.onkeydown = function (e) { - var kc = (e.code || e.key) + ''; + var kc = (e.key || e.code) + ''; if (kc.endsWith('Enter')) sh_apply.click(); }; @@ -8269,9 +15940,12 @@ var fileman = (function () { return toast.err(3, L.fr_eperm); var f = [], + sn = ++r.sn, base = vsplit(sel[0].vp)[0], mkeys; + r.f = f; + for (var a = 0; a < sel.length; a++) { var vp = sel[a].vp; if (vp.endsWith('/')) @@ -8378,7 +16052,7 @@ var fileman = (function () { (function (a) { f[a].inew.onkeydown = function (e) { rn_ok(a, true); - var kc = (e.code || e.key) + ''; + var kc = (e.key || e.code) + ''; if (kc.endsWith('Enter')) return rn_apply(); }; @@ -8476,7 +16150,7 @@ var fileman = (function () { spresets(); ire.onkeydown = ifmt.onkeydown = function (e) { - var k = (e.code || e.key) + ''; + var k = (e.key || e.code) + ''; if (k == 'Escape' || k == 'Esc') return rn_cancel(); @@ -8551,7 +16225,9 @@ var fileman = (function () { return rn_cancel(); } - toast.show('inf r', 0, esc(L.fr_busy.format(f.length, f[0].ofn))); + var msg = esc(L.fr_busy.format(f.length, f[0].ofn)); + msg += '\n<a id="fs_abrt" class="btn" href="#" onclick="fs_abrt()">' + L.fs_abrt + '</a>'; + toast.show('inf r', 0, msg); var dst = base + uricom_enc(f[0].inew.value, false); function rename_cb() { @@ -8560,13 +16236,17 @@ var fileman = (function () { toast.err(9, L.fr_efail + msg); return; } + if (r.sn != sn) + return modal.confirm('WARNING: the rename was aborted'); f.shift().inew.value = '( OK )'; return rn_apply_loop(); } + abrt_key = randstr(9); + var xhr = new XHR(); - xhr.open('POST', f[0].src + '?move=' + dst, true); + xhr.open('POST', f[0].src + '?move=' + dst + '&akey=' + abrt_key, true); xhr.onload = xhr.onerror = rename_cb; xhr.send(); } @@ -8574,6 +16254,7 @@ var fileman = (function () { r.delete = function (e) { var sel = msel.getsel(), + sn = ++r.sn, vps = []; for (var a = 0; a < sel.length; a++) @@ -8610,6 +16291,9 @@ var fileman = (function () { toast.err(9, L.fd_err + msg); return; } + if (r.sn != sn) + return modal.confirm('WARNING: the delete was aborted'); + if (this.responseText.indexOf('deleted 0 files (and 0') + 1) { toast.err(9, L.fd_none); return deleter('xbd'); @@ -8822,12 +16506,15 @@ var fileman = (function () { '<div><table id="rn_f" class="m">', '<tr><td>' + L.fr_lnew + '</td><td>' + L.fr_lold + '</td></tr>', ], + sn = ++r.sn, ui = false, f = [], indir = [], srcdir = vsplit(r.clip[0])[0], links = QSA('#files tbody td:nth-child(2) a'); + r.f = f; + for (var a = 0, aa = links.length; a < aa; a++) indir.push(uricom_dec(vsplit(noq_href(links[a]))[1])); @@ -8859,13 +16546,17 @@ var fileman = (function () { if (!t.dst) return paster(); - toast.show('inf r', 0, esc((r.ccp ? L.fcp_busy : L.fp_busy).format(f.length + 1, uricom_dec(t.src)))); + var msg = esc((r.ccp ? L.fcp_busy : L.fp_busy).format(f.length + 1, uricom_dec(t.src))); + msg += '\n<a id="fs_abrt" class="btn" href="#" onclick="fs_abrt()">' + L.fs_abrt + '</a>'; + toast.show('inf r', 0, msg); var xhr = new XHR(), act = r.ccp ? '?copy=' : '?move=', dst = get_evpath() + uricom_enc(t.dst); - xhr.open('POST', t.src + act + dst, true); + abrt_key = randstr(9); + + xhr.open('POST', t.src + act + dst + '&akey=' + abrt_key, true); xhr.onload = xhr.onerror = paste_cb; xhr.send(); } @@ -8875,6 +16566,9 @@ var fileman = (function () { toast.err(9, (r.ccp ? L.fcp_err : L.fp_err) + msg); return; } + if (r.sn != sn) + return modal.confirm('WARNING: the paste was aborted'); + paster(); } function okgo() { @@ -8938,7 +16632,7 @@ var fileman = (function () { (function (a) { var inew = ebi('rn_new_' + a); inew.onkeydown = function (e) { - if (((e.code || e.key) + '').endsWith('Enter')) + if (((e.key || e.code) + '').endsWith('Enter')) return rn_apply(); }; inew.oninput = function (e) { @@ -9371,7 +17065,8 @@ var showfile = (function () { }; r.mktree = function () { - var crumbs = linksplit(get_evpath()).join('<span>/</span>'), + var top = get_evpath().slice(SR.length), + crumbs = linksplit(top).join('<span>/</span>'), html = ['<li class="bn">' + L.tv_lst + '<br />' + crumbs + '</li>']; for (var a = 0; a < r.files.length; a++) { var file = r.files[a]; @@ -9995,7 +17690,10 @@ function tree_scrolltoo(q) { } -function tree_neigh(n) { +function tree_neigh(n, ratelimit) { + if (ratelimit && QS('.dumb_loader_thing') && Date.now() - treectl.busied < 5) + return; + var links = QSA(showfile.active() || treectl.texts ? '#docul li>a' : '#treeul li>a+a'); if (!links.length) { treectl.dir_cb = function () { @@ -10113,6 +17811,13 @@ function fselfunw(e, ae, d, rem) { } selfun(); } +var konmai = 0, konmak = (function() { + var u = "arrowup", + d = "arrowdown", + l = "arrowleft", + r = "arrowright"; + return [u, u, d, d, l, r, l, r, "b", "a", "enter"]; +})(); var ahotkeys = function (e) { if (e.altKey || e.isComposing) return; @@ -10120,10 +17825,38 @@ var ahotkeys = function (e) { if (QS('#bbox-overlay.visible') || modal.busy) return; - var k = (e.code || e.key) + '', pos = -1, n, + var k = (e.key || e.code) + '', pos = -1, n, ae = document.activeElement, aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : ''; + if (k.startsWith('Key')) + k = k.slice(3); + else if (k.startsWith('Digit')) + k = k.slice(5); + + var kl = k.toLowerCase(); + + if (dbg_kbd) + console.log('KBD', k, kl, e.key, e.code, e.keyCode, e.which); + + if (konmai < 0) + noop(); + else if (konmak[konmai] != kl) + konmai = konmai && kl == konmak[0] ? (konmai<3?konmai:1):0; + else if (++konmai >= konmak.length) { + konmai = -1; + document.documentElement.scrollTop = 0; + settheme.go(6); + start_actx(); + sfx_nice(); + toast.inf(9, 'omega clearance granted', null, 'top'); + setTimeout(function() { + apply_perms(treectl.lsc); + fileman.render(); + }, 573); + return ev(e); + } + if (k == 'Escape' || k == 'Esc') { ae && ae.blur(); tt.hide(); @@ -10171,7 +17904,7 @@ var ahotkeys = function (e) { fselfunw(e, ae, d, rem); return ev(e); } - if (k == 'Space' || k == 'Spacebar') { + if (k == 'Space' || k == 'Spacebar' || k == ' ') { clmod(ae, 'sel', 't'); msel.origin_tr(ae); msel.selui(); @@ -10179,7 +17912,7 @@ var ahotkeys = function (e) { } } if (in_ftab || !aet || (ae && ae.closest('#ggrid'))) { - if ((k == 'KeyA' || k == 'a') && ctrl(e)) { + if ((kl == 'a') && ctrl(e)) { var ntot = treectl.lsc.files.length + treectl.lsc.dirs.length, sel = msel.getsel(), all = msel.getall(); @@ -10195,7 +17928,7 @@ var ahotkeys = function (e) { } if (ae && ae.closest('pre')) { - if ((k == 'KeyA' || k == 'a') && ctrl(e)) { + if ((kl == 'a') && ctrl(e)) { var sel = document.getSelection(), ran = document.createRange(); @@ -10212,107 +17945,105 @@ var ahotkeys = function (e) { if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre') return; - if (e.key == '?') + if (k == '?') return hkhelp(); if (!e.shiftKey && ctrl(e)) { var sel = window.getSelection && window.getSelection() || {}; sel = sel && !sel.isCollapsed && sel.direction != 'none'; - if (k == 'KeyX' || k == 'x') + if (kl == 'x') return fileman.cut(e); - if ((k == 'KeyC' || k == 'c') && !sel) + if (kl == 'c' && !sel) return fileman.cpy(e); - if (k == 'KeyV' || k == 'v') + if (kl == 'v') return fileman.d_paste(e); - if (k == 'KeyK' || k == 'k') + if (kl == 'k') return fileman.delete(e); return; } - if (e.shiftKey && k != 'KeyA' && k != 'KeyD' && k != 'A' && k != 'D') + if (e.shiftKey && kl != 'a' && kl != 'd') return; - if (k.indexOf('Digit') === 0) - pos = parseInt(k.slice(-1)) * 0.1; + if (/^[0-9]$/.test(k)) + pos = parseInt(k) * 0.1; if (pos !== -1) return seek_au_mul(pos) || true; - if (k == 'KeyJ' || k == 'j') + if (kl == 'j') return prev_song() || true; - if (k == 'KeyL' || k == 'l') + if (kl == 'l') return next_song() || true; - if (k == 'KeyP' || k == 'p') + if (kl == 'p') return playpause() || true; - n = (k == 'KeyU' || k == 'u') ? -10 : - (k == 'KeyO' || k == 'o') ? 10 : 0; + n = kl == 'u' ? -10 : kl == 'o' ? 10 : 0; if (n !== 0) return seek_au_rel(n) || true; - if (k == 'KeyY') + if (kl == 'y') return msel.getsel().length ? ebi('seldl').click() : showfile.active() ? ebi('dldoc').click() : dl_song(); - n = (k == 'KeyI' || k == 'i') ? -1 : - (k == 'KeyK' || k == 'k') ? 1 : 0; + n = kl == 'i' ? -1 : kl == 'k' ? 1 : 0; if (n !== 0) - return tree_neigh(n); + return tree_neigh(n, 1); - if (k == 'KeyM' || k == 'm') + if (kl == 'm') return tree_up(); - if (k == 'KeyB' || k == 'b') + if (kl == 'b') return treectl.hidden ? treectl.entree() : treectl.detree(); - if (k == 'KeyG' || k == 'g') + if (kl == 'g') return ebi('griden').click(); - if (k == 'KeyT' || k == 't') + if (kl == 't') return ebi('thumbs').click(); - if (k == 'KeyV' || k == 'v') + if (kl == 'v') return ebi('filetree').click(); if (k == 'F2') return fileman.rename(); if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) { - if (k == 'KeyA' || k == 'a') + if (kl == 'a') return QS('#twig').click(); - if (k == 'KeyD' || k == 'd') + if (kl == 'd') return QS('#twobytwo').click(); } if (showfile.active()) { - if (k == 'KeyS' || k == 's') + if (kl == 's') showfile.tglsel(); - if ((k == 'KeyE' || k == 'e') && ebi('editdoc').style.display != 'none') + if (kl == 'e' && ebi('editdoc').style.display != 'none') ebi('editdoc').click(); } if (mp && mp.au && !mp.au.paused) { - if (k == 'KeyS') + if (kl == 's') return sel_song(); } if (thegrid.en) { - if (k == 'KeyS' || k == 's') + if (kl == 's') return ebi('gridsel').click(); - if (k == 'KeyA' || k == 'a') + if (kl == 'a') return QSA('#ghead a[z]')[0].click(); - if (k == 'KeyD' || k == 'd') + if (kl == 'd') return QSA('#ghead a[z]')[1].click(); } }; @@ -10587,7 +18318,8 @@ var search_ui = (function () { for (var a = 0; a < res.hits.length; a++) { var r = res.hits[a], ts = parseInt(r.ts), - sz = esc(r.sz + ''), + sz = parseInt(r.sz), + hsz = filesizefun(sz), rp = esc(uricom_dec(r.rp + '')), ext = rp.lastIndexOf('.') > 0 ? rp.split('.').pop().split('?')[0] : '%', id = 'f-' + ('00000000' + crc32(rp)).slice(-8); @@ -10600,7 +18332,8 @@ var search_ui = (function () { ext = '%'; var links = linksplit(r.rp + '', id).join('<span>/</span>'), - nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz]; + nodes = ['<tr><td>-</td><td><div>' + links + + '</div></td><td sortv="' + sz + '">' + hsz]; for (var b = 0; b < tagord.length; b++) { var k = esc(tagord[b]), @@ -10671,7 +18404,7 @@ function ev_load_m3u(e) { function () { load_m3u(url); }, function () { if (has(perms, 'write') && has(perms, 'delete')) - window.location = url + '?edit'; + location = url + '?edit'; else showfile.show(url); } @@ -11070,7 +18803,7 @@ var treectl = (function () { xhr.top = top; xhr.dst = dst; xhr.rst = rst; - xhr.ts = Date.now(); + xhr.ts = r.busied = Date.now(); xhr.open('GET', addq(dst, 'tree=' + top + (r.dots ? '&dots' : '') + k), true); xhr.onload = xhr.onerror = r.recvtree; xhr.send(); @@ -11109,13 +18842,22 @@ var treectl = (function () { } ebi('treeul').setAttribute('ts', ts); + if (SR && !top0) { + var x = SR.slice(1).split('/'); + while (x[0]) { + res = res['k' + x.shift()]; + if (!res) + throw 'invalid --rp-loc (or bug?)'; + } + } + var top = (top0 == '.' ? dst : top0).split('?')[0], name = uricom_dec(top.split('/').slice(-2)[0]), rtop = top.replace(/^\/+/, ""), - html = parsetree(res, rtop); + html = parsetree(res, rtop.slice(SR.length)); if (!top0) { - html = '<li><a href="#">-</a><a href="/">[root]</a>\n<ul>' + html; + html = '<li><a href="#">-</a><a href="' + SR + '/">[root]</a>\n<ul>' + html; if (rst || !ebi('treeul').getElementsByTagName('li').length) ebi('treeul').innerHTML = html + '</ul></li>'; } @@ -11258,10 +19000,6 @@ var treectl = (function () { return; } var href = this.getAttribute('href'); - if (R && !href.startsWith(SR)) { - location = href; - return; - } r.reqls(href, true); r.dir_cb = tree_scrollto; thegrid.setvis(true); @@ -11270,7 +19008,7 @@ var treectl = (function () { r.reqls = function (url, hpush, back, hydrate) { if (IE && !history.pushState) - return window.location = url; + return location = url; var xhr = new XHR(), m = /[?&](k=[^&#]+)/.exec(url), @@ -11284,8 +19022,9 @@ var treectl = (function () { xhr.back = back xhr.hpush = hpush; xhr.hydrate = hydrate; - xhr.ts = Date.now(); + xhr.ts = r.busied = Date.now(); xhr.open('GET', xhr.top + '?ls' + uq, true); + xhr.setRequestHeader('Fnugg', '' + xhr.ts); xhr.onload = xhr.onerror = recvls; xhr.send(); @@ -11333,6 +19072,7 @@ var treectl = (function () { try { var res = JSON.parse(this.responseText); Object.assign(res, res.cfg); + res.cfg.k; } catch (ex) { if (r.ls_cb) { @@ -11351,6 +19091,9 @@ var treectl = (function () { if (r.chk_index_html(this.top, res)) return; + if (this.ts != res.fnugg && res.fnugg != 'nei' && sread('no_fnugg') !== '1') + toast.warn(60, "WARNING: A proxy/CDN between your webbrowser and the server is misbehaving, and caching responses it shouldn't. As a result, you are now seeing stale directory listings. There will be many issues.\n\nIf you need to ignore this and stop these messages, you can set the global-option 'no-fnugg' on the server, or click <code>π</code> and run this: <code>STG.no_fnugg=1</code>"); + for (var a = 0; a < res.files.length; a++) if (res.files[a].tags === undefined) res.files[a].tags = {}; @@ -11432,7 +19175,7 @@ var treectl = (function () { fun(); } - if (window.have_shr && QS('#op_unpost.act') && (cdir.startsWith(SR + have_shr) || get_evpath().startsWith(SR + have_shr))) + if (can_shr && QS('#op_unpost.act') && (cdir.startsWith(SR + have_shr) || get_evpath().startsWith(SR + have_shr))) goto('unpost'); } @@ -11516,7 +19259,8 @@ var treectl = (function () { var cl = /\.PARTIAL$/.exec(fname) ? ' class="fade"' : '', ln = ['<tr' + cl + '><td>' + tn.lead + '</td><td><a href="' + - top + tn.href + '" id="' + id + '">' + hname + '</a>', tn.sz]; + top + tn.href + '" id="' + id + '">' + hname + + '</a></td><td sortv="' + tn.sz + '">' + filesizefun(tn.sz)]; for (var b = 0; b < res.taglist.length; b++) { var k = esc(res.taglist[b]), @@ -11601,6 +19345,7 @@ var treectl = (function () { return r.reqls(get_evpath(), false, undefined, true); } ls0.unlist = unlist0; + ls0.u2ts = u2ts; var top = get_evpath(); if (r.chk_index_html(top, ls0)) @@ -11725,7 +19470,7 @@ var treectl = (function () { url = '/' + (top ? top + uek : uek) + '/', sym = res[kk] ? '-' : '+', link = '<a href="#">' + sym + '</a><a href="' + - url + kdk + '">' + hek + '</a>'; + SR + url + kdk + '">' + hek + '</a>'; if (res[kk]) { var subtree = parsetree(res[kk], url.slice(1)); @@ -11836,6 +19581,19 @@ var wfp_debounce = (function () { function apply_perms(res) { perms = res.perms || []; + var axs = [], + aclass = '>', + chk = ['read', 'write', 'move', 'delete', 'get', 'admin']; + + if (konmai < 0) { + acct = 'Ted Faro'; + srvinf = 'FAS Nexus</span> // <span>57.3 EiB free of 127 EiB'; + res.shr_who = 'auth'; + perms = res.perms = chk; + have_up2k_idx = have_tags_idx = 1; + have_mv = have_del = true; + } + var a = QS('#ops a[data-dest="up2k"]'); if (have_up2k_idx) { a.removeAttribute('data-perm'); @@ -11850,10 +19608,6 @@ function apply_perms(res) { a.style.display = ''; tt.att(QS('#ops')); - var axs = [], - aclass = '>', - chk = ['read', 'write', 'move', 'delete', 'get', 'admin']; - for (var a = 0; a < chk.length; a++) if (has(perms, chk[a])) axs.push(chk[a].slice(0, 1).toUpperCase() + chk[a].slice(1)); @@ -11864,10 +19618,14 @@ function apply_perms(res) { axs += '-Only'; } + var dst = "?h"; + if (idp_login && acct == "*") + dst = idp_login.replace(/\{dst\}/g, get_evpath()); + ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf + '</span></span><span' + aclass + axs + L.access + '</span>' + (acct != '*' ? - '<form id="flogout" method="post" enctype="multipart/form-data"><input type="hidden" name="act" value="logout" /><input id="blogout" type="submit" value="' + (window.is_idp ? '' : L.logout) + acct + '"></form>' : - '<a href="?h">Login</a>'); + '<form id="flogout" method="post" enctype="multipart/form-data"><input type="hidden" name="act" value="logout" /><input id="blogout" type="submit" value="' + L.logout + acct + '"></form>' : + '<a href="' + dst + '">' + L.login + '</a>'); var o = QSA('#ops>a[data-perm]'); for (var a = 0; a < o.length; a++) { @@ -11898,6 +19656,11 @@ function apply_perms(res) { de = document.documentElement, tds = QSA('#u2conf td'); + shr_who = res.shr_who || shr_who; + can_shr = acct != '*' && (have_read || have_write) && ( + (shr_who == 'a' && has(perms, 'admin')) || + (shr_who == 'auth')); + clmod(de, "read", have_read); clmod(de, "write", have_write); clmod(de, "nread", !have_read); @@ -12190,25 +19953,21 @@ var mukey = (function () { defnot = 'rekobo_alnum'; var map = {}, - html = []; + html = [], + cb = ebi('key_notation'); for (var k in maps) { if (!maps.hasOwnProperty(k)) continue; - html.push( - '<span><input type="radio" name="keytype" value="' + k + '" id="key_' + k + '">' + - '<label for="key_' + k + '">' + k + '</label></span>'); - + html.push('<option value="{0}">{0}</option>'.format(k)); for (var a = 0; a < 24; a++) maps[k][a] = maps[k][a].trim(); } - ebi('key_notation').innerHTML = html.join('\n'); + cb.innerHTML = html.join(''); - function set_key_notation(e) { - ev(e); - var notation = this.getAttribute('value'); - load_notation(notation); + function set_key_notation() { + load_notation(cb.value); try_render(); } @@ -12265,14 +20024,10 @@ var mukey = (function () { if (!maps[notation]) notation = defnot; - ebi('key_' + notation).checked = true; + cb.value = notation; + cb.onchange = set_key_notation; load_notation(notation); - var o = QSA('#key_notation input'); - for (var a = 0; a < o.length; a++) { - o[a].onchange = set_key_notation; - } - return { "render": try_render }; @@ -12311,17 +20066,17 @@ var settheme = (function () { showfile.setstyle(); bchrome(); - var html = [], itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), - names = ['classic dark', 'classic light', 'pm-monokai', 'flat light', 'vice', 'hotdog stand', 'hacker', 'hi-con']; + var html = [], + cb = ebi('themes'), + itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0), + names = ['classic dark', 'classic light', 'pm-monokai', 'flat light', 'vice', 'hotdog stand', 'hacker', 'hi-con', 'phi95 dark', 'phi95']; for (var a = 0; a < themes; a++) - html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') + - '" tt="' + (names[a] || 'custom') + '">' + a + '</a>'); + html.push('<option value="{0}">{0} ┃ {1}</option>'.format(a, names[a] || 'custom')); ebi('themes').innerHTML = html.join(''); - var btns = QSA('#themes a'); - for (var a = 0; a < themes; a++) - btns[a].onclick = r.go; + cb.value = itheme; + cb.onchange = r.onsel; if (chldr) { var x = r.ldr[itheme] || [tre]; @@ -12330,12 +20085,13 @@ var settheme = (function () { } bcfg_set('light', light); - tt.att(ebi('themes')); } - r.go = function (e) { - var i = e; - try { ev(e); i = e.target.textContent; } catch (ex) { } + r.onsel = function () { + r.go(parseInt(ebi('themes').value)); + }; + + r.go = function (i) { light = i % 2 == 1; var c = ax[Math.floor(i / 2)], l = light ? 'y' : 'z'; @@ -12343,36 +20099,60 @@ var settheme = (function () { themen = c + l; swrite('cpp_thm', theme); freshen(); - } + }; freshen(); return r; })(); +var setfszf = (function () { + function freshen() { + var cb = ebi('fszfmt'), + fmt = sread("fszfmt", humansize_fmts) || window.dfszf; + if (!has(humansize_fmts, fmt)) + fmt = '1'; + window.filesizefun = window['humansize_' + fmt]; + cb.onchange = onch; + if (cb.value != fmt) + cb.value = fmt; + } + function onch(e) { + ev(e); + setfmt(ebi('fszfmt').value) + } + function setfmt(fmt) { + swrite("fszfmt", fmt); + freshen(); + treectl.gentab(get_evpath(), treectl.lsc); + } + freshen(); + return setfmt; +})(); + + (function () { function freshen() { lang = sread("cpp_lang", LANGS) || lang; - var k, html = []; + var k, cb = ebi('langs'), html = []; for (var a = 0; a < LANGS.length; a++) { k = LANGS[a]; - html.push('<a href="#" class="btn tgl' + (k == lang ? ' on' : '') + - '" tt="' + Ls[k].tt + '">' + k + '</a>'); + html.push('<option value="{0}">{0} ┃ {1}</option>'.format(k, Ls[k].tt)); } - ebi('langs').innerHTML = html.join(''); - var btns = QSA('#langs a'); - for (var a = 0, aa = btns.length; a < aa; a++) - btns[a].onclick = setlang; + cb.innerHTML = html.join(''); + cb.onchange = setlang; + cb.value = lang; } function setlang(e) { ev(e); var t = L.lang_set; - L = Ls[this.textContent]; - swrite("cpp_lang", this.textContent); + lang = ebi('langs').value; + L = Ls[lang]; + swrite("cpp_lang", lang); freshen(); modal.confirm(L.lang_set + "\n\n" + t, location.reload.bind(location), null); - }; + } freshen(); })(); @@ -12921,7 +20701,7 @@ function show_md(md, name, div, url, depth) { var marked_opts = { headerPrefix: 'md-', - breaks: true, + breaks: !md_no_br, gfm: true }; var ext = md_plug.pre; @@ -13510,14 +21290,6 @@ function reload_browser() { ebi('path').appendChild(o); } - var oo = QSA('#files>tbody>tr>td:nth-child(3)'); - for (var a = 0, aa = oo.length; a < aa; a++) { - var sz = oo[a].textContent.replace(/ +/g, ""), - hsz = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " "); - - oo[a].textContent = hsz; - } - reload_mp(); try { showsort(ftab); } catch (ex) { } makeSortable(ftab, function () { diff --git a/copyparty/web/dd/2.png b/copyparty/web/dd/2.png deleted file mode 100644 index 5f750958..00000000 Binary files a/copyparty/web/dd/2.png and /dev/null differ diff --git a/copyparty/web/dd/3.png b/copyparty/web/dd/3.png deleted file mode 100644 index 524facc3..00000000 Binary files a/copyparty/web/dd/3.png and /dev/null differ diff --git a/copyparty/web/dd/4.png b/copyparty/web/dd/4.png deleted file mode 100644 index 64261e2b..00000000 Binary files a/copyparty/web/dd/4.png and /dev/null differ diff --git a/copyparty/web/dd/5.png b/copyparty/web/dd/5.png deleted file mode 100644 index 3d1d181e..00000000 Binary files a/copyparty/web/dd/5.png and /dev/null differ diff --git a/copyparty/web/dd/__init__.py b/copyparty/web/dd/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/copyparty/web/idp.html b/copyparty/web/idp.html index b67dd2f1..f6e71d63 100644 --- a/copyparty/web/idp.html +++ b/copyparty/web/idp.html @@ -23,17 +23,17 @@ <th>user</th> <th>groups</th> </tr></thead><tbody> - {% for un, gn in rows %} + {%- for un, gn in rows %} <tr> <td><a href="{{ r }}/?idp=rm={{ un|e }}">forget</a></td> <td>{{ un|e }}</td> <td>{{ gn|e }}</td> </tr> - {% endfor %} + {%- endfor %} </tbody></table> - {% if not rows %} + {%- if not rows %} (there are no IdP users in the cache) - {% endif %} + {%- endif %} </div> <a href="#" id="repl">π</a> <script> diff --git a/copyparty/web/md.html b/copyparty/web/md.html index c3e2dfce..1dfed659 100644 --- a/copyparty/web/md.html +++ b/copyparty/web/md.html @@ -130,7 +130,8 @@ write markdown (most html is 🙆 too) var SR = "{{ r }}", last_modified = {{ lastmod }}, - have_emp = {{ "true" if have_emp else "false" }}, + have_emp = {{ have_emp }}, + md_no_br = {{ md_no_br }}, dfavico = "{{ favico }}"; var md_opt = { diff --git a/copyparty/web/md.js b/copyparty/web/md.js index 0109d453..66be69bb 100644 --- a/copyparty/web/md.js +++ b/copyparty/web/md.js @@ -201,7 +201,7 @@ function convert_markdown(md_text, dest_dom) { var marked_opts = { //headerPrefix: 'h-', - breaks: true, + breaks: !md_no_br, gfm: true }; @@ -217,7 +217,7 @@ function convert_markdown(md_text, dest_dom) { catch (ex) { if (IE) { dest_dom.innerHTML = 'IE cannot into markdown ;_;'; - return; + return false; } if (ext) @@ -344,6 +344,8 @@ function convert_markdown(md_text, dest_dom) { } catch (ex) { } }, 1); + + return true; } @@ -422,7 +424,7 @@ function init_toc() { } } - // hilight the correct toc items + scroll into view + // highlight the correct toc items + scroll into view function freshen_toclist() { if (anchors.length == 0) return; diff --git a/copyparty/web/md2.js b/copyparty/web/md2.js index f7d0b59c..45403f99 100644 --- a/copyparty/web/md2.js +++ b/copyparty/web/md2.js @@ -1,6 +1,10 @@ "use strict"; +var sloc0 = '' + location, + dbg_kbd = /[?&]dbgkbd\b/.exec(sloc0); + + // server state var server_md = dom_src.value; @@ -255,7 +259,7 @@ function Modpoll() { } console.log('modpoll...'); - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; @@ -346,7 +350,7 @@ function save(e) { fd.append("lastmod", (force ? -1 : last_modified)); fd.append("body", txt); - var url = (document.location + '').split('?')[0]; + var url = (location + '').split('?')[0]; var xhr = new XHR(); xhr.open('POST', url, true); xhr.responseType = 'text'; @@ -404,7 +408,7 @@ function save_cb() { function run_savechk(lastmod, txt, btn, ntry) { // download the saved doc from the server and compare - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; @@ -697,8 +701,14 @@ function reLastIndexOf(txt, ptn, end) { // table formatter function fmt_table(e) { if (e) e.preventDefault(); - //dom_tbox.className = ''; - + try { + fmt_table2(); + } + catch (ex) { + return toast.err(7, 'table-format (CTRL-K) failed:\n' + ex); + } +} +function fmt_table2() { var txt = dom_src.value, ofs = dom_src.selectionStart, //o0 = txt.lastIndexOf('\n\n', ofs), @@ -930,23 +940,30 @@ var set_lno = (function () { // hotkeys / toolbar (function () { - var keydown = function (ev) { - if (!ev && window.event) { - ev = window.event; + var keydown = function (e) { + if (!e && window.event) { + e = window.event; if (dev_fbw == 1) { toast.warn(10, 'hello from fallback code ;_;\ncheck console trace'); console.error('using window.event'); } } - var kc = ev.code || ev.keyCode || ev.which, + var k = (e.key || e.code) + '', editing = document.activeElement == dom_src; - //console.log(ev.key, ev.code, ev.keyCode, ev.which); - if (ctrl(ev) && (ev.code == "KeyS" || kc == 83)) { + if (k.startsWith('Key')) + k = k.slice(3); + + var kl = k.toLowerCase(); + + if (dbg_kbd) + console.log('KBD', k, kl, e.key, e.code, e.keyCode, e.which); + + if (ctrl(e) && kl == "s") { save(); return false; } - if (ev.code == "Escape" || kc == 27) { + if (k == "Escape" || k == "Esc") { var d = ebi('helpclose'); if (d) d.click(); @@ -954,46 +971,44 @@ var set_lno = (function () { if (editing) set_lno(); - if (ctrl(ev)) { - if (ev.code == "KeyE") { + if (ctrl(e)) { + if (kl == "e") { dom_nsbs.click(); return false; } if (!editing) return true; - if (ev.code == "KeyH" || kc == 72) { - md_header(ev.shiftKey); + if (kl == "h") { + md_header(e.shiftKey); return false; } - if (ev.code == "KeyZ" || kc == 90) { - if (ev.shiftKey) + if (kl == "z") { + if (e.shiftKey) action_stack.redo(); else action_stack.undo(); return false; } - if (ev.code == "KeyY" || kc == 89) { + if (kl == "y") { action_stack.redo(); return false; } - if (ev.code == "KeyK") { + if (kl == "k") { fmt_table(); return false; } - if (ev.code == "KeyU") { + if (kl == "u") { iter_uni(); return false; } - var up = ev.code == "ArrowUp" || kc == 38; - var dn = ev.code == "ArrowDown" || kc == 40; - if (up || dn) { - md_p_jump(dn); + if (k == "ArrowUp" || k == "ArrowDown") { + md_p_jump(k == "ArrowDown"); return false; } - if (ev.code == "KeyX" || ev.code == "KeyC") { - md_cut(ev.code == "KeyX"); + if (kl == "x" || kl == "c") { + md_cut(kl == "x"); return true; //sic } } @@ -1001,18 +1016,18 @@ var set_lno = (function () { if (!editing) return true; - if (ev.code == "Tab" || kc == 9) { - md_indent(ev.shiftKey); + if (k == "Tab") { + md_indent(e.shiftKey); return false; } - if (ev.code == "Home" || kc == 36) { - md_home(ev.shiftKey); + if (k == "Home") { + md_home(e.shiftKey); return false; } - if (!ev.shiftKey && ((ev.code + '').endsWith("Enter") || kc == 13)) { + if (!e.shiftKey && k.endsWith("Enter")) { return md_newline(); } - if (!ev.shiftKey && kc == 8) { + if (!e.shiftKey && k == "Backspace") { return md_backspace(); } } @@ -1036,7 +1051,9 @@ ebi('help').onclick = function (e) { var dom = ebi('helpbox'); var dtxt = dom.getElementsByTagName('textarea'); if (dtxt.length > 0) { - convert_markdown(dtxt[0].value, dom); + var txt = dtxt[0].value; + if (!convert_markdown(txt, dom)) + dom.innerText = txt.split('## markdown')[0]; dom.innerHTML = '<a href="#" id="helpclose">close</a>' + dom.innerHTML; } diff --git a/copyparty/web/mde.html b/copyparty/web/mde.html index adef6793..f6d0940f 100644 --- a/copyparty/web/mde.html +++ b/copyparty/web/mde.html @@ -28,7 +28,8 @@ var SR = "{{ r }}", last_modified = {{ lastmod }}, - have_emp = {{ "true" if have_emp else "false" }}, + have_emp = {{ have_emp }}, + md_no_br = {{ md_no_br }}, dfavico = "{{ favico }}"; var md_opt = { diff --git a/copyparty/web/mde.js b/copyparty/web/mde.js index 5c2872df..058825c9 100644 --- a/copyparty/web/mde.js +++ b/copyparty/web/mde.js @@ -6,7 +6,7 @@ var dom_doc = ebi('m'); var dom_md = ebi('mt'); (function () { - var n = document.location + ''; + var n = location + ''; n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/'); n[0] = 'top'; var loc = []; @@ -113,7 +113,7 @@ function save(mde) { fd.append("lastmod", (force ? -1 : last_modified)); fd.append("body", txt); - var url = (document.location + '').split('?')[0]; + var url = (location + '').split('?')[0]; var xhr = new XHR(); xhr.open('POST', url, true); xhr.responseType = 'text'; @@ -166,7 +166,7 @@ function save_cb() { //alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512); // download the saved doc from the server and compare - var url = (document.location + '').split('?')[0] + '?_=' + Date.now(); + var url = (location + '').split('?')[0] + '?_=' + Date.now(); var xhr = new XHR(); xhr.open('GET', url, true); xhr.responseType = 'text'; diff --git a/copyparty/web/opds.xml b/copyparty/web/opds.xml new file mode 100644 index 00000000..fb22371c --- /dev/null +++ b/copyparty/web/opds.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + {%- for d in dirs %} + <entry> + <title>{{ d.name }} + + {{ d.iso8601 }} + + {%- endfor %} + {%- for f in files %} + + {{ f.name }} + {{ f.iso8601 }} + + {%- if f.jpeg_thumb_href != None %} + + {%- endif %} + {%- if f.jpeg_thumb_href_hires != None %} + + {%- endif %} + + {%- endfor %} + \ No newline at end of file diff --git a/copyparty/web/rups.html b/copyparty/web/rups.html index af90eefe..2cc0443a 100644 --- a/copyparty/web/rups.html +++ b/copyparty/web/rups.html @@ -19,14 +19,7 @@ control-panel   Filter:   - - - - - - - -
sizewhowhenagedirfile
+
π # hint; default is unset + + # if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override) + ih + + # file extensions to present as plaintext + textfiles: txt,nfo,diz,cue,readme # default + + # max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS) + txt-max: 64 # default + + # title / service-name to show in html documents + doctitle: copyparty @ --name # default (--name will copy from global-option `name`) + + # server name (displayed in filebrowser document title) + bname: --name # default (copy global-option `name`) + + # powered-by link; disable with -nb + pb-url: https://github.com/9001/copyparty # default + + # show version on the control panel (incompatible with -nb) + ver + + # configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on + k304: 0 # default + + # configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on + no304: 0 # default + + # list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox + md-sbf: downloads forms popups scripts top-navigation-by-user-activation # default + + # list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs + # 📂 also available as volflag "lg_sbf" + lg-sbf: downloads forms popups scripts top-navigation-by-user-activation # default + + # the value of the iframe 'allow' attribute for README.md docs, for example [fullscreen] + # 📂 also available as volflag "md_sba" + md-sba: fullscreen # hint; default is blank + + # the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes + lg-sba: TXT # placeholder + + # don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md) + no-sb-md + + # don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support + no-sb-lg + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// debug options \\000000000000000000000000000000000000000000000000000000000000000000000000\ + + # verbose config file parser (explain config) + vc + + # generate config file from current config (best-effort; probably buggy) + cgen + + # list information about detected optional dependencies + deps + + # kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700) + no-poll + + # kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead + no-sendfile + + # kernel-bug workaround: disable scandir; do a listdir + stat on each file instead + no-scandir + + # wait for initial filesystem indexing before accepting client requests + no-fastboot + + # disable httpserver threadpool, create threads as-needed instead + no-htp + + # when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind + rm-sck + + # explain search processing, and do some extra expensive sanity checks + srch-dbg + + # use mdns-domain instead of server-ip on /?hc + rclone-mdns + + # write stacktrace to Path every S second, for example --stackmon=./st/%Y-%m/%d/%H%M.xz,60 + stackmon: P,S # placeholder + + # list active threads every SEC + log-thrs: 0 # default + + # log filekey params for files where path matches REGEX; [.] (a single dot) = all files + log-fk: /mnt/a-problematic-fuse/this-folder-breaks-filekeys/ # hint; default is unset + + # [up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir + bak-flips + + # bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M) + bf-nc: 200 # default + + # bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started + bf-dir: /srv/bitflips/ # hint; default = bf + + # bak-flips: log corruption info to a textfile at PATH + bf-log: /srv/bitflips/the-history.txt # hint; default is unset + + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + ############################################################################################### + #####/ + ##### This is the end of the [global] config section + ####/ + +[accounts] + foo: bar # username foo, password bar + +[groups] + g1: u1, u2, u3 # group "g1" with users u1, u2, and u3 + +[/the/url/to/share/this/volume/on/] + /the/actual/filesystem/path/ + accs: + r: username_who_gets_Read_access + w: username_who_gets_Write_access + m: username_who_gets_Move_access + d: username_who_gets_Delete_access + .: username_who_can_see_Dotfiles + g: username_who_gets_Get_access + G: username_who_gets_upGet_access + h: username_who_gets_html_access + a: username_who_gets_admin_access + A: username_who_gets_ReadWriteMoveDeleteDotfileAdmin_access + rwm: @g1 # the group "g1" gets Read+Write+Move + flags: + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// uploads, general \\000000000000000000000000000000000000000000000000000000000000000000000\ + + # enable symlink-based file deduplication + dedup + + # enable hardlink-based file deduplication, with fallback on symlinks when that is impossible + hardlink + + # dedup with hardlink only, never symlink; make a full copy if hardlink is impossible + hardlinkonly + + # enable reflink-based file deduplication, with fallback on full copy when that is impossible + reflink + + # verify on-disk data before using it for dedup + safededup + + # take dupe data from clients, even if available on HDD + noclone + + # rejects existing files (instead of linking/cloning them) + nodupe + + # unix-permission for new dirs/folders + chmod_d: 755 + + # unix-permission for new files + chmod_f: 644 + + # change owner of new files/folders to unix-user 573 + uid: 573 + + # change owner of new files/folders to unix-group 999 + gid: 999 + + # force use of sparse files, mainly for s3-backed storage + sparse + + # deny use of sparse files, mainly for slow storage + nosparse + + # enable full WebDAV write support (dangerous); PUT-operations will now OVERWRITE existing files + daw + + # forces all uploads into the top folder of the vfs + nosub + + # enables filetype detection for nameless uploads + magic + + # fallback filename for nameless uploads + put_name + + # default checksum-hasher for PUT/WebDAV uploads + put_ck + + # default checksum-hasher for bup/basic uploads + bup_ck + + # allows server-side gzip compression of uploads with ?gz + gz + + # allows server-side lzma compression of uploads with ?xz + xz + + # forces server-side compression, optional arg: xz,9 + pk + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload rules \\0000000000000000000000000000000000000000000000000000000000000000000000000\ + + # max 250 uploads over 15min + maxn: 250,600 + + # max 1 GiB over 5min (suffixes: b, k, m, g, t) + maxb: 1g,300 + + # total volume size max 1 GiB (suffixes: b, k, m, g, t) + vmaxb: 1g + + # max 4096 files in volume (suffixes: b, k, m, g, t) + vmaxn: 4k + + # return medialinks for non-up2k uploads (not hotlinks) + medialinks + + # write-only users can upload logues without getting renamed + wo_up_readme + + # force randomized filenames, 9 chars long by default + rand + + # randomized filenames are N chars long + nrand: N + + # overwrite existing files? 0=no 1=if-older 2=always + u2ow: N + + # [f]orce [c]lient-last-modified or [u]pload-time + u2ts: fc + + # allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk + u2abort: 1 + + # allow filesizes between 1 KiB and 3MiB + sz: 1k-3m + + # ensure 1 GiB free disk space + df: 1g + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// upload rotation -- (moves all uploads into the specified folder structure) \\00000000000\ + + # 3 levels of subfolders with 100 entries in each + rotn: 100,3 + + # date-formatted organizing + rotf: %Y-%m/%d-%H + + # uploads are deleted after 1 hour + lifetime: 3600 + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// database, general \\00000000000000000000000000000000000000000000000000000000000000000000\ + + # enable database; makes files searchable + enables upload-undo + e2d + + # scan writable folders for new files on startup; also sets -e2d + e2ds + + # scans all folders for new files on startup; also sets -e2d + e2dsa + + # enable multimedia indexing; makes it possible to search for tags + e2t + + # scan existing files for tags on startup; also sets -e2t + e2ts + + # delete all metadata from DB (full rescan); also sets -e2ts + e2tsr + + # disables metadata collection for existing files + d2ts + + # verify integrity on startup by hashing files and comparing to db + e2v + + # when e2v fails, update the db (assume on-disk files are good) + e2vu + + # when e2v fails, panic and quit copyparty + e2vp + + # disables onboot indexing, overrides -e2ds* + d2ds + + # disables metadata collection, overrides -e2t* + d2t + + # disables file verification, overrides -e2v* + d2v + + # disables all database stuff, overrides -e2* + d2d + + # puts thumbnails and indexes at that location + hist: /tmp/cdb + + # puts indexes at that location + dbpath: /tmp/cdb + + # disable db if file foo doesn't exist + landmark: foo + + # scan for new files every 60sec, same as --re-maxage + scan: 60 + + # skips hashing file contents if path matches *.iso + nohash: \.iso$ + + # fully ignores the contents at paths matching *.iso + noidx: \.iso$ + + # don't forget files when deleted from disk + noforget + + # forget uploader-IP after 30 days (GDPR) + forget_ip: 43200 + + # never store uploader-IP in the db; disables unpost + no_db_ip + + # avoid excessive reindexing on android sdcardfs + fat32 + + # database speed-durability tradeoff + dbd: [acid|swal|wal|yolo] + + # cross-volume dupe detection / linking (dangerous) + xlink + + # do not descend into other filesystems + xdev + + # do not follow symlinks leaving the volume root + xvol + + # show dotfiles in search results + dotsrch + + # hide dotfiles in search results (default) + nodotsrch + + # exclude search results with URL matching this regex + srch_excl + + # media-tags to index/display + mte: artist,title + + # media-tags to hide by default + mth: fmt,res,ac + + # uses the "audio-bpm.py" program to generate ".bpm" tags from uploads (f = overwrite tags) + mtp: .bpm=f,audio-bpm.py + + # collects two tags at once + mtp: ahash,vhash=media-hash.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// thumbnails \\000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disables all thumbnails + dthumb + + # disables video thumbnails + dvthumb + + # disables audio thumbnails (spectrograms) + dathumb + + # disables image thumbnails + dithumb + + # compress audio waveforms 33% better + pngquant + + # thumbnail res; WxH + thsize + + # center-cropping (y/n/fy/fn) + crop + + # 3x resolution (y/n/fy/fn) + th3x + + # conversion timeout in seconds + convt + + # use /b.png as thumbnail for file-extension s + ext_th: s=/b.png + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// handlers -- (better explained in --help-handlers) \\000000000000000000000000000000000000\ + + # handle 404s by executing PY file + on404: ~/bin/hook.py + + # handle 403s by executing PY file + on403: ~/bin/hook.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// event hooks -- (better explained in --help-hooks) \\000000000000000000000000000000000000\ + + # execute CMD before a file upload starts + xbu: ~/bin/hook.py + + # execute CMD after a file upload finishes + xau: ~/bin/hook.py + + # execute CMD after all uploads finish and volume is idle + xiu: ~/bin/hook.py + + # execute CMD before a file copy + xbc: ~/bin/hook.py + + # execute CMD after a file copy + xac: ~/bin/hook.py + + # execute CMD before a file rename/move + xbr: ~/bin/hook.py + + # execute CMD after a file rename/move + xar: ~/bin/hook.py + + # execute CMD before a file delete + xbd: ~/bin/hook.py + + # execute CMD after a file delete + xad: ~/bin/hook.py + + # execute CMD on message + xm: ~/bin/hook.py + + # execute CMD if someone gets banned + xban: ~/bin/hook.py + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// client and ux \\000000000000000000000000000000000000000000000000000000000000000000000000\ + + # show grid/thumbnails by default + grid + + # select files in grid by ctrl-click + gsel + + # default sort order + sort + + # natural-sort of leading digits in filenames + nsort + + # number of sort-rules to add to media URLs + hsortn + + # dont list files matching REGEX + unlist + + # includes TXT in the , or @PATH for file at PATH + html_head: + + # theme color (a hint for webbrowsers, discord, etc.) + tcolor: #fc0 + + # don't show total folder size + nodirsz + + # allows indexing by search engines (default) + robots + + # kindly asks search engines to leave + norobots + + # don't list read-access in controlpanel + unlistcr + + # don't list write-access in controlpanel + unlistcw + + # disable js sandbox for markdown files + no_sb_md + + # disable js sandbox for prologue/epilogue + no_sb_lg + + # enable js sandbox for markdown files (default) + sb_md + + # enable js sandbox for prologue/epilogue (default) + sb_lg + + # list of markdown-sandbox safeguards to disable + md_sbf + + # list of *logue-sandbox safeguards to disable + lg_sbf + + # value of iframe allow-prop for markdown-sandbox + md_sba + + # value of iframe allow-prop for *logue-sandbox + lg_sba + + # return html and markdown as text/html + nohtml + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// opengraph (discord embeds) \\00000000000000000000000000000000000000000000000000000000000\ + + # enable OG (disables hotlinking) + og + + # sitename; defaults to --name, disable with '-' + og_site + + # description text for all files; disable with '-' + og_desc + + # thumbnail format; j / jf / jf3 / w / w3 / ... + og_th: jf + + # audio title format; default: {{ artist }} - {{ title }} + og_title_a + + # video title format; default: {{ title }} + og_title_v + + # image title format; default: {{ title }} + og_title_i + + # fallback title if there's nothing in the db + og_title: foo + + # force default title; do not read from tags + og_s_title + + # custom html; see --og-tpl in --help + og_tpl + + # you want to add tags manually with og_tpl + og_no_head + + # if defined: only send OG html if useragent matches this regex + og_ua + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// textfiles \\0000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # where to put markdown backups; s=subfolder, v=volHist, n=nope + md_hist + + # enable textfile expansion; see --help-exp + exp + + # placeholders to expand in markdown files; see --help + exp_md + + # placeholders to expand in prologue/epilogue; see --help + exp_lg + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// tailing \\000000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # disable ?tail (download a growing file continuously) + notail + + # check if file was replaced (new fd) every 1 sec + tail_fd: 1 + + # check for new data every 0.2 sec + tail_rate: 0.2 + + # kill connection after 30 sec + tail_tmax: 30 + + # restrict ?tail access (1=admins,2=authed,3=everyone) + tail_who: 2 + + ###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + ###// others \\0000000000000000000000000000000000000000000000000000000000000000000000000000000\ + + # allow all users with read-access to enable the option to show dotfiles in listings + dots + + # generates per-file accesskeys, which are then required at the "g" permission; keys are invalidated if filesize or inode changes + fk: 8 + + # generates slightly weaker per-file accesskeys, which are then required at the "g" permission; not affected by filesize or inode numbers + fka: 8 + + # generates per-directory accesskeys, which are then required at the "g" permission; keys are invalidated if filesize or inode changes + dk: 8 + + # per-directory accesskeys allow browsing into subdirs + dks + + # allow seeing files (not folders) inside a specific folder with "g" perm, and does not require a valid dirkey to do so + dky + + # allow '?rss' URL suffix (experimental) + rss + + # expensive analysis for mimetype accuracy + rmagic + + # restrict viewing the list of recent uploads + ups_who: 2 + + # restrict access to download-as-zip/tar + zip_who: 2 + + # reject download-as-zip if more than 9000 files + zipmaxn: 9k + + # reject download-as-zip if size over 2 GiB + zipmaxs: 2g + + # reply with 'no' if download-as-zip exceeds max + zipmaxt: no + + # zip-size-limit does not apply to authenticated users + zipmaxu + + # disable race-the-beam (download unfinished uploads) + nopipe + + # ms-windows: timeout for renaming busy files + mv_retry + + # ms-windows: timeout for deleting busy files + rm_retry + + # ask webdav clients to login for all folders + davauth + + # show lastmod time of symlink destination, not the link itself (note: this option is always enabled for recursive listings) + davrt diff --git a/docs/devnotes.md b/docs/devnotes.md index 229cb398..fce97e1f 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -160,10 +160,12 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo` | method | params | result | |--|--|--| +| GET | `?dl` | download file (don't show in-browser) | | GET | `?ls` | list files/folders at URL as JSON | | GET | `?ls&dots` | list files/folders at URL as JSON, including dotfiles | | GET | `?ls=t` | list files/folders at URL as plaintext | | GET | `?ls=v` | list files/folders at URL, terminal-formatted | +| GET | `?opds` | list files/folders at URL as opds feed, for e-readers | | GET | `?lt` | in listings, use symlink timestamps rather than targets | | GET | `?b` | list files/folders at URL as simplified HTML | | GET | `?tree=.` | list one level of subdirectories inside URL | @@ -244,6 +246,7 @@ upload modifiers: | `Accept: json` | `want=json` | return upload info as json; same as `?j` | | `Rand: 4` | `rand=4` | generate random filename with 4 characters | | `Life: 30` | `life=30` | delete file after 30 seconds | +| `Replace: 1` | `replace` | overwrite file if exists | | `CK: no` | `ck` | disable serverside checksum (maybe faster) | | `CK: md5` | `ck=md5` | return md5 checksum instead of sha512 | | `CK: sha1` | `ck=sha1` | return sha1 checksum | @@ -252,7 +255,9 @@ upload modifiers: | `CK: b2s` | `ck=b2s` | return blake2s checksum | * `life` only has an effect if the volume has a lifetime, and the volume lifetime must be greater than the file's - +* `replace` upload-modifier: + * the header `replace: 1` works for both PUT and multipart-post + * the url-param `replace` only works for multipart-post * server behavior of `msg` can be reconfigured with `--urlform` ## admin @@ -328,9 +333,8 @@ if you don't need all the features, you can repack the sfx and save a bunch of s the features you can opt to drop are * `cm`/easymde, the "fancy" markdown editor, saves ~89k -* `hl`, prism, the syntax hilighter, saves ~41k +* `hl`, prism, the syntax highlighter, saves ~41k * `fnt`, source-code-pro, the monospace font, saves ~9k -* `dd`, the custom mouse cursor for the media player tray tab, saves ~2k for the `re`pack to work, first run one of the sfx'es once to unpack it @@ -355,12 +359,20 @@ pip install mutagen # audio metadata pip install pyftpdlib # ftp server pip install partftpy # tftp server pip install impacket # smb server -- disable Windows Defender if you REALLY need this on windows -pip install Pillow pyheif-pillow-opener # thumbnails +pip install Pillow pillow-heif # thumbnails pip install pyvips # faster thumbnails -pip install psutil # better cleanup of stuck metadata parsers on windows +pip install psutil # better cleanup of stuck metadata parsers on windows pip install black==21.12b0 click==8.0.2 bandit pylint flake8 isort mypy # vscode tooling ``` +* on archlinux you can do this: + * `sudo pacman -Sy --needed python-{pip,isort,jinja,argon2-cffi,pyzmq,mutagen,pyftpdlib,pillow}` + * then, as user: `python3 -m pip install --user --break-system-packages -U strip_hints black==21.12b0 click==8.0.2` + * for building docker images: `sudo pacman -Sy --needed qemu-user-static{,-binfmt} podman{,-docker} jq` + +* and if you want to run the python 2.7 tests: + * `git clone https://github.com/pyenv/pyenv .pyenv ; cd .pyenv/bin ; env PYTHON_CONFIGURE_OPTS='--enable-optimizations' PYTHON_CFLAGS='-march=native -mtune=native -std=c17' ./pyenv install 2.7.18 -v ; ln -s $HOME/.pyenv/versions/2.7.18/bin/python2 $HOME/bin/` + ## just the sfx diff --git a/docs/examples/docker/basic-docker-compose/copyparty.conf b/docs/examples/docker/basic-docker-compose/copyparty.conf index 59820103..ba751549 100644 --- a/docs/examples/docker/basic-docker-compose/copyparty.conf +++ b/docs/examples/docker/basic-docker-compose/copyparty.conf @@ -6,7 +6,7 @@ [global] e2dsa # enable file indexing and filesystem scanning e2ts # enable multimedia indexing - ansi # enable colors in log messages + ansi # enable colors in log messages (both in logfiles and stdout) # q, lo: /cfg/log/%Y-%m%d.log # log to file instead of docker diff --git a/docs/examples/docker/basic-docker-compose/docker-compose.yml b/docs/examples/docker/basic-docker-compose/docker-compose.yml index dd3c1f9a..d75d3117 100644 --- a/docs/examples/docker/basic-docker-compose/docker-compose.yml +++ b/docs/examples/docker/basic-docker-compose/docker-compose.yml @@ -1,5 +1,6 @@ -services: +--- +services: copyparty: image: copyparty/ac:latest container_name: copyparty diff --git a/docs/examples/docker/idp-authelia-traefik/README.md b/docs/examples/docker/idp-authelia-traefik/README.md index 7667cb00..bc7a7f2a 100644 --- a/docs/examples/docker/idp-authelia-traefik/README.md +++ b/docs/examples/docker/idp-authelia-traefik/README.md @@ -8,7 +8,7 @@ to try this out with minimal adjustments: * login to https://fs.example.com/ with username `authelia` password `authelia` to use this in a safe and secure manner: -* follow a guide on setting up authelia properly (TODO:link) and use the copyparty-specific parts of this folder as inspiration for your own config; namely the `cpp` subfolder and the `copyparty` service in `docker-compose.yml` +* follow a guide on setting up [authelia](https://www.authelia.com/integration/proxies/traefik/#docker-compose) properly and use the copyparty-specific parts of this folder as inspiration for your own config; namely the `cpp` subfolder and the `copyparty` service in `docker-compose.yml` this folder is based on: * https://github.com/authelia/authelia/tree/39763aaed24c4abdecd884b47357a052b235942d/examples/compose/lite @@ -16,20 +16,18 @@ this folder is based on: incomplete list of modifications made: * support for running with podman as root on fedora (`:z` volumes, `label:disable`) * explicitly using authelia `v4.38.0-beta3` because config syntax changed since last stable release -* disabled automatic letsencrypt certificate signing * reduced logging from debug to info -* added a warning that traefik is given access to the docker socket (as recommended by traefik docs) which means traefik is able to break out of the container and has full root access on the host machine +* implemented a docker socket-proxy to not bind the docker.socket directly to traefik +* using valkey instead of redis for caching # security there is probably/definitely room for improvement in this example setup. Some ideas taken from [github issue #62](https://github.com/9001/copyparty/issues/62): -* Add in a redis password to limit attacker lateral movement in the system -* Move redis to a private network shared with just authelia -* Pin to image hashes (or go all in on updates and add `watchtower`) +* Move valkey to a private network shared with just authelia +* Add `watchtower` to manage your image version updates * Drop bridge networking for just exposing traefik's public ports -* Configure docker for non-root access to docker socket and then move traefik to use [non-root perms](https://docs.docker.com/engine/security/rootless/) if you manage to improve on any of this, especially in a way that might be useful for other people, consider sending a PR :> @@ -47,4 +45,4 @@ currently **not optimal,** at least when compared to running the python sfx outs authelia is behaving strangely, handling 340 requests per second for a while, but then it suddenly drops to 75 and stays there... -I'm assuming all of the performance issues is due to a misconfiguration of authelia/traefik/docker on my end, but I don't relly know where to start +I'm assuming all of the performance issues is due to a misconfiguration of authelia/traefik/docker on my end, but I don't really know where to start diff --git a/docs/examples/docker/idp-authelia-traefik/authelia/configuration.yml b/docs/examples/docker/idp-authelia-traefik/authelia/configuration.yml index b6ca311a..b4352511 100644 --- a/docs/examples/docker/idp-authelia-traefik/authelia/configuration.yml +++ b/docs/examples/docker/idp-authelia-traefik/authelia/configuration.yml @@ -1,15 +1,14 @@ -# based on https://github.com/authelia/authelia/blob/39763aaed24c4abdecd884b47357a052b235942d/examples/compose/lite/authelia/configuration.yml - # Authelia configuration -# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE -jwt_secret: a_very_important_secret +identity_validation: + reset_password: + jwt_secret: 'a_very_important_secret_so_please_change_this' server: address: 'tcp://:9091' log: - level: info # debug + level: info totp: issuer: authelia.com @@ -21,29 +20,26 @@ authentication_backend: access_control: default_policy: deny rules: - # Rules applied to everyone - - domain: traefik.example.com - policy: one_factor + - domain: auth.example.com + policy: bypass # Allow access to the login UI - domain: fs.example.com policy: one_factor session: - # This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE secret: unsecure_session_secret - cookies: - name: authelia_session - domain: example.com # Should match whatever your root protected domain is + domain: example.com # this should match whatever your root protected domain is default_redirection_url: https://fs.example.com authelia_url: https://authelia.example.com/ expiration: 3600 # 1 hour inactivity: 300 # 5 minutes redis: - host: redis + host: valkey port: 6379 - # This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD_FILE - # password: authelia + password: your_secure_password_here + regulation: max_retries: 3 @@ -58,9 +54,7 @@ storage: notifier: disable_startup_check: true smtp: - username: test - # This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE - password: password - host: mail.example.com - port: 25 - sender: admin@example.com + address: 'smtp://127.0.0.1:25' + username: 'test' + password: 'password' + sender: "Authelia " diff --git a/docs/examples/docker/idp-authelia-traefik/docker-compose.yml b/docs/examples/docker/idp-authelia-traefik/docker-compose.yml index 9ebd73ba..3a45dda4 100644 --- a/docs/examples/docker/idp-authelia-traefik/docker-compose.yml +++ b/docs/examples/docker/idp-authelia-traefik/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.3' +--- networks: net: @@ -6,7 +6,7 @@ networks: services: copyparty: - image: copyparty/ac + image: copyparty/ac:latest container_name: idp_copyparty user: "1000:1000" # should match the user/group of your fileshare volumes volumes: @@ -19,19 +19,19 @@ services: labels: - 'traefik.enable=true' - 'traefik.http.routers.copyparty.rule=Host(`fs.example.com`)' - - 'traefik.http.routers.copyparty.entrypoints=https' + - 'traefik.http.routers.copyparty.entrypoints=websecure' - 'traefik.http.routers.copyparty.tls=true' + - 'traefik.http.routers.copyparty.tls.certresolver=letsencrypt' # ← THIS IS CRUCIAL - 'traefik.http.routers.copyparty.middlewares=authelia@docker' stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal environment: LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE # enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram) - PYTHONUNBUFFERED: 1 # ensures log-messages are not delayed (but can reduce speed a tiny bit) authelia: - image: authelia/authelia:v4.38.0-beta3 # the config files in the authelia folder use the new syntax + image: authelia/authelia:4.39.5@sha256:023e02e5203dfa0ebaee7a48b5bae34f393d1f9cada4a9df7fbf87eb1759c671 container_name: idp_authelia volumes: - ./authelia:/config:z @@ -40,25 +40,23 @@ services: labels: - 'traefik.enable=true' - 'traefik.http.routers.authelia.rule=Host(`authelia.example.com`)' - - 'traefik.http.routers.authelia.entrypoints=https' + - 'traefik.http.routers.authelia.entrypoints=websecure' - 'traefik.http.routers.authelia.tls=true' - #- 'traefik.http.routers.authelia.tls.certresolver=letsencrypt' # uncomment this to enable automatic certificate signing (1/2) + - 'traefik.http.routers.authelia.tls.certresolver=letsencrypt' - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/authz/forward-auth?authelia_url=https://authelia.example.com' - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email' expose: - 9091 restart: unless-stopped - healthcheck: - disable: true environment: - TZ=Etc/UTC - redis: - image: redis:7.2.4-alpine3.19 - container_name: idp_redis + valkey: + image: valkey/valkey:8.1.3-alpine3.22@sha256:0d27f0bca0249f61d060029a6aaf2e16b2c417d68d02a508e1dfb763fa2948b4 + container_name: idp_valkey volumes: - - ./redis:/data:z + - ./valkey:/data:z networks: - net expose: @@ -66,40 +64,55 @@ services: restart: unless-stopped environment: - TZ=Etc/UTC + - VALKEY_EXTRA_FLAGS=--requirepass your_secure_password_here + + socket-proxy: + image: lscr.io/linuxserver/socket-proxy:3.2.3@sha256:63d2e0ce6bb0d12dfdbde5c3af31d08fee343ec3801a050c8197a3f5ffae8bed + container_name: idp_socket_proxy + environment: + - CONTAINERS=1 + - NETWORKS=1 + - EVENTS=1 + - PING=1 + - VERSION=1 + - LOG_LEVEL=warning + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + security_opt: + - no-new-privileges:true + read_only: true + tmpfs: + - /run + networks: + - net + restart: unless-stopped + expose: + - 2375 traefik: - image: traefik:2.11.0 + image: traefik:3.5.0@sha256:4e7175cfe19be83c6b928cae49dde2f2788fb307189a4dc9550b67acf30c11a5 container_name: idp_traefik volumes: - ./traefik:/etc/traefik:z - - /var/run/docker.sock:/var/run/docker.sock # WARNING: this gives traefik full root-access to the host OS, but is recommended/required(?) by traefik - security_opt: - - label:disable # disable selinux because it (rightly) blocks access to docker.sock networks: - net labels: - 'traefik.enable=true' - - 'traefik.http.routers.api.rule=Host(`traefik.example.com`)' - - 'traefik.http.routers.api.entrypoints=https' - - 'traefik.http.routers.api.service=api@internal' - - 'traefik.http.routers.api.tls=true' - #- 'traefik.http.routers.api.tls.certresolver=letsencrypt' # uncomment this to enable automatic certificate signing (2/2) - 'traefik.http.routers.api.middlewares=authelia@docker' ports: - '80:80' - '443:443' command: - - '--api' - - '--providers.docker=true' + - '--global.sendAnonymousUsage=false' + - '--providers.docker.endpoint=tcp://socket-proxy:2375' - '--providers.docker.exposedByDefault=false' - - '--entrypoints.http=true' - - '--entrypoints.http.address=:80' - - '--entrypoints.http.http.redirections.entrypoint.to=https' - - '--entrypoints.http.http.redirections.entrypoint.scheme=https' - - '--entrypoints.https=true' - - '--entrypoints.https.address=:443' + - '--entrypoints.web.address=:80' + - '--entrypoints.web.http.redirections.entrypoint.to=websecure' + - '--entrypoints.web.http.redirections.entrypoint.scheme=https' + - '--entrypoints.websecure.address=:443' - '--certificatesResolvers.letsencrypt.acme.email=your-email@your-domain.com' - '--certificatesResolvers.letsencrypt.acme.storage=/etc/traefik/acme.json' - - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http' - - '--log=true' - - '--log.level=WARNING' # DEBUG + - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web' + - '--log.level=INFO' + depends_on: + - socket-proxy diff --git a/docs/logo-sq.svg b/docs/logo-sq.svg new file mode 100644 index 00000000..cca66519 --- /dev/null +++ b/docs/logo-sq.svg @@ -0,0 +1,216 @@ + + + + copyparty_logo + + + + + + + + + + + + image/svg+xml + + copyparty_logo + github.com/9001/copyparty + + + + + + + + + + + + + + + + + + + diff --git a/docs/logo256.svg b/docs/logo256.svg new file mode 100644 index 00000000..8fdb2782 --- /dev/null +++ b/docs/logo256.svg @@ -0,0 +1,209 @@ + + + copyparty_logo + + + + + + + + + + + + image/svg+xml + + copyparty_logo + github.com/9001/copyparty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/notes.sh b/docs/notes.sh index 3c8798cc..9152d4ac 100644 --- a/docs/notes.sh +++ b/docs/notes.sh @@ -71,7 +71,7 @@ avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} c python3 -um copyparty -nw -v srv::rw -i 127.0.0.1 2>&1 | tee log cat log | awk '!/"purl"/{next} {s=$1;sub(/[^m]+m/,"");gsub(/:/," ");t=60*(60*$1+$2)+$3} t1{printf "%.3f += %.3f - %.3f (%.3f) # %.3f -> %.3f\n",r,p,a,p-a,p,t;r+=p-a;a=t} {p=t} END {print r+p-a}' @@ -337,3 +337,5 @@ mk && t0="$(date)" && while true; do date -s "$(date '+ 1 hour')"; systemd-tmpfi mk && sudo -u ed flock /tmp/foo sleep 40 & sleep 1; ps aux | grep -E 'sleep 40$' && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0" mk && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; tar -cf/dev/null /tmp/foo; done; echo "$t0" +# number of megabytes downloaded since some date +awk =0.4.0", + "Pillow", + "pyftpdlib", + "pyopenssl", + "pyzmq", +] thumbnails = ["Pillow"] thumbnails2 = ["pyvips"] audiotags = ["mutagen"] @@ -85,8 +93,8 @@ copyparty = [ "web/*.js", "web/*.css", "web/*.html", + "web/*.xml", "web/a/*.bat", - "web/dd/*.png", "web/deps/*.gz", "web/deps/*.woff*", ] diff --git a/scripts/copyparty-repack.sh b/scripts/copyparty-repack.sh index bfe18cea..c4de5d23 100755 --- a/scripts/copyparty-repack.sh +++ b/scripts/copyparty-repack.sh @@ -137,10 +137,10 @@ repack() { } repack sfx-full "re gz" -repack sfx-ent "re no-dd" -repack sfx-ent "re no-dd gz" -repack sfx-lite "re no-dd no-cm no-hl" -repack sfx-lite "re no-dd no-cm no-hl gz" +repack sfx-ent "re" +repack sfx-ent "re gz" +repack sfx-lite "re no-cm no-hl" +repack sfx-lite "re no-cm no-hl gz" # move fuse and up2k clients into copyparty-extras/, diff --git a/scripts/deps-docker/Dockerfile b/scripts/deps-docker/Dockerfile index 874fd5c4..18570e2d 100644 --- a/scripts/deps-docker/Dockerfile +++ b/scripts/deps-docker/Dockerfile @@ -3,7 +3,7 @@ WORKDIR /z ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \ ver_hashwasm=4.12.0 \ ver_marked=4.3.0 \ - ver_dompf=3.2.6 \ + ver_dompf=3.2.7 \ ver_mde=2.18.0 \ ver_codemirror=5.65.18 \ ver_fontawesome=5.13.0 \ @@ -41,25 +41,25 @@ RUN mkdir -p /z/dist/no-pk \ && (mkdir hash-wasm \ && cd hash-wasm \ && unzip ../hash-wasm.zip) \ - && (tar -xf asmcrypto.tgz \ + && (tar --no-same-owner -xf asmcrypto.tgz \ && cd asmcrypto.js-$ver_asmcrypto \ && npm install ) \ - && (tar -xf marked.tgz \ + && (tar --no-same-owner -xf marked.tgz \ && cd marked-$ver_marked \ && npm install \ && npm i grunt uglify-js -g ) \ - && (tar -xf codemirror.tgz \ + && (tar --no-same-owner -xf codemirror.tgz \ && cd codemirror5-$ver_codemirror \ && npm install ) \ - && (tar -xf mde.tgz \ + && (tar --no-same-owner -xf mde.tgz \ && cd easy-markdown-editor* \ && npm install \ && npm i gulp-cli -g ) \ - && tar -xf dompurify.tgz \ - && tar -xf prism.tgz \ - && tar -xf fusepy.tgz \ + && tar --no-same-owner -xf dompurify.tgz \ + && tar --no-same-owner -xf prism.tgz \ + && tar --no-same-owner -xf fusepy.tgz \ && unzip fontawesome.zip \ - && tar -xf zopfli.tgz + && tar --no-same-owner -xf zopfli.tgz COPY busy-mp3.sh /z/ @@ -68,7 +68,7 @@ RUN /z/busy-mp3.sh \ # build fonttools (which needs zopfli) -RUN tar -xf zopfli.tgz \ +RUN tar --no-same-owner -xf zopfli.tgz \ && cd zopfli* \ && cmake \ -DCMAKE_INSTALL_PREFIX=/usr \ diff --git a/scripts/docker/Dockerfile.ac b/scripts/docker/Dockerfile.ac index c06f99f8..54d6fef1 100644 --- a/scripts/docker/Dockerfile.ac +++ b/scripts/docker/Dockerfile.ac @@ -1,7 +1,7 @@ FROM alpine:latest WORKDIR /z LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ - org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ + org.opencontainers.image.source="https://github.com/9001/copyparty" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.title="copyparty-ac" \ org.opencontainers.image.description="copyparty with Pillow and FFmpeg (image/audio/video thumbnails, audio transcoding, media tags)" @@ -16,6 +16,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./ ADD base ./base RUN ash innvikler.sh ac -WORKDIR /w +WORKDIR /state EXPOSE 3923 -ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] +ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"] diff --git a/scripts/docker/Dockerfile.dj b/scripts/docker/Dockerfile.dj index 8302a2cb..b377537b 100644 --- a/scripts/docker/Dockerfile.dj +++ b/scripts/docker/Dockerfile.dj @@ -1,7 +1,7 @@ FROM alpine:latest WORKDIR /z LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ - org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ + org.opencontainers.image.source="https://github.com/9001/copyparty" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.title="copyparty-dj" \ org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection" @@ -19,13 +19,16 @@ RUN apk add -U !pyc \ vips-jxl vips-heif vips-poppler vips-magick \ py3-numpy fftw libsndfile \ vamp-sdk vamp-sdk-libs \ + libraw py3-numpy cython \ && apk add -t .bd \ bash wget gcc g++ make cmake patchelf \ python3-dev ffmpeg-dev fftw-dev libsndfile-dev \ py3-wheel py3-numpy-dev libffi-dev \ vamp-sdk-dev \ + libraw-dev py3-numpy-dev \ && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ && python3 -m pip install pyvips \ + && python3 -m pip install "$(wget -O- https://api.github.com/repos/letmaik/rawpy/releases/latest | awk -F\" '$2=="tarball_url"{print$4}')" \ && bash install-deps.sh \ && apk del py3-pip .bd \ && chmod 777 /root \ @@ -35,6 +38,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./ ADD base ./base RUN ash innvikler.sh dj -WORKDIR /w +WORKDIR /state EXPOSE 3923 -ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] +ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"] diff --git a/scripts/docker/Dockerfile.im b/scripts/docker/Dockerfile.im index 94a7ad1a..f4f8124d 100644 --- a/scripts/docker/Dockerfile.im +++ b/scripts/docker/Dockerfile.im @@ -1,7 +1,7 @@ FROM alpine:latest WORKDIR /z LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ - org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ + org.opencontainers.image.source="https://github.com/9001/copyparty" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.title="copyparty-im" \ org.opencontainers.image.description="copyparty with Pillow and Mutagen (image thumbnails, media tags)" @@ -15,6 +15,6 @@ COPY i/dist/copyparty-sfx.py innvikler.sh ./ ADD base ./base RUN ash innvikler.sh im -WORKDIR /w +WORKDIR /state EXPOSE 3923 -ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] +ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"] diff --git a/scripts/docker/Dockerfile.iv b/scripts/docker/Dockerfile.iv index d639d3df..6155b327 100644 --- a/scripts/docker/Dockerfile.iv +++ b/scripts/docker/Dockerfile.iv @@ -1,7 +1,7 @@ FROM alpine:latest WORKDIR /z LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ - org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ + org.opencontainers.image.source="https://github.com/9001/copyparty" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.title="copyparty-iv" \ org.opencontainers.image.description="copyparty with Pillow, FFmpeg, libvips (image/audio/video thumbnails, audio transcoding, media tags)" @@ -14,17 +14,20 @@ RUN apk add -U !pyc \ ffmpeg \ py3-magic \ vips-jxl vips-heif vips-poppler vips-magick \ + libraw py3-numpy cython \ && apk add -t .bd \ bash wget gcc g++ make cmake patchelf \ python3-dev py3-wheel libffi-dev \ + libraw-dev py3-numpy-dev \ && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ && python3 -m pip install pyvips \ + && python3 -m pip install "$(wget -O- https://api.github.com/repos/letmaik/rawpy/releases/latest | awk -F\" '$2=="tarball_url"{print$4}')" \ && apk del py3-pip .bd COPY i/dist/copyparty-sfx.py innvikler.sh ./ ADD base ./base RUN ash innvikler.sh iv -WORKDIR /w +WORKDIR /state EXPOSE 3923 -ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"] +ENTRYPOINT ["python3", "-m", "copyparty", "-c", "/z/initcfg"] diff --git a/scripts/docker/Dockerfile.min b/scripts/docker/Dockerfile.min index 4712a8ad..8d8f380a 100644 --- a/scripts/docker/Dockerfile.min +++ b/scripts/docker/Dockerfile.min @@ -1,7 +1,7 @@ FROM alpine:latest WORKDIR /z LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ - org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ + org.opencontainers.image.source="https://github.com/9001/copyparty" \ org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.title="copyparty-min" \ org.opencontainers.image.description="just copyparty, no thumbnails / media tags / audio transcoding" @@ -13,6 +13,6 @@ RUN apk --no-cache add !pyc \ COPY i/dist/copyparty-sfx.py innvikler.sh ./ RUN ash innvikler.sh min -WORKDIR /w +WORKDIR /state EXPOSE 3923 -ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "--no-thumb", "-c", "/z/initcfg"] +ENTRYPOINT ["python3", "-m", "copyparty", "--no-thumb", "-c", "/z/initcfg"] diff --git a/scripts/docker/innvikler.sh b/scripts/docker/innvikler.sh index 27a3d230..c027dcca 100644 --- a/scripts/docker/innvikler.sh +++ b/scripts/docker/innvikler.sh @@ -15,9 +15,15 @@ rm -rf /z/base rm -rf /var/cache/apk/* /root/.cache # initial config; common for all flavors -mkdir /cfg /w -chmod 777 /cfg /w -echo % /cfg > initcfg +mkdir /state /cfg /w +chmod 777 /state /cfg /w +cat >initcfg <<'EOF' +[global] + chdir: /w + no-crt + +% /cfg +EOF # unpack sfx and dive in python3 copyparty-sfx.py --version @@ -46,6 +52,12 @@ find -name __pycache__ | grep -E 'ty/web/|/pycpar' | tr '\n' '\0' | xargs -0 rm -rf + + + + +smoketest() { + # two-for-one: # 1) smoketest copyparty even starts # 2) build any bytecode we missed @@ -82,5 +94,95 @@ kill $pid; wait $pid # output from -e2d rm -rf .hist /cfg/copyparty +} + +smoketest + + + + + +[ "$1" == min ] && { + # shrink amd64 from 45.5 to 33.2 MiB + + # libstdc++ is pulled in by libmpdec++ in libmpdec; keep libmpdec.so + cd /usr/lib ; rm -rf \ + libmpdec++.so* \ + libncurses* \ + libpanelw* \ + libreadline* \ + libstdc++.so* \ + -- + + cd /usr/lib/python3.*/lib-dynload/ ; rm -rf \ + *audioop.* \ + _asyncio.* \ + _ctypes_test.* \ + _curses* \ + _test* \ + _xx* \ + ossaudio.* \ + readline.* \ + xx* \ + -- + + # keep http/client for u2c + cd /usr/lib/python3.*/ ; rm -rf \ + site-packages/*.dist-info \ + aifc.py \ + asyncio \ + bdb.py \ + cgi.py \ + config-3.*/Makefile \ + ctypes/macholib \ + dbm \ + difflib.py \ + doctest.py \ + email/_header_value_parser.py \ + html \ + http/cookiejar.* \ + http/server.* \ + imaplib.py \ + importlib/resources \ + mailbox.py \ + nntplib.py \ + pickletools.py \ + pydoc.py \ + smtplib.py \ + statistics.py \ + tomllib \ + unittest \ + urllib/request.* \ + venv \ + wsgiref \ + xml/dom \ + xml/sax \ + xmlrpc \ + -- + + set +x + find -iname '*.pyc' | + grep -viE 'tftpy' | + while IFS= read -r x; do + y="$(printf '%s\n' "$x" | sed -r 's`/__pycache__/([^/]+)\.cpython-312\.pyc$`/\1.py`')" + [ -e "$y" ] || continue + [ "$y" = "$x" ] && continue + rm "$y" + mv "$x" "${y}c" + done + find -iname __pycache__ -print0 | xargs -0 rm -rf -- + rm -rf /a + set -x + + smoketest + + # printf '%s\n' 'FROM localhost/copyparty-min-amd64' 'COPY a /' 'RUN /bin/ash /a' >Dockerfile + # podman rmi localhost/m2 ; podman build --squash-all -t m2 . && podman images && podman run --rm -it localhost/m2 --exit=idx && podman images +} + + + + + # goodbye exec rm innvikler.sh diff --git a/scripts/docker/make.sh b/scripts/docker/make.sh old mode 100755 new mode 100644 index 88758c2a..a6ff45e9 --- a/scripts/docker/make.sh +++ b/scripts/docker/make.sh @@ -18,6 +18,13 @@ ngs=( dj-{ppc64le,s390x,arm} ) +err= +for x in awk jq podman python3 tar wget ; do + command -v $x >/dev/null && continue + err=1; echo ERROR: missing dependency: $x +done +[ $err ] && exit 1 + for v in "$@"; do [ "$v" = clean ] && clean=1 [ "$v" = hclean ] && hclean=1 @@ -56,7 +63,7 @@ filt= for a in $sarchs; do # arm/v6 podman pull --arch=$a alpine:latest done - + podman images --format "{{.ID}} {{.History}}" | awk '/library\/alpine/{print$1}' | while read id; do @@ -102,12 +109,18 @@ filt= # arm takes forever so make it top priority [ ${a::3} == arm ] && nice= || nice=-n20 + # not sure if this is necessary or if inherit-annotations=false was enough, but won't hurt + readarray -t annot < <(awk > err; printf '%096d\n' $(seq 1 42)) diff --git a/scripts/help2html.py b/scripts/help2html.py index 540091ec..4803632b 100755 --- a/scripts/help2html.py +++ b/scripts/help2html.py @@ -7,7 +7,7 @@ import subprocess as sp # to convert the copyparty --help to html, run this in xfce4-terminal @ 140x43: _ = r"""" -echo; for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -chmod -pwhash -zm; do +echo; for a in '' -bind -accounts -auth -auth-ord -flags -handlers -hooks -idp -urlform -exp -ls -dbd -chmod -pwhash -zm; do ./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0139d\n\n\n'; done # xfce4-terminal @ 140x43 """ # click [edit] => [select all] diff --git a/scripts/help2txt.sh b/scripts/help2txt.sh index f5a12565..a046bda3 100755 --- a/scripts/help2txt.sh +++ b/scripts/help2txt.sh @@ -23,7 +23,7 @@ exit 0 # first open an infinitely wide console (this is why you own an ultrawide) and copypaste this into it: -for a in '' -bind -accounts -flags -handlers -hooks -urlform -exp -ls -dbd -chmod -pwhash -zm; do +for a in '' -bind -accounts -auth -auth-ord -flags -handlers -hooks -idp -urlform -exp -ls -dbd -chmod -pwhash -zm; do ./copyparty-sfx.py --help$a 2>/dev/null; printf '\n\n\n%0255d\n\n\n'; done # then copypaste all of the output by pressing ctrl-shift-a, ctrl-shift-c diff --git a/scripts/make-rpm.sh b/scripts/make-rpm.sh new file mode 100755 index 00000000..5dd6573c --- /dev/null +++ b/scripts/make-rpm.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -e + +#--localbuild to build webdeps and tar locally; otherwise just download prebuilt +#--pm change packagemanager; otherwise default to dnf + +while [ ! -z "$1" ]; do + case $1 in + local-build) local_build=1 ; ;; + pm) shift;packagemanager="$1"; ;; + esac + shift +done + +[ -e copyparty/__main__.py ] || cd .. +[ -e copyparty/__main__.py ] || +{ + echo "run me from within the project root folder" + echo + exit 1 +} + + +packagemanager=${packagemanager:-dnf} +ver=$(awk '/^VERSION/{gsub(/[^0-9]/," ");printf "%d.%d.%d\n",$1,$2,$3}' copyparty/__version__.py) +releasedir="dist/temp_copyparty_$ver" +sourcepkg="copyparty-$ver.tar.gz" + +#make temporary directory to build rpm in +mkdir -p $releasedir/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} +trap "rm -rf $releasedir" EXIT + +# make/get tarball +if [ $local_build ]; then + if [ ! -f "copyparty/web/deps/mini-fa.woff" ]; then + sudo $packagemanager update + sudo $packagemanager install podman-docker docker + make -C deps-docker + fi + if [ ! -f "dist/$sourcepkg" ]; then + ./$cppdir/scripts/make-sfx.sh gz fast # pulls some build-deps + good smoketest + ./$cppdir/scripts/make-tgz-release.sh "$ver" + fi +else + if [ ! -f "dist/$sourcepkg" ]; then + curl -OL https://github.com/9001/copyparty/releases/download/v$ver/$sourcepkg --output-dir dist + fi +fi + +cp dist/$sourcepkg "$releasedir/SOURCES/$sourcepkg" + +cp "contrib/package/rpm/copyparty.spec" "$releasedir/SPECS/" +sed -i "s/\$pkgver/$ver/g" "$releasedir/SPECS/copyparty.spec" +sed -i "s/\$pkgrel/1/g" "$releasedir/SPECS/copyparty.spec" + +sudo $packagemanager update +sudo $packagemanager install \ + rpmdevtools python-devel pyproject-rpm-macros \ + python-wheel python-setuptools python-jinja2 \ + make pigz +cd "$releasedir/" +rpmbuild --define "_topdir `pwd`" -bb SPECS/copyparty.spec +cd - + +rpm="copyparty-$ver-1.noarch.rpm" +mv "$releasedir/RPMS/noarch/$rpm" dist/$rpm diff --git a/scripts/make-sfx.sh b/scripts/make-sfx.sh index 1ef87943..9ec9601a 100755 --- a/scripts/make-sfx.sh +++ b/scripts/make-sfx.sh @@ -41,13 +41,11 @@ help() { exec cat <<'EOF' # `no-cm` saves ~89k by removing easymde/codemirror # (the fancy markdown editor) # -# `no-hl` saves ~41k by removing syntax hilighting in the text viewer +# `no-hl` saves ~41k by removing syntax highlighting in the text viewer # # `no-fnt` saves ~9k by removing the source-code-pro font # (browsers will try to use 'Consolas' instead) # -# `no-dd` saves ~2k by removing the mouse cursor -# # _____________________________________________________________________ # build behavior: # @@ -61,8 +59,8 @@ help() { exec cat <<'EOF' # # _____________________________________________________________________ # some usage examples: -# ./scripts/make-sfx.sh lang eng no-cm no-hl no-dd no-fnt no-smb no-pf -# ./scripts/rls.sh sfx lang eng no-cm no-hl no-dd no-fnt no-smb no-pf +# ./scripts/make-sfx.sh lang eng no-cm no-hl no-fnt no-smb no-pf +# ./scripts/rls.sh sfx lang eng no-cm no-hl no-fnt no-smb no-pf # (reduces v1.14.2 from 700k to 495k) EOF @@ -76,7 +74,6 @@ gtar=$(command -v gtar || command -v gnutar) || true sed() { gsed "$@"; } find() { gfind "$@"; } sort() { gsort "$@"; } - shuf() { gshuf "$@"; } nproc() { gnproc; } sha1sum() { shasum "$@"; } unexpand() { gunexpand "$@"; } @@ -123,7 +120,6 @@ while [ ! -z "$1" ]; do no-pf) no_pf=1 ; ;; no-fnt) no_fnt=1 ; ;; no-hl) no_hl=1 ; ;; - no-dd) no_dd=1 ; ;; no-cm) no_cm=1 ; ;; dl-wd) dl_wd=1 ; ;; ign-wd) ign_wd=1 ; ;; @@ -160,9 +156,9 @@ stamp=$( done | sort | tail -n 1 | sha1sum | cut -c-16 ) -rm -rf sfx$CSN/* -mkdir -p sfx$CSN build -cd sfx$CSN +rm -rf sfx/* +mkdir -p sfx build +cd sfx tmpdir="$( printf '%s\n' "$TMPDIR" /tmp | @@ -182,6 +178,8 @@ necho() { } [ $repack ] || { + (cd ../scripts; ./genlic.py ../copyparty/res/COPYING.txt) + necho collecting ipaddress f="../build/ipaddress-1.0.23.tar.gz" [ -e "$f" ] || @@ -221,6 +219,8 @@ necho() { tar -zxf $f mv pyftpdlib-*/pyftpdlib . rm -rf pyftpdlib-* pyftpdlib/test + patch -s -p1 <../scripts/patches/pyftpdlib-win313.patch + patch -s -p1 <../scripts/patches/pyftpdlib-fe80.patch for f in pyftpdlib/_async{hat,ore}.py; do [ -e "$f" ] || continue; iawk 'NR<4||NR>27||!/^#/;NR==4{print"# license: https://opensource.org/licenses/ISC\n"}' $f @@ -309,8 +309,6 @@ necho() { # remove type hints before build instead (cd copyparty; PYTHONPATH="..:$PYTHONPATH" "$pybin" ../../scripts/strip_hints/a.py; rm uh) - - (cd ../scripts; ./genlic.py ../copyparty/res/COPYING.txt) } [ ! -e copyparty/web/deps/mini-fa.woff ] && [ $dl_wd ] && { @@ -398,7 +396,7 @@ ts=$(date -u +%s) hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx) mkdir -p ../dist -sfx_out=../dist/copyparty-sfx$CSN +sfx_out=../dist/copyparty-sfx echo cleanup find -name '*.pyc' -delete @@ -456,13 +454,6 @@ rm -f ftp/pyftpdlib/{__main__,prefork}.py ised "s/src:.*scp.*\)/src:local('Consolas')/" $f } -[ $no_dd ] && { - rm -rf copyparty/web/dd - f=copyparty/web/browser.css - gzip -d "$f.gz" || true - ised 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' $f -} - [ $langs ] && { echo $langs | grep -q eng || { langs="eng|$langs" @@ -564,7 +555,7 @@ gzres() { } -zdir="$tmpdir/cpp-mksfx$CSN" +zdir="$tmpdir/cpp-mksfx" [ -e "$zdir/$stamp" ] || rm -rf "$zdir" mkdir -p "$zdir" echo a > "$zdir/$stamp" @@ -593,15 +584,7 @@ echo gen tarlist for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort | sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1 - -for n in {1..50}; do - (grep -vE '\.gz$' list1; grep -E '\.gz$' list1 | (shuf||gshuf) ) >list || true - s=$( (sha1sum||shasum) < list | cut -c-16) - grep -q $s "$zdir/h" 2>/dev/null && continue - echo $s >> "$zdir/h" - break -done -[ $n -eq 50 ] && exit +(grep -vE '\.gz$' list1; grep -E '\.gz$' list1) >list echo creating tar tar -cf tar "${targs[@]}" --numeric-owner -T list diff --git a/scripts/patches/pyftpdlib-fe80.patch b/scripts/patches/pyftpdlib-fe80.patch new file mode 100644 index 00000000..48b0cae1 --- /dev/null +++ b/scripts/patches/pyftpdlib-fe80.patch @@ -0,0 +1,37 @@ +accept connections from IPv6 link-local addresses + +diff -NarU1 a/pyftpdlib/handlers.py b/pyftpdlib2/handlers.py +--- a/pyftpdlib/handlers.py 2024-06-23 14:03:38 ++++ b/pyftpdlib/handlers.py 2025-08-22 21:59:40 +@@ -451,3 +451,4 @@ + +- local_ip = self.cmd_channel.socket.getsockname()[0] ++ sockname = list(self.cmd_channel.socket.getsockname()) ++ local_ip = sockname[0] + if local_ip in self.cmd_channel.masquerade_address_map: +@@ -459,3 +460,5 @@ + +- if self.cmd_channel.server.socket.family != socket.AF_INET: ++ if local_ip.startswith('fe') and local_ip[2:3] in "89ab": ++ af = socket.AF_INET6 # link-local ++ elif self.cmd_channel.server.socket.family != socket.AF_INET: + # dual stack IPv4/IPv6 support +@@ -472,3 +475,4 @@ + # free unprivileged random port. +- self.bind((local_ip, 0)) ++ sockname[1] = 0 ++ self.bind(tuple(sockname)) + else: +@@ -478,4 +482,5 @@ + self.set_reuse_addr() ++ sockname[1] = port + try: +- self.bind((local_ip, port)) ++ self.bind(tuple(sockname)) + except PermissionError: +@@ -495,3 +500,4 @@ + else: +- self.bind((local_ip, 0)) ++ sockname[1] = 0 ++ self.bind(tuple(sockname)) + self.cmd_channel.log( diff --git a/scripts/patches/pyftpdlib-win313.patch b/scripts/patches/pyftpdlib-win313.patch new file mode 100644 index 00000000..62601108 --- /dev/null +++ b/scripts/patches/pyftpdlib-win313.patch @@ -0,0 +1,41 @@ +Date: Tue, 22 Oct 2024 12:47:30 +0200 +Subject: Workaround for isabs() on Windows + Python 3.13 (#652) + +Starting from Python 3.13, `os.path.isabs("/foo")` on Windows return `False` + +diff --git a/pyftpdlib/filesystems.py b/pyftpdlib/filesystems.py +index 9b9326bf..320ffe40 100644 +--- a/pyftpdlib/filesystems.py ++++ b/pyftpdlib/filesystems.py +@@ -132,6 +132,16 @@ def cwd(self, path): + + # --- Pathname / conversion utilities + ++ @staticmethod ++ def _isabs(path, _windows=os.name == "nt"): ++ # Windows + Python 3.13: isabs() changed so that a path ++ # starting with "/" is no longer considered absolute. ++ # https://github.com/python/cpython/issues/44626 ++ # https://github.com/python/cpython/pull/113829/ ++ if _windows and path.startswith("/"): ++ return True ++ return os.path.isabs(path) ++ + def ftpnorm(self, ftppath): + """Normalize a "virtual" ftp pathname (typically the raw string + coming from client) depending on the current working directory. +@@ -146,3 +156,3 @@ + assert isinstance(ftppath, unicode), ftppath +- if os.path.isabs(ftppath): ++ if self._isabs(ftppath): + p = os.path.normpath(ftppath) +@@ -162,3 +172,3 @@ + # This is for extra protection, maybe not really necessary. +- if not os.path.isabs(p): ++ if not self._isabs(p): + p = u("/") +@@ -201,3 +211,3 @@ + assert isinstance(fspath, unicode), fspath +- if os.path.isabs(fspath): ++ if self._isabs(fspath): + p = os.path.normpath(fspath) diff --git a/scripts/prep.sh b/scripts/prep.sh index c08d09ed..4e9ae313 100755 --- a/scripts/prep.sh +++ b/scripts/prep.sh @@ -29,22 +29,18 @@ update_mpr_pkgbuild() { sha=$(sha256sum "$self/../dist/copyparty-$ver.tar.gz" | awk '{print$1}') - # awk -v ver=$ver -v sha=$sha ' - # /^pkgver=/{sub(/[0-9\.]+/,ver)}; - # /^sha256sums=/{sub(/[0-9a-f]{64}/,sha)}; - # 1' PKGBUILD >a - # mv a PKGBUILD - - echo thing 1 - sed -s -i "s/pkgver=\"\"/pkgver=\"$ver\"/" PKGBUILD - sed -s -i "s/sha256sums=(\".*\")/sha256sums=(\"$sha\")/" PKGBUILD + awk -v ver=$ver -v sha=$sha ' + /^pkgver=/{sub(/[0-9\.]+/,ver)}; + /^sha256sums=/{sub(/[0-9a-f]{64}/,sha)}; + 1' PKGBUILD >a + mv a PKGBUILD rm -rf x } update_nixos_pin() { ( cd $self/../contrib/package/nix/copyparty; - ./update.py $self/../dist/copyparty-sfx.py ) + ./update.py $self/../dist/copyparty-$ver.tar.gz ) } update_arch_pkgbuild diff --git a/scripts/pyinstaller/build.sh b/scripts/pyinstaller/build.sh index a444ff94..a7944a5c 100644 --- a/scripts/pyinstaller/build.sh +++ b/scripts/pyinstaller/build.sh @@ -17,6 +17,7 @@ uname -s | grep NT-10 && w10=1 || w7=1 [ $w7 ] && [ -e up2k.sh ] && [ ! "$1" ] && ./up2k.sh [ $w7 ] && pyv=37 || pyv=313 +[ $w7 ] && sfx=en || sfx=sfx esuf= [ $w7 ] && [ $m = 32 ] && esuf=32 [ $w7 ] && [ $m = 64 ] && esuf=-winpe64 @@ -33,8 +34,16 @@ dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.ico dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.py dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.rc +[ $sfx = en ] && { + dl https://192.168.123.1:3923/cpp/dist/copyparty-en.py + + st_en=$(cat copyparty-en.py | awk '/^STAMP = [0-9]+/{print$3;exit}') 2>/dev/null + st_sfx=$(cat copyparty-sfx.py | awk '/^STAMP = [0-9]+/{print$3;exit}') 2>/dev/null + [ $st_en ] && [ $st_en -ge $st_sfx ] || sfx=sfx +} + rm -rf $TEMP/pe-copyparty* -python copyparty-sfx.py --version +python copyparty-$sfx.py --version rm -rf mods; mkdir mods cp -pR $TEMP/pe-copyparty/{copyparty,partftpy}/ $TEMP/pe-copyparty/{ftp,j2}/* mods/ @@ -128,7 +137,7 @@ dist/copyparty.exe --version csum=$(sha512sum uplod.log +curl -fkT dist/copyparty.exe -HPW:wark https://192.168.123.1:3923/copyparty$esuf.exe >uplod.log cat uplod.log grep -q $csum uplod.log && echo upload OK || { diff --git a/scripts/pyinstaller/deps.sha512 b/scripts/pyinstaller/deps.sha512 index b698b95a..c16278b7 100644 --- a/scripts/pyinstaller/deps.sha512 +++ b/scripts/pyinstaller/deps.sha512 @@ -1,14 +1,14 @@ f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f84beadd139bfeef4982e40f21ca5d9065cb794eeb0e473e82 altgraph-0.17.4-py2.py3-none-any.whl -6a624018f30da375581d5751eca0080edbbe37f102f643f856279fcfded3a4379fd1b6fb0661cdb2e72bbbbc726ca714a1f5990cc348df365db62bc53e4c4503 Git-2.45.2-32-bit.exe 17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl b297ff66ec50cf5a1abcf07d6ac949644c5150ba094ffac974c5d27c81574c3e97ed814a47547f4b03a4c83ea0fb8f026433fca06a3f08e32742dc5c024f3d07 pywin32_ctypes-0.2.3-py3-none-any.whl -085d39ef4426aa5f097fbc484595becc16e61ca23fc7da4d2a8bba540a3b82e789e390b176c7151bdc67d01735cce22b1562cdb2e31273225a2d3e275851a4ad setuptools-70.3.0-py3-none-any.whl -644931f8e1764e168c257c11c77b3d2ac5408397d97b0eef98168a058efe793d3ab6900dc2e9c54923a2bd906dd66bfbff8db6ff43418513e530a1bd501c6ccd upx-5.0.1-win32.zip +fd88d00c7231f9fcbe0fb9037217b6ae5dcb5b06a98be56243400542671a32f003d594b2e1852f77d316e9327ad4c643321e1f9f3ea20cc8500ac5bba8ae170e upx-5.0.2-win32.zip # win7 +d9d30ff960365307833425e340bf0a642fd54e35155f18ff1d9746bc1ecf8ecb864aad24948735e8fd994503b659dc8968e6ba337eeb4568217ebdf97dd292dd Git-2.46.2-32-bit.exe 3253e86471e6f9fa85bfdb7684cd2f964ed6e35c6a4db87f81cca157c049bef43e66dfcae1e037b2fb904567b1e028aaeefe8983ba3255105df787406d2aa71e en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso ab0db0283f61a5bbe44797d74546786bf41685175764a448d2e3bd629f292f1e7d829757b26be346b5044d78c9c1891736d93237cee4b1b6f5996a902c86d15f en_windows_7_professional_with_sp1_x64_dvd_u_676939.iso d130bfa136bd171b9972b5c281c578545f2a84a909fdf18a6d2d71dd12fb3d512a7a1fa5cf7300433adece1d306eb2f22d7278f4c90e744e04dc67ba627a82c0 future-1.0.0-py3-none-any.whl 0b4d07434bf8d314f42893d90bce005545b44a509e7353a73cad26dc9360b44e2824218a1a74f8174d02eba87fba91baffa82c8901279a32ebc6b8386b1b4275 importlib_metadata-6.7.0-py3-none-any.whl +085d39ef4426aa5f097fbc484595becc16e61ca23fc7da4d2a8bba540a3b82e789e390b176c7151bdc67d01735cce22b1562cdb2e31273225a2d3e275851a4ad setuptools-70.3.0-py3-none-any.whl 9d2c31701a4d3fef553928c00528a48f9e1854ab5333528b50e358a214eba90029d687f039bcda5760b6fdf9f2de3bcf3784ae21a6374cf2a97a845d33b636c6 packaging-24.0-py3-none-any.whl 5d7462a584105bccaa9cf376f5a8c5827ead099c813c8af7392d478a4398f373d9e8cac7bbad2db51b335411ab966b21e119b1b1234c9a7ab70c6ddfc9306da6 pip-24.0-py3-none-any.whl f298e34356b5590dde7477d7b3a88ad39c622a2bcf3fcd7c53870ce8384dd510f690af81b8f42e121a22d3968a767d2e07595036b2ed7049c8ef4d112bcf3a61 pyinstaller-5.13.2-py3-none-win32.whl @@ -21,14 +21,15 @@ ea73aa54cc6d5db20dfb127e54562dabf890e4cd6171a91b10a51af2bcfc76e1d64cbdce4546df2d 479a63e14586ab2f2228208116fc149ed8ee7b1e4ff360754f5bda4bf765c61af2e04b5ef123976623d04df4976b7886e0445647269da81436bd0a7b5671d361 windows6.1-kb2533623-x86.msu ac96786e5d35882e0c5b724794329c9125c2b86ae7847f17acfc49f0d294312c6afc1c3f248655de3f0ccb4ca426d7957d02ba702f4a15e9fcd7e2c314e72c19 zipp-3.15.0-py3-none-any.whl # win10 +f4b4e330995ebe96c0bd06e16e5b26062ece9473f06d369775aa68eab261dedcf32dfdd159acaa227502bbf9fa2cd8bbe57cddb89fc6f2196fef7a9ed5a80ae9 Git-2.51.0-64-bit.exe 0a2cd4cadf0395f0374974cd2bc2407e5cc65c111275acdffb6ecc5a2026eee9e1bb3da528b35c7f0ff4b64563a74857d5c2149051e281cc09ebd0d1968be9aa en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso -16cc0c58b5df6c7040893089f3eb29c074aed61d76dae6cd628d8a89a05f6223ac5d7f3f709a12417c147594a87a94cc808d1e04a6f1e407cc41f7c9f47790d1 virtio-win-0.1.248.iso +16cc0c58b5df6c7040893089f3eb29c074aed61d76dae6cd628d8a89a05f6223ac5d7f3f709a12417c147594a87a94cc808d1e04a6f1e407cc41f7c9f47790d1 virtio-win-0.1.285.iso 9a7f40edc6f9209a2acd23793f3cbd6213c94f36064048cb8bf6eb04f1bdb2c2fe991cb09f77fe8b13e5cd85c618ef23573e79813b2fef899ab2f290cd129779 jinja2-3.1.6-py3-none-any.whl 00731cfdd9d5c12efef04a7161c90c1e5ed1dc4677aa88a1d4054aff836f3430df4da5262ed4289c21637358a9e10e5df16f76743cbf5a29bb3a44b146c19cf3 MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl 8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac72f35a624401f3f3b442882ba1cc4cadaf9c88558b5b8bdae packaging-25.0-py3-none-any.whl 3e39ea6e16b502d99a2e6544579095d0f7c6097761cd85135d5e929b9dec1b32e80669a846f94ee8c2cca9be2f5fe728625d09453988864c04e16bb8445c3f91 pillow-11.3.0-cp313-cp313-win_amd64.whl -59fbbcae044f4ee73d203ac74b553b27bfad3e6b2f3fb290fd3f8774753c6b545176b6b3399c240b092d131d152290ce732750accd962dc1e48e930be85f5e53 pyinstaller-6.14.1-py3-none-win_amd64.whl -fc6f3e144c5f5b662412de07cb8bf0c2eb3b3be21d19ec448aef3c4244d779b9ab8027fd67a4871e6e13823b248ea0f5a7a9241a53aef30f3b51a6d3cb5bdb3f pyinstaller_hooks_contrib-2025.5-py3-none-any.whl -2c7a52e223b8186c21009d3fa5ed6a856d8eb4ef3b98f5d24c378c6a1afbfa1378bd7a51d6addc500e263d7989efb544c862bf920055e740f137c702dfd9d18b python-3.13.5-amd64.exe +b9b98714dfca6fa80b0b3f222965724d63be9c54d19435d1fe768e07016913d6db8d6e043fcb185b55a9bd6fe370a80cf961814fc096046a5f4640d99ed575ef pyinstaller-6.15.0-py3-none-win_amd64.whl +cad0f7cf39de691813b1d4abc7d33f8bda99a87d9c5886039b814752e8690364150da26fb61b3e28d5698ff57a90e6dcd619ed2b64b04f72b5aadb75e201bdb0 pyinstaller_hooks_contrib-2025.8-py3-none-any.whl +36db028e9f3d6805a57e89320283c07bd5eb0bb15c6edcd2ae4a7e46b06bfe6c96ed0793e8936cbb09b4f6b680a3f06dace2220a1e7d8b74ab6047698871db9e python-3.13.7-amd64.exe 2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl diff --git a/scripts/pyinstaller/deps.txt b/scripts/pyinstaller/deps.txt index 3328c765..30dc9c19 100644 --- a/scripts/pyinstaller/deps.txt +++ b/scripts/pyinstaller/deps.txt @@ -27,7 +27,7 @@ https://support.microsoft.com/en-us/topic/microsoft-security-advisory-insecure-l see web.archive.org links below # direct links to version-frozen deps -https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.248-1/virtio-win-0.1.248.iso +https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.285-1/virtio-win-0.1.285.iso https://www.python.org/ftp/python/3.7.9/python-3.7.9-amd64.exe https://www.python.org/ftp/python/3.7.9/python-3.7.9.exe https://web.archive.org/web/20200412130846if_/https://download.microsoft.com/download/2/D/7/2D78D0DD-2802-41F5-88D6-DC1D559F206D/Windows6.1-KB2533623-x86.msu diff --git a/scripts/pyinstaller/loader.ico b/scripts/pyinstaller/loader.ico new file mode 100644 index 00000000..88d358e4 Binary files /dev/null and b/scripts/pyinstaller/loader.ico differ diff --git a/scripts/pyinstaller/notes.txt b/scripts/pyinstaller/notes.txt index 61f76fa4..f6dccd6e 100644 --- a/scripts/pyinstaller/notes.txt +++ b/scripts/pyinstaller/notes.txt @@ -14,9 +14,11 @@ qemu-system-x86_64 -m 4096 -enable-kvm --machine q35 -cpu host -smp 4 -usb -devi ## ============================================================ ## first-time setup in a stock win7x32sp1 and/or win10x64 vm: -## +## -grab & install from ftp2host: Git-2.45.2-32-bit.exe +grab & install from ftp2host: + win7: Git-2.46.2-32-bit.exe + win10: Git-*-64-bit.exe ...and do this on the host so you can grab these notes too: unix2dos <~/dev/copyparty/scripts/pyinstaller/notes.txt >~/dev/pyi/notes.txt @@ -30,7 +32,7 @@ fns=( altgraph-0.17.4-py2.py3-none-any.whl pefile-2023.2.7-py3-none-any.whl pywin32_ctypes-0.2.3-py3-none-any.whl - upx-5.0.1-win32.zip + upx-5.0.2-win32.zip ) [ $w10 ] && fns+=( jinja2-3.1.6-py3-none-any.whl @@ -38,9 +40,9 @@ fns=( mutagen-1.47.0-py3-none-any.whl packaging-25.0-py3-none-any.whl pillow-11.3.0-cp313-cp313-win_amd64.whl - pyinstaller-6.14.1-py3-none-win_amd64.whl - pyinstaller_hooks_contrib-2025.5-py3-none-any.whl - python-3.13.5-amd64.exe + pyinstaller-6.15.0-py3-none-win_amd64.whl + pyinstaller_hooks_contrib-2025.8-py3-none-any.whl + python-3.13.7-amd64.exe setuptools-80.9.0-py3-none-any.whl ) [ $w7 ] && fns+=( @@ -124,13 +126,13 @@ note: if you keep accidentally killing the vm with alt-f4 then remove "-device u rm -f win7-x32.qcow2 qemu-img create -f qcow2 win7-x32.qcow2 64g qemu-system-x86_64 -m 4096 -enable-kvm --machine q35 -cpu host -smp 4 -usb -device usb-tablet -net bridge,br=virhost0 -net nic,model=e1000e -drive file=win7-x32.qcow2,discard=unmap,detect-zeroes=unmap \ - -cdrom ~/iso/win7-X17-59183-english-32bit-professional.iso + -cdrom ~/iso/~/iso/en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso # win10: use virtio hdd and net (viostor+netkvm), but do NOT use qxl graphics (kills mouse cursor) rm -f win10-e2021.qcow2 qemu-img create -f qcow2 win10-e2021.qcow2 64g qemu-system-x86_64 -m 4096 -enable-kvm --machine q35 -cpu host -smp 4 -usb -device usb-tablet -net bridge,br=virhost0 -net nic,model=virtio -drive file=win10-e2021.qcow2,if=virtio,discard=unmap \ - -drive file=$HOME/iso/virtio-win-0.1.248.iso,media=cdrom -cdrom $HOME/iso/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso + -drive file=$HOME/iso/virtio-win-0.1.285.iso,media=cdrom -cdrom $HOME/iso/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso tweak stuff to your preference, but also do these steps in order: * press ctrl-alt-g so you don't accidentally alt-f4 the vm @@ -148,7 +150,7 @@ tweak stuff to your preference, but also do these steps in order: * WIN7-ONLY: connect to ftp, download 4g.nul to desktop, then delete it (poor man's fstrim...) and finally take snapshots of the VMs by copypasting this stuff into your shell: -vmsnap() { zstd --long=31 -vT0 -19 <$1 >$1.$2; }; +vmsnap() { zstd --long=31 -vT0 -19 -o $1.$2 $1; }; vmsnap win7-x32.qcow2 snap1 vmsnap win10-e2021.qcow2 snap1 diff --git a/scripts/pyinstaller/up2k.ico b/scripts/pyinstaller/up2k.ico new file mode 100644 index 00000000..adf90cb3 Binary files /dev/null and b/scripts/pyinstaller/up2k.ico differ diff --git a/scripts/rls.sh b/scripts/rls.sh index 613b53e0..d18f12f2 100755 --- a/scripts/rls.sh +++ b/scripts/rls.sh @@ -32,11 +32,8 @@ v=$1 rm -f ../dist/copyparty-sfx* shift ./make-sfx.sh "$@" -f=../dist/copyparty-sfx -[ -e $f.py ] && s= || s=-gz -# TODO: the -gz suffix is gone, can drop all the $s stuff probably - -$f$s.py --version >/dev/null +../dist/copyparty-sfx.py --version >/dev/null +mv ../dist/copyparty-{sfx,int}.py while [ "$1" ]; do case "$1" in @@ -46,27 +43,10 @@ while [ "$1" ]; do shift done -[ $parallel -gt 1 ] && { - printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n' - trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT - touch .sfx-run - min=99999999 - for ((a=0; a<$parallel; a++)); do - while [ -e .sfx-run ]; do - CSN=$a ./make-sfx.sh re "$@" - sz=$(wc -c <$f$a$s.py | awk '{print$1}') - [ $sz -ge $min ] && continue - mv $f$a$s.py $f$s.py.$sz - min=$sz - done & - done - read - exit -} +./make-pyz.sh -while true; do - mv $f$s.py $f$s.$(wc -c <$f$s.py | awk '{print$1}').py - ./make-sfx.sh re "$@" -done +./make-sfx.sh re lang eng "$@" +mv ../dist/copyparty-{sfx,en}.py +mv ../dist/copyparty-{int,sfx}.py # git tag -d v$v; git push --delete origin v$v diff --git a/scripts/sfx.ls b/scripts/sfx.ls index 1a56e3c4..66bcf9df 100644 --- a/scripts/sfx.ls +++ b/scripts/sfx.ls @@ -73,12 +73,6 @@ copyparty/web/browser.js, copyparty/web/browser2.html, copyparty/web/cf.html, copyparty/web/copyparty.gif, -copyparty/web/dd, -copyparty/web/dd/2.png, -copyparty/web/dd/3.png, -copyparty/web/dd/4.png, -copyparty/web/dd/5.png, -copyparty/web/dd/__init__.py, copyparty/web/deps, copyparty/web/deps/__init__.py, copyparty/web/deps/busy.mp3, @@ -106,6 +100,7 @@ copyparty/web/mde.html, copyparty/web/mde.js, copyparty/web/msg.css, copyparty/web/msg.html, +copyparty/web/opds.xml, copyparty/web/rups.css, copyparty/web/rups.html, copyparty/web/rups.js, diff --git a/scripts/test/smoketest.py b/scripts/test/smoketest.py index 7dbc3dad..a498b144 100644 --- a/scripts/test/smoketest.py +++ b/scripts/test/smoketest.py @@ -102,6 +102,7 @@ def tc1(vflags): "-p4321", "-e2dsa", "-e2tsr", + "--wram", "--ban-403=no", "--dbd=yolo", "--no-mutagen", diff --git a/scripts/tl.js b/scripts/tl.js index 40ffe8a4..4d4fcaba 100644 --- a/scripts/tl.js +++ b/scripts/tl.js @@ -81,6 +81,7 @@ var tl_cpanel = { "ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!", "ae1": "active downloads:", "af1": "show recent uploads", + "ag1": "view idp cache", }, }; @@ -315,6 +316,7 @@ var tl_browser = { "ct_qdel": 'when deleting files, only ask for confirmation once">qdel', "ct_dir1st": 'sort folders before files">📁 first', "ct_nsort": 'natural sort (for filenames with leading digits)">nsort', + "ct_utc": 'show all datetimes in UTC">UTC', "ct_readme": 'show README.md in folder listings">📜 readme', "ct_idxh": 'show index.html instead of folder listing">htm', "ct_sbars": 'show scrollbars">⟊', @@ -390,6 +392,8 @@ var tl_browser = { "mt_c2owa": "opus-weba, for iOS 17.5 and newer\">owa", "mt_c2caf": "opus-caf, for iOS 11 through 17\">caf", "mt_c2mp3": "use this on very old devices\">mp3", + "mt_c2flac": "best sound quality, but huge downloads\">flac", + "mt_c2wav": "uncompressed playback (even bigger)\">wav", "mt_c2ok": "nice, good choice", "mt_c2nd": "that's not the recommended output format for your device, but that's fine", "mt_c2ng": "your device does not seem to support this output format, but let's try anyways", diff --git a/scripts/toc.sh b/scripts/toc.sh index 8c3e22e9..611e092a 100755 --- a/scripts/toc.sh +++ b/scripts/toc.sh @@ -20,7 +20,7 @@ cat $f | awk ' o{next} /^#/{s=1;rs=0;pr()} /^#* *(nix package)/{rs=1} - /^#* *(themes|install on android|dev env setup|just the sfx|complete release|optional gpl stuff|nixos module|reverse-proxy perf)|```/{s=rs} + /^#* *(themes|install on android|install on iOS|dev env setup|just the sfx|complete release|optional gpl stuff|nixos module|reverse-proxy perf)|```/{s=rs} /^#/{ lv=length($1); sub(/[^ ]+ /,""); diff --git a/setup.py b/setup.py index 12b2aa48..ce5afb47 100755 --- a/setup.py +++ b/setup.py @@ -132,11 +132,11 @@ args = { "copyparty.stolen.ifaddr", "copyparty.web", "copyparty.web.a", - "copyparty.web.dd", "copyparty.web.deps", ], "install_requires": ["jinja2"], "extras_require": { + "all": ["argon2-cffi", "partftpy>=0.4.0", "Pillow", "pyftpdlib", "pyopenssl", "pyzmq"], "thumbnails": ["Pillow"], "thumbnails2": ["pyvips"], "audiotags": ["mutagen"], diff --git a/tests/res/idp/3.conf b/tests/res/idp/3.conf index 8e1fd8f8..b8d86a11 100644 --- a/tests/res/idp/3.conf +++ b/tests/res/idp/3.conf @@ -6,11 +6,11 @@ idp-h-grp: x-idp-group [/vu/${u}] - / + /u${u} accs: r: ${u} [/vg/${g}] - /b + /g${g} accs: r: @${g} diff --git a/tests/test_idp.py b/tests/test_idp.py index bd81378f..3378d3b1 100644 --- a/tests/test_idp.py +++ b/tests/test_idp.py @@ -63,7 +63,7 @@ class TestVFS(unittest.TestCase): cfgdir = os.path.join(here, "res", "idp") # globals are applied by main so need to cheat a little - xcfg = {"idp_h_usr": "x-idp-user", "idp_h_grp": "x-idp-group"} + xcfg = {"idp_h_usr": ["x-idp-user"], "idp_h_grp": "x-idp-group"} return here, cfgdir, xcfg @@ -121,6 +121,8 @@ class TestVFS(unittest.TestCase): self.assertNodes(au.vfs.nodes["vg"], ["iga"]) self.assertApEq(au.vfs.nodes["vu"].realpath, "") self.assertApEq(au.vfs.nodes["vg"].realpath, "") + self.assertApEq(au.vfs.nodes["vu"].nodes["iua"].realpath, "/uiua") + self.assertApEq(au.vfs.nodes["vg"].nodes["iga"].realpath, "/giga") self.assertAxs(au.vfs.axs, []) self.assertAxsAt(au, "vu/iua", [["iua"]]) # same as: self.assertAxs(self.nav(au, "vu/iua").axs, [["iua"]]) diff --git a/tests/test_vfs.py b/tests/test_vfs.py index 35c2cd10..5f1c7e91 100644 --- a/tests/test_vfs.py +++ b/tests/test_vfs.py @@ -57,8 +57,10 @@ class TestVFS(unittest.TestCase): t2 = list(sorted(lst)) self.assertEqual(t1, t2) - def test(self): - td = os.path.join(self.td, "vfs") + def wipe_vfs(self, td): + os.chdir("..") + if os.path.exists(td): + shutil.rmtree(td) os.mkdir(td) os.chdir(td) @@ -72,7 +74,12 @@ class TestVFS(unittest.TestCase): with open(fn, "w") as f: f.write(fn) + def test(self): + td = os.path.join(self.td, "vfs") + self.wipe_vfs(td) + # defaults + self.wipe_vfs(td) vfs = AuthSrv(Cfg(), self.log).vfs self.assertEqual(vfs.nodes, {}) self.assertEqual(vfs.vpath, "") @@ -81,6 +88,7 @@ class TestVFS(unittest.TestCase): self.assertAxs(vfs.axs.uwrite, ["*"]) # single read-only rootfs (relative path) + self.wipe_vfs(td) vfs = AuthSrv(Cfg(v=["a/ab/::r"]), self.log).vfs self.assertEqual(vfs.nodes, {}) self.assertEqual(vfs.vpath, "") @@ -89,6 +97,7 @@ class TestVFS(unittest.TestCase): self.assertAxs(vfs.axs.uwrite, []) # single read-only rootfs (absolute path) + self.wipe_vfs(td) vfs = AuthSrv(Cfg(v=[td + "//a/ac/../aa//::r"]), self.log).vfs self.assertEqual(vfs.nodes, {}) self.assertEqual(vfs.vpath, "") @@ -97,6 +106,7 @@ class TestVFS(unittest.TestCase): self.assertAxs(vfs.axs.uwrite, []) # read-only rootfs with write-only subdirectory (read-write for k) + self.wipe_vfs(td) vfs = AuthSrv( Cfg(a=["k:k"], v=[".::r:rw,k", "a/ac/acb:a/ac/acb:w:rw,k"]), self.log, @@ -161,6 +171,7 @@ class TestVFS(unittest.TestCase): self.assertEqual(list(virt), []) # admin-only rootfs with all-read-only subfolder + self.wipe_vfs(td) vfs = AuthSrv( Cfg(a=["k:k"], v=[".::rw,k", "a:a:r"]), self.log, @@ -185,6 +196,7 @@ class TestVFS(unittest.TestCase): self.assertEqual(vfs.can_access("/a", "k"), perm_ro) # breadth-first construction + self.wipe_vfs(td) vfs = AuthSrv( Cfg( v=[ @@ -207,6 +219,7 @@ class TestVFS(unittest.TestCase): self.undot(vfs, "./.././foo/..", "") # shadowing + self.wipe_vfs(td) vfs = AuthSrv(Cfg(v=[".::r", "b:a/ac:r"]), self.log).vfs fsp, r1, v1 = self.ls(vfs, "", "*") @@ -244,6 +257,7 @@ class TestVFS(unittest.TestCase): ).encode("utf-8") ) + self.wipe_vfs(td) au = AuthSrv(Cfg(c=[cfg_path]), self.log) self.assertEqual(au.acct["a"], "123") self.assertEqual(au.acct["asd"], "fgh:jkl") diff --git a/tests/util.py b/tests/util.py index 42a47907..527f9e61 100644 --- a/tests/util.py +++ b/tests/util.py @@ -17,7 +17,7 @@ from argparse import Namespace import jinja2 -from copyparty.__init__ import MACOS, WINDOWS, E +from copyparty.__init__ import ANYWIN, MACOS, WINDOWS, E J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) # type: ignore J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT") @@ -143,10 +143,10 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime magic nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl stats uqe 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 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_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 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 uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) - ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip" + ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump wram re_dhash see_dots plain_ip" ka.update(**{k: True for k in ex.split()}) ex = "ah_cli ah_gen css_browser dbpath hist ipu js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua ua_nodoc ua_nozip" @@ -155,22 +155,22 @@ class Cfg(Namespace): ex = "gid uid" ka.update(**{k: -1 for k in ex.split()}) - ex = "hash_mt hsortn qdel safe_dedup srch_time tail_fd tail_rate u2abort u2j u2sz" + ex = "hash_mt hsortn qdel safe_dedup scan_pr_r scan_pr_s scan_st_r srch_time tail_fd tail_rate th_spec_p u2abort u2j u2sz ui_filesz unp_who" ka.update(**{k: 1 for k in ex.split()}) - ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who" + ex = "ac_convt au_vol dl_list du_iwho mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who ver_iwho zip_who" ka.update(**{k: 9 for k in ex.split()}) - ex = "db_act forget_ip idp_store k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" + ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle qr_pin qr_wait re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs" ka.update(**{k: 0 for k in ex.split()}) - ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR" + ex = "ah_alg bname chdir chmod_f chpw_db doctitle df exit favico ipa html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf 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 shr tcolor textfiles txt_eol ufavico unlist vname xff_src zipmaxt R RS SR" ka.update(**{k: "" for k in ex.split()}) ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner" ka.update(**{k: "no" for k in ex.split()}) - ex = "ext_th grp on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm" + 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" ka.update(**{k: [] for k in ex.split()}) ex = "exp_lg exp_md" @@ -183,11 +183,17 @@ class Cfg(Namespace): v=v or [], c=c, E=E, + auth_ord="idp,ipu", bup_ck="sha512", + casechk="a" if ANYWIN or MACOS else "n", chmod_d="755", + cookie_cmax=8192, + cookie_nmax=50, dbd="wal", + du_who="all", dk_salt="b" * 16, fk_salt="a" * 16, + grp_all="acct", idp_gsep=re.compile("[|:;+,]"), iobuf=256 * 1024, lang="eng", @@ -201,8 +207,10 @@ class Cfg(Namespace): put_name="put-{now.6f}-{cip}.bin", mv_retry="0/0", rm_retry="0/0", + rotf_tz="UTC", s_rd_sz=256 * 1024, s_wr_sz=256 * 1024, + shr_who="auth", sort="href", srch_hits=99999, SRS="/", @@ -216,6 +224,7 @@ class Cfg(Namespace): u2sort="s", u2ts="c", unpost=600, + ver_who="all", warksalt="hunter2", **ka ) @@ -285,6 +294,7 @@ class VHttpSrv(object): self.broker = NullBroker(args, asrv) self.prism = None + self.ipr = None self.bans = {} self.tdls = self.dls = {} self.tdli = self.dli = {} @@ -340,6 +350,7 @@ class VHttpConn(object): Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv self.hsrv = Ctor(args, asrv, log) self.ico = Ico(args) + self.ipr = None self.ipa_nm = None self.lf_url = None self.log_func = log