mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
Compare commits
46 commits
v1.19.1
...
hovudstrau
Author | SHA1 | Date | |
---|---|---|---|
|
e7f2c6d806 | ||
|
a113d3b925 | ||
|
96cb5abf53 | ||
|
55c85d0984 | ||
|
782e2f1de3 | ||
|
f4727f8ea3 | ||
|
d4cf42e760 | ||
|
98d117b8ad | ||
|
d9046f7e01 | ||
|
dcc6b1b4ef | ||
|
274c074775 | ||
|
187cae25bf | ||
|
43a19779c1 | ||
|
23ea1c8a14 | ||
|
e3c7d6776e | ||
|
4df033ecc3 | ||
|
1228b5510b | ||
|
62e072a2ed | ||
|
a4649d1e71 | ||
|
f4a3fba29c | ||
|
3aa8b7aa2d | ||
|
d56230573d | ||
|
af8620da92 | ||
|
2961dea5bb | ||
|
4e878d2f1e | ||
|
7f44875061 | ||
|
68907eaf48 | ||
|
c4a4fddd27 | ||
|
5b62742512 | ||
|
554cc2f3ee | ||
|
6303effe59 | ||
|
659f351c65 | ||
|
d676a86f3f | ||
|
715d374ee4 | ||
|
c9fd608732 | ||
|
c32a672a68 | ||
|
69d9878acd | ||
|
d8662aeb0e | ||
|
a407eb9269 | ||
|
1ebe06f51e | ||
|
88243ac8d6 | ||
|
6ccc9224f3 | ||
|
0177a9b402 | ||
|
9435e6b2e2 | ||
|
0da93659a4 | ||
|
db2a03409c |
40
README.md
40
README.md
|
@ -90,7 +90,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
|
||||
|
@ -266,6 +268,7 @@ also see [comparison to similar software](./docs/versus.md)
|
|||
* ☑ 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)
|
||||
|
@ -512,6 +515,8 @@ 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`
|
||||
|
@ -537,6 +542,7 @@ and if you want to use config files instead of commandline args (good!) then her
|
|||
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]
|
||||
|
@ -1892,6 +1898,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
|
||||
|
@ -1911,6 +1931,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
|
||||
|
@ -2792,9 +2826,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`
|
||||
|
||||
|
@ -2825,9 +2860,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`
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver="1.19.0"
|
||||
pkgver="1.19.2"
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
|
@ -23,7 +23,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("etc/${pkgname}/copyparty.conf" )
|
||||
sha256sums=("179b027d51e4fe7ebdab2b18c07475d52c57e2ce69256292b157a8efacd82118")
|
||||
sha256sums=("9f0dcd8124f260a0c72676b70d84c82388cfe5b47e7d0556f5190c88208580a2")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver=1.19.0
|
||||
pkgver=1.19.2
|
||||
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=("179b027d51e4fe7ebdab2b18c07475d52c57e2ce69256292b157a8efacd82118")
|
||||
sha256sums=("9f0dcd8124f260a0c72676b70d84c82388cfe5b47e7d0556f5190c88208580a2")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
|
|
@ -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 ];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.19.0/copyparty-sfx.py",
|
||||
"version": "1.19.0",
|
||||
"hash": "sha256-9A+zPtkVtUuGHB/JJV3fhVtJderLUGxHqvuJQz0/1+Q="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.19.2/copyparty-1.19.2.tar.gz",
|
||||
"version": "1.19.2",
|
||||
"hash": "sha256-nw3NgSTyYKDHJna3DYTII4jP5bR+fQVW9RkMiCCFgKI="
|
||||
}
|
|
@ -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
|
||||
|
|
30
contrib/package/nix/partftpy/default.nix
Normal file
30
contrib/package/nix/partftpy/default.nix
Normal file
|
@ -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;
|
||||
};
|
||||
}
|
5
contrib/package/nix/partftpy/pin.json
Normal file
5
contrib/package/nix/partftpy/pin.json
Normal file
|
@ -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="
|
||||
}
|
50
contrib/package/nix/partftpy/update.py
Executable file
50
contrib/package/nix/partftpy/update.py
Executable file
|
@ -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()
|
|
@ -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
|
||||
'';
|
||||
}
|
|
@ -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
|
||||
'';
|
||||
}
|
62
contrib/package/rpm/copyparty.spec
Normal file
62
contrib/package/rpm/copyparty.spec
Normal file
|
@ -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
|
|
@ -436,6 +436,40 @@ 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 = supp + argv
|
||||
|
||||
n = spins = 0
|
||||
while n < len(argv):
|
||||
if not n:
|
||||
if spins % 1000 == 999:
|
||||
t = "still expanding config files... giving up after %d more"
|
||||
print(t % (9999 - spins))
|
||||
if spins > 9999:
|
||||
t = "got stuck expanding config files; do you have a config-file which imports itself? this is where I gave up:\n%r"
|
||||
raise Exception(t % (argv[:1000]))
|
||||
v1 = argv[n]
|
||||
v1v = v1[2:].lstrip("=")
|
||||
try:
|
||||
v2 = argv[n + 1]
|
||||
except:
|
||||
v2 = ""
|
||||
|
||||
if v1 == "-c" and v2 and os.path.isfile(v2):
|
||||
argv = argv[:n] + args_from_cfg(v2) + argv[n + 2 :]
|
||||
spins += 1
|
||||
n = 0
|
||||
elif v1.startswith("-c") and v1v and os.path.isfile(v1v):
|
||||
argv = argv[:n] + args_from_cfg(v1v) + argv[n + 1 :]
|
||||
spins += 1
|
||||
n = 0
|
||||
else:
|
||||
n += 1
|
||||
return argv
|
||||
|
||||
|
||||
def sighandler(sig: Optional[int] = None, frame: Optional[FrameType] = None) -> None:
|
||||
msg = [""] * 5
|
||||
for th in threading.enumerate():
|
||||
|
@ -609,8 +643,41 @@ 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
|
||||
"""
|
||||
),
|
||||
],
|
||||
|
@ -762,6 +829,36 @@ 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
|
||||
"""
|
||||
),
|
||||
],
|
||||
[
|
||||
"urlform",
|
||||
"how to handle url-form POSTs",
|
||||
|
@ -1019,14 +1116,15 @@ 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.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")
|
||||
|
||||
|
||||
def add_fs(ap):
|
||||
|
@ -1056,6 +1154,7 @@ def add_upload(ap):
|
|||
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")
|
||||
|
@ -1149,7 +1248,8 @@ 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.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="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\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")
|
||||
|
@ -1162,7 +1262,11 @@ def add_auth(ap):
|
|||
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("--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)
|
||||
|
||||
|
||||
def add_chpw(ap):
|
||||
|
@ -1318,6 +1422,7 @@ def add_optouts(ap):
|
|||
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 <title>")
|
||||
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")
|
||||
|
@ -1339,7 +1444,7 @@ def add_optouts(ap):
|
|||
def add_safety(ap):
|
||||
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 --hardlink --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)")
|
||||
|
@ -1361,6 +1466,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")
|
||||
|
@ -1427,11 +1534,12 @@ def add_thumbnail(ap):
|
|||
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)")
|
||||
|
@ -1440,16 +1548,19 @@ 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):
|
||||
|
@ -1561,13 +1672,14 @@ def add_og(ap):
|
|||
|
||||
|
||||
def add_ui(ap, retry):
|
||||
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("--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)")
|
||||
|
@ -1768,20 +1880,7 @@ def main(argv: Optional[list[str]] = None) -> None:
|
|||
|
||||
ensure_webdeps()
|
||||
|
||||
if CFG_DEF:
|
||||
supp = args_from_cfg(CFG_DEF[0])
|
||||
argv.extend(supp)
|
||||
|
||||
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"),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 19, 1)
|
||||
VERSION = (1, 19, 2)
|
||||
CODENAME = "usernames"
|
||||
BUILD_DT = (2025, 8, 10)
|
||||
BUILD_DT = (2025, 8, 17)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
|
|
@ -881,6 +881,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:
|
||||
|
@ -1099,6 +1108,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
|
||||
|
@ -1208,6 +1220,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:
|
||||
|
@ -1685,6 +1698,9 @@ class AuthSrv(object):
|
|||
self.log("\n{0}\n{1}{0}".format(t, "\n".join(slns)))
|
||||
raise
|
||||
|
||||
self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
|
||||
self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
|
||||
|
||||
self.setup_pwhash(acct)
|
||||
defpw = acct.copy()
|
||||
self.setup_chpw(acct)
|
||||
|
@ -1697,7 +1713,7 @@ 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 = ""
|
||||
|
@ -1870,7 +1886,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:
|
||||
|
@ -1881,6 +1897,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:
|
||||
|
@ -2201,12 +2227,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])
|
||||
|
@ -2537,7 +2563,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))
|
||||
|
@ -2827,7 +2853,7 @@ class AuthSrv(object):
|
|||
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
|
||||
|
@ -2884,7 +2910,7 @@ class AuthSrv(object):
|
|||
n = []
|
||||
q = "insert into us values (?,?,?)"
|
||||
accs = list(self.acct)
|
||||
if self.args.idp_h_usr and self.args.idp_cookie:
|
||||
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:
|
||||
|
|
|
@ -68,6 +68,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",
|
||||
|
@ -111,12 +112,14 @@ def vf_vmap() -> dict[str, str]:
|
|||
"tail_tmax",
|
||||
"tail_who",
|
||||
"tcolor",
|
||||
"th_spec_p",
|
||||
"txt_eol",
|
||||
"unlist",
|
||||
"u2abort",
|
||||
"u2ts",
|
||||
"uid",
|
||||
"gid",
|
||||
"unp_who",
|
||||
"ups_who",
|
||||
"zip_who",
|
||||
"zipmaxn",
|
||||
|
@ -260,7 +263,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)": {
|
||||
|
@ -341,6 +346,7 @@ 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",
|
||||
"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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -96,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
|
||||
|
@ -285,9 +289,12 @@ class FtpFs(AbstractedFS):
|
|||
# returning 550 is library-default and suitable
|
||||
raise FSE("No such file or directory")
|
||||
|
||||
if vfs.realpath:
|
||||
avfs = vfs.chk_ap(ap, st)
|
||||
if not avfs:
|
||||
raise FSE("Permission denied", 1)
|
||||
else:
|
||||
avfs = vfs
|
||||
|
||||
self.cwd = nwd
|
||||
(
|
||||
|
@ -402,8 +409,12 @@ class FtpFs(AbstractedFS):
|
|||
return st
|
||||
|
||||
def utime(self, path: str, timeval: float) -> None:
|
||||
try:
|
||||
ap = self.rv2a(path, w=True)[0]
|
||||
return bos.utime(ap, (timeval, timeval))
|
||||
return bos.utime(ap, (int(time.time()), int(timeval)))
|
||||
except Exception as ex:
|
||||
logging.error("ftp.utime: %s, %r", ex, ex)
|
||||
raise
|
||||
|
||||
def lstat(self, path: str) -> os.stat_result:
|
||||
ap = self.rv2a(path)[0]
|
||||
|
@ -492,7 +503,11 @@ class FtpHandler(FTPHandler):
|
|||
def ftp_STOR(self, file: str, mode: str = "w") -> Any:
|
||||
# Optional[str]
|
||||
vp = join(self.fs.cwd, file).lstrip("/")
|
||||
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(
|
||||
|
|
|
@ -562,7 +562,7 @@ 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]
|
||||
|
@ -570,11 +570,15 @@ class HttpCli(object):
|
|||
cookie_pw = cookies.get("cppws") or cookies.get("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
|
||||
|
||||
|
@ -620,8 +624,22 @@ 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:
|
||||
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:
|
||||
break
|
||||
idp_usr = self.headers.get(hn)
|
||||
if idp_usr:
|
||||
idp_grp = (
|
||||
self.headers.get(self.args.idp_h_grp) or ""
|
||||
|
@ -677,8 +695,14 @@ class HttpCli(object):
|
|||
else:
|
||||
self.log("unknown username: %r" % (idp_usr,), 1)
|
||||
|
||||
if self.args.have_ipu_or_ipr:
|
||||
if self.args.ipu and self.uname == "*":
|
||||
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]
|
||||
|
@ -700,7 +724,7 @@ 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:
|
||||
|
@ -1987,6 +2011,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()
|
||||
|
||||
|
@ -5474,6 +5501,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:
|
||||
|
@ -5481,9 +5512,23 @@ 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.lower()):
|
||||
pass
|
||||
|
@ -5608,8 +5653,8 @@ 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.lower()):
|
||||
pass
|
||||
|
@ -5630,6 +5675,7 @@ class HttpCli(object):
|
|||
"sz": sz,
|
||||
"ip": ip,
|
||||
"at": at,
|
||||
"un": un,
|
||||
"nfk": nfk,
|
||||
"adm": adm,
|
||||
}
|
||||
|
@ -5674,12 +5720,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:
|
||||
|
@ -5958,7 +6008,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
|
||||
|
||||
|
@ -5988,10 +6040,21 @@ 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(self, ls: dict[str, Any]) -> bool:
|
||||
dirs = ls["dirs"]
|
||||
files = ls["files"]
|
||||
|
@ -6588,13 +6651,15 @@ class HttpCli(object):
|
|||
tags = {k: v for k, v in r}
|
||||
|
||||
if is_admin:
|
||||
q = "select ip, at from up where rd=? and fn=?"
|
||||
q = "select ip, at, un from up where rd=? and fn=?"
|
||||
try:
|
||||
zs1, zs2 = icur.execute(q, erd_efn).fetchone()
|
||||
zs1, zs2, zs3 = icur.execute(q, erd_efn).fetchone()
|
||||
if zs1:
|
||||
tags["up_ip"] = zs1
|
||||
if zs2:
|
||||
tags[".up_at"] = zs2
|
||||
if zs3:
|
||||
tags["up_by"] = zs3
|
||||
except:
|
||||
pass
|
||||
elif add_up_at:
|
||||
|
@ -6615,7 +6680,7 @@ class HttpCli(object):
|
|||
|
||||
lmte = list(mte)
|
||||
if self.can_admin:
|
||||
lmte.extend(("up_ip", ".up_at"))
|
||||
lmte.extend(("up_by", "up_ip", ".up_at"))
|
||||
|
||||
if "nodirsz" not in vf:
|
||||
tagset.add(".files")
|
||||
|
|
|
@ -70,6 +70,7 @@ from .util import (
|
|||
build_netmap,
|
||||
has_resource,
|
||||
ipnorm,
|
||||
load_ipr,
|
||||
load_ipu,
|
||||
load_resource,
|
||||
min_ex,
|
||||
|
@ -193,6 +194,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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -176,6 +176,9 @@ def au_unpk(
|
|||
raise Exception("no images inside cbz")
|
||||
fi = zf.open(using)
|
||||
|
||||
elif pk == "epub":
|
||||
fi = get_cover_from_epub(log, abspath)
|
||||
|
||||
else:
|
||||
raise Exception("unknown compression %s" % (pk,))
|
||||
|
||||
|
@ -205,7 +208,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",
|
||||
|
@ -219,8 +222,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 = {}
|
||||
|
@ -313,7 +325,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():
|
||||
|
@ -362,7 +374,77 @@ 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>
|
||||
cover_id = package_root.find("./metadata/meta[@name='cover']", package_ns).get(
|
||||
"content"
|
||||
)
|
||||
|
||||
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):
|
||||
|
@ -633,7 +715,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)):
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
|
@ -38,6 +39,7 @@ from .th_srv import (
|
|||
HAVE_FFPROBE,
|
||||
HAVE_HEIF,
|
||||
HAVE_PIL,
|
||||
HAVE_RAW,
|
||||
HAVE_VIPS,
|
||||
HAVE_WEBP,
|
||||
ThumbSrv,
|
||||
|
@ -64,6 +66,7 @@ from .util import (
|
|||
build_netmap,
|
||||
expat_ver,
|
||||
gzip,
|
||||
load_ipr,
|
||||
load_ipu,
|
||||
lock_file,
|
||||
min_ex,
|
||||
|
@ -72,6 +75,7 @@ from .util import (
|
|||
pybin,
|
||||
start_log_thrs,
|
||||
start_stackmon,
|
||||
termsize,
|
||||
ub64enc,
|
||||
)
|
||||
|
||||
|
@ -152,6 +156,7 @@ class SvcHub(object):
|
|||
args.no_del = True
|
||||
args.no_mv = True
|
||||
args.hardlink = True
|
||||
args.dav_auth = True
|
||||
args.vague_403 = True
|
||||
args.nih = True
|
||||
|
||||
|
@ -240,7 +245,7 @@ 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:
|
||||
if args.chpw and args.have_idp_hdrs:
|
||||
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
||||
self.log("root", t, 1)
|
||||
raise Exception(t)
|
||||
|
@ -256,6 +261,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 +274,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:
|
||||
|
@ -321,6 +330,8 @@ class SvcHub(object):
|
|||
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)
|
||||
|
||||
|
@ -427,6 +438,9 @@ class SvcHub(object):
|
|||
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 +786,39 @@ class SvcHub(object):
|
|||
def sigterm(self) -> None:
|
||||
self.signal_handler(signal.SIGTERM, None)
|
||||
|
||||
def sticky_qr(self) -> None:
|
||||
tw, th = termsize()
|
||||
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
|
||||
|
||||
def unlock():
|
||||
print("\033[s\033[r\033[u", file=sys.stderr)
|
||||
|
||||
atexit.register(unlock)
|
||||
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)
|
||||
self.pr(t, file=sys.stderr)
|
||||
|
||||
def cb_httpsrv_up(self) -> None:
|
||||
self.httpsrv_up += 1
|
||||
if self.httpsrv_up != self.broker.num_workers:
|
||||
|
@ -784,6 +831,9 @@ class SvcHub(object):
|
|||
break
|
||||
|
||||
if self.tcpsrv.qr:
|
||||
if self.args.qr_pin:
|
||||
self.sticky_qr()
|
||||
else:
|
||||
self.log("qr-code", self.tcpsrv.qr)
|
||||
else:
|
||||
self.log("root", "workers OK\n")
|
||||
|
@ -811,6 +861,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 += [
|
||||
|
@ -969,10 +1020,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)
|
||||
|
||||
|
@ -1400,7 +1464,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:
|
||||
|
|
|
@ -614,6 +614,10 @@ 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)
|
||||
|
@ -641,6 +645,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:
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
@ -85,6 +86,9 @@ try:
|
|||
if os.environ.get("PRTY_NO_PIL_HEIF"):
|
||||
raise Exception()
|
||||
|
||||
try:
|
||||
from pillow_heif import register_heif_opener
|
||||
except ImportError:
|
||||
from pyheif_pillow_opener import register_heif_opener
|
||||
|
||||
register_heif_opener()
|
||||
|
@ -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,
|
||||
|
@ -368,6 +398,8 @@ 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)
|
||||
|
@ -480,9 +512,7 @@ class ThumbSrv(object):
|
|||
|
||||
return im
|
||||
|
||||
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:
|
||||
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:
|
||||
|
@ -510,6 +540,11 @@ class ThumbSrv(object):
|
|||
|
||||
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:
|
||||
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)
|
||||
crops = ["centre", "none"]
|
||||
|
@ -531,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
|
||||
|
||||
|
@ -544,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"
|
||||
|
@ -562,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",
|
||||
|
@ -583,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
|
||||
|
||||
|
@ -631,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")
|
||||
|
||||
|
@ -669,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"
|
||||
|
@ -690,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
|
||||
|
@ -730,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:
|
||||
|
@ -772,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()
|
||||
|
@ -780,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")
|
||||
|
||||
|
@ -811,14 +921,14 @@ 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, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
tags, _, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if "ac" not in tags:
|
||||
raise Exception("not audio")
|
||||
|
||||
|
@ -836,14 +946,14 @@ class ThumbSrv(object):
|
|||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
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, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
tags, _, _, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if "ac" not in tags:
|
||||
raise Exception("not audio")
|
||||
|
||||
|
@ -871,14 +981,14 @@ class ThumbSrv(object):
|
|||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd, vn, oom=300)
|
||||
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")
|
||||
|
||||
|
@ -927,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,
|
||||
|
@ -967,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
|
||||
|
@ -994,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
|
||||
|
@ -1013,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)
|
||||
|
|
|
@ -391,7 +391,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)
|
||||
|
|
|
@ -77,7 +77,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
|
||||
|
@ -144,6 +144,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
|
||||
|
@ -902,7 +903,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()
|
||||
|
||||
|
@ -1654,7 +1655,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:
|
||||
|
@ -1663,7 +1664,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])
|
||||
|
@ -1676,6 +1677,9 @@ 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)
|
||||
|
@ -1686,6 +1690,7 @@ class Up2k(object):
|
|||
dw = ""
|
||||
ip = ""
|
||||
at = 0
|
||||
un = ""
|
||||
|
||||
self.pp.msg = "a%d %s" % (self.pp.n, abspath)
|
||||
|
||||
|
@ -1711,9 +1716,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, {}, rd, fn, lmod, sz, "", "", wark, wark, "", un, ip, at)
|
||||
db.n += 1
|
||||
db.nf += 1
|
||||
tfa += 1
|
||||
|
@ -2150,8 +2156,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
|
||||
|
@ -2170,12 +2176,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)
|
||||
else:
|
||||
if ip:
|
||||
oth_tags = {"up_ip": ip, "up_at": at}
|
||||
n_tags = self._tagscan_file(cur, entags, w, abspath, ip, at, un)
|
||||
else:
|
||||
oth_tags = {}
|
||||
if ip:
|
||||
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:
|
||||
|
@ -2331,8 +2340,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)
|
||||
|
||||
|
@ -2356,7 +2365,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
|
||||
|
@ -2545,6 +2557,7 @@ class Up2k(object):
|
|||
abspath: str,
|
||||
ip: str,
|
||||
at: float,
|
||||
un: Optional[str],
|
||||
) -> int:
|
||||
"""will mutex(main)"""
|
||||
assert self.mtag # !rm
|
||||
|
@ -2565,7 +2578,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)
|
||||
|
@ -2669,16 +2685,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)
|
||||
|
@ -2780,7 +2799,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)",
|
||||
|
@ -2813,6 +2832,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:
|
||||
|
@ -3010,7 +3038,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)
|
||||
|
||||
|
@ -3432,7 +3460,7 @@ class Up2k(object):
|
|||
try:
|
||||
vrel = vjoin(job["prel"], fname)
|
||||
xlink = bool(vf.get("xlink"))
|
||||
cur, wark, _, _, _, _ = self._find_from_vpath(ptop, vrel)
|
||||
cur, wark, _, _, _, _, _ = self._find_from_vpath(ptop, vrel)
|
||||
self._forget_file(ptop, vrel, cur, wark, True, st.st_size, xlink)
|
||||
except Exception as ex:
|
||||
self.log("skipping replace-relink: %r" % (ex,))
|
||||
|
@ -3889,14 +3917,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
|
||||
|
@ -3988,6 +4016,9 @@ class Up2k(object):
|
|||
except:
|
||||
pass
|
||||
|
||||
def handle_fs_abrt(self, akey: str) -> None:
|
||||
self.abrt_key = akey
|
||||
|
||||
def handle_rm(
|
||||
self,
|
||||
uname: str,
|
||||
|
@ -4034,7 +4065,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 {}
|
||||
|
@ -4055,17 +4086,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"
|
||||
|
@ -4154,7 +4190,7 @@ 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
|
||||
)
|
||||
|
@ -4197,7 +4233,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")
|
||||
|
||||
|
@ -4244,6 +4280,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()
|
||||
|
@ -4313,7 +4351,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)
|
||||
|
@ -4338,7 +4376,7 @@ class Up2k(object):
|
|||
w,
|
||||
w,
|
||||
"",
|
||||
"",
|
||||
un or "",
|
||||
ip or "",
|
||||
at or 0,
|
||||
)
|
||||
|
@ -4411,7 +4449,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")
|
||||
|
||||
|
@ -4466,6 +4504,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()
|
||||
|
@ -4597,7 +4637,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
|
||||
|
@ -4631,7 +4671,7 @@ class Up2k(object):
|
|||
w,
|
||||
w,
|
||||
"",
|
||||
"",
|
||||
un or "",
|
||||
ip or "",
|
||||
at or 0,
|
||||
)
|
||||
|
@ -4731,13 +4771,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:
|
||||
|
@ -4746,9 +4787,9 @@ 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,
|
||||
|
|
|
@ -399,6 +399,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
|
||||
|
@ -2951,6 +2954,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:
|
||||
|
@ -3578,7 +3602,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]
|
||||
|
|
|
@ -48,6 +48,7 @@ window.baguetteBox = (function () {
|
|||
|
||||
var onFSC = function (e) {
|
||||
isFullscreen = !!document.fullscreenElement;
|
||||
clmod(document.documentElement, 'bb_fsc', isFullscreen);
|
||||
};
|
||||
|
||||
var overlayClickHandler = function (e) {
|
||||
|
@ -402,7 +403,7 @@ window.baguetteBox = (function () {
|
|||
if (isFullscreen)
|
||||
document.exitFullscreen();
|
||||
else
|
||||
(vid() || ebi('bbox-overlay')).requestFullscreen();
|
||||
ebi('bbox-overlay').requestFullscreen();
|
||||
}
|
||||
catch (ex) {
|
||||
if (IPHONE)
|
||||
|
|
|
@ -1377,7 +1377,7 @@ html.y #ops svg circle {
|
|||
.opview select,
|
||||
.opview input[type=text] {
|
||||
color: var(--fg);
|
||||
background: var(--bg-u5);
|
||||
background: var(--txt-bg);
|
||||
border: none;
|
||||
box-shadow: 0 0 2px var(--txt-sh);
|
||||
border-bottom: 1px solid #999;
|
||||
|
@ -1388,6 +1388,7 @@ html.y #ops svg circle {
|
|||
.opview select {
|
||||
padding: .3em;
|
||||
margin: .2em .4em;
|
||||
background: var(--bg-u3);
|
||||
}
|
||||
.opview input.err {
|
||||
color: var(--err-fg);
|
||||
|
@ -1930,6 +1931,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;
|
||||
|
@ -2120,6 +2126,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;
|
||||
|
@ -3272,3 +3285,790 @@ 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 #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;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
function render() {
|
||||
var html = ['<table id="tab"><thead><tr><th>size</th><th>who</th><th>when</th><th>age</th><th>dir</th><th>file</th></tr></thead><tbody>'];
|
||||
var html = ['<table id="tab"><thead><tr><th>size</th><th>who</th><th>ip</th><th>when</th><th>age</th><th>dir</th><th>file</th></tr></thead><tbody>'];
|
||||
var ups = V.ups, now = V.now;
|
||||
ebi('filter').value = V.filter;
|
||||
ebi('hits').innerHTML = 'showing ' + ups.length + ' files';
|
||||
|
@ -16,6 +16,7 @@ function render() {
|
|||
sz = ('' + f.sz).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||||
|
||||
html.push('<tr><td>' + sz +
|
||||
'</td><td>' + (f.un || '') +
|
||||
'</td><td>' + f.ip +
|
||||
'</td><td>' + ts +
|
||||
'</td><td>' + sa +
|
||||
|
|
|
@ -24,6 +24,7 @@ h1 {
|
|||
li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
#lo,
|
||||
a {
|
||||
color: #047;
|
||||
background: #fff;
|
||||
|
@ -47,6 +48,7 @@ td a {
|
|||
float: right;
|
||||
margin: -.2em 0 0 .8em;
|
||||
}
|
||||
#lo,
|
||||
.logout,
|
||||
a.r {
|
||||
color: #c04;
|
||||
|
@ -176,12 +178,14 @@ html.z {
|
|||
html.z h1 {
|
||||
border-color: #777;
|
||||
}
|
||||
html.z #lo,
|
||||
html.z a {
|
||||
color: #fff;
|
||||
background: #057;
|
||||
border-color: #37a;
|
||||
}
|
||||
html.z .logout,
|
||||
html.z #lo,
|
||||
html.z a.r {
|
||||
background: #804;
|
||||
border-color: #c28;
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
||||
{%- else %}
|
||||
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
||||
<p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
|
||||
<p><span id="m">welcome back,</span> <strong id="un">{{ this.uname|e }}</strong></p>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
||||
|
@ -168,6 +168,13 @@
|
|||
|
||||
<li><a id="af" href="{{ r }}/?ru">show recent uploads</a></li>
|
||||
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||
|
||||
{%- if this.uname != '*' %}
|
||||
<li><form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="act" value="logout" />
|
||||
<input type="submit" id="lo" value="logout “{{ this.uname|e }}” everywhere" />
|
||||
</form></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,11 @@ var Ls = {
|
|||
"j1": "k304 bryter tilkoplingen for hver HTTP 304. Dette hjelper mot visse mellomtjenere som kan sette seg fast / plutselig slutter å laste sider, men det reduserer også ytelsen betydelig",
|
||||
"k1": "nullstill innstillinger",
|
||||
"l1": "logg inn:",
|
||||
"ls3": "logg inn",
|
||||
"lu4": "brukernavn",
|
||||
"lp4": "passord",
|
||||
"lo3": "logg ut “{0}” overalt",
|
||||
"lo2": "avslutter økten på alle nettlesere",
|
||||
"m1": "velkommen tilbake,",
|
||||
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
||||
"o1": 'eller kanskje du ikke har tilgang? prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||
|
@ -46,6 +51,7 @@ var Ls = {
|
|||
"eng": {
|
||||
"d2": "shows the state of all active threads",
|
||||
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes$N$Nnote: any changes to global settings$Nrequire a full restart to take effect",
|
||||
"lo2": "ends the session on all browsers",
|
||||
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
||||
"v2": "use this server as a local HDD",
|
||||
"ta1": "fill in your new password first",
|
||||
|
@ -68,6 +74,11 @@ var Ls = {
|
|||
"j1": "k304 会在每个 HTTP 304 时断开连接。这有助于避免某些代理服务器卡住或突然停止加载页面,但也会显著降低性能。",
|
||||
"k1": "重置设置",
|
||||
"l1": "登录:",
|
||||
"ls3": "登录", //m
|
||||
"lu4": "用户名", //m
|
||||
"lp4": "密码", //m
|
||||
"lo3": "在所有地方注销 {0}", //m
|
||||
"lo2": "这将结束在所有浏览器中的会话", //m
|
||||
"m1": "欢迎回来,",
|
||||
"n1": "404: 文件不存在 ┐( ´ -`)┌",
|
||||
"o1": '或者你可能没有权限?尝试输入密码或 <a href="' + SR + '/?h">回家</a>',
|
||||
|
@ -110,6 +121,11 @@ var Ls = {
|
|||
"j1": "povolení k304 odpojí vašeho klienta při každém HTTP 304, což může zabránit některým chybovým proxy serverům, aby se zasekly (náhle nenačítaly stránky), <em>ale</em> také to obecně zpomalí věci",
|
||||
"k1": "resetovat nastavení klienta",
|
||||
"l1": "přihlaste se pro více:",
|
||||
"ls3": "přihlásit se", //m
|
||||
"lu4": "uživatelské jméno", //m
|
||||
"lp4": "heslo", //m
|
||||
"lo3": "odhlásit “{0}” všude", //m
|
||||
"lo2": "tímto ukončíte relaci ve všech prohlížečích", //m
|
||||
"m1": "vítej zpět,",
|
||||
"n1": "404 nenalezeno ┐( ´ -`)┌",
|
||||
"o1": 'nebo možná nemáš přístup -- zkus heslo nebo <a href="' + SR + '/?h">jdi domů</a>',
|
||||
|
@ -151,6 +167,11 @@ var Ls = {
|
|||
"j1": "k304 trennt die Clientverbindung bei jedem HTTP 304, was Bugs mit problematischen Proxies vorbeugen kann (z.B. nicht ladenden Seiten), macht Dinge aber generell langsamer",
|
||||
"k1": "Client-Einstellungen zurücksetzen",
|
||||
"l1": "Melde dich an für mehr:",
|
||||
"ls3": "Anmelden", //m
|
||||
"lu4": "Benutzername", //m
|
||||
"lp4": "Passwort", //m
|
||||
"lo3": "“{0}” überall abmelden", //m
|
||||
"lo2": "Dies beendet die Sitzung in allen Browsern", //m
|
||||
"m1": "Willkommen zurück,",
|
||||
"n1": "404 Nicht gefunden ┐( ´ -`)┌",
|
||||
"o1": 'or maybe you don\'t have access -- try a password or <a href="' + SR + '/?h">go home</a>',
|
||||
|
@ -192,6 +213,11 @@ var Ls = {
|
|||
"j1": "k304 katkaisee yhteytesi jokaisella HTTP 304:llä, mikä voi estää joitain bugisia välityspalvelimia jumittumasta/lopettamasta sivujen lataamista, <em>mutta</em> se myös vähentää suorituskykyä",
|
||||
"k1": "nollaa asetukset",
|
||||
"l1": "kirjaudu sisään:",
|
||||
"ls3": "kirjaudu sisään", //m
|
||||
"lu4": "käyttäjätunnus", //m
|
||||
"lp4": "salasana", //m
|
||||
"lo3": "kirjaa “{0}” ulos kaikkialta", //m
|
||||
"lo2": "tämä lopettaa istunnon kaikissa selaimissa", //m
|
||||
"m1": "tervetuloa takaisin,",
|
||||
"n1": "404: ei löytynyt mitään ┐( ´ -`)┌",
|
||||
"o1": 'tai ehkä sinulla ei vain ole käyttöoikeuksia? kokeile salasanaa tai <a href="' + SR + '/?h">mene kotiin</a>',
|
||||
|
@ -234,6 +260,11 @@ var Ls = {
|
|||
"j1": "activer k304 va déconnecter votre client sur chaque HTTP 304, ce qui peut éviter à certains proxies défectueux de rester bloqués (les pages ne se chargent soudainement plus), <em>mais</em> cela ralentira également les choses en général",
|
||||
"k1": "réinitialiser les paramètres du client",
|
||||
"l1": "connectez-vous pour en savoir plus :",
|
||||
"ls3": "se connecter", //m
|
||||
"lu4": "nom d'utilisateur", //m
|
||||
"lp4": "mot de passe", //m
|
||||
"lo3": "déconnecter “{0}” partout", //m
|
||||
"lo2": "cela mettra fin à la session sur tous les navigateurs", //m
|
||||
"m1": "heureux de vous revoir,",
|
||||
"n1": "404 introuvable ┐( ´ -`)┌",
|
||||
"o1": 'ou peut-être que vous n\'y avez pas accès -- essayer un mot de passe ou <a href="' + SR + '/?h">aller à la page d\'accueil</a>',
|
||||
|
@ -275,6 +306,11 @@ var Ls = {
|
|||
"j1": "η ενεργοποίηση του k304 θα αποσυνδέσει το πρόγραμμα πελάτη σου σε κάθε HTTP 304, κάτι που μπορεί να αποτρέψει κάποια προβληματικά proxies από το να κολλάνε (να μην φορτώνουν ξαφνικά σελίδες), <em>αλλά</em> θα κάνει τα πράγματα, γενικά πιο αργά",
|
||||
"k1": "επαναφορά ρυθμίσεων στο πρόγραμμα πελάτη",
|
||||
"l1": "συνδέσου για περισσότερα:",
|
||||
"ls3": "σύνδεση", //m
|
||||
"lu4": "όνομα χρήστη", //m
|
||||
"lp4": "κωδικός πρόσβασης", //m
|
||||
"lo3": "αποσύνδεση του “{0}” από παντού", //m
|
||||
"lo2": "αυτό θα τερματίσει τη συνεδρία σε όλους τους περιηγητές", //m
|
||||
"m1": "καλώς ήρθες,",
|
||||
"n1": "404 δεν βρέθηκε ┐( ´ -`)┌",
|
||||
"o1": '´η μήπως δεν έχεις πρόσβαση -- δοκίμασε έναν κωδικό <a href="' + SR + '/?h">πήγαινε στην αρχική</a>',
|
||||
|
@ -316,6 +352,11 @@ var Ls = {
|
|||
"j1": "k304 interrompe la connessione per ogni HTTP 304. Questo aiuta contro alcuni proxy difettosi che possono bloccarsi o smettere improvvisamente di caricare pagine, ma riduce notevolmente le prestazioni",
|
||||
"k1": "resetta impostazioni",
|
||||
"l1": "accedi:",
|
||||
"ls3": "accedi", //m
|
||||
"lu4": "nome utente", //m
|
||||
"lp4": "password", //m
|
||||
"lo3": "disconnetti “{0}” ovunque", //m
|
||||
"lo2": "questo terminerà la sessione su tutti i browser", //m
|
||||
"m1": "bentornato,",
|
||||
"n1": "404: file non trovato ┐( ´ -`)┌",
|
||||
"o1": "oppure forse non hai accesso? prova una password o <a href=\"SR/?h\">torna alla home</a>",
|
||||
|
@ -342,6 +383,53 @@ var Ls = {
|
|||
"af1": "mostra i file caricati di recente",
|
||||
"ag1": "mostra utenti IdP conosciuti"
|
||||
},
|
||||
"kor": {
|
||||
"a1": "새로고침",
|
||||
"b1": "어이 친구! 처음 보는 얼굴인데? <small>(로그인되어 있지 않습니다)</small>",
|
||||
"c1": "로그아웃",
|
||||
"d1": "스택 덤프하기",
|
||||
"d2": "모든 활성 스레드의 상태를 표시합니다",
|
||||
"e1": "설정 다시 불러오기",
|
||||
"e2": "설정 파일(계정/볼륨/볼륨 플래그)을 다시 불러오고,$N모든 e2ds 볼륨을 다시 스캔합니다$N$N참고: 전역 설정에 대한 변경 사항은$N적용하려면 전체 재시작이 필요합니다",
|
||||
"f1": "탐색 가능한 곳:",
|
||||
"g1": "업로드 가능한 곳:",
|
||||
"cc1": "기타 항목:",
|
||||
"h1": "k304 비활성화",
|
||||
"i1": "k304 활성화",
|
||||
"j1": "k304를 활성화하면 모든 HTTP 304 응답 시 클라이언트 연결이 끊어집니다. 이는 일부 프록시가 멈추는 현상(갑자기 페이지가 로드되지 않음)을 방지할 수 있지만, <em>대신 전반적인 속도는 느려집니다.</em>",
|
||||
"k1": "클라이언트 설정 초기화",
|
||||
"l1": "로그인하기:",
|
||||
"ls3": "로그인", //m
|
||||
"lu4": "사용자 이름", //m
|
||||
"lp4": "비밀번호", //m
|
||||
"lo3": "{0}을(를) 모든 곳에서 로그아웃", //m
|
||||
"lo2": "이 작업은 모든 브라우저에서 세션을 종료합니다", //m
|
||||
"m1": "또 오셨네요,",
|
||||
"n1": "404 찾을 수 없음 ┐( ´ -`)┌",
|
||||
"o1": "또는 접근 권한이 없을 수 있습니다. 비밀번호를 입력하거나 <a href=\"' + SR + '/?h\">홈으로 이동</a>하세요",
|
||||
"p1": "403 접근 금지 ~┻━┻",
|
||||
"q1": "비밀번호를 입력하거나 <a href=\"' + SR + '/?h\">홈으로 이동</a>하세요",
|
||||
"r1": "홈으로 이동",
|
||||
".s1": "다시 스캔",
|
||||
"t1": "작업",
|
||||
"u2": "서버에 마지막으로 쓰기 작업을 한 후 경과된 시간$N(업로드 / 이름 변경 / 등등...)$N$N17d = 17일$N1h23 = 1시간 23분$N4m56 = 4분 56초",
|
||||
"v1": "연결",
|
||||
"v2": "이 서버를 로컬 하드디스크처럼 사용하기",
|
||||
"w1": "HTTPS로 전환",
|
||||
"x1": "비밀번호 변경",
|
||||
"y1": "공유 설정",
|
||||
"z1": "이 공유 잠금해제:",
|
||||
"ta1": "새 비밀번호를 먼저 입력하세요",
|
||||
"ta2": "새 비밀번호 확인을 위해 다시 입력하세요:",
|
||||
"ta3": "오타가 있습니다. 다시 시도해주세요",
|
||||
"aa1": "수신 중인 파일:",
|
||||
"ab1": "no304 비활성화",
|
||||
"ac1": "no304 활성화",
|
||||
"ad1": "no304를 활성화하면 모든 캐싱이 비활성화됩니다. k304로 충분하지 않은 경우 시도해보세요. 네트워크 트래픽이 대량으로 낭비됩니다!",
|
||||
"ae1": "활성 다운로드:",
|
||||
"af1": "최근 업로드 보기",
|
||||
"ag1": "IdP 캐시 보기"
|
||||
},
|
||||
"nld": {
|
||||
"a1": "Update",
|
||||
"b1": "Hallo, hoe gaat het met jou? <small>(Je bent niet ingelogd)</small>",
|
||||
|
@ -358,6 +446,11 @@ var Ls = {
|
|||
"j1": "k304 verbreekt de verbinding voor elke HTTP 304. Dit helpt tegen bepaalde proxy servers die kunnen vastlopen/plotseling stoppen met het laden van pagina's, maar het vermindert ook de prestaties aanzienlijk",
|
||||
"k1": "Instellingen resetten",
|
||||
"l1": "Inloggen:",
|
||||
"ls3": "inloggen", //m
|
||||
"lu4": "gebruikersnaam", //m
|
||||
"lp4": "wachtwoord", //m
|
||||
"lo3": "“{0}” overal afmelden", //m
|
||||
"lo2": "dit zal de sessie in alle browsers beëindigen", //m
|
||||
"m1": "Welkom terug,",
|
||||
"n1": "404: bestand bestaat niet ┐( ´ -`)┌",
|
||||
"o1": 'of misschien heb je geen toegang? probeer een wachtwoord of <a href="' + SR + '/?h">ga naar startscherm</a>',
|
||||
|
@ -400,6 +493,11 @@ var Ls = {
|
|||
"j1": "k304 bryt tilkoplinga for kvar HTTP 304. Dette hjelp mot visse mellomtjenarar som kan sette seg fast / plutselig sluttar å laste sider, men det sett óg ytinga ned betydelig",
|
||||
"k1": "nullstill innstillinger",
|
||||
"l1": "logg inn:",
|
||||
"ls3": "logg inn",
|
||||
"lu4": "brukarnamn",
|
||||
"lp4": "passord",
|
||||
"lo3": "logg ut “{0}” overalt",
|
||||
"lo2": "avslutt økta på alle nettlesarar",
|
||||
"m1": "velkomen attende,",
|
||||
"n1": "404: filen finnast ikkje ┐( ´ -`)┌",
|
||||
"o1": 'eller kanskje du ikkje har høve? prøv eit passord eller <a href="' + SR + '/?h">gå heim</a>',
|
||||
|
@ -442,6 +540,11 @@ var Ls = {
|
|||
"j1": "włączenie k304 będzie odłączało klienta przy każdorazowym otrzymaniu kodu HTTP 304, co może zapobiec wieszaniu się wadliwych proxy, <em>ale</em> spowolni ogólne działanie",
|
||||
"k1": "zresetuj ustawienia klienta",
|
||||
"l1": "zaloguj się po więcej:",
|
||||
"ls3": "zaloguj się", //m
|
||||
"lu4": "nazwa użytkownika", //m
|
||||
"lp4": "hasło", //m
|
||||
"lo3": "wyloguj “{0}” wszędzie", //m
|
||||
"lo2": "spowoduje to zakończenie sesji we wszystkich przeglądarkach", //m
|
||||
"m1": "Witaj,",
|
||||
"n1": "404 nie znaleziono ┐( ´ -`)┌",
|
||||
"o1": 'lub możesz nie mieć dostępu -- spróbuj wprowadzić hasło lub <a href="' + SR + '/?h">przejdź do strony głównej</a>',
|
||||
|
@ -484,6 +587,11 @@ var Ls = {
|
|||
"j1": "activar k304 desconectará tu cliente en cada HTTP 304, lo que puede evitar que algunos proxies con errores se atasquen (dejando de cargar páginas de repente), <em>pero</em> también ralentizará las cosas en general",
|
||||
"k1": "restablecer config. de cliente",
|
||||
"l1": "inicia sesión para más:",
|
||||
"ls3": "iniciar sesión", //m
|
||||
"lu4": "nombre de usuario", //m
|
||||
"lp4": "contraseña", //m
|
||||
"lo3": "cerrar sesión de “{0}” en todas partes", //m
|
||||
"lo2": "esto finalizará la sesión en todos los navegadores", //m
|
||||
"m1": "bienvenido de nuevo,",
|
||||
"n1": "404 no encontrado ┐( ´ -`)┌",
|
||||
"o1": '¿o quizás no tienes acceso? -- prueba con una contraseña o <a href=\"' + SR + '/?h\">vuelve al inicio</a>',
|
||||
|
@ -510,6 +618,53 @@ var Ls = {
|
|||
"af1": "mostrar subidas recientes",
|
||||
"ag1": "mostrar usuarios IdP conocidos"
|
||||
},
|
||||
"swe": {
|
||||
"a1": "uppdatera",
|
||||
"b1": "tjena främling <small>(du är inte inloggad)</small>",
|
||||
"c1": "logga ut",
|
||||
"d1": "dumpa stacken",
|
||||
"d2": "visar tillståndet på alla aktiva trådar",
|
||||
"e1": "ladda om konfig.",
|
||||
"e2": "ladda om konfigurationsfiler (konton/volymer/volflaggor),$Noch skanna om alla e2ds-volymer$N$Nobs.: ändrade globala inställningar$Nkräver en fullständig omstart",
|
||||
"f1": "du kan bläddra:",
|
||||
"g1": "du kan ladda upp till:",
|
||||
"cc1": "annat:",
|
||||
"h1": "avaktivera k304",
|
||||
"i1": "aktivera k304",
|
||||
"j1": "med k304 aktiverad kommer klienten att koppla bort sig vid varje HTTP 304-fel, vilket kan hindra vissa buggiga proxyservrar från att fastna (sidor slutar ladda), <em>men</em> saker kommer också att bli långsammare i allmänhet",
|
||||
"k1": "återställ klientinställningar",
|
||||
"l1": "logga in för att se mer:",
|
||||
"ls3": "logga in", //m
|
||||
"lu4": "användarnamn", //m
|
||||
"lp4": "lösenord", //m
|
||||
"lo3": "logga ut “{0}” överallt", //m
|
||||
"lo2": "avsluta sessionen i alla webbläsare", //m
|
||||
"m1": "välkommen tillbaka,",
|
||||
"n1": "404 hittades inte ┐( ´ -`)┌",
|
||||
"o1": 'eller så har du kanske inte tillgång -- prova ett lösenord eller <a href="' + SR + '/?h">åk hem</a>',
|
||||
"p1": "403 nekat ~┻━┻",
|
||||
"q1": 'använd ett lösenord eller <a href="' + SR + '/?h">åk hem</a>',
|
||||
"r1": "åk hem",
|
||||
".s1": "skanna om",
|
||||
"t1": "åtgärd",
|
||||
"u2": "tid sedan senaste serverskrivning$N( uppladdning / namnbyte / ... )$N$N17d = 17 dagar$N1h23 = 1 timme 23 minuter$N4m56 = 4 minuter 56 sekunder",
|
||||
"v1": "koppla upp",
|
||||
"v2": "använd denna server som en lokal disk",
|
||||
"w1": "byt till https",
|
||||
"x1": "byt lösenord",
|
||||
"y1": "redigera utdelningar",
|
||||
"z1": "lås upp denna utdelning:",
|
||||
"ta1": "fyll i ditt nya lösenord",
|
||||
"ta2": "upprepa det nya lösenordet:",
|
||||
"ta3": "det blev fel; vänligen försök igen",
|
||||
"aa1": "inkommande filer:",
|
||||
"ab1": "avaktivera no304",
|
||||
"ac1": "aktivera no304",
|
||||
"ad1": "detta stänger av all cachning; prova detta om k304 inte räckte till. Detta kommer att slösa enorma mängder nätverkstrafik!",
|
||||
"ae1": "aktiva nedladdningar:",
|
||||
"af1": "visa senaste uppladdningar",
|
||||
"ag1": "visa idp-cache"
|
||||
},
|
||||
"ukr": {
|
||||
"a1": "оновити",
|
||||
"b1": "привітик, незнайомцю <small>(ви не авторизовані)</small>",
|
||||
|
@ -526,6 +681,11 @@ var Ls = {
|
|||
"j1": "увімкнення k304 буде відключати ваш клієнт при кожному HTTP 304, що може запобігти зависанню деяких глючних проксі (раптово перестають завантажувати сторінки), <em>але</em> це також зробить усе повільнішим загалом",
|
||||
"k1": "скинути налаштування клієнта",
|
||||
"l1": "авторизуйтесь для інших опцій:",
|
||||
"ls3": "увійти", //m
|
||||
"lu4": "ім'я користувача", //m
|
||||
"lp4": "пароль", //m
|
||||
"lo3": "вийти з облікового запису “{0}” всюди", //m
|
||||
"lo2": "це завершить сеанс у всіх браузерах", //m
|
||||
"m1": "з поверненням,",
|
||||
"n1": "404 не знайдено ┐( ´ -`)┌",
|
||||
"o1": 'або у вас немає доступу -- спробуйте авторизуватися або <a href="' + SR + '/?h">повернутися на головну</a>',
|
||||
|
@ -568,6 +728,11 @@ var Ls = {
|
|||
"j1": "включённый k304 будет отключать вас при получении HTTP 304, что может помочь при работе с некоторыми глючными прокси (перестают загружаться страницы), <em>но</em> это также сделает работу клиента медленнее",
|
||||
"k1": "сбросить локальные настройки",
|
||||
"l1": "авторизуйтесь для других опций:",
|
||||
"ls3": "войти", //m
|
||||
"lu4": "имя пользователя", //m
|
||||
"lp4": "пароль", //m
|
||||
"lo3": "выйти из “{0}” везде", //m
|
||||
"lo2": "это завершит сеанс во всех браузерах", //m
|
||||
"m1": "с возвращением,",
|
||||
"n1": "404 не найдено ┐( ´ -`)┌",
|
||||
"o1": 'или у вас нет доступа -- попробуйте авторизоваться или <a href="' + SR + '/?h">вернуться на главную</a>',
|
||||
|
@ -612,7 +777,14 @@ for (var k in (d || {})) {
|
|||
o[a].innerHTML = d[k];
|
||||
else if (f == 2)
|
||||
o[a].setAttribute("tt", d[k]);
|
||||
else if (f == 3)
|
||||
o[a].setAttribute("value", d[k]);
|
||||
else if (f == 4)
|
||||
o[a].setAttribute("placeholder", " " + d[k]);
|
||||
}
|
||||
var o1 = ebi('lo'), o2 = ebi('un');
|
||||
if (o1 && o2 && d.lo3)
|
||||
o1.setAttribute("value", d.lo3.format(o2.textContent));
|
||||
|
||||
try {
|
||||
if (is_idp) {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
|
||||
|
||||
{% if args.idp_h_usr %}
|
||||
{% if args.have_idp_hdrs %}
|
||||
<p style="line-height:2em"><b>WARNING:</b> this server is using IdP-based authentication, so this stuff may not work as advertised. Depending on server config, these commands can probably only be used to access areas which don't require authentication, unless you auth using any non-IdP accounts defined in the copyparty config. Please see <a href="https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients">the IdP docs</a></p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -2831,7 +2831,7 @@ function up2k_init(subtle) {
|
|||
if (!t.t_uploading)
|
||||
t.t_uploading = Date.now();
|
||||
|
||||
pvis.seth(t.n, 1, "🚀 send");
|
||||
pvis.seth(t.n, 1, "🚀 " + L.ul_send);
|
||||
|
||||
var chunksize = get_chunksize(t.size),
|
||||
car = pcar * chunksize,
|
||||
|
|
|
@ -1263,10 +1263,13 @@ function sethash(hv) {
|
|||
|
||||
function dl_file(url) {
|
||||
console.log('DL [%s]', url);
|
||||
var o = mknod('a');
|
||||
qsr('#dlfth');
|
||||
var o = mknod('a', 'dlfth');
|
||||
o.setAttribute('href', url);
|
||||
o.setAttribute('download', '');
|
||||
o.click();
|
||||
document.body.appendChild(o);
|
||||
ebi('dlfth').click();
|
||||
qsr('#dlfth');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0810-1226 `v1.19.1` archlinux fix
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* new translations:
|
||||
* #486 French (thx @Tr3yWay996, @Packingdustry, @Alee14, @jakubiakfr, @Equinoxs!) e9ddfccf 7aa21483 b87f8f1b
|
||||
* #463 Polish (thx @pufereq and @daimond113!) 392a4db5
|
||||
* #537 Nynorsk (thx @chinatsu!) 3931bc27
|
||||
* #549 custom mdns domain 3c78c6a8
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #539 FTP glitches when running on windows 8ba98877
|
||||
* #555 global-config didn't load through PRTY_CONFIG (thx @icxes!) 074e106e
|
||||
* macos: could take a while to establish webdav connection from finder a01870b7
|
||||
* ux:
|
||||
* dropdown colors 347cf6a5
|
||||
* case-sensitivity in filters e5e82295
|
||||
* iOS being too enthusiastic about using saved passwords 03acd65e
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0807-2213 `v1.19.0` usernames
|
||||
|
||||
|
|
|
@ -1083,7 +1083,7 @@
|
|||
th-x3: n # default
|
||||
|
||||
# image decoders, in order of preference
|
||||
th-dec: vips,pil,ff # default
|
||||
th-dec: vips,pil,raw,ff # default
|
||||
|
||||
# disable jpg output
|
||||
th-no-jpg
|
||||
|
@ -1115,6 +1115,9 @@
|
|||
# image formats to decode using pyvips
|
||||
th-r-vips: a,very,long,list,of,file,extensions # hint
|
||||
|
||||
# image formats to decode using rawpy
|
||||
th-r-raw: a,very,long,list,of,file,extensions # hint
|
||||
|
||||
# image formats to decode using ffmpeg
|
||||
th-r-ffi: a,very,long,list,of,file,extensions # hint
|
||||
|
||||
|
|
|
@ -354,7 +354,7 @@ 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 black==21.12b0 click==8.0.2 bandit pylint flake8 isort mypy # vscode tooling
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
13
flake.nix
13
flake.nix
|
@ -12,17 +12,14 @@
|
|||
}:
|
||||
{
|
||||
nixosModules.default = ./contrib/nixos/modules/copyparty.nix;
|
||||
overlays.default = final: prev: rec {
|
||||
overlays.default = final: prev: {
|
||||
copyparty = final.python3.pkgs.callPackage ./contrib/package/nix/copyparty {
|
||||
ffmpeg = final.ffmpeg-full;
|
||||
};
|
||||
|
||||
partyfuse = prev.callPackage ./contrib/package/nix/partyfuse {
|
||||
inherit copyparty;
|
||||
python3 = prev.python3.override {
|
||||
packageOverrides = pyFinal: pyPrev: {
|
||||
partftpy = pyFinal.callPackage ./contrib/package/nix/partftpy { };
|
||||
};
|
||||
|
||||
u2c = prev.callPackage ./contrib/package/nix/u2c {
|
||||
inherit copyparty;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -54,8 +51,6 @@
|
|||
packages = {
|
||||
inherit (pkgs)
|
||||
copyparty
|
||||
partyfuse
|
||||
u2c
|
||||
;
|
||||
default = self.packages.${system}.copyparty;
|
||||
};
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -14,11 +14,14 @@ 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 ./
|
||||
|
|
66
scripts/make-rpm.sh
Executable file
66
scripts/make-rpm.sh
Executable file
|
@ -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
|
|
@ -30,5 +30,5 @@ a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac7
|
|||
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
|
||||
3c37ea72ab062f65116a5faaaccbaa960a971797c78dbe6a504f41e562b018e7f28924647b5577fdb4fedfa61ffe0f1153842a8585f93b0332ed4d97905f6609 python-3.13.6-amd64.exe
|
||||
36db028e9f3d6805a57e89320283c07bd5eb0bb15c6edcd2ae4a7e46b06bfe6c96ed0793e8936cbb09b4f6b680a3f06dace2220a1e7d8b74ab6047698871db9e python-3.13.7-amd64.exe
|
||||
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl
|
||||
|
|
|
@ -40,7 +40,7 @@ fns=(
|
|||
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.6-amd64.exe
|
||||
python-3.13.7-amd64.exe
|
||||
setuptools-80.9.0-py3-none-any.whl
|
||||
)
|
||||
[ $w7 ] && fns+=(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ class Cfg(Namespace):
|
|||
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||
ka = {}
|
||||
|
||||
ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt 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_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_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 usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||
ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt 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_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 q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl 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"
|
||||
|
@ -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 srch_time tail_fd tail_rate th_spec_p u2abort u2j u2sz 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 mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who zip_who"
|
||||
ka.update(**{k: 9 for k in ex.split()})
|
||||
|
||||
ex = "ctl_re db_act forget_ip idp_cookie 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 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 txt_eol unlist vname xff_src zipmaxt R RS SR"
|
||||
ex = "ah_alg bname chmod_f chpw_db doctitle df exit favico 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 txt_eol 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 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
||||
ka.update(**{k: [] for k in ex.split()})
|
||||
|
||||
ex = "exp_lg exp_md"
|
||||
|
@ -185,9 +185,12 @@ class Cfg(Namespace):
|
|||
E=E,
|
||||
bup_ck="sha512",
|
||||
chmod_d="755",
|
||||
cookie_cmax=8192,
|
||||
cookie_nmax=50,
|
||||
dbd="wal",
|
||||
dk_salt="b" * 16,
|
||||
fk_salt="a" * 16,
|
||||
grp_all="acct",
|
||||
idp_gsep=re.compile("[|:;+,]"),
|
||||
iobuf=256 * 1024,
|
||||
lang="eng",
|
||||
|
|
Loading…
Reference in a new issue