Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c140eeee6b | ||
|
|
c5988a04f9 | ||
|
|
a2e0f98693 | ||
|
|
1111153f06 | ||
|
|
e5a836cb7d | ||
|
|
b0de84cbc5 | ||
|
|
cbb718e10d | ||
|
|
b5ad9369fe | ||
|
|
4401de0413 | ||
|
|
6e671c5245 | ||
|
|
08848be784 | ||
|
|
b599fbae97 | ||
|
|
a8dabc99f6 | ||
|
|
f1130db131 | ||
|
|
735ec35546 | ||
|
|
5a009a2a64 | ||
|
|
d9e9526247 | ||
|
|
5a8c3b8be0 | ||
|
|
1c9c17fb9b | ||
|
|
7f82449179 | ||
|
|
e455ec994e | ||
|
|
c111027420 | ||
|
|
abcdf479e6 | ||
|
|
ad2371f810 | ||
|
|
c4e2b0f95f | ||
|
|
3da62ec234 | ||
|
|
01233991f3 | ||
|
|
ee35974273 | ||
|
|
7037e7365e | ||
|
|
03b13e8a1c | ||
|
|
cdd2da0208 | ||
|
|
cec0e0cf02 | ||
|
|
8122ddedfe | ||
|
|
55a77c5e89 | ||
|
|
461f31582d | ||
|
|
f356faa278 | ||
|
|
9f034d9c4c | ||
|
|
ba52590ae4 | ||
|
|
92edea1de5 | ||
|
|
7ff46966da | ||
|
|
fca70b3508 | ||
|
|
70009cd984 | ||
|
|
8d8b88c4fd | ||
|
|
c4b0cccefd | ||
|
|
7c2beba555 | ||
|
|
7d8d94388b | ||
|
|
0b46b1a614 | ||
|
|
5153db6bff | ||
|
|
b0af4b3712 | ||
|
|
c8f4aeaefa | ||
|
|
00da74400c | ||
|
|
83fb569d61 | ||
|
|
5a62cb4869 | ||
|
|
687df2fabd | ||
|
|
cdd0794d6e | ||
|
|
dcc988135e | ||
|
|
3db117d85f | ||
|
|
ee9aad82dd | ||
|
|
2d6eb63fce | ||
|
|
ca001c8504 | ||
|
|
4e581c59da | ||
|
|
dbd42bc6bf | ||
|
|
c862ec1b64 | ||
|
|
f709140571 | ||
|
|
ef1c4b7a20 | ||
|
|
6c94a63f1c | ||
|
|
20669c73d3 | ||
|
|
0da719f4c2 | ||
|
|
373194c38a | ||
|
|
3d245431fc | ||
|
|
250c8c56f0 | ||
|
|
e136231c8e | ||
|
|
98ffaadf52 | ||
|
|
ebb1981803 | ||
|
|
72361c99e1 | ||
|
|
d5c9c8ebbd | ||
|
|
746229846d | ||
|
|
ffd7cd3ca8 | ||
|
|
b3cecabca3 | ||
|
|
662541c64c | ||
|
|
225bd80ea8 | ||
|
|
85e54980cc | ||
|
|
a19a0fa9f3 | ||
|
|
9bb6e0dc62 | ||
|
|
15ddcf53e7 | ||
|
|
6b54972ec0 | ||
|
|
0219eada23 | ||
|
|
8916bce306 | ||
|
|
99edba4fd9 | ||
|
|
64de3e01e8 |
24
.vscode/settings.json
vendored
24
.vscode/settings.json
vendored
@@ -22,6 +22,9 @@
|
|||||||
"terminal.ansiBrightCyan": "#9cf0ed",
|
"terminal.ansiBrightCyan": "#9cf0ed",
|
||||||
"terminal.ansiBrightWhite": "#ffffff",
|
"terminal.ansiBrightWhite": "#ffffff",
|
||||||
},
|
},
|
||||||
|
"python.terminal.activateEnvironment": false,
|
||||||
|
"python.analysis.enablePytestSupport": false,
|
||||||
|
"python.analysis.typeCheckingMode": "standard",
|
||||||
"python.testing.pytestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
"python.testing.unittestEnabled": true,
|
"python.testing.unittestEnabled": true,
|
||||||
"python.testing.unittestArgs": [
|
"python.testing.unittestArgs": [
|
||||||
@@ -31,23 +34,8 @@
|
|||||||
"-p",
|
"-p",
|
||||||
"test_*.py"
|
"test_*.py"
|
||||||
],
|
],
|
||||||
"python.linting.pylintEnabled": true,
|
// python3 -m isort --py=27 --profile=black ~/dev/copyparty/{copyparty,tests}/*.py && python3 -m black -t py27 ~/dev/copyparty/{copyparty,tests,bin}/*.py $(find ~/dev/copyparty/copyparty/stolen -iname '*.py')
|
||||||
"python.linting.flake8Enabled": true,
|
"editor.formatOnSave": false,
|
||||||
"python.linting.banditEnabled": true,
|
|
||||||
"python.linting.mypyEnabled": true,
|
|
||||||
"python.linting.flake8Args": [
|
|
||||||
"--max-line-length=120",
|
|
||||||
"--ignore=E722,F405,E203,W503,W293,E402,E501,E128,E226",
|
|
||||||
],
|
|
||||||
"python.linting.banditArgs": [
|
|
||||||
"--ignore=B104,B110,B112"
|
|
||||||
],
|
|
||||||
// python3 -m isort --py=27 --profile=black copyparty/
|
|
||||||
"python.formatting.provider": "none",
|
|
||||||
"[python]": {
|
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.autoIndent": "keep",
|
"editor.autoIndent": "keep",
|
||||||
@@ -58,6 +46,4 @@
|
|||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.makefile": "makefile"
|
"*.makefile": "makefile"
|
||||||
},
|
},
|
||||||
"python.linting.enabled": true,
|
|
||||||
"python.pythonPath": "/usr/bin/python3"
|
|
||||||
}
|
}
|
||||||
170
README.md
170
README.md
@@ -1,4 +1,6 @@
|
|||||||
# 💾🎉 copyparty
|
<img src="docs/logo.svg" width="250" align="right"/>
|
||||||
|
|
||||||
|
### 💾🎉 copyparty
|
||||||
|
|
||||||
turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
|
turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
||||||
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
||||||
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
||||||
|
* [shares](#shares) - share a file or folder by creating a temporary link
|
||||||
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
||||||
* [media player](#media-player) - plays almost every audio format there is
|
* [media player](#media-player) - plays almost every audio format there is
|
||||||
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
||||||
@@ -62,7 +65,8 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
|
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
|
||||||
* [browser ux](#browser-ux) - tweaking the ui
|
* [browser ux](#browser-ux) - tweaking the ui
|
||||||
* [opengraph](#opengraph) - discord and social-media embeds
|
* [opengraph](#opengraph) - discord and social-media embeds
|
||||||
* [file indexing](#file-indexing) - enables dedup and music search ++
|
* [file deduplication](#file-deduplication) - enable symlink-based upload deduplication
|
||||||
|
* [file indexing](#file-indexing) - enable music search, upload-undo, and better dedup
|
||||||
* [exclude-patterns](#exclude-patterns) - to save some time
|
* [exclude-patterns](#exclude-patterns) - to save some time
|
||||||
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
|
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
|
||||||
* [periodic rescan](#periodic-rescan) - filesystem monitoring
|
* [periodic rescan](#periodic-rescan) - filesystem monitoring
|
||||||
@@ -76,6 +80,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
||||||
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
||||||
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
||||||
|
* [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
|
* [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 dont wanna be indexed
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
||||||
* [themes](#themes)
|
* [themes](#themes)
|
||||||
@@ -85,6 +90,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [prometheus](#prometheus) - metrics/stats can be enabled
|
* [prometheus](#prometheus) - metrics/stats can be enabled
|
||||||
* [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
|
* [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
|
||||||
* [custom mimetypes](#custom-mimetypes) - change the association of a file extension
|
* [custom mimetypes](#custom-mimetypes) - change the association of a file extension
|
||||||
|
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
|
||||||
* [packages](#packages) - the party might be closer than you think
|
* [packages](#packages) - the party might be closer than you think
|
||||||
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
||||||
* [fedora package](#fedora-package) - does not exist yet
|
* [fedora package](#fedora-package) - does not exist yet
|
||||||
@@ -111,6 +117,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [HTTP API](#HTTP-API) - see [devnotes](./docs/devnotes.md#http-api)
|
* [HTTP API](#HTTP-API) - see [devnotes](./docs/devnotes.md#http-api)
|
||||||
* [dependencies](#dependencies) - mandatory deps
|
* [dependencies](#dependencies) - mandatory deps
|
||||||
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
||||||
|
* [dependency chickenbits](#dependency-chickenbits) - prevent loading an optional dependency
|
||||||
* [optional gpl stuff](#optional-gpl-stuff)
|
* [optional gpl stuff](#optional-gpl-stuff)
|
||||||
* [sfx](#sfx) - the self-contained "binary" (recommended!)
|
* [sfx](#sfx) - the self-contained "binary" (recommended!)
|
||||||
* [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
|
* [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
|
||||||
@@ -124,7 +131,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
|
|
||||||
just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
||||||
|
|
||||||
* or install through pypi: `python3 -m pip install --user -U copyparty`
|
* or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
|
||||||
* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
|
* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
|
||||||
* or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
|
* or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
|
||||||
* or if you are on android, [install copyparty in termux](#install-on-android)
|
* or if you are on android, [install copyparty in termux](#install-on-android)
|
||||||
@@ -194,7 +201,7 @@ firewall-cmd --reload
|
|||||||
also see [comparison to similar software](./docs/versus.md)
|
also see [comparison to similar software](./docs/versus.md)
|
||||||
|
|
||||||
* backend stuff
|
* backend stuff
|
||||||
* ☑ IPv6
|
* ☑ IPv6 + unix-sockets
|
||||||
* ☑ [multiprocessing](#performance) (actual multithreading)
|
* ☑ [multiprocessing](#performance) (actual multithreading)
|
||||||
* ☑ volumes (mountpoints)
|
* ☑ volumes (mountpoints)
|
||||||
* ☑ [accounts](#accounts-and-volumes)
|
* ☑ [accounts](#accounts-and-volumes)
|
||||||
@@ -579,9 +586,6 @@ images with the following names (see `--th-covers`) become the thumbnail of the
|
|||||||
* the order is significant, so if both `cover.png` and `folder.jpg` exist in a folder, it will pick the first matching `--th-covers` entry (`folder.jpg`)
|
* the order is significant, so if both `cover.png` and `folder.jpg` exist in a folder, it will pick the first matching `--th-covers` entry (`folder.jpg`)
|
||||||
* and, if you enable [file indexing](#file-indexing), it will also try those names as dotfiles (`.folder.jpg` and so), and then fallback on the first picture in the folder (if it has any pictures at all)
|
* and, if you enable [file indexing](#file-indexing), it will also try those names as dotfiles (`.folder.jpg` and so), and then fallback on the first picture in the folder (if it has any pictures at all)
|
||||||
|
|
||||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
|
||||||
* indicated by the audio files having the ▶ icon instead of 💾
|
|
||||||
|
|
||||||
enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
|
enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
|
||||||
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
|
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
|
||||||
* the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
|
* the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
|
||||||
@@ -745,6 +749,42 @@ file selection: click somewhere on the line (not the link itsef), then:
|
|||||||
you can move files across browser tabs (cut in one tab, paste in another)
|
you can move files across browser tabs (cut in one tab, paste in another)
|
||||||
|
|
||||||
|
|
||||||
|
## shares
|
||||||
|
|
||||||
|
share a file or folder by creating a temporary link
|
||||||
|
|
||||||
|
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or alternatively:
|
||||||
|
* select a folder first to share that folder instead
|
||||||
|
* select one or more files to share only those files
|
||||||
|
|
||||||
|
this feature was made with [identity providers](#identity-providers) in mind -- configure your reverseproxy to skip the IdP's access-control for a given URL prefix and use that to safely share specific files/folders sans the usual auth checks
|
||||||
|
|
||||||
|
when creating a share, the creator can choose any of the following options:
|
||||||
|
|
||||||
|
* password-protection
|
||||||
|
* expire after a certain time; `0` or blank means infinite
|
||||||
|
* allow visitors to upload (if the user who creates the share has write-access)
|
||||||
|
|
||||||
|
semi-intentional limitations:
|
||||||
|
|
||||||
|
* cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
|
||||||
|
* only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
|
||||||
|
* no option to "delete after first access" because tricky
|
||||||
|
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
|
||||||
|
* browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky)
|
||||||
|
|
||||||
|
specify `--shr /foobar` to enable this feature; a toplevel virtual folder named `foobar` is then created, and that's where all the shares will be served from
|
||||||
|
|
||||||
|
* you can name it whatever, `foobar` is just an example
|
||||||
|
* if you're using config files, put `shr: /foobar` inside the `[global]` section instead
|
||||||
|
|
||||||
|
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
|
||||||
|
|
||||||
|
after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there
|
||||||
|
|
||||||
|
**security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
|
||||||
|
|
||||||
|
|
||||||
## batch rename
|
## batch rename
|
||||||
|
|
||||||
select some files and press `F2` to bring up the rename UI
|
select some files and press `F2` to bring up the rename UI
|
||||||
@@ -1116,9 +1156,41 @@ NOTE: because discord (and maybe others) strip query args such as `?raw` in open
|
|||||||
if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
|
if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
|
||||||
|
|
||||||
|
|
||||||
|
## file deduplication
|
||||||
|
|
||||||
|
enable symlink-based upload deduplication globally with `--dedup` or per-volume with volflag `dedup`
|
||||||
|
|
||||||
|
when someone tries to upload a file that already exists on the server, the upload will be politely declined and a symlink is created instead, pointing to the nearest copy on disk, thus reducinc disk space usage
|
||||||
|
|
||||||
|
**warning:** when enabling dedup, you should also:
|
||||||
|
* enable indexing with `-e2dsa` or volflag `e2dsa` (see [file indexing](#file-indexing) section below); strongly recommended
|
||||||
|
* ...and/or `--hardlink-only` to use hardlink-based deduplication instead of symlinks; see explanation below
|
||||||
|
|
||||||
|
it will not be safe to rename/delete files if you only enable dedup and none of the above; if you enable indexing then it is not *necessary* to also do hardlinks (but you may still want to)
|
||||||
|
|
||||||
|
by default, deduplication is done based on symlinks (symbolic links); these are tiny files which are pointers to the nearest full copy of the file
|
||||||
|
|
||||||
|
you can choose to use hardlinks instead of softlinks, globally with `--hardlink-only` or volflag `hardlinkonly`;
|
||||||
|
|
||||||
|
advantages of using hardlinks:
|
||||||
|
* hardlinks are more compatible with other software; they behave entirely like regular files
|
||||||
|
* you can safely move and rename files using other file managers
|
||||||
|
* symlinks need to be managed by copyparty to ensure the destinations remain correct
|
||||||
|
|
||||||
|
advantages of using symlinks (default):
|
||||||
|
* each symlink can have its own last-modified timestamp, but a single timestamp is shared by all hardlinks
|
||||||
|
* symlinks make it more obvious to other software that the file is not a regular file, so this can be less dangerous
|
||||||
|
* hardlinks look like regular files, so other software may assume they are safe to edit without affecting the other copies
|
||||||
|
|
||||||
|
**warning:** if you edit the contents of a deduplicated file, then you will also edit all other copies of that file! This is especially surprising with hardlinks, because they look like regular files, but that same file exists in multiple locations
|
||||||
|
|
||||||
|
global-option `--xlink` / volflag `xlink` additionally enables deduplication across volumes, but this is probably buggy and not recommended
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## file indexing
|
## file indexing
|
||||||
|
|
||||||
enables dedup and music search ++
|
enable music search, upload-undo, and better dedup
|
||||||
|
|
||||||
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volflags, or a mix of both.
|
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volflags, or a mix of both.
|
||||||
|
|
||||||
@@ -1132,7 +1204,6 @@ through arguments:
|
|||||||
* `-e2v` verfies file integrity at startup, comparing hashes from the db
|
* `-e2v` verfies file integrity at startup, comparing hashes from the db
|
||||||
* `-e2vu` patches the database with the new hashes from the filesystem
|
* `-e2vu` patches the database with the new hashes from the filesystem
|
||||||
* `-e2vp` panics and kills copyparty instead
|
* `-e2vp` panics and kills copyparty instead
|
||||||
* `--xlink` enables deduplication across volumes
|
|
||||||
|
|
||||||
the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`, `d2ts`, `d2v` for disabling:
|
the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`, `d2ts`, `d2v` for disabling:
|
||||||
* `-v ~/music::r:c,e2ds,e2tsr` does a full reindex of everything on startup
|
* `-v ~/music::r:c,e2ds,e2tsr` does a full reindex of everything on startup
|
||||||
@@ -1145,7 +1216,6 @@ note:
|
|||||||
* upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
|
* upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
|
||||||
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
||||||
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
|
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
|
||||||
* deduplication is possible on windows if you run copyparty as administrator (not saying you should!)
|
|
||||||
|
|
||||||
### exclude-patterns
|
### exclude-patterns
|
||||||
|
|
||||||
@@ -1314,6 +1384,8 @@ you can set hooks before and/or after an event happens, and currently you can ho
|
|||||||
|
|
||||||
there's a bunch of flags and stuff, see `--help-hooks`
|
there's a bunch of flags and stuff, see `--help-hooks`
|
||||||
|
|
||||||
|
if you want to write your own hooks, see [devnotes](./docs/devnotes.md#event-hooks)
|
||||||
|
|
||||||
|
|
||||||
### upload events
|
### upload events
|
||||||
|
|
||||||
@@ -1354,6 +1426,29 @@ there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik)
|
|||||||
|
|
||||||
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
|
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
|
||||||
|
|
||||||
|
but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead
|
||||||
|
|
||||||
|
|
||||||
|
## user-changeable passwords
|
||||||
|
|
||||||
|
if permitted, users can change their own passwords in the control-panel
|
||||||
|
|
||||||
|
* not compatible with [identity providers](#identity-providers)
|
||||||
|
|
||||||
|
* must be enabled with `--chpw` because account-sharing is a popular usecase
|
||||||
|
|
||||||
|
* if you want to enable the feature but deny password-changing for a specific list of accounts, you can do that with `--chpw-no name1,name2,name3,...`
|
||||||
|
|
||||||
|
* to perform a password reset, edit the server config and give the user another password there, then do a [config reload](#server-config) or server restart
|
||||||
|
|
||||||
|
* the custom passwords are kept in a textfile at filesystem-path `--chpw-db`, by default `chpw.json` in the copyparty config folder
|
||||||
|
|
||||||
|
* if you run multiple copyparty instances with different users you *almost definitely* want to specify separate DBs for each instance
|
||||||
|
|
||||||
|
* if [password hashing](#password-hashing) is enbled, the passwords in the db are also hashed
|
||||||
|
|
||||||
|
* ...which means that all user-defined passwords will be forgotten if you change password-hashing settings
|
||||||
|
|
||||||
|
|
||||||
## using the cloud as storage
|
## using the cloud as storage
|
||||||
|
|
||||||
@@ -1458,6 +1553,8 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
|
|||||||
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
||||||
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
||||||
|
|
||||||
|
for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/tmp/party.sock` (permission `770` means only members of group `www` can access it)
|
||||||
|
|
||||||
example webserver configs:
|
example webserver configs:
|
||||||
|
|
||||||
* [nginx config](contrib/nginx/copyparty.conf) -- entire domain/subdomain
|
* [nginx config](contrib/nginx/copyparty.conf) -- entire domain/subdomain
|
||||||
@@ -1558,6 +1655,23 @@ in a config-file, this is the same as:
|
|||||||
run copyparty with `--mimes` to list all the default mappings
|
run copyparty with `--mimes` to list all the default mappings
|
||||||
|
|
||||||
|
|
||||||
|
### feature chickenbits
|
||||||
|
|
||||||
|
buggy feature? rip it out by setting any of the following environment variables to disable its associated bell or whistle,
|
||||||
|
|
||||||
|
| env-var | what it does |
|
||||||
|
| -------------------- | ------------ |
|
||||||
|
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
|
||||||
|
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
|
||||||
|
| `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
|
||||||
|
| `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
|
||||||
|
| `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
|
||||||
|
| `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
|
||||||
|
| `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider |
|
||||||
|
|
||||||
|
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
|
||||||
|
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
|
|
||||||
the party might be closer than you think
|
the party might be closer than you think
|
||||||
@@ -1841,6 +1955,8 @@ below are some tweaks roughly ordered by usefulness:
|
|||||||
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
|
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
|
||||||
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
|
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
|
||||||
* and also makes thumbnails load faster, regardless of e2d/e2t
|
* and also makes thumbnails load faster, regardless of e2d/e2t
|
||||||
|
* `--dedup` enables deduplication and thus avoids writing to the HDD if someone uploads a dupe
|
||||||
|
* `--safe-dedup 1` makes deduplication much faster during upload by skipping verification of file contents; safe if there is no other software editing/moving the files in the volumes
|
||||||
* `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
|
* `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
|
||||||
* if your volumes are on a network-disk such as NFS / SMB / s3, specifying larger values for `--iobuf` and/or `--s-rd-sz` and/or `--s-wr-sz` may help; try setting all of them to `524288` or `1048576` or `4194304`
|
* if your volumes are on a network-disk such as NFS / SMB / s3, specifying larger values for `--iobuf` and/or `--s-rd-sz` and/or `--s-wr-sz` may help; try setting all of them to `524288` or `1048576` or `4194304`
|
||||||
* `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
|
* `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
|
||||||
@@ -1880,6 +1996,7 @@ some notes on hardening
|
|||||||
* cors doesn't work right otherwise
|
* cors doesn't work right otherwise
|
||||||
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
|
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
|
||||||
* this returns html documents as plaintext, and also disables markdown rendering
|
* this returns html documents as plaintext, and also disables markdown rendering
|
||||||
|
* when running behind a reverse-proxy, listen on a unix-socket for tighter access control (and more performance); see [reverse-proxy](#reverse-proxy) or `--help-bind`
|
||||||
|
|
||||||
safety profiles:
|
safety profiles:
|
||||||
|
|
||||||
@@ -1894,7 +2011,7 @@ safety profiles:
|
|||||||
* `--hardlink` creates hardlinks instead of symlinks when deduplicating uploads, which is less maintenance
|
* `--hardlink` creates hardlinks instead of symlinks when deduplicating uploads, which is less maintenance
|
||||||
* however note if you edit one file it will also affect the other copies
|
* however note if you edit one file it will also affect the other copies
|
||||||
* `--vague-403` returns a "404 not found" instead of "401 unauthorized" which is a common enterprise meme
|
* `--vague-403` returns a "404 not found" instead of "401 unauthorized" which is a common enterprise meme
|
||||||
* `--nih` removes the server hostname from directory listings
|
* `-nih` removes the server hostname from directory listings
|
||||||
|
|
||||||
* option `-sss` is a shortcut for the above plus:
|
* option `-sss` is a shortcut for the above plus:
|
||||||
* `--no-dav` disables webdav support
|
* `--no-dav` disables webdav support
|
||||||
@@ -2044,6 +2161,37 @@ enable [smb](#smb-server) support (**not** recommended):
|
|||||||
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
|
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
|
||||||
|
|
||||||
|
|
||||||
|
### dependency chickenbits
|
||||||
|
|
||||||
|
prevent loading an optional dependency , for example if:
|
||||||
|
|
||||||
|
* you have an incompatible version installed and it causes problems
|
||||||
|
* you just don't want copyparty to use it, maybe to save ram
|
||||||
|
|
||||||
|
set any of the following environment variables to disable its associated optional feature,
|
||||||
|
|
||||||
|
| env-var | what it does |
|
||||||
|
| -------------------- | ------------ |
|
||||||
|
| `PRTY_NO_ARGON2` | disable argon2-cffi password hashing |
|
||||||
|
| `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
|
||||||
|
| `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
|
||||||
|
| `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
|
||||||
|
| `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
|
||||||
|
| `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 3rd-party Pillow plugin for [AVIF support](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_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_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`
|
||||||
|
|
||||||
|
* `PRTY_NO_PIL` saves ram
|
||||||
|
* `PRTY_NO_VIPS` saves ram and startup time
|
||||||
|
* python2.7 on windows: `PRTY_NO_FFMPEG` + `PRTY_NO_FFPROBE` saves startup time
|
||||||
|
|
||||||
|
|
||||||
## optional gpl stuff
|
## optional gpl stuff
|
||||||
|
|
||||||
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
|
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ standalone programs which are executed by copyparty when an event happens (uploa
|
|||||||
|
|
||||||
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
these programs either take zero arguments, or a filepath (the affected file), or a json message with filepath + additional info
|
||||||
|
|
||||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xbu/xau/xiu/xbr/xar/xbd/xad)
|
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbr/xar/xbd/xad/xban)
|
||||||
|
|
||||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
|
|||||||
|
|
||||||
# before upload
|
# before upload
|
||||||
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
||||||
|
* [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension
|
||||||
|
|
||||||
|
|
||||||
# on message
|
# on message
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ parameters explained,
|
|||||||
t10 = abort download and continue if it takes longer than 10sec
|
t10 = abort download and continue if it takes longer than 10sec
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:xau=j,t10,bin/hooks/into-the-cache-it-goes.py
|
-v srv/inc:inc:r:rw,ed:c,xau=j,t10,bin/hooks/into-the-cache-it-goes.py
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
readable by everyone, read-write for user 'ed',
|
readable by everyone, read-write for user 'ed',
|
||||||
|
|||||||
@@ -23,17 +23,18 @@ because the keyword "anime" is in the DESTS config below
|
|||||||
needs python3
|
needs python3
|
||||||
|
|
||||||
example usage as global config (not a good idea):
|
example usage as global config (not a good idea):
|
||||||
python copyparty-sfx.py --xm f,j,t60,bin/hooks/qbittorrent-magnet.py
|
python copyparty-sfx.py --xm aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xm = execute on message (📟)
|
xm = execute on message (📟)
|
||||||
|
aw = only users with write-access can use this
|
||||||
f = fork; don't delay other hooks while this is running
|
f = fork; don't delay other hooks while this is running
|
||||||
j = provide message information as json (not just the text)
|
j = provide message information as json (not just the text)
|
||||||
t60 = abort if qbittorrent has to think about it for more than 1 min
|
t60 = abort if qbittorrent has to think about it for more than 1 min
|
||||||
|
|
||||||
example usage as a volflag (per-volume config, much better):
|
example usage as a volflag (per-volume config, much better):
|
||||||
-v srv/qb:qb:A,ed:c,xm=f,j,t60,bin/hooks/qbittorrent-magnet.py
|
-v srv/qb:qb:A,ed:c,xm=aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
(share filesystem-path srv/qb as volume /qb with Admin for user 'ed',
|
(share filesystem-path srv/qb as volume /qb with Admin for user 'ed',
|
||||||
running this plugin on all messages with the params explained above)
|
running this plugin on all messages with the params explained above)
|
||||||
@@ -44,7 +45,7 @@ example usage as a volflag in a copyparty config file:
|
|||||||
accs:
|
accs:
|
||||||
A: ed
|
A: ed
|
||||||
flags:
|
flags:
|
||||||
xm: f,j,t60,bin/hooks/qbittorrent-magnet.py
|
xm: aw,f,j,t60,bin/hooks/qbittorrent-magnet.py
|
||||||
|
|
||||||
the volflag examples only kicks in if you send the torrent magnet
|
the volflag examples only kicks in if you send the torrent magnet
|
||||||
while you're in the /qb folder (or any folder below there)
|
while you're in the /qb folder (or any folder below there)
|
||||||
|
|||||||
127
bin/hooks/reloc-by-ext.py
Normal file
127
bin/hooks/reloc-by-ext.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
relocate/redirect incoming uploads according to file extension or name
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xbu j,c1,bin/hooks/reloc-by-ext.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xbu = execute before upload
|
||||||
|
j = this hook needs upload information as json (not just the filename)
|
||||||
|
c1 = this hook returns json on stdout, so tell copyparty to read that
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reloc-by-ext.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
|
readable by everyone, read-write for user 'ed',
|
||||||
|
running this plugin on all uploads with the params explained above)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/inc]
|
||||||
|
srv/inc
|
||||||
|
accs:
|
||||||
|
r: *
|
||||||
|
rw: ed
|
||||||
|
flags:
|
||||||
|
xbu: j,c1,bin/hooks/reloc-by-ext.py
|
||||||
|
|
||||||
|
note: this could also work as an xau hook (after-upload), but
|
||||||
|
because it doesn't need to read the file contents its better
|
||||||
|
as xbu (before-upload) since that's safer / less buggy,
|
||||||
|
and only xbu works with up2k (dragdrop into browser)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
PICS = "avif bmp gif heic heif jpeg jpg jxl png psd qoi tga tif tiff webp"
|
||||||
|
VIDS = "3gp asf avi flv mkv mov mp4 mpeg mpeg2 mpegts mpg mpg2 nut ogm ogv rm ts vob webm wmv"
|
||||||
|
MUSIC = "aac aif aiff alac amr ape dfpwm flac m4a mp3 ogg opus ra tak tta wav wma wv"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
vdir, fn = os.path.split(inf["vp"])
|
||||||
|
|
||||||
|
try:
|
||||||
|
fn, ext = fn.rsplit(".", 1)
|
||||||
|
except:
|
||||||
|
# no file extension; pretend it's "bin"
|
||||||
|
ext = "bin"
|
||||||
|
|
||||||
|
ext = ext.lower()
|
||||||
|
|
||||||
|
# this function must end by printing the action to perform;
|
||||||
|
# that's handled by the print(json.dumps(... at the bottom
|
||||||
|
#
|
||||||
|
# the action can contain the following keys:
|
||||||
|
# "vp" is the folder URL to move the upload to,
|
||||||
|
# "ap" is the filesystem-path to move it to (but "vp" is safer),
|
||||||
|
# "fn" overrides the final filename to use
|
||||||
|
|
||||||
|
##
|
||||||
|
## some example actions to take; pick one by
|
||||||
|
## selecting it inside the print at the end:
|
||||||
|
##
|
||||||
|
|
||||||
|
# create a subfolder named after the filetype and move it into there
|
||||||
|
into_subfolder = {"vp": ext}
|
||||||
|
|
||||||
|
# move it into a toplevel folder named after the filetype
|
||||||
|
into_toplevel = {"vp": "/" + ext}
|
||||||
|
|
||||||
|
# move it into a filetype-named folder next to the target folder
|
||||||
|
into_sibling = {"vp": "../" + ext}
|
||||||
|
|
||||||
|
# move images into "/just/pics", vids into "/just/vids",
|
||||||
|
# music into "/just/tunes", and anything else as-is
|
||||||
|
if ext in PICS.split():
|
||||||
|
by_category = {"vp": "/just/pics"}
|
||||||
|
elif ext in VIDS.split():
|
||||||
|
by_category = {"vp": "/just/vids"}
|
||||||
|
elif ext in MUSIC.split():
|
||||||
|
by_category = {"vp": "/just/tunes"}
|
||||||
|
else:
|
||||||
|
by_category = {} # no action
|
||||||
|
|
||||||
|
# now choose the default effect to apply; can be any of these:
|
||||||
|
# into_subfolder into_toplevel into_sibling by_category
|
||||||
|
effect = {"vp": "/junk"}
|
||||||
|
|
||||||
|
##
|
||||||
|
## but we can keep going, adding more speicifc rules
|
||||||
|
## which can take precedence, replacing the fallback
|
||||||
|
## effect we just specified:
|
||||||
|
##
|
||||||
|
|
||||||
|
fn = fn.lower() # lowercase filename to make this easier
|
||||||
|
|
||||||
|
if "screenshot" in fn:
|
||||||
|
effect = {"vp": "/ss"}
|
||||||
|
if "mpv_" in fn:
|
||||||
|
effect = {"vp": "/anishots"}
|
||||||
|
elif "debian" in fn or "biebian" in fn:
|
||||||
|
effect = {"vp": "/linux-ISOs"}
|
||||||
|
elif re.search(r"ep(isode |\.)?[0-9]", fn):
|
||||||
|
effect = {"vp": "/podcasts"}
|
||||||
|
|
||||||
|
# regex lets you grab a part of the matching
|
||||||
|
# text and use that in the upload path:
|
||||||
|
m = re.search(r"\b(op|ed)([^a-z]|$)", fn)
|
||||||
|
if m:
|
||||||
|
# the regex matched; use "anime-op" or "anime-ed"
|
||||||
|
effect = {"vp": "/anime-" + m[1]}
|
||||||
|
|
||||||
|
# aaand DO IT
|
||||||
|
print(json.dumps({"reloc": effect}))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -12,18 +12,19 @@ application/x-www-form-urlencoded (for example using the
|
|||||||
📟 message-to-server-log in the web-ui)
|
📟 message-to-server-log in the web-ui)
|
||||||
|
|
||||||
example usage as global config:
|
example usage as global config:
|
||||||
--xm f,j,t3600,bin/hooks/wget.py
|
--xm aw,f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
parameters explained,
|
parameters explained,
|
||||||
xm = execute on message-to-server-log
|
xm = execute on message-to-server-log
|
||||||
|
aw = only users with write-access can use this
|
||||||
f = fork; don't delay other hooks while this is running
|
f = fork; don't delay other hooks while this is running
|
||||||
j = provide message information as json (not just the text)
|
j = provide message information as json (not just the text)
|
||||||
c3 = mute all output
|
c3 = mute all output
|
||||||
t3600 = timeout and abort download after 1 hour
|
t3600 = timeout and abort download after 1 hour
|
||||||
|
|
||||||
example usage as a volflag (per-volume config):
|
example usage as a volflag (per-volume config):
|
||||||
-v srv/inc:inc:r:rw,ed:c,xm=f,j,t3600,bin/hooks/wget.py
|
-v srv/inc:inc:r:rw,ed:c,xm=aw,f,j,t3600,bin/hooks/wget.py
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
(share filesystem-path srv/inc as volume /inc,
|
(share filesystem-path srv/inc as volume /inc,
|
||||||
readable by everyone, read-write for user 'ed',
|
readable by everyone, read-write for user 'ed',
|
||||||
@@ -36,7 +37,7 @@ example usage as a volflag in a copyparty config file:
|
|||||||
r: *
|
r: *
|
||||||
rw: ed
|
rw: ed
|
||||||
flags:
|
flags:
|
||||||
xm: f,j,t3600,bin/hooks/wget.py
|
xm: aw,f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
the volflag examples only kicks in if you send the message
|
the volflag examples only kicks in if you send the message
|
||||||
while you're in the /inc folder (or any folder below there)
|
while you're in the /inc folder (or any folder below there)
|
||||||
|
|||||||
182
bin/u2c.py
182
bin/u2c.py
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
S_VERSION = "1.20"
|
S_VERSION = "1.24"
|
||||||
S_BUILD_DT = "2024-07-22"
|
S_BUILD_DT = "2024-09-05"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
@@ -41,19 +41,25 @@ except:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
req_ses = requests.Session()
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
if EXE:
|
if "-" in sys.argv or "-h" in sys.argv:
|
||||||
|
m = ""
|
||||||
|
elif EXE:
|
||||||
raise
|
raise
|
||||||
elif sys.version_info > (2, 7):
|
elif sys.version_info > (2, 7):
|
||||||
m = "\nERROR: need 'requests'; please run this command:\n {0} -m pip install --user requests\n"
|
m = "\nERROR: need 'requests'{0}; please run this command:\n {1} -m pip install --user requests\n"
|
||||||
else:
|
else:
|
||||||
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
||||||
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
||||||
m = "\n ERROR: need these:\n" + "\n".join(m) + "\n"
|
m = "\n ERROR: need these{0}:\n" + "\n".join(m) + "\n"
|
||||||
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
||||||
|
|
||||||
print(m.format(sys.executable), "\nspecifically,", ex)
|
if m:
|
||||||
sys.exit(1)
|
t = " when not running with '-h' or url '-'"
|
||||||
|
print(m.format(t, sys.executable), "\nspecifically,", ex)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# from copyparty/__init__.py
|
# from copyparty/__init__.py
|
||||||
@@ -76,7 +82,22 @@ else:
|
|||||||
VT100 = platform.system() != "Windows"
|
VT100 = platform.system() != "Windows"
|
||||||
|
|
||||||
|
|
||||||
req_ses = requests.Session()
|
try:
|
||||||
|
UTC = datetime.timezone.utc
|
||||||
|
except:
|
||||||
|
TD_ZERO = datetime.timedelta(0)
|
||||||
|
|
||||||
|
class _UTC(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return TD_ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return TD_ZERO
|
||||||
|
|
||||||
|
UTC = _UTC()
|
||||||
|
|
||||||
|
|
||||||
class Daemon(threading.Thread):
|
class Daemon(threading.Thread):
|
||||||
@@ -271,6 +292,12 @@ class MTHash(object):
|
|||||||
_print = print
|
_print = print
|
||||||
|
|
||||||
|
|
||||||
|
def safe_print(*a, **ka):
|
||||||
|
ka["end"] = ""
|
||||||
|
zs = " ".join([unicode(x) for x in a])
|
||||||
|
_print(zs + "\n", **ka)
|
||||||
|
|
||||||
|
|
||||||
def eprint(*a, **ka):
|
def eprint(*a, **ka):
|
||||||
ka["file"] = sys.stderr
|
ka["file"] = sys.stderr
|
||||||
ka["end"] = ""
|
ka["end"] = ""
|
||||||
@@ -284,18 +311,17 @@ def eprint(*a, **ka):
|
|||||||
|
|
||||||
def flushing_print(*a, **ka):
|
def flushing_print(*a, **ka):
|
||||||
try:
|
try:
|
||||||
_print(*a, **ka)
|
safe_print(*a, **ka)
|
||||||
except:
|
except:
|
||||||
v = " ".join(str(x) for x in a)
|
v = " ".join(str(x) for x in a)
|
||||||
v = v.encode("ascii", "replace").decode("ascii")
|
v = v.encode("ascii", "replace").decode("ascii")
|
||||||
_print(v, **ka)
|
safe_print(v, **ka)
|
||||||
|
|
||||||
if "flush" not in ka:
|
if "flush" not in ka:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
if not VT100:
|
print = safe_print if VT100 else flushing_print
|
||||||
print = flushing_print
|
|
||||||
|
|
||||||
|
|
||||||
def termsize():
|
def termsize():
|
||||||
@@ -660,8 +686,15 @@ def upload(fsl, pw, stats):
|
|||||||
# type: (FileSlice, str, str) -> None
|
# type: (FileSlice, str, str) -> None
|
||||||
"""upload a range of file data, defined by one or more `cid` (chunk-hash)"""
|
"""upload a range of file data, defined by one or more `cid` (chunk-hash)"""
|
||||||
|
|
||||||
|
ctxt = fsl.cids[0]
|
||||||
|
if len(fsl.cids) > 1:
|
||||||
|
n = 192 // len(fsl.cids)
|
||||||
|
n = 9 if n > 9 else 2 if n < 2 else n
|
||||||
|
zsl = [zs[:n] for zs in fsl.cids[1:]]
|
||||||
|
ctxt += ",%d,%s" % (n, "".join(zsl))
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"X-Up2k-Hash": ",".join(fsl.cids),
|
"X-Up2k-Hash": ctxt,
|
||||||
"X-Up2k-Wark": fsl.file.wark,
|
"X-Up2k-Wark": fsl.file.wark,
|
||||||
"Content-Type": "application/octet-stream",
|
"Content-Type": "application/octet-stream",
|
||||||
}
|
}
|
||||||
@@ -755,6 +788,7 @@ class Ctl(object):
|
|||||||
else:
|
else:
|
||||||
self.at_hash = 0.0
|
self.at_hash = 0.0
|
||||||
self.at_up = 0.0
|
self.at_up = 0.0
|
||||||
|
self.at_upr = 0.0
|
||||||
self.hash_f = 0
|
self.hash_f = 0
|
||||||
self.hash_c = 0
|
self.hash_c = 0
|
||||||
self.hash_b = 0
|
self.hash_b = 0
|
||||||
@@ -762,8 +796,6 @@ class Ctl(object):
|
|||||||
self.up_c = 0
|
self.up_c = 0
|
||||||
self.up_b = 0
|
self.up_b = 0
|
||||||
self.up_br = 0
|
self.up_br = 0
|
||||||
self.hasher_busy = 1
|
|
||||||
self.handshaker_busy = 0
|
|
||||||
self.uploader_busy = 0
|
self.uploader_busy = 0
|
||||||
self.serialized = False
|
self.serialized = False
|
||||||
|
|
||||||
@@ -773,6 +805,9 @@ class Ctl(object):
|
|||||||
self.eta = "99:99:99"
|
self.eta = "99:99:99"
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.exit_cond = threading.Condition()
|
||||||
|
self.uploader_alive = ar.j
|
||||||
|
self.handshaker_alive = ar.j
|
||||||
self.q_handshake = Queue() # type: Queue[File]
|
self.q_handshake = Queue() # type: Queue[File]
|
||||||
self.q_upload = Queue() # type: Queue[FileSlice]
|
self.q_upload = Queue() # type: Queue[FileSlice]
|
||||||
|
|
||||||
@@ -843,27 +878,21 @@ class Ctl(object):
|
|||||||
Daemon(self.handshaker)
|
Daemon(self.handshaker)
|
||||||
Daemon(self.uploader)
|
Daemon(self.uploader)
|
||||||
|
|
||||||
idles = 0
|
while True:
|
||||||
while idles < 3:
|
with self.exit_cond:
|
||||||
time.sleep(0.07)
|
self.exit_cond.wait(0.07)
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
if (
|
if not self.handshaker_alive and not self.uploader_alive:
|
||||||
self.q_handshake.empty()
|
break
|
||||||
and self.q_upload.empty()
|
st_hash = self.st_hash[:]
|
||||||
and not self.hasher_busy
|
st_up = self.st_up[:]
|
||||||
and not self.handshaker_busy
|
|
||||||
and not self.uploader_busy
|
|
||||||
):
|
|
||||||
idles += 1
|
|
||||||
else:
|
|
||||||
idles = 0
|
|
||||||
|
|
||||||
if VT100 and not self.ar.ns:
|
if VT100 and not self.ar.ns:
|
||||||
maxlen = ss.w - len(str(self.nfiles)) - 14
|
maxlen = ss.w - len(str(self.nfiles)) - 14
|
||||||
txt = "\033[s\033[{0}H".format(ss.g)
|
txt = "\033[s\033[{0}H".format(ss.g)
|
||||||
for y, k, st, f in [
|
for y, k, st, f in [
|
||||||
[0, "hash", self.st_hash, self.hash_f],
|
[0, "hash", st_hash, self.hash_f],
|
||||||
[1, "send", self.st_up, self.up_f],
|
[1, "send", st_up, self.up_f],
|
||||||
]:
|
]:
|
||||||
txt += "\033[{0}H{1}:".format(ss.g + y, k)
|
txt += "\033[{0}H{1}:".format(ss.g + y, k)
|
||||||
file, arg = st
|
file, arg = st
|
||||||
@@ -903,8 +932,9 @@ class Ctl(object):
|
|||||||
t = "{0} eta @ {1}/s, {2}, {3}# left".format(self.eta, spd, sleft, nleft)
|
t = "{0} eta @ {1}/s, {2}, {3}# left".format(self.eta, spd, sleft, nleft)
|
||||||
eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
|
eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
|
||||||
|
|
||||||
spd = humansize(self.hash_b / self.at_hash)
|
if self.hash_b and self.at_hash:
|
||||||
eprint("\nhasher: %.2f sec, %s/s\n" % (self.at_hash, spd))
|
spd = humansize(self.hash_b / self.at_hash)
|
||||||
|
eprint("\nhasher: %.2f sec, %s/s\n" % (self.at_hash, spd))
|
||||||
if self.up_b and self.at_up:
|
if self.up_b and self.at_up:
|
||||||
spd = humansize(self.up_b / self.at_up)
|
spd = humansize(self.up_b / self.at_up)
|
||||||
eprint("upload: %.2f sec, %s/s\n" % (self.at_up, spd))
|
eprint("upload: %.2f sec, %s/s\n" % (self.at_up, spd))
|
||||||
@@ -1018,11 +1048,42 @@ class Ctl(object):
|
|||||||
self.hash_f += 1
|
self.hash_f += 1
|
||||||
self.hash_c += len(file.cids)
|
self.hash_c += len(file.cids)
|
||||||
self.hash_b += file.size
|
self.hash_b += file.size
|
||||||
|
if self.ar.wlist:
|
||||||
|
self.up_f = self.hash_f
|
||||||
|
self.up_c = self.hash_c
|
||||||
|
self.up_b = self.hash_b
|
||||||
|
|
||||||
|
if self.ar.wlist:
|
||||||
|
zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
|
||||||
|
zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
|
||||||
|
wark = base64.urlsafe_b64encode(zb).decode("utf-8")
|
||||||
|
vp = file.rel.decode("utf-8")
|
||||||
|
if self.ar.jw:
|
||||||
|
print("%s %s" % (wark, vp))
|
||||||
|
else:
|
||||||
|
zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
|
||||||
|
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
||||||
|
zd.year,
|
||||||
|
zd.month,
|
||||||
|
zd.day,
|
||||||
|
zd.hour,
|
||||||
|
zd.minute,
|
||||||
|
zd.second,
|
||||||
|
)
|
||||||
|
print("%s %12d %s %s" % (dt, file.size, wark, vp))
|
||||||
|
continue
|
||||||
|
|
||||||
self.q_handshake.put(file)
|
self.q_handshake.put(file)
|
||||||
|
|
||||||
self.hasher_busy = 0
|
|
||||||
self.st_hash = [None, "(finished)"]
|
self.st_hash = [None, "(finished)"]
|
||||||
|
self._check_if_done()
|
||||||
|
|
||||||
|
def _check_if_done(self):
|
||||||
|
with self.mutex:
|
||||||
|
if self.nfiles - self.up_f:
|
||||||
|
return
|
||||||
|
for _ in range(self.ar.j):
|
||||||
|
self.q_handshake.put(None)
|
||||||
|
|
||||||
def handshaker(self):
|
def handshaker(self):
|
||||||
search = self.ar.s
|
search = self.ar.s
|
||||||
@@ -1030,8 +1091,10 @@ class Ctl(object):
|
|||||||
while True:
|
while True:
|
||||||
file = self.q_handshake.get()
|
file = self.q_handshake.get()
|
||||||
if not file:
|
if not file:
|
||||||
|
with self.mutex:
|
||||||
|
self.handshaker_alive -= 1
|
||||||
self.q_upload.put(None)
|
self.q_upload.put(None)
|
||||||
break
|
return
|
||||||
|
|
||||||
upath = file.abs.decode("utf-8", "replace")
|
upath = file.abs.decode("utf-8", "replace")
|
||||||
if not VT100:
|
if not VT100:
|
||||||
@@ -1043,9 +1106,6 @@ class Ctl(object):
|
|||||||
self.errs += 1
|
self.errs += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
self.handshaker_busy += 1
|
|
||||||
|
|
||||||
while time.time() < file.cd:
|
while time.time() < file.cd:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
@@ -1053,17 +1113,17 @@ class Ctl(object):
|
|||||||
if search:
|
if search:
|
||||||
if hs:
|
if hs:
|
||||||
for hit in hs:
|
for hit in hs:
|
||||||
t = "found: {0}\n {1}{2}\n"
|
t = "found: {0}\n {1}{2}"
|
||||||
print(t.format(upath, burl, hit["rp"]), end="")
|
print(t.format(upath, burl, hit["rp"]))
|
||||||
else:
|
else:
|
||||||
print("NOT found: {0}\n".format(upath), end="")
|
print("NOT found: {0}".format(upath))
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.up_f += 1
|
self.up_f += 1
|
||||||
self.up_c += len(file.cids)
|
self.up_c += len(file.cids)
|
||||||
self.up_b += file.size
|
self.up_b += file.size
|
||||||
self.handshaker_busy -= 1
|
|
||||||
|
|
||||||
|
self._check_if_done()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if file.recheck:
|
if file.recheck:
|
||||||
@@ -1095,13 +1155,9 @@ class Ctl(object):
|
|||||||
file.up_b -= sz
|
file.up_b -= sz
|
||||||
|
|
||||||
file.ucids = hs
|
file.ucids = hs
|
||||||
self.handshaker_busy -= 1
|
|
||||||
|
|
||||||
if not hs:
|
if not hs:
|
||||||
self.at_hash += file.t_hash
|
self.at_hash += file.t_hash
|
||||||
if file.up_b:
|
|
||||||
t_up = file.t1_up - file.t0_up
|
|
||||||
self.at_up += t_up
|
|
||||||
|
|
||||||
if self.ar.spd:
|
if self.ar.spd:
|
||||||
if VT100:
|
if VT100:
|
||||||
@@ -1112,6 +1168,7 @@ class Ctl(object):
|
|||||||
|
|
||||||
spd_h = humansize(file.size / file.t_hash, True)
|
spd_h = humansize(file.size / file.t_hash, True)
|
||||||
if file.up_b:
|
if file.up_b:
|
||||||
|
t_up = file.t1_up - file.t0_up
|
||||||
spd_u = humansize(file.size / t_up, True)
|
spd_u = humansize(file.size / t_up, True)
|
||||||
|
|
||||||
t = "uploaded %s %s(h:%.2fs,%s/s,up:%.2fs,%s/s)%s"
|
t = "uploaded %s %s(h:%.2fs,%s/s,up:%.2fs,%s/s)%s"
|
||||||
@@ -1123,13 +1180,18 @@ class Ctl(object):
|
|||||||
kw = "uploaded" if file.up_b else " found"
|
kw = "uploaded" if file.up_b else " found"
|
||||||
print("{0} {1}".format(kw, upath))
|
print("{0} {1}".format(kw, upath))
|
||||||
|
|
||||||
|
self._check_if_done()
|
||||||
|
continue
|
||||||
|
|
||||||
|
chunksz = up2k_chunksize(file.size)
|
||||||
|
njoin = (self.ar.sz * 1024 * 1024) // chunksz
|
||||||
cs = hs[:]
|
cs = hs[:]
|
||||||
while cs:
|
while cs:
|
||||||
fsl = FileSlice(file, cs[:1])
|
fsl = FileSlice(file, cs[:1])
|
||||||
try:
|
try:
|
||||||
if file.nojoin:
|
if file.nojoin:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
for n in range(2, min(len(cs), self.ar.sz) + 1):
|
for n in range(2, min(len(cs), njoin + 1)):
|
||||||
fsl = FileSlice(file, cs[:n])
|
fsl = FileSlice(file, cs[:n])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -1140,13 +1202,23 @@ class Ctl(object):
|
|||||||
while True:
|
while True:
|
||||||
fsl = self.q_upload.get()
|
fsl = self.q_upload.get()
|
||||||
if not fsl:
|
if not fsl:
|
||||||
self.st_up = [None, "(finished)"]
|
done = False
|
||||||
break
|
with self.mutex:
|
||||||
|
self.uploader_alive -= 1
|
||||||
|
if not self.uploader_alive:
|
||||||
|
done = not self.handshaker_alive
|
||||||
|
self.st_up = [None, "(finished)"]
|
||||||
|
if done:
|
||||||
|
with self.exit_cond:
|
||||||
|
self.exit_cond.notify_all()
|
||||||
|
return
|
||||||
|
|
||||||
file = fsl.file
|
file = fsl.file
|
||||||
cids = fsl.cids
|
cids = fsl.cids
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
|
if not self.uploader_busy:
|
||||||
|
self.at_upr = time.time()
|
||||||
self.uploader_busy += 1
|
self.uploader_busy += 1
|
||||||
if not file.t0_up:
|
if not file.t0_up:
|
||||||
file.t0_up = time.time()
|
file.t0_up = time.time()
|
||||||
@@ -1185,6 +1257,8 @@ class Ctl(object):
|
|||||||
file.up_c += 1
|
file.up_c += 1
|
||||||
self.up_c += 1
|
self.up_c += 1
|
||||||
self.uploader_busy -= 1
|
self.uploader_busy -= 1
|
||||||
|
if not self.uploader_busy:
|
||||||
|
self.at_up += time.time() - self.at_upr
|
||||||
|
|
||||||
def up_done(self, file):
|
def up_done(self, file):
|
||||||
if self.ar.dl:
|
if self.ar.dl:
|
||||||
@@ -1223,7 +1297,7 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
ap.add_argument("-v", action="store_true", help="verbose")
|
ap.add_argument("-v", action="store_true", help="verbose")
|
||||||
ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
|
ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
|
||||||
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
||||||
ap.add_argument("-x", type=unicode, metavar="REGEX", default="", help="skip file if filesystem-abspath matches REGEX, example: '.*/\\.hist/.*'")
|
ap.add_argument("-x", type=unicode, metavar="REGEX", action="append", help="skip file if filesystem-abspath matches REGEX (option can be repeated), example: '.*/\\.hist/.*'")
|
||||||
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
||||||
ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
|
ap.add_argument("--touch", action="store_true", help="if last-modified timestamps differ, push local to server (need write+delete perms)")
|
||||||
ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
|
ap.add_argument("--ow", action="store_true", help="overwrite existing files instead of autorenaming")
|
||||||
@@ -1239,6 +1313,10 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally (implies --ow)")
|
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally (implies --ow)")
|
||||||
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
||||||
|
|
||||||
|
ap = app.add_argument_group("file-ID calculator; enable with url '-' to list warks (file identifiers) instead of upload/search")
|
||||||
|
ap.add_argument("--wsalt", type=unicode, metavar="S", default="hunter2", help="salt to use when creating warks; must match server config")
|
||||||
|
ap.add_argument("--jw", action="store_true", help="just identifier+filepath, not mtime/size too")
|
||||||
|
|
||||||
ap = app.add_argument_group("performance tweaks")
|
ap = app.add_argument_group("performance tweaks")
|
||||||
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
|
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
|
||||||
ap.add_argument("-J", type=int, metavar="CORES", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
ap.add_argument("-J", type=int, metavar="CORES", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
||||||
@@ -1270,7 +1348,11 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
if ar.dr:
|
if ar.dr:
|
||||||
ar.ow = True
|
ar.ow = True
|
||||||
|
|
||||||
for k in "dl dr drd".split():
|
ar.x = "|".join(ar.x or [])
|
||||||
|
|
||||||
|
setattr(ar, "wlist", ar.url == "-")
|
||||||
|
|
||||||
|
for k in "dl dr drd wlist".split():
|
||||||
errs = []
|
errs = []
|
||||||
if ar.safe and getattr(ar, k):
|
if ar.safe and getattr(ar, k):
|
||||||
errs.append(k)
|
errs.append(k)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.13.4"
|
pkgver="1.14.4"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
@@ -21,7 +21,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("b7039feca555e8f08ee7bd980ff37190f26d9bf544fc88ca3dcbadc19a13912e")
|
sha256sums=("1e8004e4369e59487c47a0a9949668de704b1884beda0421887e342edcff0961")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.13.4/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.14.4/copyparty-sfx.py",
|
||||||
"version": "1.13.4",
|
"version": "1.14.4",
|
||||||
"hash": "sha256-KqT6mtbLbPwoQKiDbkcBb0EvgZ+VDXB3A0BWiIPpbqs="
|
"hash": "sha256-nfcSXddM0jeHr7nGwoguOv/eq750bpHrImKmmODxK6E="
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,13 @@ point `--js-browser` to one of these by URL:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## example any-js
|
||||||
|
point `--js-browser` and/or `--js-other` to one of these by URL:
|
||||||
|
|
||||||
|
* [`banner.js`](banner.js) shows a very enterprise [legal-banner](https://github.com/user-attachments/assets/8ae8e087-b209-449c-b08d-74e040f0284b)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## example browser-css
|
## example browser-css
|
||||||
point `--css-browser` to one of these by URL:
|
point `--css-browser` to one of these by URL:
|
||||||
|
|
||||||
|
|||||||
93
contrib/plugins/banner.js
Normal file
93
contrib/plugins/banner.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
// usage: copy this to '.banner.js' in your webroot,
|
||||||
|
// and run copyparty with the following arguments:
|
||||||
|
// --js-browser /.banner.js --js-other /.banner.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// had to pick the most chuuni one as the default
|
||||||
|
var bannertext = '' +
|
||||||
|
'<h3>You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only.</h3>' +
|
||||||
|
'<p>By using this IS (which includes any device attached to this IS), you consent to the following conditions:</p>' +
|
||||||
|
'<ul>' +
|
||||||
|
'<li>The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.</li>' +
|
||||||
|
'<li>At any time, the USG may inspect and seize data stored on this IS.</li>' +
|
||||||
|
'<li>Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.</li>' +
|
||||||
|
'<li>This IS includes security measures (e.g., authentication and access controls) to protect USG interests -- not for your personal benefit or privacy.</li>' +
|
||||||
|
'<li>Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.</li>' +
|
||||||
|
'</ul>';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// fancy div to insert into pages
|
||||||
|
function bannerdiv(border) {
|
||||||
|
var ret = mknod('div', null, bannertext);
|
||||||
|
if (border)
|
||||||
|
ret.setAttribute("style", "border:1em solid var(--fg); border-width:.3em 0; margin:3em 0");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// keep all of these false and then selectively enable them in the if-blocks below
|
||||||
|
var show_msgbox = false,
|
||||||
|
login_top = false,
|
||||||
|
top = false,
|
||||||
|
bottom = false,
|
||||||
|
top_bordered = false,
|
||||||
|
bottom_bordered = false;
|
||||||
|
|
||||||
|
if (QS("h1#cc") && QS("a#k")) {
|
||||||
|
// this is the controlpanel
|
||||||
|
// (you probably want to keep just one of these enabled)
|
||||||
|
show_msgbox = true;
|
||||||
|
login_top = true;
|
||||||
|
bottom = true;
|
||||||
|
}
|
||||||
|
else if (ebi("swin") && ebi("smac")) {
|
||||||
|
// this is the connect-page, same deal here
|
||||||
|
show_msgbox = true;
|
||||||
|
top_bordered = true;
|
||||||
|
bottom_bordered = true;
|
||||||
|
}
|
||||||
|
else if (ebi("op_cfg") || ebi("div#mw") ) {
|
||||||
|
// we're running in the main filebrowser (op_cfg) or markdown-viewer/editor (div#mw),
|
||||||
|
// fragile pages which break if you do something too fancy
|
||||||
|
show_msgbox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// shows a fullscreen messagebox; works on all pages
|
||||||
|
if (show_msgbox) {
|
||||||
|
var now = Math.floor(Date.now() / 1000),
|
||||||
|
last_shown = sread("bannerts") || 0;
|
||||||
|
|
||||||
|
// 60 * 60 * 17 = 17 hour cooldown
|
||||||
|
if (now - last_shown > 60 * 60 * 17) {
|
||||||
|
swrite("bannerts", now);
|
||||||
|
modal.confirm(bannertext, null, function () {
|
||||||
|
location = 'https://this-page-intentionally-left-blank.org/';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a message on the page footer; only works on the connect-page
|
||||||
|
if (top || top_bordered) {
|
||||||
|
var dst = ebi('wrap');
|
||||||
|
dst.insertBefore(bannerdiv(top_bordered), dst.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a message on the page footer; only works on the controlpanel and connect-page
|
||||||
|
if (bottom || bottom_bordered) {
|
||||||
|
ebi('wrap').appendChild(bannerdiv(bottom_bordered));
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a message on the top of the page; only works on the controlpanel
|
||||||
|
if (login_top) {
|
||||||
|
var dst = QS('h1');
|
||||||
|
dst.parentNode.insertBefore(bannerdiv(false), dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
116
contrib/themes/bsod.css
Normal file
116
contrib/themes/bsod.css
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/* copy bsod.* into a folder named ".themes" in your webroot and then
|
||||||
|
--themes=10 --theme=9 --css-browser=/.themes/bsod.css
|
||||||
|
*/
|
||||||
|
|
||||||
|
html.ey {
|
||||||
|
--w2: #3d7bbc;
|
||||||
|
--w3: #5fcbec;
|
||||||
|
|
||||||
|
--fg: #fff;
|
||||||
|
--fg-max: #fff;
|
||||||
|
--fg-weak: var(--w3);
|
||||||
|
|
||||||
|
--bg: #2067b2;
|
||||||
|
--bg-d3: var(--bg);
|
||||||
|
--bg-d2: var(--w2);
|
||||||
|
--bg-d1: var(--fg-weak);
|
||||||
|
--bg-u2: var(--bg);
|
||||||
|
--bg-u3: var(--bg);
|
||||||
|
--bg-u5: var(--w2);
|
||||||
|
|
||||||
|
--tab-alt: var(--fg-weak);
|
||||||
|
--row-alt: var(--w2);
|
||||||
|
|
||||||
|
--scroll: var(--w3);
|
||||||
|
|
||||||
|
--a: #fff;
|
||||||
|
--a-b: #fff;
|
||||||
|
--a-hil: #fff;
|
||||||
|
--a-h-bg: var(--fg-weak);
|
||||||
|
--a-dark: var(--a);
|
||||||
|
--a-gray: var(--fg-weak);
|
||||||
|
|
||||||
|
--btn-fg: var(--a);
|
||||||
|
--btn-bg: var(--w2);
|
||||||
|
--btn-h-fg: var(--w2);
|
||||||
|
--btn-1-fg: var(--bg);
|
||||||
|
--btn-1-bg: var(--a);
|
||||||
|
--txt-sh: a;
|
||||||
|
--txt-bg: var(--w2);
|
||||||
|
|
||||||
|
--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(--a);
|
||||||
|
--sort-1: var(--fg-weak);
|
||||||
|
|
||||||
|
--tree-bg: var(--bg);
|
||||||
|
|
||||||
|
--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(--a);
|
||||||
|
--srv-3: var(--a);
|
||||||
|
|
||||||
|
--mp-sh: a;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.ey {
|
||||||
|
background: url('bsod.png') top 5em right 4.5em no-repeat fixed var(--bg);
|
||||||
|
}
|
||||||
|
html.ey body#b {
|
||||||
|
background: var(--bg); /*sandbox*/
|
||||||
|
}
|
||||||
|
html.ey #ops {
|
||||||
|
margin: 1.7em 1.5em 0 1.5em;
|
||||||
|
border-radius: .3em;
|
||||||
|
border-width: 1px 0;
|
||||||
|
}
|
||||||
|
html.ey #ops a {
|
||||||
|
text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
html.ey .opbox {
|
||||||
|
margin: 1.5em 0 0 0;
|
||||||
|
}
|
||||||
|
html.ey #tree {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
html.ey #tt {
|
||||||
|
border-color: var(--w2);
|
||||||
|
background: var(--w2);
|
||||||
|
}
|
||||||
|
html.ey .mdo a {
|
||||||
|
background: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
html.ey .mdo pre,
|
||||||
|
html.ey .mdo code {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--w2);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
html.ey .mdo h1,
|
||||||
|
html.ey .mdo h2 {
|
||||||
|
background: none;
|
||||||
|
border-color: var(--w2);
|
||||||
|
}
|
||||||
|
html.ey .mdo ul ul,
|
||||||
|
html.ey .mdo ul ol,
|
||||||
|
html.ey .mdo ol ul,
|
||||||
|
html.ey .mdo ol ol {
|
||||||
|
border-color: var(--w2);
|
||||||
|
}
|
||||||
|
html.ey .mdo p>em,
|
||||||
|
html.ey .mdo li>em,
|
||||||
|
html.ey .mdo td>em {
|
||||||
|
color: #fd0;
|
||||||
|
}
|
||||||
BIN
contrib/themes/bsod.png
Normal file
BIN
contrib/themes/bsod.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -67,7 +67,13 @@ if True: # pylint: disable=using-constant-test
|
|||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_TLS"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
import ssl
|
import ssl
|
||||||
except:
|
except:
|
||||||
@@ -344,7 +350,7 @@ def configure_ssl_ver(al: argparse.Namespace) -> None:
|
|||||||
# oh man i love openssl
|
# oh man i love openssl
|
||||||
# check this out
|
# check this out
|
||||||
# hold my beer
|
# hold my beer
|
||||||
assert ssl
|
assert ssl # type: ignore
|
||||||
ptn = re.compile(r"^OP_NO_(TLS|SSL)v")
|
ptn = re.compile(r"^OP_NO_(TLS|SSL)v")
|
||||||
sslver = terse_sslver(al.ssl_ver).split(",")
|
sslver = terse_sslver(al.ssl_ver).split(",")
|
||||||
flags = [k for k in ssl.__dict__ if ptn.match(k)]
|
flags = [k for k in ssl.__dict__ if ptn.match(k)]
|
||||||
@@ -378,7 +384,7 @@ def configure_ssl_ver(al: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def configure_ssl_ciphers(al: argparse.Namespace) -> None:
|
def configure_ssl_ciphers(al: argparse.Namespace) -> None:
|
||||||
assert ssl
|
assert ssl # type: ignore
|
||||||
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||||
if al.ssl_ver:
|
if al.ssl_ver:
|
||||||
ctx.options &= ~al.ssl_flags_en
|
ctx.options &= ~al.ssl_flags_en
|
||||||
@@ -491,6 +497,9 @@ def disable_quickedit() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def sfx_tpoke(top: str):
|
def sfx_tpoke(top: str):
|
||||||
|
if os.environ.get("PRTY_NO_TPOKE"):
|
||||||
|
return
|
||||||
|
|
||||||
files = [top] + [
|
files = [top] + [
|
||||||
os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df
|
os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df
|
||||||
]
|
]
|
||||||
@@ -518,6 +527,41 @@ def showlic() -> None:
|
|||||||
|
|
||||||
def get_sects():
|
def get_sects():
|
||||||
return [
|
return [
|
||||||
|
[
|
||||||
|
"bind",
|
||||||
|
"configure listening",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
\033[33m-i\033[0m takes a comma-separated list of interfaces to listen on;
|
||||||
|
IP-addresses and/or unix-sockets (Unix Domain Sockets)
|
||||||
|
|
||||||
|
the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses
|
||||||
|
|
||||||
|
\033[32m-i 0.0.0.0\033[0m listens on all IPv4 NICs/subnets
|
||||||
|
\033[32m-i 127.0.0.1\033[0m listens on IPv4 localhost only
|
||||||
|
\033[32m-i 127.1\033[0m listens on IPv4 localhost only
|
||||||
|
\033[32m-i 127.1,192.168.123.1\033[0m = IPv4 localhost and 192.168.123.1
|
||||||
|
|
||||||
|
\033[33m-p\033[0m takes a comma-separated list of tcp ports to listen on;
|
||||||
|
the default is \033[32m-p 3923\033[0m but as root you can \033[32m-p 80,443,3923\033[0m
|
||||||
|
|
||||||
|
when running behind a reverse-proxy, it's recommended to
|
||||||
|
use unix-sockets for improved performance and security;
|
||||||
|
|
||||||
|
\033[32m-i unix:770:www:\033[33m/tmp/a.sock\033[0m listens on \033[33m/tmp/a.sock\033[0m with
|
||||||
|
permissions \033[33m0770\033[0m; only accessible to members of the \033[33mwww\033[0m
|
||||||
|
group. This is the best approach. Alternatively,
|
||||||
|
|
||||||
|
\033[32m-i unix:777:\033[33m/tmp/a.sock\033[0m sets perms \033[33m0777\033[0m so anyone can
|
||||||
|
access it; bad unless it's inside a restricted folder
|
||||||
|
|
||||||
|
\033[32m-i unix:\033[33m/tmp/a.sock\033[0m keeps umask-defined permissions
|
||||||
|
(usually \033[33m0600\033[0m) and the same user/group as copyparty
|
||||||
|
|
||||||
|
\033[33m-p\033[0m (tcp ports) is ignored for unix sockets
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"accounts",
|
"accounts",
|
||||||
"accounts and volumes",
|
"accounts and volumes",
|
||||||
@@ -695,6 +739,11 @@ def get_sects():
|
|||||||
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
|
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
|
||||||
if the program returns 0 (true/OK) then the ban will NOT happen
|
if the program returns 0 (true/OK) then the ban will NOT happen
|
||||||
|
|
||||||
|
effects can be used to redirect uploads into other
|
||||||
|
locations, and to delete or index other files based
|
||||||
|
on new uploads, but with certain limitations. See
|
||||||
|
bin/hooks/reloc* and docs/devnotes.md#hook-effects
|
||||||
|
|
||||||
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
|
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
|
||||||
so it's recommended to use the \033[36mf\033[0m flag unless you really need
|
so it's recommended to use the \033[36mf\033[0m flag unless you really need
|
||||||
to wait for the hook to finish before continuing (without \033[36mf\033[0m
|
to wait for the hook to finish before continuing (without \033[36mf\033[0m
|
||||||
@@ -923,6 +972,16 @@ def add_fs(ap):
|
|||||||
ap2.add_argument("--mtab-age", metavar="SEC", type=int, default=60, help="rebuild mountpoint cache every \033[33mSEC\033[0m to keep track of sparse-files support; keep low on servers with removable media")
|
ap2.add_argument("--mtab-age", metavar="SEC", type=int, default=60, help="rebuild mountpoint cache every \033[33mSEC\033[0m to keep track of sparse-files support; keep low on servers with removable media")
|
||||||
|
|
||||||
|
|
||||||
|
def add_share(ap):
|
||||||
|
db_path = os.path.join(E.cfg, "shares.db")
|
||||||
|
ap2 = ap.add_argument_group('share-url options')
|
||||||
|
ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
|
||||||
|
ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in")
|
||||||
|
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
|
||||||
|
ap2.add_argument("--shr-rt", metavar="MIN", type=int, default=1440, help="shares can be revived by their owner if they expired less than MIN minutes ago; [\033[32m60\033[0m]=hour, [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week")
|
||||||
|
ap2.add_argument("--shr-v", action="store_true", help="debug")
|
||||||
|
|
||||||
|
|
||||||
def add_upload(ap):
|
def add_upload(ap):
|
||||||
ap2 = ap.add_argument_group('upload options')
|
ap2 = ap.add_argument_group('upload options')
|
||||||
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
|
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
|
||||||
@@ -933,9 +992,10 @@ def add_upload(ap):
|
|||||||
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")
|
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")
|
||||||
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
|
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
|
||||||
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
|
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
|
||||||
ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem) (volflag=hardlink)")
|
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
|
||||||
ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=neversymlink)")
|
ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
|
||||||
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes)")
|
ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
|
||||||
|
ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
|
||||||
ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
|
ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
|
||||||
ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
|
ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
|
||||||
ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
|
ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
|
||||||
@@ -955,8 +1015,8 @@ def add_upload(ap):
|
|||||||
|
|
||||||
def add_network(ap):
|
def add_network(ap):
|
||||||
ap2 = ap.add_argument_group('network options')
|
ap2 = ap.add_argument_group('network options')
|
||||||
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6")
|
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6")
|
||||||
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets")
|
||||||
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)")
|
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)")
|
||||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy")
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy")
|
||||||
ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from")
|
ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from")
|
||||||
@@ -1016,6 +1076,16 @@ def add_auth(ap):
|
|||||||
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
||||||
|
|
||||||
|
|
||||||
|
def add_chpw(ap):
|
||||||
|
db_path = os.path.join(E.cfg, "chpw.json")
|
||||||
|
ap2 = ap.add_argument_group('user-changeable passwords options')
|
||||||
|
ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords")
|
||||||
|
ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="do not allow password-changes for this comma-separated list of usernames")
|
||||||
|
ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)")
|
||||||
|
ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length")
|
||||||
|
ap2.add_argument("--chpw-v", metavar="LVL", type=int, default=2, help="verbosity of summary on config load [\033[32m0\033[0m] = nothing at all, [\033[32m1\033[0m] = number of users, [\033[32m2\033[0m] = list users with default-pw, [\033[32m3\033[0m] = list all users")
|
||||||
|
|
||||||
|
|
||||||
def add_zeroconf(ap):
|
def add_zeroconf(ap):
|
||||||
ap2 = ap.add_argument_group("Zeroconf options")
|
ap2 = ap.add_argument_group("Zeroconf options")
|
||||||
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
||||||
@@ -1123,6 +1193,7 @@ def add_hooks(ap):
|
|||||||
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file delete")
|
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file delete")
|
||||||
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m on message")
|
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m on message")
|
||||||
ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)")
|
ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)")
|
||||||
|
ap2.add_argument("--hook-v", action="store_true", help="verbose hooks")
|
||||||
|
|
||||||
|
|
||||||
def add_stats(ap):
|
def add_stats(ap):
|
||||||
@@ -1215,6 +1286,7 @@ def add_logging(ap):
|
|||||||
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
|
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
|
||||||
ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
|
ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
|
||||||
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
|
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
|
||||||
|
ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)")
|
||||||
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
|
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
|
||||||
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
|
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
|
||||||
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
||||||
@@ -1273,7 +1345,7 @@ def add_transcoding(ap):
|
|||||||
def add_db_general(ap, hcores):
|
def add_db_general(ap, hcores):
|
||||||
noidx = APPLESAN_TXT if MACOS else ""
|
noidx = APPLESAN_TXT if MACOS else ""
|
||||||
ap2 = ap.add_argument_group('general db options')
|
ap2 = ap.add_argument_group('general db options')
|
||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplication")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database; this enables file search, upload-undo, improves deduplication")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m")
|
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets \033[33m-e2d\033[0m")
|
||||||
ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m")
|
ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets \033[33m-e2ds\033[0m")
|
||||||
ap2.add_argument("-e2v", action="store_true", help="verify file integrity; rehash all files and compare with db")
|
ap2.add_argument("-e2v", action="store_true", help="verify file integrity; rehash all files and compare with db")
|
||||||
@@ -1286,7 +1358,7 @@ def add_db_general(ap, hcores):
|
|||||||
ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
|
ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
|
||||||
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
|
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
|
||||||
ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
|
ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
|
||||||
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
|
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) (volflag=xlink)")
|
||||||
ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
|
ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
|
||||||
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
|
||||||
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
|
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
|
||||||
@@ -1343,7 +1415,7 @@ def add_ui(ap, retry):
|
|||||||
ap2 = ap.add_argument_group('ui options')
|
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("--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("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
|
||||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor\033[0m")
|
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("--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("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
||||||
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
|
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
|
||||||
@@ -1351,9 +1423,10 @@ def add_ui(ap, retry):
|
|||||||
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
||||||
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
||||||
ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
|
ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
|
||||||
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include")
|
ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
|
||||||
ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include")
|
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
|
||||||
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages; can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)")
|
ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
|
||||||
|
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)")
|
||||||
ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
|
ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
|
||||||
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
||||||
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
||||||
@@ -1372,12 +1445,14 @@ def add_debug(ap):
|
|||||||
ap2 = ap.add_argument_group('debug options')
|
ap2 = ap.add_argument_group('debug options')
|
||||||
ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
|
ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
|
||||||
ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
|
ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
|
||||||
|
ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies")
|
||||||
if hasattr(select, "poll"):
|
if hasattr(select, "poll"):
|
||||||
ap2.add_argument("--no-poll", action="store_true", help="kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700)")
|
ap2.add_argument("--no-poll", action="store_true", help="kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700)")
|
||||||
ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead")
|
ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead")
|
||||||
ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead")
|
ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead")
|
||||||
ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests")
|
ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests")
|
||||||
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
||||||
|
ap2.add_argument("--rm-sck", action="store_true", help="when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind")
|
||||||
ap2.add_argument("--srch-dbg", action="store_true", help="explain search processing, and do some extra expensive sanity checks")
|
ap2.add_argument("--srch-dbg", action="store_true", help="explain search processing, and do some extra expensive sanity checks")
|
||||||
ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc")
|
ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc")
|
||||||
ap2.add_argument("--stackmon", metavar="P,S", type=u, default="", help="write stacktrace to \033[33mP\033[0math every \033[33mS\033[0m second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60")
|
ap2.add_argument("--stackmon", metavar="P,S", type=u, default="", help="write stacktrace to \033[33mP\033[0math every \033[33mS\033[0m second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60")
|
||||||
@@ -1420,11 +1495,13 @@ def run_argparse(
|
|||||||
add_tls(ap, cert_path)
|
add_tls(ap, cert_path)
|
||||||
add_cert(ap, cert_path)
|
add_cert(ap, cert_path)
|
||||||
add_auth(ap)
|
add_auth(ap)
|
||||||
|
add_chpw(ap)
|
||||||
add_qr(ap, tty)
|
add_qr(ap, tty)
|
||||||
add_zeroconf(ap)
|
add_zeroconf(ap)
|
||||||
add_zc_mdns(ap)
|
add_zc_mdns(ap)
|
||||||
add_zc_ssdp(ap)
|
add_zc_ssdp(ap)
|
||||||
add_fs(ap)
|
add_fs(ap)
|
||||||
|
add_share(ap)
|
||||||
add_upload(ap)
|
add_upload(ap)
|
||||||
add_db_general(ap, hcores)
|
add_db_general(ap, hcores)
|
||||||
add_db_metadata(ap)
|
add_db_metadata(ap)
|
||||||
@@ -1544,6 +1621,7 @@ def main(argv: Optional[list[str]] = None, rsrc: Optional[str] = None) -> None:
|
|||||||
("--hdr-au-usr", "--idp-h-usr"),
|
("--hdr-au-usr", "--idp-h-usr"),
|
||||||
("--idp-h-sep", "--idp-gsep"),
|
("--idp-h-sep", "--idp-gsep"),
|
||||||
("--th-no-crop", "--th-crop=n"),
|
("--th-no-crop", "--th-crop=n"),
|
||||||
|
("--never-symlink", "--hardlink-only"),
|
||||||
]
|
]
|
||||||
for dk, nk in deprecated:
|
for dk, nk in deprecated:
|
||||||
idx = -1
|
idx = -1
|
||||||
@@ -1568,7 +1646,7 @@ def main(argv: Optional[list[str]] = None, rsrc: Optional[str] = None) -> None:
|
|||||||
argv.extend(["--qr"])
|
argv.extend(["--qr"])
|
||||||
if ANYWIN or not os.geteuid():
|
if ANYWIN or not os.geteuid():
|
||||||
# win10 allows symlinks if admin; can be unexpected
|
# win10 allows symlinks if admin; can be unexpected
|
||||||
argv.extend(["-p80,443,3923", "--ign-ebind", "--no-dedup"])
|
argv.extend(["-p80,443,3923", "--ign-ebind"])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 13, 5)
|
VERSION = (1, 15, 0)
|
||||||
CODENAME = "race the beam"
|
CODENAME = "fill the drives"
|
||||||
BUILD_DT = (2024, 7, 22)
|
BUILD_DT = (2024, 9, 8)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
@@ -12,7 +13,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .__init__ import ANYWIN, TYPE_CHECKING, WINDOWS, E
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
||||||
from .pwhash import PWHash
|
from .pwhash import PWHash
|
||||||
@@ -34,9 +35,11 @@ from .util import (
|
|||||||
odfusion,
|
odfusion,
|
||||||
relchk,
|
relchk,
|
||||||
statdir,
|
statdir,
|
||||||
|
ub64enc,
|
||||||
uncyg,
|
uncyg,
|
||||||
undot,
|
undot,
|
||||||
unhumanize,
|
unhumanize,
|
||||||
|
vjoin,
|
||||||
vsplit,
|
vsplit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,6 +59,9 @@ if TYPE_CHECKING:
|
|||||||
# Vflags: TypeAlias = dict[str, Any]
|
# Vflags: TypeAlias = dict[str, Any]
|
||||||
# Mflags: TypeAlias = dict[str, Vflags]
|
# Mflags: TypeAlias = dict[str, Vflags]
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
LEELOO_DALLAS = "leeloo_dallas"
|
LEELOO_DALLAS = "leeloo_dallas"
|
||||||
|
|
||||||
@@ -338,6 +344,8 @@ class VFS(object):
|
|||||||
self.histtab: dict[str, str] = {} # all realpath->histpath
|
self.histtab: dict[str, str] = {} # all realpath->histpath
|
||||||
self.dbv: Optional[VFS] = None # closest full/non-jump parent
|
self.dbv: Optional[VFS] = None # closest full/non-jump parent
|
||||||
self.lim: Optional[Lim] = None # upload limits; only set for dbv
|
self.lim: Optional[Lim] = None # upload limits; only set for dbv
|
||||||
|
self.shr_src: Optional[tuple[VFS, str]] = None # source vfs+rem of a share
|
||||||
|
self.shr_files: set[str] = set() # filenames to include from shr_src
|
||||||
self.aread: dict[str, list[str]] = {}
|
self.aread: dict[str, list[str]] = {}
|
||||||
self.awrite: dict[str, list[str]] = {}
|
self.awrite: dict[str, list[str]] = {}
|
||||||
self.amove: dict[str, list[str]] = {}
|
self.amove: dict[str, list[str]] = {}
|
||||||
@@ -362,6 +370,9 @@ class VFS(object):
|
|||||||
self.all_aps = []
|
self.all_aps = []
|
||||||
self.all_vps = []
|
self.all_vps = []
|
||||||
|
|
||||||
|
self.get_dbv = self._get_dbv
|
||||||
|
self.ls = self._ls
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "VFS(%s)" % (
|
return "VFS(%s)" % (
|
||||||
", ".join(
|
", ".join(
|
||||||
@@ -441,7 +452,7 @@ class VFS(object):
|
|||||||
|
|
||||||
def _find(self, vpath: str) -> tuple["VFS", str]:
|
def _find(self, vpath: str) -> tuple["VFS", str]:
|
||||||
"""return [vfs,remainder]"""
|
"""return [vfs,remainder]"""
|
||||||
if vpath == "":
|
if not vpath:
|
||||||
return self, ""
|
return self, ""
|
||||||
|
|
||||||
if "/" in vpath:
|
if "/" in vpath:
|
||||||
@@ -451,7 +462,7 @@ class VFS(object):
|
|||||||
rem = ""
|
rem = ""
|
||||||
|
|
||||||
if name in self.nodes:
|
if name in self.nodes:
|
||||||
return self.nodes[name]._find(undot(rem))
|
return self.nodes[name]._find(rem)
|
||||||
|
|
||||||
return self, vpath
|
return self, vpath
|
||||||
|
|
||||||
@@ -518,12 +529,20 @@ class VFS(object):
|
|||||||
t = "{} has no {} in [{}] => [{}] => [{}]"
|
t = "{} has no {} in [{}] => [{}] => [{}]"
|
||||||
self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6)
|
self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6)
|
||||||
|
|
||||||
t = 'you don\'t have %s-access in "/%s"'
|
t = 'you don\'t have %s-access in "/%s" or below "/%s"'
|
||||||
raise Pebkac(err, t % (msg, cvpath))
|
raise Pebkac(err, t % (msg, cvpath, vn.vpath))
|
||||||
|
|
||||||
return vn, rem
|
return vn, rem
|
||||||
|
|
||||||
def get_dbv(self, vrem: str) -> tuple["VFS", str]:
|
def _get_share_src(self, vrem: str) -> tuple["VFS", str]:
|
||||||
|
src = self.shr_src
|
||||||
|
if not src:
|
||||||
|
return self._get_dbv(vrem)
|
||||||
|
|
||||||
|
shv, srem = src
|
||||||
|
return shv, vjoin(srem, vrem)
|
||||||
|
|
||||||
|
def _get_dbv(self, vrem: str) -> tuple["VFS", str]:
|
||||||
dbv = self.dbv
|
dbv = self.dbv
|
||||||
if not dbv:
|
if not dbv:
|
||||||
return self, vrem
|
return self, vrem
|
||||||
@@ -549,7 +568,26 @@ class VFS(object):
|
|||||||
ad, fn = os.path.split(ap)
|
ad, fn = os.path.split(ap)
|
||||||
return os.path.join(absreal(ad), fn)
|
return os.path.join(absreal(ad), fn)
|
||||||
|
|
||||||
def ls(
|
def _ls_nope(
|
||||||
|
self, *a, **ka
|
||||||
|
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
||||||
|
raise Pebkac(500, "nope.avi")
|
||||||
|
|
||||||
|
def _ls_shr(
|
||||||
|
self,
|
||||||
|
rem: str,
|
||||||
|
uname: str,
|
||||||
|
scandir: bool,
|
||||||
|
permsets: list[list[bool]],
|
||||||
|
lstat: bool = False,
|
||||||
|
) -> tuple[str, list[tuple[str, os.stat_result]], dict[str, "VFS"]]:
|
||||||
|
"""replaces _ls for certain shares (single-file, or file selection)"""
|
||||||
|
vn, rem = self.shr_src # type: ignore
|
||||||
|
abspath, real, _ = vn.ls(rem, "\n", scandir, permsets, lstat)
|
||||||
|
real = [x for x in real if os.path.basename(x[0]) in self.shr_files]
|
||||||
|
return abspath, real, {}
|
||||||
|
|
||||||
|
def _ls(
|
||||||
self,
|
self,
|
||||||
rem: str,
|
rem: str,
|
||||||
uname: str,
|
uname: str,
|
||||||
@@ -804,6 +842,7 @@ class AuthSrv(object):
|
|||||||
self.vfs = VFS(log_func, "", "", AXS(), {})
|
self.vfs = VFS(log_func, "", "", AXS(), {})
|
||||||
self.acct: dict[str, str] = {}
|
self.acct: dict[str, str] = {}
|
||||||
self.iacct: dict[str, str] = {}
|
self.iacct: dict[str, str] = {}
|
||||||
|
self.defpw: dict[str, str] = {}
|
||||||
self.grps: dict[str, list[str]] = {}
|
self.grps: dict[str, list[str]] = {}
|
||||||
self.re_pwd: Optional[re.Pattern] = None
|
self.re_pwd: Optional[re.Pattern] = None
|
||||||
|
|
||||||
@@ -1349,7 +1388,7 @@ class AuthSrv(object):
|
|||||||
flags[name] = vals
|
flags[name] = vals
|
||||||
self._e("volflag [{}] += {} ({})".format(name, vals, desc))
|
self._e("volflag [{}] += {} ({})".format(name, vals, desc))
|
||||||
|
|
||||||
def reload(self) -> None:
|
def reload(self, verbosity: int = 9) -> None:
|
||||||
"""
|
"""
|
||||||
construct a flat list of mountpoints and usernames
|
construct a flat list of mountpoints and usernames
|
||||||
first from the commandline arguments
|
first from the commandline arguments
|
||||||
@@ -1357,9 +1396,9 @@ class AuthSrv(object):
|
|||||||
before finally building the VFS
|
before finally building the VFS
|
||||||
"""
|
"""
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self._reload()
|
self._reload(verbosity)
|
||||||
|
|
||||||
def _reload(self) -> None:
|
def _reload(self, verbosity: int = 9) -> None:
|
||||||
acct: dict[str, str] = {} # username:password
|
acct: dict[str, str] = {} # username:password
|
||||||
grps: dict[str, list[str]] = {} # groupname:usernames
|
grps: dict[str, list[str]] = {} # groupname:usernames
|
||||||
daxs: dict[str, AXS] = {}
|
daxs: dict[str, AXS] = {}
|
||||||
@@ -1437,6 +1476,8 @@ class AuthSrv(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
self.setup_pwhash(acct)
|
self.setup_pwhash(acct)
|
||||||
|
defpw = acct.copy()
|
||||||
|
self.setup_chpw(acct)
|
||||||
|
|
||||||
# case-insensitive; normalize
|
# case-insensitive; normalize
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
@@ -1452,9 +1493,8 @@ class AuthSrv(object):
|
|||||||
vfs = VFS(self.log_func, absreal("."), "", axs, {})
|
vfs = VFS(self.log_func, absreal("."), "", axs, {})
|
||||||
elif "" not in mount:
|
elif "" not in mount:
|
||||||
# there's volumes but no root; make root inaccessible
|
# there's volumes but no root; make root inaccessible
|
||||||
vfs = VFS(self.log_func, "", "", AXS(), {})
|
zsd = {"d2d": True, "tcolor": self.args.tcolor}
|
||||||
vfs.flags["tcolor"] = self.args.tcolor
|
vfs = VFS(self.log_func, "", "", AXS(), zsd)
|
||||||
vfs.flags["d2d"] = True
|
|
||||||
|
|
||||||
maxdepth = 0
|
maxdepth = 0
|
||||||
for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
|
for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
|
||||||
@@ -1483,6 +1523,56 @@ class AuthSrv(object):
|
|||||||
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
||||||
vol.root = vfs
|
vol.root = vfs
|
||||||
|
|
||||||
|
enshare = self.args.shr
|
||||||
|
shr = enshare[1:-1]
|
||||||
|
shrs = enshare[1:]
|
||||||
|
if enshare:
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
shv = VFS(self.log_func, "", shr, AXS(), {"d2d": True})
|
||||||
|
|
||||||
|
db_path = self.args.shr_db
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cur = db.cursor()
|
||||||
|
cur2 = db.cursor()
|
||||||
|
now = time.time()
|
||||||
|
for row in cur.execute("select * from sh"):
|
||||||
|
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
||||||
|
if s_t1 and s_t1 < now:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.args.shr_v:
|
||||||
|
t = "loading %s share [%s] by [%s] => [%s]"
|
||||||
|
self.log(t % (s_pr, s_k, s_un, s_vp))
|
||||||
|
|
||||||
|
if s_pw:
|
||||||
|
# gotta reuse the "account" for all shares with this pw,
|
||||||
|
# so do a light scramble as this appears in the web-ui
|
||||||
|
zs = ub64enc(hashlib.sha512(s_pw.encode("utf-8")).digest())[4:16]
|
||||||
|
sun = "s_%s" % (zs.decode("utf-8"),)
|
||||||
|
acct[sun] = s_pw
|
||||||
|
else:
|
||||||
|
sun = "*"
|
||||||
|
|
||||||
|
s_axs = AXS(
|
||||||
|
[sun] if "r" in s_pr else [],
|
||||||
|
[sun] if "w" in s_pr else [],
|
||||||
|
[sun] if "m" in s_pr else [],
|
||||||
|
[sun] if "d" in s_pr else [],
|
||||||
|
)
|
||||||
|
|
||||||
|
# don't know the abspath yet + wanna ensure the user
|
||||||
|
# still has the privs they granted, so nullmap it
|
||||||
|
shv.nodes[s_k] = VFS(
|
||||||
|
self.log_func, "", "%s/%s" % (shr, s_k), s_axs, shv.flags.copy()
|
||||||
|
)
|
||||||
|
|
||||||
|
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
||||||
|
for vol in shv.nodes.values():
|
||||||
|
vfs.all_vols[vol.vpath] = vol
|
||||||
|
vol.get_dbv = vol._get_share_src
|
||||||
|
vol.ls = vol._ls_nope
|
||||||
|
|
||||||
zss = set(acct)
|
zss = set(acct)
|
||||||
zss.update(self.idp_accs)
|
zss.update(self.idp_accs)
|
||||||
zss.discard("*")
|
zss.discard("*")
|
||||||
@@ -1501,7 +1591,7 @@ class AuthSrv(object):
|
|||||||
for usr in unames:
|
for usr in unames:
|
||||||
for vp, vol in vfs.all_vols.items():
|
for vp, vol in vfs.all_vols.items():
|
||||||
zx = getattr(vol.axs, axs_key)
|
zx = getattr(vol.axs, axs_key)
|
||||||
if usr in zx:
|
if usr in zx and (not enshare or not vp.startswith(shrs)):
|
||||||
umap[usr].append(vp)
|
umap[usr].append(vp)
|
||||||
umap[usr].sort()
|
umap[usr].sort()
|
||||||
setattr(vfs, "a" + perm, umap)
|
setattr(vfs, "a" + perm, umap)
|
||||||
@@ -1551,6 +1641,8 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
for usr in acct:
|
for usr in acct:
|
||||||
if usr not in associated_users:
|
if usr not in associated_users:
|
||||||
|
if enshare and usr.startswith("s_"):
|
||||||
|
continue
|
||||||
if len(vfs.all_vols) > 1:
|
if len(vfs.all_vols) > 1:
|
||||||
# user probably familiar enough that the verbose message is not necessary
|
# user probably familiar enough that the verbose message is not necessary
|
||||||
t = "account [%s] is not mentioned in any volume definitions; see --help-accounts"
|
t = "account [%s] is not mentioned in any volume definitions; see --help-accounts"
|
||||||
@@ -1799,6 +1891,11 @@ class AuthSrv(object):
|
|||||||
if len(zs) == 3: # fc5 => ffcc55
|
if len(zs) == 3: # fc5 => ffcc55
|
||||||
vol.flags["tcolor"] = "".join([x * 2 for x in zs])
|
vol.flags["tcolor"] = "".join([x * 2 for x in zs])
|
||||||
|
|
||||||
|
if vol.flags.get("neversymlink"):
|
||||||
|
vol.flags["hardlinkonly"] = True # was renamed
|
||||||
|
if vol.flags.get("hardlinkonly"):
|
||||||
|
vol.flags["hardlink"] = True
|
||||||
|
|
||||||
for k1, k2 in IMPLICATIONS:
|
for k1, k2 in IMPLICATIONS:
|
||||||
if k1 in vol.flags:
|
if k1 in vol.flags:
|
||||||
vol.flags[k2] = True
|
vol.flags[k2] = True
|
||||||
@@ -1895,7 +1992,7 @@ class AuthSrv(object):
|
|||||||
self.log(t.format(vol.vpath), 1)
|
self.log(t.format(vol.vpath), 1)
|
||||||
del vol.flags["lifetime"]
|
del vol.flags["lifetime"]
|
||||||
|
|
||||||
needs_e2d = [x for x in hooks if x != "xm"]
|
needs_e2d = [x for x in hooks if x in ("xau", "xiu")]
|
||||||
drop = [x for x in needs_e2d if vol.flags.get(x)]
|
drop = [x for x in needs_e2d if vol.flags.get(x)]
|
||||||
if drop:
|
if drop:
|
||||||
t = 'removing [{}] from volume "/{}" because e2d is disabled'
|
t = 'removing [{}] from volume "/{}" because e2d is disabled'
|
||||||
@@ -1903,9 +2000,6 @@ class AuthSrv(object):
|
|||||||
for x in drop:
|
for x in drop:
|
||||||
vol.flags.pop(x)
|
vol.flags.pop(x)
|
||||||
|
|
||||||
if vol.flags.get("neversymlink") and not vol.flags.get("hardlink"):
|
|
||||||
vol.flags["copydupes"] = True
|
|
||||||
|
|
||||||
# verify tags mentioned by -mt[mp] are used by -mte
|
# verify tags mentioned by -mt[mp] are used by -mte
|
||||||
local_mtp = {}
|
local_mtp = {}
|
||||||
local_only_mtp = {}
|
local_only_mtp = {}
|
||||||
@@ -1984,11 +2078,16 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
have_e2d = False
|
have_e2d = False
|
||||||
have_e2t = False
|
have_e2t = False
|
||||||
|
have_dedup = False
|
||||||
|
unsafe_dedup = []
|
||||||
t = "volumes and permissions:\n"
|
t = "volumes and permissions:\n"
|
||||||
for zv in vfs.all_vols.values():
|
for zv in vfs.all_vols.values():
|
||||||
if not self.warn_anonwrite:
|
if not self.warn_anonwrite or verbosity < 5:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if enshare and (zv.vpath == shr or zv.vpath.startswith(shrs)):
|
||||||
|
continue
|
||||||
|
|
||||||
t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
|
t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
|
||||||
for txt, attr in [
|
for txt, attr in [
|
||||||
[" read", "uread"],
|
[" read", "uread"],
|
||||||
@@ -2013,9 +2112,14 @@ class AuthSrv(object):
|
|||||||
if "e2t" in zv.flags:
|
if "e2t" in zv.flags:
|
||||||
have_e2t = True
|
have_e2t = True
|
||||||
|
|
||||||
|
if "dedup" in zv.flags:
|
||||||
|
have_dedup = True
|
||||||
|
if "e2d" not in zv.flags and "hardlink" not in zv.flags:
|
||||||
|
unsafe_dedup.append("/" + zv.vpath)
|
||||||
|
|
||||||
t += "\n"
|
t += "\n"
|
||||||
|
|
||||||
if self.warn_anonwrite:
|
if self.warn_anonwrite and verbosity > 4:
|
||||||
if not self.args.no_voldump:
|
if not self.args.no_voldump:
|
||||||
self.log(t)
|
self.log(t)
|
||||||
|
|
||||||
@@ -2025,10 +2129,17 @@ class AuthSrv(object):
|
|||||||
self.log("\n\033[{}\033[0m\n".format(t))
|
self.log("\n\033[{}\033[0m\n".format(t))
|
||||||
|
|
||||||
if not have_e2t:
|
if not have_e2t:
|
||||||
t = "hint: argument -e2ts enables multimedia indexing (artist/title/...)"
|
t = "hint: enable multimedia indexing (artist/title/...) with argument -e2ts"
|
||||||
self.log(t, 6)
|
self.log(t, 6)
|
||||||
else:
|
else:
|
||||||
t = "hint: argument -e2dsa enables searching, upload-undo, and better deduplication"
|
t = "hint: enable searching and upload-undo with argument -e2dsa"
|
||||||
|
self.log(t, 6)
|
||||||
|
|
||||||
|
if unsafe_dedup:
|
||||||
|
t = "WARNING: symlink-based deduplication is enabled for some volumes, but without indexing. Please enable -e2dsa and/or --hardlink to avoid problems when moving/renaming files. Affected volumes: %s"
|
||||||
|
self.log(t % (", ".join(unsafe_dedup)), 3)
|
||||||
|
elif not have_dedup:
|
||||||
|
t = "hint: enable upload deduplication with --dedup (but see readme for consequences)"
|
||||||
self.log(t, 6)
|
self.log(t, 6)
|
||||||
|
|
||||||
zv, _ = vfs.get("/", "*", False, False)
|
zv, _ = vfs.get("/", "*", False, False)
|
||||||
@@ -2039,7 +2150,7 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
zv, _ = vfs.get("", "*", False, True, err=999)
|
zv, _ = vfs.get("", "*", False, True, err=999)
|
||||||
if self.warn_anonwrite and os.getcwd() == zv.realpath:
|
if self.warn_anonwrite and verbosity > 4 and os.getcwd() == zv.realpath:
|
||||||
t = "anyone can write to the current directory: {}\n"
|
t = "anyone can write to the current directory: {}\n"
|
||||||
self.log(t.format(zv.realpath), c=1)
|
self.log(t.format(zv.realpath), c=1)
|
||||||
|
|
||||||
@@ -2066,6 +2177,7 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
self.vfs = vfs
|
self.vfs = vfs
|
||||||
self.acct = acct
|
self.acct = acct
|
||||||
|
self.defpw = defpw
|
||||||
self.grps = grps
|
self.grps = grps
|
||||||
self.iacct = {v: k for k, v in acct.items()}
|
self.iacct = {v: k for k, v in acct.items()}
|
||||||
|
|
||||||
@@ -2086,6 +2198,169 @@ class AuthSrv(object):
|
|||||||
MIMES[ext] = mime
|
MIMES[ext] = mime
|
||||||
EXTS.update({v: k for k, v in MIMES.items()})
|
EXTS.update({v: k for k, v in MIMES.items()})
|
||||||
|
|
||||||
|
if enshare:
|
||||||
|
# hide shares from controlpanel
|
||||||
|
vfs.all_vols = {
|
||||||
|
x: y
|
||||||
|
for x, y in vfs.all_vols.items()
|
||||||
|
if x != shr and not x.startswith(shrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert db and cur and cur2 and shv # type: ignore
|
||||||
|
for row in cur.execute("select * from sh"):
|
||||||
|
s_k, s_pw, s_vp, s_pr, s_nf, s_un, s_t0, s_t1 = row
|
||||||
|
shn = shv.nodes.get(s_k, None)
|
||||||
|
if not shn:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
s_vfs, s_rem = vfs.get(
|
||||||
|
s_vp, s_un, "r" in s_pr, "w" in s_pr, "m" in s_pr, "d" in s_pr
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
t = "removing share [%s] by [%s] to [%s] due to %r"
|
||||||
|
self.log(t % (s_k, s_un, s_vp, ex), 3)
|
||||||
|
shv.nodes.pop(s_k)
|
||||||
|
continue
|
||||||
|
|
||||||
|
fns = []
|
||||||
|
if s_nf:
|
||||||
|
q = "select vp from sf where k = ?"
|
||||||
|
for (s_fn,) in cur2.execute(q, (s_k,)):
|
||||||
|
fns.append(s_fn)
|
||||||
|
|
||||||
|
shn.shr_files = set(fns)
|
||||||
|
shn.ls = shn._ls_shr
|
||||||
|
else:
|
||||||
|
shn.ls = shn._ls
|
||||||
|
|
||||||
|
shn.shr_src = (s_vfs, s_rem)
|
||||||
|
shn.realpath = s_vfs.canonical(s_rem)
|
||||||
|
|
||||||
|
if self.args.shr_v:
|
||||||
|
t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
|
||||||
|
self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))
|
||||||
|
|
||||||
|
# transplant shadowing into shares
|
||||||
|
for vn in shv.nodes.values():
|
||||||
|
svn, srem = vn.shr_src # type: ignore
|
||||||
|
if srem:
|
||||||
|
continue # free branch, safe
|
||||||
|
ap = svn.canonical(srem)
|
||||||
|
if bos.path.isfile(ap):
|
||||||
|
continue # also fine
|
||||||
|
for zs in svn.nodes.keys():
|
||||||
|
# hide subvolume
|
||||||
|
vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
|
||||||
|
|
||||||
|
cur2.close()
|
||||||
|
cur.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def chpw(self, broker: Optional["BrokerCli"], uname, pw) -> tuple[bool, str]:
|
||||||
|
if not self.args.chpw:
|
||||||
|
return False, "feature disabled in server config"
|
||||||
|
|
||||||
|
if uname == "*" or uname not in self.defpw:
|
||||||
|
return False, "not logged in"
|
||||||
|
|
||||||
|
if uname in self.args.chpw_no:
|
||||||
|
return False, "not allowed for this account"
|
||||||
|
|
||||||
|
if len(pw) < self.args.chpw_len:
|
||||||
|
t = "minimum password length: %d characters"
|
||||||
|
return False, t % (self.args.chpw_len,)
|
||||||
|
|
||||||
|
hpw = self.ah.hash(pw) if self.ah.on else pw
|
||||||
|
|
||||||
|
if hpw == self.acct[uname]:
|
||||||
|
return False, "that's already your password my dude"
|
||||||
|
|
||||||
|
if hpw in self.iacct:
|
||||||
|
return False, "password is taken"
|
||||||
|
|
||||||
|
with self.mutex:
|
||||||
|
ap = self.args.chpw_db
|
||||||
|
if not bos.path.exists(ap):
|
||||||
|
pwdb = {}
|
||||||
|
else:
|
||||||
|
with open(ap, "r", encoding="utf-8") as f:
|
||||||
|
pwdb = json.load(f)
|
||||||
|
|
||||||
|
pwdb = [x for x in pwdb if x[0] != uname]
|
||||||
|
pwdb.append((uname, self.defpw[uname], hpw))
|
||||||
|
|
||||||
|
with open(ap, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(pwdb, f, separators=(",\n", ": "))
|
||||||
|
|
||||||
|
self.log("reinitializing due to password-change for user [%s]" % (uname,))
|
||||||
|
|
||||||
|
if not broker:
|
||||||
|
# only true for tests
|
||||||
|
self._reload()
|
||||||
|
return True, "new password OK"
|
||||||
|
|
||||||
|
broker.ask("_reload_blocking", False, False).get()
|
||||||
|
return True, "new password OK"
|
||||||
|
|
||||||
|
def setup_chpw(self, acct: dict[str, str]) -> None:
|
||||||
|
ap = self.args.chpw_db
|
||||||
|
if not self.args.chpw or not bos.path.exists(ap):
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(ap, "r", encoding="utf-8") as f:
|
||||||
|
pwdb = json.load(f)
|
||||||
|
|
||||||
|
useen = set()
|
||||||
|
urst = set()
|
||||||
|
uok = set()
|
||||||
|
for usr, orig, mod in pwdb:
|
||||||
|
useen.add(usr)
|
||||||
|
if usr not in acct:
|
||||||
|
# previous user, no longer known
|
||||||
|
continue
|
||||||
|
if acct[usr] != orig:
|
||||||
|
urst.add(usr)
|
||||||
|
continue
|
||||||
|
uok.add(usr)
|
||||||
|
acct[usr] = mod
|
||||||
|
|
||||||
|
if not self.args.chpw_v:
|
||||||
|
return
|
||||||
|
|
||||||
|
for usr in acct:
|
||||||
|
if usr not in useen:
|
||||||
|
urst.add(usr)
|
||||||
|
|
||||||
|
for zs in uok:
|
||||||
|
urst.discard(zs)
|
||||||
|
|
||||||
|
if self.args.chpw_v == 1 or (self.args.chpw_v == 2 and not urst):
|
||||||
|
t = "chpw: %d changed, %d unchanged"
|
||||||
|
self.log(t % (len(uok), len(urst)))
|
||||||
|
return
|
||||||
|
|
||||||
|
elif self.args.chpw_v == 2:
|
||||||
|
t = "chpw: %d changed" % (len(uok))
|
||||||
|
if urst:
|
||||||
|
t += ", \033[0munchanged:\033[35m %s" % (", ".join(list(urst)))
|
||||||
|
|
||||||
|
self.log(t, 6)
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = ""
|
||||||
|
if uok:
|
||||||
|
t = "\033[0mchanged: \033[32m%s"
|
||||||
|
msg += t % (", ".join(list(uok)),)
|
||||||
|
if urst:
|
||||||
|
t = "%s\033[0munchanged: \033[35m%s"
|
||||||
|
msg += t % (
|
||||||
|
", " if msg else "",
|
||||||
|
", ".join(list(urst)),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log("chpw: " + msg, 6)
|
||||||
|
|
||||||
def setup_pwhash(self, acct: dict[str, str]) -> None:
|
def setup_pwhash(self, acct: dict[str, str]) -> None:
|
||||||
self.ah = PWHash(self.args)
|
self.ah = PWHash(self.args)
|
||||||
if not self.ah.on:
|
if not self.ah.on:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import time
|
|||||||
from .__init__ import ANYWIN
|
from .__init__ import ANYWIN
|
||||||
from .util import Netdev, runcmd, wrename, wunlink
|
from .util import Netdev, runcmd, wrename, wunlink
|
||||||
|
|
||||||
HAVE_CFSSL = True
|
HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from .util import NamedLogger, RootLogger
|
from .util import NamedLogger, RootLogger
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
"dav_auth": "davauth",
|
"dav_auth": "davauth",
|
||||||
"dav_rt": "davrt",
|
"dav_rt": "davrt",
|
||||||
"ed": "dots",
|
"ed": "dots",
|
||||||
"never_symlink": "neversymlink",
|
"hardlink_only": "hardlinkonly",
|
||||||
"no_dedup": "copydupes",
|
|
||||||
"no_dupe": "nodupe",
|
"no_dupe": "nodupe",
|
||||||
"no_forget": "noforget",
|
"no_forget": "noforget",
|
||||||
"no_pipe": "nopipe",
|
"no_pipe": "nopipe",
|
||||||
@@ -23,6 +22,7 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
"no_athumb": "dathumb",
|
"no_athumb": "dathumb",
|
||||||
}
|
}
|
||||||
for k in (
|
for k in (
|
||||||
|
"dedup",
|
||||||
"dotsrch",
|
"dotsrch",
|
||||||
"e2d",
|
"e2d",
|
||||||
"e2ds",
|
"e2ds",
|
||||||
@@ -58,6 +58,7 @@ def vf_vmap() -> dict[str, str]:
|
|||||||
"no_hash": "nohash",
|
"no_hash": "nohash",
|
||||||
"no_idx": "noidx",
|
"no_idx": "noidx",
|
||||||
"re_maxage": "scan",
|
"re_maxage": "scan",
|
||||||
|
"safe_dedup": "safededup",
|
||||||
"th_convt": "convt",
|
"th_convt": "convt",
|
||||||
"th_size": "thsize",
|
"th_size": "thsize",
|
||||||
"th_crop": "crop",
|
"th_crop": "crop",
|
||||||
@@ -129,10 +130,11 @@ permdescs = {
|
|||||||
|
|
||||||
flagcats = {
|
flagcats = {
|
||||||
"uploads, general": {
|
"uploads, general": {
|
||||||
|
"dedup": "enable symlink-based file deduplication",
|
||||||
|
"hardlink": "enable hardlink-based file deduplication,\nwith fallback on symlinks when that is impossible",
|
||||||
|
"hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
|
||||||
|
"safededup": "verify on-disk data before using it for dedup",
|
||||||
"nodupe": "rejects existing files (instead of symlinking them)",
|
"nodupe": "rejects existing files (instead of symlinking them)",
|
||||||
"hardlink": "does dedup with hardlinks instead of symlinks",
|
|
||||||
"neversymlink": "disables symlink fallback; full copy instead",
|
|
||||||
"copydupes": "disables dedup, always saves full copies of dupes",
|
|
||||||
"sparse": "force use of sparse files, mainly for s3-backed storage",
|
"sparse": "force use of sparse files, mainly for s3-backed storage",
|
||||||
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
||||||
"nosub": "forces all uploads into the top folder of the vfs",
|
"nosub": "forces all uploads into the top folder of the vfs",
|
||||||
@@ -159,7 +161,7 @@ flagcats = {
|
|||||||
"lifetime=3600": "uploads are deleted after 1 hour",
|
"lifetime=3600": "uploads are deleted after 1 hour",
|
||||||
},
|
},
|
||||||
"database, general": {
|
"database, general": {
|
||||||
"e2d": "enable database; makes files searchable + enables upload dedup",
|
"e2d": "enable database; makes files searchable + enables upload-undo",
|
||||||
"e2ds": "scan writable folders for new files on startup; also sets -e2d",
|
"e2ds": "scan writable folders for new files on startup; also sets -e2d",
|
||||||
"e2dsa": "scans all folders for new files on startup; also sets -e2d",
|
"e2dsa": "scans all folders for new files on startup; also sets -e2d",
|
||||||
"e2t": "enable multimedia indexing; makes it possible to search for tags",
|
"e2t": "enable multimedia indexing; makes it possible to search for tags",
|
||||||
@@ -177,7 +179,7 @@ flagcats = {
|
|||||||
"noforget": "don't forget files when deleted from disk",
|
"noforget": "don't forget files when deleted from disk",
|
||||||
"fat32": "avoid excessive reindexing on android sdcardfs",
|
"fat32": "avoid excessive reindexing on android sdcardfs",
|
||||||
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
||||||
"xlink": "cross-volume dupe detection / linking",
|
"xlink": "cross-volume dupe detection / linking (dangerous)",
|
||||||
"xdev": "do not descend into other filesystems",
|
"xdev": "do not descend into other filesystems",
|
||||||
"xvol": "do not follow symlinks leaving the volume root",
|
"xvol": "do not follow symlinks leaving the volume root",
|
||||||
"dotsrch": "show dotfiles in search results",
|
"dotsrch": "show dotfiles in search results",
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import time
|
|||||||
from .__init__ import ANYWIN, MACOS
|
from .__init__ import ANYWIN, MACOS
|
||||||
from .authsrv import AXS, VFS
|
from .authsrv import AXS, VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import chkcmd, min_ex
|
from .util import chkcmd, min_ex, undot
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from .util import RootLogger
|
from .util import RootLogger, undot
|
||||||
|
|
||||||
|
|
||||||
class Fstab(object):
|
class Fstab(object):
|
||||||
@@ -52,7 +52,7 @@ class Fstab(object):
|
|||||||
self.log(msg.format(path, fs, min_ex()), 3)
|
self.log(msg.format(path, fs, min_ex()), 3)
|
||||||
return fs
|
return fs
|
||||||
|
|
||||||
path = path.lstrip("/")
|
path = undot(path)
|
||||||
try:
|
try:
|
||||||
return self.cache[path]
|
return self.cache[path]
|
||||||
except:
|
except:
|
||||||
@@ -124,7 +124,7 @@ class Fstab(object):
|
|||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
path = self._winpath(path)
|
path = self._winpath(path)
|
||||||
|
|
||||||
path = path.lstrip("/")
|
path = undot(path)
|
||||||
ptn = re.compile(r"^[^\\/]*")
|
ptn = re.compile(r"^[^\\/]*")
|
||||||
vn, rem = self.tab._find(path)
|
vn, rem = self.tab._find(path)
|
||||||
if not self.trusted:
|
if not self.trusted:
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
import typing
|
import typing
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class FSE(FilesystemError):
|
class FSE(FilesystemError):
|
||||||
def __init__(self, msg: str, severity: int = 0) -> None:
|
def __init__(self, msg: str, severity: int = 0) -> None:
|
||||||
@@ -350,7 +353,7 @@ class FtpFs(AbstractedFS):
|
|||||||
svp = join(self.cwd, src).lstrip("/")
|
svp = join(self.cwd, src).lstrip("/")
|
||||||
dvp = join(self.cwd, dst).lstrip("/")
|
dvp = join(self.cwd, dst).lstrip("/")
|
||||||
try:
|
try:
|
||||||
self.hub.up2k.handle_mv(self.uname, svp, dvp)
|
self.hub.up2k.handle_mv(self.uname, self.h.cli_ip, svp, dvp)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise FSE(str(ex))
|
raise FSE(str(ex))
|
||||||
|
|
||||||
@@ -468,6 +471,9 @@ class FtpHandler(FTPHandler):
|
|||||||
xbu = vfs.flags.get("xbu")
|
xbu = vfs.flags.get("xbu")
|
||||||
if xbu and not runhook(
|
if xbu and not runhook(
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
|
self.hub.up2k,
|
||||||
|
"xbu.ftpd",
|
||||||
xbu,
|
xbu,
|
||||||
ap,
|
ap,
|
||||||
vp,
|
vp,
|
||||||
@@ -477,7 +483,7 @@ class FtpHandler(FTPHandler):
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
self.cli_ip,
|
self.cli_ip,
|
||||||
0,
|
time.time(),
|
||||||
"",
|
"",
|
||||||
):
|
):
|
||||||
raise FSE("Upload blocked by xbu server config")
|
raise FSE("Upload blocked by xbu server config")
|
||||||
@@ -580,9 +586,15 @@ class Ftpd(object):
|
|||||||
if "::" in ips:
|
if "::" in ips:
|
||||||
ips.append("0.0.0.0")
|
ips.append("0.0.0.0")
|
||||||
|
|
||||||
|
ips = [x for x in ips if "unix:" not in x]
|
||||||
|
|
||||||
if self.args.ftp4:
|
if self.args.ftp4:
|
||||||
ips = [x for x in ips if ":" not in x]
|
ips = [x for x in ips if ":" not in x]
|
||||||
|
|
||||||
|
if not ips:
|
||||||
|
lgr.fatal("cannot start ftp-server; no compatible IPs in -i")
|
||||||
|
return
|
||||||
|
|
||||||
ips = list(ODict.fromkeys(ips)) # dedup
|
ips = list(ODict.fromkeys(ips)) # dedup
|
||||||
|
|
||||||
ioloop = IOLoop()
|
ioloop = IOLoop()
|
||||||
|
|||||||
@@ -13,18 +13,22 @@ import json
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import string
|
import string
|
||||||
import threading # typechk
|
import threading # typechk
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from email.utils import formatdate, parsedate
|
from email.utils import parsedate
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
import jinja2 # typechk
|
import jinja2 # typechk
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_LZMA"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
import lzma
|
import lzma
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -41,6 +45,7 @@ from .util import unquote # type: ignore
|
|||||||
from .util import (
|
from .util import (
|
||||||
APPLESAN_RE,
|
APPLESAN_RE,
|
||||||
BITNESS,
|
BITNESS,
|
||||||
|
HAVE_SQLITE3,
|
||||||
HTTPCODE,
|
HTTPCODE,
|
||||||
META_NOBOTS,
|
META_NOBOTS,
|
||||||
UTC,
|
UTC,
|
||||||
@@ -54,6 +59,7 @@ from .util import (
|
|||||||
alltrace,
|
alltrace,
|
||||||
atomic_move,
|
atomic_move,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
|
formatdate,
|
||||||
fsenc,
|
fsenc,
|
||||||
gen_filekey,
|
gen_filekey,
|
||||||
gen_filekey_dbg,
|
gen_filekey_dbg,
|
||||||
@@ -69,7 +75,9 @@ from .util import (
|
|||||||
humansize,
|
humansize,
|
||||||
ipnorm,
|
ipnorm,
|
||||||
loadpy,
|
loadpy,
|
||||||
|
log_reloc,
|
||||||
min_ex,
|
min_ex,
|
||||||
|
pathmod,
|
||||||
quotep,
|
quotep,
|
||||||
rand_name,
|
rand_name,
|
||||||
read_header,
|
read_header,
|
||||||
@@ -105,6 +113,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .httpconn import HttpConn
|
from .httpconn import HttpConn
|
||||||
|
|
||||||
|
if not hasattr(socket, "AF_UNIX"):
|
||||||
|
setattr(socket, "AF_UNIX", -9001)
|
||||||
|
|
||||||
_ = (argparse, threading)
|
_ = (argparse, threading)
|
||||||
|
|
||||||
NO_CACHE = {"Cache-Control": "no-cache"}
|
NO_CACHE = {"Cache-Control": "no-cache"}
|
||||||
@@ -222,6 +233,11 @@ class HttpCli(object):
|
|||||||
ka["s_doctitle"] = self.args.doctitle
|
ka["s_doctitle"] = self.args.doctitle
|
||||||
ka["tcolor"] = self.vn.flags["tcolor"]
|
ka["tcolor"] = self.vn.flags["tcolor"]
|
||||||
|
|
||||||
|
if self.args.js_other and "js" not in ka:
|
||||||
|
zs = self.args.js_other
|
||||||
|
zs += "&" if "?" in zs else "?"
|
||||||
|
ka["js"] = zs
|
||||||
|
|
||||||
zso = self.vn.flags.get("html_head")
|
zso = self.vn.flags.get("html_head")
|
||||||
if zso:
|
if zso:
|
||||||
ka["this"] = self
|
ka["this"] = self
|
||||||
@@ -303,8 +319,11 @@ class HttpCli(object):
|
|||||||
)
|
)
|
||||||
self.host = self.headers.get("host") or ""
|
self.host = self.headers.get("host") or ""
|
||||||
if not self.host:
|
if not self.host:
|
||||||
zs = "%s:%s" % self.s.getsockname()[:2]
|
if self.s.family == socket.AF_UNIX:
|
||||||
self.host = zs[7:] if zs.startswith("::ffff:") else zs
|
self.host = self.args.name
|
||||||
|
else:
|
||||||
|
zs = "%s:%s" % self.s.getsockname()[:2]
|
||||||
|
self.host = zs[7:] if zs.startswith("::ffff:") else zs
|
||||||
|
|
||||||
trusted_xff = False
|
trusted_xff = False
|
||||||
n = self.args.rproxy
|
n = self.args.rproxy
|
||||||
@@ -436,7 +455,7 @@ class HttpCli(object):
|
|||||||
t = "incorrect --rp-loc or webserver config; expected vpath starting with [{}] but got [{}]"
|
t = "incorrect --rp-loc or webserver config; expected vpath starting with [{}] but got [{}]"
|
||||||
self.log(t.format(self.args.R, vpath), 1)
|
self.log(t.format(self.args.R, vpath), 1)
|
||||||
|
|
||||||
self.ouparam = {k: zs for k, zs in uparam.items()}
|
self.ouparam = uparam.copy()
|
||||||
|
|
||||||
if self.args.rsp_slp:
|
if self.args.rsp_slp:
|
||||||
time.sleep(self.args.rsp_slp)
|
time.sleep(self.args.rsp_slp)
|
||||||
@@ -445,6 +464,9 @@ class HttpCli(object):
|
|||||||
|
|
||||||
zso = self.headers.get("cookie")
|
zso = self.headers.get("cookie")
|
||||||
if zso:
|
if zso:
|
||||||
|
if len(zso) > 8192:
|
||||||
|
self.loud_reply("cookie header too big", status=400)
|
||||||
|
return False
|
||||||
zsll = [x.split("=", 1) for x in zso.split(";") if "=" in x]
|
zsll = [x.split("=", 1) for x in zso.split(";") if "=" in x]
|
||||||
cookies = {k.strip(): unescape_cookie(zs) for k, zs in zsll}
|
cookies = {k.strip(): unescape_cookie(zs) for k, zs in zsll}
|
||||||
cookie_pw = cookies.get("cppws") or cookies.get("cppwd") or ""
|
cookie_pw = cookies.get("cppws") or cookies.get("cppwd") or ""
|
||||||
@@ -691,6 +713,9 @@ class HttpCli(object):
|
|||||||
xban = self.vn.flags.get("xban")
|
xban = self.vn.flags.get("xban")
|
||||||
if not xban or not runhook(
|
if not xban or not runhook(
|
||||||
self.log,
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xban",
|
||||||
xban,
|
xban,
|
||||||
self.vn.canonical(self.rem),
|
self.vn.canonical(self.rem),
|
||||||
self.vpath,
|
self.vpath,
|
||||||
@@ -787,7 +812,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
# close if unknown length, otherwise take client's preference
|
# close if unknown length, otherwise take client's preference
|
||||||
response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
|
response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
|
||||||
response.append("Date: " + formatdate(usegmt=True))
|
response.append("Date: " + formatdate())
|
||||||
|
|
||||||
# headers{} overrides anything set previously
|
# headers{} overrides anything set previously
|
||||||
if headers:
|
if headers:
|
||||||
@@ -811,9 +836,9 @@ class HttpCli(object):
|
|||||||
self.cbonk(self.conn.hsrv.gmal, zs, "cc_hdr", "Cc in out-hdr")
|
self.cbonk(self.conn.hsrv.gmal, zs, "cc_hdr", "Cc in out-hdr")
|
||||||
raise Pebkac(999)
|
raise Pebkac(999)
|
||||||
|
|
||||||
|
response.append("\r\n")
|
||||||
try:
|
try:
|
||||||
# best practice to separate headers and body into different packets
|
self.s.sendall("\r\n".join(response).encode("utf-8"))
|
||||||
self.s.sendall("\r\n".join(response).encode("utf-8") + b"\r\n\r\n")
|
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "client d/c while replying headers")
|
raise Pebkac(400, "client d/c while replying headers")
|
||||||
|
|
||||||
@@ -947,10 +972,10 @@ class HttpCli(object):
|
|||||||
status: int = 200,
|
status: int = 200,
|
||||||
use302: bool = False,
|
use302: bool = False,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
vp = self.args.RS + vpath
|
vp = self.args.SRS + vpath
|
||||||
html = self.j2s(
|
html = self.j2s(
|
||||||
"msg",
|
"msg",
|
||||||
h2='<a href="/{}">{} /{}</a>'.format(
|
h2='<a href="{}">{} {}</a>'.format(
|
||||||
quotep(vp) + suf, flavor, html_escape(vp, crlf=True) + suf
|
quotep(vp) + suf, flavor, html_escape(vp, crlf=True) + suf
|
||||||
),
|
),
|
||||||
pre=msg,
|
pre=msg,
|
||||||
@@ -958,7 +983,7 @@ class HttpCli(object):
|
|||||||
).encode("utf-8", "replace")
|
).encode("utf-8", "replace")
|
||||||
|
|
||||||
if use302:
|
if use302:
|
||||||
self.reply(html, status=302, headers={"Location": "/" + vpath})
|
self.reply(html, status=302, headers={"Location": vp})
|
||||||
else:
|
else:
|
||||||
self.reply(html, status=status)
|
self.reply(html, status=status)
|
||||||
|
|
||||||
@@ -1120,7 +1145,7 @@ class HttpCli(object):
|
|||||||
if "move" in self.uparam:
|
if "move" in self.uparam:
|
||||||
return self.handle_mv()
|
return self.handle_mv()
|
||||||
|
|
||||||
if not self.vpath:
|
if not self.vpath and self.ouparam:
|
||||||
if "reload" in self.uparam:
|
if "reload" in self.uparam:
|
||||||
return self.handle_reload()
|
return self.handle_reload()
|
||||||
|
|
||||||
@@ -1142,23 +1167,12 @@ class HttpCli(object):
|
|||||||
if "hc" in self.uparam:
|
if "hc" in self.uparam:
|
||||||
return self.tx_svcs()
|
return self.tx_svcs()
|
||||||
|
|
||||||
|
if "shares" in self.uparam:
|
||||||
|
return self.tx_shares()
|
||||||
|
|
||||||
if "h" in self.uparam:
|
if "h" in self.uparam:
|
||||||
return self.tx_mounts()
|
return self.tx_mounts()
|
||||||
|
|
||||||
# conditional redirect to single volumes
|
|
||||||
if self.vpath == "" and not self.ouparam:
|
|
||||||
nread = len(self.rvol)
|
|
||||||
nwrite = len(self.wvol)
|
|
||||||
if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1):
|
|
||||||
if nread == 1:
|
|
||||||
vpath = self.rvol[0]
|
|
||||||
else:
|
|
||||||
vpath = self.wvol[0]
|
|
||||||
|
|
||||||
if self.vpath != vpath:
|
|
||||||
self.redirect(vpath, flavor="redirecting to", use302=True)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return self.tx_browser()
|
return self.tx_browser()
|
||||||
|
|
||||||
def handle_propfind(self) -> bool:
|
def handle_propfind(self) -> bool:
|
||||||
@@ -1168,7 +1182,8 @@ class HttpCli(object):
|
|||||||
if self.args.no_dav:
|
if self.args.no_dav:
|
||||||
raise Pebkac(405, "WebDAV is disabled in server config")
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
||||||
|
|
||||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False, err=401)
|
vn = self.vn
|
||||||
|
rem = self.rem
|
||||||
tap = vn.canonical(rem)
|
tap = vn.canonical(rem)
|
||||||
|
|
||||||
if "davauth" in vn.flags and self.uname == "*":
|
if "davauth" in vn.flags and self.uname == "*":
|
||||||
@@ -1305,7 +1320,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
pvs: dict[str, str] = {
|
pvs: dict[str, str] = {
|
||||||
"displayname": html_escape(rp.split("/")[-1]),
|
"displayname": html_escape(rp.split("/")[-1]),
|
||||||
"getlastmodified": formatdate(mtime, usegmt=True),
|
"getlastmodified": formatdate(mtime),
|
||||||
"resourcetype": '<D:collection xmlns:D="DAV:"/>' if isdir else "",
|
"resourcetype": '<D:collection xmlns:D="DAV:"/>' if isdir else "",
|
||||||
"supportedlock": '<D:lockentry xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>',
|
"supportedlock": '<D:lockentry xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>',
|
||||||
}
|
}
|
||||||
@@ -1552,8 +1567,8 @@ class HttpCli(object):
|
|||||||
self.log("PUT %s @%s" % (self.req, self.uname))
|
self.log("PUT %s @%s" % (self.req, self.uname))
|
||||||
|
|
||||||
if not self.can_write:
|
if not self.can_write:
|
||||||
t = "user {} does not have write-access here"
|
t = "user %s does not have write-access under /%s"
|
||||||
raise Pebkac(403, t.format(self.uname))
|
raise Pebkac(403, t % (self.uname, self.vn.vpath))
|
||||||
|
|
||||||
if not self.args.no_dav and self._applesan():
|
if not self.args.no_dav and self._applesan():
|
||||||
return self.headers.get("content-length") == "0"
|
return self.headers.get("content-length") == "0"
|
||||||
@@ -1596,6 +1611,9 @@ class HttpCli(object):
|
|||||||
if "delete" in self.uparam:
|
if "delete" in self.uparam:
|
||||||
return self.handle_rm([])
|
return self.handle_rm([])
|
||||||
|
|
||||||
|
if "eshare" in self.uparam:
|
||||||
|
return self.handle_eshare()
|
||||||
|
|
||||||
if "application/octet-stream" in ctype:
|
if "application/octet-stream" in ctype:
|
||||||
return self.handle_post_binary()
|
return self.handle_post_binary()
|
||||||
|
|
||||||
@@ -1628,6 +1646,9 @@ class HttpCli(object):
|
|||||||
if xm:
|
if xm:
|
||||||
runhook(
|
runhook(
|
||||||
self.log,
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xm",
|
||||||
xm,
|
xm,
|
||||||
self.vn.canonical(self.rem),
|
self.vn.canonical(self.rem),
|
||||||
self.vpath,
|
self.vpath,
|
||||||
@@ -1776,11 +1797,15 @@ class HttpCli(object):
|
|||||||
|
|
||||||
if xbu:
|
if xbu:
|
||||||
at = time.time() - lifetime
|
at = time.time() - lifetime
|
||||||
if not runhook(
|
vp = vjoin(self.vpath, fn) if nameless else self.vpath
|
||||||
|
hr = runhook(
|
||||||
self.log,
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xbu.http.dump",
|
||||||
xbu,
|
xbu,
|
||||||
path,
|
path,
|
||||||
self.vpath,
|
vp,
|
||||||
self.host,
|
self.host,
|
||||||
self.uname,
|
self.uname,
|
||||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||||
@@ -1789,10 +1814,25 @@ class HttpCli(object):
|
|||||||
self.ip,
|
self.ip,
|
||||||
at,
|
at,
|
||||||
"",
|
"",
|
||||||
):
|
)
|
||||||
|
if not hr:
|
||||||
t = "upload blocked by xbu server config"
|
t = "upload blocked by xbu server config"
|
||||||
self.log(t, 1)
|
self.log(t, 1)
|
||||||
raise Pebkac(403, t)
|
raise Pebkac(403, t)
|
||||||
|
if hr.get("reloc"):
|
||||||
|
x = pathmod(self.asrv.vfs, path, vp, hr["reloc"])
|
||||||
|
if x:
|
||||||
|
if self.args.hook_v:
|
||||||
|
log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem)
|
||||||
|
fdir, self.vpath, fn, (vfs, rem) = x
|
||||||
|
if self.args.nw:
|
||||||
|
fn = os.devnull
|
||||||
|
else:
|
||||||
|
bos.makedirs(fdir)
|
||||||
|
path = os.path.join(fdir, fn)
|
||||||
|
if not nameless:
|
||||||
|
self.vpath = vjoin(self.vpath, fn)
|
||||||
|
params["fdir"] = fdir
|
||||||
|
|
||||||
if is_put and not (self.args.no_dav or self.args.nw) and bos.path.exists(path):
|
if is_put and not (self.args.no_dav or self.args.nw) and bos.path.exists(path):
|
||||||
# allow overwrite if...
|
# allow overwrite if...
|
||||||
@@ -1867,24 +1907,45 @@ class HttpCli(object):
|
|||||||
fn = fn2
|
fn = fn2
|
||||||
path = path2
|
path = path2
|
||||||
|
|
||||||
if xau and not runhook(
|
if xau:
|
||||||
self.log,
|
vp = vjoin(self.vpath, fn) if nameless else self.vpath
|
||||||
xau,
|
hr = runhook(
|
||||||
path,
|
self.log,
|
||||||
self.vpath,
|
self.conn.hsrv.broker,
|
||||||
self.host,
|
None,
|
||||||
self.uname,
|
"xau.http.dump",
|
||||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
xau,
|
||||||
mt,
|
path,
|
||||||
post_sz,
|
vp,
|
||||||
self.ip,
|
self.host,
|
||||||
at,
|
self.uname,
|
||||||
"",
|
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||||
):
|
mt,
|
||||||
t = "upload blocked by xau server config"
|
post_sz,
|
||||||
self.log(t, 1)
|
self.ip,
|
||||||
wunlink(self.log, path, vfs.flags)
|
at,
|
||||||
raise Pebkac(403, t)
|
"",
|
||||||
|
)
|
||||||
|
if not hr:
|
||||||
|
t = "upload blocked by xau server config"
|
||||||
|
self.log(t, 1)
|
||||||
|
wunlink(self.log, path, vfs.flags)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
if hr.get("reloc"):
|
||||||
|
x = pathmod(self.asrv.vfs, path, vp, hr["reloc"])
|
||||||
|
if x:
|
||||||
|
if self.args.hook_v:
|
||||||
|
log_reloc(self.log, hr["reloc"], x, path, vp, fn, vfs, rem)
|
||||||
|
fdir, self.vpath, fn, (vfs, rem) = x
|
||||||
|
bos.makedirs(fdir)
|
||||||
|
path2 = os.path.join(fdir, fn)
|
||||||
|
atomic_move(self.log, path, path2, vfs.flags)
|
||||||
|
path = path2
|
||||||
|
if not nameless:
|
||||||
|
self.vpath = vjoin(self.vpath, fn)
|
||||||
|
sz = bos.path.getsize(path)
|
||||||
|
else:
|
||||||
|
sz = post_sz
|
||||||
|
|
||||||
vfs, rem = vfs.get_dbv(rem)
|
vfs, rem = vfs.get_dbv(rem)
|
||||||
self.conn.hsrv.broker.say(
|
self.conn.hsrv.broker.say(
|
||||||
@@ -1907,7 +1968,7 @@ class HttpCli(object):
|
|||||||
alg,
|
alg,
|
||||||
self.args.fk_salt,
|
self.args.fk_salt,
|
||||||
path,
|
path,
|
||||||
post_sz,
|
sz,
|
||||||
0 if ANYWIN else bos.stat(path).st_ino,
|
0 if ANYWIN else bos.stat(path).st_ino,
|
||||||
)[: vfs.flags["fk"]]
|
)[: vfs.flags["fk"]]
|
||||||
|
|
||||||
@@ -2024,6 +2085,9 @@ class HttpCli(object):
|
|||||||
if act == "zip":
|
if act == "zip":
|
||||||
return self.handle_zip_post()
|
return self.handle_zip_post()
|
||||||
|
|
||||||
|
if act == "chpw":
|
||||||
|
return self.handle_chpw()
|
||||||
|
|
||||||
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
||||||
|
|
||||||
def handle_zip_post(self) -> bool:
|
def handle_zip_post(self) -> bool:
|
||||||
@@ -2082,6 +2146,9 @@ class HttpCli(object):
|
|||||||
if "srch" in self.uparam or "srch" in body:
|
if "srch" in self.uparam or "srch" in body:
|
||||||
return self.handle_search(body)
|
return self.handle_search(body)
|
||||||
|
|
||||||
|
if "share" in self.uparam:
|
||||||
|
return self.handle_share(body)
|
||||||
|
|
||||||
if "delete" in self.uparam:
|
if "delete" in self.uparam:
|
||||||
return self.handle_rm(body)
|
return self.handle_rm(body)
|
||||||
|
|
||||||
@@ -2138,7 +2205,9 @@ class HttpCli(object):
|
|||||||
def handle_search(self, body: dict[str, Any]) -> bool:
|
def handle_search(self, body: dict[str, Any]) -> bool:
|
||||||
idx = self.conn.get_u2idx()
|
idx = self.conn.get_u2idx()
|
||||||
if not idx or not hasattr(idx, "p_end"):
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
raise Pebkac(500, "server busy, or sqlite3 not available; cannot search")
|
if not HAVE_SQLITE3:
|
||||||
|
raise Pebkac(500, "sqlite3 not found on server; search is disabled")
|
||||||
|
raise Pebkac(500, "server busy, cannot search; please retry in a bit")
|
||||||
|
|
||||||
vols: list[VFS] = []
|
vols: list[VFS] = []
|
||||||
seen: dict[VFS, bool] = {}
|
seen: dict[VFS, bool] = {}
|
||||||
@@ -2210,13 +2279,21 @@ class HttpCli(object):
|
|||||||
raise Pebkac(400, "need hash and wark headers for binary POST")
|
raise Pebkac(400, "need hash and wark headers for binary POST")
|
||||||
|
|
||||||
chashes = [x.strip() for x in chashes]
|
chashes = [x.strip() for x in chashes]
|
||||||
|
if len(chashes) == 3 and len(chashes[1]) == 1:
|
||||||
|
# the first hash, then length of consecutive hashes,
|
||||||
|
# then a list of stitched hashes as one long string
|
||||||
|
clen = int(chashes[1])
|
||||||
|
siblings = chashes[2]
|
||||||
|
chashes = [chashes[0]]
|
||||||
|
for n in range(0, len(siblings), clen):
|
||||||
|
chashes.append(siblings[n : n + clen])
|
||||||
|
|
||||||
vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||||
ptop = (vfs.dbv or vfs).realpath
|
ptop = (vfs.dbv or vfs).realpath
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_chunks", ptop, wark, chashes)
|
x = self.conn.hsrv.broker.ask("up2k.handle_chunks", ptop, wark, chashes)
|
||||||
response = x.get()
|
response = x.get()
|
||||||
chunksize, cstarts, path, lastmod, sprs = response
|
chashes, chunksize, cstarts, path, lastmod, sprs = response
|
||||||
maxsize = chunksize * len(chashes)
|
maxsize = chunksize * len(chashes)
|
||||||
cstart0 = cstarts[0]
|
cstart0 = cstarts[0]
|
||||||
|
|
||||||
@@ -2320,6 +2397,22 @@ class HttpCli(object):
|
|||||||
self.reply(b"thank")
|
self.reply(b"thank")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def handle_chpw(self) -> bool:
|
||||||
|
assert self.parser
|
||||||
|
pwd = self.parser.require("pw", 64)
|
||||||
|
self.parser.drop()
|
||||||
|
|
||||||
|
ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd)
|
||||||
|
if ok:
|
||||||
|
ok, msg = self.get_pwd_cookie(pwd)
|
||||||
|
if ok:
|
||||||
|
msg = "new password OK"
|
||||||
|
|
||||||
|
redir = (self.args.SRS + "?h") if ok else ""
|
||||||
|
html = self.j2s("msg", h1=msg, h2='<a href="/?h">ack</a>', redir=redir)
|
||||||
|
self.reply(html.encode("utf-8"))
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_login(self) -> bool:
|
def handle_login(self) -> bool:
|
||||||
assert self.parser
|
assert self.parser
|
||||||
pwd = self.parser.require("cppwd", 64)
|
pwd = self.parser.require("cppwd", 64)
|
||||||
@@ -2344,12 +2437,12 @@ class HttpCli(object):
|
|||||||
dst += "&" if "?" in dst else "?"
|
dst += "&" if "?" in dst else "?"
|
||||||
dst += "_=1#" + html_escape(uhash, True, True)
|
dst += "_=1#" + html_escape(uhash, True, True)
|
||||||
|
|
||||||
msg = self.get_pwd_cookie(pwd)
|
_, msg = self.get_pwd_cookie(pwd)
|
||||||
html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
||||||
self.reply(html.encode("utf-8"))
|
self.reply(html.encode("utf-8"))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_pwd_cookie(self, pwd: str) -> str:
|
def get_pwd_cookie(self, pwd: str) -> tuple[bool, str]:
|
||||||
hpwd = self.asrv.ah.hash(pwd)
|
hpwd = self.asrv.ah.hash(pwd)
|
||||||
uname = self.asrv.iacct.get(hpwd)
|
uname = self.asrv.iacct.get(hpwd)
|
||||||
if uname:
|
if uname:
|
||||||
@@ -2381,7 +2474,7 @@ class HttpCli(object):
|
|||||||
ck = gencookie(k, pwd, self.args.R, self.is_https, dur, "; HttpOnly")
|
ck = gencookie(k, pwd, self.args.R, self.is_https, dur, "; HttpOnly")
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
|
|
||||||
return msg
|
return dur > 0, msg
|
||||||
|
|
||||||
def handle_mkdir(self) -> bool:
|
def handle_mkdir(self) -> bool:
|
||||||
assert self.parser
|
assert self.parser
|
||||||
@@ -2420,7 +2513,7 @@ class HttpCli(object):
|
|||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
self.out_headers["X-New-Dir"] = quotep(vpath)
|
self.out_headers["X-New-Dir"] = quotep(self.args.RS + vpath)
|
||||||
|
|
||||||
if dav:
|
if dav:
|
||||||
self.reply(b"", 201)
|
self.reply(b"", 201)
|
||||||
@@ -2524,18 +2617,15 @@ class HttpCli(object):
|
|||||||
fname = sanitize_fn(
|
fname = sanitize_fn(
|
||||||
p_file or "", "", [".prologue.html", ".epilogue.html"]
|
p_file or "", "", [".prologue.html", ".epilogue.html"]
|
||||||
)
|
)
|
||||||
|
abspath = os.path.join(fdir, fname)
|
||||||
|
suffix = "-%.6f-%s" % (time.time(), dip)
|
||||||
if p_file and not nullwrite:
|
if p_file and not nullwrite:
|
||||||
if rnd:
|
if rnd:
|
||||||
fname = rand_name(fdir, fname, rnd)
|
fname = rand_name(fdir, fname, rnd)
|
||||||
|
|
||||||
if not bos.path.isdir(fdir):
|
|
||||||
raise Pebkac(404, "that folder does not exist")
|
|
||||||
|
|
||||||
suffix = "-{:.6f}-{}".format(time.time(), dip)
|
|
||||||
open_args = {"fdir": fdir, "suffix": suffix}
|
open_args = {"fdir": fdir, "suffix": suffix}
|
||||||
|
|
||||||
if "replace" in self.uparam:
|
if "replace" in self.uparam:
|
||||||
abspath = os.path.join(fdir, fname)
|
|
||||||
if not self.can_delete:
|
if not self.can_delete:
|
||||||
self.log("user not allowed to overwrite with ?replace")
|
self.log("user not allowed to overwrite with ?replace")
|
||||||
elif bos.path.exists(abspath):
|
elif bos.path.exists(abspath):
|
||||||
@@ -2545,6 +2635,58 @@ class HttpCli(object):
|
|||||||
except:
|
except:
|
||||||
t = "toctou while deleting for ?replace: %s"
|
t = "toctou while deleting for ?replace: %s"
|
||||||
self.log(t % (abspath,))
|
self.log(t % (abspath,))
|
||||||
|
else:
|
||||||
|
open_args = {}
|
||||||
|
tnam = fname = os.devnull
|
||||||
|
fdir = abspath = ""
|
||||||
|
|
||||||
|
if xbu:
|
||||||
|
at = time.time() - lifetime
|
||||||
|
hr = runhook(
|
||||||
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xbu.http.bup",
|
||||||
|
xbu,
|
||||||
|
abspath,
|
||||||
|
vjoin(upload_vpath, fname),
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.asrv.vfs.get_perms(upload_vpath, self.uname),
|
||||||
|
at,
|
||||||
|
0,
|
||||||
|
self.ip,
|
||||||
|
at,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if not hr:
|
||||||
|
t = "upload blocked by xbu server config"
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
if hr.get("reloc"):
|
||||||
|
zs = vjoin(upload_vpath, fname)
|
||||||
|
x = pathmod(self.asrv.vfs, abspath, zs, hr["reloc"])
|
||||||
|
if x:
|
||||||
|
if self.args.hook_v:
|
||||||
|
log_reloc(
|
||||||
|
self.log,
|
||||||
|
hr["reloc"],
|
||||||
|
x,
|
||||||
|
abspath,
|
||||||
|
zs,
|
||||||
|
fname,
|
||||||
|
vfs,
|
||||||
|
rem,
|
||||||
|
)
|
||||||
|
fdir, upload_vpath, fname, (vfs, rem) = x
|
||||||
|
abspath = os.path.join(fdir, fname)
|
||||||
|
if nullwrite:
|
||||||
|
fdir = abspath = ""
|
||||||
|
else:
|
||||||
|
open_args["fdir"] = fdir
|
||||||
|
|
||||||
|
if p_file and not nullwrite:
|
||||||
|
bos.makedirs(fdir)
|
||||||
|
|
||||||
# reserve destination filename
|
# reserve destination filename
|
||||||
with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as zfw:
|
with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as zfw:
|
||||||
@@ -2560,26 +2702,6 @@ class HttpCli(object):
|
|||||||
tnam = fname = os.devnull
|
tnam = fname = os.devnull
|
||||||
fdir = abspath = ""
|
fdir = abspath = ""
|
||||||
|
|
||||||
if xbu:
|
|
||||||
at = time.time() - lifetime
|
|
||||||
if not runhook(
|
|
||||||
self.log,
|
|
||||||
xbu,
|
|
||||||
abspath,
|
|
||||||
self.vpath,
|
|
||||||
self.host,
|
|
||||||
self.uname,
|
|
||||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
|
||||||
at,
|
|
||||||
0,
|
|
||||||
self.ip,
|
|
||||||
at,
|
|
||||||
"",
|
|
||||||
):
|
|
||||||
t = "upload blocked by xbu server config"
|
|
||||||
self.log(t, 1)
|
|
||||||
raise Pebkac(403, t)
|
|
||||||
|
|
||||||
if lim:
|
if lim:
|
||||||
lim.chk_bup(self.ip)
|
lim.chk_bup(self.ip)
|
||||||
lim.chk_nup(self.ip)
|
lim.chk_nup(self.ip)
|
||||||
@@ -2622,29 +2744,58 @@ class HttpCli(object):
|
|||||||
|
|
||||||
tabspath = ""
|
tabspath = ""
|
||||||
|
|
||||||
|
at = time.time() - lifetime
|
||||||
|
if xau:
|
||||||
|
hr = runhook(
|
||||||
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xau.http.bup",
|
||||||
|
xau,
|
||||||
|
abspath,
|
||||||
|
vjoin(upload_vpath, fname),
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.asrv.vfs.get_perms(upload_vpath, self.uname),
|
||||||
|
at,
|
||||||
|
sz,
|
||||||
|
self.ip,
|
||||||
|
at,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if not hr:
|
||||||
|
t = "upload blocked by xau server config"
|
||||||
|
self.log(t, 1)
|
||||||
|
wunlink(self.log, abspath, vfs.flags)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
if hr.get("reloc"):
|
||||||
|
zs = vjoin(upload_vpath, fname)
|
||||||
|
x = pathmod(self.asrv.vfs, abspath, zs, hr["reloc"])
|
||||||
|
if x:
|
||||||
|
if self.args.hook_v:
|
||||||
|
log_reloc(
|
||||||
|
self.log,
|
||||||
|
hr["reloc"],
|
||||||
|
x,
|
||||||
|
abspath,
|
||||||
|
zs,
|
||||||
|
fname,
|
||||||
|
vfs,
|
||||||
|
rem,
|
||||||
|
)
|
||||||
|
fdir, upload_vpath, fname, (vfs, rem) = x
|
||||||
|
ap2 = os.path.join(fdir, fname)
|
||||||
|
if nullwrite:
|
||||||
|
fdir = ap2 = ""
|
||||||
|
else:
|
||||||
|
bos.makedirs(fdir)
|
||||||
|
atomic_move(self.log, abspath, ap2, vfs.flags)
|
||||||
|
abspath = ap2
|
||||||
|
sz = bos.path.getsize(abspath)
|
||||||
|
|
||||||
files.append(
|
files.append(
|
||||||
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath)
|
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath)
|
||||||
)
|
)
|
||||||
at = time.time() - lifetime
|
|
||||||
if xau and not runhook(
|
|
||||||
self.log,
|
|
||||||
xau,
|
|
||||||
abspath,
|
|
||||||
self.vpath,
|
|
||||||
self.host,
|
|
||||||
self.uname,
|
|
||||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
|
||||||
at,
|
|
||||||
sz,
|
|
||||||
self.ip,
|
|
||||||
at,
|
|
||||||
"",
|
|
||||||
):
|
|
||||||
t = "upload blocked by xau server config"
|
|
||||||
self.log(t, 1)
|
|
||||||
wunlink(self.log, abspath, vfs.flags)
|
|
||||||
raise Pebkac(403, t)
|
|
||||||
|
|
||||||
dbv, vrem = vfs.get_dbv(rem)
|
dbv, vrem = vfs.get_dbv(rem)
|
||||||
self.conn.hsrv.broker.say(
|
self.conn.hsrv.broker.say(
|
||||||
"up2k.hash_file",
|
"up2k.hash_file",
|
||||||
@@ -2700,13 +2851,14 @@ class HttpCli(object):
|
|||||||
for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
|
for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
|
||||||
vsuf = ""
|
vsuf = ""
|
||||||
if (self.can_read or self.can_upget) and "fk" in vfs.flags:
|
if (self.can_read or self.can_upget) and "fk" in vfs.flags:
|
||||||
|
st = bos.stat(ap)
|
||||||
alg = 2 if "fka" in vfs.flags else 1
|
alg = 2 if "fka" in vfs.flags else 1
|
||||||
vsuf = "?k=" + self.gen_fk(
|
vsuf = "?k=" + self.gen_fk(
|
||||||
alg,
|
alg,
|
||||||
self.args.fk_salt,
|
self.args.fk_salt,
|
||||||
ap,
|
ap,
|
||||||
sz,
|
st.st_size,
|
||||||
0 if ANYWIN or not ap else bos.stat(ap).st_ino,
|
0 if ANYWIN or not ap else st.st_ino,
|
||||||
)[: vfs.flags["fk"]]
|
)[: vfs.flags["fk"]]
|
||||||
|
|
||||||
if "media" in self.uparam or "medialinks" in vfs.flags:
|
if "media" in self.uparam or "medialinks" in vfs.flags:
|
||||||
@@ -2873,6 +3025,9 @@ class HttpCli(object):
|
|||||||
if xbu:
|
if xbu:
|
||||||
if not runhook(
|
if not runhook(
|
||||||
self.log,
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xbu.http.txt",
|
||||||
xbu,
|
xbu,
|
||||||
fp,
|
fp,
|
||||||
self.vpath,
|
self.vpath,
|
||||||
@@ -2912,6 +3067,9 @@ class HttpCli(object):
|
|||||||
xau = vfs.flags.get("xau")
|
xau = vfs.flags.get("xau")
|
||||||
if xau and not runhook(
|
if xau and not runhook(
|
||||||
self.log,
|
self.log,
|
||||||
|
self.conn.hsrv.broker,
|
||||||
|
None,
|
||||||
|
"xau.http.txt",
|
||||||
xau,
|
xau,
|
||||||
fp,
|
fp,
|
||||||
self.vpath,
|
self.vpath,
|
||||||
@@ -2952,7 +3110,7 @@ class HttpCli(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _chk_lastmod(self, file_ts: int) -> tuple[str, bool]:
|
def _chk_lastmod(self, file_ts: int) -> tuple[str, bool]:
|
||||||
file_lastmod = formatdate(file_ts, usegmt=True)
|
file_lastmod = formatdate(file_ts)
|
||||||
cli_lastmod = self.headers.get("if-modified-since")
|
cli_lastmod = self.headers.get("if-modified-since")
|
||||||
if cli_lastmod:
|
if cli_lastmod:
|
||||||
try:
|
try:
|
||||||
@@ -3034,8 +3192,8 @@ class HttpCli(object):
|
|||||||
for n, fn in enumerate([".prologue.html", ".epilogue.html"]):
|
for n, fn in enumerate([".prologue.html", ".epilogue.html"]):
|
||||||
if lnames is not None and fn not in lnames:
|
if lnames is not None and fn not in lnames:
|
||||||
continue
|
continue
|
||||||
fn = os.path.join(abspath, fn)
|
fn = "%s/%s" % (abspath, fn)
|
||||||
if bos.path.exists(fn):
|
if bos.path.isfile(fn):
|
||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
logues[n] = f.read().decode("utf-8")
|
logues[n] = f.read().decode("utf-8")
|
||||||
if "exp" in vn.flags:
|
if "exp" in vn.flags:
|
||||||
@@ -3053,7 +3211,7 @@ class HttpCli(object):
|
|||||||
fns = []
|
fns = []
|
||||||
|
|
||||||
for fn in fns:
|
for fn in fns:
|
||||||
fn = os.path.join(abspath, fn)
|
fn = "%s/%s" % (abspath, fn)
|
||||||
if bos.path.isfile(fn):
|
if bos.path.isfile(fn):
|
||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
readme = f.read().decode("utf-8")
|
readme = f.read().decode("utf-8")
|
||||||
@@ -3108,7 +3266,8 @@ class HttpCli(object):
|
|||||||
raise Exception("not found in registry")
|
raise Exception("not found in registry")
|
||||||
self.pipes.set(req_path, job)
|
self.pipes.set(req_path, job)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
|
if getattr(ex, "errno", 0) != errno.ENOENT:
|
||||||
|
self.log("will not pipe [%s]; %s" % (ap_data, ex), 6)
|
||||||
ptop = None
|
ptop = None
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -3588,7 +3747,7 @@ class HttpCli(object):
|
|||||||
# (useragent-sniffing kinshi due to caching proxies)
|
# (useragent-sniffing kinshi due to caching proxies)
|
||||||
mime, ico = self.ico.get(txt, not small, "raster" in self.uparam)
|
mime, ico = self.ico.get(txt, not small, "raster" in self.uparam)
|
||||||
|
|
||||||
lm = formatdate(self.E.t0, usegmt=True)
|
lm = formatdate(self.E.t0)
|
||||||
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -3667,6 +3826,11 @@ class HttpCli(object):
|
|||||||
"arg_base": arg_base,
|
"arg_base": arg_base,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.args.js_other and "js" not in targs:
|
||||||
|
zs = self.args.js_other
|
||||||
|
zs += "&" if "?" in zs else "?"
|
||||||
|
targs["js"] = zs
|
||||||
|
|
||||||
zfv = self.vn.flags.get("html_head")
|
zfv = self.vn.flags.get("html_head")
|
||||||
if zfv:
|
if zfv:
|
||||||
targs["this"] = self
|
targs["this"] = self
|
||||||
@@ -3795,6 +3959,7 @@ class HttpCli(object):
|
|||||||
rvol=rvol,
|
rvol=rvol,
|
||||||
wvol=wvol,
|
wvol=wvol,
|
||||||
avol=avol,
|
avol=avol,
|
||||||
|
in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]),
|
||||||
vstate=vstate,
|
vstate=vstate,
|
||||||
scanning=vs["scanning"],
|
scanning=vs["scanning"],
|
||||||
hashq=vs["hashq"],
|
hashq=vs["hashq"],
|
||||||
@@ -3805,6 +3970,7 @@ class HttpCli(object):
|
|||||||
k304=self.k304(),
|
k304=self.k304(),
|
||||||
k304vis=self.args.k304 > 0,
|
k304vis=self.args.k304 > 0,
|
||||||
ver=S_VERSION if self.args.ver else "",
|
ver=S_VERSION if self.args.ver else "",
|
||||||
|
chpw=self.args.chpw and self.uname != "*",
|
||||||
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
||||||
)
|
)
|
||||||
self.reply(html.encode("utf-8"))
|
self.reply(html.encode("utf-8"))
|
||||||
@@ -3842,10 +4008,10 @@ class HttpCli(object):
|
|||||||
def tx_404(self, is_403: bool = False) -> bool:
|
def tx_404(self, is_403: bool = False) -> bool:
|
||||||
rc = 404
|
rc = 404
|
||||||
if self.args.vague_403:
|
if self.args.vague_403:
|
||||||
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try logging in or <a href="{}/?h">go home</a></p>'
|
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try a password or <a href="{}/?h">go home</a></p>'
|
||||||
pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try logging in)"
|
pt = "404 not found ┐( ´ -`)┌ (or maybe you don't have access -- try a password)"
|
||||||
elif is_403:
|
elif is_403:
|
||||||
t = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">you\'ll have to log in or <a href="{}/?h">go home</a></p>'
|
t = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">use a password or <a href="{}/?h">go home</a></p>'
|
||||||
pt = "403 forbiddena ~┻━┻ (you'll have to log in)"
|
pt = "403 forbiddena ~┻━┻ (you'll have to log in)"
|
||||||
rc = 403
|
rc = 403
|
||||||
else:
|
else:
|
||||||
@@ -3862,7 +4028,8 @@ class HttpCli(object):
|
|||||||
|
|
||||||
t = t.format(self.args.SR)
|
t = t.format(self.args.SR)
|
||||||
qv = quotep(self.vpaths) + self.ourlq()
|
qv = quotep(self.vpaths) + self.ourlq()
|
||||||
html = self.j2s("splash", this=self, qvpath=qv, msg=t)
|
in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:])
|
||||||
|
html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t)
|
||||||
self.reply(html.encode("utf-8"), status=rc)
|
self.reply(html.encode("utf-8"), status=rc)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -3939,7 +4106,9 @@ class HttpCli(object):
|
|||||||
dst = dst[len(top) + 1 :]
|
dst = dst[len(top) + 1 :]
|
||||||
|
|
||||||
ret = self.gen_tree(top, dst, self.uparam.get("k", ""))
|
ret = self.gen_tree(top, dst, self.uparam.get("k", ""))
|
||||||
if self.is_vproxied:
|
if self.is_vproxied and not self.uparam["tree"]:
|
||||||
|
# uparam is '' on initial load, which is
|
||||||
|
# the only time we gotta fill in the blanks
|
||||||
parents = self.args.R.split("/")
|
parents = self.args.R.split("/")
|
||||||
for parent in reversed(parents):
|
for parent in reversed(parents):
|
||||||
ret = {"k%s" % (parent,): ret, "a": []}
|
ret = {"k%s" % (parent,): ret, "a": []}
|
||||||
@@ -4014,7 +4183,9 @@ class HttpCli(object):
|
|||||||
def tx_ups(self) -> bool:
|
def tx_ups(self) -> bool:
|
||||||
idx = self.conn.get_u2idx()
|
idx = self.conn.get_u2idx()
|
||||||
if not idx or not hasattr(idx, "p_end"):
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
raise Pebkac(500, "sqlite3 is not available on the server; cannot unpost")
|
if not HAVE_SQLITE3:
|
||||||
|
raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
|
||||||
|
raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
|
||||||
|
|
||||||
filt = self.uparam.get("filter") or ""
|
filt = self.uparam.get("filter") or ""
|
||||||
lm = "ups [{}]".format(filt)
|
lm = "ups [{}]".format(filt)
|
||||||
@@ -4103,6 +4274,187 @@ class HttpCli(object):
|
|||||||
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def tx_shares(self) -> bool:
|
||||||
|
if self.uname == "*":
|
||||||
|
self.loud_reply("you're not logged in")
|
||||||
|
return True
|
||||||
|
|
||||||
|
idx = self.conn.get_u2idx()
|
||||||
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
raise Pebkac(500, "sqlite3 not found on server; sharing is disabled")
|
||||||
|
raise Pebkac(500, "server busy, cannot list shares; please retry in a bit")
|
||||||
|
|
||||||
|
cur = idx.get_shr()
|
||||||
|
if not cur:
|
||||||
|
raise Pebkac(400, "huh, sharing must be disabled in the server config...")
|
||||||
|
|
||||||
|
rows = cur.execute("select * from sh").fetchall()
|
||||||
|
rows = [list(x) for x in rows]
|
||||||
|
|
||||||
|
if self.uname != self.args.shr_adm:
|
||||||
|
rows = [x for x in rows if x[5] == self.uname]
|
||||||
|
|
||||||
|
for x in rows:
|
||||||
|
x[1] = "yes" if x[1] else ""
|
||||||
|
|
||||||
|
html = self.j2s(
|
||||||
|
"shares", this=self, shr=self.args.shr, rows=rows, now=int(time.time())
|
||||||
|
)
|
||||||
|
self.reply(html.encode("utf-8"), status=200)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def handle_eshare(self) -> bool:
|
||||||
|
idx = self.conn.get_u2idx()
|
||||||
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
raise Pebkac(500, "sqlite3 not found on server; sharing is disabled")
|
||||||
|
raise Pebkac(500, "server busy, cannot create share; please retry in a bit")
|
||||||
|
|
||||||
|
if self.args.shr_v:
|
||||||
|
self.log("handle_eshare: " + self.req)
|
||||||
|
|
||||||
|
cur = idx.get_shr()
|
||||||
|
if not cur:
|
||||||
|
raise Pebkac(400, "huh, sharing must be disabled in the server config...")
|
||||||
|
|
||||||
|
skey = self.vpath.split("/")[-1]
|
||||||
|
|
||||||
|
rows = cur.execute("select un, t1 from sh where k = ?", (skey,)).fetchall()
|
||||||
|
un = rows[0][0] if rows and rows[0] else ""
|
||||||
|
|
||||||
|
if not un:
|
||||||
|
raise Pebkac(400, "that sharekey didn't match anything")
|
||||||
|
|
||||||
|
expiry = rows[0][1]
|
||||||
|
|
||||||
|
if un != self.uname and self.uname != self.args.shr_adm:
|
||||||
|
t = "your username (%r) does not match the sharekey's owner (%r) and you're not admin"
|
||||||
|
raise Pebkac(400, t % (self.uname, un))
|
||||||
|
|
||||||
|
reload = False
|
||||||
|
act = self.uparam["eshare"]
|
||||||
|
if act == "rm":
|
||||||
|
cur.execute("delete from sh where k = ?", (skey,))
|
||||||
|
if skey in self.asrv.vfs.nodes[self.args.shr.strip("/")].nodes:
|
||||||
|
reload = True
|
||||||
|
else:
|
||||||
|
now = time.time()
|
||||||
|
if expiry < now:
|
||||||
|
expiry = now
|
||||||
|
reload = True
|
||||||
|
expiry += int(act) * 60
|
||||||
|
cur.execute("update sh set t1 = ? where k = ?", (expiry, skey))
|
||||||
|
|
||||||
|
cur.connection.commit()
|
||||||
|
if reload:
|
||||||
|
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
||||||
|
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
||||||
|
|
||||||
|
self.redirect(self.args.SRS + "?shares")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def handle_share(self, req: dict[str, str]) -> bool:
|
||||||
|
idx = self.conn.get_u2idx()
|
||||||
|
if not idx or not hasattr(idx, "p_end"):
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
raise Pebkac(500, "sqlite3 not found on server; sharing is disabled")
|
||||||
|
raise Pebkac(500, "server busy, cannot create share; please retry in a bit")
|
||||||
|
|
||||||
|
if self.args.shr_v:
|
||||||
|
self.log("handle_share: " + json.dumps(req, indent=4))
|
||||||
|
|
||||||
|
skey = req["k"]
|
||||||
|
vps = req["vp"]
|
||||||
|
fns = []
|
||||||
|
if len(vps) == 1:
|
||||||
|
vp = vps[0]
|
||||||
|
if not vp.endswith("/"):
|
||||||
|
vp, zs = vp.rsplit("/", 1)
|
||||||
|
fns = [zs]
|
||||||
|
else:
|
||||||
|
for zs in vps:
|
||||||
|
if zs.endswith("/"):
|
||||||
|
t = "you cannot select more than one folder, or mix flies and folders in one selection"
|
||||||
|
raise Pebkac(400, t)
|
||||||
|
vp = vps[0].rsplit("/", 1)[0]
|
||||||
|
for zs in vps:
|
||||||
|
vp2, fn = zs.rsplit("/", 1)
|
||||||
|
fns.append(fn)
|
||||||
|
if vp != vp2:
|
||||||
|
t = "mismatching base paths in selection:\n [%s]\n [%s]"
|
||||||
|
raise Pebkac(400, t % (vp, vp2))
|
||||||
|
|
||||||
|
vp = vp.strip("/")
|
||||||
|
if self.is_vproxied and (vp == self.args.R or vp.startswith(self.args.RS)):
|
||||||
|
vp = vp[len(self.args.RS) :]
|
||||||
|
|
||||||
|
m = re.search(r"([^0-9a-zA-Z_-])", skey)
|
||||||
|
if m:
|
||||||
|
raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
|
||||||
|
|
||||||
|
if vp.startswith(self.args.shr[1:]):
|
||||||
|
raise Pebkac(400, "yo dawg...")
|
||||||
|
|
||||||
|
cur = idx.get_shr()
|
||||||
|
if not cur:
|
||||||
|
raise Pebkac(400, "huh, sharing must be disabled in the server config...")
|
||||||
|
|
||||||
|
q = "select * from sh where k = ?"
|
||||||
|
qr = cur.execute(q, (skey,)).fetchall()
|
||||||
|
if qr and qr[0]:
|
||||||
|
self.log("sharekey taken by %r" % (qr,))
|
||||||
|
raise Pebkac(400, "sharekey [%s] is already in use" % (skey,))
|
||||||
|
|
||||||
|
# ensure user has requested perms
|
||||||
|
s_rd = "read" in req["perms"]
|
||||||
|
s_wr = "write" in req["perms"]
|
||||||
|
s_mv = "move" in req["perms"]
|
||||||
|
s_del = "delete" in req["perms"]
|
||||||
|
try:
|
||||||
|
vfs, rem = self.asrv.vfs.get(vp, self.uname, s_rd, s_wr, s_mv, s_del)
|
||||||
|
except:
|
||||||
|
raise Pebkac(400, "you dont have all the perms you tried to grant")
|
||||||
|
|
||||||
|
ap, reals, _ = vfs.ls(
|
||||||
|
rem, self.uname, not self.args.no_scandir, [[s_rd, s_wr, s_mv, s_del]]
|
||||||
|
)
|
||||||
|
rfns = set([x[0] for x in reals])
|
||||||
|
for fn in fns:
|
||||||
|
if fn not in rfns:
|
||||||
|
raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
|
||||||
|
|
||||||
|
pw = req.get("pw") or ""
|
||||||
|
now = int(time.time())
|
||||||
|
sexp = req["exp"]
|
||||||
|
exp = int(sexp) if sexp else 0
|
||||||
|
exp = now + exp * 60 if exp else 0
|
||||||
|
pr = "".join(zc for zc, zb in zip("rwmd", (s_rd, s_wr, s_mv, s_del)) if zb)
|
||||||
|
|
||||||
|
q = "insert into sh values (?,?,?,?,?,?,?,?)"
|
||||||
|
cur.execute(q, (skey, pw, vp, pr, len(fns), self.uname, now, exp))
|
||||||
|
|
||||||
|
q = "insert into sf values (?,?)"
|
||||||
|
for fn in fns:
|
||||||
|
cur.execute(q, (skey, fn))
|
||||||
|
|
||||||
|
cur.connection.commit()
|
||||||
|
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
||||||
|
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
||||||
|
|
||||||
|
fn = quotep(fns[0]) if len(fns) == 1 else ""
|
||||||
|
|
||||||
|
surl = "created share: %s://%s%s%s%s/%s" % (
|
||||||
|
"https" if self.is_https else "http",
|
||||||
|
self.host,
|
||||||
|
self.args.SR,
|
||||||
|
self.args.shr,
|
||||||
|
skey,
|
||||||
|
fn,
|
||||||
|
)
|
||||||
|
self.loud_reply(surl, status=201)
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_rm(self, req: list[str]) -> bool:
|
def handle_rm(self, req: list[str]) -> bool:
|
||||||
if not req and not self.can_delete:
|
if not req and not self.can_delete:
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
raise Pebkac(403, "not allowed for user " + self.uname)
|
||||||
@@ -4144,7 +4496,7 @@ class HttpCli(object):
|
|||||||
if self.args.no_mv:
|
if self.args.no_mv:
|
||||||
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, vsrc, vdst)
|
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
||||||
self.loud_reply(x.get(), status=201)
|
self.loud_reply(x.get(), status=201)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -4501,6 +4853,7 @@ class HttpCli(object):
|
|||||||
"have_mv": (not self.args.no_mv),
|
"have_mv": (not self.args.no_mv),
|
||||||
"have_del": (not self.args.no_del),
|
"have_del": (not self.args.no_del),
|
||||||
"have_zip": (not self.args.no_zip),
|
"have_zip": (not self.args.no_zip),
|
||||||
|
"have_shr": self.args.shr,
|
||||||
"have_unpost": int(self.args.unpost),
|
"have_unpost": int(self.args.unpost),
|
||||||
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
|
"sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
|
||||||
"dgrid": "grid" in vf,
|
"dgrid": "grid" in vf,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import threading # typechk
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_TLS"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
import ssl
|
import ssl
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import time
|
|||||||
|
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
from .__init__ import ANYWIN, CORES, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
|
from .__init__ import ANYWIN, CORES, EXE, MACOS, PY2, TYPE_CHECKING, EnvParams, unicode
|
||||||
|
|
||||||
try:
|
try:
|
||||||
MNFE = ModuleNotFoundError
|
MNFE = ModuleNotFoundError
|
||||||
@@ -84,6 +84,12 @@ if TYPE_CHECKING:
|
|||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
if not hasattr(socket, "AF_UNIX"):
|
||||||
|
setattr(socket, "AF_UNIX", -9001)
|
||||||
|
|
||||||
|
|
||||||
class HttpSrv(object):
|
class HttpSrv(object):
|
||||||
"""
|
"""
|
||||||
@@ -148,7 +154,17 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
env = jinja2.Environment()
|
env = jinja2.Environment()
|
||||||
env.loader = jinja2.FileSystemLoader(os.path.join(self.E.mod, "web"))
|
env.loader = jinja2.FileSystemLoader(os.path.join(self.E.mod, "web"))
|
||||||
jn = ["splash", "svcs", "browser", "browser2", "msg", "md", "mde", "cf"]
|
jn = [
|
||||||
|
"splash",
|
||||||
|
"shares",
|
||||||
|
"svcs",
|
||||||
|
"browser",
|
||||||
|
"browser2",
|
||||||
|
"msg",
|
||||||
|
"md",
|
||||||
|
"mde",
|
||||||
|
"cf",
|
||||||
|
]
|
||||||
self.j2 = {x: env.get_template(x + ".html") for x in jn}
|
self.j2 = {x: env.get_template(x + ".html") for x in jn}
|
||||||
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
||||||
self.prism = os.path.exists(zs)
|
self.prism = os.path.exists(zs)
|
||||||
@@ -240,15 +256,24 @@ class HttpSrv(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
||||||
|
tcp = sck.family != socket.AF_UNIX
|
||||||
|
|
||||||
if self.args.j != 1:
|
if self.args.j != 1:
|
||||||
# lost in the pickle; redefine
|
# lost in the pickle; redefine
|
||||||
if not ANYWIN or self.args.reuseaddr:
|
if not ANYWIN or self.args.reuseaddr:
|
||||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
if tcp:
|
||||||
|
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
sck.settimeout(None) # < does not inherit, ^ opts above do
|
sck.settimeout(None) # < does not inherit, ^ opts above do
|
||||||
|
|
||||||
ip, port = sck.getsockname()[:2]
|
if tcp:
|
||||||
|
ip, port = sck.getsockname()[:2]
|
||||||
|
else:
|
||||||
|
ip = re.sub(r"\.[0-9]+$", "", sck.getsockname().split("/")[-1])
|
||||||
|
port = 0
|
||||||
|
|
||||||
self.srvs.append(sck)
|
self.srvs.append(sck)
|
||||||
self.bound.add((ip, port))
|
self.bound.add((ip, port))
|
||||||
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
||||||
@@ -260,10 +285,19 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
def thr_listen(self, srv_sck: socket.socket) -> None:
|
def thr_listen(self, srv_sck: socket.socket) -> None:
|
||||||
"""listens on a shared tcp server"""
|
"""listens on a shared tcp server"""
|
||||||
ip, port = srv_sck.getsockname()[:2]
|
|
||||||
fno = srv_sck.fileno()
|
fno = srv_sck.fileno()
|
||||||
hip = "[{}]".format(ip) if ":" in ip else ip
|
if srv_sck.family == socket.AF_UNIX:
|
||||||
msg = "subscribed @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
ip = re.sub(r"\.[0-9]+$", "", srv_sck.getsockname())
|
||||||
|
msg = "subscribed @ %s f%d p%d" % (ip, fno, os.getpid())
|
||||||
|
ip = ip.split("/")[-1]
|
||||||
|
port = 0
|
||||||
|
tcp = False
|
||||||
|
else:
|
||||||
|
tcp = True
|
||||||
|
ip, port = srv_sck.getsockname()[:2]
|
||||||
|
hip = "[%s]" % (ip,) if ":" in ip else ip
|
||||||
|
msg = "subscribed @ %s:%d f%d p%d" % (hip, port, fno, os.getpid())
|
||||||
|
|
||||||
self.log(self.name, msg)
|
self.log(self.name, msg)
|
||||||
|
|
||||||
Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
|
Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
|
||||||
@@ -335,11 +369,13 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
sck, saddr = srv_sck.accept()
|
sck, saddr = srv_sck.accept()
|
||||||
cip = unicode(saddr[0])
|
if tcp:
|
||||||
if cip.startswith("::ffff:"):
|
cip = unicode(saddr[0])
|
||||||
cip = cip[7:]
|
if cip.startswith("::ffff:"):
|
||||||
|
cip = cip[7:]
|
||||||
addr = (cip, saddr[1])
|
addr = (cip, saddr[1])
|
||||||
|
else:
|
||||||
|
addr = ("127.8.3.7", sck.fileno())
|
||||||
except (OSError, socket.error) as ex:
|
except (OSError, socket.error) as ex:
|
||||||
if self.stopping:
|
if self.stopping:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class Ico(object):
|
|||||||
try:
|
try:
|
||||||
_, _, tw, th = pb.textbbox((0, 0), ext)
|
_, _, tw, th = pb.textbbox((0, 0), ext)
|
||||||
except:
|
except:
|
||||||
tw, th = pb.textsize(ext)
|
tw, th = pb.textsize(ext) # type: ignore
|
||||||
|
|
||||||
tw += len(ext)
|
tw += len(ext)
|
||||||
cw = tw // len(ext)
|
cw = tw // len(ext)
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ if True: # pylint: disable=using-constant-test
|
|||||||
from .util import NamedLogger, RootLogger
|
from .util import NamedLogger, RootLogger
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_MUTAGEN"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
from mutagen import version # noqa: F401
|
||||||
|
|
||||||
|
HAVE_MUTAGEN = True
|
||||||
|
except:
|
||||||
|
HAVE_MUTAGEN = False
|
||||||
|
|
||||||
|
|
||||||
def have_ff(scmd: str) -> bool:
|
def have_ff(scmd: str) -> bool:
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
scmd += ".exe"
|
scmd += ".exe"
|
||||||
@@ -48,8 +59,8 @@ def have_ff(scmd: str) -> bool:
|
|||||||
return bool(shutil.which(scmd))
|
return bool(shutil.which(scmd))
|
||||||
|
|
||||||
|
|
||||||
HAVE_FFMPEG = have_ff("ffmpeg")
|
HAVE_FFMPEG = not os.environ.get("PRTY_NO_FFMPEG") and have_ff("ffmpeg")
|
||||||
HAVE_FFPROBE = have_ff("ffprobe")
|
HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
|
||||||
|
|
||||||
|
|
||||||
class MParser(object):
|
class MParser(object):
|
||||||
@@ -336,9 +347,7 @@ class MTag(object):
|
|||||||
|
|
||||||
if self.backend == "mutagen":
|
if self.backend == "mutagen":
|
||||||
self._get = self.get_mutagen
|
self._get = self.get_mutagen
|
||||||
try:
|
if not HAVE_MUTAGEN:
|
||||||
from mutagen import version # noqa: F401
|
|
||||||
except:
|
|
||||||
self.log("could not load Mutagen, trying FFprobe instead", c=3)
|
self.log("could not load Mutagen, trying FFprobe instead", c=3)
|
||||||
self.backend = "ffprobe"
|
self.backend = "ffprobe"
|
||||||
|
|
||||||
@@ -578,7 +587,7 @@ class MTag(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if k == ".aq":
|
if k == ".aq":
|
||||||
v /= 1000
|
v /= 1000 # type: ignore
|
||||||
|
|
||||||
if k == "ac" and v.startswith("mp4a.40."):
|
if k == "ac" and v.startswith("mp4a.40."):
|
||||||
v = "aac"
|
v = "aac"
|
||||||
|
|||||||
@@ -4,11 +4,21 @@ from __future__ import print_function, unicode_literals
|
|||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import unicode
|
from .__init__ import unicode
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_ARGON2"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
HAVE_ARGON2 = True
|
||||||
|
from argon2 import __version__ as argon2ver
|
||||||
|
except:
|
||||||
|
HAVE_ARGON2 = False
|
||||||
|
|
||||||
|
|
||||||
class PWHash(object):
|
class PWHash(object):
|
||||||
def __init__(self, args: argparse.Namespace):
|
def __init__(self, args: argparse.Namespace):
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ class SMB(object):
|
|||||||
|
|
||||||
debug('%s("%s", %s) %s @%s\033[K\033[0m', caller, vpath, str(a), perms, uname)
|
debug('%s("%s", %s) %s @%s\033[K\033[0m', caller, vpath, str(a), perms, uname)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
|
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
return vfs, vfs.canonical(rem)
|
return vfs, vfs.canonical(rem)
|
||||||
|
|
||||||
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
|
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
|
||||||
@@ -195,6 +197,8 @@ class SMB(object):
|
|||||||
uname = self._uname()
|
uname = self._uname()
|
||||||
# debug('listdir("%s", %s) @%s\033[K\033[0m', vpath, str(a), uname)
|
# debug('listdir("%s", %s) @%s\033[K\033[0m', vpath, str(a), uname)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, uname, False, False)
|
vfs, rem = self.asrv.vfs.get(vpath, uname, False, False)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
_, vfs_ls, vfs_virt = vfs.ls(
|
_, vfs_ls, vfs_virt = vfs.ls(
|
||||||
rem, uname, not self.args.no_scandir, [[False, False]]
|
rem, uname, not self.args.no_scandir, [[False, False]]
|
||||||
)
|
)
|
||||||
@@ -240,7 +244,21 @@ class SMB(object):
|
|||||||
|
|
||||||
xbu = vfs.flags.get("xbu")
|
xbu = vfs.flags.get("xbu")
|
||||||
if xbu and not runhook(
|
if xbu and not runhook(
|
||||||
self.nlog, xbu, ap, vpath, "", "", "", 0, 0, "1.7.6.2", 0, ""
|
self.nlog,
|
||||||
|
None,
|
||||||
|
self.hub.up2k,
|
||||||
|
"xbu.smb",
|
||||||
|
xbu,
|
||||||
|
ap,
|
||||||
|
vpath,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"1.7.6.2",
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
):
|
):
|
||||||
yeet("blocked by xbu server config: " + vpath)
|
yeet("blocked by xbu server config: " + vpath)
|
||||||
|
|
||||||
@@ -297,7 +315,7 @@ class SMB(object):
|
|||||||
t = "blocked rename (no-move-acc %s): /%s @%s"
|
t = "blocked rename (no-move-acc %s): /%s @%s"
|
||||||
yeet(t % (vfs1.axs.umove, vp1, uname))
|
yeet(t % (vfs1.axs.umove, vp1, uname))
|
||||||
|
|
||||||
self.hub.up2k.handle_mv(uname, vp1, vp2)
|
self.hub.up2k.handle_mv(uname, "1.7.6.2", vp1, vp2)
|
||||||
try:
|
try:
|
||||||
bos.makedirs(ap2)
|
bos.makedirs(ap2)
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import errno
|
|||||||
import re
|
import re
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
from email.utils import formatdate
|
import time
|
||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import TYPE_CHECKING
|
||||||
from .multicast import MC_Sck, MCast
|
from .multicast import MC_Sck, MCast
|
||||||
from .util import CachedSet, html_escape, min_ex
|
from .util import CachedSet, formatdate, html_escape, min_ex
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .broker_util import BrokerCli
|
from .broker_util import BrokerCli
|
||||||
@@ -229,7 +229,7 @@ CONFIGID.UPNP.ORG: 1
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
v4 = srv.ip.replace("::ffff:", "")
|
v4 = srv.ip.replace("::ffff:", "")
|
||||||
zs = zs.format(formatdate(usegmt=True), v4, srv.hport, self.args.zsid)
|
zs = zs.format(formatdate(), v4, srv.hport, self.args.zsid)
|
||||||
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
||||||
srv.sck.sendto(zb, addr[:2])
|
srv.sck.sendto(zb, addr[:2])
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ from .label import DNSBuffer, DNSLabel
|
|||||||
from .ranges import IP4, IP6, H, I, check_bytes
|
from .ranges import IP4, IP6, H, I, check_bytes
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
range = xrange
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DNSError(Exception):
|
class DNSError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,21 @@ import os
|
|||||||
|
|
||||||
from ._shared import IP, Adapter
|
from ._shared import IP, Adapter
|
||||||
|
|
||||||
if os.name == "nt":
|
|
||||||
|
def nope(include_unconfigured=False):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
S390X = os.uname().machine == "s390x"
|
||||||
|
except:
|
||||||
|
S390X = False
|
||||||
|
|
||||||
|
|
||||||
|
if os.environ.get("PRTY_NO_IFADDR") or S390X:
|
||||||
|
# s390x deadlocks at libc.getifaddrs
|
||||||
|
get_adapters = nope
|
||||||
|
elif os.name == "nt":
|
||||||
from ._win32 import get_adapters
|
from ._win32 import get_adapters
|
||||||
elif os.name == "posix":
|
elif os.name == "posix":
|
||||||
from ._posix import get_adapters
|
from ._posix import get_adapters
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ if not PY2:
|
|||||||
U: Callable[[str], str] = str
|
U: Callable[[str], str] = str
|
||||||
else:
|
else:
|
||||||
U = unicode # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
U = unicode # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
||||||
|
range = xrange # noqa: F821 # pylint: disable=undefined-variable,self-assigning-variable
|
||||||
|
|
||||||
|
|
||||||
class Adapter(object):
|
class Adapter(object):
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ if True: # pylint: disable=using-constant-test
|
|||||||
|
|
||||||
from typing import Callable, List, Optional, Tuple, Union
|
from typing import Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
range = xrange
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def num_char_count_bits(ver: int) -> int:
|
def num_char_count_bits(ver: int) -> int:
|
||||||
return 16 if (ver + 7) // 17 else 8
|
return 16 if (ver + 7) // 17 else 8
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import tempfile
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .__init__ import CORES
|
from .__init__ import CORES
|
||||||
from .authsrv import AuthSrv, VFS
|
from .authsrv import VFS, AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .th_cli import ThumbCli
|
from .th_cli import ThumbCli
|
||||||
from .util import UTC, vjoin, vol_san
|
from .util import UTC, vjoin, vol_san
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import calendar
|
|
||||||
import errno
|
import errno
|
||||||
import gzip
|
import gzip
|
||||||
import logging
|
import logging
|
||||||
@@ -16,7 +15,7 @@ import string
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
# from inspect import currentframe
|
# from inspect import currentframe
|
||||||
# print(currentframe().f_lineno)
|
# print(currentframe().f_lineno)
|
||||||
@@ -28,18 +27,30 @@ if True: # pylint: disable=using-constant-test
|
|||||||
import typing
|
import typing
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from .__init__ import ANYWIN, EXE, MACOS, TYPE_CHECKING, E, EnvParams, unicode
|
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
|
||||||
from .authsrv import BAD_CFG, AuthSrv
|
from .authsrv import BAD_CFG, AuthSrv
|
||||||
from .cert import ensure_cert
|
from .cert import ensure_cert
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
|
||||||
|
from .pwhash import HAVE_ARGON2
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .th_srv import HAVE_PIL, HAVE_VIPS, HAVE_WEBP, ThumbSrv
|
from .th_srv import (
|
||||||
|
HAVE_AVIF,
|
||||||
|
HAVE_FFMPEG,
|
||||||
|
HAVE_FFPROBE,
|
||||||
|
HAVE_HEIF,
|
||||||
|
HAVE_PIL,
|
||||||
|
HAVE_VIPS,
|
||||||
|
HAVE_WEBP,
|
||||||
|
ThumbSrv,
|
||||||
|
)
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
from .util import (
|
from .util import (
|
||||||
DEF_EXP,
|
DEF_EXP,
|
||||||
DEF_MTE,
|
DEF_MTE,
|
||||||
DEF_MTH,
|
DEF_MTH,
|
||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
|
HAVE_PSUTIL,
|
||||||
|
HAVE_SQLITE3,
|
||||||
UTC,
|
UTC,
|
||||||
VERSIONS,
|
VERSIONS,
|
||||||
Daemon,
|
Daemon,
|
||||||
@@ -65,6 +76,9 @@ if TYPE_CHECKING:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class SvcHub(object):
|
class SvcHub(object):
|
||||||
"""
|
"""
|
||||||
@@ -89,8 +103,10 @@ class SvcHub(object):
|
|||||||
self.argv = argv
|
self.argv = argv
|
||||||
self.E: EnvParams = args.E
|
self.E: EnvParams = args.E
|
||||||
self.no_ansi = args.no_ansi
|
self.no_ansi = args.no_ansi
|
||||||
|
self.tz = UTC if args.log_utc else None
|
||||||
self.logf: Optional[typing.TextIO] = None
|
self.logf: Optional[typing.TextIO] = None
|
||||||
self.logf_base_fn = ""
|
self.logf_base_fn = ""
|
||||||
|
self.is_dut = False # running in unittest; always False
|
||||||
self.stop_req = False
|
self.stop_req = False
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
@@ -102,7 +118,8 @@ class SvcHub(object):
|
|||||||
self.httpsrv_up = 0
|
self.httpsrv_up = 0
|
||||||
|
|
||||||
self.log_mutex = threading.Lock()
|
self.log_mutex = threading.Lock()
|
||||||
self.next_day = 0
|
self.cday = 0
|
||||||
|
self.cmon = 0
|
||||||
self.tstack = 0.0
|
self.tstack = 0.0
|
||||||
|
|
||||||
self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8)
|
self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8)
|
||||||
@@ -193,6 +210,20 @@ class SvcHub(object):
|
|||||||
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
||||||
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
||||||
|
|
||||||
|
if args.chpw and args.idp_h_usr:
|
||||||
|
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
||||||
|
self.log("root", t, 1)
|
||||||
|
raise Exception(t)
|
||||||
|
|
||||||
|
noch = set()
|
||||||
|
for zs in args.chpw_no or []:
|
||||||
|
zsl = [x.strip() for x in zs.split(",")]
|
||||||
|
noch.update([x for x in zsl if x])
|
||||||
|
args.chpw_no = noch
|
||||||
|
|
||||||
|
if args.shr:
|
||||||
|
self.setup_share_db()
|
||||||
|
|
||||||
bri = "zy"[args.theme % 2 :][:1]
|
bri = "zy"[args.theme % 2 :][:1]
|
||||||
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
||||||
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
||||||
@@ -232,6 +263,8 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.up2k = Up2k(self)
|
self.up2k = Up2k(self)
|
||||||
|
|
||||||
|
self._feature_test()
|
||||||
|
|
||||||
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
||||||
if not HAVE_VIPS:
|
if not HAVE_VIPS:
|
||||||
decs.pop("vips", None)
|
decs.pop("vips", None)
|
||||||
@@ -336,6 +369,93 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.broker = Broker(self)
|
self.broker = Broker(self)
|
||||||
|
|
||||||
|
def setup_share_db(self) -> None:
|
||||||
|
al = self.args
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
self.log("root", "sqlite3 not available; disabling --shr", 1)
|
||||||
|
al.shr = ""
|
||||||
|
return
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
al.shr = al.shr.strip("/")
|
||||||
|
if "/" in al.shr or not al.shr:
|
||||||
|
t = "config error: --shr must be the name of a virtual toplevel directory to put shares inside"
|
||||||
|
self.log("root", t, 1)
|
||||||
|
raise Exception(t)
|
||||||
|
|
||||||
|
al.shr = "/%s/" % (al.shr,)
|
||||||
|
|
||||||
|
create = True
|
||||||
|
modified = False
|
||||||
|
db_path = self.args.shr_db
|
||||||
|
self.log("root", "opening shares-db %s" % (db_path,))
|
||||||
|
for n in range(2):
|
||||||
|
try:
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
cur = db.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("select count(*) from sh").fetchone()
|
||||||
|
create = False
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except Exception as ex:
|
||||||
|
if n:
|
||||||
|
raise
|
||||||
|
t = "shares-db corrupt; deleting and recreating: %r"
|
||||||
|
self.log("root", t % (ex,), 3)
|
||||||
|
try:
|
||||||
|
cur.close() # type: ignore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
db.close() # type: ignore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
os.unlink(db_path)
|
||||||
|
|
||||||
|
sch1 = [
|
||||||
|
r"create table kv (k text, v int)",
|
||||||
|
r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)",
|
||||||
|
# sharekey, password, src, perms, numFiles, owner, created, expires
|
||||||
|
]
|
||||||
|
sch2 = [
|
||||||
|
r"create table sf (k text, vp text)",
|
||||||
|
r"create index sf_k on sf(k)",
|
||||||
|
r"create index sh_k on sh(k)",
|
||||||
|
r"create index sh_t1 on sh(t1)",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert db # type: ignore
|
||||||
|
assert cur # type: ignore
|
||||||
|
if create:
|
||||||
|
dver = 2
|
||||||
|
modified = True
|
||||||
|
for cmd in sch1 + sch2:
|
||||||
|
cur.execute(cmd)
|
||||||
|
self.log("root", "created new shares-db")
|
||||||
|
else:
|
||||||
|
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
|
||||||
|
|
||||||
|
if dver == 1:
|
||||||
|
modified = True
|
||||||
|
for cmd in sch2:
|
||||||
|
cur.execute(cmd)
|
||||||
|
cur.execute("update sh set st = 0")
|
||||||
|
self.log("root", "shares-db schema upgrade ok")
|
||||||
|
|
||||||
|
if modified:
|
||||||
|
for cmd in [
|
||||||
|
r"delete from kv where k = 'sver'",
|
||||||
|
r"insert into kv values ('sver', %d)" % (2,),
|
||||||
|
]:
|
||||||
|
cur.execute(cmd)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
db.close()
|
||||||
|
|
||||||
def start_ftpd(self) -> None:
|
def start_ftpd(self) -> None:
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
|
|
||||||
@@ -420,6 +540,58 @@ class SvcHub(object):
|
|||||||
|
|
||||||
Daemon(self.sd_notify, "sd-notify")
|
Daemon(self.sd_notify, "sd-notify")
|
||||||
|
|
||||||
|
def _feature_test(self) -> None:
|
||||||
|
fok = []
|
||||||
|
fng = []
|
||||||
|
t_ff = "transcode audio, create spectrograms, video thumbnails"
|
||||||
|
to_check = [
|
||||||
|
(HAVE_SQLITE3, "sqlite", "file and media indexing"),
|
||||||
|
(HAVE_PIL, "pillow", "image thumbnails (plenty fast)"),
|
||||||
|
(HAVE_VIPS, "vips", "image thumbnails (faster, eats more ram)"),
|
||||||
|
(HAVE_WEBP, "pillow-webp", "create thumbnails as webp files"),
|
||||||
|
(HAVE_FFMPEG, "ffmpeg", t_ff + ", good-but-slow image thumbnails"),
|
||||||
|
(HAVE_FFPROBE, "ffprobe", t_ff + ", read audio/media tags"),
|
||||||
|
(HAVE_MUTAGEN, "mutagen", "read audio tags (ffprobe is better but slower)"),
|
||||||
|
(HAVE_ARGON2, "argon2", "secure password hashing (advanced users only)"),
|
||||||
|
(HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
|
||||||
|
(HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
|
||||||
|
]
|
||||||
|
if ANYWIN:
|
||||||
|
to_check += [
|
||||||
|
(HAVE_PSUTIL, "psutil", "improved plugin cleanup (rarely useful)")
|
||||||
|
]
|
||||||
|
|
||||||
|
verbose = self.args.deps
|
||||||
|
if verbose:
|
||||||
|
self.log("dependencies", "")
|
||||||
|
|
||||||
|
for have, feat, what in to_check:
|
||||||
|
lst = fok if have else fng
|
||||||
|
lst.append((feat, what))
|
||||||
|
if verbose:
|
||||||
|
zi = 2 if have else 5
|
||||||
|
sgot = "found" if have else "missing"
|
||||||
|
t = "%7s: %s \033[36m(%s)"
|
||||||
|
self.log("dependencies", t % (sgot, feat, what), zi)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
self.log("dependencies", "")
|
||||||
|
return
|
||||||
|
|
||||||
|
sok = ", ".join(x[0] for x in fok)
|
||||||
|
sng = ", ".join(x[0] for x in fng)
|
||||||
|
|
||||||
|
t = ""
|
||||||
|
if sok:
|
||||||
|
t += "OK: \033[32m" + sok
|
||||||
|
if sng:
|
||||||
|
if t:
|
||||||
|
t += ", "
|
||||||
|
t += "\033[0mNG: \033[35m" + sng
|
||||||
|
|
||||||
|
t += "\033[0m, see --deps"
|
||||||
|
self.log("dependencies", t, 6)
|
||||||
|
|
||||||
def _check_env(self) -> None:
|
def _check_env(self) -> None:
|
||||||
try:
|
try:
|
||||||
files = os.listdir(E.cfg)
|
files = os.listdir(E.cfg)
|
||||||
@@ -620,7 +792,7 @@ class SvcHub(object):
|
|||||||
self.args.nc = min(self.args.nc, soft // 2)
|
self.args.nc = min(self.args.nc, soft // 2)
|
||||||
|
|
||||||
def _logname(self) -> str:
|
def _logname(self) -> str:
|
||||||
dt = datetime.now(UTC)
|
dt = datetime.now(self.tz)
|
||||||
fn = str(self.args.lo)
|
fn = str(self.args.lo)
|
||||||
for fs in "YmdHMS":
|
for fs in "YmdHMS":
|
||||||
fs = "%" + fs
|
fs = "%" + fs
|
||||||
@@ -746,18 +918,21 @@ class SvcHub(object):
|
|||||||
Daemon(self._reload, "reloading")
|
Daemon(self._reload, "reloading")
|
||||||
return "reload initiated"
|
return "reload initiated"
|
||||||
|
|
||||||
def _reload(self, rescan_all_vols: bool = True) -> None:
|
def _reload(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
||||||
with self.up2k.mutex:
|
with self.up2k.mutex:
|
||||||
if self.reloading != 1:
|
if self.reloading != 1:
|
||||||
return
|
return
|
||||||
self.reloading = 2
|
self.reloading = 2
|
||||||
self.log("root", "reloading config")
|
self.log("root", "reloading config")
|
||||||
self.asrv.reload()
|
self.asrv.reload(9 if up2k else 4)
|
||||||
self.up2k.reload(rescan_all_vols)
|
if up2k:
|
||||||
|
self.up2k.reload(rescan_all_vols)
|
||||||
|
else:
|
||||||
|
self.log("root", "reload done")
|
||||||
self.broker.reload()
|
self.broker.reload()
|
||||||
self.reloading = 0
|
self.reloading = 0
|
||||||
|
|
||||||
def _reload_blocking(self, rescan_all_vols: bool = True) -> None:
|
def _reload_blocking(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
||||||
while True:
|
while True:
|
||||||
with self.up2k.mutex:
|
with self.up2k.mutex:
|
||||||
if self.reloading < 2:
|
if self.reloading < 2:
|
||||||
@@ -768,7 +943,7 @@ class SvcHub(object):
|
|||||||
# try to handle multiple pending IdP reloads at once:
|
# try to handle multiple pending IdP reloads at once:
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
self._reload(rescan_all_vols=rescan_all_vols)
|
self._reload(rescan_all_vols=rescan_all_vols, up2k=up2k)
|
||||||
|
|
||||||
def stop_thr(self) -> None:
|
def stop_thr(self) -> None:
|
||||||
while not self.stop_req:
|
while not self.stop_req:
|
||||||
@@ -890,12 +1065,12 @@ class SvcHub(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
zd = datetime.now(UTC)
|
dt = datetime.now(self.tz)
|
||||||
ts = self.log_dfmt % (
|
ts = self.log_dfmt % (
|
||||||
zd.year,
|
dt.year,
|
||||||
zd.month * 100 + zd.day,
|
dt.month * 100 + dt.day,
|
||||||
(zd.hour * 100 + zd.minute) * 100 + zd.second,
|
(dt.hour * 100 + dt.minute) * 100 + dt.second,
|
||||||
zd.microsecond // self.log_div,
|
dt.microsecond // self.log_div,
|
||||||
)
|
)
|
||||||
|
|
||||||
if c and not self.args.no_ansi:
|
if c and not self.args.no_ansi:
|
||||||
@@ -916,41 +1091,26 @@ class SvcHub(object):
|
|||||||
if not self.args.no_logflush:
|
if not self.args.no_logflush:
|
||||||
self.logf.flush()
|
self.logf.flush()
|
||||||
|
|
||||||
now = time.time()
|
if dt.day != self.cday or dt.month != self.cmon:
|
||||||
if int(now) >= self.next_day:
|
self._set_next_day(dt)
|
||||||
self._set_next_day()
|
|
||||||
|
|
||||||
def _set_next_day(self) -> None:
|
def _set_next_day(self, dt: datetime) -> None:
|
||||||
if self.next_day and self.logf and self.logf_base_fn != self._logname():
|
if self.cday and self.logf and self.logf_base_fn != self._logname():
|
||||||
self.logf.close()
|
self.logf.close()
|
||||||
self._setup_logfile("")
|
self._setup_logfile("")
|
||||||
|
|
||||||
dt = datetime.now(UTC)
|
self.cday = dt.day
|
||||||
|
self.cmon = dt.month
|
||||||
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
|
||||||
day_now = dt.day
|
|
||||||
while dt.day == day_now:
|
|
||||||
dt += timedelta(hours=12)
|
|
||||||
|
|
||||||
dt = dt.replace(hour=0, minute=0, second=0)
|
|
||||||
try:
|
|
||||||
tt = dt.utctimetuple()
|
|
||||||
except:
|
|
||||||
# still makes me hella uncomfortable
|
|
||||||
tt = dt.timetuple()
|
|
||||||
|
|
||||||
self.next_day = calendar.timegm(tt)
|
|
||||||
|
|
||||||
def _log_enabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
|
def _log_enabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
"""handles logging from all components"""
|
"""handles logging from all components"""
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
now = time.time()
|
dt = datetime.now(self.tz)
|
||||||
if int(now) >= self.next_day:
|
if dt.day != self.cday or dt.month != self.cmon:
|
||||||
dt = datetime.fromtimestamp(now, UTC)
|
|
||||||
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
|
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
|
||||||
zs = zs.format(dt.strftime("%Y-%m-%d"))
|
zs = zs.format(dt.strftime("%Y-%m-%d"))
|
||||||
print(zs, end="")
|
print(zs, end="")
|
||||||
self._set_next_day()
|
self._set_next_day(dt)
|
||||||
if self.logf:
|
if self.logf:
|
||||||
self.logf.write(zs)
|
self.logf.write(zs)
|
||||||
|
|
||||||
@@ -969,12 +1129,11 @@ class SvcHub(object):
|
|||||||
else:
|
else:
|
||||||
msg = "%s%s\033[0m" % (c, msg)
|
msg = "%s%s\033[0m" % (c, msg)
|
||||||
|
|
||||||
zd = datetime.fromtimestamp(now, UTC)
|
|
||||||
ts = self.log_efmt % (
|
ts = self.log_efmt % (
|
||||||
zd.hour,
|
dt.hour,
|
||||||
zd.minute,
|
dt.minute,
|
||||||
zd.second,
|
dt.second,
|
||||||
zd.microsecond // self.log_div,
|
dt.microsecond // self.log_div,
|
||||||
)
|
)
|
||||||
msg = fmt % (ts, src, msg)
|
msg = fmt % (ts, src, msg)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ def dostime2unix(buf: bytes) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def unixtime2dos(ts: int) -> bytes:
|
def unixtime2dos(ts: int) -> bytes:
|
||||||
tt = time.gmtime(ts + 1)
|
dy, dm, dd, th, tm, ts, _, _, _ = time.gmtime(ts + 1)
|
||||||
dy, dm, dd, th, tm, ts = list(tt)[:6]
|
|
||||||
|
|
||||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||||
bt = (th << 11) + (tm << 5) + ts // 2
|
bt = (th << 11) + (tm << 5) + ts // 2
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -17,18 +17,23 @@ from .util import (
|
|||||||
E_UNREACH,
|
E_UNREACH,
|
||||||
HAVE_IPV6,
|
HAVE_IPV6,
|
||||||
IP6ALL,
|
IP6ALL,
|
||||||
|
VF_CAREFUL,
|
||||||
Netdev,
|
Netdev,
|
||||||
|
atomic_move,
|
||||||
min_ex,
|
min_ex,
|
||||||
sunpack,
|
sunpack,
|
||||||
termsize,
|
termsize,
|
||||||
)
|
)
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
from typing import Generator
|
from typing import Generator, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if not hasattr(socket, "AF_UNIX"):
|
||||||
|
setattr(socket, "AF_UNIX", -9001)
|
||||||
|
|
||||||
if not hasattr(socket, "IPPROTO_IPV6"):
|
if not hasattr(socket, "IPPROTO_IPV6"):
|
||||||
setattr(socket, "IPPROTO_IPV6", 41)
|
setattr(socket, "IPPROTO_IPV6", 41)
|
||||||
|
|
||||||
@@ -217,14 +222,41 @@ class TcpSrv(object):
|
|||||||
if self.args.qr or self.args.qrs:
|
if self.args.qr or self.args.qrs:
|
||||||
self.qr = self._qr(qr1, qr2)
|
self.qr = self._qr(qr1, qr2)
|
||||||
|
|
||||||
|
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||||
|
self.log("tcpsrv", msg, c)
|
||||||
|
|
||||||
def _listen(self, ip: str, port: int) -> None:
|
def _listen(self, ip: str, port: int) -> None:
|
||||||
ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
uds_perm = uds_gid = -1
|
||||||
|
if "unix:" in ip:
|
||||||
|
tcp = False
|
||||||
|
ipv = socket.AF_UNIX
|
||||||
|
uds = ip.split(":")
|
||||||
|
ip = uds[-1]
|
||||||
|
if len(uds) > 2:
|
||||||
|
uds_perm = int(uds[1], 8)
|
||||||
|
if len(uds) > 3:
|
||||||
|
try:
|
||||||
|
uds_gid = int(uds[2])
|
||||||
|
except:
|
||||||
|
import grp
|
||||||
|
|
||||||
|
uds_gid = grp.getgrnam(uds[2]).gr_gid
|
||||||
|
|
||||||
|
elif ":" in ip:
|
||||||
|
tcp = True
|
||||||
|
ipv = socket.AF_INET6
|
||||||
|
else:
|
||||||
|
tcp = True
|
||||||
|
ipv = socket.AF_INET
|
||||||
|
|
||||||
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
||||||
|
|
||||||
if not ANYWIN or self.args.reuseaddr:
|
if not ANYWIN or self.args.reuseaddr:
|
||||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
if tcp:
|
||||||
|
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
srv.settimeout(None) # < does not inherit, ^ opts above do
|
srv.settimeout(None) # < does not inherit, ^ opts above do
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -236,8 +268,25 @@ class TcpSrv(object):
|
|||||||
srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
|
srv.setsockopt(socket.SOL_IP, socket.IP_FREEBIND, 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
srv.bind((ip, port))
|
if tcp:
|
||||||
sport = srv.getsockname()[1]
|
srv.bind((ip, port))
|
||||||
|
else:
|
||||||
|
if ANYWIN or self.args.rm_sck:
|
||||||
|
if os.path.exists(ip):
|
||||||
|
os.unlink(ip)
|
||||||
|
srv.bind(ip)
|
||||||
|
else:
|
||||||
|
tf = "%s.%d" % (ip, os.getpid())
|
||||||
|
if os.path.exists(tf):
|
||||||
|
os.unlink(tf)
|
||||||
|
srv.bind(tf)
|
||||||
|
if uds_gid != -1:
|
||||||
|
os.chown(tf, -1, uds_gid)
|
||||||
|
if uds_perm != -1:
|
||||||
|
os.chmod(tf, uds_perm)
|
||||||
|
atomic_move(self.nlog, tf, ip, VF_CAREFUL)
|
||||||
|
|
||||||
|
sport = srv.getsockname()[1] if tcp else port
|
||||||
if port != sport:
|
if port != sport:
|
||||||
# linux 6.0.16 lets you bind a port which is in use
|
# linux 6.0.16 lets you bind a port which is in use
|
||||||
# except it just gives you a random port instead
|
# except it just gives you a random port instead
|
||||||
@@ -249,12 +298,23 @@ class TcpSrv(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
e = ""
|
||||||
if ex.errno in E_ADDR_IN_USE:
|
if ex.errno in E_ADDR_IN_USE:
|
||||||
e = "\033[1;31mport {} is busy on interface {}\033[0m".format(port, ip)
|
e = "\033[1;31mport {} is busy on interface {}\033[0m".format(port, ip)
|
||||||
|
if not tcp:
|
||||||
|
e = "\033[1;31munix-socket {} is busy\033[0m".format(ip)
|
||||||
elif ex.errno in E_ADDR_NOT_AVAIL:
|
elif ex.errno in E_ADDR_NOT_AVAIL:
|
||||||
e = "\033[1;31minterface {} does not exist\033[0m".format(ip)
|
e = "\033[1;31minterface {} does not exist\033[0m".format(ip)
|
||||||
else:
|
|
||||||
|
if not e:
|
||||||
|
if not tcp:
|
||||||
|
t = "\n\n\n NOTE: this crash may be due to a unix-socket bug; try --rm-sck\n"
|
||||||
|
self.log("tcpsrv", t, 2)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if not tcp and not self.args.rm_sck:
|
||||||
|
e += "; maybe this is a bug? try --rm-sck"
|
||||||
|
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
@@ -262,7 +322,14 @@ class TcpSrv(object):
|
|||||||
bound: list[tuple[str, int]] = []
|
bound: list[tuple[str, int]] = []
|
||||||
srvs: list[socket.socket] = []
|
srvs: list[socket.socket] = []
|
||||||
for srv in self.srv:
|
for srv in self.srv:
|
||||||
ip, port = srv.getsockname()[:2]
|
if srv.family == socket.AF_UNIX:
|
||||||
|
tcp = False
|
||||||
|
ip = re.sub(r"\.[0-9]+$", "", srv.getsockname())
|
||||||
|
port = 0
|
||||||
|
else:
|
||||||
|
tcp = True
|
||||||
|
ip, port = srv.getsockname()[:2]
|
||||||
|
|
||||||
if ip == IP6ALL:
|
if ip == IP6ALL:
|
||||||
ip = "::" # jython
|
ip = "::" # jython
|
||||||
|
|
||||||
@@ -294,8 +361,12 @@ class TcpSrv(object):
|
|||||||
bound.append((ip, port))
|
bound.append((ip, port))
|
||||||
srvs.append(srv)
|
srvs.append(srv)
|
||||||
fno = srv.fileno()
|
fno = srv.fileno()
|
||||||
hip = "[{}]".format(ip) if ":" in ip else ip
|
if tcp:
|
||||||
msg = "listening @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
hip = "[{}]".format(ip) if ":" in ip else ip
|
||||||
|
msg = "listening @ {}:{} f{} p{}".format(hip, port, fno, os.getpid())
|
||||||
|
else:
|
||||||
|
msg = "listening @ {} f{} p{}".format(ip, fno, os.getpid())
|
||||||
|
|
||||||
self.log("tcpsrv", msg)
|
self.log("tcpsrv", msg)
|
||||||
if self.args.q:
|
if self.args.q:
|
||||||
print(msg)
|
print(msg)
|
||||||
@@ -348,6 +419,8 @@ class TcpSrv(object):
|
|||||||
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
|
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
|
||||||
from .stolen.ifaddr import get_adapters
|
from .stolen.ifaddr import get_adapters
|
||||||
|
|
||||||
|
listen_ips = [x for x in listen_ips if "unix:" not in x]
|
||||||
|
|
||||||
nics = get_adapters(True)
|
nics = get_adapters(True)
|
||||||
eps: dict[str, Netdev] = {}
|
eps: dict[str, Netdev] = {}
|
||||||
for nic in nics:
|
for nic in nics:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from partftpy.TftpShared import TftpException
|
|||||||
from .__init__ import EXE, PY2, TYPE_CHECKING
|
from .__init__ import EXE, PY2, TYPE_CHECKING
|
||||||
from .authsrv import VFS
|
from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .util import BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
|
from .util import UTC, BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
@@ -44,6 +44,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
lg = logging.getLogger("tftp")
|
lg = logging.getLogger("tftp")
|
||||||
debug, info, warning, error = (lg.debug, lg.info, lg.warning, lg.error)
|
debug, info, warning, error = (lg.debug, lg.info, lg.warning, lg.error)
|
||||||
@@ -163,9 +166,16 @@ class Tftpd(object):
|
|||||||
if "::" in ips:
|
if "::" in ips:
|
||||||
ips.append("0.0.0.0")
|
ips.append("0.0.0.0")
|
||||||
|
|
||||||
|
ips = [x for x in ips if "unix:" not in x]
|
||||||
|
|
||||||
if self.args.tftp4:
|
if self.args.tftp4:
|
||||||
ips = [x for x in ips if ":" not in x]
|
ips = [x for x in ips if ":" not in x]
|
||||||
|
|
||||||
|
if not ips:
|
||||||
|
t = "cannot start tftp-server; no compatible IPs in -i"
|
||||||
|
self.nlog(t, 1)
|
||||||
|
return
|
||||||
|
|
||||||
ips = list(ODict.fromkeys(ips)) # dedup
|
ips = list(ODict.fromkeys(ips)) # dedup
|
||||||
|
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
@@ -241,6 +251,8 @@ class Tftpd(object):
|
|||||||
|
|
||||||
debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
|
debug('%s("%s", %s) %s\033[K\033[0m', caller, vpath, str(a), perms)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
|
vfs, rem = self.asrv.vfs.get(vpath, "*", *perms)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
return vfs, vfs.canonical(rem)
|
return vfs, vfs.canonical(rem)
|
||||||
|
|
||||||
def _ls(self, vpath: str, raddress: str, rport: int, force=False) -> Any:
|
def _ls(self, vpath: str, raddress: str, rport: int, force=False) -> Any:
|
||||||
@@ -262,7 +274,7 @@ class Tftpd(object):
|
|||||||
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
||||||
fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
|
fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
|
||||||
real1 = dirs1 + fils1
|
real1 = dirs1 + fils1
|
||||||
realt = [(datetime.fromtimestamp(mt), sz, fn) for mt, sz, fn in real1]
|
realt = [(datetime.fromtimestamp(mt, UTC), sz, fn) for mt, sz, fn in real1]
|
||||||
reals = [
|
reals = [
|
||||||
(
|
(
|
||||||
"%04d-%02d-%02d %02d:%02d:%02d"
|
"%04d-%02d-%02d %02d:%02d:%02d"
|
||||||
@@ -328,7 +340,21 @@ class Tftpd(object):
|
|||||||
|
|
||||||
xbu = vfs.flags.get("xbu")
|
xbu = vfs.flags.get("xbu")
|
||||||
if xbu and not runhook(
|
if xbu and not runhook(
|
||||||
self.nlog, xbu, ap, vpath, "", "", "", 0, 0, "8.3.8.7", 0, ""
|
self.nlog,
|
||||||
|
None,
|
||||||
|
self.hub.up2k,
|
||||||
|
"xbu.tftpd",
|
||||||
|
xbu,
|
||||||
|
ap,
|
||||||
|
vpath,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"8.3.8.7",
|
||||||
|
time.time(),
|
||||||
|
"",
|
||||||
):
|
):
|
||||||
yeet("blocked by xbu server config: " + vpath)
|
yeet("blocked by xbu server config: " + vpath)
|
||||||
|
|
||||||
@@ -336,7 +362,7 @@ class Tftpd(object):
|
|||||||
return self._ls(vpath, "", 0, True)
|
return self._ls(vpath, "", 0, True)
|
||||||
|
|
||||||
if not a:
|
if not a:
|
||||||
a = [self.args.iobuf]
|
a = (self.args.iobuf,)
|
||||||
|
|
||||||
return open(ap, mode, *a, **ka)
|
return open(ap, mode, *a, **ka)
|
||||||
|
|
||||||
@@ -377,7 +403,7 @@ class Tftpd(object):
|
|||||||
bos.stat(ap)
|
bos.stat(ap)
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
return False
|
return vpath == "/"
|
||||||
|
|
||||||
def _p_isdir(self, vpath: str) -> bool:
|
def _p_isdir(self, vpath: str) -> bool:
|
||||||
try:
|
try:
|
||||||
@@ -385,7 +411,7 @@ class Tftpd(object):
|
|||||||
ret = stat.S_ISDIR(st.st_mode)
|
ret = stat.S_ISDIR(st.st_mode)
|
||||||
return ret
|
return ret
|
||||||
except:
|
except:
|
||||||
return False
|
return vpath == "/"
|
||||||
|
|
||||||
def _hook(self, *a: Any, **ka: Any) -> None:
|
def _hook(self, *a: Any, **ka: Any) -> None:
|
||||||
src = inspect.currentframe().f_back.f_code.co_name
|
src = inspect.currentframe().f_back.f_code.co_name
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import time
|
|||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from .__init__ import ANYWIN, TYPE_CHECKING
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING
|
||||||
from .authsrv import VFS
|
from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
|
||||||
@@ -38,6 +38,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
HAVE_PIL = False
|
HAVE_PIL = False
|
||||||
HAVE_PILF = False
|
HAVE_PILF = False
|
||||||
HAVE_HEIF = False
|
HAVE_HEIF = False
|
||||||
@@ -45,22 +48,34 @@ HAVE_AVIF = False
|
|||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
from PIL import ExifTags, Image, ImageFont, ImageOps
|
from PIL import ExifTags, Image, ImageFont, ImageOps
|
||||||
|
|
||||||
HAVE_PIL = True
|
HAVE_PIL = True
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PILF"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
ImageFont.load_default(size=16)
|
ImageFont.load_default(size=16)
|
||||||
HAVE_PILF = True
|
HAVE_PILF = True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL_WEBP"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
Image.new("RGB", (2, 2)).save(BytesIO(), format="webp")
|
Image.new("RGB", (2, 2)).save(BytesIO(), format="webp")
|
||||||
HAVE_WEBP = True
|
HAVE_WEBP = True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL_HEIF"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
from pyheif_pillow_opener import register_heif_opener
|
from pyheif_pillow_opener import register_heif_opener
|
||||||
|
|
||||||
register_heif_opener()
|
register_heif_opener()
|
||||||
@@ -69,6 +84,9 @@ try:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PIL_AVIF"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
import pillow_avif # noqa: F401 # pylint: disable=unused-import
|
import pillow_avif # noqa: F401 # pylint: disable=unused-import
|
||||||
|
|
||||||
HAVE_AVIF = True
|
HAVE_AVIF = True
|
||||||
@@ -80,6 +98,9 @@ except:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_VIPS"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_VIPS = True
|
HAVE_VIPS = True
|
||||||
import pyvips
|
import pyvips
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from .__init__ import ANYWIN, TYPE_CHECKING, unicode
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
|
||||||
from .authsrv import LEELOO_DALLAS, VFS
|
from .authsrv import LEELOO_DALLAS, VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .up2k import up2k_wark_from_hashlist
|
from .up2k import up2k_wark_from_hashlist
|
||||||
@@ -38,6 +38,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class U2idx(object):
|
class U2idx(object):
|
||||||
def __init__(self, hsrv: "HttpSrv") -> None:
|
def __init__(self, hsrv: "HttpSrv") -> None:
|
||||||
@@ -56,6 +59,8 @@ class U2idx(object):
|
|||||||
self.mem_cur = sqlite3.connect(":memory:", check_same_thread=False).cursor()
|
self.mem_cur = sqlite3.connect(":memory:", check_same_thread=False).cursor()
|
||||||
self.mem_cur.execute(r"create table a (b text)")
|
self.mem_cur.execute(r"create table a (b text)")
|
||||||
|
|
||||||
|
self.sh_cur: Optional["sqlite3.Cursor"] = None
|
||||||
|
|
||||||
self.p_end = 0.0
|
self.p_end = 0.0
|
||||||
self.p_dur = 0.0
|
self.p_dur = 0.0
|
||||||
|
|
||||||
@@ -92,17 +97,31 @@ class U2idx(object):
|
|||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
def get_cur(self, vn: VFS) -> Optional["sqlite3.Cursor"]:
|
def get_shr(self) -> Optional["sqlite3.Cursor"]:
|
||||||
if not HAVE_SQLITE3:
|
if self.sh_cur:
|
||||||
|
return self.sh_cur
|
||||||
|
|
||||||
|
if not HAVE_SQLITE3 or not self.args.shr:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
assert sqlite3 # type: ignore
|
||||||
|
|
||||||
|
db = sqlite3.connect(self.args.shr_db, timeout=2, check_same_thread=False)
|
||||||
|
cur = db.cursor()
|
||||||
|
cur.execute('pragma table_info("sh")').fetchall()
|
||||||
|
self.sh_cur = cur
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def get_cur(self, vn: VFS) -> Optional["sqlite3.Cursor"]:
|
||||||
cur = self.cur.get(vn.realpath)
|
cur = self.cur.get(vn.realpath)
|
||||||
if cur:
|
if cur:
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
if "e2d" not in vn.flags:
|
if not HAVE_SQLITE3 or "e2d" not in vn.flags:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
assert sqlite3 # type: ignore
|
||||||
|
|
||||||
ptop = vn.realpath
|
ptop = vn.realpath
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,6 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from email.utils import formatdate
|
|
||||||
|
|
||||||
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
@@ -60,6 +59,10 @@ except:
|
|||||||
UTC = _UTC()
|
UTC = _UTC()
|
||||||
|
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 7) or (
|
if sys.version_info >= (3, 7) or (
|
||||||
sys.version_info >= (3, 6) and platform.python_implementation() == "CPython"
|
sys.version_info >= (3, 6) and platform.python_implementation() == "CPython"
|
||||||
):
|
):
|
||||||
@@ -99,6 +102,9 @@ except:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_SQLITE"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_SQLITE3 = True
|
HAVE_SQLITE3 = True
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
@@ -107,6 +113,9 @@ except:
|
|||||||
HAVE_SQLITE3 = False
|
HAVE_SQLITE3 = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_PSUTIL"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
HAVE_PSUTIL = True
|
HAVE_PSUTIL = True
|
||||||
import psutil
|
import psutil
|
||||||
except:
|
except:
|
||||||
@@ -137,10 +146,15 @@ if TYPE_CHECKING:
|
|||||||
import magic
|
import magic
|
||||||
|
|
||||||
from .authsrv import VFS
|
from .authsrv import VFS
|
||||||
|
from .broker_util import BrokerCli
|
||||||
|
from .up2k import Up2k
|
||||||
|
|
||||||
FAKE_MP = False
|
FAKE_MP = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_MP"):
|
||||||
|
raise ImportError()
|
||||||
|
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
|
||||||
# import multiprocessing.dummy as mp
|
# import multiprocessing.dummy as mp
|
||||||
@@ -159,6 +173,9 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_IPV6"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
socket.inet_pton(socket.AF_INET6, "::1")
|
socket.inet_pton(socket.AF_INET6, "::1")
|
||||||
HAVE_IPV6 = True
|
HAVE_IPV6 = True
|
||||||
except:
|
except:
|
||||||
@@ -243,6 +260,8 @@ IMPLICATIONS = [
|
|||||||
["e2vu", "e2v"],
|
["e2vu", "e2v"],
|
||||||
["e2vp", "e2v"],
|
["e2vp", "e2v"],
|
||||||
["e2v", "e2d"],
|
["e2v", "e2d"],
|
||||||
|
["hardlink_only", "hardlink"],
|
||||||
|
["hardlink", "dedup"],
|
||||||
["tftpvv", "tftpv"],
|
["tftpvv", "tftpv"],
|
||||||
["smbw", "smb"],
|
["smbw", "smb"],
|
||||||
["smb1", "smb"],
|
["smb1", "smb"],
|
||||||
@@ -794,7 +813,7 @@ class CachedSet(object):
|
|||||||
|
|
||||||
c = self.c = {k: v for k, v in self.c.items() if now - v < self.maxage}
|
c = self.c = {k: v for k, v in self.c.items() if now - v < self.maxage}
|
||||||
try:
|
try:
|
||||||
self.oldest = c[min(c, key=c.get)]
|
self.oldest = c[min(c, key=c.get)] # type: ignore
|
||||||
except:
|
except:
|
||||||
self.oldest = now
|
self.oldest = now
|
||||||
|
|
||||||
@@ -898,7 +917,6 @@ class ProgressPrinter(threading.Thread):
|
|||||||
self.msg = ""
|
self.msg = ""
|
||||||
self.end = False
|
self.end = False
|
||||||
self.n = -1
|
self.n = -1
|
||||||
self.start()
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
sigblock()
|
sigblock()
|
||||||
@@ -1743,7 +1761,7 @@ def read_header(sr: Unrecv, t_idle: int, t_tot: int) -> list[str]:
|
|||||||
|
|
||||||
ofs = ret.find(b"\r\n\r\n")
|
ofs = ret.find(b"\r\n\r\n")
|
||||||
if ofs < 0:
|
if ofs < 0:
|
||||||
if len(ret) > 1024 * 64:
|
if len(ret) > 1024 * 32:
|
||||||
raise Pebkac(400, "header 2big")
|
raise Pebkac(400, "header 2big")
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
@@ -1821,10 +1839,21 @@ def gen_filekey_dbg(
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
WKDAYS = "Mon Tue Wed Thu Fri Sat Sun".split()
|
||||||
|
MONTHS = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()
|
||||||
|
RFC2822 = "%s, %02d %s %04d %02d:%02d:%02d GMT"
|
||||||
|
|
||||||
|
|
||||||
|
def formatdate(ts: Optional[float] = None) -> str:
|
||||||
|
# gmtime ~= datetime.fromtimestamp(ts, UTC).timetuple()
|
||||||
|
y, mo, d, h, mi, s, wd, _, _ = time.gmtime(ts)
|
||||||
|
return RFC2822 % (WKDAYS[wd], d, MONTHS[mo - 1], y, h, mi, s)
|
||||||
|
|
||||||
|
|
||||||
def gencookie(k: str, v: str, r: str, tls: bool, dur: int = 0, txt: str = "") -> str:
|
def gencookie(k: str, v: str, r: str, tls: bool, dur: int = 0, txt: str = "") -> str:
|
||||||
v = v.replace("%", "%25").replace(";", "%3B")
|
v = v.replace("%", "%25").replace(";", "%3B")
|
||||||
if dur:
|
if dur:
|
||||||
exp = formatdate(time.time() + dur, usegmt=True)
|
exp = formatdate(time.time() + dur)
|
||||||
else:
|
else:
|
||||||
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
||||||
|
|
||||||
@@ -1839,12 +1868,10 @@ def humansize(sz: float, terse: bool = False) -> str:
|
|||||||
|
|
||||||
sz /= 1024.0
|
sz /= 1024.0
|
||||||
|
|
||||||
ret = " ".join([str(sz)[:4].rstrip("."), unit])
|
if terse:
|
||||||
|
return "%s%s" % (str(sz)[:4].rstrip("."), unit[:1])
|
||||||
if not terse:
|
else:
|
||||||
return ret
|
return "%s %s" % (str(sz)[:4].rstrip("."), unit)
|
||||||
|
|
||||||
return ret.replace("iB", "").replace(" ", "")
|
|
||||||
|
|
||||||
|
|
||||||
def unhumanize(sz: str) -> int:
|
def unhumanize(sz: str) -> int:
|
||||||
@@ -1896,7 +1923,7 @@ def uncyg(path: str) -> str:
|
|||||||
def undot(path: str) -> str:
|
def undot(path: str) -> str:
|
||||||
ret: list[str] = []
|
ret: list[str] = []
|
||||||
for node in path.split("/"):
|
for node in path.split("/"):
|
||||||
if node in ["", "."]:
|
if node == "." or not node:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if node == "..":
|
if node == "..":
|
||||||
@@ -2049,7 +2076,7 @@ def _quotep2(txt: str) -> str:
|
|||||||
"""url quoter which deals with bytes correctly"""
|
"""url quoter which deals with bytes correctly"""
|
||||||
btxt = w8enc(txt)
|
btxt = w8enc(txt)
|
||||||
quot = quote(btxt, safe=b"/")
|
quot = quote(btxt, safe=b"/")
|
||||||
return w8dec(quot.replace(b" ", b"+"))
|
return w8dec(quot.replace(b" ", b"+")) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _quotep3(txt: str) -> str:
|
def _quotep3(txt: str) -> str:
|
||||||
@@ -2093,6 +2120,72 @@ def ujoin(rd: str, fn: str) -> str:
|
|||||||
return rd or fn
|
return rd or fn
|
||||||
|
|
||||||
|
|
||||||
|
def log_reloc(
|
||||||
|
log: "NamedLogger",
|
||||||
|
re: dict[str, str],
|
||||||
|
pm: tuple[str, str, str, tuple["VFS", str]],
|
||||||
|
ap: str,
|
||||||
|
vp: str,
|
||||||
|
fn: str,
|
||||||
|
vn: "VFS",
|
||||||
|
rem: str,
|
||||||
|
) -> None:
|
||||||
|
nap, nvp, nfn, (nvn, nrem) = pm
|
||||||
|
t = "reloc %s:\nold ap [%s]\nnew ap [%s\033[36m/%s\033[0m]\nold vp [%s]\nnew vp [%s\033[36m/%s\033[0m]\nold fn [%s]\nnew fn [%s]\nold vfs [%s]\nnew vfs [%s]\nold rem [%s]\nnew rem [%s]"
|
||||||
|
log(t % (re, ap, nap, nfn, vp, nvp, nfn, fn, nfn, vn.vpath, nvn.vpath, rem, nrem))
|
||||||
|
|
||||||
|
|
||||||
|
def pathmod(
|
||||||
|
vfs: "VFS", ap: str, vp: str, mod: dict[str, str]
|
||||||
|
) -> Optional[tuple[str, str, str, tuple["VFS", str]]]:
|
||||||
|
# vfs: authsrv.vfs
|
||||||
|
# ap: original abspath to a file
|
||||||
|
# vp: original urlpath to a file
|
||||||
|
# mod: modification (ap/vp/fn)
|
||||||
|
|
||||||
|
nvp = "\n" # new vpath
|
||||||
|
ap = os.path.dirname(ap)
|
||||||
|
vp, fn = vsplit(vp)
|
||||||
|
if mod.get("fn"):
|
||||||
|
fn = mod["fn"]
|
||||||
|
nvp = vp
|
||||||
|
|
||||||
|
for ref, k in ((ap, "ap"), (vp, "vp")):
|
||||||
|
if k not in mod:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ms = mod[k].replace(os.sep, "/")
|
||||||
|
if ms.startswith("/"):
|
||||||
|
np = ms
|
||||||
|
elif k == "vp":
|
||||||
|
np = undot(vjoin(ref, ms))
|
||||||
|
else:
|
||||||
|
np = os.path.abspath(os.path.join(ref, ms))
|
||||||
|
|
||||||
|
if k == "vp":
|
||||||
|
nvp = np.lstrip("/")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# try to map abspath to vpath
|
||||||
|
np = np.replace("/", os.sep)
|
||||||
|
for vn_ap, vn in vfs.all_aps:
|
||||||
|
if not np.startswith(vn_ap):
|
||||||
|
continue
|
||||||
|
zs = np[len(vn_ap) :].replace(os.sep, "/")
|
||||||
|
nvp = vjoin(vn.vpath, zs)
|
||||||
|
break
|
||||||
|
|
||||||
|
if nvp == "\n":
|
||||||
|
return None
|
||||||
|
|
||||||
|
vn, rem = vfs.get(nvp, "*", False, False)
|
||||||
|
if not vn.realpath:
|
||||||
|
raise Exception("unmapped vfs")
|
||||||
|
|
||||||
|
ap = vn.canonical(rem)
|
||||||
|
return ap, nvp, fn, (vn, rem)
|
||||||
|
|
||||||
|
|
||||||
def _w8dec2(txt: bytes) -> str:
|
def _w8dec2(txt: bytes) -> str:
|
||||||
"""decodes filesystem-bytes to wtf8"""
|
"""decodes filesystem-bytes to wtf8"""
|
||||||
return surrogateescape.decodefilename(txt)
|
return surrogateescape.decodefilename(txt)
|
||||||
@@ -2709,30 +2802,30 @@ def rmdirs_up(top: str, stop: str) -> tuple[list[str], list[str]]:
|
|||||||
|
|
||||||
def unescape_cookie(orig: str) -> str:
|
def unescape_cookie(orig: str) -> str:
|
||||||
# mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn # qwe,rty;asd fgh+jkl%zxc&vbn
|
# mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn # qwe,rty;asd fgh+jkl%zxc&vbn
|
||||||
ret = ""
|
ret = []
|
||||||
esc = ""
|
esc = ""
|
||||||
for ch in orig:
|
for ch in orig:
|
||||||
if ch == "%":
|
if ch == "%":
|
||||||
if len(esc) > 0:
|
if esc:
|
||||||
ret += esc
|
ret.append(esc)
|
||||||
esc = ch
|
esc = ch
|
||||||
|
|
||||||
elif len(esc) > 0:
|
elif esc:
|
||||||
esc += ch
|
esc += ch
|
||||||
if len(esc) == 3:
|
if len(esc) == 3:
|
||||||
try:
|
try:
|
||||||
ret += chr(int(esc[1:], 16))
|
ret.append(chr(int(esc[1:], 16)))
|
||||||
except:
|
except:
|
||||||
ret += esc
|
ret.append(esc)
|
||||||
esc = ""
|
esc = ""
|
||||||
|
|
||||||
else:
|
else:
|
||||||
ret += ch
|
ret.append(ch)
|
||||||
|
|
||||||
if len(esc) > 0:
|
if esc:
|
||||||
ret += esc
|
ret.append(esc)
|
||||||
|
|
||||||
return ret
|
return "".join(ret)
|
||||||
|
|
||||||
|
|
||||||
def guess_mime(url: str, fallback: str = "application/octet-stream") -> str:
|
def guess_mime(url: str, fallback: str = "application/octet-stream") -> str:
|
||||||
@@ -3106,6 +3199,7 @@ def runihook(
|
|||||||
|
|
||||||
def _runhook(
|
def _runhook(
|
||||||
log: Optional["NamedLogger"],
|
log: Optional["NamedLogger"],
|
||||||
|
src: str,
|
||||||
cmd: str,
|
cmd: str,
|
||||||
ap: str,
|
ap: str,
|
||||||
vp: str,
|
vp: str,
|
||||||
@@ -3117,14 +3211,16 @@ def _runhook(
|
|||||||
ip: str,
|
ip: str,
|
||||||
at: float,
|
at: float,
|
||||||
txt: str,
|
txt: str,
|
||||||
) -> bool:
|
) -> dict[str, Any]:
|
||||||
|
ret = {"rc": 0}
|
||||||
areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||||
if areq:
|
if areq:
|
||||||
for ch in areq:
|
for ch in areq:
|
||||||
if ch not in perms:
|
if ch not in perms:
|
||||||
t = "user %s not allowed to run hook %s; need perms %s, have %s"
|
t = "user %s not allowed to run hook %s; need perms %s, have %s"
|
||||||
log(t % (uname, cmd, areq, perms))
|
if log:
|
||||||
return True # fallthrough to next hook
|
log(t % (uname, cmd, areq, perms))
|
||||||
|
return ret # fallthrough to next hook
|
||||||
if jtxt:
|
if jtxt:
|
||||||
ja = {
|
ja = {
|
||||||
"ap": ap,
|
"ap": ap,
|
||||||
@@ -3136,6 +3232,7 @@ def _runhook(
|
|||||||
"host": host,
|
"host": host,
|
||||||
"user": uname,
|
"user": uname,
|
||||||
"perms": perms,
|
"perms": perms,
|
||||||
|
"src": src,
|
||||||
"txt": txt,
|
"txt": txt,
|
||||||
}
|
}
|
||||||
arg = json.dumps(ja)
|
arg = json.dumps(ja)
|
||||||
@@ -3154,18 +3251,34 @@ def _runhook(
|
|||||||
else:
|
else:
|
||||||
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
|
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
|
||||||
if chk and rc:
|
if chk and rc:
|
||||||
|
ret["rc"] = rc
|
||||||
retchk(rc, bcmd, err, log, 5)
|
retchk(rc, bcmd, err, log, 5)
|
||||||
return False
|
else:
|
||||||
|
try:
|
||||||
|
ret = json.loads(v)
|
||||||
|
except:
|
||||||
|
ret = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if "stdout" not in ret:
|
||||||
|
ret["stdout"] = v
|
||||||
|
if "rc" not in ret:
|
||||||
|
ret["rc"] = rc
|
||||||
|
except:
|
||||||
|
ret = {"rc": rc, "stdout": v}
|
||||||
|
|
||||||
wait -= time.time() - t0
|
wait -= time.time() - t0
|
||||||
if wait > 0:
|
if wait > 0:
|
||||||
time.sleep(wait)
|
time.sleep(wait)
|
||||||
|
|
||||||
return True
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def runhook(
|
def runhook(
|
||||||
log: Optional["NamedLogger"],
|
log: Optional["NamedLogger"],
|
||||||
|
broker: Optional["BrokerCli"],
|
||||||
|
up2k: Optional["Up2k"],
|
||||||
|
src: str,
|
||||||
cmds: list[str],
|
cmds: list[str],
|
||||||
ap: str,
|
ap: str,
|
||||||
vp: str,
|
vp: str,
|
||||||
@@ -3177,19 +3290,42 @@ def runhook(
|
|||||||
ip: str,
|
ip: str,
|
||||||
at: float,
|
at: float,
|
||||||
txt: str,
|
txt: str,
|
||||||
) -> bool:
|
) -> dict[str, Any]:
|
||||||
|
assert broker or up2k
|
||||||
|
asrv = (broker or up2k).asrv
|
||||||
|
args = (broker or up2k).args
|
||||||
vp = vp.replace("\\", "/")
|
vp = vp.replace("\\", "/")
|
||||||
|
ret = {"rc": 0}
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
try:
|
try:
|
||||||
if not _runhook(log, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt):
|
hr = _runhook(
|
||||||
return False
|
log, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
|
||||||
|
)
|
||||||
|
if log and args.hook_v:
|
||||||
|
log("hook(%s) [%s] => \033[32m%s" % (src, cmd, hr), 6)
|
||||||
|
if not hr:
|
||||||
|
return {}
|
||||||
|
for k, v in hr.items():
|
||||||
|
if k in ("idx", "del") and v:
|
||||||
|
if broker:
|
||||||
|
broker.say("up2k.hook_fx", k, v, vp)
|
||||||
|
else:
|
||||||
|
up2k.fx_backlog.append((k, v, vp))
|
||||||
|
elif k == "reloc" and v:
|
||||||
|
# idk, just take the last one ig
|
||||||
|
ret["reloc"] = v
|
||||||
|
elif k in ret:
|
||||||
|
if k == "rc" and v:
|
||||||
|
ret[k] = v
|
||||||
|
else:
|
||||||
|
ret[k] = v
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
(log or print)("hook: {}".format(ex))
|
(log or print)("hook: {}".format(ex))
|
||||||
if ",c," in "," + cmd:
|
if ",c," in "," + cmd:
|
||||||
return False
|
return {}
|
||||||
break
|
break
|
||||||
|
|
||||||
return True
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def loadpy(ap: str, hot: bool) -> Any:
|
def loadpy(ap: str, hot: bool) -> Any:
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
if (a != 'hidden' || b != 'scroll')
|
if (a != 'hidden' || b != 'scroll')
|
||||||
scrollCSS = [a, b];
|
scrollCSS = [a, b];
|
||||||
|
|
||||||
document.documentElement.style.overflowY = 'hidden';
|
document.documentElement.style.overflowY = 'hidden';
|
||||||
document.body.style.overflowY = 'scroll';
|
document.body.style.overflowY = 'scroll';
|
||||||
}
|
}
|
||||||
@@ -1067,7 +1067,7 @@ window.baguetteBox = (function () {
|
|||||||
return showNextImage();
|
return showNextImage();
|
||||||
|
|
||||||
show_buttons('t');
|
show_buttons('t');
|
||||||
|
|
||||||
if (Date.now() - ctime <= 500 && !IPHONE)
|
if (Date.now() - ctime <= 500 && !IPHONE)
|
||||||
tglfull();
|
tglfull();
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
--fg2-max: #fff;
|
--fg2-max: #fff;
|
||||||
--fg-weak: #bbb;
|
--fg-weak: #bbb;
|
||||||
|
|
||||||
--bg-u7: #555;
|
|
||||||
--bg-u6: #4c4c4c;
|
--bg-u6: #4c4c4c;
|
||||||
--bg-u5: #444;
|
--bg-u5: #444;
|
||||||
--bg-u4: #383838;
|
--bg-u4: #383838;
|
||||||
@@ -43,8 +42,14 @@
|
|||||||
--btn-h-bg: #805;
|
--btn-h-bg: #805;
|
||||||
--btn-1-fg: #400;
|
--btn-1-fg: #400;
|
||||||
--btn-1-bg: var(--a);
|
--btn-1-bg: var(--a);
|
||||||
|
--btn-h-bs: var(--btn-bs);
|
||||||
|
--btn-h-bb: var(--btn-bb);
|
||||||
|
--btn-1-bs: var(--btn-bs);
|
||||||
|
--btn-1-bb: var(--btn-bb);
|
||||||
--btn-1h-fg: var(--btn-1-fg);
|
--btn-1h-fg: var(--btn-1-fg);
|
||||||
--btn-1h-bg: #fe8;
|
--btn-1h-bg: #fe8;
|
||||||
|
--btn-1h-bs: var(--btn-1-bs);
|
||||||
|
--btn-1h-bb: var(--btn-1-bb);
|
||||||
--chk-fg: var(--tab-alt);
|
--chk-fg: var(--tab-alt);
|
||||||
--txt-sh: var(--bg-d2);
|
--txt-sh: var(--bg-d2);
|
||||||
--txt-bg: var(--btn-bg);
|
--txt-bg: var(--btn-bg);
|
||||||
@@ -59,7 +64,7 @@
|
|||||||
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
||||||
--u2-tab-b1: rgba(128,128,128,0.8);
|
--u2-tab-b1: rgba(128,128,128,0.8);
|
||||||
--u2-tab-1-fg: #fd7;
|
--u2-tab-1-fg: #fd7;
|
||||||
--u2-tab-1-bg: linear-gradient(to bottom, var(#353), var(--bg) 80%);
|
--u2-tab-1-bg: linear-gradient(to bottom, #353, var(--bg) 80%);
|
||||||
--u2-tab-1-b1: #7c5;
|
--u2-tab-1-b1: #7c5;
|
||||||
--u2-tab-1-b2: #583;
|
--u2-tab-1-b2: #583;
|
||||||
--u2-tab-1-sh: #280;
|
--u2-tab-1-sh: #280;
|
||||||
@@ -212,22 +217,19 @@ html.y {
|
|||||||
html.a {
|
html.a {
|
||||||
--op-aa-sh: 0 0 .2em var(--bg-d3) inset;
|
--op-aa-sh: 0 0 .2em var(--bg-d3) inset;
|
||||||
|
|
||||||
--u2-o-bg: #603;
|
--btn-bs: 0 0 .2em var(--bg-d3);
|
||||||
--u2-o-b1: #a16;
|
}
|
||||||
--u2-o-sh: #a00;
|
html.az {
|
||||||
--u2-o-h-bg: var(--u2-o-bg);
|
--btn-1-bs: 0 0 .1em var(--fg) inset;
|
||||||
--u2-o-h-b1: #fb0;
|
|
||||||
--u2-o-h-sh: #fb0;
|
|
||||||
--u2-o-1-bg: #6a1;
|
|
||||||
--u2-o-1-b1: #efa;
|
|
||||||
--u2-o-1-sh: #0c0;
|
|
||||||
--u2-o-1h-bg: var(--u2-o-1-bg);
|
|
||||||
}
|
}
|
||||||
html.ay {
|
html.ay {
|
||||||
--op-aa-sh: 0 .1em .2em #ccc;
|
--op-aa-sh: 0 .1em .2em #ccc;
|
||||||
--op-aa-bg: var(--bg-max);
|
--op-aa-bg: var(--bg-max);
|
||||||
}
|
}
|
||||||
html.b {
|
html.b {
|
||||||
|
--btn-bs: 0 .05em 0 var(--bg-d3) inset;
|
||||||
|
--btn-1-bs: 0 .05em 0 var(--btn-1h-bg) inset;
|
||||||
|
|
||||||
--tree-bg: var(--bg);
|
--tree-bg: var(--bg);
|
||||||
|
|
||||||
--g-bg: var(--bg);
|
--g-bg: var(--bg);
|
||||||
@@ -244,17 +246,13 @@ html.b {
|
|||||||
--u2-b1-bg: rgba(128,128,128,0.15);
|
--u2-b1-bg: rgba(128,128,128,0.15);
|
||||||
--u2-b2-bg: var(--u2-b1-bg);
|
--u2-b2-bg: var(--u2-b1-bg);
|
||||||
|
|
||||||
--u2-o-bg: var(--btn-bg);
|
|
||||||
--u2-o-h-bg: var(--btn-h-bg);
|
|
||||||
--u2-o-1-bg: var(--a);
|
|
||||||
--u2-o-1h-bg: var(--a-hil);
|
|
||||||
|
|
||||||
--f-sh1: 0.1;
|
--f-sh1: 0.1;
|
||||||
--mp-b-bg: transparent;
|
--mp-b-bg: transparent;
|
||||||
}
|
}
|
||||||
html.bz {
|
html.bz {
|
||||||
--fg: #cce;
|
--fg: #cce;
|
||||||
--fg-weak: #bbd;
|
--fg-weak: #bbd;
|
||||||
|
|
||||||
--bg-u5: #3b3f58;
|
--bg-u5: #3b3f58;
|
||||||
--bg-u4: #1e2130;
|
--bg-u4: #1e2130;
|
||||||
--bg-u3: #1e2130;
|
--bg-u3: #1e2130;
|
||||||
@@ -266,12 +264,14 @@ html.bz {
|
|||||||
|
|
||||||
--row-alt: #181a27;
|
--row-alt: #181a27;
|
||||||
|
|
||||||
|
--a-b: #fb4;
|
||||||
|
|
||||||
--btn-bg: #202231;
|
--btn-bg: #202231;
|
||||||
--btn-h-bg: #2d2f45;
|
--btn-h-bg: #2d2f45;
|
||||||
--btn-1-bg: #ba2959;
|
--btn-1-bg: #eb6;
|
||||||
--btn-1-is: #f59;
|
--btn-1-fg: #000;
|
||||||
--btn-1-fg: #fff;
|
|
||||||
--btn-1h-fg: #000;
|
--btn-1h-fg: #000;
|
||||||
|
--btn-1h-bg: #ff9;
|
||||||
--txt-sh: a;
|
--txt-sh: a;
|
||||||
|
|
||||||
--u2-tab-b1: var(--bg-u5);
|
--u2-tab-b1: var(--bg-u5);
|
||||||
@@ -306,6 +306,7 @@ html.by {
|
|||||||
}
|
}
|
||||||
html.c {
|
html.c {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
--fg: #fff;
|
--fg: #fff;
|
||||||
--fg-weak: #cef;
|
--fg-weak: #cef;
|
||||||
--bg-u5: #409;
|
--bg-u5: #409;
|
||||||
@@ -326,17 +327,25 @@ html.c {
|
|||||||
--chk-fg: #d90;
|
--chk-fg: #d90;
|
||||||
|
|
||||||
--op-aa-bg: #f9dd22;
|
--op-aa-bg: #f9dd22;
|
||||||
--u2-o-1-bg: #4cf;
|
|
||||||
|
|
||||||
--srv-1: #ea0;
|
--srv-1: #ea0;
|
||||||
--mp-b-bg: transparent;
|
--mp-b-bg: transparent;
|
||||||
}
|
}
|
||||||
html.cz {
|
html.cz {
|
||||||
--bgg: var(--bg-u2);
|
--bgg: var(--bg-u2);
|
||||||
|
|
||||||
--sel-bg: var(--bg-u5);
|
--sel-bg: var(--bg-u5);
|
||||||
--sel-fg: var(--fg);
|
--sel-fg: var(--fg);
|
||||||
|
|
||||||
|
--btn-bb: .2em solid #709;
|
||||||
|
--btn-bs: 0 .1em .6em rgba(255,0,185,0.5);
|
||||||
|
--btn-1-bb: .2em solid #e90;
|
||||||
|
--btn-1-bs: 0 .1em .8em rgba(255,205,0,0.9);
|
||||||
|
|
||||||
--srv-3: #fff;
|
--srv-3: #fff;
|
||||||
|
|
||||||
--u2-tab-b1: var(--bg-d3);
|
--u2-tab-b1: var(--bg-d3);
|
||||||
|
--u2-tab-1-bg: a;
|
||||||
}
|
}
|
||||||
html.cy {
|
html.cy {
|
||||||
--fg: #fff;
|
--fg: #fff;
|
||||||
@@ -363,16 +372,20 @@ html.cy {
|
|||||||
--btn-h-fg: #fff;
|
--btn-h-fg: #fff;
|
||||||
--btn-1-bg: #ff0;
|
--btn-1-bg: #ff0;
|
||||||
--btn-1-fg: #000;
|
--btn-1-fg: #000;
|
||||||
|
--btn-bs: 0 .25em 0 #f00;
|
||||||
--chk-fg: #fd0;
|
--chk-fg: #fd0;
|
||||||
|
|
||||||
|
--txt-bg: #000;
|
||||||
--srv-1: #f00;
|
--srv-1: #f00;
|
||||||
--srv-3: #fff;
|
--srv-3: #fff;
|
||||||
--op-aa-bg: #fff;
|
--op-aa-bg: #fff;
|
||||||
|
|
||||||
--u2-b1-bg: #f00;
|
--u2-b1-bg: #f00;
|
||||||
--u2-b2-bg: #f00;
|
--u2-b2-bg: #f00;
|
||||||
--u2-o-bg: #ff0;
|
|
||||||
--u2-o-1-bg: #f00;
|
--g-sel-fg: #fff;
|
||||||
|
--g-sel-bg: #aaa;
|
||||||
|
--g-fsel-bg: #aaa;
|
||||||
}
|
}
|
||||||
html.dz {
|
html.dz {
|
||||||
--fg: #4d4;
|
--fg: #4d4;
|
||||||
@@ -380,7 +393,6 @@ html.dz {
|
|||||||
--fg2-max: #fff;
|
--fg2-max: #fff;
|
||||||
--fg-weak: #2a2;
|
--fg-weak: #2a2;
|
||||||
|
|
||||||
--bg-u7: #020;
|
|
||||||
--bg-u6: #020;
|
--bg-u6: #020;
|
||||||
--bg-u5: #050;
|
--bg-u5: #050;
|
||||||
--bg-u4: #020;
|
--bg-u4: #020;
|
||||||
@@ -413,6 +425,9 @@ html.dz {
|
|||||||
--btn-1-bg: #4f4;
|
--btn-1-bg: #4f4;
|
||||||
--btn-1h-fg: var(--btn-1-fg);
|
--btn-1h-fg: var(--btn-1-fg);
|
||||||
--btn-1h-bg: #3f3;
|
--btn-1h-bg: #3f3;
|
||||||
|
--btn-bs: 0 0 0 .1em #080 inset;
|
||||||
|
--btn-1-bs: a;
|
||||||
|
|
||||||
--chk-fg: var(--tab-alt);
|
--chk-fg: var(--tab-alt);
|
||||||
--txt-sh: var(--bg-d2);
|
--txt-sh: var(--bg-d2);
|
||||||
--txt-bg: var(--btn-bg);
|
--txt-bg: var(--btn-bg);
|
||||||
@@ -427,19 +442,13 @@ html.dz {
|
|||||||
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
|
||||||
--u2-tab-b1: var(--fg-weak);
|
--u2-tab-b1: var(--fg-weak);
|
||||||
--u2-tab-1-fg: #fff;
|
--u2-tab-1-fg: #fff;
|
||||||
--u2-tab-1-bg: linear-gradient(to bottom, var(#353), var(--bg) 80%);
|
--u2-tab-1-bg: linear-gradient(to bottom, #151, var(--bg) 80%);
|
||||||
--u2-tab-1-b1: #7c5;
|
--u2-tab-1-b1: #7c5;
|
||||||
--u2-tab-1-b2: #583;
|
--u2-tab-1-b2: #583;
|
||||||
--u2-tab-1-sh: #280;
|
--u2-tab-1-sh: #280;
|
||||||
--u2-b-fg: #fff;
|
--u2-b-fg: #fff;
|
||||||
--u2-b1-bg: #3a3;
|
--u2-b1-bg: #3a3;
|
||||||
--u2-b2-bg: #3a3;
|
--u2-b2-bg: #3a3;
|
||||||
--u2-o-bg: var(--btn-bg);
|
|
||||||
--u2-o-b1: var(--bg-u5);
|
|
||||||
--u2-o-h-bg: var(--fg-weak);
|
|
||||||
--u2-o-1-bg: var(--fg-weak);
|
|
||||||
--u2-o-1-b1: var(--a);
|
|
||||||
--u2-o-1h-bg: var(--a);
|
|
||||||
--u2-inf-bg: #07a;
|
--u2-inf-bg: #07a;
|
||||||
--u2-inf-b1: #0be;
|
--u2-inf-b1: #0be;
|
||||||
--u2-ok-bg: #380;
|
--u2-ok-bg: #380;
|
||||||
@@ -551,10 +560,6 @@ html.dy {
|
|||||||
--u2-tab-1-bg: a;
|
--u2-tab-1-bg: a;
|
||||||
--u2-b1-bg: #000;
|
--u2-b1-bg: #000;
|
||||||
--u2-b2-bg: #000;
|
--u2-b2-bg: #000;
|
||||||
--u2-o-h-bg: #999;
|
|
||||||
--u2-o-1h-bg: #999;
|
|
||||||
--u2-o-bg: #eee;
|
|
||||||
--u2-o-1-bg: #000;
|
|
||||||
|
|
||||||
--ud-b1: a;
|
--ud-b1: a;
|
||||||
|
|
||||||
@@ -627,6 +632,7 @@ pre, code, tt, #doc, #doc>code {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
color: var(--bg);
|
||||||
}
|
}
|
||||||
html .ayjump:focus {
|
html .ayjump:focus {
|
||||||
z-index: 80386;
|
z-index: 80386;
|
||||||
@@ -963,6 +969,8 @@ html.y #path a:hover {
|
|||||||
#files tbody tr.play a:hover {
|
#files tbody tr.play a:hover {
|
||||||
color: var(--btn-1h-fg);
|
color: var(--btn-1h-fg);
|
||||||
background: var(--btn-1h-bg);
|
background: var(--btn-1h-bg);
|
||||||
|
box-shadow: var(--btn-1h-bs);
|
||||||
|
border-bottom: var(--btn-1h-bb);
|
||||||
}
|
}
|
||||||
#ggrid {
|
#ggrid {
|
||||||
margin: -.2em -.5em;
|
margin: -.2em -.5em;
|
||||||
@@ -971,6 +979,7 @@ html.y #path a:hover {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
line-clamp: var(--grid-ln);
|
||||||
-webkit-line-clamp: var(--grid-ln);
|
-webkit-line-clamp: var(--grid-ln);
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
padding-top: .3em;
|
padding-top: .3em;
|
||||||
@@ -1017,9 +1026,6 @@ html.y #path a:hover {
|
|||||||
color: var(--g-dfg);
|
color: var(--g-dfg);
|
||||||
}
|
}
|
||||||
#ggrid>a.au:before {
|
#ggrid>a.au:before {
|
||||||
content: '💾';
|
|
||||||
}
|
|
||||||
html.np_open #ggrid>a.au:before {
|
|
||||||
content: '▶';
|
content: '▶';
|
||||||
}
|
}
|
||||||
#ggrid>a:before {
|
#ggrid>a:before {
|
||||||
@@ -1148,6 +1154,7 @@ html.y #widget.open {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
#fshr,
|
||||||
#wtgrid,
|
#wtgrid,
|
||||||
#wtico {
|
#wtico {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1334,6 +1341,7 @@ html.y #widget.open {
|
|||||||
#widget.cmp #wtoggle {
|
#widget.cmp #wtoggle {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
#widget.cmp #fshr,
|
||||||
#widget.cmp #wtgrid {
|
#widget.cmp #wtgrid {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1434,7 +1442,11 @@ input[type="checkbox"]+label {
|
|||||||
input[type="radio"]:checked+label,
|
input[type="radio"]:checked+label,
|
||||||
input[type="checkbox"]:checked+label {
|
input[type="checkbox"]:checked+label {
|
||||||
color: #0e0;
|
color: #0e0;
|
||||||
color: var(--a);
|
color: var(--btn-1-bg);
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked+label {
|
||||||
|
box-shadow: var(--btn-1-bs);
|
||||||
|
border-bottom: var(--btn-1-bb);
|
||||||
}
|
}
|
||||||
html.dz input {
|
html.dz input {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
@@ -1612,6 +1624,8 @@ html {
|
|||||||
color: var(--btn-fg);
|
color: var(--btn-fg);
|
||||||
background: #eee;
|
background: #eee;
|
||||||
background: var(--btn-bg);
|
background: var(--btn-bg);
|
||||||
|
box-shadow: var(--btn-bs);
|
||||||
|
border-bottom: var(--btn-bb);
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
padding: .2em .4em;
|
padding: .2em .4em;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -1625,20 +1639,14 @@ html.c .btn,
|
|||||||
html.a .btn {
|
html.a .btn {
|
||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
}
|
}
|
||||||
html.cz .btn {
|
|
||||||
box-shadow: 0 .1em .6em rgba(255,0,185,0.5);
|
|
||||||
border-bottom: .2em solid #709;
|
|
||||||
}
|
|
||||||
html.dz .btn {
|
html.dz .btn {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
box-shadow: 0 0 0 .1em #080 inset;
|
|
||||||
}
|
|
||||||
html.dz .tgl.btn.on {
|
|
||||||
box-shadow: 0 0 0 .1em var(--btn-1-bg) inset;
|
|
||||||
}
|
}
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
color: var(--btn-h-fg);
|
color: var(--btn-h-fg);
|
||||||
background: var(--btn-h-bg);
|
background: var(--btn-h-bg);
|
||||||
|
box-shadow: var(--btn-h-bs);
|
||||||
|
border-bottom: var(--btn-h-bb);
|
||||||
}
|
}
|
||||||
.tgl.btn.on {
|
.tgl.btn.on {
|
||||||
background: #000;
|
background: #000;
|
||||||
@@ -1646,14 +1654,14 @@ html.dz .tgl.btn.on {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
color: var(--btn-1-fg);
|
color: var(--btn-1-fg);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
box-shadow: var(--btn-1-bs);
|
||||||
html.cz .tgl.btn.on {
|
border-bottom: var(--btn-1-bb);
|
||||||
box-shadow: 0 .1em .8em rgba(255,205,0,0.9);
|
|
||||||
border-bottom: .2em solid #e90;
|
|
||||||
}
|
}
|
||||||
.tgl.btn.on:hover {
|
.tgl.btn.on:hover {
|
||||||
background: var(--btn-1h-bg);
|
|
||||||
color: var(--btn-1h-fg);
|
color: var(--btn-1h-fg);
|
||||||
|
background: var(--btn-1h-bg);
|
||||||
|
box-shadow: var(--btn-1h-bs);
|
||||||
|
border-bottom: var(--btn-1h-bb);
|
||||||
}
|
}
|
||||||
#detree {
|
#detree {
|
||||||
padding: .3em .5em;
|
padding: .3em .5em;
|
||||||
@@ -1858,6 +1866,7 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
#unpost td:nth-child(4) {
|
#unpost td:nth-child(4) {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
#shui,
|
||||||
#rui {
|
#rui {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
@@ -1873,13 +1882,25 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
z-index: 765;
|
z-index: 765;
|
||||||
}
|
}
|
||||||
|
#shui div+div,
|
||||||
#rui div+div {
|
#rui div+div {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
#shui table,
|
||||||
#rui table {
|
#rui table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
#shui button {
|
||||||
|
margin: 0 1em 0 0;
|
||||||
|
}
|
||||||
|
#shui .btn {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
#shui td {
|
||||||
|
padding: .8em 0;
|
||||||
|
}
|
||||||
|
#shui td+td,
|
||||||
#rui td+td {
|
#rui td+td {
|
||||||
padding: .2em 0 .2em .5em;
|
padding: .2em 0 .2em .5em;
|
||||||
}
|
}
|
||||||
@@ -1887,10 +1908,15 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
font-family: var(--font-mono), 'scp', monospace, monospace;
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
|
#shui td+td,
|
||||||
#rui td+td,
|
#rui td+td,
|
||||||
|
#shui td input[type="text"],
|
||||||
#rui td input[type="text"] {
|
#rui td input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#shui td.exs input[type="text"] {
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
#rn_f.m td:first-child {
|
#rn_f.m td:first-child {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -2685,23 +2711,25 @@ html.b #u2conf a.b:hover {
|
|||||||
#u2conf input[type="checkbox"]:checked+label {
|
#u2conf input[type="checkbox"]:checked+label {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--u2-o-bg);
|
background: var(--btn-bg);
|
||||||
border-bottom: .2em solid var(--u2-o-b1);
|
box-shadow: var(--btn-bs);
|
||||||
box-shadow: 0 .1em .3em var(--u2-o-sh) inset;
|
border-bottom: var(--btn-bb);
|
||||||
text-shadow: 1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000;
|
text-shadow: 1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000;
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]:checked+label {
|
#u2conf input[type="checkbox"]:checked+label {
|
||||||
background: var(--u2-o-1-bg);
|
background: var(--btn-1-bg);
|
||||||
border-bottom: .2em solid var(--u2-o-1-b1);
|
box-shadow: var(--btn-1-bs);
|
||||||
box-shadow: 0 .1em .5em var(--u2-o-1-sh);
|
border-bottom: var(--btn-1-bb);
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]+label:hover {
|
#u2conf input[type="checkbox"]+label:hover {
|
||||||
box-shadow: 0 .1em .3em var(--u2-o-h-sh);
|
background: var(--btn-h-bg);
|
||||||
border-color: var(--u2-o-h-b1);
|
box-shadow: var(--btn-h-bs);
|
||||||
background: var(--u2-o-h-bg);
|
border-bottom: var(--btn-h-bb);
|
||||||
}
|
}
|
||||||
#u2conf input[type="checkbox"]:checked+label:hover {
|
#u2conf input[type="checkbox"]:checked+label:hover {
|
||||||
background: var(--u2-o-1h-bg);
|
background: var(--btn-1h-bg);
|
||||||
|
box-shadow: var(--btn-1h-bs);
|
||||||
|
border-bottom: var(--btn-1h-bb);
|
||||||
}
|
}
|
||||||
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
||||||
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
||||||
@@ -3061,14 +3089,6 @@ html.b #ggrid>a {
|
|||||||
html.b .btn {
|
html.b .btn {
|
||||||
top: -.1em;
|
top: -.1em;
|
||||||
}
|
}
|
||||||
html.b .btn,
|
|
||||||
html.b #u2conf a.b,
|
|
||||||
html.b #u2conf input[type="checkbox"]:not(:checked)+label {
|
|
||||||
box-shadow: 0 .05em 0 var(--bg-d3) inset;
|
|
||||||
}
|
|
||||||
html.b .tgl.btn.on {
|
|
||||||
box-shadow: 0 .05em 0 var(--btn-1-is) inset;
|
|
||||||
}
|
|
||||||
html.b #op_up2k.srch sup {
|
html.b #op_up2k.srch sup {
|
||||||
color: #fc0;
|
color: #fc0;
|
||||||
}
|
}
|
||||||
@@ -3098,18 +3118,30 @@ html.by #u2cards a.act {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.cy #wrap {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
html.cy .mdo a {
|
html.cy .mdo a {
|
||||||
background: #f00;
|
background: #f00;
|
||||||
}
|
}
|
||||||
|
html.cy #wrap,
|
||||||
|
html.cy #acc_info a,
|
||||||
html.cy #op_up2k,
|
html.cy #op_up2k,
|
||||||
html.cy #files,
|
html.cy #files,
|
||||||
html.cy #files a,
|
html.cy #files a,
|
||||||
html.cy #files tbody div a:last-child {
|
html.cy #files tbody div a:last-child {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
html.cy #u2tab a,
|
||||||
|
html.cy #u2cards a {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
|
html.cy #unpost a {
|
||||||
|
color: #ff0;
|
||||||
|
}
|
||||||
|
html.cy #barbuf {
|
||||||
|
filter: hue-rotate(267deg) brightness(0.8) contrast(4);
|
||||||
|
}
|
||||||
|
html.cy #pvol {
|
||||||
|
filter: hue-rotate(4deg) contrast(2.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,14 +67,14 @@
|
|||||||
<div id="op_up2k" class="opview"></div>
|
<div id="op_up2k" class="opview"></div>
|
||||||
|
|
||||||
<div id="op_cfg" class="opview opbox opwide"></div>
|
<div id="op_cfg" class="opview opbox opwide"></div>
|
||||||
|
|
||||||
<h1 id="path">
|
<h1 id="path">
|
||||||
<a href="#" id="entree">🌲</a>
|
<a href="#" id="entree">🌲</a>
|
||||||
{%- for n in vpnodes %}
|
{%- for n in vpnodes %}
|
||||||
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div id="tree"></div>
|
<div id="tree"></div>
|
||||||
|
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
@@ -118,11 +118,11 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
|
||||||
|
|
||||||
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
||||||
|
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,6 @@
|
|||||||
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
|
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
|
||||||
a{display:block}
|
a{display:block}
|
||||||
</style>
|
</style>
|
||||||
{{ html_head }}
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -52,11 +51,11 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{%- if logues[1] %}
|
{%- if logues[1] %}
|
||||||
<div>{{ logues[1] }}</div><br />
|
<div>{{ logues[1] }}</div><br />
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
<div id="mp" class="mdo"></div>
|
<div id="mp" class="mdo"></div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<div id="helpbox">
|
<div id="helpbox">
|
||||||
<textarea autocomplete="off">
|
<textarea autocomplete="off">
|
||||||
@@ -125,7 +125,7 @@ write markdown (most html is 🙆 too)
|
|||||||
</textarea>
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var SR = {{ r|tojson }},
|
var SR = {{ r|tojson }},
|
||||||
@@ -159,5 +159,8 @@ try { l.light = drk? 0:1; } catch (ex) { }
|
|||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|||||||
@@ -53,5 +53,8 @@ try { l.light = drk? 0:1; } catch (ex) { }
|
|||||||
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
</script>
|
</script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
82
copyparty/web/shares.css
Normal file
82
copyparty/web/shares.css
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
html {
|
||||||
|
color: #333;
|
||||||
|
background: #f7f7f7;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-family: var(--font-main), sans-serif;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
#wrap {
|
||||||
|
margin: 2em auto;
|
||||||
|
padding: 0 1em 3em 1em;
|
||||||
|
line-height: 2.3em;
|
||||||
|
}
|
||||||
|
#wrap>span {
|
||||||
|
margin: 0 0 0 1em;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #047;
|
||||||
|
background: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-bottom: 1px solid #8ab;
|
||||||
|
border-radius: .2em;
|
||||||
|
padding: .2em .6em;
|
||||||
|
margin: 0 .3em;
|
||||||
|
}
|
||||||
|
td a {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#w {
|
||||||
|
color: #fff;
|
||||||
|
background: #940;
|
||||||
|
border-color: #b70;
|
||||||
|
}
|
||||||
|
#repl {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
bottom: .25em;
|
||||||
|
left: .2em;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
top: -1px;
|
||||||
|
position: sticky;
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
padding: .3em .6em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
td+td+td+td+td+td+td+td {
|
||||||
|
font-family: var(--font-mono), monospace, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
html.z {
|
||||||
|
background: #222;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
html.z a {
|
||||||
|
color: #fff;
|
||||||
|
background: #057;
|
||||||
|
border-color: #37a;
|
||||||
|
}
|
||||||
|
html.z th {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
html.bz {
|
||||||
|
color: #bbd;
|
||||||
|
background: #11121d;
|
||||||
|
}
|
||||||
76
copyparty/web/shares.html
Normal file
76
copyparty/web/shares.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ s_doctitle }}</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
<meta name="theme-color" content="#{{ tcolor }}">
|
||||||
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
|
||||||
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
|
{{ html_head }}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="wrap">
|
||||||
|
<a id="a" href="{{ r }}/?shares" class="af">refresh</a>
|
||||||
|
<a id="a" href="{{ r }}/?h" class="af">control-panel</a>
|
||||||
|
|
||||||
|
<span>axs = perms (read,write,move,delet)</span>
|
||||||
|
<span>nf = numFiles (0=dir)</span>
|
||||||
|
<span>min/hrs = time left</span>
|
||||||
|
|
||||||
|
<table id="tab"><thead><tr>
|
||||||
|
<th>delete</th>
|
||||||
|
<th>sharekey</th>
|
||||||
|
<th>pw</th>
|
||||||
|
<th>source</th>
|
||||||
|
<th>axs</th>
|
||||||
|
<th>nf</th>
|
||||||
|
<th>user</th>
|
||||||
|
<th>created</th>
|
||||||
|
<th>expires</th>
|
||||||
|
<th>min</th>
|
||||||
|
<th>hrs</th>
|
||||||
|
<th>add time</th>
|
||||||
|
</tr></thead><tbody>
|
||||||
|
{% for k, pw, vp, pr, st, un, t0, t1 in rows %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="#" k="{{ k }}">delete</a></td>
|
||||||
|
<td><a href="{{ r }}{{ shr }}{{ k }}">{{ k }}</a></td>
|
||||||
|
<td>{{ pw }}</td>
|
||||||
|
<td><a href="{{ r }}/{{ vp|e }}">{{ vp|e }}</a></td>
|
||||||
|
<td>{{ pr }}</td>
|
||||||
|
<td>{{ st }}</td>
|
||||||
|
<td>{{ un|e }}</td>
|
||||||
|
<td>{{ t0 }}</td>
|
||||||
|
<td>{{ t1 }}</td>
|
||||||
|
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 60) | round(1) }}</td>
|
||||||
|
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 3600) | round(1) }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody></table>
|
||||||
|
{% if not rows %}
|
||||||
|
(you don't have any active shares btw)
|
||||||
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var SR = {{ r|tojson }},
|
||||||
|
shr="{{ shr }}",
|
||||||
|
lang="{{ lang }}",
|
||||||
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
|
var STG = window.localStorage;
|
||||||
|
document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
|
<script src="{{ r }}/.cpr/shares.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
56
copyparty/web/shares.js
Normal file
56
copyparty/web/shares.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
var t = QSA('a[k]');
|
||||||
|
for (var a = 0; a < t.length; a++)
|
||||||
|
t[a].onclick = rm;
|
||||||
|
|
||||||
|
function rm() {
|
||||||
|
var u = SR + shr + uricom_enc(this.getAttribute('k')) + '?eshare=rm',
|
||||||
|
xhr = new XHR();
|
||||||
|
|
||||||
|
xhr.open('POST', u, true);
|
||||||
|
xhr.onload = xhr.onerror = cb;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bump() {
|
||||||
|
var k = this.closest('tr').getElementsByTagName('a')[0].getAttribute('k'),
|
||||||
|
u = SR + shr + uricom_enc(k) + '?eshare=' + this.value,
|
||||||
|
xhr = new XHR();
|
||||||
|
|
||||||
|
xhr.open('POST', u, true);
|
||||||
|
xhr.onload = xhr.onerror = cb;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cb() {
|
||||||
|
if (this.status !== 200)
|
||||||
|
return modal.alert('<h6>server error</h6>' + esc(unpre(this.responseText)));
|
||||||
|
|
||||||
|
document.location = '?shares';
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var tab = ebi('tab').tBodies[0],
|
||||||
|
tr = Array.prototype.slice.call(tab.rows, 0);
|
||||||
|
|
||||||
|
var buf = [];
|
||||||
|
for (var a = 0; a < tr.length; a++)
|
||||||
|
for (var b = 7; b < 9; b++)
|
||||||
|
buf.push(parseInt(tr[a].cells[b].innerHTML));
|
||||||
|
|
||||||
|
var ibuf = 0;
|
||||||
|
for (var a = 0; a < tr.length; a++)
|
||||||
|
for (var b = 7; b < 9; b++) {
|
||||||
|
var v = buf[ibuf++];
|
||||||
|
tr[a].cells[b].innerHTML =
|
||||||
|
v ? unix2iso(v).replace(' ', ', ') : 'never';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = 0; a < tr.length; a++)
|
||||||
|
tr[a].cells[11].innerHTML =
|
||||||
|
'<button value="1">1min</button> ' +
|
||||||
|
'<button value="60">1h</button>';
|
||||||
|
|
||||||
|
var btns = QSA('td button'), aa = btns.length;
|
||||||
|
for (var a = 0; a < aa; a++)
|
||||||
|
btns[a].onclick = bump;
|
||||||
|
})();
|
||||||
@@ -182,13 +182,18 @@ html.z a.g {
|
|||||||
border-color: #af4;
|
border-color: #af4;
|
||||||
box-shadow: 0 .3em 1em #7d0;
|
box-shadow: 0 .3em 1em #7d0;
|
||||||
}
|
}
|
||||||
|
form {
|
||||||
|
line-height: 2.5em;
|
||||||
|
}
|
||||||
|
#x,
|
||||||
input {
|
input {
|
||||||
color: #a50;
|
color: #a50;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #a50;
|
border: 1px solid #a50;
|
||||||
border-radius: .5em;
|
border-radius: .3em;
|
||||||
padding: .5em .7em;
|
padding: .25em .6em;
|
||||||
margin: 0 .5em 0 0;
|
margin: 0 .3em 0 0;
|
||||||
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
input::placeholder {
|
input::placeholder {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -197,6 +202,7 @@ input::placeholder {
|
|||||||
opacity: 0.64;
|
opacity: 0.64;
|
||||||
color: #930;
|
color: #930;
|
||||||
}
|
}
|
||||||
|
#x,
|
||||||
html.z input {
|
html.z input {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #626;
|
background: #626;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
|
{%- if not in_shr %}
|
||||||
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
||||||
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@
|
|||||||
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
||||||
<p><span id="m">welcome back,</span> <strong>{{ this.uname }}</strong></p>
|
<p><span id="m">welcome back,</span> <strong>{{ this.uname|e }}</strong></p>
|
||||||
|
{%- endif %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{%- if msg %}
|
{%- if msg %}
|
||||||
@@ -76,8 +78,43 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h1 id="cc">client config:</h1>
|
{%- if in_shr %}
|
||||||
|
<h1 id="z">unlock this share:</h1>
|
||||||
|
<div>
|
||||||
|
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||||
|
<input type="hidden" id="la" name="act" value="login" />
|
||||||
|
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||||
|
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||||
|
<input type="submit" id="ls" value="Unlock" />
|
||||||
|
{% if ahttps %}
|
||||||
|
<a id="w" href="{{ ahttps }}">switch to https</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{%- else %}
|
||||||
|
<h1 id="l">login for more:</h1>
|
||||||
|
<div>
|
||||||
|
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||||
|
<input type="hidden" id="la" name="act" value="login" />
|
||||||
|
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||||
|
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||||
|
<input type="submit" id="ls" value="Login" />
|
||||||
|
{% if chpw %}
|
||||||
|
<a id="x" href="#">change password</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if ahttps %}
|
||||||
|
<a id="w" href="{{ ahttps }}">switch to https</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
<h1 id="cc">other stuff:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
|
{%- if this.uname != '*' and this.args.shr %}
|
||||||
|
<li><a id="y" href="{{ r }}/?shares">edit shares</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if k304 or k304vis %}
|
{% if k304 or k304vis %}
|
||||||
{% if k304 %}
|
{% if k304 %}
|
||||||
<li><a id="h" href="{{ r }}/?k304=n">disable k304</a> (currently enabled)
|
<li><a id="h" href="{{ r }}/?k304=n">disable k304</a> (currently enabled)
|
||||||
@@ -90,18 +127,6 @@
|
|||||||
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1 id="l">login for more:</h1>
|
|
||||||
<div>
|
|
||||||
<form method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
|
||||||
<input type="hidden" name="act" value="login" />
|
|
||||||
<input type="password" name="cppwd" placeholder=" password" />
|
|
||||||
<input type="hidden" name="uhash" id="uhash" value="x" />
|
|
||||||
<input type="submit" value="Login" />
|
|
||||||
{% if ahttps %}
|
|
||||||
<a id="w" href="{{ ahttps }}">switch to https</a>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
{%- if not this.args.nb %}
|
{%- if not this.args.nb %}
|
||||||
@@ -119,6 +144,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
|
|||||||
</script>
|
</script>
|
||||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var Ls = {
|
|||||||
"e2": "leser inn konfigurasjonsfiler på nytt$N(kontoer, volumer, volumbrytere)$Nog kartlegger alle e2ds-volumer$N$Nmerk: endringer i globale parametere$Nkrever en full restart for å ta gjenge",
|
"e2": "leser inn konfigurasjonsfiler på nytt$N(kontoer, volumer, volumbrytere)$Nog kartlegger alle e2ds-volumer$N$Nmerk: endringer i globale parametere$Nkrever en full restart for å ta gjenge",
|
||||||
"f1": "du kan betrakte:",
|
"f1": "du kan betrakte:",
|
||||||
"g1": "du kan laste opp til:",
|
"g1": "du kan laste opp til:",
|
||||||
"cc1": "klient-konfigurasjon",
|
"cc1": "brytere og sånt",
|
||||||
"h1": "skru av k304",
|
"h1": "skru av k304",
|
||||||
"i1": "skru på k304",
|
"i1": "skru på k304",
|
||||||
"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",
|
"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",
|
||||||
@@ -17,9 +17,9 @@ var Ls = {
|
|||||||
"l1": "logg inn:",
|
"l1": "logg inn:",
|
||||||
"m1": "velkommen tilbake,",
|
"m1": "velkommen tilbake,",
|
||||||
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
||||||
"o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
|
"o1": 'eller kanskje du ikke har tilgang? prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||||
"p1": "403: tilgang nektet ~┻━┻",
|
"p1": "403: tilgang nektet ~┻━┻",
|
||||||
"q1": 'du må logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
|
"q1": 'prøv et passord eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||||
"r1": "gå hjem",
|
"r1": "gå hjem",
|
||||||
".s1": "kartlegg",
|
".s1": "kartlegg",
|
||||||
"t1": "handling",
|
"t1": "handling",
|
||||||
@@ -27,21 +27,65 @@ var Ls = {
|
|||||||
"v1": "koble til",
|
"v1": "koble til",
|
||||||
"v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!",
|
"v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!",
|
||||||
"w1": "bytt til https",
|
"w1": "bytt til https",
|
||||||
|
"x1": "bytt passord",
|
||||||
|
"y1": "dine delinger",
|
||||||
|
"z1": "lås opp område",
|
||||||
|
"ta1": "du må skrive et nytt passord først",
|
||||||
|
"ta2": "gjenta for å bekrefte nytt passord:",
|
||||||
|
"ta3": "fant en skrivefeil; vennligst prøv igjen",
|
||||||
},
|
},
|
||||||
"eng": {
|
"eng": {
|
||||||
"d2": "shows the state of all active threads",
|
"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",
|
"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",
|
||||||
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
"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$N$NWARNING: this will show your password!",
|
"v2": "use this server as a local HDD$N$NWARNING: this will show your password!",
|
||||||
|
"ta1": "fill in your new password first",
|
||||||
|
"ta2": "repeat to confirm new password:",
|
||||||
|
"ta3": "found a typo; please try again",
|
||||||
|
},
|
||||||
|
|
||||||
|
"chi": {
|
||||||
|
"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 时断开连接。这有助于避免某些代理服务器卡住或突然停止加载页面,但也会显著降低性能。",
|
||||||
|
"k1": "重置设置",
|
||||||
|
"l1": "登录:",
|
||||||
|
"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": "将此服务器用作本地硬盘$N$N警告:这将显示你的密码!",
|
||||||
|
"w1": "切换到 https",
|
||||||
|
"x1": "更改密码",
|
||||||
|
"y1": "你的分享",
|
||||||
|
"z1": "解锁区域",
|
||||||
|
"ta1": "请先输入新密码",
|
||||||
|
"ta2": "重复以确认新密码:",
|
||||||
|
"ta3": "发现拼写错误;请重试",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var LANGS = ["eng", "nor"];
|
|
||||||
|
|
||||||
if (window.langmod)
|
if (window.langmod)
|
||||||
langmod();
|
langmod();
|
||||||
|
|
||||||
var d = Ls[sread("cpp_lang", LANGS) || lang] || Ls.eng || Ls.nor;
|
var d = Ls[sread("cpp_lang", Object.keys(Ls)) || lang] ||
|
||||||
|
Ls.eng || Ls.nor || Ls.chi;
|
||||||
|
|
||||||
for (var k in (d || {})) {
|
for (var k in (d || {})) {
|
||||||
var f = k.slice(-1),
|
var f = k.slice(-1),
|
||||||
@@ -74,3 +118,42 @@ if (o && /[0-9]+$/.exec(o.innerHTML))
|
|||||||
o.innerHTML = shumantime(o.innerHTML);
|
o.innerHTML = shumantime(o.innerHTML);
|
||||||
|
|
||||||
ebi('uhash').value = '' + location.hash;
|
ebi('uhash').value = '' + location.hash;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
if (!ebi('x'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var pwi = ebi('lp');
|
||||||
|
|
||||||
|
function redo(msg) {
|
||||||
|
modal.alert(msg, function() {
|
||||||
|
pwi.value = '';
|
||||||
|
pwi.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function mok(v) {
|
||||||
|
if (v !== pwi.value)
|
||||||
|
return redo(d.ta3);
|
||||||
|
|
||||||
|
pwi.setAttribute('name', 'pw');
|
||||||
|
ebi('la').value = 'chpw';
|
||||||
|
ebi('lf').submit();
|
||||||
|
}
|
||||||
|
function stars() {
|
||||||
|
var m = ebi('modali');
|
||||||
|
function enstars(n) {
|
||||||
|
setTimeout(function() { m.value = ''; }, n);
|
||||||
|
}
|
||||||
|
m.setAttribute('type', 'password');
|
||||||
|
enstars(17);
|
||||||
|
enstars(32);
|
||||||
|
enstars(69);
|
||||||
|
}
|
||||||
|
ebi('x').onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
if (!pwi.value)
|
||||||
|
return redo(d.ta1);
|
||||||
|
|
||||||
|
modal.prompt(d.ta2, "y", mok, null, stars);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
||||||
<pre>
|
<pre>
|
||||||
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ rvp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
<pre>
|
<pre>
|
||||||
http{{ s }}://k:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}
|
http{{ s }}://k:<b>{{ pw }}</b>@{{ ep }}/{{ rvp }}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
{% if s %}
|
{% if s %}
|
||||||
<p><em>replace <code>https</code> with <code>http</code> if it doesn't work</em></p>
|
<p><em>replace <code>https</code> with <code>http</code> if it doesn't work</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -245,6 +245,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ args.theme }}";
|
|||||||
</script>
|
</script>
|
||||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
||||||
|
{%- if js %}
|
||||||
|
<script src="{{ js }}_={{ ts }}"></script>
|
||||||
|
{%- endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,11 @@ html.y #tth {
|
|||||||
box-shadow: 0 .3em 3em rgba(0,0,0,0.5);
|
box-shadow: 0 .3em 3em rgba(0,0,0,0.5);
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
max-height: 30em;
|
max-height: 30em;
|
||||||
overflow: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
#modalc.yk {
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
#modalc td {
|
#modalc td {
|
||||||
text-align: unset;
|
text-align: unset;
|
||||||
@@ -381,6 +385,7 @@ html.y textarea:focus {
|
|||||||
}
|
}
|
||||||
.mdo pre,
|
.mdo pre,
|
||||||
.mdo code,
|
.mdo code,
|
||||||
|
.mdo code[class*="language-"],
|
||||||
.mdo tt {
|
.mdo tt {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
font-family: var(--font-mono), 'scp', monospace, monospace;
|
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||||
|
|||||||
@@ -152,12 +152,13 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
r.mod0 = null;
|
r.mod0 = null;
|
||||||
|
|
||||||
var markup = {
|
var markup = {
|
||||||
'404': '<span class="err">404</span>',
|
'404': '<span class="err">' + L.utl_404 + '</span>',
|
||||||
'ERROR': '<span class="err">ERROR</span>',
|
'ERROR': '<span class="err">' + L.utl_err + '</span>',
|
||||||
'OS-error': '<span class="err">OS-error</span>',
|
'OS-error': '<span class="err">' + L.utl_oserr + '</span>',
|
||||||
'found': '<span class="inf">found</span>',
|
'found': '<span class="inf">' + L.utl_found + '</span>',
|
||||||
'YOLO': '<span class="inf">YOLO</span>',
|
'defer': '<span class="inf">' + L.utl_defer + '</span>',
|
||||||
'done': '<span class="ok">done</span>',
|
'YOLO': '<span class="inf">' + L.utl_yolo + '</span>',
|
||||||
|
'done': '<span class="ok">' + L.utl_done + '</span>',
|
||||||
};
|
};
|
||||||
|
|
||||||
r.addfile = function (entry, sz, draw) {
|
r.addfile = function (entry, sz, draw) {
|
||||||
@@ -445,9 +446,7 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
r.npotato = 0;
|
r.npotato = 0;
|
||||||
var html = [
|
var html = [L.u_pott.format(r.ctr.ok, r.ctr.ng, r.ctr.bz, r.ctr.q)];
|
||||||
"<p>files: <b>{0}</b> finished, <b>{1}</b> failed, <b>{2}</b> busy, <b>{3}</b> queued</p>".format(
|
|
||||||
r.ctr.ok, r.ctr.ng, r.ctr.bz, r.ctr.q)];
|
|
||||||
|
|
||||||
while (r.head < r.tab.length && has(["ok", "ng"], r.tab[r.head].in))
|
while (r.head < r.tab.length && has(["ok", "ng"], r.tab[r.head].in))
|
||||||
r.head++;
|
r.head++;
|
||||||
@@ -602,7 +601,7 @@ function U2pvis(act, btns, uc, st) {
|
|||||||
if (nf < 9000)
|
if (nf < 9000)
|
||||||
return go();
|
return go();
|
||||||
|
|
||||||
modal.confirm('about to show ' + nf + ' files\n\nthis may crash your browser, are you sure?', go, null);
|
modal.confirm(L.u_bigtab.format(nf), go, null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,7 +1036,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
document.body.ondragenter = document.body.ondragleave = document.body.ondragover = null;
|
document.body.ondragenter = document.body.ondragleave = document.body.ondragover = null;
|
||||||
return modal.alert('your browser does not support drag-and-drop uploading');
|
return modal.alert(L.u_nodrop);
|
||||||
}
|
}
|
||||||
if (btn)
|
if (btn)
|
||||||
return;
|
return;
|
||||||
@@ -1104,7 +1103,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!good_files.length && bad_files.length)
|
if (!good_files.length && bad_files.length)
|
||||||
return toast.err(30, "that's not a folder!\n\nyour browser is too old,\nplease try dragdrop instead");
|
return toast.err(30, L.u_notdir);
|
||||||
|
|
||||||
return read_dirs(null, [], [], good_files, nil_files, bad_files);
|
return read_dirs(null, [], [], good_files, nil_files, bad_files);
|
||||||
}
|
}
|
||||||
@@ -1122,7 +1121,7 @@ function up2k_init(subtle) {
|
|||||||
if (err)
|
if (err)
|
||||||
return modal.alert('sorry, ' + err);
|
return modal.alert('sorry, ' + err);
|
||||||
|
|
||||||
toast.inf(0, 'Scanning files...');
|
toast.inf(0, L.u_scan);
|
||||||
|
|
||||||
if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
|
if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
|
||||||
tgl_fsearch();
|
tgl_fsearch();
|
||||||
@@ -1210,7 +1209,7 @@ function up2k_init(subtle) {
|
|||||||
match = false;
|
match = false;
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:<ul>'.format(missing.length)];
|
var msg = [L.u_dirstuck.format(missing.length) + '<ul>'];
|
||||||
for (var a = 0; a < Math.min(20, missing.length); a++)
|
for (var a = 0; a < Math.min(20, missing.length); a++)
|
||||||
msg.push('<li>' + esc(missing[a]) + '</li>');
|
msg.push('<li>' + esc(missing[a]) + '</li>');
|
||||||
|
|
||||||
@@ -1281,7 +1280,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gotallfiles(good_files, nil_files, bad_files) {
|
function gotallfiles(good_files, nil_files, bad_files) {
|
||||||
if (toast.txt == 'Scanning files...')
|
if (toast.txt == L.u_scan)
|
||||||
toast.hide();
|
toast.hide();
|
||||||
|
|
||||||
if (uc.fsearch && !uc.turbo)
|
if (uc.fsearch && !uc.turbo)
|
||||||
@@ -1437,7 +1436,7 @@ function up2k_init(subtle) {
|
|||||||
if (!actx || actx.state != 'suspended' || toast.visible)
|
if (!actx || actx.state != 'suspended' || toast.visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
toast.warn(30, "<div onclick=\"start_actx();toast.inf(3,'thanks!')\">please click this text to<br />unlock full upload speed</div>");
|
toast.warn(30, "<div onclick=\"start_actx();toast.inf(3,'thanks!')\">" + L.u_actx + "</div>");
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1479,7 +1478,7 @@ function up2k_init(subtle) {
|
|||||||
ev(e);
|
ev(e);
|
||||||
var txt = linklist();
|
var txt = linklist();
|
||||||
cliptxt(txt + '\n', function () {
|
cliptxt(txt + '\n', function () {
|
||||||
toast.inf(5, txt.split('\n').length + ' links copied to clipboard');
|
toast.inf(5, un_clip.format(txt.split('\n').length));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1746,14 +1745,6 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
var mou_ikkai = false;
|
var mou_ikkai = false;
|
||||||
|
|
||||||
if (st.busy.handshake.length &&
|
|
||||||
st.busy.handshake[0].t_busied < now - 30 * 1000
|
|
||||||
) {
|
|
||||||
console.log("retrying stuck handshake");
|
|
||||||
var t = st.busy.handshake.shift();
|
|
||||||
st.todo.handshake.unshift(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
var nprev = -1;
|
var nprev = -1;
|
||||||
for (var a = 0; a < st.todo.upload.length; a++) {
|
for (var a = 0; a < st.todo.upload.length; a++) {
|
||||||
var nf = st.todo.upload[a].nfile;
|
var nf = st.todo.upload[a].nfile;
|
||||||
@@ -2186,7 +2177,7 @@ function up2k_init(subtle) {
|
|||||||
st.busy.head.push(t);
|
st.busy.head.push(t);
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.onerror = function () {
|
xhr.onerror = xhr.ontimeout = function () {
|
||||||
console.log('head onerror, retrying', t.name, t);
|
console.log('head onerror, retrying', t.name, t);
|
||||||
if (!toast.visible)
|
if (!toast.visible)
|
||||||
toast.warn(9.98, L.u_enethd + "\n\nfile: " + t.name, t);
|
toast.warn(9.98, L.u_enethd + "\n\nfile: " + t.name, t);
|
||||||
@@ -2230,6 +2221,7 @@ function up2k_init(subtle) {
|
|||||||
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
xhr.timeout = 34000;
|
||||||
xhr.open('HEAD', t.purl + uricom_enc(t.name), true);
|
xhr.open('HEAD', t.purl + uricom_enc(t.name), true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
@@ -2254,8 +2246,11 @@ function up2k_init(subtle) {
|
|||||||
if (keepalive)
|
if (keepalive)
|
||||||
console.log("sending keepalive handshake", t.name, t);
|
console.log("sending keepalive handshake", t.name, t);
|
||||||
|
|
||||||
|
if (!t.srch && !t.t_handshake)
|
||||||
|
pvis.seth(t.n, 2, L.u_hs);
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.onerror = function () {
|
xhr.onerror = xhr.ontimeout = function () {
|
||||||
if (t.t_busied != me) // t.done ok
|
if (t.t_busied != me) // t.done ok
|
||||||
return console.log('zombie handshake onerror', t.name, t);
|
return console.log('zombie handshake onerror', t.name, t);
|
||||||
|
|
||||||
@@ -2280,7 +2275,8 @@ function up2k_init(subtle) {
|
|||||||
apop(st.busy.handshake, t);
|
apop(st.busy.handshake, t);
|
||||||
st.todo.handshake.unshift(t);
|
st.todo.handshake.unshift(t);
|
||||||
t.cooldown = Date.now() + 5000 + Math.floor(Math.random() * 3000);
|
t.cooldown = Date.now() + 5000 + Math.floor(Math.random() * 3000);
|
||||||
return toast.err(0, 'Handshake error; will retry...\n\n' + L.badreply + ':\n\n' + unpre(xhr.responseText));
|
var txt = t.t_uploading ? L.u_ehsfin : t.srch ? L.u_ehssrch : L.u_ehsinit;
|
||||||
|
return toast.err(0, txt + '\n\n' + L.badreply + ':\n\n' + unpre(xhr.responseText));
|
||||||
}
|
}
|
||||||
|
|
||||||
t.t_handshake = Date.now();
|
t.t_handshake = Date.now();
|
||||||
@@ -2382,15 +2378,31 @@ function up2k_init(subtle) {
|
|||||||
var arr = st.todo.upload,
|
var arr = st.todo.upload,
|
||||||
sort = arr.length && arr[arr.length - 1].nfile > t.n;
|
sort = arr.length && arr[arr.length - 1].nfile > t.n;
|
||||||
|
|
||||||
|
if (!t.stitch_sz) {
|
||||||
|
// keep all connections busy
|
||||||
|
var bpc = (st.bytes.total - st.bytes.finished) / (parallel_uploads || 1),
|
||||||
|
ocs = 1024 * 1024,
|
||||||
|
stp = 1024 * 512,
|
||||||
|
ccs = ocs;
|
||||||
|
while (ccs < bpc) {
|
||||||
|
ocs = ccs;
|
||||||
|
ccs += stp; if (ccs < bpc) ocs = ccs;
|
||||||
|
ccs += stp; stp *= 2;
|
||||||
|
}
|
||||||
|
ocs = Math.floor(ocs / 1024 / 1024);
|
||||||
|
t.stitch_sz = Math.min(ocs, stitch_tgt);
|
||||||
|
}
|
||||||
|
|
||||||
for (var a = 0; a < t.postlist.length; a++) {
|
for (var a = 0; a < t.postlist.length; a++) {
|
||||||
var nparts = [], tbytes = 0, stitch = stitch_tgt;
|
var nparts = [], tbytes = 0, stitch = t.stitch_sz;
|
||||||
if (t.nojoin && t.nojoin - t.postlist.length < 6)
|
if (t.nojoin && t.nojoin - t.postlist.length < 6)
|
||||||
stitch = 1;
|
stitch = 1;
|
||||||
|
|
||||||
--a;
|
--a;
|
||||||
for (var b = 0; b < stitch; b++) {
|
for (var b = 0; b < stitch; b++) {
|
||||||
nparts.push(t.postlist[++a]);
|
nparts.push(t.postlist[++a]);
|
||||||
if (tbytes + chunksize > 64 * 1024 * 1024 || t.postlist[a+1] - t.postlist[a] !== 1)
|
tbytes += chunksize;
|
||||||
|
if (tbytes + chunksize > stitch * 1024 * 1024 || t.postlist[a + 1] - t.postlist[a] !== 1)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
arr.push({
|
arr.push({
|
||||||
@@ -2398,6 +2410,7 @@ function up2k_init(subtle) {
|
|||||||
'nparts': nparts
|
'nparts': nparts
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
t.nojoin = 0;
|
||||||
|
|
||||||
msg = null;
|
msg = null;
|
||||||
done = false;
|
done = false;
|
||||||
@@ -2442,6 +2455,7 @@ function up2k_init(subtle) {
|
|||||||
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
||||||
|
|
||||||
var err = "",
|
var err = "",
|
||||||
|
cls = "ERROR",
|
||||||
rsp = unpre(xhr.responseText),
|
rsp = unpre(xhr.responseText),
|
||||||
ofs = rsp.lastIndexOf('\nURL: ');
|
ofs = rsp.lastIndexOf('\nURL: ');
|
||||||
|
|
||||||
@@ -2471,6 +2485,8 @@ function up2k_init(subtle) {
|
|||||||
if (!t.rechecks && (err_pend || err_srcb)) {
|
if (!t.rechecks && (err_pend || err_srcb)) {
|
||||||
t.rechecks = 0;
|
t.rechecks = 0;
|
||||||
t.want_recheck = true;
|
t.want_recheck = true;
|
||||||
|
err = L.u_dupdefer;
|
||||||
|
cls = 'defer';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rsp.indexOf('server HDD is full') + 1)
|
if (rsp.indexOf('server HDD is full') + 1)
|
||||||
@@ -2480,7 +2496,7 @@ function up2k_init(subtle) {
|
|||||||
if (!t.t_uploading)
|
if (!t.t_uploading)
|
||||||
st.bytes.finished += t.size;
|
st.bytes.finished += t.size;
|
||||||
|
|
||||||
pvis.seth(t.n, 1, "ERROR");
|
pvis.seth(t.n, 1, cls);
|
||||||
pvis.seth(t.n, 2, err);
|
pvis.seth(t.n, 2, err);
|
||||||
pvis.move(t.n, 'ng');
|
pvis.move(t.n, 'ng');
|
||||||
|
|
||||||
@@ -2512,6 +2528,8 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
xhr.open('POST', t.purl, true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
|
xhr.timeout = 42000 + (t.srch || t.t_uploaded ? 0 :
|
||||||
|
(t.size / (1048 * 20))); // safededup 20M/s hdd
|
||||||
xhr.send(JSON.stringify(req));
|
xhr.send(JSON.stringify(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2603,10 +2621,10 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
else if (txt.indexOf('already got that') + 1 ||
|
else if (txt.indexOf('already got that') + 1 ||
|
||||||
txt.indexOf('already being written') + 1) {
|
txt.indexOf('already being written') + 1) {
|
||||||
t.nojoin = t.postlist.length;
|
t.nojoin = t.nojoin || t.postlist.length;
|
||||||
console.log("ignoring dupe-segment with backoff", t.nojoin, t.name, t);
|
console.log("ignoring dupe-segment with backoff", t.nojoin, t.name, t);
|
||||||
if (!toast.visible && st.todo.upload.length < 4)
|
if (!toast.visible && st.todo.upload.length < 4)
|
||||||
toast.msg(10, L.u_cbusy);
|
toast.inf(10, L.u_cbusy);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)", "warn", t);
|
xhrchk(xhr, L.u_cuerr2.format(snpart, Math.ceil(t.size / chunksize), t.name), "404, target folder not found (???)", "warn", t);
|
||||||
@@ -2631,32 +2649,48 @@ function up2k_init(subtle) {
|
|||||||
btot = Math.floor(st.bytes.total / 1024 / 1024);
|
btot = Math.floor(st.bytes.total / 1024 / 1024);
|
||||||
|
|
||||||
xhr.upload.onprogress = function (xev) {
|
xhr.upload.onprogress = function (xev) {
|
||||||
var nb = xev.loaded;
|
var nb = xev.loaded,
|
||||||
st.bytes.inflight += nb - xhr.bsent;
|
db = nb - xhr.bsent;
|
||||||
|
|
||||||
|
if (!db)
|
||||||
|
return;
|
||||||
|
|
||||||
|
st.bytes.inflight += db;
|
||||||
xhr.bsent = nb;
|
xhr.bsent = nb;
|
||||||
|
xhr.timeout = 64000 + Date.now() - xhr.t0;
|
||||||
pvis.prog(t, pcar, nb);
|
pvis.prog(t, pcar, nb);
|
||||||
};
|
};
|
||||||
xhr.onload = function (xev) {
|
xhr.onload = function (xev) {
|
||||||
try { orz(xhr); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
try { orz(xhr); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
|
||||||
};
|
};
|
||||||
xhr.onerror = function (xev) {
|
xhr.onerror = xhr.ontimeout = function (xev) {
|
||||||
if (crashed)
|
if (crashed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
st.bytes.inflight -= (xhr.bsent || 0);
|
st.bytes.inflight -= (xhr.bsent || 0);
|
||||||
|
xhr.bsent = 0;
|
||||||
|
|
||||||
if (!toast.visible)
|
if (!toast.visible)
|
||||||
toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), t.name), t);
|
toast.warn(9.98, L.u_cuerr.format(snpart, Math.ceil(t.size / chunksize), t.name), t);
|
||||||
|
|
||||||
|
t.nojoin = t.nojoin || t.postlist.length; // maybe rproxy postsize limit
|
||||||
console.log('chunkpit onerror,', ++tries, t.name, t);
|
console.log('chunkpit onerror,', ++tries, t.name, t);
|
||||||
orz2(xhr);
|
orz2(xhr);
|
||||||
};
|
};
|
||||||
var chashes = [];
|
|
||||||
for (var a = pcar; a <= pcdr; a++)
|
var chashes = [],
|
||||||
chashes.push(t.hash[a]);
|
ctxt = t.hash[pcar],
|
||||||
|
plen = Math.floor(192 / nparts.length);
|
||||||
|
|
||||||
|
plen = plen > 9 ? 9 : plen < 2 ? 2 : plen;
|
||||||
|
for (var a = pcar + 1; a <= pcdr; a++)
|
||||||
|
chashes.push(t.hash[a].slice(0, plen));
|
||||||
|
|
||||||
|
if (chashes.length)
|
||||||
|
ctxt += ',' + plen + ',' + chashes.join('');
|
||||||
|
|
||||||
xhr.open('POST', t.purl, true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.setRequestHeader("X-Up2k-Hash", chashes.join(","));
|
xhr.setRequestHeader("X-Up2k-Hash", ctxt);
|
||||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||||
xhr.setRequestHeader("X-Up2k-Stat", "{0}/{1}/{2}/{3} {4}/{5} {6}".format(
|
xhr.setRequestHeader("X-Up2k-Stat", "{0}/{1}/{2}/{3} {4}/{5} {6}".format(
|
||||||
pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin,
|
pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin,
|
||||||
@@ -2666,6 +2700,8 @@ function up2k_init(subtle) {
|
|||||||
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
||||||
|
|
||||||
xhr.bsent = 0;
|
xhr.bsent = 0;
|
||||||
|
xhr.t0 = Date.now();
|
||||||
|
xhr.timeout = 42000;
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.send(t.fobj.slice(car, cdr));
|
xhr.send(t.fobj.slice(car, cdr));
|
||||||
}
|
}
|
||||||
@@ -2766,15 +2802,17 @@ function up2k_init(subtle) {
|
|||||||
if (parallel_uploads > 16)
|
if (parallel_uploads > 16)
|
||||||
parallel_uploads = 16;
|
parallel_uploads = 16;
|
||||||
|
|
||||||
if (parallel_uploads > 7)
|
if (parallel_uploads > 6)
|
||||||
toast.warn(10, L.u_maxconn);
|
toast.warn(10, L.u_maxconn);
|
||||||
|
else if (toast.txt == L.u_maxconn)
|
||||||
|
toast.hide();
|
||||||
|
|
||||||
obj.value = parallel_uploads;
|
obj.value = parallel_uploads;
|
||||||
bumpthread({ "target": 1 });
|
bumpthread({ "target": 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
var read_u2sz = function () {
|
var read_u2sz = function () {
|
||||||
var el = ebi('u2szg'), n = parseInt(el.value), dv = u2sz.split(',');
|
var el = ebi('u2szg'), n = parseInt(el.value), dv = u2sz.split(',');
|
||||||
stitch_tgt = n = (
|
stitch_tgt = n = (
|
||||||
isNaN(n) ? dv[1] :
|
isNaN(n) ? dv[1] :
|
||||||
n < dv[0] ? dv[0] :
|
n < dv[0] ? dv[0] :
|
||||||
|
|||||||
@@ -127,13 +127,13 @@ if ((document.location + '').indexOf(',rej,') + 1)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.hist = [];
|
console.hist = [];
|
||||||
var CMAXHIST = 1000;
|
var CMAXHIST = MOBILE ? 9000 : 44000;
|
||||||
var hook = function (t) {
|
var hook = function (t) {
|
||||||
var orig = console[t].bind(console),
|
var orig = console[t].bind(console),
|
||||||
cfun = function () {
|
cfun = function () {
|
||||||
console.hist.push(Date.now() + ' ' + t + ': ' + Array.from(arguments).join(', '));
|
console.hist.push(Date.now() + ' ' + t + ': ' + Array.from(arguments).join(', '));
|
||||||
if (console.hist.length > CMAXHIST)
|
if (console.hist.length > CMAXHIST)
|
||||||
console.hist = console.hist.slice(CMAXHIST / 2);
|
console.hist = console.hist.slice(CMAXHIST / 4);
|
||||||
|
|
||||||
orig.apply(console, arguments);
|
orig.apply(console, arguments);
|
||||||
};
|
};
|
||||||
@@ -473,6 +473,24 @@ function crc32(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function randstr(len) {
|
||||||
|
var ret = '';
|
||||||
|
try {
|
||||||
|
var ar = new Uint32Array(Math.floor((len + 3) / 4));
|
||||||
|
crypto.getRandomValues(ar);
|
||||||
|
for (var a = 0; a < ar.length; a++)
|
||||||
|
ret += ('000' + ar[a].toString(36)).slice(-4);
|
||||||
|
return ret.slice(0, len);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log('using unsafe randstr because ' + ex);
|
||||||
|
while (ret.length < len)
|
||||||
|
ret += ('000' + Math.floor(Math.random() * 1679616).toString(36)).slice(-4);
|
||||||
|
return ret.slice(0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function clmod(el, cls, add) {
|
function clmod(el, cls, add) {
|
||||||
if (!el)
|
if (!el)
|
||||||
return false;
|
return false;
|
||||||
@@ -1536,6 +1554,7 @@ var modal = (function () {
|
|||||||
var r = {},
|
var r = {},
|
||||||
q = [],
|
q = [],
|
||||||
o = null,
|
o = null,
|
||||||
|
scrolling = null,
|
||||||
cb_up = null,
|
cb_up = null,
|
||||||
cb_ok = null,
|
cb_ok = null,
|
||||||
cb_ng = null,
|
cb_ng = null,
|
||||||
@@ -1556,6 +1575,7 @@ var modal = (function () {
|
|||||||
r.nofocus = 0;
|
r.nofocus = 0;
|
||||||
|
|
||||||
r.show = function (html) {
|
r.show = function (html) {
|
||||||
|
tt.hide();
|
||||||
o = mknod('div', 'modal');
|
o = mknod('div', 'modal');
|
||||||
o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
|
o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
|
||||||
document.body.appendChild(o);
|
document.body.appendChild(o);
|
||||||
@@ -1579,6 +1599,7 @@ var modal = (function () {
|
|||||||
|
|
||||||
document.addEventListener('focus', onfocus);
|
document.addEventListener('focus', onfocus);
|
||||||
document.addEventListener('selectionchange', onselch);
|
document.addEventListener('selectionchange', onselch);
|
||||||
|
timer.add(scrollchk, 1);
|
||||||
timer.add(onfocus);
|
timer.add(onfocus);
|
||||||
if (cb_up)
|
if (cb_up)
|
||||||
setTimeout(cb_up, 1);
|
setTimeout(cb_up, 1);
|
||||||
@@ -1586,6 +1607,8 @@ var modal = (function () {
|
|||||||
|
|
||||||
r.hide = function () {
|
r.hide = function () {
|
||||||
timer.rm(onfocus);
|
timer.rm(onfocus);
|
||||||
|
timer.rm(scrollchk);
|
||||||
|
scrolling = null;
|
||||||
try {
|
try {
|
||||||
ebi('modal-ok').removeEventListener('blur', onblur);
|
ebi('modal-ok').removeEventListener('blur', onblur);
|
||||||
}
|
}
|
||||||
@@ -1604,13 +1627,28 @@ var modal = (function () {
|
|||||||
r.hide();
|
r.hide();
|
||||||
if (cb_ok)
|
if (cb_ok)
|
||||||
cb_ok(v);
|
cb_ok(v);
|
||||||
}
|
};
|
||||||
var ng = function (e) {
|
var ng = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
r.hide();
|
r.hide();
|
||||||
if (cb_ng)
|
if (cb_ng)
|
||||||
cb_ng(null);
|
cb_ng(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
var scrollchk = function () {
|
||||||
|
if (scrolling === true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var o = ebi('modalc'),
|
||||||
|
vis = o.offsetHeight,
|
||||||
|
all = o.scrollHeight,
|
||||||
|
nsc = 8 + vis < all;
|
||||||
|
|
||||||
|
if (scrolling !== nsc)
|
||||||
|
clmod(o, 'yk', !nsc);
|
||||||
|
|
||||||
|
scrolling = nsc;
|
||||||
|
};
|
||||||
|
|
||||||
var onselch = function () {
|
var onselch = function () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,3 +1,256 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0902-0108 `v1.14.4` another
|
||||||
|
|
||||||
|
## recent important news
|
||||||
|
|
||||||
|
* [v1.14.3 (2024-08-30)](https://github.com/9001/copyparty/releases/tag/v1.14.3) fixed a bug that was introduced in v1.13.8 (2024-08-13); this bug could lead to **data loss** -- see the v1.14.3 release-notes for details
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* a network glitch could cause the uploader UI to panic d9e95262
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0830-2311 `v1.14.3` important dedup fix
|
||||||
|
|
||||||
|
<img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="250" align="right"/>
|
||||||
|
|
||||||
|
* read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
* [docker image](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) ╱ [similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) ╱ [client testbed](https://cd.ocv.me/b/)
|
||||||
|
|
||||||
|
there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` in case of future important updates, such as [vulnerabilities](https://github.com/9001/copyparty/security) (most recently 2023-07-23)
|
||||||
|
|
||||||
|
# important bugfix ☢️
|
||||||
|
|
||||||
|
this version fixes a file deduplication bug which was introduced in [v1.13.8](https://github.com/9001/copyparty/releases/tag/v1.13.8), released 2024-08-13
|
||||||
|
|
||||||
|
its worst-case outcome is **loss of data** in the following scenario:
|
||||||
|
* someone uploads a file into a folder where that filename is already taken, but the file contents are different, and the server already has a copy of that new file elsewhere under a different name
|
||||||
|
|
||||||
|
specific example:
|
||||||
|
* the server has two existing files, `logo.png` and `logo-v2.png`, in the same volume but not necessarily in the same folder, and those files contain different data
|
||||||
|
* you have a local copy of `logo-v2.png` on your laptop, but your local filename is `logo.png`
|
||||||
|
* you upload your local `logo.png` onto the server, into the same folder as the server's `logo.png`
|
||||||
|
* because the files contain different data, the server accidentally replaces the contents of `logo.png` with your version
|
||||||
|
|
||||||
|
if you have been using the database feature (globally with `-e2dsa` or volflag `e2ds`), and you suspect you may have hit this bug, then it is a good idea to make a backup of the up2k databases for all your volumes (the files with names starting with `up2k.db`) before restarting copyparty and before you do anything else, especially if you do not have serverlogs from far back in time -- if you have either the databases and/or the serverlogs, then it is possible to identify replaced files with some manual work
|
||||||
|
|
||||||
|
you can check if you hit the bug using one of the following two approaches:
|
||||||
|
* if your OS has the [gnu find](https://linux.die.net/man/1/find) command, do a search for empty files with `find -type f -size 0`
|
||||||
|
* using copyparty (any OS), do the following steps:
|
||||||
|
* make sure that reindex-on-startup is enabled; either globally with `-e2dsa` or volflag `e2ds`
|
||||||
|
* then install this new copyparty version
|
||||||
|
* click the search tab `[🔎]` and type the number `0` into the `maximum MiB` textbox
|
||||||
|
|
||||||
|
if you find any empty files with a filename that indicates it was autogenerated to avoid a name collision, for example `logo.png-1725040569.239207-kbt0xteO.png`, and the value of the number after `logo.png` is larger than `1723507200` (unixtime for 2024-08-13), then this indicates that `logo.png` may have been replaced by another upload
|
||||||
|
|
||||||
|
if you have the serverlogs from when the original upload of `logo.png` was made, then this can be used to identify the original contents of the file that was replaced, and to look for other copies. Please get in touch on the discord for assistance if necessary
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* shares: add revival and expiration extension ad2371f8
|
||||||
|
* share-owners can revive expired shares for `--shr-rt` minutes (default 1 day)
|
||||||
|
* ...and extend expiration time by adding 1 minute or 1 hour to the timer
|
||||||
|
* [sfx customizer](https://github.com/9001/copyparty/blob/hovudstraum/scripts/make-sfx.sh) improvements 03b13e8a
|
||||||
|
* improved translations stripper
|
||||||
|
* add more examples
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* the dedup bug 3da62ec2
|
||||||
|
* tftp: support unmapped root 01233991
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* copyparty.exe: update to pyinstaller 6.10.0
|
||||||
|
* textviewer wordwrapping c4e2b0f9
|
||||||
|
* add logo 7037e736 ee359742
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0823-2307 `v1.14.2` bing chilling
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* #94 @ultwcz translated the UI to Chinese (thx!) 92edea1d
|
||||||
|
* #84 improvements to [shares](https://github.com/9001/copyparty#shares): 8122dded
|
||||||
|
* if one or more files are selected for sharing, they are placed into a virtual folder
|
||||||
|
* more appropriate password UI for accessing protected shares
|
||||||
|
* human-readable timestamps in shares listing
|
||||||
|
* u2c (commandline uploader): support multiple exclusion patterns f356faa2
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* remove confusing logmessage when downloading a zerobyte file 9f034d9c
|
||||||
|
* shares: 7ff46966
|
||||||
|
* fix crash if the root volume is unmapped
|
||||||
|
* log-spam on config reload
|
||||||
|
* password coalescing
|
||||||
|
* add chrome support
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* #93 add html IDs to the tabstrip 461f3158
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0819-0014 `v1.14.1` one step forward
|
||||||
|
|
||||||
|
[if i turn back now, then this will always follow... one step forward, forward](https://youtu.be/xe3Wkzc0O3k?t=27)
|
||||||
|
|
||||||
|
* read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
* [docker image](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) ╱ [similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) ╱ [client testbed](https://cd.ocv.me/b/)
|
||||||
|
|
||||||
|
there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` in case of future important updates, such as [vulnerabilities](https://github.com/9001/copyparty/security) (most recently 2023-07-23)
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* #92 users can change their own passwords 83fb569d 00da7440
|
||||||
|
* this feature is default-disabled; see [readme](https://github.com/9001/copyparty#user-changeable-passwords)
|
||||||
|
* #84 share files/folders by creating a temporary url 7c2beba5
|
||||||
|
* inspired by other file servers; click the share-button to create a link like `example.com/share/enkz8g374o8g`
|
||||||
|
* primary usecase is to sneak past authentication services (see issue description)
|
||||||
|
* the create-share UI has options to accept uploads into the share, and/or set expiration time
|
||||||
|
* this feature is default-disabled; see [readme](https://github.com/9001/copyparty#shares)
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* #93 fixes for vproxy / location-based / not-vhost-based reverse-proxying 0b46b1a6
|
||||||
|
* using `--rp-loc` to reverse-proxy from a subfolder made some UI stuff break
|
||||||
|
* listening on unix-sockets: 687df2fa
|
||||||
|
* fix `x-forwarded-for` support, and avoid a possible container-specific collision
|
||||||
|
* new syntax which allows setting unix-permissions and unix-group
|
||||||
|
* `-i unix:770:www:/tmp/party.sock` (see `--help-bind` for more examples)
|
||||||
|
* using relocation hooks (introduced in previous ver) could cause dedup issues c8f4aeae b0af4b37
|
||||||
|
* custom fonts using `@import` css statements 5a62cb48
|
||||||
|
* invert volume scrollwheel 7d8d9438
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* changed the button colors in theme 2 (pm-monokai) from red to yellow 5153db6b
|
||||||
|
* the red buttons look better, but are too confusing because usually red means off
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0813-0008 `v1.13.8` hook into place
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* #86 intentional side-effects from hooks 6c94a63f
|
||||||
|
* use hooks (plugins) to conditionally move uploads into another folder depending on filename, extension, uploader ip/name, file contents, ...
|
||||||
|
* hooks can create additional files and tell copyparty to index them immediately, or delete an existing file based on some condition
|
||||||
|
* only one example so far though, [reloc-by-ext](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks#before-upload) which was a feature-request to dodge [sharex#3992](https://github.com/ShareX/ShareX/issues/3992)
|
||||||
|
* listen on unix-sockets ee9aad82
|
||||||
|
* `-i unix:/tmp/party.sock` stops listening on TCP ports entirely, and only listens on that unix-socket
|
||||||
|
* can be combined with regular sockets, `-i 127.0.0.1,unix:/tmp/a.sock`
|
||||||
|
* kinda buggy for now (need to `--xff-src=any` and doesn't let you set socket-perms yet), will be fixed in next ver
|
||||||
|
* makes it 10% faster, but more importantly offers tighter access control behind reverse-proxies
|
||||||
|
* inspired by https://www.oligo.security/blog/0-0-0-0-day-exploiting-localhost-apis-from-the-browser
|
||||||
|
* up2k stitching:
|
||||||
|
* more optimal stitch sizes for max throughput across connections c862ec1b
|
||||||
|
* improve fat32 compatibility 373194c3
|
||||||
|
* new option `--js-other` to load custom javascript dbd42bc6
|
||||||
|
* `--js-browser` affects the filebrowser page, `--js-other` does all the others
|
||||||
|
* endless possibilities, such as [adding a login-banner](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/banner.js) which [looks like this](https://github.com/user-attachments/assets/8ae8e087-b209-449c-b08d-74e040f0284b)
|
||||||
|
* list detected optional dependencies on startup 3db117d8
|
||||||
|
* hopefully reduces the guesswork / jank factor by a tiny bit
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* up2k stitching:
|
||||||
|
* put the request headers on a diet so they fit through more reverse-proxies 0da719f4
|
||||||
|
* fix deadlock on s390x (IBM mainframes) 250c8c56
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* add flags to disengage [features](https://github.com/9001/copyparty/tree/hovudstraum#feature-chickenbits) and [dependencies](https://github.com/9001/copyparty/tree/hovudstraum#dependency-chickenbits) in case they cause trouble 72361c99
|
||||||
|
* optimizations
|
||||||
|
* 6% faster on average d5c9c8eb
|
||||||
|
* docker: reduce ram usage 98ffaadf
|
||||||
|
* python2: reduce ram usage ebb19818
|
||||||
|
* docker: add [portainer howto](https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/portainer.md) e136231c
|
||||||
|
* update deps ca001c85
|
||||||
|
* pyftpdlib 1.5.10
|
||||||
|
* copyparty.exe: python 3.12.5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0729-2028 `v1.13.6` not that big
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* up2k.js: set clientside timeouts on http connections during upload 85e54980
|
||||||
|
* some reverse-proxy setups could cause uploads to hang indefinitely by eating requests; should recover nicely now
|
||||||
|
* audio-player shows statustext while loading 662541c6
|
||||||
|
* [bsod theme](https://github.com/9001/copyparty/tree/hovudstraum/contrib/themes) [(live demo)](https://cd.ocv.me/c/) 15ddcf53
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* fix bugs in the [long-distance upload optimizations](https://github.com/9001/copyparty/releases/tag/v1.13.5) in the previous version:
|
||||||
|
* up2k.js didn't necessarily use the expected chunksize when stitching 225bd80e
|
||||||
|
* u2c (commandline uploader): 8916bce3
|
||||||
|
* use the correct chunksize instead of overshooting like crazy
|
||||||
|
* could crash on exit if `-z` was enabled (so basically harmless)
|
||||||
|
* the "time spent uploading" statustext that was printed on exit could multiply by `-j` and exceed walltime
|
||||||
|
* misc ux 9bb6e0dc
|
||||||
|
* don't accept hotkeys until it's safe to do so
|
||||||
|
* improve messages regarding the [firefox crash](https://bugzilla.mozilla.org/show_bug.cgi?id=1790500)
|
||||||
|
* keep more console logs in memory (easier to debug)
|
||||||
|
* fix wordwrap in messageboxes on firefox a19a0fa9
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* changed the `xm` / "on message" [hook examples](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks#on-message) to reject users without write-access 99edba4f
|
||||||
|
* docker images were rebuilt on 2024-08-02, 23:30 UTC with new optimizations: 98ffaadf
|
||||||
|
* 😃 RAM usage decreased by `5-6 MiB` for most flavors; `10 MiB` for dj/iv
|
||||||
|
* 😕 image size grew by `4 MiB` (min), `6 MiB` (ac/im/iv), `9 MiB` (dj)
|
||||||
|
* 😃 startup time reduced to about half
|
||||||
|
* and avoids a deadlock on IBM mainframes
|
||||||
|
* updated comparison to other software 6b54972e
|
||||||
|
* `hfs2` is dead, `hfs3` and `filebrowser` improved
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2024-0722-2323 `v1.13.5` american sized
|
||||||
|
|
||||||
|
## new features
|
||||||
|
|
||||||
|
* long-distance uploads are now **twice as fast** on average 132a8350
|
||||||
|
* boost tcp windowsize scaling by stitching together smaller chunks into bigger chonks so they fly better across the atlantic
|
||||||
|
* i'm not kidding, on the two routes we've tested this on we gained 1.6x / 160% (from US-West to Finland) and **2.6x / 260%** (Norway to US-East)
|
||||||
|
* files that are between 4 MiB and 256 MiB see the biggest improvement; 70% faster <= 768 MiB, 40% <= 1.5 GiB, 10% <= 6G
|
||||||
|
* if this turns out to be buggy, disable it serverside with `--u2sz 1,1,1` or clientside in the browser-ui: `[⚙️]` -> `up2k switches` -> change `64` to `1`
|
||||||
|
* u2c.py (CLI uploader): support stitching (☝️) + print a summary with hashing and upload speeds 987bce21
|
||||||
|
* video files can play as audio 53f1e3c9
|
||||||
|
* audio is extracted serverside to avoid wasting bandwidth
|
||||||
|
* extraction is lossy (converted to opus or mp3 depending on browser)
|
||||||
|
* togglebutton `🎧` in the gridview toolbar to enable/disable
|
||||||
|
* new hook: [into-the-cache-it-goes.py](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks#after-upload) d26a944d
|
||||||
|
* avoids a cloudflare bug (race condition?) where it will send truncated files to visitors on the very first load if several people simultaneously access a file that hasn't been viewed before
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
|
||||||
|
* inline markdown/logues rendered black-on-black in firefox 54 and some other browsers from 2017 and older eeef8091
|
||||||
|
* unintuitive folder thumbnail selection if folder contains both `Cover.jpg` and `cover.jpg` f955d2bd
|
||||||
|
* the gridview toolbar got undocked after viewing a pic/vid dc449bf8
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
|
||||||
|
* #90 recommend rclone in favor of davfs2 ef0ecf87
|
||||||
|
* improved some error messages e565ad5f
|
||||||
|
* added helptext exporters to generate the online [html](https://ocv.me/copyparty/helptext.html) and [txt](https://ocv.me/copyparty/helptext.txt) editions 59533990
|
||||||
|
* mention that cloudflare is incompatible with uploading files larger than 383.9 GiB
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2024-0716-0457 `v1.13.4` descript.ion
|
# 2024-0716-0457 `v1.13.4` descript.ion
|
||||||
|
|
||||||
@@ -677,7 +930,7 @@ dnf install https://ocv.me/copyparty/fedora/39/python3-copyparty.fc39.noarch.rpm
|
|||||||
## other changes
|
## other changes
|
||||||
* improved [systemd example](https://github.com/9001/copyparty/tree/hovudstraum/contrib/systemd) with hardening and a better example config
|
* improved [systemd example](https://github.com/9001/copyparty/tree/hovudstraum/contrib/systemd) with hardening and a better example config
|
||||||
* logfiles are flushed for every line written; can be disabled with `--no-logflush` for ~3% more performance best-case
|
* logfiles are flushed for every line written; can be disabled with `--no-logflush` for ~3% more performance best-case
|
||||||
* iphones probably won't broadcast cover-art to car stereos over bluetooth anymore since the thingamajig in iOS that's in charge of that doesn't have cookie-access, and strapping in the auth is too funky so let's stop doing that b7723ac245b8b3e38d6410891ef1aa92d4772114
|
* iphones probably won't broadcast cover-art to car stereos over bluetooth anymore since the thingamajig in iOS that's in charge of that doesn't have cookie-access, and strapping in the auth is too funky so let's stop doing that b7723ac2
|
||||||
* can be remedied by enabling filekeys and granting unauthenticated people access that way, but that's too much effort for anyone to bother with I'm sure
|
* can be remedied by enabling filekeys and granting unauthenticated people access that way, but that's too much effort for anyone to bother with I'm sure
|
||||||
|
|
||||||
|
|
||||||
@@ -946,12 +1199,12 @@ okay, i swear this is the last version for weeks! probably
|
|||||||
* [r0c is much better](https://github.com/9001/r0c) than this joke
|
* [r0c is much better](https://github.com/9001/r0c) than this joke
|
||||||
|
|
||||||
## bugfixes
|
## bugfixes
|
||||||
* 163e3fce46122d64bf824762b6733ff2c3551ba5 the `x-forwarded-for` header was ignored if the nearest reverse-proxy is not asking from 127.0.0.1, which broke client IPs in containerized deployments
|
* 163e3fce the `x-forwarded-for` header was ignored if the nearest reverse-proxy is not asking from 127.0.0.1, which broke client IPs in containerized deployments
|
||||||
* the serverlog will now explain how to trust the reverse-proxy to provide client IPs, but basically,
|
* the serverlog will now explain how to trust the reverse-proxy to provide client IPs, but basically,
|
||||||
* `--xff-hdr` specifies which header to read the client's real ip from
|
* `--xff-hdr` specifies which header to read the client's real ip from
|
||||||
* `--xff-src` is an allowlist of IP-addresses to trust that header from
|
* `--xff-src` is an allowlist of IP-addresses to trust that header from
|
||||||
* a62f744a187bc9f821b540e8bb4e0b9a67bd01c8 if copyparty was started while an external HDD was not connected, and that volume's index was stored elsewhere, then the index would get wiped (since all the files are gone)
|
* a62f744a if copyparty was started while an external HDD was not connected, and that volume's index was stored elsewhere, then the index would get wiped (since all the files are gone)
|
||||||
* 3b8f66c0d5c27a68841814ec06f1758f146a5ff5 javascript could crash while uploading from a very unreliable internet connection
|
* 3b8f66c0 javascript could crash while uploading from a very unreliable internet connection
|
||||||
|
|
||||||
## other changes
|
## other changes
|
||||||
* copyparty.exe: updated pillow to 10.0.1 which fixes the webp cve
|
* copyparty.exe: updated pillow to 10.0.1 which fixes the webp cve
|
||||||
@@ -997,7 +1250,7 @@ hello! it's been a while, an entire day even...
|
|||||||
* default compression levels are gz:3, bz2:2, xz:1; override with `?tar=gz:9`
|
* default compression levels are gz:3, bz2:2, xz:1; override with `?tar=gz:9`
|
||||||
|
|
||||||
# bugfixes
|
# bugfixes
|
||||||
* c1efd227b7377144a5760bc6cff64f4e87b626d9 symlink-deduplicated files got indexed with the wrong last-modified timestamp
|
* c1efd227 symlink-deduplicated files got indexed with the wrong last-modified timestamp
|
||||||
* mostly inconsequential; would cause the dupe's uploader-ip to be forgotten on the next server restart since it would reindex to "fix" the timestamp
|
* mostly inconsequential; would cause the dupe's uploader-ip to be forgotten on the next server restart since it would reindex to "fix" the timestamp
|
||||||
* when linking [a search query](https://a.ocv.me/pub/#q=tags%20like%20soundsho*) it loads the results faster
|
* when linking [a search query](https://a.ocv.me/pub/#q=tags%20like%20soundsho*) it loads the results faster
|
||||||
|
|
||||||
@@ -1012,12 +1265,12 @@ hello! it's been a while, an entire day even...
|
|||||||
|
|
||||||
## new features
|
## new features
|
||||||
* iPhones and iPads are now able to...
|
* iPhones and iPads are now able to...
|
||||||
* 9986136dfb2364edb35aa9fbb87410641c6d6af3 play entire albums while the screen is off without the music randomly stopping
|
* 9986136d play entire albums while the screen is off without the music randomly stopping
|
||||||
* apple keeps breaking AudioContext in new and interesting ways; time to give up (no more equalizer)
|
* apple keeps breaking AudioContext in new and interesting ways; time to give up (no more equalizer)
|
||||||
* 1c0d978979a703edeb792e552b18d3b7695b2d90 perform search queries and execude js code
|
* 1c0d9789 perform search queries and execude js code
|
||||||
* by translating [smart-quotes](https://stackoverflow.com/questions/48678359/ios-11-safari-html-disable-smart-punctuation) into regular `'` and `"` characters
|
* by translating [smart-quotes](https://stackoverflow.com/questions/48678359/ios-11-safari-html-disable-smart-punctuation) into regular `'` and `"` characters
|
||||||
* python 3.12 support
|
* python 3.12 support
|
||||||
* technically a bugfix since it was added [a year ago](https://github.com/9001/copyparty/commit/32e22dfe84d5e0b13914b4d0e15c1b8c9725a76d) way before the first py3.12 alpha was released but turns out i botched it, oh well
|
* technically a bugfix since it was added [a year ago](https://github.com/9001/copyparty/commit/32e22dfe) way before the first py3.12 alpha was released but turns out i botched it, oh well
|
||||||
* filter error messages so they never include the filesystem path where copyparty's python files reside
|
* filter error messages so they never include the filesystem path where copyparty's python files reside
|
||||||
* print more context in server logs if someone hits an unexpected permission-denied
|
* print more context in server logs if someone hits an unexpected permission-denied
|
||||||
|
|
||||||
@@ -1238,8 +1491,8 @@ Thanks for flying copyparty! And especially if you decide to continue doing so :
|
|||||||
```bash
|
```bash
|
||||||
(gzip -dc access.log.*.gz; cat access.log) | sed -r 's/" [0-9]+ .*//' | grep -E 'cpr/.*%2[^0]' | grep -vF data:image/svg
|
(gzip -dc access.log.*.gz; cat access.log) | sed -r 's/" [0-9]+ .*//' | grep -E 'cpr/.*%2[^0]' | grep -vF data:image/svg
|
||||||
```
|
```
|
||||||
* 77f1e5144455eb946db7368792ea11c934f0f6da fixes an extremely unlikely race-condition (see the commit for details)
|
* 77f1e514 fixes an extremely unlikely race-condition (see the commit for details)
|
||||||
* 8f59afb1593a75b8ce8c91ceee304097a07aea6e fixes another race-condition which is a bit worse:
|
* 8f59afb1 fixes another race-condition which is a bit worse:
|
||||||
* the unpost feature could collide with other database activity, with the worst-case outcome being aborted batch operations, for example a directory move or a batch-rename which stops halfways
|
* the unpost feature could collide with other database activity, with the worst-case outcome being aborted batch operations, for example a directory move or a batch-rename which stops halfways
|
||||||
|
|
||||||
----
|
----
|
||||||
@@ -1413,7 +1666,7 @@ don't get excited! nothing new and revolutionary, but `xvol` and `xdev` changed
|
|||||||
# 2023-0426-2300 `v1.6.15` unexpected boost
|
# 2023-0426-2300 `v1.6.15` unexpected boost
|
||||||
|
|
||||||
## new features
|
## new features
|
||||||
* 30% faster folder listings due to [the very last thing](https://github.com/9001/copyparty/commit/55c74ad164633a0a64dceb51f7f534da0422cbb5) i'd ever expect to be a bottleneck, [thx perf](https://docs.python.org/3.12/howto/perf_profiling.html)
|
* 30% faster folder listings due to [the very last thing](https://github.com/9001/copyparty/commit/55c74ad1) i'd ever expect to be a bottleneck, [thx perf](https://docs.python.org/3.12/howto/perf_profiling.html)
|
||||||
* option to see the lastmod timestamps of symlinks instead of the target files
|
* option to see the lastmod timestamps of symlinks instead of the target files
|
||||||
* makes the turbo mode of [u2cli, the commandline uploader and folder-sync tool](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) more turbo since copyparty dedupes uploads by symlinking to an existing copy and the symlink is stamped with the deduped file's lastmod
|
* makes the turbo mode of [u2cli, the commandline uploader and folder-sync tool](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) more turbo since copyparty dedupes uploads by symlinking to an existing copy and the symlink is stamped with the deduped file's lastmod
|
||||||
* **webdav:** enabled by default (because rclone will want this), can be disabled with arg `--dav-rt` or volflag `davrt`
|
* **webdav:** enabled by default (because rclone will want this), can be disabled with arg `--dav-rt` or volflag `davrt`
|
||||||
@@ -1617,7 +1870,7 @@ don't get excited! nothing new and revolutionary, but `xvol` and `xdev` changed
|
|||||||
the commandline up2k upload / filesearch client, now as a standalone windows exe
|
the commandline up2k upload / filesearch client, now as a standalone windows exe
|
||||||
* based on python 3.7 so it runs on 32bit windows7 or anything newer
|
* based on python 3.7 so it runs on 32bit windows7 or anything newer
|
||||||
* *no https support* (saves space + the python3.7 openssl is getting old)
|
* *no https support* (saves space + the python3.7 openssl is getting old)
|
||||||
* built from b39ff92f34e3fca389c78109d20d5454af761f8e so it can do long filepaths and mojibake
|
* built from b39ff92f so it can do long filepaths and mojibake
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -1824,7 +2077,7 @@ but nothing is affected (that i know of):
|
|||||||
* tar/zip-download of hidden folders
|
* tar/zip-download of hidden folders
|
||||||
* unpost filtering was buggy for non-ascii characters
|
* unpost filtering was buggy for non-ascii characters
|
||||||
* moving a deduplicated file on a volume where deduplication was since disabled
|
* moving a deduplicated file on a volume where deduplication was since disabled
|
||||||
* improved the [linux 6.0.16](https://utcc.utoronto.ca/~cks/space/blog/linux/KernelBindBugIn6016) kernel bug [workaround](https://github.com/9001/copyparty/commit/9065226c3d634a9fc15b14a768116158bc1761ad) because there is similar funk in 5.x
|
* improved the [linux 6.0.16](https://utcc.utoronto.ca/~cks/space/blog/linux/KernelBindBugIn6016) kernel bug [workaround](https://github.com/9001/copyparty/commit/9065226c) because there is similar funk in 5.x
|
||||||
* add custom text selection colors because chrome is currently broken on fedora
|
* add custom text selection colors because chrome is currently broken on fedora
|
||||||
* blockdevs (`/dev/nvme0n1`) couldn't be downloaded as files
|
* blockdevs (`/dev/nvme0n1`) couldn't be downloaded as files
|
||||||
* misc fixes for location-based reverse-proxying
|
* misc fixes for location-based reverse-proxying
|
||||||
@@ -1853,7 +2106,7 @@ hello from warsaw airport (goodbye japan ;_;)
|
|||||||
* browser ui didn't allow specifying number of threads for file search
|
* browser ui didn't allow specifying number of threads for file search
|
||||||
* dont panic if a digit key is pressed while viewing an image
|
* dont panic if a digit key is pressed while viewing an image
|
||||||
* workaround [linux kernel bug](https://utcc.utoronto.ca/~cks/space/blog/linux/KernelBindBugIn6016) causing log spam on dualstack
|
* workaround [linux kernel bug](https://utcc.utoronto.ca/~cks/space/blog/linux/KernelBindBugIn6016) causing log spam on dualstack
|
||||||
* ~~related issue (also mostly harmless) will be fixed next relese 010770684db95bece206943768621f2c7c27bace~~
|
* ~~related issue (also mostly harmless) will be fixed next relese 01077068~~
|
||||||
* they fixed it in linux 6.1 so these workarounds will be gone too
|
* they fixed it in linux 6.1 so these workarounds will be gone too
|
||||||
|
|
||||||
|
|
||||||
@@ -2819,7 +3072,7 @@ fixed another dumdum, sorry for the spam
|
|||||||
* the ftp server is not compatible with python 3.12 (releasing october 2023)
|
* the ftp server is not compatible with python 3.12 (releasing october 2023)
|
||||||
* will be fixed in a [future version of pyftpdlib](https://github.com/giampaolo/pyftpdlib/issues/560)
|
* will be fixed in a [future version of pyftpdlib](https://github.com/giampaolo/pyftpdlib/issues/560)
|
||||||
|
|
||||||
the sfx was built from https://github.com/9001/copyparty/commit/39e7a7a2311ab8da43b2a9a18ae39d06202105e3
|
the sfx was built from https://github.com/9001/copyparty/commit/39e7a7a2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3600,7 +3853,7 @@ we did it reddit 👉😎👉
|
|||||||
* latest gzip edition of the sfx: [v0.11.18](https://github.com/9001/copyparty/releases/tag/v0.11.18)
|
* latest gzip edition of the sfx: [v0.11.18](https://github.com/9001/copyparty/releases/tag/v0.11.18)
|
||||||
* if upgrading from v0.11.x or before, see [v0.12.4](https://github.com/9001/copyparty/releases/tag/v0.12.4)
|
* if upgrading from v0.11.x or before, see [v0.12.4](https://github.com/9001/copyparty/releases/tag/v0.12.4)
|
||||||
|
|
||||||
note: `copyparty-sfx.py` is https://github.com/9001/copyparty/commit/5955940b82adddb7149125a60463aba22f1c8c31 which fixes upload eta
|
note: `copyparty-sfx.py` is https://github.com/9001/copyparty/commit/5955940b which fixes upload eta
|
||||||
|
|
||||||
## new features
|
## new features
|
||||||
* provide password using basic-authentication
|
* provide password using basic-authentication
|
||||||
@@ -5070,7 +5323,7 @@ nothing really important happened since [v0.11.6](https://github.com/9001/copypa
|
|||||||
* this release fixes a missing permission check which could allow users to download write-only folders
|
* this release fixes a missing permission check which could allow users to download write-only folders
|
||||||
* this bug was introduced 19 days ago, in `v0.10.17`
|
* this bug was introduced 19 days ago, in `v0.10.17`
|
||||||
* the requirement to be affected is write-only folders mounted within readable folders
|
* the requirement to be affected is write-only folders mounted within readable folders
|
||||||
* and the worst part is there was a unit-test exactly for this, https://github.com/9001/copyparty/commit/273ca0c8da0d94f9d06ca16bd86c0301d9d06455 way overdue
|
* and the worst part is there was a unit-test exactly for this, https://github.com/9001/copyparty/commit/273ca0c8 way overdue
|
||||||
* also fixes minor bugs introduced in `v0.11.1`
|
* also fixes minor bugs introduced in `v0.11.1`
|
||||||
* this version is the same as `v0.11.5` on pypi
|
* this version is the same as `v0.11.5` on pypi
|
||||||
|
|
||||||
@@ -5254,8 +5507,8 @@ in other news, minor ui tweaks:
|
|||||||
* a few lightmode adjustments
|
* a few lightmode adjustments
|
||||||
* less cpu usage? should be
|
* less cpu usage? should be
|
||||||
|
|
||||||
`copyparty-sfx.py` (latest) made from c5db7c1a0c8f6ab23138ad7ea7642a6260e7da9b (v0.10.15-15) fixes `-j` (multiprocessing/high-performance)
|
`copyparty-sfx.py` (latest) made from c5db7c1a (v0.10.15-15) fixes `-j` (multiprocessing/high-performance)
|
||||||
`copyparty-sfx-5a579db.py` (old) made from 5a579dba52e46c202b79c3d80c3b1c996c7b2e4a (v0.10.15-5) reduced the size
|
`copyparty-sfx-5a579db.py` (old) made from 5a579dba (v0.10.15-5) reduced the size
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -5568,7 +5821,7 @@ and i just realized i never added runtime tag scanning so copyparty will have to
|
|||||||
|
|
||||||
use `-e2dsa` and `-e2ts` to enable the media tag features globally, or enable/disable them per-volume (see readme)
|
use `-e2dsa` and `-e2ts` to enable the media tag features globally, or enable/disable them per-volume (see readme)
|
||||||
|
|
||||||
**NOTE:** older fuse clients (from before 5e3775c1afc9438f9930080a9b8542a063ba1765 / older than v0.8.0) must be upgraded for this copyparty release, however the new client still supports connecting to old servers
|
**NOTE:** older fuse clients (from before 5e3775c1 / older than v0.8.0) must be upgraded for this copyparty release, however the new client still supports connecting to old servers
|
||||||
|
|
||||||
other changes include
|
other changes include
|
||||||
* support chunked PUT requests from curl
|
* support chunked PUT requests from curl
|
||||||
@@ -5788,7 +6041,7 @@ valvrave-stop.jpg
|
|||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2020-0818-1822 `v0.5.2` da setter vi punktum
|
# 2020-0818-1822 `v0.5.2` da setter vi punktum
|
||||||
|
|
||||||
full disclaimer: `copyparty-sfx.py` was built using `sfx.py` from ~~82e568d4c9f25bfdfd1bf5166f0ebedf058723ee~~ f550a8171d298992f4ef569d2fc99a6037a44ea8
|
full disclaimer: `copyparty-sfx.py` was built using `sfx.py` from ~~82e568d4~~ f550a817
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
## devnotes toc
|
## devnotes toc
|
||||||
|
|
||||||
* top
|
* top
|
||||||
* [future plans](#future-plans) - some improvement ideas
|
* [future ideas](#future-ideas) - list of dreams which will probably never happen
|
||||||
* [design](#design)
|
* [design](#design)
|
||||||
* [up2k](#up2k) - quick outline of the up2k protocol
|
* [up2k](#up2k) - quick outline of the up2k protocol
|
||||||
* [why not tus](#why-not-tus) - I didn't know about [tus](https://tus.io/)
|
* [why not tus](#why-not-tus) - I didn't know about [tus](https://tus.io/)
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
* [write](#write)
|
* [write](#write)
|
||||||
* [admin](#admin)
|
* [admin](#admin)
|
||||||
* [general](#general)
|
* [general](#general)
|
||||||
|
* [event hooks](#event-hooks) - on writing your own [hooks](../README.md#event-hooks)
|
||||||
|
* [hook effects](#hook-effects) - hooks can cause intentional side-effects
|
||||||
* [assumptions](#assumptions)
|
* [assumptions](#assumptions)
|
||||||
* [mdns](#mdns)
|
* [mdns](#mdns)
|
||||||
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
||||||
@@ -25,9 +27,9 @@
|
|||||||
* [discarded ideas](#discarded-ideas)
|
* [discarded ideas](#discarded-ideas)
|
||||||
|
|
||||||
|
|
||||||
# future plans
|
# future ideas
|
||||||
|
|
||||||
some improvement ideas
|
list of dreams which will probably never happen
|
||||||
|
|
||||||
* the JS is a mess -- a ~~preact~~ rewrite would be nice
|
* the JS is a mess -- a ~~preact~~ rewrite would be nice
|
||||||
* preferably without build dependencies like webpack/babel/node.js, maybe a python thing to assemble js files into main.js
|
* preferably without build dependencies like webpack/babel/node.js, maybe a python thing to assemble js files into main.js
|
||||||
@@ -137,6 +139,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
|||||||
| GET | `?tar&w` | pregenerate webp thumbnails |
|
| GET | `?tar&w` | pregenerate webp thumbnails |
|
||||||
| GET | `?tar&j` | pregenerate jpg thumbnails |
|
| GET | `?tar&j` | pregenerate jpg thumbnails |
|
||||||
| GET | `?tar&p` | pregenerate audio waveforms |
|
| GET | `?tar&p` | pregenerate audio waveforms |
|
||||||
|
| GET | `?shares` | list your shared files/folders |
|
||||||
| GET | `?ups` | show recent uploads from your IP |
|
| GET | `?ups` | show recent uploads from your IP |
|
||||||
| GET | `?ups&filter=f` | ...where URL contains `f` |
|
| GET | `?ups&filter=f` | ...where URL contains `f` |
|
||||||
| GET | `?mime=foo` | specify return mimetype `foo` |
|
| GET | `?mime=foo` | specify return mimetype `foo` |
|
||||||
@@ -173,6 +176,9 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
|||||||
| mPOST | `?media` | `f=FILE` | ...and return medialink (not hotlink) |
|
| mPOST | `?media` | `f=FILE` | ...and return medialink (not hotlink) |
|
||||||
| mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL |
|
| mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL |
|
||||||
| POST | `?delete` | | delete URL recursively |
|
| POST | `?delete` | | delete URL recursively |
|
||||||
|
| POST | `?eshare=rm` | | stop sharing a file/folder |
|
||||||
|
| POST | `?eshare=3` | | set expiration to 3 minutes |
|
||||||
|
| jPOST | `?share` | (complicated) | create temp URL for file/folder |
|
||||||
| jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively |
|
| jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively |
|
||||||
| uPOST | | `msg=foo` | send message `foo` into server log |
|
| uPOST | | `msg=foo` | send message `foo` into server log |
|
||||||
| mPOST | | `act=tput`, `body=TEXT` | overwrite markdown document at URL |
|
| mPOST | | `act=tput`, `body=TEXT` | overwrite markdown document at URL |
|
||||||
@@ -204,6 +210,32 @@ upload modifiers:
|
|||||||
| GET | `?pw=x` | logout |
|
| GET | `?pw=x` | logout |
|
||||||
|
|
||||||
|
|
||||||
|
# event hooks
|
||||||
|
|
||||||
|
on writing your own [hooks](../README.md#event-hooks)
|
||||||
|
|
||||||
|
## hook effects
|
||||||
|
|
||||||
|
hooks can cause intentional side-effects, such as redirecting an upload into another location, or creating+indexing additional files, or deleting existing files, by returning json on stdout
|
||||||
|
|
||||||
|
* `reloc` can redirect uploads before/after uploading has finished, based on filename, extension, file contents, uploader ip/name etc.
|
||||||
|
* `idx` informs copyparty about a new file to index as a consequence of this upload
|
||||||
|
* `del` tells copyparty to delete an unrelated file by vpath
|
||||||
|
|
||||||
|
for these to take effect, the hook must be defined with the `c1` flag; see example [reloc-by-ext](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reloc-by-ext.py)
|
||||||
|
|
||||||
|
a subset of effect types are available for a subset of hook types,
|
||||||
|
|
||||||
|
* most hook types (xbu/xau/xbr/xar/xbd/xad/xm) support `idx` and `del` for all http protocols (up2k / basic-uploader / webdav), but not ftp/tftp/smb
|
||||||
|
* most hook types will abort/reject the action if the hook returns nonzero, assuming flag `c` is given, see examples [reject-extension](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reject-extension.py) and [reject-mimetype](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reject-mimetype.py)
|
||||||
|
* `xbu` supports `reloc` for all http protocols (up2k / basic-uploader / webdav), but not ftp/tftp/smb
|
||||||
|
* `xau` supports `reloc` for basic-uploader / webdav only, not up2k or ftp/tftp/smb
|
||||||
|
* so clients like sharex are supported, but not dragdrop into browser
|
||||||
|
|
||||||
|
to trigger indexing of files `/foo/1.txt` and `/foo/bar/2.txt`, a hook can `print(json.dumps({"idx":{"vp":["/foo/1.txt","/foo/bar/2.txt"]}}))` (and replace "idx" with "del" to delete instead)
|
||||||
|
* note: paths starting with `/` are absolute URLs, but you can also do `../3.txt` relative to the destination folder of each uploaded file
|
||||||
|
|
||||||
|
|
||||||
# assumptions
|
# assumptions
|
||||||
|
|
||||||
## mdns
|
## mdns
|
||||||
|
|||||||
45
docs/examples/docker/portainer.md
Normal file
45
docs/examples/docker/portainer.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
the following setup appears to work (copyparty starts, accepts uploads, is able to persist config)
|
||||||
|
|
||||||
|
tested on debian 12 using [portainer-ce](https://docs.portainer.io/start/install-ce/server/docker/linux) with [docker-ce](https://docs.docker.com/engine/install/debian/) as root (not rootless)
|
||||||
|
|
||||||
|
before making the container, first `mkdir /etc/copyparty /srv/pub` which will be bind-mounts into the container
|
||||||
|
|
||||||
|
> both `/etc/copyparty` and `/srv/pub` are examples; you can change them if you'd like
|
||||||
|
|
||||||
|
put your copyparty config files directly into `/etc/copyparty` and the files to share inside `/srv/pub`
|
||||||
|
|
||||||
|
on first startup, copyparty will create a subfolder inside `/etc/copyparty` called `copyparty` where it puts some runtime state; for example replacing `/etc/copyparty/copyparty/cert.pem` with another TLS certificate is a quick and dirty way to get valid HTTPS (if you really want copyparty to handle that and not a reverse-proxy)
|
||||||
|
|
||||||
|
|
||||||
|
## in portainer:
|
||||||
|
|
||||||
|
```
|
||||||
|
environments -> local -> containers -> add container:
|
||||||
|
|
||||||
|
name = copyparty-ac
|
||||||
|
registry = docker hub
|
||||||
|
image = copyparty/ac
|
||||||
|
always pull = no
|
||||||
|
|
||||||
|
manual network port publishing:
|
||||||
|
3923 to 3923 [TCP]
|
||||||
|
|
||||||
|
advanced -> command & logging:
|
||||||
|
console = interactive & tty
|
||||||
|
|
||||||
|
advanced -> volumes -> map additional volume:
|
||||||
|
container = /cfg [Bind]
|
||||||
|
host = /etc/copyparty [Writable]
|
||||||
|
|
||||||
|
advanced -> volumes -> map additional volume:
|
||||||
|
container = /w [Bind]
|
||||||
|
host = /srv/pub [Writable]
|
||||||
|
```
|
||||||
|
|
||||||
|
notes:
|
||||||
|
|
||||||
|
* `/cfg` is where copyparty expects to find its config files; `/etc/copyparty` is just an example mapping to that
|
||||||
|
|
||||||
|
* `/w` is where copyparty expects to find the folder to share; `/srv/pub` is just an example mapping to that
|
||||||
|
|
||||||
|
* the volumes must be bind-mounts to avoid permission issues (or so the theory goes)
|
||||||
@@ -16,7 +16,7 @@ open up notepad and save the following as `c:\users\you\documents\party.conf` (f
|
|||||||
```yaml
|
```yaml
|
||||||
[global]
|
[global]
|
||||||
lo: ~/logs/cpp-%Y-%m%d.xz # log to c:\users\you\logs\
|
lo: ~/logs/cpp-%Y-%m%d.xz # log to c:\users\you\logs\
|
||||||
e2dsa, e2ts, no-dedup, z # sets 4 flags; see expl.
|
e2dsa, e2ts, z # sets 3 flags; see explanation
|
||||||
p: 80, 443 # listen on ports 80 and 443, not 3923
|
p: 80, 443 # listen on ports 80 and 443, not 3923
|
||||||
theme: 2 # default theme: protonmail-monokai
|
theme: 2 # default theme: protonmail-monokai
|
||||||
lang: nor # default language: viking
|
lang: nor # default language: viking
|
||||||
@@ -46,11 +46,10 @@ open up notepad and save the following as `c:\users\you\documents\party.conf` (f
|
|||||||
|
|
||||||
### config explained: [global]
|
### config explained: [global]
|
||||||
|
|
||||||
the `[global]` section accepts any config parameters [listed here](https://ocv.me/copyparty/helptext.html), also viewable by running copyparty (either the exe or the sfx.py) with `--help`, so this is the same as running copyparty with arguments `--lo c:\users\you\logs\copyparty-%Y-%m%d.xz -e2dsa -e2ts --no-dedup -z -p 80,443 --theme 2 --lang nor`
|
the `[global]` section accepts any config parameters [listed here](https://ocv.me/copyparty/helptext.html), also viewable by running copyparty (either the exe or the sfx.py) with `--help`, so this is the same as running copyparty with arguments `--lo c:\users\you\logs\copyparty-%Y-%m%d.xz -e2dsa -e2ts -z -p 80,443 --theme 2 --lang nor`
|
||||||
* `lo: ~/logs/cpp-%Y-%m%d.xz` writes compressed logs (the compression will make them delayed)
|
* `lo: ~/logs/cpp-%Y-%m%d.xz` writes compressed logs (the compression will make them delayed)
|
||||||
* `e2dsa` enables the upload deduplicator and file indexer, which enables searching
|
* `e2dsa` enables the file indexer, which enables searching and upload-undo
|
||||||
* `e2ts` enables music metadata indexing, making albums / titles etc. searchable too
|
* `e2ts` enables music metadata indexing, making albums / titles etc. searchable too
|
||||||
* `no-dedup` writes full dupes to disk instead of symlinking, since lots of windows software doesn't handle symlinks well
|
|
||||||
* but the improved upload speed from `e2dsa` is not affected
|
* but the improved upload speed from `e2dsa` is not affected
|
||||||
* `z` enables zeroconf, making the server available at `http://HOSTNAME.local/` from any other machine in the LAN
|
* `z` enables zeroconf, making the server available at `http://HOSTNAME.local/` from any other machine in the LAN
|
||||||
* `p: 80,443` listens on the ports `80` and `443` instead of the default `3923`
|
* `p: 80,443` listens on the ports `80` and `443` instead of the default `3923`
|
||||||
|
|||||||
210
docs/logo.svg
Normal file
210
docs/logo.svg
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="300mm"
|
||||||
|
height="207mm"
|
||||||
|
viewBox="0 0 300 207"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<title
|
||||||
|
id="title1">copyparty_logo</title>
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient1">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffcc55;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop1" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffcc00;stop-opacity:1"
|
||||||
|
offset="0.2"
|
||||||
|
id="stop2" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ff8800;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop3" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1"
|
||||||
|
id="linearGradient2"
|
||||||
|
x1="15"
|
||||||
|
y1="15"
|
||||||
|
x2="15"
|
||||||
|
y2="143"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>copyparty_logo</dc:title>
|
||||||
|
<dc:source>github.com/9001/copyparty</dc:source>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="kassett">
|
||||||
|
<rect
|
||||||
|
style="fill:#333333"
|
||||||
|
id="rect1"
|
||||||
|
width="300"
|
||||||
|
height="205"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
rx="12"
|
||||||
|
ry="12" />
|
||||||
|
<rect
|
||||||
|
style="fill:url(#linearGradient2)"
|
||||||
|
id="rect2"
|
||||||
|
width="270"
|
||||||
|
height="128"
|
||||||
|
x="15"
|
||||||
|
y="15"
|
||||||
|
rx="8"
|
||||||
|
ry="8" />
|
||||||
|
<rect
|
||||||
|
style="fill:#333333"
|
||||||
|
id="rect3"
|
||||||
|
width="172"
|
||||||
|
height="52"
|
||||||
|
x="64"
|
||||||
|
y="72"
|
||||||
|
rx="26"
|
||||||
|
ry="26" />
|
||||||
|
<circle
|
||||||
|
style="fill:#cccccc"
|
||||||
|
id="circle1"
|
||||||
|
cx="91"
|
||||||
|
cy="98"
|
||||||
|
r="18" />
|
||||||
|
<circle
|
||||||
|
style="fill:#cccccc"
|
||||||
|
id="circle2"
|
||||||
|
cx="209"
|
||||||
|
cy="98"
|
||||||
|
r="18" />
|
||||||
|
<path
|
||||||
|
style="fill:#737373;stroke-width:1px"
|
||||||
|
d="m 48,207 10,-39 c 1.79,-6.2 5.6,-7.8 12,-8 60,-1 100,-1 160,0 6.4,0.2 10,1.8 12,8 l 10,39 z"
|
||||||
|
id="path1"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer3"
|
||||||
|
inkscape:label="tekst"
|
||||||
|
style="display:none">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:38.8056px;line-height:1.25;font-family:Akbar;-inkscape-font-specification:Akbar;letter-spacing:3.70417px;word-spacing:0px;fill:#333333"
|
||||||
|
x="47.153069"
|
||||||
|
y="55.548954"
|
||||||
|
id="text1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1"
|
||||||
|
x="47.153069"
|
||||||
|
y="55.548954"
|
||||||
|
style="-inkscape-font-specification:Akbar"
|
||||||
|
rotate="0 0">copyparty</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="stensatt">
|
||||||
|
<path
|
||||||
|
d="m 63.5,50.9 q -0.85,0.93 -4.73,2.3 -3.6,1.3 -4.4,1.3 -3.3,0 -5.1,-2.1 -1.75,-2 -1.75,-5.36 0,-4.6 3.76,-7.64 3.3,-2.7 7.3,-2.7 0.4,0 0.93,0.74 0.54,0.7 0.54,1.16 0,2.06 -2.2,2.7 -1.36,0.4 -4.04,1.16 -2.2,1.16 -2.2,4.4 0,3.2 2.9,3.2 0.85,0 0.85,0 0.54,0 1.44,-0.16 1.1,-0.23 2.9,-0.74 1.8,-0.54 2.13,-0.54 0.4,0 1.75,0.6 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path11" />
|
||||||
|
<path
|
||||||
|
d="m 87.6,45 q 0,4.2 -3.7,6.95 -3.2,2.3 -6.87,2.3 -3.4,0 -6,-2.6 -2.5,-2.6 -2.5,-6 0,-3.6 3.14,-6.64 3.2,-3 6.8,-3 3.5,0 6.3,2.76 2.83,2.76 2.83,6.25 z m -3.4,0.16 q 0,-2.25 -1.75,-3.7 -1.7,-1.5 -4,-1.5 -0.1,0 -1.6,1.6 -1.44,1.55 -2.44,1.55 -0.6,0 -0.8,-0.3 -1.16,2.3 -1.16,3 0,2.25 2.13,3.4 1.6,0.9 3.6,0.9 2,0 3.76,-1.1 2.25,-1.4 2.25,-3.84 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path12" />
|
||||||
|
<path
|
||||||
|
d="m 112.8,46.8 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.7,1.5 -0.7,0 -2.7,-0.4 -1.9,-0.4 -2.6,-0.4 -2.1,0 -2.1,2.64 0,0.85 0.23,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.77,2.83 -1.44,0 -3,-0.85 -1.46,-9.5 -1.46,-12 0,-3.65 1.75,-8.1 2.37,-6.05 6.45,-6.05 3.7,0 7.3,4.1 3.3,3.84 3.3,7.14 z m -3.8,0.2 q -0.6,-2.2 -2.6,-4.4 -2.3,-2.5 -4.3,-2.5 -1.3,0 -2.33,2.2 -0.9,1.8 -0.9,3.26 0,0.47 0.38,1.24 0.43,0.8 0.85,0.8 1.1,0 3.2,0.3 2.1,0.3 3.2,0.3 0.3,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path13" />
|
||||||
|
<path
|
||||||
|
d="m 133,40 q -2.1,4.1 -3.2,7 -0.1,0.3 -1.6,4.5 -0.4,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.3,2.64 -1.4,-0.2 -1.6,-1.6 0,-0.2 0,-0.5 0,-0.16 0.3,-1.5 1,-5.04 1,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.5,-1.36 2.1,-1.36 0.4,0 1.1,0.6 0.6,0.6 0.7,1.1 0.8,6.2 4.9,11.1 1,-1.8 1.8,-4.04 0.5,-1.4 1.6,-4.15 1.9,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.9,0.3 1.3,2.8 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path14" />
|
||||||
|
<path
|
||||||
|
d="m 157.5,48 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.7,1.5 -0.7,0 -2.7,-0.4 -1.9,-0.4 -2.6,-0.4 -2,0 -2,2.64 0,0.85 0.2,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.7,2.83 -1.5,0 -3,-0.85 -1.5,-9.5 -1.5,-11.95 0,-3.65 1.8,-8.1 2.3,-6.05 6.4,-6.05 3.7,0 7.2,4.1 3.3,3.84 3.3,7.14 z m -3.8,0.2 q -0.6,-2.2 -2.6,-4.4 -2.3,-2.5 -4.3,-2.5 -1.3,0 -2.3,2.2 -0.9,1.8 -0.9,3.26 0,0.47 0.4,1.24 0.4,0.8 0.8,0.8 1.1,0 3.2,0.3 2.1,0.3 3.2,0.3 0.3,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path15" />
|
||||||
|
<path
|
||||||
|
d="m 182,53.3 q 0,0.9 -0.6,1.5 -0.6,0.6 -1.4,0.6 -1.6,0 -3,-0.9 -1.4,-0.93 -2.1,-2.3 -0.7,-0.1 -1.5,0.85 -0.9,1.16 -1.1,1.24 -1.2,0.54 -3.9,0.54 -2.2,0 -3.9,-2.44 -1.5,-2.13 -1.5,-4 0,-3.4 3.4,-6.4 3.2,-2.9 6.7,-2.9 0.9,0 1.7,0.6 0.8,0.6 0.8,1.44 0,0.54 -0.4,1.1 2.4,0.9 2.4,2.83 0,0.35 -0.1,1.05 -0.1,0.7 -0.1,1.05 0,0.4 0.1,0.6 0.5,1.3 2.5,3.4 1.9,1.9 1.9,2.2 z m -8.1,-10.1 q -0.4,0 -1.1,-0.1 -0.8,-0.16 -1.1,-0.16 -1.3,0 -3.2,1.94 -1.9,1.94 -1.9,3.3 0,0.8 0.7,1.8 0.9,1.3 2.2,1.3 2.6,0 3.5,-2.9 0.5,-2.6 1,-5.16 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path16" />
|
||||||
|
<path
|
||||||
|
d="m 203.8,42.4 q -0.4,0.4 -1.5,0.4 -0.9,0 -2.5,-0.3 -1.7,-0.3 -2.5,-0.3 -4.7,0 -5.5,6.9 -0.3,3.1 -0.4,3.3 -0.4,1 -1.7,2.3 h -1.1 q -0.7,-1.2 -1.3,-4.1 -0.6,-2.76 -0.6,-4.27 0,-1.16 0.1,-1.5 0.2,-0.54 1,-0.54 0.3,0 0.6,0.3 0.4,0.3 0.4,0.3 1.9,-3.53 3.1,-4.6 1.8,-1.7 5.1,-1.7 1.4,0 3.6,0.9 2.8,1.16 3.3,2.8 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path17" />
|
||||||
|
<path
|
||||||
|
d="m 229.5,37.16 q 0.3,0.8 0.3,1.44 0,1.86 -2.4,1.86 -1,0 -3.5,-0.5 -2.5,-0.54 -3.4,-0.54 -1.3,0 -1.5,0.1 -0.4,0.2 -0.4,1.2 0,2.2 0.6,6.9 0.7,5.86 1.6,6.13 -0.4,0.35 -0.4,1.1 -1.2,0.7 -2.6,0.7 -1.4,0 -2,-3.9 -0.2,-1.36 -0.5,-7.76 -0.2,-4.6 -0.8,-5.5 -0.3,-0.47 -4.3,-0.35 -1,0 -1.6,0.1 -0.5,0 -0.3,0 -0.8,0 -1.2,-0.7 -0.5,-1.3 -0.5,-1.4 0,-1.44 4.1,-2 1.6,-0.16 4.7,-0.5 0,-0.85 -0.1,-2.56 0,-1.75 0,-2.6 0,-4.35 2.1,-4.35 0.5,0 1.1,0.6 0.6,0.6 0.6,1.1 v 7.9 q 1.1,1.2 5,1.7 3.9,0.5 5.3,1.86 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path18" />
|
||||||
|
<path
|
||||||
|
d="m 251.2,40.2 q -2,4.1 -3.2,7 -0.1,0.3 -1.5,4.5 -0.5,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.4,2.64 -1.4,-0.2 -1.5,-1.6 -0.1,-0.2 -0.1,-0.5 0,-0.16 0.3,-1.5 1.1,-5.04 1.1,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.4,-1.36 2.1,-1.36 0.4,0 1,0.6 0.6,0.6 0.7,1.1 0.9,6.2 4.9,11.1 1,-1.8 1.9,-4.04 0.5,-1.4 1.6,-4.15 1.8,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.8,0.3 1.2,2.8 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path19" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="tagger">
|
||||||
|
<g
|
||||||
|
id="g1">
|
||||||
|
<path
|
||||||
|
id="path4"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path5"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path6"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g2"
|
||||||
|
transform="rotate(30,150,318.19)">
|
||||||
|
<path
|
||||||
|
id="path7"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path8"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path9"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.3 KiB |
@@ -141,6 +141,9 @@ find -maxdepth 1 -printf '%s %p\n' | sort -n | awk '!/-([0-9a-zA-Z_-]{11})\.(mkv
|
|||||||
# unique stacks in a stackdump
|
# unique stacks in a stackdump
|
||||||
f=a; rm -rf stacks; mkdir stacks; grep -E '^#' $f | while IFS= read -r n; do awk -v n="$n" '!$0{o=0} o; $0==n{o=1}' <$f >stacks/f; h=$(sha1sum <stacks/f | cut -c-16); mv stacks/f stacks/$h-"$n"; done ; find stacks/ | sort | uniq -cw24
|
f=a; rm -rf stacks; mkdir stacks; grep -E '^#' $f | while IFS= read -r n; do awk -v n="$n" '!$0{o=0} o; $0==n{o=1}' <$f >stacks/f; h=$(sha1sum <stacks/f | cut -c-16); mv stacks/f stacks/$h-"$n"; done ; find stacks/ | sort | uniq -cw24
|
||||||
|
|
||||||
|
# find unused css variables
|
||||||
|
cat browser.css | sed -r 's/(var\()/\n\1/g' | awk '{sub(/:/," ")} $1~/^--/{d[$1]=1} /var\(/{sub(/.*var\(/,"");sub(/\).*/,"");u[$1]=1} END{for (x in u) delete d[x]; for (x in d) print x}' | tr '\n' '|'
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## sqlite3 stuff
|
## sqlite3 stuff
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ currently up to date with [awesome-selfhosted](https://github.com/awesome-selfho
|
|||||||
* 💾 = what copyparty offers as an alternative
|
* 💾 = what copyparty offers as an alternative
|
||||||
* 🔵 = similarities
|
* 🔵 = similarities
|
||||||
* ⚠️ = disadvantages (something copyparty does "better")
|
* ⚠️ = disadvantages (something copyparty does "better")
|
||||||
|
* 🔥 = hazards
|
||||||
|
|
||||||
|
|
||||||
## toc
|
## toc
|
||||||
@@ -37,7 +38,7 @@ currently up to date with [awesome-selfhosted](https://github.com/awesome-selfho
|
|||||||
* [another matrix](#another-matrix)
|
* [another matrix](#another-matrix)
|
||||||
* [reviews](#reviews)
|
* [reviews](#reviews)
|
||||||
* [copyparty](#copyparty)
|
* [copyparty](#copyparty)
|
||||||
* [hfs2](#hfs2)
|
* [hfs2](#hfs2) 🔥
|
||||||
* [hfs3](#hfs3)
|
* [hfs3](#hfs3)
|
||||||
* [nextcloud](#nextcloud)
|
* [nextcloud](#nextcloud)
|
||||||
* [seafile](#seafile)
|
* [seafile](#seafile)
|
||||||
@@ -83,8 +84,8 @@ the table headers in the matrixes below are the different softwares, with a quic
|
|||||||
|
|
||||||
the softwares,
|
the softwares,
|
||||||
* `a` = [copyparty](https://github.com/9001/copyparty)
|
* `a` = [copyparty](https://github.com/9001/copyparty)
|
||||||
* `b` = [hfs2](https://rejetto.com/hfs/)
|
* `b` = [hfs2](https://github.com/rejetto/hfs2/) 🔥
|
||||||
* `c` = [hfs3](https://github.com/rejetto/hfs)
|
* `c` = [hfs3](https://rejetto.com/hfs/)
|
||||||
* `d` = [nextcloud](https://github.com/nextcloud/server)
|
* `d` = [nextcloud](https://github.com/nextcloud/server)
|
||||||
* `e` = [seafile](https://github.com/haiwen/seafile)
|
* `e` = [seafile](https://github.com/haiwen/seafile)
|
||||||
* `f` = [rclone](https://github.com/rclone/rclone), specifically `rclone serve webdav .`
|
* `f` = [rclone](https://github.com/rclone/rclone), specifically `rclone serve webdav .`
|
||||||
@@ -152,19 +153,20 @@ symbol legend,
|
|||||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| download folder as zip | █ | █ | █ | █ | ╱ | | █ | | █ | █ | ╱ | █ | ╱ |
|
| download folder as zip | █ | █ | █ | █ | ╱ | | █ | | █ | █ | ╱ | █ | ╱ |
|
||||||
| download folder as tar | █ | | | | | | | | | █ | | | |
|
| download folder as tar | █ | | | | | | | | | | | | |
|
||||||
| upload | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
| upload | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | ╱ | █ | █ |
|
||||||
| parallel uploads | █ | | | █ | █ | | • | | █ | | █ | | █ |
|
| parallel uploads | █ | | | █ | █ | | • | | █ | █ | █ | | █ |
|
||||||
| resumable uploads | █ | | | | | | | | █ | | █ | ╱ | |
|
| resumable uploads | █ | | █ | | | | | | █ | █ | █ | ╱ | |
|
||||||
| upload segmenting | █ | | | | | | | █ | █ | | █ | ╱ | █ |
|
| upload segmenting | █ | | | | | | | █ | █ | █ | █ | ╱ | █ |
|
||||||
| upload acceleration | █ | | | | | | | | █ | | █ | | |
|
| upload acceleration | █ | | | | | | | | █ | | █ | | |
|
||||||
| upload verification | █ | | | █ | █ | | | | █ | | | | |
|
| upload verification | █ | | | █ | █ | | | | █ | | | | |
|
||||||
| upload deduplication | █ | | | | █ | | | | █ | | | | |
|
| upload deduplication | █ | | | | █ | | | | █ | | | | |
|
||||||
| upload a 999 TiB file | █ | | | | █ | █ | • | | █ | | █ | ╱ | ╱ |
|
| upload a 999 TiB file | █ | | | | █ | █ | • | | █ | | █ | ╱ | ╱ |
|
||||||
| race the beam ("p2p") | █ | | | | | | | | | • | | | |
|
| CTRL-V from device | █ | | | █ | | | | | | | | | |
|
||||||
|
| race the beam ("p2p") | █ | | | | | | | | | | | | |
|
||||||
| keep last-modified time | █ | | | █ | █ | █ | | | | | | █ | |
|
| keep last-modified time | █ | | | █ | █ | █ | | | | | | █ | |
|
||||||
| upload rules | ╱ | ╱ | ╱ | ╱ | ╱ | | | ╱ | ╱ | | ╱ | ╱ | ╱ |
|
| upload rules | ╱ | ╱ | ╱ | ╱ | ╱ | | | ╱ | ╱ | | ╱ | ╱ | ╱ |
|
||||||
| ┗ max disk usage | █ | █ | | | █ | | | | █ | | | █ | █ |
|
| ┗ max disk usage | █ | █ | █ | | █ | | | | █ | | | █ | █ |
|
||||||
| ┗ max filesize | █ | | | | | | | █ | | | █ | █ | █ |
|
| ┗ max filesize | █ | | | | | | | █ | | | █ | █ | █ |
|
||||||
| ┗ max items in folder | █ | | | | | | | | | | | ╱ | |
|
| ┗ max items in folder | █ | | | | | | | | | | | ╱ | |
|
||||||
| ┗ max file age | █ | | | | | | | | █ | | | | |
|
| ┗ max file age | █ | | | | | | | | █ | | | | |
|
||||||
@@ -173,6 +175,7 @@ symbol legend,
|
|||||||
| ┗ randomize filename | █ | | | | | | | █ | █ | | | | |
|
| ┗ randomize filename | █ | | | | | | | █ | █ | | | | |
|
||||||
| ┗ mimetype reject-list | ╱ | | | | | | | | • | ╱ | | ╱ | • |
|
| ┗ mimetype reject-list | ╱ | | | | | | | | • | ╱ | | ╱ | • |
|
||||||
| ┗ extension reject-list | ╱ | | | | | | | █ | • | ╱ | | ╱ | • |
|
| ┗ extension reject-list | ╱ | | | | | | | █ | • | ╱ | | ╱ | • |
|
||||||
|
| ┗ upload routing | █ | | | | | | | | | | | | |
|
||||||
| checksums provided | | | | █ | █ | | | | █ | ╱ | | | |
|
| checksums provided | | | | █ | █ | | | | █ | ╱ | | | |
|
||||||
| cloud storage backend | ╱ | ╱ | ╱ | █ | █ | █ | ╱ | | | ╱ | █ | █ | ╱ |
|
| cloud storage backend | ╱ | ╱ | ╱ | █ | █ | █ | ╱ | | | ╱ | █ | █ | ╱ |
|
||||||
|
|
||||||
@@ -182,8 +185,13 @@ symbol legend,
|
|||||||
|
|
||||||
* `upload verification` = uploads are checksummed or otherwise confirmed to have been transferred correctly
|
* `upload verification` = uploads are checksummed or otherwise confirmed to have been transferred correctly
|
||||||
|
|
||||||
|
* `CTRL-V from device` = press CTRL-C in Windows Explorer (or whatever) and paste into the webbrowser to upload it
|
||||||
|
|
||||||
* `race the beam` = files can be downloaded while they're still uploading; downloaders are slowed down such that the uploader is always ahead
|
* `race the beam` = files can be downloaded while they're still uploading; downloaders are slowed down such that the uploader is always ahead
|
||||||
|
|
||||||
|
* `upload routing` = depending on filetype / contents / uploader etc., the file can be redirected to another location or otherwise transformed; mitigates limitations such as [sharex#3992](https://github.com/ShareX/ShareX/issues/3992)
|
||||||
|
* copyparty example: [reloc-by-ext](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks#before-upload)
|
||||||
|
|
||||||
* `checksums provided` = when downloading a file from the server, the file's checksum is provided for verification client-side
|
* `checksums provided` = when downloading a file from the server, the file's checksum is provided for verification client-side
|
||||||
|
|
||||||
* `cloud storage backend` = able to serve files from (and write to) s3 or similar cloud services; `╱` means the software can do this with some help from `rclone mount` as a bridge
|
* `cloud storage backend` = able to serve files from (and write to) s3 or similar cloud services; `╱` means the software can do this with some help from `rclone mount` as a bridge
|
||||||
@@ -213,7 +221,7 @@ symbol legend,
|
|||||||
| serve sftp (ssh) | | | | | | █ | | | | | | █ | █ |
|
| serve sftp (ssh) | | | | | | █ | | | | | | █ | █ |
|
||||||
| serve smb/cifs | ╱ | | | | | █ | | | | | | | |
|
| serve smb/cifs | ╱ | | | | | █ | | | | | | | |
|
||||||
| serve dlna | | | | | | █ | | | | | | | |
|
| serve dlna | | | | | | █ | | | | | | | |
|
||||||
| listen on unix-socket | | | | █ | █ | | █ | █ | █ | | █ | █ | |
|
| listen on unix-socket | █ | | | █ | █ | | █ | █ | █ | █ | █ | █ | |
|
||||||
| zeroconf | █ | | | | | | | | | | | | █ |
|
| zeroconf | █ | | | | | | | | | | | | █ |
|
||||||
| supports netscape 4 | ╱ | | | | | █ | | | | | • | | ╱ |
|
| supports netscape 4 | ╱ | | | | | █ | | | | | • | | ╱ |
|
||||||
| ...internet explorer 6 | ╱ | █ | | █ | | █ | | | | | • | | ╱ |
|
| ...internet explorer 6 | ╱ | █ | | █ | | █ | | | | | • | | ╱ |
|
||||||
@@ -243,7 +251,7 @@ symbol legend,
|
|||||||
| listen multiple ports | █ | | | | | | | | | | | █ | |
|
| listen multiple ports | █ | | | | | | | | | | | █ | |
|
||||||
| virtual file system | █ | █ | █ | | | | █ | | | | | █ | |
|
| virtual file system | █ | █ | █ | | | | █ | | | | | █ | |
|
||||||
| reverse-proxy ok | █ | | █ | █ | █ | █ | █ | █ | • | • | • | █ | ╱ |
|
| reverse-proxy ok | █ | | █ | █ | █ | █ | █ | █ | • | • | • | █ | ╱ |
|
||||||
| folder-rproxy ok | █ | | | | █ | █ | | • | • | • | • | | • |
|
| folder-rproxy ok | █ | | █ | | █ | █ | | • | • | █ | • | | • |
|
||||||
|
|
||||||
* `folder-rproxy` = reverse-proxying without dedicating an entire (sub)domain, using a subfolder instead
|
* `folder-rproxy` = reverse-proxying without dedicating an entire (sub)domain, using a subfolder instead
|
||||||
* `l`/sftpgo:
|
* `l`/sftpgo:
|
||||||
@@ -266,9 +274,9 @@ symbol legend,
|
|||||||
| per-folder permissions | ╱ | | | █ | █ | | █ | | █ | █ | ╱ | █ | █ |
|
| per-folder permissions | ╱ | | | █ | █ | | █ | | █ | █ | ╱ | █ | █ |
|
||||||
| per-file permissions | | | | █ | █ | | █ | | █ | | | | █ |
|
| per-file permissions | | | | █ | █ | | █ | | █ | | | | █ |
|
||||||
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | | █ |
|
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | | █ |
|
||||||
| unmap subfolders | █ | | | | | | █ | | | █ | ╱ | • | |
|
| unmap subfolders | █ | | █ | | | | █ | | | █ | ╱ | • | |
|
||||||
| index.html blocks list | ╱ | | | | | | █ | | | • | | | |
|
| index.html blocks list | ╱ | | | | | | █ | | | • | | | |
|
||||||
| write-only folders | █ | | | | | | | | | | █ | █ | |
|
| write-only folders | █ | | █ | | | | | | | | █ | █ | |
|
||||||
| files stored as-is | █ | █ | █ | █ | | █ | █ | | | █ | █ | █ | █ |
|
| files stored as-is | █ | █ | █ | █ | | █ | █ | | | █ | █ | █ | █ |
|
||||||
| file versioning | | | | █ | █ | | | | | | | | |
|
| file versioning | | | | █ | █ | | | | | | | | |
|
||||||
| file encryption | | | | █ | █ | █ | | | | | | █ | |
|
| file encryption | | | | █ | █ | █ | | | | | | █ | |
|
||||||
@@ -298,6 +306,7 @@ symbol legend,
|
|||||||
* `file action event hooks` = run script before/after upload, move, rename, ...
|
* `file action event hooks` = run script before/after upload, move, rename, ...
|
||||||
* `one-way folder sync` = like rsync, optionally deleting unexpected files at target
|
* `one-way folder sync` = like rsync, optionally deleting unexpected files at target
|
||||||
* `full sync` = stateful, dropbox-like sync
|
* `full sync` = stateful, dropbox-like sync
|
||||||
|
* `speed throttle` = rate limiting (per ip, per user, per connection, anything like that)
|
||||||
* `curl-friendly ls` = returns a [sortable plaintext folder listing](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png) when curled
|
* `curl-friendly ls` = returns a [sortable plaintext folder listing](https://user-images.githubusercontent.com/241032/215322619-ea5fd606-3654-40ad-94ee-2bc058647bb2.png) when curled
|
||||||
* `curl-friendly upload` = uploading with curl is just `curl -T some.bin http://.../`
|
* `curl-friendly upload` = uploading with curl is just `curl -T some.bin http://.../`
|
||||||
* `a`/copyparty remarks:
|
* `a`/copyparty remarks:
|
||||||
@@ -323,14 +332,14 @@ symbol legend,
|
|||||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||||
| ---------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
| ---------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||||
| single-page app | █ | | █ | █ | █ | | | █ | █ | █ | █ | | █ |
|
| single-page app | █ | | █ | █ | █ | | | █ | █ | █ | █ | | █ |
|
||||||
| themes | █ | █ | | █ | | | | | █ | | | | |
|
| themes | █ | █ | █ | █ | | | | | █ | | | | |
|
||||||
| directory tree nav | █ | ╱ | | | █ | | | | █ | | ╱ | | |
|
| directory tree nav | █ | ╱ | | | █ | | | | █ | | ╱ | | |
|
||||||
| multi-column sorting | █ | | | | | | | | | | | | |
|
| multi-column sorting | █ | | | | | | | | | | | | |
|
||||||
| thumbnails | █ | | | ╱ | ╱ | | | █ | █ | ╱ | | | █ |
|
| thumbnails | █ | | | ╱ | ╱ | | | █ | █ | ╱ | | | █ |
|
||||||
| ┗ image thumbnails | █ | | | █ | █ | | | █ | █ | █ | | | █ |
|
| ┗ image thumbnails | █ | | | █ | █ | | | █ | █ | █ | | | █ |
|
||||||
| ┗ video thumbnails | █ | | | █ | █ | | | | █ | | | | █ |
|
| ┗ video thumbnails | █ | | | █ | █ | | | | █ | | | | █ |
|
||||||
| ┗ audio spectrograms | █ | | | | | | | | | | | | |
|
| ┗ audio spectrograms | █ | | | | | | | | | | | | |
|
||||||
| audio player | █ | | | █ | █ | | | | █ | ╱ | | | █ |
|
| audio player | █ | | ╱ | █ | █ | | | | █ | ╱ | | | █ |
|
||||||
| ┗ gapless playback | █ | | | | | | | | • | | | | |
|
| ┗ gapless playback | █ | | | | | | | | • | | | | |
|
||||||
| ┗ audio equalizer | █ | | | | | | | | | | | | |
|
| ┗ audio equalizer | █ | | | | | | | | | | | | |
|
||||||
| ┗ waveform seekbar | █ | | | | | | | | | | | | |
|
| ┗ waveform seekbar | █ | | | | | | | | | | | | |
|
||||||
@@ -348,16 +357,16 @@ symbol legend,
|
|||||||
| search by custom parser | █ | | | | | | | | | | | | |
|
| search by custom parser | █ | | | | | | | | | | | | |
|
||||||
| find local file | █ | | | | | | | | | | | | |
|
| find local file | █ | | | | | | | | | | | | |
|
||||||
| undo recent uploads | █ | | | | | | | | | | | | |
|
| undo recent uploads | █ | | | | | | | | | | | | |
|
||||||
| create directories | █ | | | █ | █ | ╱ | █ | █ | █ | █ | █ | █ | █ |
|
| create directories | █ | | █ | █ | █ | ╱ | █ | █ | █ | █ | █ | █ | █ |
|
||||||
| image viewer | █ | | | █ | █ | | | | █ | █ | █ | | █ |
|
| image viewer | █ | | █ | █ | █ | | | | █ | █ | █ | | █ |
|
||||||
| markdown viewer | █ | | | | █ | | | | █ | ╱ | ╱ | | █ |
|
| markdown viewer | █ | | | | █ | | | | █ | ╱ | ╱ | | █ |
|
||||||
| markdown editor | █ | | | | █ | | | | █ | ╱ | ╱ | | █ |
|
| markdown editor | █ | | | | █ | | | | █ | ╱ | ╱ | | █ |
|
||||||
| readme.md in listing | █ | | | █ | | | | | | | | | |
|
| readme.md in listing | █ | | | █ | | | | | | | | | |
|
||||||
| rename files | █ | █ | █ | █ | █ | ╱ | █ | | █ | █ | █ | █ | █ |
|
| rename files | █ | █ | █ | █ | █ | ╱ | █ | | █ | █ | █ | █ | █ |
|
||||||
| batch rename | █ | | | | | | | | █ | | | | |
|
| batch rename | █ | | | | | | | | █ | | | | |
|
||||||
| cut / paste files | █ | █ | | █ | █ | | | | █ | | | | █ |
|
| cut / paste files | █ | █ | | █ | █ | | | | █ | | | | █ |
|
||||||
| move files | █ | █ | | █ | █ | | █ | | █ | █ | █ | | █ |
|
| move files | █ | █ | █ | █ | █ | | █ | | █ | █ | █ | | █ |
|
||||||
| delete files | █ | █ | | █ | █ | ╱ | █ | █ | █ | █ | █ | █ | █ |
|
| delete files | █ | █ | █ | █ | █ | ╱ | █ | █ | █ | █ | █ | █ | █ |
|
||||||
| copy files | | | | | █ | | | | █ | █ | █ | | █ |
|
| copy files | | | | | █ | | | | █ | █ | █ | | █ |
|
||||||
|
|
||||||
* `single-page app` = multitasking; possible to continue navigating while uploading
|
* `single-page app` = multitasking; possible to continue navigating while uploading
|
||||||
@@ -367,8 +376,12 @@ symbol legend,
|
|||||||
* `undo recent uploads` = accounts without delete permissions have a time window where they can undo their own uploads
|
* `undo recent uploads` = accounts without delete permissions have a time window where they can undo their own uploads
|
||||||
* `a`/copyparty has teeny-tiny skips playing gapless albums depending on audio codec (opus best)
|
* `a`/copyparty has teeny-tiny skips playing gapless albums depending on audio codec (opus best)
|
||||||
* `b`/hfs2 has a very basic directory tree view, not showing sibling folders
|
* `b`/hfs2 has a very basic directory tree view, not showing sibling folders
|
||||||
|
* `c`/hfs3 remarks:
|
||||||
|
* audio playback does not continue into next song
|
||||||
* `f`/rclone can do some file management (mkdir, rename, delete) when hosting througn webdav
|
* `f`/rclone can do some file management (mkdir, rename, delete) when hosting througn webdav
|
||||||
* `j`/filebrowser has a plaintext viewer/editor
|
* `j`/filebrowser remarks:
|
||||||
|
* audio playback does not continue into next song
|
||||||
|
* plaintext viewer/editor
|
||||||
* `k`/filegator directory tree is a modal window
|
* `k`/filegator directory tree is a modal window
|
||||||
|
|
||||||
|
|
||||||
@@ -424,6 +437,7 @@ symbol legend,
|
|||||||
* 💾 are what copyparty offers as an alternative
|
* 💾 are what copyparty offers as an alternative
|
||||||
* 🔵 are similarities
|
* 🔵 are similarities
|
||||||
* ⚠️ are disadvantages (something copyparty does "better")
|
* ⚠️ are disadvantages (something copyparty does "better")
|
||||||
|
* 🔥 are hazards
|
||||||
|
|
||||||
## [copyparty](https://github.com/9001/copyparty)
|
## [copyparty](https://github.com/9001/copyparty)
|
||||||
* resumable uploads which are verified server-side
|
* resumable uploads which are verified server-side
|
||||||
@@ -431,8 +445,9 @@ symbol legend,
|
|||||||
* both of the above are surprisingly uncommon features
|
* both of the above are surprisingly uncommon features
|
||||||
* very cross-platform (python, no dependencies)
|
* very cross-platform (python, no dependencies)
|
||||||
|
|
||||||
## [hfs2](https://rejetto.com/hfs/)
|
## [hfs2](https://github.com/rejetto/hfs2/)
|
||||||
* the OG, the legend
|
* the OG, the legend (now replaced by [hfs3](#hfs3))
|
||||||
|
* 🔥 hfs2 is dead and dangerous! unfixed RCE: [info](https://github.com/rejetto/hfs2/issues/44), [info](https://github.com/drapid/hfs/issues/3), [info](https://asec.ahnlab.com/en/67650/)
|
||||||
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* ⚠️ on cloudflare: max upload size 100 MiB
|
||||||
* ⚠️ windows-only
|
* ⚠️ windows-only
|
||||||
@@ -440,10 +455,19 @@ symbol legend,
|
|||||||
* vfs with gui config, per-volume permissions
|
* vfs with gui config, per-volume permissions
|
||||||
* starting to show its age, hence the rewrite:
|
* starting to show its age, hence the rewrite:
|
||||||
|
|
||||||
## [hfs3](https://github.com/rejetto/hfs)
|
## [hfs3](https://rejetto.com/hfs/)
|
||||||
* nodejs; cross-platform
|
* nodejs; cross-platform
|
||||||
* vfs with gui config, per-volume permissions
|
* vfs with gui config, per-volume permissions
|
||||||
* still early development, let's revisit later
|
* 🔵 uploads are resumable
|
||||||
|
* ⚠️ uploads are not segmented; max upload size 100 MiB on cloudflare
|
||||||
|
* ⚠️ uploads are not accelerated (copyparty is 3x faster across the atlantic)
|
||||||
|
* ⚠️ uploads are not integrity-checked
|
||||||
|
* ⚠️ copies the file after upload; need twice filesize free disk space
|
||||||
|
* ⚠️ doesn't support crazy filenames
|
||||||
|
* ✅ config GUI
|
||||||
|
* ✅ download counter
|
||||||
|
* ✅ watch active connections
|
||||||
|
* ✅ plugins
|
||||||
|
|
||||||
## [nextcloud](https://github.com/nextcloud/server)
|
## [nextcloud](https://github.com/nextcloud/server)
|
||||||
* php, mariadb
|
* php, mariadb
|
||||||
@@ -497,6 +521,7 @@ symbol legend,
|
|||||||
* rust; cross-platform (windows, linux, macos)
|
* rust; cross-platform (windows, linux, macos)
|
||||||
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* ⚠️ on cloudflare: max upload size 100 MiB
|
||||||
|
* ⚠️ across the atlantic, copyparty is 3x faster
|
||||||
* ⚠️ doesn't support crazy filenames
|
* ⚠️ doesn't support crazy filenames
|
||||||
* ✅ per-url access control (copyparty is per-volume)
|
* ✅ per-url access control (copyparty is per-volume)
|
||||||
* 🔵 basic but really snappy ui
|
* 🔵 basic but really snappy ui
|
||||||
@@ -539,8 +564,10 @@ symbol legend,
|
|||||||
|
|
||||||
## [filebrowser](https://github.com/filebrowser/filebrowser)
|
## [filebrowser](https://github.com/filebrowser/filebrowser)
|
||||||
* go; cross-platform (windows, linux, mac)
|
* go; cross-platform (windows, linux, mac)
|
||||||
* ⚠️ uploads not resumable / accelerated / integrity-checked
|
* 🔵 uploads are resumable and segmented
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* 🔵 multiple files are uploaded in parallel, but...
|
||||||
|
* ⚠️ big files are not accelerated (copyparty is 5x faster across the atlantic)
|
||||||
|
* ⚠️ uploads are not integrity-checked
|
||||||
* ⚠️ http only; no webdav / ftp / zeroconf
|
* ⚠️ http only; no webdav / ftp / zeroconf
|
||||||
* ⚠️ doesn't support crazy filenames
|
* ⚠️ doesn't support crazy filenames
|
||||||
* ⚠️ no directory tree nav
|
* ⚠️ no directory tree nav
|
||||||
@@ -550,12 +577,14 @@ symbol legend,
|
|||||||
* ⚠️ but no directory tree for navigation
|
* ⚠️ but no directory tree for navigation
|
||||||
* ✅ user signup
|
* ✅ user signup
|
||||||
* ✅ command runner / remote shell
|
* ✅ command runner / remote shell
|
||||||
* 🔵 supposed to have write-only folders but couldn't get it to work
|
* ✅ more efficient; can handle around twice as much simultaneous traffic
|
||||||
|
|
||||||
## [filegator](https://github.com/filegator/filegator)
|
## [filegator](https://github.com/filegator/filegator)
|
||||||
* go; cross-platform (windows, linux, mac)
|
* php; cross-platform (windows, linux, mac)
|
||||||
* 🔵 *it has upload segmenting and acceleration*
|
* 🔵 *it has upload segmenting and acceleration*
|
||||||
* ⚠️ but uploads are still not integrity-checked
|
* ⚠️ but uploads are still not integrity-checked
|
||||||
|
* ⚠️ on copyparty, uploads are 40x faster
|
||||||
|
* compared to the official filegator docker example which might be bad
|
||||||
* ⚠️ http only; no webdav / ftp / zeroconf
|
* ⚠️ http only; no webdav / ftp / zeroconf
|
||||||
* ⚠️ does not support symlinks
|
* ⚠️ does not support symlinks
|
||||||
* ⚠️ expensive download-as-zip feature
|
* ⚠️ expensive download-as-zip feature
|
||||||
@@ -566,6 +595,7 @@ symbol legend,
|
|||||||
* go; cross-platform (windows, linux, mac)
|
* go; cross-platform (windows, linux, mac)
|
||||||
* ⚠️ http uploads not resumable / accelerated / integrity-checked
|
* ⚠️ http uploads not resumable / accelerated / integrity-checked
|
||||||
* ⚠️ on cloudflare: max upload size 100 MiB
|
* ⚠️ on cloudflare: max upload size 100 MiB
|
||||||
|
* ⚠️ across the atlantic, copyparty is 2.5x faster
|
||||||
* 🔵 sftp uploads are resumable
|
* 🔵 sftp uploads are resumable
|
||||||
* ⚠️ web UI is very minimal + a bit slow
|
* ⚠️ web UI is very minimal + a bit slow
|
||||||
* ⚠️ no thumbnails / image viewer / audio player
|
* ⚠️ no thumbnails / image viewer / audio player
|
||||||
@@ -573,6 +603,7 @@ symbol legend,
|
|||||||
* ⚠️ no filesystem indexing / search
|
* ⚠️ no filesystem indexing / search
|
||||||
* ⚠️ doesn't run on phones, tablets
|
* ⚠️ doesn't run on phones, tablets
|
||||||
* ⚠️ no zeroconf (mdns/ssdp)
|
* ⚠️ no zeroconf (mdns/ssdp)
|
||||||
|
* ⚠️ impractical directory URLs
|
||||||
* ⚠️ AGPL licensed
|
* ⚠️ AGPL licensed
|
||||||
* 🔵 ftp, ftps, webdav
|
* 🔵 ftp, ftps, webdav
|
||||||
* ✅ sftp server
|
* ✅ sftp server
|
||||||
@@ -589,11 +620,13 @@ symbol legend,
|
|||||||
## [arozos](https://github.com/tobychui/arozos)
|
## [arozos](https://github.com/tobychui/arozos)
|
||||||
* big suite of applications similar to [kodbox](#kodbox), copyparty is better at downloading/uploading/music/indexing but arozos has other advantages
|
* big suite of applications similar to [kodbox](#kodbox), copyparty is better at downloading/uploading/music/indexing but arozos has other advantages
|
||||||
* go; primarily linux (limited support for windows)
|
* go; primarily linux (limited support for windows)
|
||||||
|
* ⚠️ needs root
|
||||||
* ⚠️ uploads not resumable / integrity-checked
|
* ⚠️ uploads not resumable / integrity-checked
|
||||||
* ⚠️ uploading small files to copyparty is 2.7x faster
|
* ⚠️ uploading small files to copyparty is 2.7x faster
|
||||||
* ⚠️ uploading large files to copyparty is at least 10% faster
|
* ⚠️ uploading large files to copyparty is at least 10% faster
|
||||||
* arozos is websocket-based, 512 KiB chunks; writes each chunk to separate files and then merges
|
* arozos is websocket-based, 512 KiB chunks; writes each chunk to separate files and then merges
|
||||||
* copyparty splices directly into the final file; faster and better for the HDD and filesystem
|
* copyparty splices directly into the final file; faster and better for the HDD and filesystem
|
||||||
|
* ⚠️ across the atlantic, uploading to copyparty is 6x faster
|
||||||
* ⚠️ no directory tree navpane; not as easy to navigate
|
* ⚠️ no directory tree navpane; not as easy to navigate
|
||||||
* ⚠️ download-as-zip is not streaming; creates a temp.file on the server
|
* ⚠️ download-as-zip is not streaming; creates a temp.file on the server
|
||||||
* ⚠️ not self-contained (pulls from jsdelivr)
|
* ⚠️ not self-contained (pulls from jsdelivr)
|
||||||
|
|||||||
@@ -5,19 +5,16 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
|
|||||||
org.opencontainers.image.licenses="MIT" \
|
org.opencontainers.image.licenses="MIT" \
|
||||||
org.opencontainers.image.title="copyparty-ac" \
|
org.opencontainers.image.title="copyparty-ac" \
|
||||||
org.opencontainers.image.description="copyparty with Pillow and FFmpeg (image/audio/video thumbnails, audio transcoding, media tags)"
|
org.opencontainers.image.description="copyparty with Pillow and FFmpeg (image/audio/video thumbnails, audio transcoding, media tags)"
|
||||||
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
|
ENV XDG_CONFIG_HOME=/cfg
|
||||||
XDG_CONFIG_HOME=/cfg
|
|
||||||
|
|
||||||
RUN apk --no-cache add !pyc \
|
RUN apk --no-cache add !pyc \
|
||||||
wget \
|
tzdata wget \
|
||||||
py3-argon2-cffi py3-pillow \
|
py3-jinja2 py3-argon2-cffi py3-pillow \
|
||||||
ffmpeg \
|
ffmpeg
|
||||||
&& rm -rf /tmp/pyc \
|
|
||||||
&& mkdir /cfg /w \
|
COPY i/dist/copyparty-sfx.py innvikler.sh ./
|
||||||
&& chmod 777 /cfg /w \
|
RUN ash innvikler.sh && rm innvikler.sh
|
||||||
&& echo % /cfg > initcfg
|
|
||||||
|
|
||||||
COPY i/dist/copyparty-sfx.py ./
|
|
||||||
WORKDIR /w
|
WORKDIR /w
|
||||||
EXPOSE 3923
|
EXPOSE 3923
|
||||||
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
|
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
|
|||||||
org.opencontainers.image.licenses="MIT" \
|
org.opencontainers.image.licenses="MIT" \
|
||||||
org.opencontainers.image.title="copyparty-dj" \
|
org.opencontainers.image.title="copyparty-dj" \
|
||||||
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection"
|
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection"
|
||||||
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
|
ENV XDG_CONFIG_HOME=/cfg
|
||||||
XDG_CONFIG_HOME=/cfg
|
|
||||||
|
|
||||||
COPY i/bin/mtag/install-deps.sh ./
|
COPY i/bin/mtag/install-deps.sh ./
|
||||||
COPY i/bin/mtag/audio-bpm.py /mtag/
|
COPY i/bin/mtag/audio-bpm.py /mtag/
|
||||||
COPY i/bin/mtag/audio-key.py /mtag/
|
COPY i/bin/mtag/audio-key.py /mtag/
|
||||||
RUN apk add -U !pyc \
|
RUN apk add -U !pyc \
|
||||||
wget \
|
tzdata wget \
|
||||||
py3-argon2-cffi py3-pillow py3-pip py3-cffi \
|
py3-jinja2 py3-argon2-cffi py3-pillow py3-pip py3-cffi \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
vips-jxl vips-heif vips-poppler vips-magick \
|
vips-jxl vips-heif vips-poppler vips-magick \
|
||||||
py3-numpy fftw libsndfile \
|
py3-numpy fftw libsndfile \
|
||||||
@@ -27,18 +26,12 @@ RUN apk add -U !pyc \
|
|||||||
&& python3 -m pip install pyvips \
|
&& python3 -m pip install pyvips \
|
||||||
&& bash install-deps.sh \
|
&& bash install-deps.sh \
|
||||||
&& apk del py3-pip .bd \
|
&& apk del py3-pip .bd \
|
||||||
&& rm -rf /var/cache/apk/* /tmp/pyc \
|
|
||||||
&& chmod 777 /root \
|
&& chmod 777 /root \
|
||||||
&& ln -s /root/vamp /root/.local / \
|
&& ln -s /root/vamp /root/.local /
|
||||||
&& mkdir /cfg /w \
|
|
||||||
&& chmod 777 /cfg /w \
|
COPY i/dist/copyparty-sfx.py innvikler.sh ./
|
||||||
&& echo % /cfg > initcfg
|
RUN ash innvikler.sh && rm innvikler.sh
|
||||||
|
|
||||||
COPY i/dist/copyparty-sfx.py ./
|
|
||||||
WORKDIR /w
|
WORKDIR /w
|
||||||
EXPOSE 3923
|
EXPOSE 3923
|
||||||
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
|
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
|
||||||
|
|
||||||
# size: 286 MB
|
|
||||||
# bpm/key: 529 sec
|
|
||||||
# idx-bench: 2352 MB/s
|
|
||||||
|
|||||||
@@ -5,18 +5,15 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
|
|||||||
org.opencontainers.image.licenses="MIT" \
|
org.opencontainers.image.licenses="MIT" \
|
||||||
org.opencontainers.image.title="copyparty-im" \
|
org.opencontainers.image.title="copyparty-im" \
|
||||||
org.opencontainers.image.description="copyparty with Pillow and Mutagen (image thumbnails, media tags)"
|
org.opencontainers.image.description="copyparty with Pillow and Mutagen (image thumbnails, media tags)"
|
||||||
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
|
ENV XDG_CONFIG_HOME=/cfg
|
||||||
XDG_CONFIG_HOME=/cfg
|
|
||||||
|
|
||||||
RUN apk --no-cache add !pyc \
|
RUN apk --no-cache add !pyc \
|
||||||
wget \
|
tzdata wget \
|
||||||
py3-argon2-cffi py3-pillow py3-mutagen \
|
py3-jinja2 py3-argon2-cffi py3-pillow py3-mutagen
|
||||||
&& rm -rf /tmp/pyc \
|
|
||||||
&& mkdir /cfg /w \
|
COPY i/dist/copyparty-sfx.py innvikler.sh ./
|
||||||
&& chmod 777 /cfg /w \
|
RUN ash innvikler.sh && rm innvikler.sh
|
||||||
&& echo % /cfg > initcfg
|
|
||||||
|
|
||||||
COPY i/dist/copyparty-sfx.py ./
|
|
||||||
WORKDIR /w
|
WORKDIR /w
|
||||||
EXPOSE 3923
|
EXPOSE 3923
|
||||||
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
|
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
|
|||||||
org.opencontainers.image.licenses="MIT" \
|
org.opencontainers.image.licenses="MIT" \
|
||||||
org.opencontainers.image.title="copyparty-iv" \
|
org.opencontainers.image.title="copyparty-iv" \
|
||||||
org.opencontainers.image.description="copyparty with Pillow, FFmpeg, libvips (image/audio/video thumbnails, audio transcoding, media tags)"
|
org.opencontainers.image.description="copyparty with Pillow, FFmpeg, libvips (image/audio/video thumbnails, audio transcoding, media tags)"
|
||||||
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
|
ENV XDG_CONFIG_HOME=/cfg
|
||||||
XDG_CONFIG_HOME=/cfg
|
|
||||||
|
|
||||||
RUN apk add -U !pyc \
|
RUN apk add -U !pyc \
|
||||||
wget \
|
tzdata wget \
|
||||||
py3-argon2-cffi py3-pillow py3-pip py3-cffi \
|
py3-jinja2 py3-argon2-cffi py3-pillow py3-pip py3-cffi \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
vips-jxl vips-heif vips-poppler vips-magick \
|
vips-jxl vips-heif vips-poppler vips-magick \
|
||||||
&& apk add -t .bd \
|
&& apk add -t .bd \
|
||||||
@@ -18,13 +17,11 @@ RUN apk add -U !pyc \
|
|||||||
python3-dev py3-wheel \
|
python3-dev py3-wheel \
|
||||||
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
|
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
|
||||||
&& python3 -m pip install pyvips \
|
&& python3 -m pip install pyvips \
|
||||||
&& apk del py3-pip .bd \
|
&& apk del py3-pip .bd
|
||||||
&& rm -rf /var/cache/apk/* /tmp/pyc \
|
|
||||||
&& mkdir /cfg /w \
|
COPY i/dist/copyparty-sfx.py innvikler.sh ./
|
||||||
&& chmod 777 /cfg /w \
|
RUN ash innvikler.sh && rm innvikler.sh
|
||||||
&& echo % /cfg > initcfg
|
|
||||||
|
|
||||||
COPY i/dist/copyparty-sfx.py ./
|
|
||||||
WORKDIR /w
|
WORKDIR /w
|
||||||
EXPOSE 3923
|
EXPOSE 3923
|
||||||
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
|
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "-c", "/z/initcfg"]
|
||||||
|
|||||||
@@ -5,17 +5,14 @@ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
|
|||||||
org.opencontainers.image.licenses="MIT" \
|
org.opencontainers.image.licenses="MIT" \
|
||||||
org.opencontainers.image.title="copyparty-min" \
|
org.opencontainers.image.title="copyparty-min" \
|
||||||
org.opencontainers.image.description="just copyparty, no thumbnails / media tags / audio transcoding"
|
org.opencontainers.image.description="just copyparty, no thumbnails / media tags / audio transcoding"
|
||||||
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
|
ENV XDG_CONFIG_HOME=/cfg
|
||||||
XDG_CONFIG_HOME=/cfg
|
|
||||||
|
|
||||||
RUN apk --no-cache add !pyc \
|
RUN apk --no-cache add !pyc \
|
||||||
python3 \
|
py3-jinja2
|
||||||
&& rm -rf /tmp/pyc \
|
|
||||||
&& mkdir /cfg /w \
|
COPY i/dist/copyparty-sfx.py innvikler.sh ./
|
||||||
&& chmod 777 /cfg /w \
|
RUN ash innvikler.sh && rm innvikler.sh
|
||||||
&& echo % /cfg > initcfg
|
|
||||||
|
|
||||||
COPY i/dist/copyparty-sfx.py ./
|
|
||||||
WORKDIR /w
|
WORKDIR /w
|
||||||
EXPOSE 3923
|
EXPOSE 3923
|
||||||
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "--no-thumb", "-c", "/z/initcfg"]
|
ENTRYPOINT ["python3", "-m", "copyparty", "--no-crt", "--no-thumb", "-c", "/z/initcfg"]
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ this example is also available as a podman-compatible [docker-compose yaml](http
|
|||||||
i'm not very familiar with containers, so let me know if this section could be better 🙏
|
i'm not very familiar with containers, so let me know if this section could be better 🙏
|
||||||
|
|
||||||
|
|
||||||
|
## portainer
|
||||||
|
|
||||||
|
* there is a [portainer howto](https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/portainer.md) which is mostly untested
|
||||||
|
|
||||||
|
|
||||||
## configuration
|
## configuration
|
||||||
|
|
||||||
> this section basically explains how the [docker-compose yaml](https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/basic-docker-compose) works, so you may look there instead
|
> this section basically explains how the [docker-compose yaml](https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/basic-docker-compose) works, so you may look there instead
|
||||||
|
|||||||
46
scripts/docker/innvikler.sh
Normal file
46
scripts/docker/innvikler.sh
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/ash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# cleanup for flavors with python build steps (dj/iv)
|
||||||
|
rm -rf /var/cache/apk/* /root/.cache
|
||||||
|
|
||||||
|
# initial config; common for all flavors
|
||||||
|
mkdir /cfg /w
|
||||||
|
chmod 777 /cfg /w
|
||||||
|
echo % /cfg > initcfg
|
||||||
|
|
||||||
|
# unpack sfx and dive in
|
||||||
|
python3 copyparty-sfx.py --version
|
||||||
|
cd /tmp/pe-copyparty.0
|
||||||
|
|
||||||
|
# steal the stuff we need
|
||||||
|
mv copyparty partftpy ftp/* /usr/lib/python3.*/site-packages/
|
||||||
|
|
||||||
|
# golf
|
||||||
|
cd /usr/lib/python3.*/
|
||||||
|
rm -rf \
|
||||||
|
/tmp/pe-* /z/copyparty-sfx.py \
|
||||||
|
ensurepip pydoc_data turtle.py turtledemo lib2to3
|
||||||
|
|
||||||
|
# drop bytecode
|
||||||
|
find / -xdev -name __pycache__ -print0 | xargs -0 rm -rf
|
||||||
|
|
||||||
|
# build the stuff we want
|
||||||
|
python3 -m compileall -qj4 site-packages sqlite3 xml
|
||||||
|
|
||||||
|
# drop the stuff we dont
|
||||||
|
find -name __pycache__ |
|
||||||
|
grep -E 'ty/web/|/pycpar' |
|
||||||
|
tr '\n' '\0' | xargs -0 rm -rf
|
||||||
|
|
||||||
|
# two-for-one:
|
||||||
|
# 1) smoketest copyparty even starts
|
||||||
|
# 2) build any bytecode we missed
|
||||||
|
# this tends to race other builders (alle gode ting er tre)
|
||||||
|
cd /z
|
||||||
|
python3 -m copyparty \
|
||||||
|
--ign-ebind -p$((1024+RANDOM)),$((1024+RANDOM)),$((1024+RANDOM)) \
|
||||||
|
--no-crt -qi127.1 --exit=idx -e2dsa -e2ts
|
||||||
|
|
||||||
|
# output from -e2d
|
||||||
|
rm -rf .hist
|
||||||
@@ -6,6 +6,8 @@ set -e
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suf=-b1
|
||||||
|
suf=
|
||||||
sarchs="386 amd64 arm/v7 arm64/v8 ppc64le s390x"
|
sarchs="386 amd64 arm/v7 arm64/v8 ppc64le s390x"
|
||||||
archs="amd64 arm s390x 386 arm64 ppc64le"
|
archs="amd64 arm s390x 386 arm64 ppc64le"
|
||||||
imgs="dj iv min im ac"
|
imgs="dj iv min im ac"
|
||||||
@@ -98,16 +100,17 @@ filt=
|
|||||||
aa="$(printf '%11s' $a-$i)"
|
aa="$(printf '%11s' $a-$i)"
|
||||||
|
|
||||||
# arm takes forever so make it top priority
|
# arm takes forever so make it top priority
|
||||||
[ ${a::3} == arm ] && nice= || nice=nice
|
[ ${a::3} == arm ] && nice= || nice=-n20
|
||||||
|
|
||||||
# --pull=never does nothing at all btw
|
# --pull=never does nothing at all btw
|
||||||
(set -x
|
(set -x
|
||||||
$nice podman build \
|
nice $nice podman build \
|
||||||
|
--squash \
|
||||||
--pull=never \
|
--pull=never \
|
||||||
--from localhost/alpine-$a \
|
--from localhost/alpine-$a \
|
||||||
-t copyparty-$i-$a \
|
-t copyparty-$i-$a$suf \
|
||||||
-f Dockerfile.$i . ||
|
-f Dockerfile.$i . ||
|
||||||
(echo $? $i-$a >> err)
|
(echo $? $i-$a >> err; printf '%096d\n' $(seq 1 42))
|
||||||
rm -f .blk
|
rm -f .blk
|
||||||
) 2> >(tee $a.err | sed "s/^/$aa:/" >&2) > >(tee $a.out | sed "s/^/$aa:/") &
|
) 2> >(tee $a.err | sed "s/^/$aa:/" >&2) > >(tee $a.out | sed "s/^/$aa:/") &
|
||||||
done
|
done
|
||||||
@@ -134,9 +137,10 @@ filt=
|
|||||||
variants=
|
variants=
|
||||||
for a in $archs; do
|
for a in $archs; do
|
||||||
[[ " ${ngs[*]} " =~ " $i-$a " ]] && continue
|
[[ " ${ngs[*]} " =~ " $i-$a " ]] && continue
|
||||||
variants="$variants containers-storage:localhost/copyparty-$i-$a"
|
variants="$variants containers-storage:localhost/copyparty-$i-$a$suf"
|
||||||
done
|
done
|
||||||
podman manifest create copyparty-$i $variants
|
podman manifest rm copyparty-$i$suf || echo "(that's fine btw)"
|
||||||
|
podman manifest create copyparty-$i$suf $variants
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ set -e
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
berr() { p=$(head -c 72 </dev/zero | tr '\0' =); printf '\n%s\n\n' $p; cat; printf '\n%s\n\n' $p; }
|
berr() { p=$(head -c 72 </dev/zero | tr '\0' =); printf '\n%s\n\n' $p; cat; printf '\n%s\n\n' $p; }
|
||||||
|
aerr() { printf '%s\n' "$*" | berr; }
|
||||||
|
|
||||||
help() { exec cat <<'EOF'
|
help() { exec cat <<'EOF'
|
||||||
|
|
||||||
@@ -28,9 +29,11 @@ help() { exec cat <<'EOF'
|
|||||||
#
|
#
|
||||||
# `no-tfp` saves ~10k by removing the tftp server, disabling --tftp
|
# `no-tfp` saves ~10k by removing the tftp server, disabling --tftp
|
||||||
#
|
#
|
||||||
|
# `no-zm` saves ~7k by removing the zeroconf mDNS server
|
||||||
|
#
|
||||||
# `no-smb` saves ~3.5k by removing the smb / cifs server
|
# `no-smb` saves ~3.5k by removing the smb / cifs server
|
||||||
#
|
#
|
||||||
# `no-zm` saves ~k by removing the zeroconf mDNS server
|
# `no-pf` saves ~2.8k by removing the option to download partyfuse
|
||||||
#
|
#
|
||||||
# _____________________________________________________________________
|
# _____________________________________________________________________
|
||||||
# web features:
|
# web features:
|
||||||
@@ -52,10 +55,15 @@ help() { exec cat <<'EOF'
|
|||||||
#
|
#
|
||||||
# `ign-wd` allows building an sfx without webdeps
|
# `ign-wd` allows building an sfx without webdeps
|
||||||
#
|
#
|
||||||
# ---------------------------------------------------------------------
|
# _____________________________________________________________________
|
||||||
#
|
|
||||||
# if you are on windows, you can use msys2:
|
# if you are on windows, you can use msys2:
|
||||||
# PATH=/c/Users/$USER/AppData/Local/Programs/Python/Python310:"$PATH" ./make-sfx.sh fast
|
# PATH=/c/Users/$USER/AppData/Local/Programs/Python/Python310:"$PATH" ./make-sfx.sh fast
|
||||||
|
#
|
||||||
|
# _____________________________________________________________________
|
||||||
|
# some usage examples:
|
||||||
|
# ./scripts/make-sfx.sh lang eng no-cm no-hl no-dd no-fnt no-smb no-pf
|
||||||
|
# ./scripts/rls.sh sfx lang eng no-cm no-hl no-dd no-fnt no-smb no-pf
|
||||||
|
# (reduces v1.14.2 from 700k to 495k)
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -112,6 +120,7 @@ while [ ! -z "$1" ]; do
|
|||||||
no-tfp) no_tfp=1 ; ;;
|
no-tfp) no_tfp=1 ; ;;
|
||||||
no-smb) no_smb=1 ; ;;
|
no-smb) no_smb=1 ; ;;
|
||||||
no-zm) no_zm=1 ; ;;
|
no-zm) no_zm=1 ; ;;
|
||||||
|
no-pf) no_pf=1 ; ;;
|
||||||
no-fnt) no_fnt=1 ; ;;
|
no-fnt) no_fnt=1 ; ;;
|
||||||
no-hl) no_hl=1 ; ;;
|
no-hl) no_hl=1 ; ;;
|
||||||
no-dd) no_dd=1 ; ;;
|
no-dd) no_dd=1 ; ;;
|
||||||
@@ -119,7 +128,6 @@ while [ ! -z "$1" ]; do
|
|||||||
dl-wd) dl_wd=1 ; ;;
|
dl-wd) dl_wd=1 ; ;;
|
||||||
ign-wd) ign_wd=1 ; ;;
|
ign-wd) ign_wd=1 ; ;;
|
||||||
fast) zopf= ; ;;
|
fast) zopf= ; ;;
|
||||||
ultra) ultra=1 ; ;;
|
|
||||||
lang) shift;langs="$1"; ;;
|
lang) shift;langs="$1"; ;;
|
||||||
*) help ; ;;
|
*) help ; ;;
|
||||||
esac
|
esac
|
||||||
@@ -202,14 +210,14 @@ necho() {
|
|||||||
mv {markupsafe,jinja2} j2/
|
mv {markupsafe,jinja2} j2/
|
||||||
|
|
||||||
necho collecting pyftpdlib
|
necho collecting pyftpdlib
|
||||||
f="../build/pyftpdlib-1.5.9.tar.gz"
|
f="../build/pyftpdlib-1.5.10.tar.gz"
|
||||||
[ -e "$f" ] ||
|
[ -e "$f" ] ||
|
||||||
(url=https://github.com/giampaolo/pyftpdlib/archive/refs/tags/release-1.5.9.tar.gz;
|
(url=https://files.pythonhosted.org/packages/cf/31/8d910cf40317dd0db74ba0b8558d0dee23c8b002468c14d3a5dec0e6e9fd/pyftpdlib-1.5.10.tar.gz;
|
||||||
wget -O$f "$url" || curl -L "$url" >$f)
|
wget -O$f "$url" || curl -L "$url" >$f)
|
||||||
|
|
||||||
tar -zxf $f
|
tar -zxf $f
|
||||||
mv pyftpdlib-release-*/pyftpdlib .
|
mv pyftpdlib-*/pyftpdlib .
|
||||||
rm -rf pyftpdlib-release-* pyftpdlib/test
|
rm -rf pyftpdlib-* pyftpdlib/test
|
||||||
for f in pyftpdlib/_async{hat,ore}.py; do
|
for f in pyftpdlib/_async{hat,ore}.py; do
|
||||||
[ -e "$f" ] || continue;
|
[ -e "$f" ] || continue;
|
||||||
iawk 'NR<4||NR>27||!/^#/;NR==4{print"# license: https://opensource.org/licenses/ISC\n"}' $f
|
iawk 'NR<4||NR>27||!/^#/;NR==4{print"# license: https://opensource.org/licenses/ISC\n"}' $f
|
||||||
@@ -413,7 +421,7 @@ rm have
|
|||||||
|
|
||||||
ised /fork_process/d ftp/pyftpdlib/servers.py
|
ised /fork_process/d ftp/pyftpdlib/servers.py
|
||||||
iawk '/^class _Base/{s=1}!s' ftp/pyftpdlib/authorizers.py
|
iawk '/^class _Base/{s=1}!s' ftp/pyftpdlib/authorizers.py
|
||||||
iawk '/^ {0,4}[^ ]/{s=0}/^ {4}def (serve_forever|_loop)/{s=1}!s' ftp/pyftpdlib/servers.py
|
iawk '/^ {0,4}[a-zA-Z]/{s=0}/^ {4}def (serve_forever|_loop)/{s=1}!s' ftp/pyftpdlib/servers.py
|
||||||
rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
||||||
|
|
||||||
[ $no_ftp ] &&
|
[ $no_ftp ] &&
|
||||||
@@ -428,6 +436,9 @@ rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
|||||||
[ $no_zm ] &&
|
[ $no_zm ] &&
|
||||||
rm -rf copyparty/mdns.py copyparty/stolen/dnslib
|
rm -rf copyparty/mdns.py copyparty/stolen/dnslib
|
||||||
|
|
||||||
|
[ $no_pf ] &&
|
||||||
|
rm -rf copyparty/web/a/partyfuse.py
|
||||||
|
|
||||||
[ $no_cm ] && {
|
[ $no_cm ] && {
|
||||||
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
||||||
echo h > copyparty/web/mde.html
|
echo h > copyparty/web/mde.html
|
||||||
@@ -451,11 +462,16 @@ rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
|||||||
ised 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' $f
|
ised 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' $f
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $langs ] &&
|
[ $langs ] && {
|
||||||
|
echo $langs | grep -q eng || {
|
||||||
|
langs="eng|$langs"
|
||||||
|
aerr "ERROR: removing english is not supported; will do this instead: $langs"
|
||||||
|
}
|
||||||
for f in copyparty/web/{browser.js,splash.js}; do
|
for f in copyparty/web/{browser.js,splash.js}; do
|
||||||
gzip -d "$f.gz" || true
|
gzip -d "$f.gz" || true
|
||||||
iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f
|
iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} !l{next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f
|
||||||
done
|
done
|
||||||
|
}
|
||||||
|
|
||||||
[ ! $repack ] && {
|
[ ! $repack ] && {
|
||||||
# uncomment
|
# uncomment
|
||||||
@@ -593,7 +609,7 @@ pc="bzip2 -"; pe=bz2
|
|||||||
[ $use_gzz ] && pc="pigz -11 -I$use_gzz" && pe=gz
|
[ $use_gzz ] && pc="pigz -11 -I$use_gzz" && pe=gz
|
||||||
|
|
||||||
echo compressing tar
|
echo compressing tar
|
||||||
for n in {2..9}; do cp tar t.$n; nice $pc$n t.$n & done; wait
|
for n in {2..9}; do cp tar t.$n; nice -n20 $pc$n t.$n & done; wait
|
||||||
minf=$(for f in t.*.$pe; do
|
minf=$(for f in t.*.$pe; do
|
||||||
s1=$(wc -c <$f)
|
s1=$(wc -c <$f)
|
||||||
s2=$(tr -d '\r\n\0' <$f | wc -c)
|
s2=$(tr -d '\r\n\0' <$f | wc -c)
|
||||||
|
|||||||
@@ -34,6 +34,6 @@ d1420c8417fad7888766dd26b9706a87c63e8f33dceeb8e26d0056d5127b0b3ed9272e44b4b76113
|
|||||||
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
||||||
0203ec2551c4836696cfab0b2c9fff603352f03fa36e7476e2e1ca7ec57a3a0c24bd791fcd92f342bf817f0887854d9f072e0271c643de4b313d8c9569ba8813 packaging-24.1-py3-none-any.whl
|
0203ec2551c4836696cfab0b2c9fff603352f03fa36e7476e2e1ca7ec57a3a0c24bd791fcd92f342bf817f0887854d9f072e0271c643de4b313d8c9569ba8813 packaging-24.1-py3-none-any.whl
|
||||||
2be320b4191f208cdd6af183c77ba2cf460ea52164ee45ac3ff17d6dfa57acd9deff016636c2dd42a21f4f6af977d5f72df7dacf599bebcf41757272354d14c1 pillow-10.4.0-cp312-cp312-win_amd64.whl
|
2be320b4191f208cdd6af183c77ba2cf460ea52164ee45ac3ff17d6dfa57acd9deff016636c2dd42a21f4f6af977d5f72df7dacf599bebcf41757272354d14c1 pillow-10.4.0-cp312-cp312-win_amd64.whl
|
||||||
776378f5414efd26ec8a1cb3228a7b5fdf6afca3fa335a0e9b071266d55d9d9e66ee157c25a468a05bfa70ccd33c48b101998523fc6ff6bcf5e82a1d81ed0af8 pyinstaller-6.9.0-py3-none-win_amd64.whl
|
896ddddbd4b85e86e0600cb65eb4c07fbc7f3802d47e7f660411e20b5500831469b97ed4770f25820f4e75cbfac40308da624fd86d4f62e578149d5c276a9cde pyinstaller-6.10.0-py3-none-win_amd64.whl
|
||||||
c0af77d2a57cb063ab038dc986ed3582bc5acc8c8bd91d726101935d6388f50854ddbca26bc846ed5d1022cdee4d96242938c66f0ddc4565c36b60d691064db8 pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl
|
873781decaeef07f6a79b0ed8b9f35f3fa534a1ea0d866991e40278a10818fa5b60c70b0d5828971b045364f1099694cd1e5d5d60d480acb93fcfbfbced4a09e pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
||||||
2f9a11ffae6d9f1ed76bf816f28812fcba71f87080b0c92e52bfccb46243118c5803a7e25dd78003ca7d66501bfcdce8ff7c691c63c0038b0d409ca3842dcc89 python-3.12.4-amd64.exe
|
37fa7250b10b0c03b87d800bf4f920589649309cb4fbd25864475084bb7873d62b809a4fdeabd06c79f03f33614218eb7e01a9bd796de29dd3b141f1906d588c python-3.12.6-amd64.exe
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ fns=(
|
|||||||
mutagen-1.47.0-py3-none-any.whl
|
mutagen-1.47.0-py3-none-any.whl
|
||||||
packaging-24.1-py3-none-any.whl
|
packaging-24.1-py3-none-any.whl
|
||||||
pillow-10.4.0-cp312-cp312-win_amd64.whl
|
pillow-10.4.0-cp312-cp312-win_amd64.whl
|
||||||
pyinstaller-6.9.0-py3-none-win_amd64.whl
|
pyinstaller-6.10.0-py3-none-win_amd64.whl
|
||||||
pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl
|
pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
||||||
python-3.12.4-amd64.exe
|
python-3.12.6-amd64.exe
|
||||||
)
|
)
|
||||||
[ $w7 ] && fns+=( # u2c stuff
|
[ $w7 ] && fns+=( # u2c stuff
|
||||||
certifi-2024.2.2-py3-none-any.whl
|
certifi-2024.2.2-py3-none-any.whl
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ grep -E '^from .ssl_ import' $APPDATA/python/python37/site-packages/urllib3/util
|
|||||||
echo golfing
|
echo golfing
|
||||||
echo > $APPDATA/python/python37/site-packages/requests/certs.py
|
echo > $APPDATA/python/python37/site-packages/requests/certs.py
|
||||||
sed -ri 's/^(DEFAULT_CA_BUNDLE_PATH = ).*/\1""/' $APPDATA/python/python37/site-packages/requests/utils.py
|
sed -ri 's/^(DEFAULT_CA_BUNDLE_PATH = ).*/\1""/' $APPDATA/python/python37/site-packages/requests/utils.py
|
||||||
sed -ri '/^import zipfile$/d' $APPDATA/python/python37/site-packages/requests/utils.py
|
sed -ri '/^import zipfile$/d' $APPDATA/python/python37/site-packages/requests/utils.py
|
||||||
sed -ri 's/"idna"//' $APPDATA/python/python37/site-packages/requests/packages.py
|
sed -ri 's/"idna"//' $APPDATA/python/python37/site-packages/requests/packages.py
|
||||||
sed -ri 's/import charset_normalizer.*/pass/' $APPDATA/python/python37/site-packages/requests/compat.py
|
sed -ri 's/import charset_normalizer.*/pass/' $APPDATA/python/python37/site-packages/requests/compat.py
|
||||||
sed -ri 's/raise.*charset_normalizer.*/pass/' $APPDATA/python/python37/site-packages/requests/__init__.py
|
sed -ri 's/raise.*charset_normalizer.*/pass/' $APPDATA/python/python37/site-packages/requests/__init__.py
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# if specified, keep the following sfx flags last: gz gzz fast
|
||||||
|
|
||||||
parallel=1
|
parallel=1
|
||||||
|
|
||||||
[ -e make-sfx.sh ] || cd scripts
|
[ -e make-sfx.sh ] || cd scripts
|
||||||
@@ -35,6 +37,14 @@ f=../dist/copyparty-sfx
|
|||||||
|
|
||||||
$f$s.py --version >/dev/null
|
$f$s.py --version >/dev/null
|
||||||
|
|
||||||
|
while [ "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
gz*) break;;
|
||||||
|
fast) break;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
[ $parallel -gt 1 ] && {
|
[ $parallel -gt 1 ] && {
|
||||||
printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n'
|
printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n'
|
||||||
trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT
|
trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ copyparty/web/mde.html,
|
|||||||
copyparty/web/mde.js,
|
copyparty/web/mde.js,
|
||||||
copyparty/web/msg.css,
|
copyparty/web/msg.css,
|
||||||
copyparty/web/msg.html,
|
copyparty/web/msg.html,
|
||||||
|
copyparty/web/shares.css,
|
||||||
|
copyparty/web/shares.html,
|
||||||
|
copyparty/web/shares.js,
|
||||||
copyparty/web/splash.css,
|
copyparty/web/splash.css,
|
||||||
copyparty/web/splash.html,
|
copyparty/web/splash.html,
|
||||||
copyparty/web/splash.js,
|
copyparty/web/splash.js,
|
||||||
|
|||||||
@@ -3,34 +3,39 @@ set -ex
|
|||||||
|
|
||||||
# PYTHONPATH=.:~/dev/partftpy/ taskset -c 0 python3 -m copyparty -v srv::r -v srv/junk:junk:A --tftp 3969
|
# PYTHONPATH=.:~/dev/partftpy/ taskset -c 0 python3 -m copyparty -v srv::r -v srv/junk:junk:A --tftp 3969
|
||||||
|
|
||||||
get_src=~/dev/copyparty/srv/palette.flac
|
get_src=~/dev/copyparty/srv/ro/palette.flac
|
||||||
get_fn=${get_src##*/}
|
get_fp=ro/${get_src##*/} # server url
|
||||||
|
get_fn=${get_fp##*/} # just filename
|
||||||
|
|
||||||
put_src=~/Downloads/102.zip
|
put_src=~/Downloads/102.zip
|
||||||
put_dst=~/dev/copyparty/srv/junk/102.zip
|
put_dst=~/dev/copyparty/srv/junk/102.zip
|
||||||
|
|
||||||
|
export PATH="$PATH:$HOME/src/atftp-0.8.0"
|
||||||
|
|
||||||
cd /dev/shm
|
cd /dev/shm
|
||||||
|
|
||||||
echo curl get 1428 v4; curl --tftp-blksize 1428 tftp://127.0.0.1:3969/$get_fn | cmp $get_src || exit 1
|
echo curl get 1428 v4; curl --tftp-blksize 1428 tftp://127.0.0.1:3969/$get_fp | cmp $get_src || exit 1
|
||||||
echo curl get 1428 v6; curl --tftp-blksize 1428 tftp://[::1]:3969/$get_fn | cmp $get_src || exit 1
|
echo curl get 1428 v6; curl --tftp-blksize 1428 tftp://[::1]:3969/$get_fp | cmp $get_src || exit 1
|
||||||
|
|
||||||
echo curl put 1428 v4; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1
|
echo curl put 1428 v4; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1
|
||||||
echo curl put 1428 v6; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://[::1]:3969/junk/ && cmp $put_src $put_dst || exit 1
|
echo curl put 1428 v6; rm -f $put_dst && curl --tftp-blksize 1428 -T $put_src tftp://[::1]:3969/junk/ && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
echo atftp get 1428; rm -f $get_fn && ~/src/atftp/atftp --option "blksize 1428" -g -r $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1
|
echo atftp get 1428; rm -f $get_fn && atftp --option "blksize 1428" -g -r $get_fp -l $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1
|
||||||
|
|
||||||
echo atftp put 1428; rm -f $put_dst && ~/src/atftp/atftp --option "blksize 1428" 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1
|
echo atftp put 1428; rm -f $put_dst && atftp --option "blksize 1428" 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
echo tftp-hpa get; rm -f $put_dst && tftp -v -m binary 127.0.0.1 3969 -c get $get_fn && cmp $get_src $get_fn || exit 1
|
echo tftp-hpa get; rm -f $get_fn && tftp -v -m binary 127.0.0.1 3969 -c get $get_fp && cmp $get_src $get_fn || exit 1
|
||||||
|
|
||||||
echo tftp-hpa put; rm -f $put_dst && tftp -v -m binary 127.0.0.1 3969 -c put $put_src junk/102.zip && cmp $put_src $put_dst || exit 1
|
echo tftp-hpa put; rm -f $put_dst && tftp -v -m binary 127.0.0.1 3969 -c put $put_src junk/102.zip && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
echo curl get 512; curl tftp://127.0.0.1:3969/$get_fn | cmp $get_src || exit 1
|
echo curl get 512; curl tftp://127.0.0.1:3969/$get_fp | cmp $get_src || exit 1
|
||||||
|
|
||||||
echo curl put 512; rm -f $put_dst && curl -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1
|
echo curl put 512; rm -f $put_dst && curl -T $put_src tftp://127.0.0.1:3969/junk/ && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
echo atftp get 512; rm -f $get_fn && ~/src/atftp/atftp -g -r $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1
|
echo atftp get 512; rm -f $get_fn && atftp -g -r $get_fp -l $get_fn 127.0.0.1 3969 && cmp $get_fn $get_src || exit 1
|
||||||
|
|
||||||
echo atftp put 512; rm -f $put_dst && ~/src/atftp/atftp 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1
|
echo atftp put 512; rm -f $put_dst && atftp 127.0.0.1 3969 -p -l $put_src -r junk/102.zip && cmp $put_src $put_dst || exit 1
|
||||||
|
|
||||||
echo nice
|
echo nice
|
||||||
|
|
||||||
|
rm -f $get_fn
|
||||||
|
|||||||
50
scripts/tlcheck.sh
Executable file
50
scripts/tlcheck.sh
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# usage: ./scripts/tlcheck.sh eng chi copyparty/web/browser.js
|
||||||
|
|
||||||
|
awk <"$3" -v lang1=\"$1\": -v lang2=\"$2\": '
|
||||||
|
/^\t\}/{fa=0;fb=0}
|
||||||
|
!/":/{next}
|
||||||
|
$0~lang1{fa=1}
|
||||||
|
$0~lang2{fb=1}
|
||||||
|
fa{a[ia++]=$0}
|
||||||
|
fb{b[ib++]=$0}
|
||||||
|
END{for (i=0;i<ia;i++) printf "%s\n%s\n\n",a[i],b[i]}
|
||||||
|
' |
|
||||||
|
awk -v apos=\' -v quot=\" '
|
||||||
|
# count special chars and prefix to line
|
||||||
|
function c(ch) {
|
||||||
|
m=$0;
|
||||||
|
gsub(ch,"",m);
|
||||||
|
t=t sprintf("%s%d ", ch, length($0)-length(m))
|
||||||
|
}
|
||||||
|
!$0 && t!=tp {
|
||||||
|
print "\n\033[1;37;41m====DIFF===="
|
||||||
|
}
|
||||||
|
!$0 { print; next; }
|
||||||
|
{
|
||||||
|
tp=t; t="";
|
||||||
|
c(quot);
|
||||||
|
c(apos);
|
||||||
|
c("<");
|
||||||
|
c(">");
|
||||||
|
c("{");
|
||||||
|
c("}");
|
||||||
|
c("&");
|
||||||
|
c("\\\$");
|
||||||
|
c("\\\\");
|
||||||
|
print t $0;
|
||||||
|
}
|
||||||
|
' |
|
||||||
|
sed -r $'
|
||||||
|
s/\\\\/\033[1;37;41m\\\\\033[0m/g;
|
||||||
|
s/\$N/\033[1;37;45m$N\033[0m/g;
|
||||||
|
s/([{}])/\033[34m\\1\033[0m/g;
|
||||||
|
s/"/\033[44m"\033[0m/g;
|
||||||
|
s/\'/\033[45m\'\033[0m/g;
|
||||||
|
s/&/\033[1;43;30m&\033[0m/g;
|
||||||
|
s/([<>])/\033[30;47m\\1\033[0m/g
|
||||||
|
' |
|
||||||
|
sed -r 's/\t+//' |
|
||||||
|
less -R
|
||||||
@@ -20,7 +20,7 @@ cat $f | awk '
|
|||||||
o{next}
|
o{next}
|
||||||
/^#/{s=1;rs=0;pr()}
|
/^#/{s=1;rs=0;pr()}
|
||||||
/^#* *(nix package)/{rs=1}
|
/^#* *(nix package)/{rs=1}
|
||||||
/^#* *(install on android|dev env setup|just the sfx|complete release|optional gpl stuff|nixos module)|`$/{s=rs}
|
/^#* *(themes|install on android|dev env setup|just the sfx|complete release|optional gpl stuff|nixos module)|```/{s=rs}
|
||||||
/^#/{
|
/^#/{
|
||||||
lv=length($1);
|
lv=length($1);
|
||||||
sub(/[^ ]+ /,"");
|
sub(/[^ ]+ /,"");
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import sys
|
|||||||
import tokenize
|
import tokenize
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE
|
||||||
|
except:
|
||||||
|
FSTRING_MIDDLE = -9001
|
||||||
|
|
||||||
|
|
||||||
def uncomment(fpath):
|
def uncomment(fpath):
|
||||||
"""modified https://stackoverflow.com/a/62074206"""
|
"""modified https://stackoverflow.com/a/62074206"""
|
||||||
|
|
||||||
@@ -31,7 +37,7 @@ def uncomment(fpath):
|
|||||||
if start_line > last_lineno:
|
if start_line > last_lineno:
|
||||||
last_col = 0
|
last_col = 0
|
||||||
|
|
||||||
if start_col > last_col:
|
if start_col > last_col and prev_toktype != FSTRING_MIDDLE:
|
||||||
out += " " * (start_col - last_col)
|
out += " " * (start_col - last_col)
|
||||||
|
|
||||||
is_legalese = (
|
is_legalese = (
|
||||||
@@ -48,6 +54,10 @@ def uncomment(fpath):
|
|||||||
out += token_string
|
out += token_string
|
||||||
else:
|
else:
|
||||||
out += '"a"'
|
out += '"a"'
|
||||||
|
elif token_type == FSTRING_MIDDLE:
|
||||||
|
out += token_string.replace(r"{", r"{{").replace(r"}", r"}}")
|
||||||
|
if not code and token_string.strip():
|
||||||
|
code = True
|
||||||
elif token_type != tokenize.COMMENT:
|
elif token_type != tokenize.COMMENT:
|
||||||
out += token_string
|
out += token_string
|
||||||
if not code and token_string.strip():
|
if not code and token_string.strip():
|
||||||
|
|||||||
192
tests/test_dedup.py
Normal file
192
tests/test_dedup.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
from tests import util as tu
|
||||||
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
|
||||||
|
class TestDedup(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.td = tu.get_ramdisk()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(tempfile.gettempdir())
|
||||||
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
if os.path.exists(td):
|
||||||
|
shutil.rmtree(td)
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
return td
|
||||||
|
|
||||||
|
def cinit(self):
|
||||||
|
if self.conn:
|
||||||
|
self.fstab = self.conn.hsrv.hub.up2k.fstab
|
||||||
|
self.conn.hsrv.hub.up2k.shutdown()
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"", True)
|
||||||
|
if self.fstab:
|
||||||
|
self.conn.hsrv.hub.up2k.fstab = self.fstab
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
quick = True # sufficient for regular smoketests
|
||||||
|
# quick = False
|
||||||
|
|
||||||
|
dirnames = ["d1", "d2"]
|
||||||
|
filenames = ["f1", "f2"]
|
||||||
|
files = [
|
||||||
|
(
|
||||||
|
"one",
|
||||||
|
"BfcDQQeKz2oG1CPSFyD5ZD1flTYm2IoCY23DqeeVgq6w",
|
||||||
|
"XMbpLRqVdtGmgggqjUI6uSoNMTqZVX4K6zr74XA1BRKc",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"two",
|
||||||
|
"ko1Q0eJNq3zKYs_oT83Pn8aVFgonj5G1wK8itwnYL4qj",
|
||||||
|
"fxvihWlnQIbVbUPr--TxyV41913kPLhXPD1ngXYxDfou",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
# (data, chash, wark)
|
||||||
|
|
||||||
|
self.ctr = 336 if quick else 2016 # estimated total num uploads
|
||||||
|
self.conn = None
|
||||||
|
self.fstab = None
|
||||||
|
for e2d in [True, False]:
|
||||||
|
self.args = Cfg(v=[".::A"], a=[], e2d=e2d)
|
||||||
|
for cm1 in product(dirnames, filenames, files):
|
||||||
|
for cm2 in product(dirnames, filenames, files):
|
||||||
|
if cm1 == cm2:
|
||||||
|
continue
|
||||||
|
for cm3 in product(dirnames, filenames, files):
|
||||||
|
if cm3 in (cm1, cm2):
|
||||||
|
continue
|
||||||
|
|
||||||
|
f1 = cm1[2]
|
||||||
|
f2 = cm2[2]
|
||||||
|
f3 = cm3[2]
|
||||||
|
if not e2d:
|
||||||
|
rms = [-1]
|
||||||
|
elif f1 == f2:
|
||||||
|
if f1 == f3:
|
||||||
|
rms = [0, 1, 2]
|
||||||
|
else:
|
||||||
|
rms = [0, 1]
|
||||||
|
elif f1 == f3:
|
||||||
|
rms = [0, 2]
|
||||||
|
else:
|
||||||
|
rms = [1, 2]
|
||||||
|
|
||||||
|
for rm in rms:
|
||||||
|
self.do_tc(cm1, cm2, cm3, rm)
|
||||||
|
|
||||||
|
if quick:
|
||||||
|
break
|
||||||
|
|
||||||
|
def do_tc(self, cm1, cm2, cm3, irm):
|
||||||
|
dn1, fn1, f1 = cm1
|
||||||
|
dn2, fn2, f2 = cm2
|
||||||
|
dn3, fn3, f3 = cm3
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.cinit()
|
||||||
|
|
||||||
|
fn1 = self.do_post(dn1, fn1, f1, True)
|
||||||
|
fn2 = self.do_post(dn2, fn2, f2, False)
|
||||||
|
fn3 = self.do_post(dn3, fn3, f3, False)
|
||||||
|
|
||||||
|
if irm < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
cms = [(dn1, fn1, f1), (dn2, fn2, f2), (dn3, fn3, f3)]
|
||||||
|
rm = cms[irm]
|
||||||
|
dn, fn, _ = rm
|
||||||
|
h, b = self.curl("%s/%s?delete" % (dn, fn), meth="POST")
|
||||||
|
self.assertIn(" 200 OK", h)
|
||||||
|
self.assertIn("deleted 1 files", b)
|
||||||
|
h, b = self.curl("%s/%s" % (dn, fn))
|
||||||
|
self.assertIn(" 404 Not Fo", h)
|
||||||
|
for cm in cms:
|
||||||
|
if cm == rm:
|
||||||
|
continue
|
||||||
|
dn, fn, f = cm
|
||||||
|
h, b = self.curl("%s/%s" % (dn, fn))
|
||||||
|
self.assertEqual(b, f[0])
|
||||||
|
|
||||||
|
def do_post(self, dn, fn, fi, first):
|
||||||
|
print("\n\n# do_post", self.ctr, repr((dn, fn, fi, first)))
|
||||||
|
self.ctr -= 1
|
||||||
|
|
||||||
|
data, chash, wark = fi
|
||||||
|
hs = self.handshake(dn, fn, fi)
|
||||||
|
self.assertEqual(hs["wark"], wark)
|
||||||
|
|
||||||
|
sfn = hs["name"]
|
||||||
|
if sfn == fn:
|
||||||
|
print("using original name " + fn)
|
||||||
|
else:
|
||||||
|
print(fn + " got renamed to " + sfn)
|
||||||
|
if first:
|
||||||
|
raise Exception("wait what")
|
||||||
|
|
||||||
|
if hs["hash"]:
|
||||||
|
self.assertEqual(hs["hash"][0], chash)
|
||||||
|
self.put_chunk(dn, wark, chash, data)
|
||||||
|
elif first:
|
||||||
|
raise Exception("found first; %r, %r" % ((dn, fn, fi), hs))
|
||||||
|
|
||||||
|
h, b = self.curl("%s/%s" % (dn, sfn))
|
||||||
|
self.assertEqual(b, data)
|
||||||
|
return sfn
|
||||||
|
|
||||||
|
def handshake(self, dn, fn, fi):
|
||||||
|
hdr = "POST /%s/ HTTP/1.1\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n"
|
||||||
|
msg = {"name": fn, "size": 3, "lmod": 1234567890, "life": 0, "hash": [fi[1]]}
|
||||||
|
buf = json.dumps(msg).encode("utf-8")
|
||||||
|
buf = (hdr % (dn, len(buf))).encode("utf-8") + buf
|
||||||
|
print("HS -->", buf)
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("HS <--", ret)
|
||||||
|
return json.loads(ret[1])
|
||||||
|
|
||||||
|
def put_chunk(self, dn, wark, chash, data):
|
||||||
|
msg = [
|
||||||
|
"POST /%s/ HTTP/1.1" % (dn,),
|
||||||
|
"Connection: close",
|
||||||
|
"Content-Type: application/octet-stream",
|
||||||
|
"Content-Length: 3",
|
||||||
|
"X-Up2k-Hash: " + chash,
|
||||||
|
"X-Up2k-Wark: " + wark,
|
||||||
|
"",
|
||||||
|
data,
|
||||||
|
]
|
||||||
|
buf = "\r\n".join(msg).encode("utf-8")
|
||||||
|
print("PUT -->", buf)
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
self.assertEqual(ret[1], "thank")
|
||||||
|
|
||||||
|
def curl(self, url, binary=False, meth=None):
|
||||||
|
h = "%s /%s HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||||
|
h = h % (meth or "GET", url)
|
||||||
|
HttpCli(self.conn.setbuf(h.encode("utf-8"))).run()
|
||||||
|
if binary:
|
||||||
|
h, b = self.conn.s._reply.split(b"\r\n\r\n", 1)
|
||||||
|
return [h.decode("utf-8"), b]
|
||||||
|
|
||||||
|
return self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
|
||||||
|
def log(self, src, msg, c=0):
|
||||||
|
print(msg)
|
||||||
@@ -24,6 +24,10 @@ def hdr(query, uname):
|
|||||||
|
|
||||||
|
|
||||||
class TestDots(unittest.TestCase):
|
class TestDots(unittest.TestCase):
|
||||||
|
def __init__(self, *a, **ka):
|
||||||
|
super(TestDots, self).__init__(*a, **ka)
|
||||||
|
self.is_dut = True
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.td = tu.get_ramdisk()
|
self.td = tu.get_ramdisk()
|
||||||
|
|
||||||
|
|||||||
111
tests/test_hooks.py
Normal file
111
tests/test_hooks.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
from tests import util as tu
|
||||||
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
|
||||||
|
def hdr(query):
|
||||||
|
h = "GET /{} HTTP/1.1\r\nPW: o\r\nConnection: close\r\n\r\n"
|
||||||
|
return h.format(query).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
class TestHooks(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.td = tu.get_ramdisk()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(tempfile.gettempdir())
|
||||||
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
if os.path.exists(td):
|
||||||
|
shutil.rmtree(td)
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
return td
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
vcfg = ["a/b/c/d:c/d:A", "a:a:r"]
|
||||||
|
|
||||||
|
scenarios = (
|
||||||
|
('{"vp":"x/y"}', "c/d/a.png", "c/d/x/y/a.png"),
|
||||||
|
('{"vp":"x/y"}', "c/d/e/a.png", "c/d/e/x/y/a.png"),
|
||||||
|
('{"vp":"../x/y"}', "c/d/e/a.png", "c/d/x/y/a.png"),
|
||||||
|
('{"ap":"x/y"}', "c/d/a.png", "c/d/x/y/a.png"),
|
||||||
|
('{"ap":"x/y"}', "c/d/e/a.png", "c/d/e/x/y/a.png"),
|
||||||
|
('{"ap":"../x/y"}', "c/d/e/a.png", "c/d/x/y/a.png"),
|
||||||
|
('{"ap":"../x/y"}', "c/d/a.png", "a/b/c/x/y/a.png"),
|
||||||
|
('{"fn":"b.png"}', "c/d/a.png", "c/d/b.png"),
|
||||||
|
('{"vp":"x","fn":"b.png"}', "c/d/a.png", "c/d/x/b.png"),
|
||||||
|
)
|
||||||
|
|
||||||
|
for x in scenarios:
|
||||||
|
print("\n\n\n", x)
|
||||||
|
hooktxt, url_up, url_dl = x
|
||||||
|
for hooktype in ("xbu", "xau"):
|
||||||
|
for upfun in (self.put, self.bup):
|
||||||
|
self.reset()
|
||||||
|
self.makehook("""print('{"reloc":%s}')""" % (hooktxt,))
|
||||||
|
ka = {hooktype: ["j,c1,h.py"]}
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o"], e2d=True, **ka)
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
|
||||||
|
h, b = upfun(url_up)
|
||||||
|
self.assertIn("201 Created", h)
|
||||||
|
h, b = self.curl(url_dl)
|
||||||
|
self.assertEqual(b, "ok %s\n" % (url_up))
|
||||||
|
|
||||||
|
def makehook(self, hs):
|
||||||
|
with open("h.py", "wb") as f:
|
||||||
|
f.write(hs.encode("utf-8"))
|
||||||
|
|
||||||
|
def put(self, url):
|
||||||
|
buf = "PUT /{0} HTTP/1.1\r\nPW: o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
|
||||||
|
buf = buf.format(url, len(url) + 4).encode("utf-8")
|
||||||
|
print("PUT -->", buf)
|
||||||
|
conn = tu.VHttpConn(self.args, self.asrv, self.log, buf)
|
||||||
|
HttpCli(conn).run()
|
||||||
|
ret = conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("PUT <--", ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def bup(self, url):
|
||||||
|
hdr = "POST /%s HTTP/1.1\r\nPW: o\r\nConnection: close\r\nContent-Type: multipart/form-data; boundary=XD\r\nContent-Length: %d\r\n\r\n"
|
||||||
|
bdy = '--XD\r\nContent-Disposition: form-data; name="act"\r\n\r\nbput\r\n--XD\r\nContent-Disposition: form-data; name="f"; filename="%s"\r\n\r\n'
|
||||||
|
ftr = "\r\n--XD--\r\n"
|
||||||
|
try:
|
||||||
|
url, fn = url.rsplit("/", 1)
|
||||||
|
except:
|
||||||
|
fn = url
|
||||||
|
url = ""
|
||||||
|
|
||||||
|
buf = (bdy % (fn,) + "ok %s/%s\n" % (url, fn) + ftr).encode("utf-8")
|
||||||
|
buf = (hdr % (url, len(buf))).encode("utf-8") + buf
|
||||||
|
print("PoST -->", buf)
|
||||||
|
conn = tu.VHttpConn(self.args, self.asrv, self.log, buf)
|
||||||
|
HttpCli(conn).run()
|
||||||
|
ret = conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("POST <--", ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def curl(self, url, binary=False):
|
||||||
|
conn = tu.VHttpConn(self.args, self.asrv, self.log, hdr(url))
|
||||||
|
HttpCli(conn).run()
|
||||||
|
if binary:
|
||||||
|
h, b = conn.s._reply.split(b"\r\n\r\n", 1)
|
||||||
|
return [h.decode("utf-8"), b]
|
||||||
|
|
||||||
|
return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
|
||||||
|
def log(self, src, msg, c=0):
|
||||||
|
print(msg)
|
||||||
@@ -10,6 +10,7 @@ import tarfile
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from copyparty.authsrv import AuthSrv
|
from copyparty.authsrv import AuthSrv
|
||||||
from copyparty.httpcli import HttpCli
|
from copyparty.httpcli import HttpCli
|
||||||
@@ -31,6 +32,9 @@ class TestHttpCli(unittest.TestCase):
|
|||||||
shutil.rmtree(self.td)
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
|
test_tar = True
|
||||||
|
test_zip = True
|
||||||
|
|
||||||
td = os.path.join(self.td, "vfs")
|
td = os.path.join(self.td, "vfs")
|
||||||
os.mkdir(td)
|
os.mkdir(td)
|
||||||
os.chdir(td)
|
os.chdir(td)
|
||||||
@@ -40,6 +44,7 @@ class TestHttpCli(unittest.TestCase):
|
|||||||
self.can_write = ["wa", "wo", "aa", "ao"]
|
self.can_write = ["wa", "wo", "aa", "ao"]
|
||||||
self.fn = "g{:x}g".format(int(time.time() * 3))
|
self.fn = "g{:x}g".format(int(time.time() * 3))
|
||||||
|
|
||||||
|
tctr = 0
|
||||||
allfiles = []
|
allfiles = []
|
||||||
allvols = []
|
allvols = []
|
||||||
for top in self.dtypes:
|
for top in self.dtypes:
|
||||||
@@ -81,8 +86,10 @@ class TestHttpCli(unittest.TestCase):
|
|||||||
|
|
||||||
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
self.asrv = AuthSrv(self.args, self.log)
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
vfiles = [x for x in allfiles if x.startswith(top)]
|
vfiles = [x for x in allfiles if x.startswith(top)]
|
||||||
for fp in vfiles:
|
for fp in vfiles:
|
||||||
|
tctr += 1
|
||||||
rok, wok = self.can_rw(fp)
|
rok, wok = self.can_rw(fp)
|
||||||
furl = fp.split("/", 1)[1]
|
furl = fp.split("/", 1)[1]
|
||||||
durl = furl.rsplit("/", 1)[0] if "/" in furl else ""
|
durl = furl.rsplit("/", 1)[0] if "/" in furl else ""
|
||||||
@@ -112,39 +119,61 @@ class TestHttpCli(unittest.TestCase):
|
|||||||
eprint("\033[33m{}\n# {}\033[0m".format(ret, url))
|
eprint("\033[33m{}\n# {}\033[0m".format(ret, url))
|
||||||
self.fail()
|
self.fail()
|
||||||
|
|
||||||
# tar
|
# expected files in archives
|
||||||
url = durl + "?tar"
|
if rok:
|
||||||
h, b = self.curl(url, True)
|
|
||||||
# with open(os.path.join(td, "tar"), "wb") as f:
|
|
||||||
# f.write(b)
|
|
||||||
try:
|
|
||||||
tar = tarfile.open(fileobj=io.BytesIO(b), mode="r|").getnames()
|
|
||||||
except:
|
|
||||||
if "HTTP/1.1 403 Forbidden" not in h and b != b"\nJ2EOT":
|
|
||||||
eprint("bad tar?", url, h, b)
|
|
||||||
raise
|
|
||||||
tar = []
|
|
||||||
tar = [x.split("/", 1)[1] for x in tar]
|
|
||||||
tar = ["/".join([y for y in [top, durl, x] if y]) for x in tar]
|
|
||||||
tar = [[x] + self.can_rw(x) for x in tar]
|
|
||||||
tar_ok = [x[0] for x in tar if x[1]]
|
|
||||||
tar_ng = [x[0] for x in tar if not x[1]]
|
|
||||||
self.assertEqual([], tar_ng)
|
|
||||||
|
|
||||||
if durl.split("/")[-1] in self.can_read:
|
|
||||||
ref = [x for x in vfiles if self.in_dive(top + "/" + durl, x)]
|
ref = [x for x in vfiles if self.in_dive(top + "/" + durl, x)]
|
||||||
for f in ref:
|
|
||||||
ok = f in tar_ok
|
|
||||||
pr = print if ok else eprint
|
|
||||||
pr("{}: {}".format("ok" if ok else "NG", f))
|
|
||||||
ref.sort()
|
ref.sort()
|
||||||
|
else:
|
||||||
|
ref = []
|
||||||
|
|
||||||
|
if test_tar:
|
||||||
|
url = durl + "?tar"
|
||||||
|
h, b = self.curl(url, True)
|
||||||
|
try:
|
||||||
|
tar = tarfile.open(fileobj=io.BytesIO(b), mode="r|").getnames()
|
||||||
|
except:
|
||||||
|
if "HTTP/1.1 403 Forbidden" not in h and b != b"\nJ2EOT":
|
||||||
|
eprint("bad tar?", url, h, b)
|
||||||
|
raise
|
||||||
|
tar = []
|
||||||
|
tar = [x.split("/", 1)[1] for x in tar]
|
||||||
|
tar = ["/".join([y for y in [top, durl, x] if y]) for x in tar]
|
||||||
|
tar = [[x] + self.can_rw(x) for x in tar]
|
||||||
|
tar_ok = [x[0] for x in tar if x[1]]
|
||||||
|
tar_ng = [x[0] for x in tar if not x[1]]
|
||||||
tar_ok.sort()
|
tar_ok.sort()
|
||||||
self.assertEqual(ref, tar_ok)
|
self.assertEqual(ref, tar_ok)
|
||||||
|
self.assertEqual([], tar_ng)
|
||||||
|
|
||||||
|
if test_zip:
|
||||||
|
url = durl + "?zip"
|
||||||
|
h, b = self.curl(url, True)
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(io.BytesIO(b), "r") as zf:
|
||||||
|
zfi = zf.infolist()
|
||||||
|
except:
|
||||||
|
if "HTTP/1.1 403 Forbidden" not in h and b != b"\nJ2EOT":
|
||||||
|
eprint("bad zip?", url, h, b)
|
||||||
|
raise
|
||||||
|
zfi = []
|
||||||
|
zfn = [x.filename.split("/", 1)[1] for x in zfi]
|
||||||
|
zfn = ["/".join([y for y in [top, durl, x] if y]) for x in zfn]
|
||||||
|
zfn = [[x] + self.can_rw(x) for x in zfn]
|
||||||
|
zf_ok = [x[0] for x in zfn if x[1]]
|
||||||
|
zf_ng = [x[0] for x in zfn if not x[1]]
|
||||||
|
zf_ok.sort()
|
||||||
|
self.assertEqual(ref, zf_ok)
|
||||||
|
self.assertEqual([], zf_ng)
|
||||||
|
|
||||||
# stash
|
# stash
|
||||||
h, ret = self.put(url)
|
h, ret = self.put(url)
|
||||||
res = h.startswith("HTTP/1.1 201 ")
|
res = h.startswith("HTTP/1.1 201 ")
|
||||||
self.assertEqual(res, wok)
|
self.assertEqual(res, wok)
|
||||||
|
if wok:
|
||||||
|
vp = h.split("\nLocation: http://a:1/")[1].split("\r")[0]
|
||||||
|
vn, rem = self.asrv.vfs.get(vp, "*", False, False)
|
||||||
|
ap = os.path.join(vn.realpath, rem)
|
||||||
|
os.unlink(ap)
|
||||||
|
|
||||||
def can_rw(self, fp):
|
def can_rw(self, fp):
|
||||||
# lowest non-neutral folder declares permissions
|
# lowest non-neutral folder declares permissions
|
||||||
@@ -176,14 +205,14 @@ class TestHttpCli(unittest.TestCase):
|
|||||||
buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
|
buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
|
||||||
buf = buf.format(url, len(url) + 4).encode("utf-8")
|
buf = buf.format(url, len(url) + 4).encode("utf-8")
|
||||||
print("PUT -->", buf)
|
print("PUT -->", buf)
|
||||||
conn = tu.VHttpConn(self.args, self.asrv, self.log, buf)
|
conn = self.conn.setbuf(buf)
|
||||||
HttpCli(conn).run()
|
HttpCli(conn).run()
|
||||||
ret = conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
ret = conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
print("PUT <--", ret)
|
print("PUT <--", ret)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def curl(self, url, binary=False):
|
def curl(self, url, binary=False):
|
||||||
conn = tu.VHttpConn(self.args, self.asrv, self.log, hdr(url))
|
conn = self.conn.setbuf(hdr(url))
|
||||||
HttpCli(conn).run()
|
HttpCli(conn).run()
|
||||||
if binary:
|
if binary:
|
||||||
h, b = conn.s._reply.split(b"\r\n\r\n", 1)
|
h, b = conn.s._reply.split(b"\r\n\r\n", 1)
|
||||||
|
|||||||
198
tests/test_mv.py
Normal file
198
tests/test_mv.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
from copyparty.__init__ import PY2
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
from tests import util as tu
|
||||||
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
"""
|
||||||
|
TODO inject tags into db and verify ls
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestDedup(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.td = tu.get_ramdisk()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(tempfile.gettempdir())
|
||||||
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
if os.path.exists(td):
|
||||||
|
shutil.rmtree(td)
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
return td
|
||||||
|
|
||||||
|
def cinit(self):
|
||||||
|
if self.conn:
|
||||||
|
self.fstab = self.conn.hsrv.hub.up2k.fstab
|
||||||
|
self.conn.hsrv.hub.up2k.shutdown()
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"", True)
|
||||||
|
if self.fstab:
|
||||||
|
self.conn.hsrv.hub.up2k.fstab = self.fstab
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
if PY2:
|
||||||
|
raise unittest.SkipTest()
|
||||||
|
|
||||||
|
# tc_e2d = [True, False] # maybe-TODO only known symlinks are translated
|
||||||
|
tc_e2d = [True]
|
||||||
|
tc_dedup = ["sym", "no", "sym-no"]
|
||||||
|
tc_vols = [["::A"], ["::A", "d1:d1:A"]]
|
||||||
|
dirs = ["d1", "d1/d2", "d1/d2/d3", "d1/d4"]
|
||||||
|
files = [
|
||||||
|
(
|
||||||
|
"one",
|
||||||
|
"BfcDQQeKz2oG1CPSFyD5ZD1flTYm2IoCY23DqeeVgq6w",
|
||||||
|
"XMbpLRqVdtGmgggqjUI6uSoNMTqZVX4K6zr74XA1BRKc",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
# (data, chash, wark)
|
||||||
|
|
||||||
|
self.conn = None
|
||||||
|
self.fstab = None
|
||||||
|
self.ctr = 0 # 2304
|
||||||
|
tcgen = product(tc_e2d, tc_dedup, tc_vols, dirs, ["d9", "../d9"])
|
||||||
|
for e2d, dedup, vols, mv_from, dst in tcgen:
|
||||||
|
if "/" not in mv_from and dst.startswith(".."):
|
||||||
|
continue # would move past top of fs
|
||||||
|
if len(vols) > 1 and mv_from == "d1":
|
||||||
|
continue # cannot move a vol
|
||||||
|
|
||||||
|
# print(e2d, dedup, vols, mv_from, dst)
|
||||||
|
ka = {"e2d": e2d}
|
||||||
|
if dedup == "hard":
|
||||||
|
ka["hardlink"] = True
|
||||||
|
elif dedup == "no":
|
||||||
|
ka["no_dedup"] = True
|
||||||
|
self.args = Cfg(v=vols[:], a=[], **ka)
|
||||||
|
|
||||||
|
for u1, u2, u3, u4 in product(dirs, dirs, dirs, dirs):
|
||||||
|
ups = (u1, u2, u3, u4)
|
||||||
|
if len(set(ups)) < 4:
|
||||||
|
continue # not unique
|
||||||
|
|
||||||
|
t = "e2d:%s dedup:%s vols:%d from:%s to:%s"
|
||||||
|
t = t % (e2d, dedup, len(vols), mv_from, dst)
|
||||||
|
print("\n\n\033[0;7m# files:", ups, t, "\033[0m")
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
self.cinit()
|
||||||
|
|
||||||
|
for up in [u1, u2, u3, u4]:
|
||||||
|
self.do_post(up, "fn", files[0], up == u1)
|
||||||
|
|
||||||
|
restore_args = None
|
||||||
|
if dedup == "sym-no":
|
||||||
|
restore_args = self.args
|
||||||
|
ka = {"e2d": e2d, "no_dedup": True}
|
||||||
|
self.args = Cfg(v=vols[:], a=[], **ka)
|
||||||
|
self.cinit()
|
||||||
|
|
||||||
|
mv_to = mv_from
|
||||||
|
for _ in range(2 if dst.startswith("../") else 1):
|
||||||
|
mv_to = mv_from.rsplit("/", 1)[0] if "/" in mv_from else ""
|
||||||
|
mv_to += "/" + dst.lstrip("./")
|
||||||
|
|
||||||
|
self.do_mv(mv_from, mv_to)
|
||||||
|
|
||||||
|
for dirpath in [u1, u2, u3, u4]:
|
||||||
|
if dirpath == mv_from:
|
||||||
|
dirpath = mv_to
|
||||||
|
elif dirpath.startswith(mv_from):
|
||||||
|
dirpath = mv_to + dirpath[len(mv_from) :]
|
||||||
|
h, b = self.curl(dirpath + "/fn")
|
||||||
|
self.assertEqual(b, "one")
|
||||||
|
|
||||||
|
if restore_args:
|
||||||
|
self.args = restore_args
|
||||||
|
|
||||||
|
def do_mv(self, src, dst):
|
||||||
|
hdr = "POST /%s?move=/%s HTTP/1.1\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
|
||||||
|
buf = (hdr % (src, dst)).encode("utf-8")
|
||||||
|
print("MV [%s] => [%s]" % (src, dst))
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("MV <-- ", ret)
|
||||||
|
self.assertIn(" 201 Created", ret[0])
|
||||||
|
self.assertEqual("k\r\n", ret[1])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def do_post(self, dn, fn, fi, first):
|
||||||
|
print("\n# do_post", self.ctr, repr((dn, fn, fi, first)))
|
||||||
|
self.ctr -= 1
|
||||||
|
|
||||||
|
data, chash, wark = fi
|
||||||
|
hs = self.handshake(dn, fn, fi)
|
||||||
|
self.assertEqual(hs["wark"], wark)
|
||||||
|
|
||||||
|
sfn = hs["name"]
|
||||||
|
if sfn == fn:
|
||||||
|
print("using original name " + fn)
|
||||||
|
else:
|
||||||
|
print(fn + " got renamed to " + sfn)
|
||||||
|
if first:
|
||||||
|
raise Exception("wait what")
|
||||||
|
|
||||||
|
if hs["hash"]:
|
||||||
|
self.assertEqual(hs["hash"][0], chash)
|
||||||
|
self.put_chunk(dn, wark, chash, data)
|
||||||
|
elif first:
|
||||||
|
raise Exception("found first; %r, %r" % ((dn, fn, fi), hs))
|
||||||
|
|
||||||
|
h, b = self.curl("%s/%s" % (dn, sfn))
|
||||||
|
self.assertEqual(b, data)
|
||||||
|
|
||||||
|
def handshake(self, dn, fn, fi):
|
||||||
|
hdr = "POST /%s/ HTTP/1.1\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n"
|
||||||
|
msg = {"name": fn, "size": 3, "lmod": 1234567890, "life": 0, "hash": [fi[1]]}
|
||||||
|
buf = json.dumps(msg).encode("utf-8")
|
||||||
|
buf = (hdr % (dn, len(buf))).encode("utf-8") + buf
|
||||||
|
print("HS -->", buf)
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
print("HS <--", ret)
|
||||||
|
return json.loads(ret[1])
|
||||||
|
|
||||||
|
def put_chunk(self, dn, wark, chash, data):
|
||||||
|
msg = [
|
||||||
|
"POST /%s/ HTTP/1.1" % (dn,),
|
||||||
|
"Connection: close",
|
||||||
|
"Content-Type: application/octet-stream",
|
||||||
|
"Content-Length: 3",
|
||||||
|
"X-Up2k-Hash: " + chash,
|
||||||
|
"X-Up2k-Wark: " + wark,
|
||||||
|
"",
|
||||||
|
data,
|
||||||
|
]
|
||||||
|
buf = "\r\n".join(msg).encode("utf-8")
|
||||||
|
print("PUT -->", buf)
|
||||||
|
HttpCli(self.conn.setbuf(buf)).run()
|
||||||
|
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
self.assertEqual(ret[1], "thank")
|
||||||
|
|
||||||
|
def curl(self, url, binary=False):
|
||||||
|
h = "GET /%s HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||||
|
HttpCli(self.conn.setbuf((h % (url,)).encode("utf-8"))).run()
|
||||||
|
if binary:
|
||||||
|
h, b = self.conn.s._reply.split(b"\r\n\r\n", 1)
|
||||||
|
return [h.decode("utf-8"), b]
|
||||||
|
|
||||||
|
return self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
|
||||||
|
def log(self, src, msg, c=0):
|
||||||
|
print(msg)
|
||||||
@@ -235,7 +235,7 @@ class TestVFS(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
u a:123
|
u a:123
|
||||||
u asd:fgh:jkl
|
u asd:fgh:jkl
|
||||||
|
|
||||||
./src
|
./src
|
||||||
/dst
|
/dst
|
||||||
r a
|
r a
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -15,9 +15,7 @@ from argparse import Namespace
|
|||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
WINDOWS = platform.system() == "Windows"
|
from copyparty.__init__ import MACOS, WINDOWS, E
|
||||||
ANYWIN = WINDOWS or sys.platform in ["msys"]
|
|
||||||
MACOS = platform.system() == "Darwin"
|
|
||||||
|
|
||||||
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) # type: ignore
|
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) # type: ignore
|
||||||
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT")
|
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT")
|
||||||
@@ -41,10 +39,11 @@ if MACOS:
|
|||||||
# 25% faster; until any tests do symlink stuff
|
# 25% faster; until any tests do symlink stuff
|
||||||
|
|
||||||
|
|
||||||
from copyparty.__init__ import E
|
|
||||||
from copyparty.__main__ import init_E
|
from copyparty.__main__ import init_E
|
||||||
|
from copyparty.broker_thr import BrokerThr
|
||||||
from copyparty.ico import Ico
|
from copyparty.ico import Ico
|
||||||
from copyparty.u2idx import U2idx
|
from copyparty.u2idx import U2idx
|
||||||
|
from copyparty.up2k import Up2k
|
||||||
from copyparty.util import FHC, CachedDict, Garda, Unrecv
|
from copyparty.util import FHC, CachedDict, Garda, Unrecv
|
||||||
|
|
||||||
init_E(E)
|
init_E(E)
|
||||||
@@ -68,6 +67,13 @@ def chkcmd(argv):
|
|||||||
|
|
||||||
def get_ramdisk():
|
def get_ramdisk():
|
||||||
def subdir(top):
|
def subdir(top):
|
||||||
|
for d in os.listdir(top):
|
||||||
|
if not d.startswith("cptd-"):
|
||||||
|
continue
|
||||||
|
p = os.path.join(top, d)
|
||||||
|
st = os.stat(p)
|
||||||
|
if time.time() - st.st_mtime > 300:
|
||||||
|
shutil.rmtree(p)
|
||||||
ret = os.path.join(top, "cptd-{}".format(os.getpid()))
|
ret = os.path.join(top, "cptd-{}".format(os.getpid()))
|
||||||
shutil.rmtree(ret, True)
|
shutil.rmtree(ret, True)
|
||||||
os.mkdir(ret)
|
os.mkdir(ret)
|
||||||
@@ -111,16 +117,16 @@ class Cfg(Namespace):
|
|||||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver xdev xlink xvol"
|
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_dav no_db_ip no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dotpart dotsrch no_dhash no_fastboot no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
|
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
|
||||||
ka.update(**{k: True for k in ex.split()})
|
ka.update(**{k: True for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_cli ah_gen css_browser hist js_browser mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua"
|
ex = "ah_cli ah_gen css_browser hist js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua"
|
||||||
ka.update(**{k: None for k in ex.split()})
|
ka.update(**{k: None for k in ex.split()})
|
||||||
|
|
||||||
ex = "hash_mt srch_time u2abort u2j u2sz"
|
ex = "hash_mt safe_dedup srch_time u2abort u2j u2sz"
|
||||||
ka.update(**{k: 1 for k in ex.split()})
|
ka.update(**{k: 1 for k in ex.split()})
|
||||||
|
|
||||||
ex = "au_vol mtab_age reg_cap s_thead s_tbody th_convt"
|
ex = "au_vol mtab_age reg_cap s_thead s_tbody th_convt"
|
||||||
@@ -129,9 +135,12 @@ class Cfg(Namespace):
|
|||||||
ex = "db_act k304 loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
ex = "db_act k304 loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_alg bname doctitle df exit favico idp_h_usr html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i tcolor textfiles unlist vname R RS SR"
|
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src R RS SR"
|
||||||
ka.update(**{k: "" for k in ex.split()})
|
ka.update(**{k: "" for k in ex.split()})
|
||||||
|
|
||||||
|
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
|
||||||
|
ka.update(**{k: "no" for k in ex.split()})
|
||||||
|
|
||||||
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
||||||
ka.update(**{k: [] for k in ex.split()})
|
ka.update(**{k: [] for k in ex.split()})
|
||||||
|
|
||||||
@@ -162,6 +171,7 @@ class Cfg(Namespace):
|
|||||||
s_wr_sz=256 * 1024,
|
s_wr_sz=256 * 1024,
|
||||||
sort="href",
|
sort="href",
|
||||||
srch_hits=99999,
|
srch_hits=99999,
|
||||||
|
SRS="/",
|
||||||
th_covers=["folder.png"],
|
th_covers=["folder.png"],
|
||||||
th_coversd=["folder.png"],
|
th_coversd=["folder.png"],
|
||||||
th_covers_set=set(["folder.png"]),
|
th_covers_set=set(["folder.png"]),
|
||||||
@@ -178,6 +188,10 @@ class Cfg(Namespace):
|
|||||||
|
|
||||||
|
|
||||||
class NullBroker(object):
|
class NullBroker(object):
|
||||||
|
def __init__(self, args, asrv):
|
||||||
|
self.args = args
|
||||||
|
self.asrv = asrv
|
||||||
|
|
||||||
def say(self, *args):
|
def say(self, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -189,6 +203,7 @@ class VSock(object):
|
|||||||
def __init__(self, buf):
|
def __init__(self, buf):
|
||||||
self._query = buf
|
self._query = buf
|
||||||
self._reply = b""
|
self._reply = b""
|
||||||
|
self.family = socket.AF_INET
|
||||||
self.sendall = self.send
|
self.sendall = self.send
|
||||||
|
|
||||||
def recv(self, sz):
|
def recv(self, sz):
|
||||||
@@ -207,19 +222,37 @@ class VSock(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VHub(object):
|
||||||
|
def __init__(self, args, asrv, log):
|
||||||
|
self.args = args
|
||||||
|
self.asrv = asrv
|
||||||
|
self.log = log
|
||||||
|
self.is_dut = True
|
||||||
|
self.up2k = Up2k(self)
|
||||||
|
|
||||||
|
|
||||||
|
class VBrokerThr(BrokerThr):
|
||||||
|
def __init__(self, hub):
|
||||||
|
self.hub = hub
|
||||||
|
self.log = hub.log
|
||||||
|
self.args = hub.args
|
||||||
|
self.asrv = hub.asrv
|
||||||
|
|
||||||
|
|
||||||
class VHttpSrv(object):
|
class VHttpSrv(object):
|
||||||
def __init__(self, args, asrv, log):
|
def __init__(self, args, asrv, log):
|
||||||
self.args = args
|
self.args = args
|
||||||
self.asrv = asrv
|
self.asrv = asrv
|
||||||
self.log = log
|
self.log = log
|
||||||
|
self.hub = None
|
||||||
|
|
||||||
self.broker = NullBroker()
|
self.broker = NullBroker(args, asrv)
|
||||||
self.prism = None
|
self.prism = None
|
||||||
self.bans = {}
|
self.bans = {}
|
||||||
self.nreq = 0
|
self.nreq = 0
|
||||||
self.nsus = 0
|
self.nsus = 0
|
||||||
|
|
||||||
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
aliases = ["splash", "shares", "browser", "browser2", "msg", "md", "mde"]
|
||||||
self.j2 = {x: J2_FILES for x in aliases}
|
self.j2 = {x: J2_FILES for x in aliases}
|
||||||
|
|
||||||
self.gpwd = Garda("")
|
self.gpwd = Garda("")
|
||||||
@@ -238,18 +271,25 @@ class VHttpSrv(object):
|
|||||||
return self.u2idx
|
return self.u2idx
|
||||||
|
|
||||||
|
|
||||||
|
class VHttpSrvUp2k(VHttpSrv):
|
||||||
|
def __init__(self, args, asrv, log):
|
||||||
|
super(VHttpSrvUp2k, self).__init__(args, asrv, log)
|
||||||
|
self.hub = VHub(args, asrv, log)
|
||||||
|
self.broker = VBrokerThr(self.hub)
|
||||||
|
|
||||||
|
|
||||||
class VHttpConn(object):
|
class VHttpConn(object):
|
||||||
def __init__(self, args, asrv, log, buf):
|
def __init__(self, args, asrv, log, buf, use_up2k=False):
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
self.s = VSock(buf)
|
|
||||||
self.sr = Unrecv(self.s, None) # type: ignore
|
|
||||||
self.aclose = {}
|
self.aclose = {}
|
||||||
self.addr = ("127.0.0.1", "42069")
|
self.addr = ("127.0.0.1", "42069")
|
||||||
self.args = args
|
self.args = args
|
||||||
self.asrv = asrv
|
self.asrv = asrv
|
||||||
self.bans = {}
|
self.bans = {}
|
||||||
self.freshen_pwd = 0.0
|
self.freshen_pwd = 0.0
|
||||||
self.hsrv = VHttpSrv(args, asrv, log)
|
|
||||||
|
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
|
||||||
|
self.hsrv = Ctor(args, asrv, log)
|
||||||
self.ico = Ico(args)
|
self.ico = Ico(args)
|
||||||
self.ipa_nm = None
|
self.ipa_nm = None
|
||||||
self.lf_url = None
|
self.lf_url = None
|
||||||
@@ -265,6 +305,12 @@ class VHttpConn(object):
|
|||||||
self.u2fh = FHC()
|
self.u2fh = FHC()
|
||||||
|
|
||||||
self.get_u2idx = self.hsrv.get_u2idx
|
self.get_u2idx = self.hsrv.get_u2idx
|
||||||
|
self.setbuf(buf)
|
||||||
|
|
||||||
|
def setbuf(self, buf):
|
||||||
|
self.s = VSock(buf)
|
||||||
|
self.sr = Unrecv(self.s, None) # type: ignore
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
|
|||||||
Reference in New Issue
Block a user