Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5d822c70a | ||
|
|
9c09b4061a | ||
|
|
c26fb43ced | ||
|
|
deb8f20db6 | ||
|
|
50e18ed8ff | ||
|
|
31f3895f40 | ||
|
|
615929268a | ||
|
|
b8b15814cf | ||
|
|
7766fffe83 | ||
|
|
2a16c150d1 | ||
|
|
418c2166cc | ||
|
|
a4dd44f648 | ||
|
|
5352f7cda7 | ||
|
|
5533b47099 | ||
|
|
e9b14464ee | ||
|
|
4e986e5cd1 | ||
|
|
8a59b40c53 | ||
|
|
391caca043 | ||
|
|
171ce348d6 | ||
|
|
c2cc729135 | ||
|
|
e7e71b76f0 | ||
|
|
a2af61cf6f | ||
|
|
e111edd5e4 | ||
|
|
3375377371 | ||
|
|
0ced020c67 | ||
|
|
c0d7aa9e4a | ||
|
|
e5b3d2a312 | ||
|
|
7b4a794981 | ||
|
|
86a859de17 | ||
|
|
b3aaa7bd0f | ||
|
|
a90586e6a8 | ||
|
|
807f272895 | ||
|
|
f050647b43 | ||
|
|
73baebbd16 | ||
|
|
f327f698b9 | ||
|
|
8164910fe8 | ||
|
|
3498644055 | ||
|
|
d31116b54c | ||
|
|
aced110cdf | ||
|
|
e9ab6aec77 | ||
|
|
15b261c861 | ||
|
|
970badce66 | ||
|
|
64304a9d65 | ||
|
|
d1983553d2 | ||
|
|
6b15df3bcd | ||
|
|
730b1fff71 | ||
|
|
c3add751e5 | ||
|
|
9da2dbdc1c | ||
|
|
977f09c470 | ||
|
|
4d0c6a8802 | ||
|
|
5345565037 | ||
|
|
be38c27c64 | ||
|
|
82a0401099 | ||
|
|
33bea1b663 | ||
|
|
f083acd46d | ||
|
|
5aacd15272 | ||
|
|
cb7674b091 | ||
|
|
3899c7ad56 | ||
|
|
d2debced09 | ||
|
|
b86c0ddc48 | ||
|
|
ba36f33bd8 | ||
|
|
49368a10ba | ||
|
|
ac1568cacf | ||
|
|
862ca3439d | ||
|
|
fdd4f9f2aa | ||
|
|
aa2dc49ebe | ||
|
|
cc23b7ee74 | ||
|
|
f6f9fc5a45 | ||
|
|
26c8589399 | ||
|
|
c2469935cb | ||
|
|
5e7c20955e | ||
|
|
967fa38108 | ||
|
|
280fe8e36b | ||
|
|
03ca96ccc3 | ||
|
|
b5b8a2c9d5 | ||
|
|
0008832730 |
143
README.md
143
README.md
@@ -46,7 +46,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
||||||
* [other tricks](#other-tricks)
|
* [other tricks](#other-tricks)
|
||||||
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
||||||
* [server config](#server-config)
|
* [server config](#server-config) - using arguments or config files, or a mix of both
|
||||||
* [file indexing](#file-indexing)
|
* [file indexing](#file-indexing)
|
||||||
* [upload rules](#upload-rules) - set upload rules using volume flags
|
* [upload rules](#upload-rules) - set upload rules using volume flags
|
||||||
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
||||||
@@ -60,11 +60,17 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [up2k](#up2k) - quick outline of the up2k protocol, see [uploading](#uploading) for the web-client
|
* [up2k](#up2k) - quick outline of the up2k protocol, see [uploading](#uploading) for the web-client
|
||||||
* [why chunk-hashes](#why-chunk-hashes) - a single sha512 would be better, right?
|
* [why chunk-hashes](#why-chunk-hashes) - a single sha512 would be better, right?
|
||||||
* [performance](#performance) - defaults are usually fine - expect `8 GiB/s` download, `1 GiB/s` upload
|
* [performance](#performance) - defaults are usually fine - expect `8 GiB/s` download, `1 GiB/s` upload
|
||||||
|
* [client-side](#client-side) - when uploading files
|
||||||
* [security](#security) - some notes on hardening
|
* [security](#security) - some notes on hardening
|
||||||
* [gotchas](#gotchas) - behavior that might be unexpected
|
* [gotchas](#gotchas) - behavior that might be unexpected
|
||||||
* [recovering from crashes](#recovering-from-crashes)
|
* [recovering from crashes](#recovering-from-crashes)
|
||||||
* [client crashes](#client-crashes)
|
* [client crashes](#client-crashes)
|
||||||
* [frefox wsod](#frefox-wsod) - firefox 87 can crash during uploads
|
* [frefox wsod](#frefox-wsod) - firefox 87 can crash during uploads
|
||||||
|
* [HTTP API](#HTTP-API)
|
||||||
|
* [read](#read)
|
||||||
|
* [write](#write)
|
||||||
|
* [admin](#admin)
|
||||||
|
* [general](#general)
|
||||||
* [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
|
||||||
* [install recommended deps](#install-recommended-deps)
|
* [install recommended deps](#install-recommended-deps)
|
||||||
@@ -153,7 +159,7 @@ feature summary
|
|||||||
* browser
|
* browser
|
||||||
* ☑ [navpane](#navpane) (directory tree sidebar)
|
* ☑ [navpane](#navpane) (directory tree sidebar)
|
||||||
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
||||||
* ☑ audio player (with OS media controls)
|
* ☑ audio player (with OS media controls and opus transcoding)
|
||||||
* ☑ image gallery with webm player
|
* ☑ image gallery with webm player
|
||||||
* ☑ [thumbnails](#thumbnails)
|
* ☑ [thumbnails](#thumbnails)
|
||||||
* ☑ ...of images using Pillow
|
* ☑ ...of images using Pillow
|
||||||
@@ -218,6 +224,7 @@ some improvement ideas
|
|||||||
* Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
|
* Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
|
||||||
* Windows: python 2.7 cannot handle filenames with mojibake
|
* Windows: python 2.7 cannot handle filenames with mojibake
|
||||||
* `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
|
* `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
|
||||||
|
* `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
|
||||||
|
|
||||||
## general bugs
|
## general bugs
|
||||||
|
|
||||||
@@ -249,7 +256,10 @@ some improvement ideas
|
|||||||
|
|
||||||
# accounts and volumes
|
# accounts and volumes
|
||||||
|
|
||||||
per-folder, per-user permissions
|
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
|
||||||
|
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if logged in as admin)
|
||||||
|
|
||||||
|
configuring accounts/volumes with arguments:
|
||||||
* `-a usr:pwd` adds account `usr` with password `pwd`
|
* `-a usr:pwd` adds account `usr` with password `pwd`
|
||||||
* `-v .::r` adds current-folder `.` as the webroot, `r`eadable by anyone
|
* `-v .::r` adds current-folder `.` as the webroot, `r`eadable by anyone
|
||||||
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
|
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
|
||||||
@@ -304,6 +314,7 @@ the browser has the following hotkeys (always qwerty)
|
|||||||
* `B` toggle breadcrumbs / [navpane](#navpane)
|
* `B` toggle breadcrumbs / [navpane](#navpane)
|
||||||
* `I/K` prev/next folder
|
* `I/K` prev/next folder
|
||||||
* `M` parent folder (or unexpand current)
|
* `M` parent folder (or unexpand current)
|
||||||
|
* `V` toggle folders / textfiles in the navpane
|
||||||
* `G` toggle list / [grid view](#thumbnails)
|
* `G` toggle list / [grid view](#thumbnails)
|
||||||
* `T` toggle thumbnails / icons
|
* `T` toggle thumbnails / icons
|
||||||
* `ctrl-X` cut selected files/folders
|
* `ctrl-X` cut selected files/folders
|
||||||
@@ -315,6 +326,10 @@ the browser has the following hotkeys (always qwerty)
|
|||||||
* ctrl+`Up/Down` move cursor and scroll viewport
|
* ctrl+`Up/Down` move cursor and scroll viewport
|
||||||
* `Space` toggle file selection
|
* `Space` toggle file selection
|
||||||
* `Ctrl-A` toggle select all
|
* `Ctrl-A` toggle select all
|
||||||
|
* when a textfile is open:
|
||||||
|
* `I/K` prev/next textfile
|
||||||
|
* `S` toggle selection of open file
|
||||||
|
* `M` close textfile
|
||||||
* when playing audio:
|
* when playing audio:
|
||||||
* `J/L` prev/next song
|
* `J/L` prev/next song
|
||||||
* `U/O` skip 10sec back/forward
|
* `U/O` skip 10sec back/forward
|
||||||
@@ -366,6 +381,8 @@ press `g` to toggle grid-view instead of the file listing, and `t` toggles icon
|
|||||||
|
|
||||||
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
|
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
|
||||||
|
|
||||||
|
audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`)
|
||||||
|
|
||||||
images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg`
|
images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg`
|
||||||
|
|
||||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
||||||
@@ -421,6 +438,8 @@ see [up2k](#up2k) for details on how it works
|
|||||||
|
|
||||||
**protip:** you can avoid scaring away users with [docs/minimal-up2k.html](docs/minimal-up2k.html) which makes it look [much simpler](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
|
**protip:** you can avoid scaring away users with [docs/minimal-up2k.html](docs/minimal-up2k.html) which makes it look [much simpler](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png)
|
||||||
|
|
||||||
|
**protip:** if you enable `favicon` in the `[⚙️] settings` tab (by typing something into the textbox), the icon in the browser tab will indicate upload progress
|
||||||
|
|
||||||
the up2k UI is the epitome of polished inutitive experiences:
|
the up2k UI is the epitome of polished inutitive experiences:
|
||||||
* "parallel uploads" specifies how many chunks to upload at the same time
|
* "parallel uploads" specifies how many chunks to upload at the same time
|
||||||
* `[🏃]` analysis of other files should continue while one is uploading
|
* `[🏃]` analysis of other files should continue while one is uploading
|
||||||
@@ -457,8 +476,6 @@ the files will be hashed on the client-side, and each hash is sent to the server
|
|||||||
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
|
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
|
||||||
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
|
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
|
||||||
|
|
||||||
adding the same file multiple times is blocked, so if you first search for a file and then decide to upload it, you have to click the `[cleanup]` button to discard `[done]` files (or just refresh the page)
|
|
||||||
|
|
||||||
|
|
||||||
### unpost
|
### unpost
|
||||||
|
|
||||||
@@ -576,6 +593,12 @@ add the argument `-e2ts` to also scan/index tags from music files, which brings
|
|||||||
|
|
||||||
# server config
|
# server config
|
||||||
|
|
||||||
|
using arguments or config files, or a mix of both:
|
||||||
|
* config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf)
|
||||||
|
* `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting
|
||||||
|
* or click the `[reload cfg]` button in the control-panel when logged in as admin
|
||||||
|
|
||||||
|
|
||||||
## file indexing
|
## file indexing
|
||||||
|
|
||||||
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, volume flags, 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, volume flags, or a mix of both.
|
||||||
@@ -612,7 +635,7 @@ if you set `--no-hash [...]` globally, you can enable hashing for specific volum
|
|||||||
|
|
||||||
set upload rules using volume flags, some examples:
|
set upload rules using volume flags, some examples:
|
||||||
|
|
||||||
* `:c,sz=1k-3m` sets allowed filesize between 1 KiB and 3 MiB inclusive (suffixes: b, k, m, g)
|
* `:c,sz=1k-3m` sets allowed filesize between 1 KiB and 3 MiB inclusive (suffixes: `b`, `k`, `m`, `g`)
|
||||||
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
|
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
|
||||||
* `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1)
|
* `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1)
|
||||||
* `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format
|
* `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format
|
||||||
@@ -781,8 +804,8 @@ quick summary of more eccentric web-browsers trying to view a directory index:
|
|||||||
interact with copyparty using non-browser clients
|
interact with copyparty using non-browser clients
|
||||||
|
|
||||||
* javascript: dump some state into a file (two separate examples)
|
* javascript: dump some state into a file (two separate examples)
|
||||||
* `await fetch('https://127.0.0.1:3923/', {method:"PUT", body: JSON.stringify(foo)});`
|
* `await fetch('//127.0.0.1:3923/', {method:"PUT", body: JSON.stringify(foo)});`
|
||||||
* `var xhr = new XMLHttpRequest(); xhr.open('POST', 'https://127.0.0.1:3923/msgs?raw'); xhr.send('foo');`
|
* `var xhr = new XMLHttpRequest(); xhr.open('POST', '//127.0.0.1:3923/msgs?raw'); xhr.send('foo');`
|
||||||
|
|
||||||
* curl/wget: upload some files (post=file, chunk=stdin)
|
* curl/wget: upload some files (post=file, chunk=stdin)
|
||||||
* `post(){ curl -b cppwd=wark -F act=bput -F f=@"$1" http://127.0.0.1:3923/;}`
|
* `post(){ curl -b cppwd=wark -F act=bput -F f=@"$1" http://127.0.0.1:3923/;}`
|
||||||
@@ -860,6 +883,21 @@ below are some tweaks roughly ordered by usefulness:
|
|||||||
...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u
|
...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u
|
||||||
|
|
||||||
|
|
||||||
|
## client-side
|
||||||
|
|
||||||
|
when uploading files,
|
||||||
|
|
||||||
|
* chrome is recommended, at least compared to firefox:
|
||||||
|
* up to 90% faster when hashing, especially on SSDs
|
||||||
|
* up to 40% faster when uploading over extremely fast internets
|
||||||
|
* but [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) can be 40% faster than chrome again
|
||||||
|
|
||||||
|
* if you're cpu-bottlenecked, or the browser is maxing a cpu core:
|
||||||
|
* up to 30% faster uploads if you hide the upload status list by switching away from the `[🚀]` up2k ui-tab (or closing it)
|
||||||
|
* switching to another browser-tab also works, the favicon will update every 10 seconds in that case
|
||||||
|
* unlikely to be a problem, but can happen when uploding many small files, or your internet is too fast, or PC too slow
|
||||||
|
|
||||||
|
|
||||||
# security
|
# security
|
||||||
|
|
||||||
some notes on hardening
|
some notes on hardening
|
||||||
@@ -903,6 +941,83 @@ however you can hit `F12` in the up2k tab and use the devtools to see how far yo
|
|||||||
`await fetch('/inc', {method:'PUT', body:JSON.stringify(ng,null,1)})`
|
`await fetch('/inc', {method:'PUT', body:JSON.stringify(ng,null,1)})`
|
||||||
|
|
||||||
|
|
||||||
|
# HTTP API
|
||||||
|
|
||||||
|
* table-column `params` = URL parameters; `?foo=bar&qux=...`
|
||||||
|
* table-column `body` = POST payload
|
||||||
|
* method `jPOST` = json post
|
||||||
|
* method `mPOST` = multipart post
|
||||||
|
* method `uPOST` = url-encoded post
|
||||||
|
* `FILE` = conventional HTTP file upload entry (rfc1867 et al, filename in `Content-Disposition`)
|
||||||
|
|
||||||
|
authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
||||||
|
|
||||||
|
## read
|
||||||
|
|
||||||
|
| method | params | result |
|
||||||
|
|--|--|--|
|
||||||
|
| GET | `?ls` | list files/folders at URL as JSON |
|
||||||
|
| GET | `?ls&dots` | list files/folders at URL as JSON, including dotfiles |
|
||||||
|
| GET | `?ls=t` | list files/folders at URL as plaintext |
|
||||||
|
| GET | `?ls=v` | list files/folders at URL, terminal-formatted |
|
||||||
|
| GET | `?b` | list files/folders at URL as simplified HTML |
|
||||||
|
| GET | `?tree=.` | list one level of subdirectories inside URL |
|
||||||
|
| GET | `?tree` | list one level of subdirectories for each level until URL |
|
||||||
|
| GET | `?tar` | download everything below URL as a tar file |
|
||||||
|
| GET | `?zip=utf-8` | download everything below URL as a zip file |
|
||||||
|
| GET | `?ups` | show recent uploads from your IP |
|
||||||
|
| GET | `?ups&filter=f` | ...where URL contains `f` |
|
||||||
|
| GET | `?mime=foo` | specify return mimetype `foo` |
|
||||||
|
| GET | `?raw` | get markdown file at URL as plaintext |
|
||||||
|
| GET | `?txt` | get file at URL as plaintext |
|
||||||
|
| GET | `?txt=iso-8859-1` | ...with specific charset |
|
||||||
|
| GET | `?th` | get image/video at URL as thumbnail |
|
||||||
|
| GET | `?th=opus` | convert audio file to 128kbps opus |
|
||||||
|
|
||||||
|
| method | body | result |
|
||||||
|
|--|--|--|
|
||||||
|
| jPOST | `{"q":"foo"}` | do a server-wide search; see the `[🔎]` search tab `raw` field for syntax |
|
||||||
|
|
||||||
|
| method | params | body | result |
|
||||||
|
|--|--|--|--|
|
||||||
|
| jPOST | `?tar` | `["foo","bar"]` | download folders `foo` and `bar` inside URL as a tar file |
|
||||||
|
|
||||||
|
## write
|
||||||
|
|
||||||
|
| method | params | result |
|
||||||
|
|--|--|--|
|
||||||
|
| GET | `?move=/foo/bar` | move/rename the file/folder at URL to /foo/bar |
|
||||||
|
|
||||||
|
| method | params | body | result |
|
||||||
|
|--|--|--|--|
|
||||||
|
| PUT | | (binary data) | upload into file at URL |
|
||||||
|
| PUT | `?gz` | (binary data) | compress with gzip and write into file at URL |
|
||||||
|
| PUT | `?xz` | (binary data) | compress with xz and write into file at URL |
|
||||||
|
| mPOST | | `act=bput`, `f=FILE` | upload `FILE` into the folder at URL |
|
||||||
|
| mPOST | `?j` | `act=bput`, `f=FILE` | ...and reply with json |
|
||||||
|
| mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL |
|
||||||
|
| GET | `?delete` | | delete URL recursively |
|
||||||
|
| jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively |
|
||||||
|
| uPOST | | `msg=foo` | send message `foo` into server log |
|
||||||
|
| mPOST | | `act=tput`, `body=TEXT` | overwrite markdown document at URL |
|
||||||
|
|
||||||
|
server behavior of `msg` can be reconfigured with `--urlform`
|
||||||
|
|
||||||
|
## admin
|
||||||
|
|
||||||
|
| method | params | result |
|
||||||
|
|--|--|--|
|
||||||
|
| GET | `?reload=cfg` | reload config files and rescan volumes |
|
||||||
|
| GET | `?scan` | initiate a rescan of the volume which provides URL |
|
||||||
|
| GET | `?stack` | show a stacktrace of all threads |
|
||||||
|
|
||||||
|
## general
|
||||||
|
|
||||||
|
| method | params | result |
|
||||||
|
|--|--|--|
|
||||||
|
| GET | `?pw=x` | logout |
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
|
||||||
mandatory deps:
|
mandatory deps:
|
||||||
@@ -919,7 +1034,7 @@ enable music tags:
|
|||||||
|
|
||||||
enable [thumbnails](#thumbnails) of...
|
enable [thumbnails](#thumbnails) of...
|
||||||
* **images:** `Pillow` (requires py2.7 or py3.5+)
|
* **images:** `Pillow` (requires py2.7 or py3.5+)
|
||||||
* **videos:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
|
* **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
|
||||||
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler)
|
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler)
|
||||||
* **AVIF pictures:** `pillow-avif-plugin`
|
* **AVIF pictures:** `pillow-avif-plugin`
|
||||||
|
|
||||||
@@ -953,13 +1068,15 @@ pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `
|
|||||||
reduce the size of an sfx by removing features
|
reduce the size of an sfx by removing features
|
||||||
|
|
||||||
if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
|
if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
|
||||||
* `525k` size of original sfx.py as of v0.11.30
|
* `584k` size of original sfx.py as of v1.1.0
|
||||||
* `315k` after `./scripts/make-sfx.sh re no-ogv`
|
* `392k` after `./scripts/make-sfx.sh re no-ogv`
|
||||||
* `223k` after `./scripts/make-sfx.sh re no-ogv no-cm`
|
* `310k` after `./scripts/make-sfx.sh re no-ogv no-cm`
|
||||||
|
* `269k` after `./scripts/make-sfx.sh re no-ogv no-cm no-hl`
|
||||||
|
|
||||||
the features you can opt to drop are
|
the features you can opt to drop are
|
||||||
* `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files, saves ~192k
|
* `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files, saves ~192k
|
||||||
* `cm`/easymde, the "fancy" markdown editor, saves ~92k
|
* `cm`/easymde, the "fancy" markdown editor, saves ~82k
|
||||||
|
* `hl`, prism, the syntax hilighter, saves ~41k
|
||||||
* `fnt`, source-code-pro, the monospace font, saves ~9k
|
* `fnt`, source-code-pro, the monospace font, saves ~9k
|
||||||
* `dd`, the custom mouse cursor for the media player tray tab, saves ~2k
|
* `dd`, the custom mouse cursor for the media player tray tab, saves ~2k
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
||||||
* file uploads, file-search, autoresume of aborted/broken uploads
|
* file uploads, file-search, autoresume of aborted/broken uploads
|
||||||
* faster than browsers
|
* faster than browsers
|
||||||
* early beta, if something breaks just restart it
|
* if something breaks just restart it
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
up2k.py: upload to copyparty
|
up2k.py: upload to copyparty
|
||||||
2021-10-29, v0.10, ed <irc.rizon.net>, MIT-Licensed
|
2021-10-31, v0.11, ed <irc.rizon.net>, MIT-Licensed
|
||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
|
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
|
||||||
|
|
||||||
- dependencies: requests
|
- dependencies: requests
|
||||||
@@ -455,6 +455,7 @@ class Ctl(object):
|
|||||||
self.nbytes = nbytes
|
self.nbytes = nbytes
|
||||||
|
|
||||||
if ar.td:
|
if ar.td:
|
||||||
|
requests.packages.urllib3.disable_warnings()
|
||||||
req_ses.verify = False
|
req_ses.verify = False
|
||||||
if ar.te:
|
if ar.te:
|
||||||
req_ses.verify = ar.te
|
req_ses.verify = ar.te
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ however if your copyparty is behind a reverse-proxy, you may want to use [`share
|
|||||||
# OS integration
|
# OS integration
|
||||||
init-scripts to start copyparty as a service
|
init-scripts to start copyparty as a service
|
||||||
* [`systemd/copyparty.service`](systemd/copyparty.service) runs the sfx normally
|
* [`systemd/copyparty.service`](systemd/copyparty.service) runs the sfx normally
|
||||||
|
* [`rc/copyparty`](rc/copyparty) runs sfx normally on freebsd, create a `copyparty` user
|
||||||
* [`systemd/prisonparty.service`](systemd/prisonparty.service) runs the sfx in a chroot
|
* [`systemd/prisonparty.service`](systemd/prisonparty.service) runs the sfx in a chroot
|
||||||
* [`openrc/copyparty`](openrc/copyparty)
|
* [`openrc/copyparty`](openrc/copyparty)
|
||||||
|
|
||||||
|
|||||||
31
contrib/rc/copyparty
Normal file
31
contrib/rc/copyparty
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# PROVIDE: copyparty
|
||||||
|
# REQUIRE: networking
|
||||||
|
# KEYWORD:
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name="copyparty"
|
||||||
|
rcvar="copyparty_enable"
|
||||||
|
copyparty_user="copyparty"
|
||||||
|
copyparty_args="-e2dsa -v /storage:/storage:r" # change as you see fit
|
||||||
|
copyparty_command="/usr/local/bin/python3.8 /usr/local/copyparty/copyparty-sfx.py ${copyparty_args}"
|
||||||
|
pidfile="/var/run/copyparty/${name}.pid"
|
||||||
|
command="/usr/sbin/daemon"
|
||||||
|
command_args="-P ${pidfile} -r -f ${copyparty_command}"
|
||||||
|
|
||||||
|
stop_postcmd="copyparty_shutdown"
|
||||||
|
|
||||||
|
copyparty_shutdown()
|
||||||
|
{
|
||||||
|
if [ -e "${pidfile}" ]; then
|
||||||
|
echo "Stopping supervising daemon."
|
||||||
|
kill -s TERM `cat ${pidfile}`
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
load_rc_config $name
|
||||||
|
: ${copyparty_enable:=no}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
# change '/mnt::rw' to another location or permission-set
|
# change '/mnt::rw' to another location or permission-set
|
||||||
# remove '-p 80,443,3923' to only listen on port 3923
|
# remove '-p 80,443,3923' to only listen on port 3923
|
||||||
# add '-i 127.0.0.1' to only allow local connections
|
# add '-i 127.0.0.1' to only allow local connections
|
||||||
# add '--use-fpool' if uploading into nfs locations
|
|
||||||
#
|
#
|
||||||
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
||||||
# accept connections; correctly delaying units depending on copyparty.
|
# accept connections; correctly delaying units depending on copyparty.
|
||||||
@@ -20,11 +19,8 @@
|
|||||||
# python disabling line-buffering, so messages are out-of-order:
|
# python disabling line-buffering, so messages are out-of-order:
|
||||||
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
||||||
#
|
#
|
||||||
# enable line-buffering for realtime logging (slight performance cost):
|
# if you remove -q to enable logging, you may also want to remove the
|
||||||
# modify ExecStart and prefix it with `/usr/bin/stdbuf -oL` like so:
|
# following line to enable buffering (slightly better performance):
|
||||||
# ExecStart=/usr/bin/stdbuf -oL /usr/bin/python3 [...]
|
|
||||||
# but some systemd versions require this instead (higher performance cost):
|
|
||||||
# inside the [Service] block, add the following line:
|
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -33,8 +29,10 @@ Description=copyparty file server
|
|||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
SyslogIdentifier=copyparty
|
SyslogIdentifier=copyparty
|
||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -v /mnt::rw
|
Environment=PYTHONUNBUFFERED=x
|
||||||
|
ExecReload=/bin/kill -s USR1 $MAINPID
|
||||||
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
||||||
|
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -v /mnt::rw
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
@@ -186,6 +186,32 @@ def configure_ssl_ciphers(al):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def args_from_cfg(cfg_path):
|
||||||
|
ret = []
|
||||||
|
skip = False
|
||||||
|
with open(cfg_path, "rb") as f:
|
||||||
|
for ln in [x.decode("utf-8").strip() for x in f]:
|
||||||
|
if not ln:
|
||||||
|
skip = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ln.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not ln.startswith("-"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if skip:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
ret.extend(ln.split(" ", 1))
|
||||||
|
except:
|
||||||
|
ret.append(ln)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def sighandler(sig=None, frame=None):
|
def sighandler(sig=None, frame=None):
|
||||||
msg = [""] * 5
|
msg = [""] * 5
|
||||||
for th in threading.enumerate():
|
for th in threading.enumerate():
|
||||||
@@ -355,7 +381,9 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
||||||
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 bind (comma/range)")
|
||||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
||||||
|
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
||||||
|
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="socket write delay in seconds")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
||||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
||||||
@@ -397,11 +425,13 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('admin panel options')
|
ap2 = ap.add_argument_group('admin panel options')
|
||||||
|
ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
|
||||||
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
||||||
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
|
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('thumbnail options')
|
ap2 = ap.add_argument_group('thumbnail options')
|
||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
||||||
|
ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms)")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
|
||||||
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
|
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
|
||||||
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails")
|
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails")
|
||||||
@@ -409,11 +439,16 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
||||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||||
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
|
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
|
||||||
|
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs")
|
||||||
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
|
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
|
||||||
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
||||||
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||||
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
|
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
|
||||||
|
|
||||||
|
ap2 = ap.add_argument_group('transcoding options')
|
||||||
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
||||||
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete transcode output after SEC seconds")
|
||||||
|
|
||||||
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")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
||||||
@@ -441,6 +476,7 @@ def run_argparse(argv, formatter):
|
|||||||
ap2 = ap.add_argument_group('ui options')
|
ap2 = ap.add_argument_group('ui options')
|
||||||
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
||||||
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
||||||
|
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('debug options')
|
ap2 = ap.add_argument_group('debug options')
|
||||||
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
||||||
@@ -483,7 +519,12 @@ def main(argv=None):
|
|||||||
if HAVE_SSL:
|
if HAVE_SSL:
|
||||||
ensure_cert()
|
ensure_cert()
|
||||||
|
|
||||||
deprecated = [["-e2s", "-e2ds"]]
|
for k, v in zip(argv, argv[1:]):
|
||||||
|
if k == "-c":
|
||||||
|
supp = args_from_cfg(v)
|
||||||
|
argv.extend(supp)
|
||||||
|
|
||||||
|
deprecated = []
|
||||||
for dk, nk in deprecated:
|
for dk, nk in deprecated:
|
||||||
try:
|
try:
|
||||||
idx = argv.index(dk)
|
idx = argv.index(dk)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 0, 14)
|
VERSION = (1, 1, 2)
|
||||||
CODENAME = "sufficient"
|
CODENAME = "opus"
|
||||||
BUILD_DT = (2021, 10, 30)
|
BUILD_DT = (2021, 11, 12)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
def _parse_config_file(self, fd, acct, daxs, mflags, mount):
|
def _parse_config_file(self, fd, acct, daxs, mflags, mount):
|
||||||
# type: (any, str, dict[str, AXS], any, str) -> None
|
# type: (any, str, dict[str, AXS], any, str) -> None
|
||||||
|
skip = False
|
||||||
vol_src = None
|
vol_src = None
|
||||||
vol_dst = None
|
vol_dst = None
|
||||||
self.line_ctr = 0
|
self.line_ctr = 0
|
||||||
@@ -555,6 +556,11 @@ class AuthSrv(object):
|
|||||||
vol_src = None
|
vol_src = None
|
||||||
vol_dst = None
|
vol_dst = None
|
||||||
|
|
||||||
|
if skip:
|
||||||
|
if not ln:
|
||||||
|
skip = False
|
||||||
|
continue
|
||||||
|
|
||||||
if not ln or ln.startswith("#"):
|
if not ln or ln.startswith("#"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -562,6 +568,8 @@ class AuthSrv(object):
|
|||||||
if ln.startswith("u "):
|
if ln.startswith("u "):
|
||||||
u, p = ln[2:].split(":", 1)
|
u, p = ln[2:].split(":", 1)
|
||||||
acct[u] = p
|
acct[u] = p
|
||||||
|
elif ln.startswith("-"):
|
||||||
|
skip = True # argv
|
||||||
else:
|
else:
|
||||||
vol_src = ln
|
vol_src = ln
|
||||||
continue
|
continue
|
||||||
@@ -613,7 +621,7 @@ class AuthSrv(object):
|
|||||||
if uname == "":
|
if uname == "":
|
||||||
uname = "*"
|
uname = "*"
|
||||||
|
|
||||||
for un in uname.split(","):
|
for un in uname.replace(",", " ").strip().split():
|
||||||
if "r" in lvl:
|
if "r" in lvl:
|
||||||
axs.uread[un] = 1
|
axs.uread[un] = 1
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ class BrokerMp(object):
|
|||||||
|
|
||||||
procs.pop()
|
procs.pop()
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
self.log("broker", "reloading")
|
||||||
|
for _, proc in enumerate(self.procs):
|
||||||
|
proc.q_pend.put([0, "reload", []])
|
||||||
|
|
||||||
def collector(self, proc):
|
def collector(self, proc):
|
||||||
"""receive message from hub in other process"""
|
"""receive message from hub in other process"""
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class MpWorker(object):
|
|||||||
# we inherited signal_handler from parent,
|
# we inherited signal_handler from parent,
|
||||||
# replace it with something harmless
|
# replace it with something harmless
|
||||||
if not FAKE_MP:
|
if not FAKE_MP:
|
||||||
for sig in [signal.SIGINT, signal.SIGTERM]:
|
for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]:
|
||||||
signal.signal(sig, self.signal_handler)
|
signal.signal(sig, self.signal_handler)
|
||||||
|
|
||||||
# starting to look like a good idea
|
# starting to look like a good idea
|
||||||
@@ -69,6 +69,11 @@ class MpWorker(object):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
elif dest == "reload":
|
||||||
|
self.logw("mpw.asrv reloading")
|
||||||
|
self.asrv.reload()
|
||||||
|
self.logw("mpw.asrv reloaded")
|
||||||
|
|
||||||
elif dest == "listen":
|
elif dest == "listen":
|
||||||
self.httpsrv.listen(args[0], args[1])
|
self.httpsrv.listen(args[0], args[1])
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ class BrokerThr(object):
|
|||||||
|
|
||||||
# instantiate all services here (TODO: inheritance?)
|
# instantiate all services here (TODO: inheritance?)
|
||||||
self.httpsrv = HttpSrv(self, None)
|
self.httpsrv = HttpSrv(self, None)
|
||||||
|
self.reload = self.noop
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
# self.log("broker", "shutting down")
|
# self.log("broker", "shutting down")
|
||||||
self.httpsrv.shutdown()
|
self.httpsrv.shutdown()
|
||||||
|
|
||||||
|
def noop(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def put(self, want_retval, dest, *args):
|
def put(self, want_retval, dest, *args):
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
# default to utf8 html if no content-type is set
|
# default to utf8 html if no content-type is set
|
||||||
if not mime:
|
if not mime:
|
||||||
mime = self.out_headers.get("Content-Type", "text/html; charset=UTF-8")
|
mime = self.out_headers.get("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
self.out_headers["Content-Type"] = mime
|
self.out_headers["Content-Type"] = mime
|
||||||
|
|
||||||
@@ -419,6 +419,9 @@ class HttpCli(object):
|
|||||||
return self.scanvol()
|
return self.scanvol()
|
||||||
|
|
||||||
if not self.vpath:
|
if not self.vpath:
|
||||||
|
if "reload" in self.uparam:
|
||||||
|
return self.handle_reload()
|
||||||
|
|
||||||
if "stack" in self.uparam:
|
if "stack" in self.uparam:
|
||||||
return self.tx_stack()
|
return self.tx_stack()
|
||||||
|
|
||||||
@@ -529,11 +532,11 @@ class HttpCli(object):
|
|||||||
raise Pebkac(405, "don't know how to handle POST({})".format(ctype))
|
raise Pebkac(405, "don't know how to handle POST({})".format(ctype))
|
||||||
|
|
||||||
def get_body_reader(self):
|
def get_body_reader(self):
|
||||||
chunked = "chunked" in self.headers.get("transfer-encoding", "").lower()
|
if "chunked" in self.headers.get("transfer-encoding", "").lower():
|
||||||
|
return read_socket_chunked(self.sr), -1
|
||||||
|
|
||||||
remains = int(self.headers.get("content-length", -1))
|
remains = int(self.headers.get("content-length", -1))
|
||||||
if chunked:
|
if remains == -1:
|
||||||
return read_socket_chunked(self.sr), remains
|
|
||||||
elif remains == -1:
|
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
return read_socket_unbounded(self.sr), remains
|
return read_socket_unbounded(self.sr), remains
|
||||||
else:
|
else:
|
||||||
@@ -1461,12 +1464,15 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
self.permit_caching()
|
self.permit_caching()
|
||||||
|
|
||||||
|
if "txt" in self.uparam:
|
||||||
|
mime = "text/plain; charset={}".format(self.uparam["txt"] or "utf-8")
|
||||||
|
elif "mime" in self.uparam:
|
||||||
|
mime = self.uparam.get("mime")
|
||||||
|
else:
|
||||||
|
mime = guess_mime(req_path)
|
||||||
|
|
||||||
self.out_headers["Accept-Ranges"] = "bytes"
|
self.out_headers["Accept-Ranges"] = "bytes"
|
||||||
self.send_headers(
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
||||||
length=upper - lower,
|
|
||||||
status=status,
|
|
||||||
mime=guess_mime(req_path),
|
|
||||||
)
|
|
||||||
|
|
||||||
logmsg += unicode(status) + logtail
|
logmsg += unicode(status) + logtail
|
||||||
|
|
||||||
@@ -1478,10 +1484,10 @@ class HttpCli(object):
|
|||||||
|
|
||||||
ret = True
|
ret = True
|
||||||
with open_func(*open_args) as f:
|
with open_func(*open_args) as f:
|
||||||
if use_sendfile:
|
sendfun = sendfile_kern if use_sendfile else sendfile_py
|
||||||
remains = sendfile_kern(lower, upper, f, self.s)
|
remains = sendfun(
|
||||||
else:
|
lower, upper, f, self.s, self.args.s_wr_sz, self.args.s_wr_slp
|
||||||
remains = sendfile_py(lower, upper, f, self.s)
|
)
|
||||||
|
|
||||||
if remains > 0:
|
if remains > 0:
|
||||||
logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
|
logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
|
||||||
@@ -1711,7 +1717,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
vn, _ = self.asrv.vfs.get(self.vpath, self.uname, True, True)
|
vn, _ = self.asrv.vfs.get(self.vpath, self.uname, True, True)
|
||||||
|
|
||||||
args = [self.asrv.vfs.all_vols, [vn.vpath]]
|
args = [self.asrv.vfs.all_vols, [vn.vpath], False]
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.put(True, "up2k.rescan", *args)
|
x = self.conn.hsrv.broker.put(True, "up2k.rescan", *args)
|
||||||
x = x.get()
|
x = x.get()
|
||||||
@@ -1721,6 +1727,20 @@ class HttpCli(object):
|
|||||||
|
|
||||||
raise Pebkac(500, x)
|
raise Pebkac(500, x)
|
||||||
|
|
||||||
|
def handle_reload(self):
|
||||||
|
act = self.uparam.get("reload")
|
||||||
|
if act != "cfg":
|
||||||
|
raise Pebkac(400, "only config files ('cfg') can be reloaded rn")
|
||||||
|
|
||||||
|
if not [x for x in self.wvol if x in self.rvol]:
|
||||||
|
raise Pebkac(403, "not allowed for user " + self.uname)
|
||||||
|
|
||||||
|
if self.args.no_reload:
|
||||||
|
raise Pebkac(403, "the reload feature is disabled in server config")
|
||||||
|
|
||||||
|
x = self.conn.hsrv.broker.put(True, "reload")
|
||||||
|
return self.redirect("", "?h", x.get(), "return to", False)
|
||||||
|
|
||||||
def tx_stack(self):
|
def tx_stack(self):
|
||||||
if not [x for x in self.wvol if x in self.rvol]:
|
if not [x for x in self.wvol if x in self.rvol]:
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
raise Pebkac(403, "not allowed for user " + self.uname)
|
||||||
@@ -1862,7 +1882,7 @@ class HttpCli(object):
|
|||||||
arg = self.uparam["ls"]
|
arg = self.uparam["ls"]
|
||||||
if arg in ["v", "t", "txt"]:
|
if arg in ["v", "t", "txt"]:
|
||||||
try:
|
try:
|
||||||
biggest = max(ls["files"], key=itemgetter("sz"))["sz"]
|
biggest = max(ls["files"] + ls["dirs"], key=itemgetter("sz"))["sz"]
|
||||||
except:
|
except:
|
||||||
biggest = 0
|
biggest = 0
|
||||||
|
|
||||||
@@ -1904,14 +1924,14 @@ class HttpCli(object):
|
|||||||
for x in y
|
for x in y
|
||||||
]
|
]
|
||||||
ret = "\n".join(ret)
|
ret = "\n".join(ret)
|
||||||
mime = "text/plain; encoding=utf-8"
|
mime = "text/plain; charset=utf-8"
|
||||||
else:
|
else:
|
||||||
[x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y]
|
[x.pop(k) for k in ["name", "dt"] for y in [dirs, files] for x in y]
|
||||||
|
|
||||||
ret = json.dumps(ls)
|
ret = json.dumps(ls)
|
||||||
mime = "application/json"
|
mime = "application/json"
|
||||||
|
|
||||||
self.reply(ret.encode("utf-8", "replace"), mime=mime)
|
self.reply(ret.encode("utf-8", "replace") + b"\n", mime=mime)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def tx_browser(self):
|
def tx_browser(self):
|
||||||
@@ -2072,6 +2092,7 @@ class HttpCli(object):
|
|||||||
"def_hcols": [],
|
"def_hcols": [],
|
||||||
"have_up2k_idx": ("e2d" in vn.flags),
|
"have_up2k_idx": ("e2d" in vn.flags),
|
||||||
"have_tags_idx": ("e2t" in vn.flags),
|
"have_tags_idx": ("e2t" in vn.flags),
|
||||||
|
"have_acode": (not self.args.no_acode),
|
||||||
"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),
|
||||||
@@ -2267,6 +2288,22 @@ class HttpCli(object):
|
|||||||
ls_ret["taglist"] = taglist
|
ls_ret["taglist"] = taglist
|
||||||
return self.tx_ls(ls_ret)
|
return self.tx_ls(ls_ret)
|
||||||
|
|
||||||
|
doc = self.uparam.get("doc") if self.can_read else None
|
||||||
|
if doc:
|
||||||
|
doc = unquotep(doc.replace("+", " "))
|
||||||
|
j2a["docname"] = doc
|
||||||
|
if next((x for x in files if x["name"] == doc), None):
|
||||||
|
with open(os.path.join(abspath, doc), "rb") as f:
|
||||||
|
doc = f.read().decode("utf-8", "replace")
|
||||||
|
else:
|
||||||
|
self.log("doc 404: [{}]".format(doc), c=6)
|
||||||
|
doc = "( textfile not found )"
|
||||||
|
|
||||||
|
j2a["doc"] = doc
|
||||||
|
|
||||||
|
if not self.conn.hsrv.prism:
|
||||||
|
j2a["no_prism"] = True
|
||||||
|
|
||||||
for d in dirs:
|
for d in dirs:
|
||||||
d["name"] += "/"
|
d["name"] += "/"
|
||||||
|
|
||||||
@@ -2275,6 +2312,7 @@ class HttpCli(object):
|
|||||||
j2a["files"] = dirs + files
|
j2a["files"] = dirs + files
|
||||||
j2a["logues"] = logues
|
j2a["logues"] = logues
|
||||||
j2a["taglist"] = taglist
|
j2a["taglist"] = taglist
|
||||||
|
j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
|
||||||
|
|
||||||
if "mth" in vn.flags:
|
if "mth" in vn.flags:
|
||||||
j2a["def_hcols"] = vn.flags["mth"].split(",")
|
j2a["def_hcols"] = vn.flags["mth"].split(",")
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class HttpConn(object):
|
|||||||
self.u2fh = hsrv.u2fh
|
self.u2fh = hsrv.u2fh
|
||||||
|
|
||||||
enth = HAVE_PIL and not self.args.no_thumb
|
enth = HAVE_PIL and not self.args.no_thumb
|
||||||
self.thumbcli = ThumbCli(hsrv.broker) if enth else None
|
self.thumbcli = ThumbCli(hsrv) if enth else None
|
||||||
self.ico = Ico(self.args)
|
self.ico = Ico(self.args)
|
||||||
|
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class HttpSrv(object):
|
|||||||
x: env.get_template(x + ".html")
|
x: env.get_template(x + ".html")
|
||||||
for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
|
for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
|
||||||
}
|
}
|
||||||
|
self.prism = os.path.exists(os.path.join(E.mod, "web", "deps", "prism.js.gz"))
|
||||||
|
|
||||||
cert_path = os.path.join(E.cfg, "cert.pem")
|
cert_path = os.path.join(E.cfg, "cert.pem")
|
||||||
if bos.path.exists(cert_path):
|
if bos.path.exists(cert_path):
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .authsrv import AuthSrv
|
|||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
from .th_srv import ThumbSrv, HAVE_PIL, HAVE_WEBP
|
from .th_srv import ThumbSrv, HAVE_PIL, HAVE_WEBP
|
||||||
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
||||||
|
|
||||||
|
|
||||||
class SvcHub(object):
|
class SvcHub(object):
|
||||||
@@ -36,7 +37,9 @@ class SvcHub(object):
|
|||||||
self.argv = argv
|
self.argv = argv
|
||||||
self.logf = None
|
self.logf = None
|
||||||
self.stop_req = False
|
self.stop_req = False
|
||||||
|
self.reload_req = False
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
self.reloading = False
|
||||||
self.stop_cond = threading.Condition()
|
self.stop_cond = threading.Condition()
|
||||||
self.retcode = 0
|
self.retcode = 0
|
||||||
self.httpsrv_up = 0
|
self.httpsrv_up = 0
|
||||||
@@ -54,8 +57,10 @@ class SvcHub(object):
|
|||||||
if args.log_thrs:
|
if args.log_thrs:
|
||||||
start_log_thrs(self.log, args.log_thrs, 0)
|
start_log_thrs(self.log, args.log_thrs, 0)
|
||||||
|
|
||||||
if not ANYWIN and not args.use_fpool:
|
if not args.use_fpool and args.j != 1:
|
||||||
args.no_fpool = True
|
args.no_fpool = True
|
||||||
|
m = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems"
|
||||||
|
self.log("root", m.format(args.j))
|
||||||
|
|
||||||
if not args.no_fpool and args.j != 1:
|
if not args.no_fpool and args.j != 1:
|
||||||
m = "WARNING: --use-fpool combined with multithreading is untested and can probably cause undefined behavior"
|
m = "WARNING: --use-fpool combined with multithreading is untested and can probably cause undefined behavior"
|
||||||
@@ -88,6 +93,18 @@ class SvcHub(object):
|
|||||||
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3
|
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not args.no_acode and args.no_thumb:
|
||||||
|
msg = "setting --no-acode because --no-thumb (sorry)"
|
||||||
|
self.log("thumb", msg, c=6)
|
||||||
|
args.no_acode = True
|
||||||
|
|
||||||
|
if not args.no_acode and (not HAVE_FFMPEG or not HAVE_FFPROBE):
|
||||||
|
msg = "setting --no-acode because either FFmpeg or FFprobe is not available"
|
||||||
|
self.log("thumb", msg, c=6)
|
||||||
|
args.no_acode = True
|
||||||
|
|
||||||
|
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
||||||
|
|
||||||
# decide which worker impl to use
|
# decide which worker impl to use
|
||||||
if self.check_mp_enable():
|
if self.check_mp_enable():
|
||||||
from .broker_mp import BrokerMp as Broker
|
from .broker_mp import BrokerMp as Broker
|
||||||
@@ -182,7 +199,11 @@ class SvcHub(object):
|
|||||||
thr.daemon = True
|
thr.daemon = True
|
||||||
thr.start()
|
thr.start()
|
||||||
|
|
||||||
for sig in [signal.SIGINT, signal.SIGTERM]:
|
sigs = [signal.SIGINT, signal.SIGTERM]
|
||||||
|
if not ANYWIN:
|
||||||
|
sigs.append(signal.SIGUSR1)
|
||||||
|
|
||||||
|
for sig in sigs:
|
||||||
signal.signal(sig, self.signal_handler)
|
signal.signal(sig, self.signal_handler)
|
||||||
|
|
||||||
# macos hangs after shutdown on sigterm with while-sleep,
|
# macos hangs after shutdown on sigterm with while-sleep,
|
||||||
@@ -206,18 +227,45 @@ class SvcHub(object):
|
|||||||
else:
|
else:
|
||||||
self.stop_thr()
|
self.stop_thr()
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
if self.reloading:
|
||||||
|
return "cannot reload; already in progress"
|
||||||
|
|
||||||
|
self.reloading = True
|
||||||
|
t = threading.Thread(target=self._reload)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
return "reload initiated"
|
||||||
|
|
||||||
|
def _reload(self):
|
||||||
|
self.log("root", "reload scheduled")
|
||||||
|
with self.up2k.mutex:
|
||||||
|
self.asrv.reload()
|
||||||
|
self.up2k.reload()
|
||||||
|
self.broker.reload()
|
||||||
|
|
||||||
|
self.reloading = False
|
||||||
|
|
||||||
def stop_thr(self):
|
def stop_thr(self):
|
||||||
while not self.stop_req:
|
while not self.stop_req:
|
||||||
with self.stop_cond:
|
with self.stop_cond:
|
||||||
self.stop_cond.wait(9001)
|
self.stop_cond.wait(9001)
|
||||||
|
|
||||||
|
if self.reload_req:
|
||||||
|
self.reload_req = False
|
||||||
|
self.reload()
|
||||||
|
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
|
||||||
def signal_handler(self, sig, frame):
|
def signal_handler(self, sig, frame):
|
||||||
if self.stopping:
|
if self.stopping:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.stop_req = True
|
if sig == signal.SIGUSR1:
|
||||||
|
self.reload_req = True
|
||||||
|
else:
|
||||||
|
self.stop_req = True
|
||||||
|
|
||||||
with self.stop_cond:
|
with self.stop_cond:
|
||||||
self.stop_cond.notify_all()
|
self.stop_cond.notify_all()
|
||||||
|
|
||||||
|
|||||||
@@ -4,28 +4,44 @@ from __future__ import print_function, unicode_literals
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from .util import Cooldown
|
from .util import Cooldown
|
||||||
from .th_srv import thumb_path, THUMBABLE, FMT_FF
|
from .th_srv import thumb_path, THUMBABLE, FMT_FFV, FMT_FFA
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
class ThumbCli(object):
|
class ThumbCli(object):
|
||||||
def __init__(self, broker):
|
def __init__(self, hsrv):
|
||||||
self.broker = broker
|
self.broker = hsrv.broker
|
||||||
self.args = broker.args
|
self.log_func = hsrv.log
|
||||||
self.asrv = broker.asrv
|
self.args = hsrv.args
|
||||||
|
self.asrv = hsrv.asrv
|
||||||
|
|
||||||
# cache on both sides for less broker spam
|
# cache on both sides for less broker spam
|
||||||
self.cooldown = Cooldown(self.args.th_poke)
|
self.cooldown = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
|
def log(self, msg, c=0):
|
||||||
|
self.log_func("thumbcli", msg, c)
|
||||||
|
|
||||||
def get(self, ptop, rem, mtime, fmt):
|
def get(self, ptop, rem, mtime, fmt):
|
||||||
ext = rem.rsplit(".")[-1].lower()
|
ext = rem.rsplit(".")[-1].lower()
|
||||||
if ext not in THUMBABLE:
|
if ext not in THUMBABLE:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
is_vid = ext in FMT_FF
|
is_vid = ext in FMT_FFV
|
||||||
if is_vid and self.args.no_vthumb:
|
if is_vid and self.args.no_vthumb:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
want_opus = fmt == "opus"
|
||||||
|
is_au = ext in FMT_FFA
|
||||||
|
if is_au:
|
||||||
|
if want_opus:
|
||||||
|
if self.args.no_acode:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
if self.args.no_athumb:
|
||||||
|
return None
|
||||||
|
elif want_opus:
|
||||||
|
return None
|
||||||
|
|
||||||
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
|
||||||
return os.path.join(ptop, rem)
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
@@ -33,10 +49,14 @@ class ThumbCli(object):
|
|||||||
fmt = "w"
|
fmt = "w"
|
||||||
|
|
||||||
if fmt == "w":
|
if fmt == "w":
|
||||||
if self.args.th_no_webp or (is_vid and self.args.th_ff_jpg):
|
if self.args.th_no_webp or ((is_vid or is_au) and self.args.th_ff_jpg):
|
||||||
fmt = "j"
|
fmt = "j"
|
||||||
|
|
||||||
histpath = self.asrv.vfs.histtab[ptop]
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
|
if not histpath:
|
||||||
|
self.log("no histpath for [{}]".format(ptop))
|
||||||
|
return None
|
||||||
|
|
||||||
tpath = thumb_path(histpath, rem, mtime, fmt)
|
tpath = thumb_path(histpath, rem, mtime, fmt)
|
||||||
ret = None
|
ret = None
|
||||||
try:
|
try:
|
||||||
@@ -53,6 +73,11 @@ class ThumbCli(object):
|
|||||||
if self.cooldown.poke(tdir):
|
if self.cooldown.poke(tdir):
|
||||||
self.broker.put(False, "thumbsrv.poke", tdir)
|
self.broker.put(False, "thumbsrv.poke", tdir)
|
||||||
|
|
||||||
|
if want_opus:
|
||||||
|
# audio files expire individually
|
||||||
|
if self.cooldown.poke(tpath):
|
||||||
|
self.broker.put(False, "thumbsrv.poke", tpath)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
|
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import threading
|
|||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
from .__init__ import PY2, unicode
|
from .__init__ import PY2, unicode
|
||||||
from .util import fsenc, vsplit, runcmd, Queue, Cooldown, BytesIO, min_ex
|
from .util import fsenc, vsplit, statdir, runcmd, Queue, Cooldown, BytesIO, min_ex
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
||||||
|
|
||||||
@@ -50,7 +50,8 @@ except:
|
|||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
# ffmpeg -formats
|
# ffmpeg -formats
|
||||||
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
|
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
|
||||||
FMT_FF = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
|
FMT_FFV = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
|
||||||
|
FMT_FFA = "aac m4a ogg opus flac alac mp3 mp2 ac3 dts wma ra wav aif aiff au alaw ulaw mulaw amr gsm ape tak tta wv"
|
||||||
|
|
||||||
if HAVE_HEIF:
|
if HAVE_HEIF:
|
||||||
FMT_PIL += " heif heifs heic heics"
|
FMT_PIL += " heif heifs heic heics"
|
||||||
@@ -58,7 +59,9 @@ if HAVE_HEIF:
|
|||||||
if HAVE_AVIF:
|
if HAVE_AVIF:
|
||||||
FMT_PIL += " avif avifs"
|
FMT_PIL += " avif avifs"
|
||||||
|
|
||||||
FMT_PIL, FMT_FF = [{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FF]]
|
FMT_PIL, FMT_FFV, FMT_FFA = [
|
||||||
|
{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FFV, FMT_FFA]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
THUMBABLE = {}
|
THUMBABLE = {}
|
||||||
@@ -67,7 +70,8 @@ if HAVE_PIL:
|
|||||||
THUMBABLE.update(FMT_PIL)
|
THUMBABLE.update(FMT_PIL)
|
||||||
|
|
||||||
if HAVE_FFMPEG and HAVE_FFPROBE:
|
if HAVE_FFMPEG and HAVE_FFPROBE:
|
||||||
THUMBABLE.update(FMT_FF)
|
THUMBABLE.update(FMT_FFV)
|
||||||
|
THUMBABLE.update(FMT_FFA)
|
||||||
|
|
||||||
|
|
||||||
def thumb_path(histpath, rem, mtime, fmt):
|
def thumb_path(histpath, rem, mtime, fmt):
|
||||||
@@ -86,9 +90,13 @@ def thumb_path(histpath, rem, mtime, fmt):
|
|||||||
h = hashlib.sha512(fsenc(fn)).digest()
|
h = hashlib.sha512(fsenc(fn)).digest()
|
||||||
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
|
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
|
||||||
|
|
||||||
return "{}/th/{}/{}.{:x}.{}".format(
|
if fmt == "opus":
|
||||||
histpath, rd, fn, int(mtime), "webp" if fmt == "w" else "jpg"
|
cat = "ac"
|
||||||
)
|
else:
|
||||||
|
fmt = "webp" if fmt == "w" else "jpg"
|
||||||
|
cat = "th"
|
||||||
|
|
||||||
|
return "{}/{}/{}/{}.{:x}.{}".format(histpath, cat, rd, fn, int(mtime), fmt)
|
||||||
|
|
||||||
|
|
||||||
class ThumbSrv(object):
|
class ThumbSrv(object):
|
||||||
@@ -115,7 +123,8 @@ class ThumbSrv(object):
|
|||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
if not self.args.no_vthumb and (not HAVE_FFMPEG or not HAVE_FFPROBE):
|
want_ff = not self.args.no_vthumb or not self.args.no_athumb
|
||||||
|
if want_ff and (not HAVE_FFMPEG or not HAVE_FFPROBE):
|
||||||
missing = []
|
missing = []
|
||||||
if not HAVE_FFMPEG:
|
if not HAVE_FFMPEG:
|
||||||
missing.append("FFmpeg")
|
missing.append("FFmpeg")
|
||||||
@@ -123,7 +132,7 @@ class ThumbSrv(object):
|
|||||||
if not HAVE_FFPROBE:
|
if not HAVE_FFPROBE:
|
||||||
missing.append("FFprobe")
|
missing.append("FFprobe")
|
||||||
|
|
||||||
msg = "cannot create video thumbnails because some of the required programs are not available: "
|
msg = "cannot create audio/video thumbnails because some of the required programs are not available: "
|
||||||
msg += ", ".join(missing)
|
msg += ", ".join(missing)
|
||||||
self.log(msg, c=3)
|
self.log(msg, c=3)
|
||||||
|
|
||||||
@@ -145,7 +154,11 @@ class ThumbSrv(object):
|
|||||||
return not self.nthr
|
return not self.nthr
|
||||||
|
|
||||||
def get(self, ptop, rem, mtime, fmt):
|
def get(self, ptop, rem, mtime, fmt):
|
||||||
histpath = self.asrv.vfs.histtab[ptop]
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
|
if not histpath:
|
||||||
|
self.log("no histpath for [{}]".format(ptop))
|
||||||
|
return None
|
||||||
|
|
||||||
tpath = thumb_path(histpath, rem, mtime, fmt)
|
tpath = thumb_path(histpath, rem, mtime, fmt)
|
||||||
abspath = os.path.join(ptop, rem)
|
abspath = os.path.join(ptop, rem)
|
||||||
cond = threading.Condition(self.mutex)
|
cond = threading.Condition(self.mutex)
|
||||||
@@ -181,6 +194,7 @@ class ThumbSrv(object):
|
|||||||
try:
|
try:
|
||||||
st = bos.stat(tpath)
|
st = bos.stat(tpath)
|
||||||
if st.st_size:
|
if st.st_size:
|
||||||
|
self.poke(tpath)
|
||||||
return tpath
|
return tpath
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -199,8 +213,13 @@ class ThumbSrv(object):
|
|||||||
if not bos.path.exists(tpath):
|
if not bos.path.exists(tpath):
|
||||||
if ext in FMT_PIL:
|
if ext in FMT_PIL:
|
||||||
fun = self.conv_pil
|
fun = self.conv_pil
|
||||||
elif ext in FMT_FF:
|
elif ext in FMT_FFV:
|
||||||
fun = self.conv_ffmpeg
|
fun = self.conv_ffmpeg
|
||||||
|
elif ext in FMT_FFA:
|
||||||
|
if tpath.endswith(".opus"):
|
||||||
|
fun = self.conv_opus
|
||||||
|
else:
|
||||||
|
fun = self.conv_spec
|
||||||
|
|
||||||
if fun:
|
if fun:
|
||||||
try:
|
try:
|
||||||
@@ -326,8 +345,10 @@ class ThumbSrv(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
# self.log((b" ".join(cmd)).decode("utf-8"))
|
self._run_ff(cmd)
|
||||||
|
|
||||||
|
def _run_ff(self, cmd):
|
||||||
|
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||||
ret, sout, serr = runcmd(cmd)
|
ret, sout, serr = runcmd(cmd)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
m = "FFmpeg failed (probably a corrupt video file):\n"
|
m = "FFmpeg failed (probably a corrupt video file):\n"
|
||||||
@@ -335,16 +356,81 @@ class ThumbSrv(object):
|
|||||||
self.log(m, c="1;30")
|
self.log(m, c="1;30")
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
|
def conv_spec(self, abspath, tpath):
|
||||||
|
ret, _ = ffprobe(abspath)
|
||||||
|
if "ac" not in ret:
|
||||||
|
raise Exception("not audio")
|
||||||
|
|
||||||
|
fc = "[0:a:0]aresample=48000{},showspectrumpic=s=640x512,crop=780:544:70:50[o]"
|
||||||
|
|
||||||
|
if self.args.th_ff_swr:
|
||||||
|
fco = ":filter_size=128:cutoff=0.877"
|
||||||
|
else:
|
||||||
|
fco = ":resampler=soxr"
|
||||||
|
|
||||||
|
fc = fc.format(fco)
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
b"ffmpeg",
|
||||||
|
b"-nostdin",
|
||||||
|
b"-v", b"error",
|
||||||
|
b"-hide_banner",
|
||||||
|
b"-i", fsenc(abspath),
|
||||||
|
b"-filter_complex", fc.encode("utf-8"),
|
||||||
|
b"-map", b"[o]"
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
if tpath.endswith(".jpg"):
|
||||||
|
cmd += [
|
||||||
|
b"-q:v",
|
||||||
|
b"6", # default=??
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
cmd += [
|
||||||
|
b"-q:v",
|
||||||
|
b"50", # default=75
|
||||||
|
b"-compression_level:v",
|
||||||
|
b"6", # default=4, 0=fast, 6=max
|
||||||
|
]
|
||||||
|
|
||||||
|
cmd += [fsenc(tpath)]
|
||||||
|
self._run_ff(cmd)
|
||||||
|
|
||||||
|
def conv_opus(self, abspath, tpath):
|
||||||
|
if self.args.no_acode:
|
||||||
|
raise Exception("disabled in server config")
|
||||||
|
|
||||||
|
ret, _ = ffprobe(abspath)
|
||||||
|
if "ac" not in ret:
|
||||||
|
raise Exception("not audio")
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
b"ffmpeg",
|
||||||
|
b"-nostdin",
|
||||||
|
b"-v", b"error",
|
||||||
|
b"-hide_banner",
|
||||||
|
b"-i", fsenc(abspath),
|
||||||
|
b"-map", b"0:a:0",
|
||||||
|
b"-c:a", b"libopus",
|
||||||
|
b"-b:a", b"128k",
|
||||||
|
fsenc(tpath)
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
self._run_ff(cmd)
|
||||||
|
|
||||||
def poke(self, tdir):
|
def poke(self, tdir):
|
||||||
if not self.poke_cd.poke(tdir):
|
if not self.poke_cd.poke(tdir):
|
||||||
return
|
return
|
||||||
|
|
||||||
ts = int(time.time())
|
ts = int(time.time())
|
||||||
try:
|
try:
|
||||||
p1 = os.path.dirname(tdir)
|
for _ in range(4):
|
||||||
p2 = os.path.dirname(p1)
|
bos.utime(tdir, (ts, ts))
|
||||||
for dp in [tdir, p1, p2]:
|
tdir = os.path.dirname(tdir)
|
||||||
bos.utime(dp, (ts, ts))
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -364,25 +450,36 @@ class ThumbSrv(object):
|
|||||||
self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
|
self.log("\033[Jcln ok; rm {} dirs".format(ndirs))
|
||||||
|
|
||||||
def clean(self, histpath):
|
def clean(self, histpath):
|
||||||
thumbpath = os.path.join(histpath, "th")
|
ret = 0
|
||||||
|
for cat in ["th", "ac"]:
|
||||||
|
ret += self._clean(histpath, cat, None)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _clean(self, histpath, cat, thumbpath):
|
||||||
|
if not thumbpath:
|
||||||
|
thumbpath = os.path.join(histpath, cat)
|
||||||
|
|
||||||
# self.log("cln {}".format(thumbpath))
|
# self.log("cln {}".format(thumbpath))
|
||||||
maxage = self.args.th_maxage
|
exts = ["jpg", "webp"] if cat == "th" else ["opus"]
|
||||||
|
maxage = getattr(self.args, cat + "_maxage")
|
||||||
now = time.time()
|
now = time.time()
|
||||||
prev_b64 = None
|
prev_b64 = None
|
||||||
prev_fp = None
|
prev_fp = None
|
||||||
try:
|
try:
|
||||||
ents = bos.listdir(thumbpath)
|
ents = statdir(self.log, not self.args.no_scandir, False, thumbpath)
|
||||||
|
ents = sorted(list(ents))
|
||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
ndirs = 0
|
ndirs = 0
|
||||||
for f in sorted(ents):
|
for f, inf in ents:
|
||||||
fp = os.path.join(thumbpath, f)
|
fp = os.path.join(thumbpath, f)
|
||||||
cmp = fp.lower().replace("\\", "/")
|
cmp = fp.lower().replace("\\", "/")
|
||||||
|
|
||||||
# "top" or b64 prefix/full (a folder)
|
# "top" or b64 prefix/full (a folder)
|
||||||
if len(f) <= 3 or len(f) == 24:
|
if len(f) <= 3 or len(f) == 24:
|
||||||
age = now - bos.path.getmtime(fp)
|
age = now - inf.st_mtime
|
||||||
if age > maxage:
|
if age > maxage:
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
safe = True
|
safe = True
|
||||||
@@ -396,16 +493,15 @@ class ThumbSrv(object):
|
|||||||
self.log("rm -rf [{}]".format(fp))
|
self.log("rm -rf [{}]".format(fp))
|
||||||
shutil.rmtree(fp, ignore_errors=True)
|
shutil.rmtree(fp, ignore_errors=True)
|
||||||
else:
|
else:
|
||||||
ndirs += self.clean(fp)
|
self._clean(histpath, cat, fp)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# thumb file
|
# thumb file
|
||||||
try:
|
try:
|
||||||
b64, ts, ext = f.split(".")
|
b64, ts, ext = f.split(".")
|
||||||
if len(b64) != 24 or len(ts) != 8 or ext not in ["jpg", "webp"]:
|
if len(b64) != 24 or len(ts) != 8 or ext not in exts:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
ts = int(ts, 16)
|
|
||||||
except:
|
except:
|
||||||
if f != "dir.txt":
|
if f != "dir.txt":
|
||||||
self.log("foreign file in thumbs dir: [{}]".format(fp), 1)
|
self.log("foreign file in thumbs dir: [{}]".format(fp), 1)
|
||||||
@@ -416,6 +512,10 @@ class ThumbSrv(object):
|
|||||||
self.log("rm replaced [{}]".format(fp))
|
self.log("rm replaced [{}]".format(fp))
|
||||||
bos.unlink(prev_fp)
|
bos.unlink(prev_fp)
|
||||||
|
|
||||||
|
if cat != "th" and inf.st_mtime + maxage < now:
|
||||||
|
self.log("rm expired [{}]".format(fp))
|
||||||
|
bos.unlink(fp)
|
||||||
|
|
||||||
prev_b64 = b64
|
prev_b64 = b64
|
||||||
prev_fp = fp
|
prev_fp = fp
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ class U2idx(object):
|
|||||||
if cur:
|
if cur:
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
histpath = self.asrv.vfs.histtab[ptop]
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
|
if not histpath:
|
||||||
|
self.log("no histpath for [{}]".format(ptop))
|
||||||
|
return None
|
||||||
|
|
||||||
db_path = os.path.join(histpath, "up2k.db")
|
db_path = os.path.join(histpath, "up2k.db")
|
||||||
if not bos.path.exists(db_path):
|
if not bos.path.exists(db_path):
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class Up2k(object):
|
|||||||
self.tagq = Queue()
|
self.tagq = Queue()
|
||||||
self.n_hashq = 0
|
self.n_hashq = 0
|
||||||
self.n_tagq = 0
|
self.n_tagq = 0
|
||||||
|
self.gid = 0
|
||||||
self.volstate = {}
|
self.volstate = {}
|
||||||
self.need_rescan = {}
|
self.need_rescan = {}
|
||||||
self.dupesched = {}
|
self.dupesched = {}
|
||||||
@@ -114,6 +115,12 @@ class Up2k(object):
|
|||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
self.gid += 1
|
||||||
|
self.log("reload #{} initiated".format(self.gid))
|
||||||
|
all_vols = self.asrv.vfs.all_vols
|
||||||
|
self.rescan(all_vols, list(all_vols.keys()), True)
|
||||||
|
|
||||||
def deferred_init(self):
|
def deferred_init(self):
|
||||||
all_vols = self.asrv.vfs.all_vols
|
all_vols = self.asrv.vfs.all_vols
|
||||||
have_e2d = self.init_indexes(all_vols)
|
have_e2d = self.init_indexes(all_vols)
|
||||||
@@ -168,15 +175,15 @@ class Up2k(object):
|
|||||||
}
|
}
|
||||||
return json.dumps(ret, indent=4)
|
return json.dumps(ret, indent=4)
|
||||||
|
|
||||||
def rescan(self, all_vols, scan_vols):
|
def rescan(self, all_vols, scan_vols, wait):
|
||||||
if hasattr(self, "pp"):
|
if not wait and hasattr(self, "pp"):
|
||||||
return "cannot initiate; scan is already in progress"
|
return "cannot initiate; scan is already in progress"
|
||||||
|
|
||||||
args = (all_vols, scan_vols)
|
args = (all_vols, scan_vols)
|
||||||
t = threading.Thread(
|
t = threading.Thread(
|
||||||
target=self.init_indexes,
|
target=self.init_indexes,
|
||||||
args=args,
|
args=args,
|
||||||
name="up2k-rescan-{}".format(scan_vols[0]),
|
name="up2k-rescan-{}".format(scan_vols[0] if scan_vols else "all"),
|
||||||
)
|
)
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
@@ -196,6 +203,10 @@ class Up2k(object):
|
|||||||
if now < cooldown:
|
if now < cooldown:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if hasattr(self, "pp"):
|
||||||
|
cooldown = now + 5
|
||||||
|
continue
|
||||||
|
|
||||||
timeout = now + 9001
|
timeout = now + 9001
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
|
for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
|
||||||
@@ -217,7 +228,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
if vols:
|
if vols:
|
||||||
cooldown = now + 10
|
cooldown = now + 10
|
||||||
err = self.rescan(self.asrv.vfs.all_vols, vols)
|
err = self.rescan(self.asrv.vfs.all_vols, vols, False)
|
||||||
if err:
|
if err:
|
||||||
for v in vols:
|
for v in vols:
|
||||||
self.need_rescan[v] = True
|
self.need_rescan[v] = True
|
||||||
@@ -299,6 +310,16 @@ class Up2k(object):
|
|||||||
return True, ret
|
return True, ret
|
||||||
|
|
||||||
def init_indexes(self, all_vols, scan_vols=None):
|
def init_indexes(self, all_vols, scan_vols=None):
|
||||||
|
gid = self.gid
|
||||||
|
while hasattr(self, "pp") and gid == self.gid:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
if gid != self.gid:
|
||||||
|
return
|
||||||
|
|
||||||
|
if gid:
|
||||||
|
self.log("reload #{} running".format(self.gid))
|
||||||
|
|
||||||
self.pp = ProgressPrinter()
|
self.pp = ProgressPrinter()
|
||||||
vols = all_vols.values()
|
vols = all_vols.values()
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@@ -429,7 +450,11 @@ class Up2k(object):
|
|||||||
return have_e2d
|
return have_e2d
|
||||||
|
|
||||||
def register_vpath(self, ptop, flags):
|
def register_vpath(self, ptop, flags):
|
||||||
histpath = self.asrv.vfs.histtab[ptop]
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
|
if not histpath:
|
||||||
|
self.log("no histpath for [{}]".format(ptop))
|
||||||
|
return None
|
||||||
|
|
||||||
db_path = os.path.join(histpath, "up2k.db")
|
db_path = os.path.join(histpath, "up2k.db")
|
||||||
if ptop in self.registry:
|
if ptop in self.registry:
|
||||||
try:
|
try:
|
||||||
@@ -797,10 +822,11 @@ class Up2k(object):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _run_all_mtp(self):
|
def _run_all_mtp(self):
|
||||||
|
gid = self.gid
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
for ptop, flags in self.flags.items():
|
for ptop, flags in self.flags.items():
|
||||||
if "mtp" in flags:
|
if "mtp" in flags:
|
||||||
self._run_one_mtp(ptop)
|
self._run_one_mtp(ptop, gid)
|
||||||
|
|
||||||
td = time.time() - t0
|
td = time.time() - t0
|
||||||
msg = "mtp finished in {:.2f} sec ({})"
|
msg = "mtp finished in {:.2f} sec ({})"
|
||||||
@@ -811,7 +837,10 @@ class Up2k(object):
|
|||||||
if "OFFLINE" not in self.volstate[k]:
|
if "OFFLINE" not in self.volstate[k]:
|
||||||
self.volstate[k] = "online, idle"
|
self.volstate[k] = "online, idle"
|
||||||
|
|
||||||
def _run_one_mtp(self, ptop):
|
def _run_one_mtp(self, ptop, gid):
|
||||||
|
if gid != self.gid:
|
||||||
|
return
|
||||||
|
|
||||||
entags = self.entags[ptop]
|
entags = self.entags[ptop]
|
||||||
|
|
||||||
parsers = {}
|
parsers = {}
|
||||||
@@ -844,6 +873,9 @@ class Up2k(object):
|
|||||||
in_progress = {}
|
in_progress = {}
|
||||||
while True:
|
while True:
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
|
if gid != self.gid:
|
||||||
|
break
|
||||||
|
|
||||||
q = "select w from mt where k = 't:mtp' limit ?"
|
q = "select w from mt where k = 't:mtp' limit ?"
|
||||||
warks = cur.execute(q, (batch_sz,)).fetchall()
|
warks = cur.execute(q, (batch_sz,)).fetchall()
|
||||||
warks = [x[0] for x in warks]
|
warks = [x[0] for x in warks]
|
||||||
@@ -1960,7 +1992,8 @@ class Up2k(object):
|
|||||||
self.snap_prev = {}
|
self.snap_prev = {}
|
||||||
while True:
|
while True:
|
||||||
time.sleep(self.snap_persist_interval)
|
time.sleep(self.snap_persist_interval)
|
||||||
self.do_snapshot()
|
if not hasattr(self, "pp"):
|
||||||
|
self.do_snapshot()
|
||||||
|
|
||||||
def do_snapshot(self):
|
def do_snapshot(self):
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
@@ -1969,7 +2002,10 @@ class Up2k(object):
|
|||||||
|
|
||||||
def _snap_reg(self, ptop, reg):
|
def _snap_reg(self, ptop, reg):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
histpath = self.asrv.vfs.histtab[ptop]
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
|
if not histpath:
|
||||||
|
return
|
||||||
|
|
||||||
rm = [x for x in reg.values() if now - x["poke"] > self.snap_discard_interval]
|
rm = [x for x in reg.values() if now - x["poke"] > self.snap_discard_interval]
|
||||||
if rm:
|
if rm:
|
||||||
m = "dropping {} abandoned uploads in {}".format(len(rm), ptop)
|
m = "dropping {} abandoned uploads in {}".format(len(rm), ptop)
|
||||||
|
|||||||
@@ -100,10 +100,24 @@ IMPLICATIONS = [
|
|||||||
|
|
||||||
|
|
||||||
MIMES = {
|
MIMES = {
|
||||||
"md": "text/plain; charset=UTF-8",
|
"md": "text/plain",
|
||||||
|
"txt": "text/plain",
|
||||||
|
"js": "text/javascript",
|
||||||
"opus": "audio/ogg; codecs=opus",
|
"opus": "audio/ogg; codecs=opus",
|
||||||
"webp": "image/webp",
|
"mp3": "audio/mpeg",
|
||||||
|
"m4a": "audio/mp4",
|
||||||
|
"jpg": "image/jpeg",
|
||||||
}
|
}
|
||||||
|
for ln in """text css html csv
|
||||||
|
application json wasm xml pdf rtf zip
|
||||||
|
image webp jpeg png gif bmp
|
||||||
|
audio aac ogg wav
|
||||||
|
video webm mp4 mpeg
|
||||||
|
font woff woff2 otf ttf
|
||||||
|
""".splitlines():
|
||||||
|
k, vs = ln.split(" ", 1)
|
||||||
|
for v in vs.strip().split():
|
||||||
|
MIMES[v] = "{}/{}".format(k, v)
|
||||||
|
|
||||||
|
|
||||||
REKOBO_KEY = {
|
REKOBO_KEY = {
|
||||||
@@ -1150,12 +1164,14 @@ def hashcopy(fin, fout):
|
|||||||
return tlen, hashobj.hexdigest(), digest_b64
|
return tlen, hashobj.hexdigest(), digest_b64
|
||||||
|
|
||||||
|
|
||||||
def sendfile_py(lower, upper, f, s):
|
def sendfile_py(lower, upper, f, s, bufsz, slp):
|
||||||
remains = upper - lower
|
remains = upper - lower
|
||||||
f.seek(lower)
|
f.seek(lower)
|
||||||
while remains > 0:
|
while remains > 0:
|
||||||
# time.sleep(0.01)
|
if slp:
|
||||||
buf = f.read(min(1024 * 32, remains))
|
time.sleep(slp)
|
||||||
|
|
||||||
|
buf = f.read(min(bufsz, remains))
|
||||||
if not buf:
|
if not buf:
|
||||||
return remains
|
return remains
|
||||||
|
|
||||||
@@ -1168,7 +1184,7 @@ def sendfile_py(lower, upper, f, s):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def sendfile_kern(lower, upper, f, s):
|
def sendfile_kern(lower, upper, f, s, bufsz, slp):
|
||||||
out_fd = s.fileno()
|
out_fd = s.fileno()
|
||||||
in_fd = f.fileno()
|
in_fd = f.fileno()
|
||||||
ofs = lower
|
ofs = lower
|
||||||
@@ -1280,11 +1296,18 @@ def guess_mime(url, fallback="application/octet-stream"):
|
|||||||
except:
|
except:
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
ret = MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
|
ret = MIMES.get(ext)
|
||||||
|
|
||||||
|
if not ret:
|
||||||
|
x = mimetypes.guess_type(url)
|
||||||
|
ret = "application/{}".format(x[1]) if x[1] else x[0]
|
||||||
|
|
||||||
|
if not ret:
|
||||||
|
ret = fallback
|
||||||
|
|
||||||
if ";" not in ret:
|
if ";" not in ret:
|
||||||
if ret.startswith("text/") or ret.endswith("/javascript"):
|
if ret.startswith("text/") or ret.endswith("/javascript"):
|
||||||
ret += "; charset=UTF-8"
|
ret += "; charset=utf-8"
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function keyDownHandler(e) {
|
function keyDownHandler(e) {
|
||||||
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing || modal.busy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var k = e.code + '', v = vid();
|
var k = e.code + '', v = vid();
|
||||||
@@ -331,7 +331,7 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
function tglsel() {
|
function tglsel() {
|
||||||
var thumb = currentGallery[currentIndex].imageElement,
|
var thumb = currentGallery[currentIndex].imageElement,
|
||||||
name = vsplit(thumb.href)[1],
|
name = vsplit(thumb.href)[1].split('?')[0],
|
||||||
files = msel.getall();
|
files = msel.getall();
|
||||||
|
|
||||||
for (var a = 0; a < files.length; a++)
|
for (var a = 0; a < files.length; a++)
|
||||||
@@ -345,7 +345,7 @@ window.baguetteBox = (function () {
|
|||||||
function selbg() {
|
function selbg() {
|
||||||
var img = vidimg(),
|
var img = vidimg(),
|
||||||
thumb = currentGallery[currentIndex].imageElement,
|
thumb = currentGallery[currentIndex].imageElement,
|
||||||
name = vsplit(thumb.href)[1],
|
name = vsplit(thumb.href)[1].split('?')[0],
|
||||||
files = msel.getsel(),
|
files = msel.getsel(),
|
||||||
sel = false;
|
sel = false;
|
||||||
|
|
||||||
@@ -530,9 +530,7 @@ window.baguetteBox = (function () {
|
|||||||
if (options.bodyClass && document.body.classList)
|
if (options.bodyClass && document.body.classList)
|
||||||
document.body.classList.remove(options.bodyClass);
|
document.body.classList.remove(options.bodyClass);
|
||||||
|
|
||||||
var h = ebi('bbox-halp');
|
qsr('#bbox-halp');
|
||||||
if (h)
|
|
||||||
h.parentNode.removeChild(h);
|
|
||||||
|
|
||||||
if (options.afterHide)
|
if (options.afterHide)
|
||||||
options.afterHide();
|
options.afterHide();
|
||||||
@@ -590,8 +588,7 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
image.addEventListener(is_vid ? 'loadedmetadata' : 'load', function () {
|
image.addEventListener(is_vid ? 'loadedmetadata' : 'load', function () {
|
||||||
// Remove loader element
|
// Remove loader element
|
||||||
var spinner = QS('#baguette-img-' + index + ' .bbox-spinner');
|
qsr('#baguette-img-' + index + ' .bbox-spinner');
|
||||||
figure.removeChild(spinner);
|
|
||||||
if (!options.async && callback)
|
if (!options.async && callback)
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
pre, code, tt {
|
pre, code, tt, #doc, #doc>code {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
}
|
}
|
||||||
#path,
|
#path,
|
||||||
@@ -31,9 +31,8 @@ pre, code, tt {
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
#path {
|
#path {
|
||||||
color: #aca;
|
color: #ccc;
|
||||||
text-shadow: 1px 1px 0 #000;
|
text-shadow: 1px 1px 0 #000;
|
||||||
font-variant: small-caps;
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: .35em .5em .2em .5em;
|
padding: .35em .5em .2em .5em;
|
||||||
@@ -205,6 +204,12 @@ a, #files tbody div a:last-child {
|
|||||||
#repl {
|
#repl {
|
||||||
padding: .33em;
|
padding: .33em;
|
||||||
}
|
}
|
||||||
|
#files a.doc {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
#files a.doc.bri {
|
||||||
|
color: #f5a;
|
||||||
|
}
|
||||||
#files tbody a.play {
|
#files tbody a.play {
|
||||||
color: #e70;
|
color: #e70;
|
||||||
padding: .2em;
|
padding: .2em;
|
||||||
@@ -554,6 +559,11 @@ input[type="radio"]:checked+label,
|
|||||||
input[type="checkbox"]:checked+label {
|
input[type="checkbox"]:checked+label {
|
||||||
color: #fc5;
|
color: #fc5;
|
||||||
}
|
}
|
||||||
|
.opwide div>span>input+label {
|
||||||
|
padding: .3em 0 .3em .3em;
|
||||||
|
margin: 0 0 0 -.3em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.opview input.i {
|
.opview input.i {
|
||||||
width: calc(100% - 16.2em);
|
width: calc(100% - 16.2em);
|
||||||
}
|
}
|
||||||
@@ -668,6 +678,17 @@ input.eq_gain {
|
|||||||
border-bottom: 1px solid #111;
|
border-bottom: 1px solid #111;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
#treepar {
|
||||||
|
z-index: 1;
|
||||||
|
position: fixed;
|
||||||
|
left: -.75em;
|
||||||
|
width: calc(var(--nav-sz) - 0.5em);
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#treepar.off {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#tree, #treeh {
|
#tree, #treeh {
|
||||||
border-radius: 0 .3em 0 0;
|
border-radius: 0 .3em 0 0;
|
||||||
}
|
}
|
||||||
@@ -732,37 +753,46 @@ input.eq_gain {
|
|||||||
#tree li:last-child {
|
#tree li:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
#treeul a.hl {
|
#tree ul a.sel {
|
||||||
|
background: #111;
|
||||||
|
box-shadow: -.8em 0 0 #c37 inset;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#tree ul a.hl {
|
||||||
color: #400;
|
color: #400;
|
||||||
background: #fc4;
|
background: #fc4;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
#treeul a {
|
#tree ul a.par {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#tree ul a {
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
#treeul a+a {
|
.ntree a+a {
|
||||||
width: calc(100% - 2em);
|
width: calc(100% - 2em);
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
#tree.nowrap #treeul li {
|
#tree.nowrap li {
|
||||||
min-height: 1.4em;
|
min-height: 1.4em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
#tree.nowrap #treeul a+a:hover {
|
#tree.nowrap .ntree a+a:hover {
|
||||||
background: rgba(16, 16, 16, 0.67);
|
background: rgba(16, 16, 16, 0.67);
|
||||||
min-width: calc(var(--nav-sz) - 2em);
|
min-width: calc(var(--nav-sz) - 2em);
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
html.light #tree.nowrap #treeul a+a:hover {
|
html.light #tree.nowrap .ntree a+a:hover {
|
||||||
background: rgba(255, 255, 255, 0.67);
|
background: rgba(255, 255, 255, 0.67);
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
#treeul a+a:hover {
|
#docul a:hover,
|
||||||
|
#tree .ntree a+a:hover {
|
||||||
background: #181818;
|
background: #181818;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
#treeul a:first-child {
|
.ntree a:first-child {
|
||||||
font-family: 'scp', monospace, monospace;
|
font-family: 'scp', monospace, monospace;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
@@ -845,11 +875,13 @@ html.light #tree.nowrap #treeul a+a:hover {
|
|||||||
border-bottom: 1px solid #555;
|
border-bottom: 1px solid #555;
|
||||||
}
|
}
|
||||||
#thumbs,
|
#thumbs,
|
||||||
|
#au_fullpre,
|
||||||
#au_osd_cv,
|
#au_osd_cv,
|
||||||
#u2tdate {
|
#u2tdate {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
}
|
}
|
||||||
#griden.on+#thumbs,
|
#griden.on+#thumbs,
|
||||||
|
#au_preload.on+#au_fullpre,
|
||||||
#au_os_ctl.on+#au_osd_cv,
|
#au_os_ctl.on+#au_osd_cv,
|
||||||
#u2turbo.on+#u2tdate {
|
#u2turbo.on+#u2tdate {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -857,30 +889,31 @@ html.light #tree.nowrap #treeul a+a:hover {
|
|||||||
#wraptree.on+#hovertree {
|
#wraptree.on+#hovertree {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#ghead {
|
.ghead {
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
padding: .2em .5em;
|
padding: .2em .5em;
|
||||||
line-height: 2.3em;
|
line-height: 2.3em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
#ghead {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: -.3em;
|
top: -.3em;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
html.light #ghead {
|
html.light .ghead {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
border-color: #ddd;
|
border-color: #ddd;
|
||||||
}
|
}
|
||||||
#ghead .btn {
|
.ghead .btn {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
#ghead>span {
|
.ghead>span {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
padding-left: .3em;
|
padding-left: .3em;
|
||||||
}
|
}
|
||||||
#ggrid {
|
#ggrid {
|
||||||
padding-top: .5em;
|
margin: -.2em -.5em;
|
||||||
margin: 0 -.5em;
|
|
||||||
}
|
}
|
||||||
#ggrid>a>span {
|
#ggrid>a>span {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -989,6 +1022,53 @@ html.light #rui {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
#doc {
|
||||||
|
background: none;
|
||||||
|
overflow: visible;
|
||||||
|
margin: -1em 0 .5em 0;
|
||||||
|
padding: 1em 0 1em 0;
|
||||||
|
}
|
||||||
|
#docul {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#docul li.bn {
|
||||||
|
text-align: center;
|
||||||
|
padding: .5em;
|
||||||
|
}
|
||||||
|
#doc.prism {
|
||||||
|
padding-left: 3em;
|
||||||
|
}
|
||||||
|
#doc>code {
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#doc.mdo {
|
||||||
|
white-space: normal;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
#doc.prism * {
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
#doc .line-highlight {
|
||||||
|
border-radius: .3em;
|
||||||
|
box-shadow: 0 0 .5em #333;
|
||||||
|
background: linear-gradient(90deg, #111, #222);
|
||||||
|
}
|
||||||
|
html.light #doc .line-highlight {
|
||||||
|
box-shadow: 0 0 .5em #ccc;
|
||||||
|
background: linear-gradient(90deg, #fff, #eee);
|
||||||
|
}
|
||||||
|
#docul li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#tree #docul a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#seldoc.sel {
|
||||||
|
color: #fff;
|
||||||
|
background: #925;
|
||||||
|
}
|
||||||
#pvol,
|
#pvol,
|
||||||
#barbuf,
|
#barbuf,
|
||||||
#barpos,
|
#barpos,
|
||||||
@@ -1043,7 +1123,7 @@ html,
|
|||||||
.opbox,
|
.opbox,
|
||||||
#path,
|
#path,
|
||||||
#srch_form,
|
#srch_form,
|
||||||
#ghead {
|
.ghead {
|
||||||
background: #2b2b2b;
|
background: #2b2b2b;
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
box-shadow: 0 0 .3em #111;
|
box-shadow: 0 0 .3em #111;
|
||||||
@@ -1052,7 +1132,8 @@ html,
|
|||||||
background: #282828;
|
background: #282828;
|
||||||
}
|
}
|
||||||
#tree,
|
#tree,
|
||||||
#treeh {
|
#treeh,
|
||||||
|
#treepar {
|
||||||
background: #2b2b2b;
|
background: #2b2b2b;
|
||||||
}
|
}
|
||||||
#wtoggle,
|
#wtoggle,
|
||||||
@@ -1110,12 +1191,15 @@ html.light #ops,
|
|||||||
html.light .opbox,
|
html.light .opbox,
|
||||||
html.light #path,
|
html.light #path,
|
||||||
html.light #srch_form,
|
html.light #srch_form,
|
||||||
html.light #ghead,
|
html.light .ghead,
|
||||||
html.light #u2etas {
|
html.light #u2etas {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
box-shadow: 0 0 .3em #ccc;
|
box-shadow: 0 0 .3em #ccc;
|
||||||
border-color: #f7f7f7;
|
border-color: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
html.light #wrap.doc {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
html.light #ops a.act {
|
html.light #ops a.act {
|
||||||
box-shadow: 0 .2em .2em #ccc;
|
box-shadow: 0 .2em .2em #ccc;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -1154,21 +1238,25 @@ html.light #acc_info {
|
|||||||
html.light #srv_info span {
|
html.light #srv_info span {
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
html.light #treeul a+a {
|
html.light #tree .ntree a+a {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
color: #06a;
|
color: #06a;
|
||||||
}
|
}
|
||||||
html.light #treeul a.hl {
|
html.light #tree ul a.hl {
|
||||||
background: #07a;
|
background: #07a;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
html.light #treeul a.hl:hover {
|
html.light #tree ul a.par {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
html.light #tree ul a.hl:hover {
|
||||||
background: #059;
|
background: #059;
|
||||||
}
|
}
|
||||||
html.light #tree li {
|
html.light #tree li,
|
||||||
|
html.light #tree #treepar {
|
||||||
border-color: #f7f7f7 #fff #ddd #fff;
|
border-color: #f7f7f7 #fff #ddd #fff;
|
||||||
}
|
}
|
||||||
html.light #treeul a:hover {
|
html.light #tree ul a:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
html.light #tree ul {
|
html.light #tree ul {
|
||||||
@@ -1216,6 +1304,12 @@ html.light #files tbody a.play {
|
|||||||
html.light #files tbody a.play.act {
|
html.light #files tbody a.play.act {
|
||||||
color: #90c;
|
color: #90c;
|
||||||
}
|
}
|
||||||
|
html.light #files a.doc {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
html.light #files a.doc.bri {
|
||||||
|
color: #d38;
|
||||||
|
}
|
||||||
html.light #files tr.play td {
|
html.light #files tr.play td {
|
||||||
background: #fc5;
|
background: #fc5;
|
||||||
border-color: #eb1;
|
border-color: #eb1;
|
||||||
@@ -1283,6 +1377,7 @@ html.light #files td div span {
|
|||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
html.light #path {
|
html.light #path {
|
||||||
|
color: #777;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
box-shadow: 0 0 .3em #bbb;
|
box-shadow: 0 0 .3em #bbb;
|
||||||
@@ -1300,13 +1395,15 @@ html.light #path a:hover {
|
|||||||
html.light #files tbody div a {
|
html.light #files tbody div a {
|
||||||
color: #d38;
|
color: #d38;
|
||||||
}
|
}
|
||||||
|
html.light #docul a:hover,
|
||||||
html.light #files a:hover,
|
html.light #files a:hover,
|
||||||
html.light #files tr.sel a:hover {
|
html.light #files tr.sel a:hover {
|
||||||
color: #000;
|
color: #000;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
html.light #treeh {
|
html.light #treeh,
|
||||||
|
html.light #treepar {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
border-color: #ddd;
|
border-color: #ddd;
|
||||||
}
|
}
|
||||||
@@ -2004,10 +2101,6 @@ html.light #u2foot .warn span {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
padding-left: .2em;
|
padding-left: .2em;
|
||||||
}
|
}
|
||||||
#u2cleanup {
|
|
||||||
float: right;
|
|
||||||
margin-bottom: -.3em;
|
|
||||||
}
|
|
||||||
.fsearch_explain {
|
.fsearch_explain {
|
||||||
padding-left: .7em;
|
padding-left: .7em;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
|||||||
@@ -76,6 +76,12 @@
|
|||||||
|
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
|
|
||||||
|
{%- if doc %}
|
||||||
|
<div id="bdoc"><pre>{{ doc|e }}</pre></div>
|
||||||
|
{%- else %}
|
||||||
|
<div id="bdoc"></div>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
<div id="pro" class="logue">{{ logues[0] }}</div>
|
<div id="pro" class="logue">{{ logues[0] }}</div>
|
||||||
|
|
||||||
<table id="files">
|
<table id="files">
|
||||||
@@ -130,10 +136,13 @@
|
|||||||
def_hcols = {{ def_hcols|tojson }},
|
def_hcols = {{ def_hcols|tojson }},
|
||||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
|
have_acode = {{ have_acode|tojson }},
|
||||||
have_mv = {{ have_mv|tojson }},
|
have_mv = {{ have_mv|tojson }},
|
||||||
have_del = {{ have_del|tojson }},
|
have_del = {{ have_del|tojson }},
|
||||||
have_unpost = {{ have_unpost|tojson }},
|
have_unpost = {{ have_unpost|tojson }},
|
||||||
have_zip = {{ have_zip|tojson }},
|
have_zip = {{ have_zip|tojson }},
|
||||||
|
txt_ext = "{{ txt_ext }}",
|
||||||
|
{% if no_prism %}no_prism = 1,{% endif %}
|
||||||
readme = {{ readme|tojson }};
|
readme = {{ readme|tojson }};
|
||||||
|
|
||||||
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -164,10 +164,7 @@ function copydom(src, dst, lv) {
|
|||||||
|
|
||||||
|
|
||||||
function md_plug_err(ex, js) {
|
function md_plug_err(ex, js) {
|
||||||
var errbox = ebi('md_errbox');
|
qsr('#md_errbox');
|
||||||
if (errbox)
|
|
||||||
errbox.parentNode.removeChild(errbox);
|
|
||||||
|
|
||||||
if (!ex)
|
if (!ex)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -183,7 +180,7 @@ function md_plug_err(ex, js) {
|
|||||||
o.textContent = lns[ln - 1];
|
o.textContent = lns[ln - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errbox = mknod('div');
|
var errbox = mknod('div');
|
||||||
errbox.setAttribute('id', 'md_errbox');
|
errbox.setAttribute('id', 'md_errbox');
|
||||||
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
|
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
|
||||||
errbox.textContent = msg;
|
errbox.textContent = msg;
|
||||||
@@ -381,8 +378,7 @@ function convert_markdown(md_text, dest_dom) {
|
|||||||
|
|
||||||
|
|
||||||
function init_toc() {
|
function init_toc() {
|
||||||
var loader = ebi('ml');
|
qsr('#ml');
|
||||||
loader.parentNode.removeChild(loader);
|
|
||||||
|
|
||||||
var anchors = []; // list of toc entries, complex objects
|
var anchors = []; // list of toc entries, complex objects
|
||||||
var anchor = null; // current toc node
|
var anchor = null; // current toc node
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ var mde = (function () {
|
|||||||
mde.codemirror.on("change", function () {
|
mde.codemirror.on("change", function () {
|
||||||
md_changed(mde);
|
md_changed(mde);
|
||||||
});
|
});
|
||||||
var loader = ebi('ml');
|
qsr('#ml');
|
||||||
loader.parentNode.removeChild(loader);
|
|
||||||
return mde;
|
return mde;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -29,15 +29,18 @@ a {
|
|||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
padding: .2em .8em;
|
padding: .2em .8em;
|
||||||
}
|
}
|
||||||
|
a+a {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
.refresh,
|
.refresh,
|
||||||
.logout {
|
.logout {
|
||||||
float: right;
|
float: right;
|
||||||
margin-top: -.2em;
|
margin: -.2em 0 0 .5em;
|
||||||
}
|
}
|
||||||
.logout {
|
.logout,
|
||||||
|
.btns a {
|
||||||
color: #c04;
|
color: #c04;
|
||||||
border-color: #c7a;
|
border-color: #c7a;
|
||||||
margin-right: .5em;
|
|
||||||
}
|
}
|
||||||
#repl {
|
#repl {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -52,6 +55,7 @@ table {
|
|||||||
.vols th {
|
.vols th {
|
||||||
padding: .3em .6em;
|
padding: .3em .6em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.num {
|
.num {
|
||||||
border-right: 1px solid #bbb;
|
border-right: 1px solid #bbb;
|
||||||
@@ -91,7 +95,8 @@ html.dark a {
|
|||||||
background: #057;
|
background: #057;
|
||||||
border-color: #37a;
|
border-color: #37a;
|
||||||
}
|
}
|
||||||
html.dark .logout {
|
html.dark .logout,
|
||||||
|
html.dark .btns a {
|
||||||
background: #804;
|
background: #804;
|
||||||
border-color: #c28;
|
border-color: #c28;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a href="/?stack">dump stack</a>
|
<a href="/?stack" tt="shows the state of all active threads">dump stack</a>
|
||||||
|
<a href="/?reload=cfg" tt="reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes">reload cfg</a>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
@@ -88,5 +89,6 @@ if (localStorage.lightmode != 1)
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
|
<script>tt.init();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ html {
|
|||||||
}
|
}
|
||||||
#modalc code,
|
#modalc code,
|
||||||
#tt code {
|
#tt code {
|
||||||
background: #3c3c3c;
|
color: #eee;
|
||||||
|
background: #444;
|
||||||
padding: .1em .3em;
|
padding: .1em .3em;
|
||||||
border-top: 1px solid #777;
|
border-top: 1px solid #777;
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
|
|||||||
@@ -332,8 +332,7 @@ function U2pvis(act, btns) {
|
|||||||
r.head++;
|
r.head++;
|
||||||
|
|
||||||
if (!bz_act) {
|
if (!bz_act) {
|
||||||
var tr = ebi("f" + nfile);
|
qsr("#f" + nfile);
|
||||||
tr.parentNode.removeChild(tr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else return;
|
else return;
|
||||||
@@ -352,9 +351,7 @@ function U2pvis(act, btns) {
|
|||||||
last = parseInt(last.getAttribute('id').slice(1));
|
last = parseInt(last.getAttribute('id').slice(1));
|
||||||
|
|
||||||
while (r.head - first > r.wsz) {
|
while (r.head - first > r.wsz) {
|
||||||
var obj = ebi('f' + (first++));
|
qsr('#f' + (first++));
|
||||||
if (obj)
|
|
||||||
obj.parentNode.removeChild(obj);
|
|
||||||
}
|
}
|
||||||
while (last - r.tail < r.wsz && last < r.tab.length - 2) {
|
while (last - r.tail < r.wsz && last < r.tab.length - 2) {
|
||||||
var obj = ebi('f' + (++last));
|
var obj = ebi('f' + (++last));
|
||||||
@@ -565,9 +562,9 @@ function fsearch_explain(n) {
|
|||||||
return toast.inf(60, 'your access to this folder is Read-Only\n\n' + (acct == '*' ? 'you are currently not logged in' : 'you are currently logged in as "' + acct + '"'));
|
return toast.inf(60, 'your access to this folder is Read-Only\n\n' + (acct == '*' ? 'you are currently not logged in' : 'you are currently logged in as "' + acct + '"'));
|
||||||
|
|
||||||
if (bcfg_get('fsearch', false))
|
if (bcfg_get('fsearch', false))
|
||||||
return toast.inf(60, 'you are currently in file-search mode\n\nswitch to upload-mode by clicking the green magnifying glass (next to the big yellow search button), and then refresh\n\nsorry');
|
return toast.inf(60, 'you are currently in file-search mode\n\nswitch to upload-mode by clicking the green magnifying glass (next to the big yellow search button), and try uploading again\n\nsorry');
|
||||||
|
|
||||||
return toast.inf(60, 'refresh the page and try again, it should work now');
|
return toast.inf(60, 'try again, it should work now');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -672,6 +669,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
var st = {
|
var st = {
|
||||||
"files": [],
|
"files": [],
|
||||||
|
"seen": {},
|
||||||
"todo": {
|
"todo": {
|
||||||
"head": [],
|
"head": [],
|
||||||
"hash": [],
|
"hash": [],
|
||||||
@@ -997,13 +995,9 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function up_them(good_files) {
|
function up_them(good_files) {
|
||||||
var seen = {},
|
var evpath = get_evpath(),
|
||||||
evpath = get_evpath(),
|
|
||||||
draw_each = good_files.length < 50;
|
draw_each = good_files.length < 50;
|
||||||
|
|
||||||
for (var a = 0; a < st.files.length; a++)
|
|
||||||
seen[st.files[a].name + '\n' + st.files[a].size] = 1;
|
|
||||||
|
|
||||||
for (var a = 0; a < good_files.length; a++) {
|
for (var a = 0; a < good_files.length; a++) {
|
||||||
var fobj = good_files[a][0],
|
var fobj = good_files[a][0],
|
||||||
name = good_files[a][1],
|
name = good_files[a][1],
|
||||||
@@ -1029,15 +1023,20 @@ function up2k_init(subtle) {
|
|||||||
"bytes_uploaded": 0,
|
"bytes_uploaded": 0,
|
||||||
"hash": []
|
"hash": []
|
||||||
},
|
},
|
||||||
key = entry.name + '\n' + entry.size;
|
key = name + '\n' + entry.size + '\n' + lmod + '\n' + uc.fsearch;
|
||||||
|
|
||||||
if (uc.fsearch)
|
if (uc.fsearch)
|
||||||
entry.srch = 1;
|
entry.srch = 1;
|
||||||
|
|
||||||
if (seen[key])
|
try {
|
||||||
continue;
|
if (st.seen[fdir][key])
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
st.seen[fdir] = {};
|
||||||
|
}
|
||||||
|
|
||||||
seen[key] = 1;
|
st.seen[fdir][key] = 1;
|
||||||
|
|
||||||
pvis.addfile([
|
pvis.addfile([
|
||||||
uc.fsearch ? esc(entry.name) : linksplit(
|
uc.fsearch ? esc(entry.name) : linksplit(
|
||||||
@@ -1070,22 +1069,6 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
more_one_file();
|
more_one_file();
|
||||||
|
|
||||||
function u2cleanup(e) {
|
|
||||||
ev(e);
|
|
||||||
for (var a = 0; a < st.files.length; a++) {
|
|
||||||
var t = st.files[a];
|
|
||||||
if (t.done && t.name) {
|
|
||||||
var tr = ebi('f' + t.n);
|
|
||||||
if (!tr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
tr.parentNode.removeChild(tr);
|
|
||||||
t.name = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ebi('u2cleanup').onclick = u2cleanup;
|
|
||||||
|
|
||||||
var etaref = 0, etaskip = 0, op_minh = 0;
|
var etaref = 0, etaskip = 0, op_minh = 0;
|
||||||
function etafun() {
|
function etafun() {
|
||||||
var nhash = st.busy.head.length + st.busy.hash.length + st.todo.head.length + st.todo.hash.length,
|
var nhash = st.busy.head.length + st.busy.hash.length + st.todo.head.length + st.todo.hash.length,
|
||||||
@@ -1916,8 +1899,8 @@ function up2k_init(subtle) {
|
|||||||
wpx = window.innerWidth,
|
wpx = window.innerWidth,
|
||||||
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
||||||
wem = wpx * 1.0 / fpx,
|
wem = wpx * 1.0 / fpx,
|
||||||
wide = wem > 54 ? 'w' : '',
|
|
||||||
write = has(perms, 'write'),
|
write = has(perms, 'write'),
|
||||||
|
wide = write && wem > 54 ? 'w' : '',
|
||||||
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
|
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
|
||||||
btn = ebi('u2btn');
|
btn = ebi('u2btn');
|
||||||
|
|
||||||
@@ -1929,7 +1912,7 @@ function up2k_init(subtle) {
|
|||||||
ebi('u2etaw').setAttribute('class', wide);
|
ebi('u2etaw').setAttribute('class', wide);
|
||||||
}
|
}
|
||||||
|
|
||||||
wide = wem > 78 ? 'ww' : wide;
|
wide = write && wem > 78 ? 'ww' : wide;
|
||||||
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
|
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
|
||||||
var its = [ebi('u2etaw'), ebi('u2cards')];
|
var its = [ebi('u2etaw'), ebi('u2cards')];
|
||||||
if (its[0].parentNode !== parent) {
|
if (its[0].parentNode !== parent) {
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ var ebi = document.getElementById.bind(document),
|
|||||||
mknod = document.createElement.bind(document);
|
mknod = document.createElement.bind(document);
|
||||||
|
|
||||||
|
|
||||||
|
function qsr(sel) {
|
||||||
|
var el = QS(sel);
|
||||||
|
if (el)
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// error handler for mobile devices
|
// error handler for mobile devices
|
||||||
function esc(txt) {
|
function esc(txt) {
|
||||||
return txt.replace(/[&"<>]/g, function (c) {
|
return txt.replace(/[&"<>]/g, function (c) {
|
||||||
@@ -163,7 +172,6 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
|||||||
catch (e) {
|
catch (e) {
|
||||||
document.body.innerHTML = html.join('\n');
|
document.body.innerHTML = html.join('\n');
|
||||||
}
|
}
|
||||||
throw 'fatal_err';
|
|
||||||
}
|
}
|
||||||
function ignex(all) {
|
function ignex(all) {
|
||||||
var o = ebi('exbox');
|
var o = ebi('exbox');
|
||||||
@@ -727,7 +735,7 @@ function hist_replace(url) {
|
|||||||
|
|
||||||
function sethash(hv) {
|
function sethash(hv) {
|
||||||
if (window.history && history.replaceState) {
|
if (window.history && history.replaceState) {
|
||||||
hist_replace(document.location.pathname + '#' + hv);
|
hist_replace(document.location.pathname + document.location.search + '#' + hv);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
document.location.hash = hv;
|
document.location.hash = hv;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ point `--css-browser` to one of these by URL:
|
|||||||
* notes on using rclone as a fuse client/server
|
* notes on using rclone as a fuse client/server
|
||||||
|
|
||||||
## [`example.conf`](example.conf)
|
## [`example.conf`](example.conf)
|
||||||
* example config file for `-c` (supports accounts, volumes, and volume-flags)
|
* example config file for `-c`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
# append some arguments to the commandline;
|
||||||
|
# the first space in a line counts as a separator,
|
||||||
|
# any additional spaces are part of the value
|
||||||
|
-e2dsa
|
||||||
|
-e2ts
|
||||||
|
-i 127.0.0.1
|
||||||
|
|
||||||
# create users:
|
# create users:
|
||||||
# u username:password
|
# u username:password
|
||||||
u ed:123
|
u ed:123
|
||||||
@@ -24,7 +31,8 @@ rw ed
|
|||||||
r k
|
r k
|
||||||
rw ed
|
rw ed
|
||||||
|
|
||||||
# this does the same thing:
|
# this does the same thing,
|
||||||
|
# and will cause an error on startup since /priv is already taken:
|
||||||
./priv
|
./priv
|
||||||
/priv
|
/priv
|
||||||
r ed k
|
r ed k
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#ops, #tree, #path, #wrap>h2:last-child, /* main tabs and navigators (tree/breadcrumbs) */
|
#ops, #tree, #path, #wrap>h2:last-child, /* main tabs and navigators (tree/breadcrumbs) */
|
||||||
|
|
||||||
#u2cleanup, #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
#u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */
|
||||||
|
|
||||||
#srch_dz, #srch_zd, /* the filesearch dropzone */
|
#srch_dz, #srch_zd, /* the filesearch dropzone */
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ para() { for s in 1 2 3 4 5 6 7 8 12 16 24 32 48 64; do echo $s; for r in {1..4}
|
|||||||
avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} csz=$1;sum=0;nsmp=0} {sub(/\r$/,"")} /^[0-9]+$/ {pr($1);next} / MiB/ {sub(/ MiB.*/,"");sub(/.* /,"");sum+=$1;nsmp++} END {pr(0)}' "$1"; }
|
avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} csz=$1;sum=0;nsmp=0} {sub(/\r$/,"")} /^[0-9]+$/ {pr($1);next} / MiB/ {sub(/ MiB.*/,"");sub(/.* /,"");sum+=$1;nsmp++} END {pr(0)}' "$1"; }
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## time between first and last upload
|
||||||
|
|
||||||
|
python3 -um copyparty -nw -v srv::rw -i 127.0.0.1 2>&1 | tee log
|
||||||
|
cat log | awk '!/"purl"/{next} {s=$1;sub(/[^m]+m/,"");gsub(/:/," ");t=60*(60*$1+$2)+$3} !a{a=t;sa=s} {b=t;sb=s} END {print b-a,sa,sb}'
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## bad filenames
|
## bad filenames
|
||||||
|
|
||||||
@@ -162,7 +169,7 @@ brew install python@2
|
|||||||
pip install virtualenv
|
pip install virtualenv
|
||||||
|
|
||||||
# readme toc
|
# readme toc
|
||||||
cat README.md | awk 'function pr() { if (!h) {return}; if (/^ *[*!#]/||!s) {printf "%s\n",h;h=0;return}; if (/.../) {printf "%s - %s\n",h,$0;h=0}; }; /^#/{s=1;pr()} /^#* *(file indexing|install on android|dev env setup|just the sfx|complete release|optional gpl stuff)|`$/{s=0} /^#/{lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab); h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab);next} !h{next} {sub(/ .*/,"");sub(/[:,]$/,"")} {pr()}' > toc; grep -E '^## readme toc' -B1000 -A2 <README.md >p1; grep -E '^## quickstart' -B2 -A999999 <README.md >p2; (cat p1; grep quickstart -A1000 <toc; cat p2) >README.md; rm p1 p2 toc
|
cat README.md | awk 'function pr() { if (!h) {return}; if (/^ *[*!#|]/||!s) {printf "%s\n",h;h=0;return}; if (/.../) {printf "%s - %s\n",h,$0;h=0}; }; /^#/{s=1;pr()} /^#* *(file indexing|install on android|dev env setup|just the sfx|complete release|optional gpl stuff)|`$/{s=0} /^#/{lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab); h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab);next} !h{next} {sub(/ .*/,"");sub(/[:,]$/,"")} {pr()}' > toc; grep -E '^## readme toc' -B1000 -A2 <README.md >p1; grep -E '^## quickstart' -B2 -A999999 <README.md >p2; (cat p1; grep quickstart -A1000 <toc; cat p2) >README.md; rm p1 p2 toc
|
||||||
|
|
||||||
# fix firefox phantom breakpoints,
|
# fix firefox phantom breakpoints,
|
||||||
# suggestions from bugtracker, doesnt work (debugger is not attachable)
|
# suggestions from bugtracker, doesnt work (debugger is not attachable)
|
||||||
|
|||||||
@@ -10,14 +10,41 @@ set -e
|
|||||||
# (and those are usually linux so bash is good inaff)
|
# (and those are usually linux so bash is good inaff)
|
||||||
# (but that said this even has macos support)
|
# (but that said this even has macos support)
|
||||||
#
|
#
|
||||||
# bundle will look like:
|
# output summary (filesizes and contents):
|
||||||
# -rwxr-xr-x 0 ed ed 183808 Nov 19 00:43 copyparty
|
#
|
||||||
# -rw-r--r-- 0 ed ed 491318 Nov 19 00:40 copyparty-extras/copyparty-0.5.4.tar.gz
|
# 535672 copyparty-extras/sfx-full/copyparty-sfx.sh
|
||||||
# -rwxr-xr-x 0 ed ed 30254 Nov 17 23:58 copyparty-extras/copyparty-fuse.py
|
# 550760 copyparty-extras/sfx-full/copyparty-sfx.py
|
||||||
# -rwxr-xr-x 0 ed ed 481403 Nov 19 00:40 copyparty-extras/sfx-full/copyparty-sfx.sh
|
# `- original unmodified sfx from github
|
||||||
# -rwxr-xr-x 0 ed ed 506043 Nov 19 00:40 copyparty-extras/sfx-full/copyparty-sfx.py
|
#
|
||||||
# -rwxr-xr-x 0 ed ed 167699 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.sh
|
# 572923 copyparty-extras/sfx-full/copyparty-sfx-gz.py
|
||||||
# -rwxr-xr-x 0 ed ed 183808 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.py
|
# `- unmodified but recompressed from bzip2 to gzip
|
||||||
|
#
|
||||||
|
# 341792 copyparty-extras/sfx-ent/copyparty-sfx.sh
|
||||||
|
# 353975 copyparty-extras/sfx-ent/copyparty-sfx.py
|
||||||
|
# 376934 copyparty-extras/sfx-ent/copyparty-sfx-gz.py
|
||||||
|
# `- removed iOS ogg/opus/vorbis audio decoder,
|
||||||
|
# removed the audio tray mouse cursor,
|
||||||
|
# "enterprise edition"
|
||||||
|
#
|
||||||
|
# 259288 copyparty-extras/sfx-lite/copyparty-sfx.sh
|
||||||
|
# 270004 copyparty-extras/sfx-lite/copyparty-sfx.py
|
||||||
|
# 293159 copyparty-extras/sfx-lite/copyparty-sfx-gz.py
|
||||||
|
# `- also removed the codemirror markdown editor
|
||||||
|
# and the text-viewer syntax hilighting,
|
||||||
|
# only essential features remaining
|
||||||
|
#
|
||||||
|
# 646297 copyparty-extras/copyparty-1.0.14.tar.gz
|
||||||
|
# 4823 copyparty-extras/copyparty-repack.sh
|
||||||
|
# `- source files from github
|
||||||
|
#
|
||||||
|
# 23663 copyparty-extras/up2k.py
|
||||||
|
# `- standalone utility to upload or search for files
|
||||||
|
#
|
||||||
|
# 32280 copyparty-extras/copyparty-fuse.py
|
||||||
|
# `- standalone to mount a URL as a local read-only filesystem
|
||||||
|
#
|
||||||
|
# 270004 copyparty
|
||||||
|
# `- minimal binary, same as sfx-lite/copyparty-sfx.py
|
||||||
|
|
||||||
|
|
||||||
command -v gnutar && tar() { gnutar "$@"; }
|
command -v gnutar && tar() { gnutar "$@"; }
|
||||||
@@ -54,6 +81,7 @@ cache="$od/.copyparty-repack.cache"
|
|||||||
# fallback to awk (sorry)
|
# fallback to awk (sorry)
|
||||||
awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
|
awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
|
||||||
) |
|
) |
|
||||||
|
grep -E '(sfx\.(sh|py)|tar\.gz)$' |
|
||||||
tee /dev/stderr |
|
tee /dev/stderr |
|
||||||
tr -d '\r' | tr '\n' '\0' |
|
tr -d '\r' | tr '\n' '\0' |
|
||||||
xargs -0 bash -c 'dl_files "$@"' _
|
xargs -0 bash -c 'dl_files "$@"' _
|
||||||
@@ -64,7 +92,7 @@ cache="$od/.copyparty-repack.cache"
|
|||||||
|
|
||||||
# move src into copyparty-extras/,
|
# move src into copyparty-extras/,
|
||||||
# move sfx into copyparty-extras/sfx-full/
|
# move sfx into copyparty-extras/sfx-full/
|
||||||
mkdir -p copyparty-extras/sfx-{full,lite}
|
mkdir -p copyparty-extras/sfx-{full,ent,lite}
|
||||||
mv copyparty-sfx.* copyparty-extras/sfx-full/
|
mv copyparty-sfx.* copyparty-extras/sfx-full/
|
||||||
mv copyparty-*.tar.gz copyparty-extras/
|
mv copyparty-*.tar.gz copyparty-extras/
|
||||||
|
|
||||||
@@ -112,14 +140,17 @@ repack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repack sfx-full "re gz no-sh"
|
repack sfx-full "re gz no-sh"
|
||||||
repack sfx-lite "re no-ogv no-cm"
|
repack sfx-ent "re no-dd no-ogv"
|
||||||
repack sfx-lite "re no-ogv no-cm gz no-sh"
|
repack sfx-ent "re no-dd no-ogv gz no-sh"
|
||||||
|
repack sfx-lite "re no-dd no-ogv no-cm no-hl"
|
||||||
|
repack sfx-lite "re no-dd no-ogv no-cm no-hl gz no-sh"
|
||||||
|
|
||||||
|
|
||||||
# move fuse client into copyparty-extras/,
|
# move fuse and up2k clients into copyparty-extras/,
|
||||||
# copy lite-sfx.py to ./copyparty,
|
# copy lite-sfx.py to ./copyparty,
|
||||||
# delete extracted source code
|
# delete extracted source code
|
||||||
( cd copyparty-extras/
|
( cd copyparty-extras/
|
||||||
|
mv copyparty-*/bin/up2k.py .
|
||||||
mv copyparty-*/bin/copyparty-fuse.py .
|
mv copyparty-*/bin/copyparty-fuse.py .
|
||||||
cp -pv sfx-lite/copyparty-sfx.py ../copyparty
|
cp -pv sfx-lite/copyparty-sfx.py ../copyparty
|
||||||
rm -rf copyparty-{0..9}*.*.*{0..9}
|
rm -rf copyparty-{0..9}*.*.*{0..9}
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ RUN mkdir -p /z/dist/no-pk \
|
|||||||
&& tar -xf zopfli.tgz
|
&& tar -xf zopfli.tgz
|
||||||
|
|
||||||
|
|
||||||
|
# todo
|
||||||
|
# https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js
|
||||||
|
# https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/default.min.css
|
||||||
|
# https://prismjs.com/download.html#themes=prism-funky&languages=markup+css+clike+javascript+autohotkey+bash+basic+batch+c+csharp+cpp+cmake+diff+docker+go+ini+java+json+kotlin+latex+less+lisp+lua+makefile+objectivec+perl+powershell+python+r+jsx+ruby+rust+sass+scss+sql+swift+systemd+toml+typescript+vbnet+verilog+vhdl+yaml&plugins=line-highlight+line-numbers+autolinker
|
||||||
|
|
||||||
|
|
||||||
# build fonttools (which needs zopfli)
|
# build fonttools (which needs zopfli)
|
||||||
RUN tar -xf zopfli.tgz \
|
RUN tar -xf zopfli.tgz \
|
||||||
&& cd zopfli* \
|
&& cd zopfli* \
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ help() { exec cat <<'EOF'
|
|||||||
# `no-ogv` saves ~192k by removing the opus/vorbis audio codecs
|
# `no-ogv` saves ~192k by removing the opus/vorbis audio codecs
|
||||||
# (only affects apple devices; everything else has native support)
|
# (only affects apple devices; everything else has native support)
|
||||||
#
|
#
|
||||||
# `no-cm` saves ~92k by removing easymde/codemirror
|
# `no-cm` saves ~82k by removing easymde/codemirror
|
||||||
# (the fancy markdown editor)
|
# (the fancy markdown editor)
|
||||||
#
|
#
|
||||||
|
# `no-hl` saves ~41k by removing syntax hilighting in the text viewer
|
||||||
|
#
|
||||||
# `no-fnt` saves ~9k by removing the source-code-pro font
|
# `no-fnt` saves ~9k by removing the source-code-pro font
|
||||||
# (browsers will try to use 'Consolas' instead)
|
# (browsers will try to use 'Consolas' instead)
|
||||||
#
|
#
|
||||||
@@ -75,6 +77,7 @@ while [ ! -z "$1" ]; do
|
|||||||
gz) use_gz=1 ; ;;
|
gz) use_gz=1 ; ;;
|
||||||
no-ogv) no_ogv=1 ; ;;
|
no-ogv) no_ogv=1 ; ;;
|
||||||
no-fnt) no_fnt=1 ; ;;
|
no-fnt) no_fnt=1 ; ;;
|
||||||
|
no-hl) no_hl=1 ; ;;
|
||||||
no-dd) no_dd=1 ; ;;
|
no-dd) no_dd=1 ; ;;
|
||||||
no-cm) no_cm=1 ; ;;
|
no-cm) no_cm=1 ; ;;
|
||||||
no-sh) do_sh= ; ;;
|
no-sh) do_sh= ; ;;
|
||||||
@@ -226,6 +229,9 @@ rm have
|
|||||||
tmv "$f"
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ $no_hl ] &&
|
||||||
|
rm -rf copyparty/web/deps/prism*
|
||||||
|
|
||||||
[ $no_fnt ] && {
|
[ $no_fnt ] && {
|
||||||
rm -f copyparty/web/deps/scp.woff2
|
rm -f copyparty/web/deps/scp.woff2
|
||||||
f=copyparty/web/ui.css
|
f=copyparty/web/ui.css
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ copyparty/web/deps/ogv-demuxer-ogg-wasm.js,
|
|||||||
copyparty/web/deps/ogv-demuxer-ogg-wasm.wasm,
|
copyparty/web/deps/ogv-demuxer-ogg-wasm.wasm,
|
||||||
copyparty/web/deps/ogv-worker-audio.js,
|
copyparty/web/deps/ogv-worker-audio.js,
|
||||||
copyparty/web/deps/ogv.js,
|
copyparty/web/deps/ogv.js,
|
||||||
|
copyparty/web/deps/prism.js,
|
||||||
|
copyparty/web/deps/prism.css,
|
||||||
|
copyparty/web/deps/prismd.css,
|
||||||
copyparty/web/deps/scp.woff2,
|
copyparty/web/deps/scp.woff2,
|
||||||
copyparty/web/deps/sha512.ac.js,
|
copyparty/web/deps/sha512.ac.js,
|
||||||
copyparty/web/deps/sha512.hw.js,
|
copyparty/web/deps/sha512.hw.js,
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ class Cfg(Namespace):
|
|||||||
mtp=[],
|
mtp=[],
|
||||||
mte="a",
|
mte="a",
|
||||||
mth="",
|
mth="",
|
||||||
|
textfiles="",
|
||||||
hist=None,
|
hist=None,
|
||||||
no_idx=None,
|
no_idx=None,
|
||||||
no_hash=None,
|
no_hash=None,
|
||||||
js_browser=None,
|
js_browser=None,
|
||||||
css_browser=None,
|
css_browser=None,
|
||||||
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr no_acode".split()}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ from copyparty import util
|
|||||||
|
|
||||||
class Cfg(Namespace):
|
class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None):
|
def __init__(self, a=None, v=None, c=None):
|
||||||
ex = {k: False for k in "nw e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode"
|
||||||
|
ex = {k: False for k in ex.split()}
|
||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
"mte": "a",
|
"mte": "a",
|
||||||
@@ -28,8 +29,6 @@ class Cfg(Namespace):
|
|||||||
"js_browser": None,
|
"js_browser": None,
|
||||||
"css_browser": None,
|
"css_browser": None,
|
||||||
"no_voldump": True,
|
"no_voldump": True,
|
||||||
"no_logues": False,
|
|
||||||
"no_readme": False,
|
|
||||||
"re_maxage": 0,
|
"re_maxage": 0,
|
||||||
"rproxy": 0,
|
"rproxy": 0,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ class VSock(object):
|
|||||||
class VHttpSrv(object):
|
class VHttpSrv(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.broker = NullBroker()
|
self.broker = NullBroker()
|
||||||
|
self.prism = None
|
||||||
|
|
||||||
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
||||||
self.j2 = {x: J2_FILES for x in aliases}
|
self.j2 = {x: J2_FILES for x in aliases}
|
||||||
|
|||||||
Reference in New Issue
Block a user