Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db7becacd2 | ||
|
|
28b63e587b | ||
|
|
9cb93ae1ed | ||
|
|
e3e51fb83a | ||
|
|
49c7124776 | ||
|
|
60fb1207fc | ||
|
|
48470f6b50 | ||
|
|
1d308eeb4c | ||
|
|
84f5f41747 | ||
|
|
19189afb34 | ||
|
|
23e77a3389 | ||
|
|
ecced0c4f2 | ||
|
|
d4a8071de5 | ||
|
|
261236e302 | ||
|
|
0de09860f6 | ||
|
|
bfb39969a4 | ||
|
|
256dad8cc0 | ||
|
|
a247ba9ca3 | ||
|
|
0a9a807772 | ||
|
|
41fa6b2552 | ||
|
|
f425ff51ae | ||
|
|
7cde9a2976 | ||
|
|
5dcd88a6c8 | ||
|
|
c3ef3fdc1f | ||
|
|
b9ba783c1c | ||
|
|
d1bca1f52f | ||
|
|
94352f278b | ||
|
|
4fb87ebe32 | ||
|
|
3cbb7243ab | ||
|
|
fff45552da | ||
|
|
95157d02c9 | ||
|
|
3090c74832 | ||
|
|
4195762d2a | ||
|
|
dc3b7a2720 | ||
|
|
ad200f2b97 | ||
|
|
897f9d328d | ||
|
|
efbe34f29d | ||
|
|
dbfc899d79 | ||
|
|
74fb4b0cb8 | ||
|
|
68e7000275 | ||
|
|
38c2dcce3e | ||
|
|
5b3a5fe76b | ||
|
|
d5a9bd80b2 | ||
|
|
71c5565949 | ||
|
|
db33d68d42 | ||
|
|
e1c20c7a18 | ||
|
|
d3f1b45ce3 | ||
|
|
c7aa1a3558 | ||
|
|
7b2bd6da83 | ||
|
|
2bd955ba9f | ||
|
|
98dcaee210 | ||
|
|
361aebf877 | ||
|
|
ffc1610980 | ||
|
|
233075aee7 | ||
|
|
d1a4d335df | ||
|
|
96acbd3593 | ||
|
|
4b876dd133 | ||
|
|
a06c5eb048 | ||
|
|
c9cdc3e1c1 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -34,7 +34,7 @@ remove the ones that are not relevant:
|
|||||||
### Server details (if you're NOT using docker/podman)
|
### Server details (if you're NOT using docker/podman)
|
||||||
remove the ones that are not relevant:
|
remove the ones that are not relevant:
|
||||||
* **server OS / version:**
|
* **server OS / version:**
|
||||||
* **what copyparty did you grab:** (sfx/exe/pip/aur/...)
|
* **what copyparty did you grab:** (sfx/exe/pip/arch/...)
|
||||||
* **how you're running it:** (in a terminal, as a systemd-service, ...)
|
* **how you're running it:** (in a terminal, as a systemd-service, ...)
|
||||||
* run copyparty with `--version` and grab the last 3 lines (they start with `copyparty`, `CPython`, `sqlite`) and paste them below this line:
|
* run copyparty with `--version` and grab the last 3 lines (they start with `copyparty`, `CPython`, `sqlite`) and paste them below this line:
|
||||||
* **copyparty arguments and/or config-file:**
|
* **copyparty arguments and/or config-file:**
|
||||||
|
|||||||
60
README.md
60
README.md
@@ -14,6 +14,8 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
|
|
||||||
🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
|
🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
|
||||||
|
|
||||||
|
made in Norway 🇳🇴
|
||||||
|
|
||||||
|
|
||||||
## readme toc
|
## readme toc
|
||||||
|
|
||||||
@@ -50,6 +52,8 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [rss feeds](#rss-feeds) - monitor a folder with your RSS reader
|
* [rss feeds](#rss-feeds) - monitor a folder with your RSS reader
|
||||||
* [recent uploads](#recent-uploads) - list all recent uploads
|
* [recent uploads](#recent-uploads) - list all recent uploads
|
||||||
* [media player](#media-player) - plays almost every audio format there is
|
* [media player](#media-player) - plays almost every audio format there is
|
||||||
|
* [playlists](#playlists) - create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists
|
||||||
|
* [creating a playlist](#creating-a-playlist) - with a standalone mediaplayer or copyparty
|
||||||
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
||||||
* [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
|
* [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
|
||||||
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
||||||
@@ -102,7 +106,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
|
* [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
|
||||||
* [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
|
* [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
|
||||||
* [packages](#packages) - the party might be closer than you think
|
* [packages](#packages) - the party might be closer than you think
|
||||||
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
* [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
|
||||||
* [fedora package](#fedora-package) - does not exist yet
|
* [fedora package](#fedora-package) - does not exist yet
|
||||||
* [nix package](#nix-package) - `nix profile install github:9001/copyparty`
|
* [nix package](#nix-package) - `nix profile install github:9001/copyparty`
|
||||||
* [nixos module](#nixos-module)
|
* [nixos module](#nixos-module)
|
||||||
@@ -147,6 +151,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
|
|||||||
* or if you are on android, [install copyparty in termux](#install-on-android)
|
* or if you are on android, [install copyparty in termux](#install-on-android)
|
||||||
* or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
|
* or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
|
||||||
* or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
|
* or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
|
||||||
|
* or if your OS is dead, give the [bootable flashdrive / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) a spin
|
||||||
* or if you don't trust copyparty yet and want to isolate it a little, then...
|
* or if you don't trust copyparty yet and want to isolate it a little, then...
|
||||||
* ...maybe [prisonparty](./bin/prisonparty.sh) to create a tiny [chroot](https://wiki.archlinux.org/title/Chroot) (very portable),
|
* ...maybe [prisonparty](./bin/prisonparty.sh) to create a tiny [chroot](https://wiki.archlinux.org/title/Chroot) (very portable),
|
||||||
* ...or [bubbleparty](./bin/bubbleparty.sh) to wrap it in [bubblewrap](https://github.com/containers/bubblewrap) (much better)
|
* ...or [bubbleparty](./bin/bubbleparty.sh) to wrap it in [bubblewrap](https://github.com/containers/bubblewrap) (much better)
|
||||||
@@ -250,6 +255,7 @@ also see [comparison to similar software](./docs/versus.md)
|
|||||||
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
||||||
* ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
|
* ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
|
||||||
* ☑ play video files as audio (converted on server)
|
* ☑ play video files as audio (converted on server)
|
||||||
|
* ☑ create and play [m3u8 playlists](#playlists)
|
||||||
* ☑ image gallery with webm player
|
* ☑ image gallery with webm player
|
||||||
* ☑ textfile browser with syntax hilighting
|
* ☑ textfile browser with syntax hilighting
|
||||||
* ☑ [thumbnails](#thumbnails)
|
* ☑ [thumbnails](#thumbnails)
|
||||||
@@ -302,6 +308,8 @@ project goals / philosophy
|
|||||||
* adaptable, malleable, hackable
|
* adaptable, malleable, hackable
|
||||||
* no build steps; modify the js/python without needing node.js or anything like that
|
* no build steps; modify the js/python without needing node.js or anything like that
|
||||||
|
|
||||||
|
becoming rich is specifically *not* a motivation, but if you wanna donate then see my [github profile](https://github.com/9001) regarding donations for my FOSS stuff in general (also THANKS!)
|
||||||
|
|
||||||
|
|
||||||
## notes
|
## notes
|
||||||
|
|
||||||
@@ -411,6 +419,9 @@ upgrade notes
|
|||||||
|
|
||||||
"frequently" asked questions
|
"frequently" asked questions
|
||||||
|
|
||||||
|
* CopyParty?
|
||||||
|
* nope! the name is either copyparty (all-lowercase) or Copyparty -- it's [one word](https://en.wiktionary.org/wiki/copyparty) after all :>
|
||||||
|
|
||||||
* can I change the 🌲 spinning pine-tree loading animation?
|
* can I change the 🌲 spinning pine-tree loading animation?
|
||||||
* [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
|
* [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
|
||||||
|
|
||||||
@@ -730,6 +741,7 @@ select which type of archive you want in the `[⚙️] config` tab:
|
|||||||
* `up2k.db` and `dir.txt` is always excluded
|
* `up2k.db` and `dir.txt` is always excluded
|
||||||
* bsdtar supports streaming unzipping: `curl foo?zip | bsdtar -xv`
|
* bsdtar supports streaming unzipping: `curl foo?zip | bsdtar -xv`
|
||||||
* good, because copyparty's zip is faster than tar on small files
|
* good, because copyparty's zip is faster than tar on small files
|
||||||
|
* but `?tar` is better for large files, especially if the total exceeds 4 GiB
|
||||||
* `zip_crc` will take longer to download since the server has to read each file twice
|
* `zip_crc` will take longer to download since the server has to read each file twice
|
||||||
* this is only to support MS-DOS PKZIP v2.04g (october 1993) and older
|
* this is only to support MS-DOS PKZIP v2.04g (october 1993) and older
|
||||||
* how are you accessing copyparty actually
|
* how are you accessing copyparty actually
|
||||||
@@ -908,6 +920,7 @@ semi-intentional limitations:
|
|||||||
|
|
||||||
* cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
|
* cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
|
||||||
* only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
|
* only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
|
||||||
|
* if you change [password hashing](#password-hashing) settings after creating a password-protected share, then that share will stop working
|
||||||
* related to [IdP volumes being forgotten on shutdown](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#idp-volumes-are-forgotten-on-shutdown), any shares pointing into a user's IdP volume will be unavailable until that user makes their first request after a restart
|
* related to [IdP volumes being forgotten on shutdown](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#idp-volumes-are-forgotten-on-shutdown), any shares pointing into a user's IdP volume will be unavailable until that user makes their first request after a restart
|
||||||
* no option to "delete after first access" because tricky
|
* no option to "delete after first access" because tricky
|
||||||
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
|
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
|
||||||
@@ -1034,11 +1047,13 @@ click the `play` link next to an audio file, or copy the link target to [share i
|
|||||||
|
|
||||||
open the `[🎺]` media-player-settings tab to configure it,
|
open the `[🎺]` media-player-settings tab to configure it,
|
||||||
* "switches":
|
* "switches":
|
||||||
|
* `[🔁]` repeats one single song forever
|
||||||
* `[🔀]` shuffles the files inside each folder
|
* `[🔀]` shuffles the files inside each folder
|
||||||
* `[preload]` starts loading the next track when it's about to end, reduces the silence between songs
|
* `[preload]` starts loading the next track when it's about to end, reduces the silence between songs
|
||||||
* `[full]` does a full preload by downloading the entire next file; good for unreliable connections, bad for slow connections
|
* `[full]` does a full preload by downloading the entire next file; good for unreliable connections, bad for slow connections
|
||||||
* `[~s]` toggles the seekbar waveform display
|
* `[~s]` toggles the seekbar waveform display
|
||||||
* `[/np]` enables buttons to copy the now-playing info as an irc message
|
* `[/np]` enables buttons to copy the now-playing info as an irc message
|
||||||
|
* `[📻]` enables buttons to create an [m3u playlist](#playlists) with the selected songs
|
||||||
* `[os-ctl]` makes it possible to control audio playback from the lockscreen of your device (enables [mediasession](https://developer.mozilla.org/en-US/docs/Web/API/MediaSession))
|
* `[os-ctl]` makes it possible to control audio playback from the lockscreen of your device (enables [mediasession](https://developer.mozilla.org/en-US/docs/Web/API/MediaSession))
|
||||||
* `[seek]` allows seeking with lockscreen controls (buggy on some devices)
|
* `[seek]` allows seeking with lockscreen controls (buggy on some devices)
|
||||||
* `[art]` shows album art on the lockscreen
|
* `[art]` shows album art on the lockscreen
|
||||||
@@ -1057,11 +1072,39 @@ open the `[🎺]` media-player-settings tab to configure it,
|
|||||||
* "transcode to":
|
* "transcode to":
|
||||||
* `[opus]` produces an `opus` whenever transcoding is necessary (the best choice on Android and PCs)
|
* `[opus]` produces an `opus` whenever transcoding is necessary (the best choice on Android and PCs)
|
||||||
* `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
|
* `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
|
||||||
* `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the mos tpart
|
* `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
|
||||||
* `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
|
* `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
|
||||||
* "tint" reduces the contrast of the playback bar
|
* "tint" reduces the contrast of the playback bar
|
||||||
|
|
||||||
|
|
||||||
|
### playlists
|
||||||
|
|
||||||
|
create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists -- see example [text](https://a.ocv.me/pub/demo/music/?doc=example-playlist.m3u) and [player](https://a.ocv.me/pub/demo/music/#m3u=example-playlist.m3u)
|
||||||
|
|
||||||
|
click a file with the extension `m3u` or `m3u8` (for example `mixtape.m3u` or `touhou.m3u8` ) and you get two choices: Play / Edit
|
||||||
|
|
||||||
|
playlists can include songs across folders anywhere on the server, but filekeys/dirkeys are NOT supported, so the listener must have read-access or get-access to the files
|
||||||
|
|
||||||
|
|
||||||
|
### creating a playlist
|
||||||
|
|
||||||
|
with a standalone mediaplayer or copyparty
|
||||||
|
|
||||||
|
you can use foobar2000, deadbeef, just about any standalone player should work -- but you might need to edit the filepaths in the playlist so they fit with the server-URLs
|
||||||
|
|
||||||
|
alternatively, you can create the playlist using copyparty itself:
|
||||||
|
|
||||||
|
* open the `[🎺]` media-player-settings tab and enable the `[📻]` create-playlist feature -- this adds two new buttons in the bottom-right tray, `[📻add]` and `[📻copy]` which appear when you listen to music, or when you select a few audiofiles
|
||||||
|
|
||||||
|
* click the `📻add` button while a song is playing (or when you've selected some songs) and they'll be added to "the list" (you can't see it yet)
|
||||||
|
|
||||||
|
* at any time, click `📻copy` to send the playlist to your clipboard
|
||||||
|
* you can then continue adding more songs if you'd like
|
||||||
|
* if you want to wipe the playlist and start from scratch, just refresh the page
|
||||||
|
|
||||||
|
* create a new textfile, name it `something.m3u` and paste the playlist there
|
||||||
|
|
||||||
|
|
||||||
### audio equalizer
|
### audio equalizer
|
||||||
|
|
||||||
and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
||||||
@@ -2166,10 +2209,14 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on
|
|||||||
|
|
||||||
## arch package
|
## arch package
|
||||||
|
|
||||||
now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
|
`pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
|
||||||
|
|
||||||
it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/`
|
it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/`
|
||||||
|
|
||||||
|
after installing it, you may want to `cp /usr/lib/systemd/system/copyparty.service /etc/systemd/system/` and then `vim /etc/systemd/system/copyparty.service` to change what user/group it is running as (you only need to do this once)
|
||||||
|
|
||||||
|
NOTE: there used to be an aur package; this evaporated when copyparty was adopted by the official archlinux repos. If you're still using the aur package, please move
|
||||||
|
|
||||||
|
|
||||||
## fedora package
|
## fedora package
|
||||||
|
|
||||||
@@ -2383,6 +2430,8 @@ copyparty returns a truncated sha512sum of your PUT/POST as base64; you can gene
|
|||||||
|
|
||||||
you can provide passwords using header `PW: hunter2`, cookie `cppwd=hunter2`, url-param `?pw=hunter2`, or with basic-authentication (either as the username or password)
|
you can provide passwords using header `PW: hunter2`, cookie `cppwd=hunter2`, url-param `?pw=hunter2`, or with basic-authentication (either as the username or password)
|
||||||
|
|
||||||
|
> for basic-authentication, all of the following are accepted: `password` / `whatever:password` / `password:whatever` (the username is ignored)
|
||||||
|
|
||||||
NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename
|
NOTE: curl will not send the original filename if you use `-T` combined with url-params! Also, make sure to always leave a trailing slash in URLs unless you want to override the filename
|
||||||
|
|
||||||
|
|
||||||
@@ -2471,6 +2520,11 @@ below are some tweaks roughly ordered by usefulness:
|
|||||||
|
|
||||||
when uploading files,
|
when uploading files,
|
||||||
|
|
||||||
|
* when uploading from very fast storage (NVMe SSD) with chrome/firefox, enable `[wasm]` in the `[⚙️] settings` tab to more effectively use all CPU-cores for hashing
|
||||||
|
* don't do this on Safari (runs faster without)
|
||||||
|
* don't do this on older browsers; likely to provoke browser-bugs (browser eats all RAM and crashes)
|
||||||
|
* can be made default-enabled serverside with `--nosubtle 137` (chrome v137+) or `--nosubtle 2` (chrome+firefox)
|
||||||
|
|
||||||
* chrome is recommended (unfortunately), at least compared to firefox:
|
* chrome is recommended (unfortunately), at least compared to firefox:
|
||||||
* up to 90% faster when hashing, especially on SSDs
|
* up to 90% faster when hashing, especially on SSDs
|
||||||
* up to 40% faster when uploading over extremely fast internets
|
* up to 40% faster when uploading over extremely fast internets
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ run copyparty with `--help-hooks` for usage details / hook type explanations (xm
|
|||||||
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
* [discord-announce.py](discord-announce.py) announces new uploads on discord using webhooks ([example](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png))
|
||||||
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
* [reject-mimetype.py](reject-mimetype.py) rejects uploads unless the mimetype is acceptable
|
||||||
* [into-the-cache-it-goes.py](into-the-cache-it-goes.py) avoids bugs in caching proxies by immediately downloading each file that is uploaded
|
* [into-the-cache-it-goes.py](into-the-cache-it-goes.py) avoids bugs in caching proxies by immediately downloading each file that is uploaded
|
||||||
|
* [podcast-normalizer.py](podcast-normalizer.py) creates a second file with dynamic-range-compression whenever an audio file is uploaded
|
||||||
|
* good example of the `idx` [hook effect](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#hook-effects) to tell copyparty about additional files to scan/index
|
||||||
|
|
||||||
|
|
||||||
# upload batches
|
# upload batches
|
||||||
@@ -25,6 +27,7 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
|
|||||||
# before upload
|
# before upload
|
||||||
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
||||||
* [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension
|
* [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension
|
||||||
|
* good example of the `reloc` [hook effect](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#hook-effects)
|
||||||
|
|
||||||
|
|
||||||
# on message
|
# on message
|
||||||
|
|||||||
121
bin/hooks/podcast-normalizer.py
Executable file
121
bin/hooks/podcast-normalizer.py
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
sends all uploaded audio files through an aggressive
|
||||||
|
dynamic-range-compressor to even out the volume levels
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
ffmpeg
|
||||||
|
|
||||||
|
being an xau hook, this gets eXecuted After Upload completion
|
||||||
|
but before copyparty has started hashing/indexing the file, so
|
||||||
|
we'll create a second normalized copy in a subfolder and tell
|
||||||
|
copyparty to hash/index that additional file as well
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
-e2d -e2t --xau j,c1,bin/hooks/podcast-normalizer.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
e2d/e2t = enable database and metadata indexing
|
||||||
|
xau = execute after upload
|
||||||
|
j = this hook needs upload information as json (not just the filename)
|
||||||
|
c1 = this hook returns json on stdout, so tell copyparty to read that
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc/pods:inc/pods:r:rw,ed:c,xau=j,c1,bin/hooks/podcast-normalizer.py
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
(share fs-path srv/inc/pods at URL /inc/pods,
|
||||||
|
readable by all, read-write for user ed,
|
||||||
|
running this xau (exec-after-upload) plugin for all uploaded files)
|
||||||
|
|
||||||
|
example usage as a volflag in a copyparty config file:
|
||||||
|
[/inc/pods]
|
||||||
|
srv/inc/pods
|
||||||
|
accs:
|
||||||
|
r: *
|
||||||
|
rw: ed
|
||||||
|
flags:
|
||||||
|
e2d # enables file indexing
|
||||||
|
e2t # metadata tags too
|
||||||
|
xau: j,c1,bin/hooks/podcast-normalizer.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
### CONFIG
|
||||||
|
|
||||||
|
# filetypes to process; ignores everything else
|
||||||
|
EXTS = "mp3 flac ogg oga opus m4a aac wav wma"
|
||||||
|
|
||||||
|
# the name of the subdir to put the normalized files in
|
||||||
|
SUBDIR = "normalized"
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# try to enable support for crazy filenames
|
||||||
|
try:
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
except:
|
||||||
|
|
||||||
|
def fsenc(p):
|
||||||
|
return p.encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# read info from copyparty
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
vpath = inf["vp"]
|
||||||
|
abspath = inf["ap"]
|
||||||
|
|
||||||
|
# check if the file-extension is on the to-be-processed list
|
||||||
|
ext = abspath.lower().split(".")[-1]
|
||||||
|
if ext not in EXTS.split():
|
||||||
|
return
|
||||||
|
|
||||||
|
# jump into the folder where the file was uploaded
|
||||||
|
# and create the subfolder to place the normalized copy inside
|
||||||
|
dirpath, filename = os.path.split(abspath)
|
||||||
|
os.chdir(fsenc(dirpath))
|
||||||
|
os.makedirs(SUBDIR, exist_ok=True)
|
||||||
|
|
||||||
|
# the input and output filenames to give ffmpeg
|
||||||
|
fname_in = fsenc(f"./{filename}")
|
||||||
|
fname_out = fsenc(f"{SUBDIR}/{filename}.opus")
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
# create and run the ffmpeg command
|
||||||
|
cmd = [
|
||||||
|
b"ffmpeg",
|
||||||
|
b"-nostdin",
|
||||||
|
b"-hide_banner",
|
||||||
|
b"-i", fname_in,
|
||||||
|
b"-af", b"dynaudnorm=f=100:g=9", # the normalizer config
|
||||||
|
b"-c:a", b"libopus",
|
||||||
|
b"-b:a", b"128k",
|
||||||
|
fname_out,
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
sp.check_output(cmd)
|
||||||
|
|
||||||
|
# and finally, tell copyparty about the new file
|
||||||
|
# so it appears in the database and rss-feed:
|
||||||
|
vpath = f"{SUBDIR}/{filename}.opus"
|
||||||
|
print(json.dumps({"idx": {"vp": [vpath]}}))
|
||||||
|
|
||||||
|
# (it's fine to give it a relative path like that; it gets
|
||||||
|
# resolved relative to the folder the file was uploaded into)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as ex:
|
||||||
|
print("podcast-normalizer failed; %r" % (ex,))
|
||||||
@@ -71,6 +71,9 @@ def main():
|
|||||||
## selecting it inside the print at the end:
|
## selecting it inside the print at the end:
|
||||||
##
|
##
|
||||||
|
|
||||||
|
# move all uploads to one specific folder
|
||||||
|
into_junk = {"vp": "/junk"}
|
||||||
|
|
||||||
# create a subfolder named after the filetype and move it into there
|
# create a subfolder named after the filetype and move it into there
|
||||||
into_subfolder = {"vp": ext}
|
into_subfolder = {"vp": ext}
|
||||||
|
|
||||||
@@ -92,8 +95,8 @@ def main():
|
|||||||
by_category = {} # no action
|
by_category = {} # no action
|
||||||
|
|
||||||
# now choose the default effect to apply; can be any of these:
|
# now choose the default effect to apply; can be any of these:
|
||||||
# into_subfolder into_toplevel into_sibling by_category
|
# into_junk into_subfolder into_toplevel into_sibling by_category
|
||||||
effect = {"vp": "/junk"}
|
effect = into_sibling
|
||||||
|
|
||||||
##
|
##
|
||||||
## but we can keep going, adding more speicifc rules
|
## but we can keep going, adding more speicifc rules
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
S_VERSION = "2.10"
|
S_VERSION = "2.11"
|
||||||
S_BUILD_DT = "2025-02-19"
|
S_BUILD_DT = "2025-05-18"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
u2c.py: upload to copyparty
|
u2c.py: upload to copyparty
|
||||||
@@ -1289,7 +1289,7 @@ class Ctl(object):
|
|||||||
if self.ar.jw:
|
if self.ar.jw:
|
||||||
print("%s %s" % (wark, vp))
|
print("%s %s" % (wark, vp))
|
||||||
else:
|
else:
|
||||||
zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
|
zd = datetime.datetime.fromtimestamp(max(0, file.lmod), UTC)
|
||||||
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
||||||
zd.year,
|
zd.year,
|
||||||
zd.month,
|
zd.month,
|
||||||
|
|||||||
@@ -2,19 +2,38 @@
|
|||||||
# not accept more consecutive clients than what copyparty is able to;
|
# not accept more consecutive clients than what copyparty is able to;
|
||||||
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
||||||
#
|
#
|
||||||
|
# ======================================================================
|
||||||
|
#
|
||||||
|
# to reverse-proxy a specific path/subpath/location below a domain
|
||||||
|
# (rather than a complete subdomain), for example "/qw/er", you must
|
||||||
|
# run copyparty with --rp-loc /qw/as and also change the following:
|
||||||
|
# location / {
|
||||||
|
# proxy_pass http://cpp_tcp;
|
||||||
|
# to this:
|
||||||
|
# location /qw/er/ {
|
||||||
|
# proxy_pass http://cpp_tcp/qw/er/;
|
||||||
|
#
|
||||||
|
# ======================================================================
|
||||||
|
#
|
||||||
# rarely, in some extreme usecases, it can be good to add -j0
|
# rarely, in some extreme usecases, it can be good to add -j0
|
||||||
# (40'000 requests per second, or 20gbps upload/download in parallel)
|
# (40'000 requests per second, or 20gbps upload/download in parallel)
|
||||||
# but this is usually counterproductive and slightly buggy
|
# but this is usually counterproductive and slightly buggy
|
||||||
#
|
#
|
||||||
|
# ======================================================================
|
||||||
|
#
|
||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
#
|
#
|
||||||
# if you are behind cloudflare (or another protection service),
|
# ======================================================================
|
||||||
|
#
|
||||||
|
# if you are behind cloudflare (or another CDN/WAF/protection service),
|
||||||
# remember to reject all connections which are not coming from your
|
# remember to reject all connections which are not coming from your
|
||||||
# protection service -- for cloudflare in particular, you can
|
# protection service -- for cloudflare in particular, you can
|
||||||
# generate the list of permitted IP ranges like so:
|
# generate the list of permitted IP ranges like so:
|
||||||
# (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf
|
# (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf
|
||||||
#
|
#
|
||||||
# and then enable it below by uncomenting the cloudflare-only.conf line
|
# and then enable it below by uncomenting the cloudflare-only.conf line
|
||||||
|
#
|
||||||
|
# ======================================================================
|
||||||
|
|
||||||
|
|
||||||
upstream cpp_tcp {
|
upstream cpp_tcp {
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
with lib;
|
pkgs,
|
||||||
|
lib,
|
||||||
let
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
mkKeyValue = key: value:
|
mkKeyValue = key: value:
|
||||||
if value == true then
|
if value == true
|
||||||
# sets with a true boolean value are coerced to just the key name
|
then
|
||||||
|
# sets with a true boolean value are coerced to just the key name
|
||||||
key
|
key
|
||||||
else if value == false then
|
else if value == false
|
||||||
# or omitted completely when false
|
then
|
||||||
|
# or omitted completely when false
|
||||||
""
|
""
|
||||||
else
|
else (generators.mkKeyValueDefault {inherit mkValueString;} ": " key value);
|
||||||
(generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);
|
|
||||||
|
|
||||||
mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value);
|
mkAttrsString = value: (generators.toKeyValue {inherit mkKeyValue;} value);
|
||||||
|
|
||||||
mkValueString = value:
|
mkValueString = value:
|
||||||
if isList value then
|
if isList value
|
||||||
(concatStringsSep ", " (map mkValueString value))
|
then (concatStringsSep ", " (map mkValueString value))
|
||||||
else if isAttrs value then
|
else if isAttrs value
|
||||||
"\n" + (mkAttrsString value)
|
then "\n" + (mkAttrsString value)
|
||||||
else
|
else (generators.mkValueStringDefault {} value);
|
||||||
(generators.mkValueStringDefault { } value);
|
|
||||||
|
|
||||||
mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]";
|
mkSectionName = value: "[" + (escape ["[" "]"] value) + "]";
|
||||||
|
|
||||||
mkSection = name: attrs: ''
|
mkSection = name: attrs: ''
|
||||||
${mkSectionName name}
|
${mkSectionName name}
|
||||||
@@ -49,12 +51,12 @@ let
|
|||||||
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
name = "copyparty";
|
|
||||||
cfg = config.services.copyparty;
|
cfg = config.services.copyparty;
|
||||||
configFile = pkgs.writeText "${name}.conf" configStr;
|
configFile = pkgs.writeText "copyparty.conf" configStr;
|
||||||
runtimeConfigPath = "/run/${name}/${name}.conf";
|
runtimeConfigPath = "/run/copyparty/copyparty.conf";
|
||||||
home = "/var/lib/${name}";
|
externalCacheDir = "/var/cache/copyparty";
|
||||||
defaultShareDir = "${home}/data";
|
externalStateDir = "/var/lib/copyparty";
|
||||||
|
defaultShareDir = "${externalStateDir}/data";
|
||||||
in {
|
in {
|
||||||
options.services.copyparty = {
|
options.services.copyparty = {
|
||||||
enable = mkEnableOption "web-based file manager";
|
enable = mkEnableOption "web-based file manager";
|
||||||
@@ -68,6 +70,35 @@ in {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mkHashWrapper = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Make a shell script wrapper called 'copyparty-hash' with all options set here,
|
||||||
|
that launches the hashing cli.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "copyparty";
|
||||||
|
description = ''
|
||||||
|
The user that copyparty will run under.
|
||||||
|
|
||||||
|
If changed from default, you are responsible for making sure the user exists.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "copyparty";
|
||||||
|
description = ''
|
||||||
|
The group that copyparty will run under.
|
||||||
|
|
||||||
|
If changed from default, you are responsible for making sure the user exists.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
openFilesLimit = mkOption {
|
openFilesLimit = mkOption {
|
||||||
default = 4096;
|
default = 4096;
|
||||||
type = types.either types.int types.str;
|
type = types.either types.int types.str;
|
||||||
@@ -79,22 +110,25 @@ in {
|
|||||||
description = ''
|
description = ''
|
||||||
Global settings to apply.
|
Global settings to apply.
|
||||||
Directly maps to values in the [global] section of the copyparty config.
|
Directly maps to values in the [global] section of the copyparty config.
|
||||||
|
Cannot set "c" or "hist", those are set by this module.
|
||||||
See `${getExe cfg.package} --help` for more details.
|
See `${getExe cfg.package} --help` for more details.
|
||||||
'';
|
'';
|
||||||
default = {
|
default = {
|
||||||
i = "127.0.0.1";
|
i = "127.0.0.1";
|
||||||
no-reload = true;
|
no-reload = true;
|
||||||
|
hist = externalCacheDir;
|
||||||
};
|
};
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
{
|
{
|
||||||
i = "0.0.0.0";
|
i = "0.0.0.0";
|
||||||
no-reload = true;
|
no-reload = true;
|
||||||
|
hist = ${externalCacheDir};
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
accounts = mkOption {
|
accounts = mkOption {
|
||||||
type = types.attrsOf (types.submodule ({ ... }: {
|
type = types.attrsOf (types.submodule ({...}: {
|
||||||
options = {
|
options = {
|
||||||
passwordFile = mkOption {
|
passwordFile = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
@@ -109,7 +143,7 @@ in {
|
|||||||
description = ''
|
description = ''
|
||||||
A set of copyparty accounts to create.
|
A set of copyparty accounts to create.
|
||||||
'';
|
'';
|
||||||
default = { };
|
default = {};
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
{
|
{
|
||||||
ed.passwordFile = "/run/keys/copyparty/ed";
|
ed.passwordFile = "/run/keys/copyparty/ed";
|
||||||
@@ -118,10 +152,10 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
volumes = mkOption {
|
volumes = mkOption {
|
||||||
type = types.attrsOf (types.submodule ({ ... }: {
|
type = types.attrsOf (types.submodule ({...}: {
|
||||||
options = {
|
options = {
|
||||||
path = mkOption {
|
path = mkOption {
|
||||||
type = types.str;
|
type = types.path;
|
||||||
description = ''
|
description = ''
|
||||||
Path of a directory to share.
|
Path of a directory to share.
|
||||||
'';
|
'';
|
||||||
@@ -177,7 +211,7 @@ in {
|
|||||||
nohash = "\.iso$";
|
nohash = "\.iso$";
|
||||||
};
|
};
|
||||||
'';
|
'';
|
||||||
default = { };
|
default = {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
@@ -185,7 +219,7 @@ in {
|
|||||||
default = {
|
default = {
|
||||||
"/" = {
|
"/" = {
|
||||||
path = defaultShareDir;
|
path = defaultShareDir;
|
||||||
access = { r = "*"; };
|
access = {r = "*";};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
example = literalExpression ''
|
example = literalExpression ''
|
||||||
@@ -204,52 +238,65 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable (let
|
||||||
|
command = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
||||||
|
in {
|
||||||
systemd.services.copyparty = {
|
systemd.services.copyparty = {
|
||||||
description = "http file sharing hub";
|
description = "http file sharing hub";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = ["multi-user.target"];
|
||||||
|
|
||||||
environment = {
|
environment = {
|
||||||
PYTHONUNBUFFERED = "true";
|
PYTHONUNBUFFERED = "true";
|
||||||
XDG_CONFIG_HOME = "${home}/.config";
|
XDG_CONFIG_HOME = externalStateDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
preStart = let
|
preStart = let
|
||||||
replaceSecretCommand = name: attrs:
|
replaceSecretCommand = name: attrs: "${getExe pkgs.replace-secret} '${
|
||||||
"${getExe pkgs.replace-secret} '${
|
passwordPlaceholder name
|
||||||
passwordPlaceholder name
|
}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
||||||
}' '${attrs.passwordFile}' ${runtimeConfigPath}";
|
|
||||||
in ''
|
in ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
install -m 600 ${configFile} ${runtimeConfigPath}
|
install -m 600 ${configFile} ${runtimeConfigPath}
|
||||||
${concatStringsSep "\n"
|
${concatStringsSep "\n"
|
||||||
(mapAttrsToList replaceSecretCommand cfg.accounts)}
|
(mapAttrsToList replaceSecretCommand cfg.accounts)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
ExecStart = "${getExe cfg.package} -c ${runtimeConfigPath}";
|
ExecStart = command;
|
||||||
|
|
||||||
# Hardening options
|
# Hardening options
|
||||||
User = "copyparty";
|
User = cfg.user;
|
||||||
Group = "copyparty";
|
Group = cfg.group;
|
||||||
RuntimeDirectory = name;
|
RuntimeDirectory = ["copyparty"];
|
||||||
RuntimeDirectoryMode = "0700";
|
RuntimeDirectoryMode = "0700";
|
||||||
StateDirectory = [ name "${name}/data" "${name}/.config" ];
|
StateDirectory = ["copyparty"];
|
||||||
StateDirectoryMode = "0700";
|
StateDirectoryMode = "0700";
|
||||||
WorkingDirectory = home;
|
CacheDirectory = lib.mkIf (cfg.settings ? hist) ["copyparty"];
|
||||||
|
CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700";
|
||||||
|
WorkingDirectory = externalStateDir;
|
||||||
|
BindReadOnlyPaths =
|
||||||
|
[
|
||||||
|
"/nix/store"
|
||||||
|
"-/etc/resolv.conf"
|
||||||
|
"-/etc/nsswitch.conf"
|
||||||
|
"-/etc/hosts"
|
||||||
|
"-/etc/localtime"
|
||||||
|
]
|
||||||
|
++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
||||||
|
BindPaths =
|
||||||
|
(
|
||||||
|
if cfg.settings ? hist
|
||||||
|
then [cfg.settings.hist]
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
++ [externalStateDir]
|
||||||
|
++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
||||||
|
# ProtectSystem = "strict";
|
||||||
|
# Note that unlike what 'ro' implies,
|
||||||
|
# this actually makes it impossible to read anything in the root FS,
|
||||||
|
# except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`.
|
||||||
|
# This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible.
|
||||||
TemporaryFileSystem = "/:ro";
|
TemporaryFileSystem = "/:ro";
|
||||||
BindReadOnlyPaths = [
|
|
||||||
"/nix/store"
|
|
||||||
"-/etc/resolv.conf"
|
|
||||||
"-/etc/nsswitch.conf"
|
|
||||||
"-/etc/hosts"
|
|
||||||
"-/etc/localtime"
|
|
||||||
] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
|
|
||||||
BindPaths = [ home ] ++ (mapAttrsToList (k: v: v.path) cfg.volumes);
|
|
||||||
# Would re-mount paths ignored by temporary root
|
|
||||||
#ProtectSystem = "strict";
|
|
||||||
ProtectHome = true;
|
|
||||||
PrivateTmp = true;
|
PrivateTmp = true;
|
||||||
PrivateDevices = true;
|
PrivateDevices = true;
|
||||||
ProtectKernelTunables = true;
|
ProtectKernelTunables = true;
|
||||||
@@ -269,15 +316,48 @@ in {
|
|||||||
NoNewPrivileges = true;
|
NoNewPrivileges = true;
|
||||||
LockPersonality = true;
|
LockPersonality = true;
|
||||||
RestrictRealtime = true;
|
RestrictRealtime = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.copyparty = { };
|
# ensure volumes exist:
|
||||||
users.users.copyparty = {
|
systemd.tmpfiles.settings."copyparty" = (
|
||||||
|
lib.attrsets.mapAttrs' (
|
||||||
|
name: value:
|
||||||
|
lib.attrsets.nameValuePair (value.path) {
|
||||||
|
d = {
|
||||||
|
#: in front of things means it wont change it if the directory already exists.
|
||||||
|
group = ":${cfg.group}";
|
||||||
|
user = ":${cfg.user}";
|
||||||
|
mode = ":755";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cfg.volumes
|
||||||
|
);
|
||||||
|
|
||||||
|
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {};
|
||||||
|
users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {
|
||||||
description = "Service user for copyparty";
|
description = "Service user for copyparty";
|
||||||
group = "copyparty";
|
group = "copyparty";
|
||||||
home = home;
|
home = externalStateDir;
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
};
|
};
|
||||||
};
|
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
|
||||||
|
(pkgs.writeShellScriptBin
|
||||||
|
"copyparty-hash"
|
||||||
|
''
|
||||||
|
set -a # automatically export variables
|
||||||
|
# set same environment variables as the systemd service
|
||||||
|
${lib.pipe config.systemd.services.copyparty.environment [
|
||||||
|
(lib.filterAttrs (n: v: v != null && n != "PATH"))
|
||||||
|
(lib.mapAttrs (_: v: "${v}"))
|
||||||
|
(lib.toShellVars)
|
||||||
|
]}
|
||||||
|
PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH
|
||||||
|
|
||||||
|
exec ${command} --ah-cli
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.16.18"
|
pkgver="1.17.1"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
@@ -22,7 +22,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("7f16b08a1393523c39a758649dbab23646e7b35d2c3f5dc7c50c63bdc73e4d20")
|
sha256sums=("2ecbe68dea1de8612fa8beee42e07c62dc1f7c0068e527a8d63961a47e376207")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ lib, stdenv, makeWrapper, fetchurl, utillinux, python, jinja2, impacket, pyftpdlib, pyopenssl, argon2-cffi, pillow, pyvips, pyzmq, ffmpeg, mutagen,
|
{ lib, stdenv, makeWrapper, fetchurl, util-linux, python, jinja2, impacket, pyftpdlib, pyopenssl, argon2-cffi, pillow, pyvips, pyzmq, ffmpeg, mutagen,
|
||||||
|
|
||||||
# use argon2id-hashed passwords in config files (sha2 is always available)
|
# use argon2id-hashed passwords in config files (sha2 is always available)
|
||||||
withHashedPasswords ? true,
|
withHashedPasswords ? true,
|
||||||
@@ -61,7 +61,7 @@ in stdenv.mkDerivation {
|
|||||||
installPhase = ''
|
installPhase = ''
|
||||||
install -Dm755 $src $out/share/copyparty-sfx.py
|
install -Dm755 $src $out/share/copyparty-sfx.py
|
||||||
makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \
|
makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \
|
||||||
--set PATH '${lib.makeBinPath ([ utillinux ] ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \
|
--set PATH '${lib.makeBinPath ([ util-linux ] ++ lib.optional withMediaProcessing ffmpeg)}:$PATH' \
|
||||||
--add-flags "$out/share/copyparty-sfx.py"
|
--add-flags "$out/share/copyparty-sfx.py"
|
||||||
'';
|
'';
|
||||||
meta.mainProgram = "copyparty";
|
meta.mainProgram = "copyparty";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.16.18/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.17.1/copyparty-sfx.py",
|
||||||
"version": "1.16.18",
|
"version": "1.17.1",
|
||||||
"hash": "sha256-5O1wBeKEh3pIlSAkrmxVaMOcVcGDE0llzINBfR9+Dz0="
|
"hash": "sha256-dvfsqPqnT1HNbxG/sqDqz+6PgAKB6+LVKx2sJ2zKYZc="
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,23 @@ almost the same as minimal-up2k.html except this one...:
|
|||||||
|
|
||||||
-- looks slightly better
|
-- looks slightly better
|
||||||
|
|
||||||
|
|
||||||
|
========================
|
||||||
|
== USAGE INSTRUCTIONS ==
|
||||||
|
|
||||||
|
1. create a volume which anyone can read from (if you haven't already)
|
||||||
|
2. copy this file into that volume, so anyone can download it
|
||||||
|
3. enable the plugin by telling the webbrowser to load this file;
|
||||||
|
assuming the URL to the public volume is /res/, and
|
||||||
|
assuming you're using config-files, then add this to your config:
|
||||||
|
|
||||||
|
[global]
|
||||||
|
js-browser: /res/minimal-up2k.js
|
||||||
|
|
||||||
|
alternatively, if you're not using config-files, then
|
||||||
|
add the following commandline argument instead:
|
||||||
|
--js-browser=/res/minimal-up2k.js
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var u2min = `
|
var u2min = `
|
||||||
|
|||||||
@@ -1003,6 +1003,9 @@ def add_upload(ap):
|
|||||||
ap2 = ap.add_argument_group('upload options')
|
ap2 = ap.add_argument_group('upload options')
|
||||||
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
|
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
|
||||||
ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
|
ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
|
||||||
|
ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)")
|
||||||
|
ap2.add_argument("--put-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck)")
|
||||||
|
ap2.add_argument("--bup-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck)")
|
||||||
ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
|
ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
|
||||||
ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)")
|
ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)")
|
||||||
ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
|
ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
|
||||||
@@ -1025,7 +1028,8 @@ def add_upload(ap):
|
|||||||
ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
|
ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
|
||||||
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
|
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
|
||||||
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
|
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
|
||||||
ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
|
ap2.add_argument("--nosubtle", metavar="N", type=int, default=0, help="when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [\033[32m0\033[0m] = only when necessary (non-https), [\033[32m1\033[0m] = always (all browsers), [\033[32m2\033[0m] = always on chrome/firefox, [\033[32m3\033[0m] = always on chrome, [\033[32mN\033[0m] = chrome-version N and newer (recommendation: 137)")
|
||||||
|
ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed")
|
||||||
ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
|
ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
|
||||||
ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
|
ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
|
||||||
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
|
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
|
||||||
@@ -1308,6 +1312,9 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt):
|
|||||||
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
|
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
|
||||||
ap2.add_argument("--dk-salt", metavar="SALT", type=u, default=dk_salt, help="per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission")
|
ap2.add_argument("--dk-salt", metavar="SALT", type=u, default=dk_salt, help="per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission")
|
||||||
ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
|
ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
|
||||||
|
ap2.add_argument("--show-ah-salt", action="store_true", help="on startup, print the effective value of \033[33m--ah-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)")
|
||||||
|
ap2.add_argument("--show-fk-salt", action="store_true", help="on startup, print the effective value of \033[33m--fk-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)")
|
||||||
|
ap2.add_argument("--show-dk-salt", action="store_true", help="on startup, print the effective value of \033[33m--dk-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)")
|
||||||
|
|
||||||
|
|
||||||
def add_shutdown(ap):
|
def add_shutdown(ap):
|
||||||
@@ -1349,7 +1356,7 @@ def add_admin(ap):
|
|||||||
|
|
||||||
def add_thumbnail(ap):
|
def add_thumbnail(ap):
|
||||||
th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6
|
th_ram = (RAM_AVAIL or RAM_TOTAL or 9) * 0.6
|
||||||
th_ram = int(max(min(th_ram, 6), 1) * 10) / 10
|
th_ram = int(max(min(th_ram, 6), 0.3) * 10) / 10
|
||||||
ap2 = ap.add_argument_group('thumbnail options')
|
ap2 = ap.add_argument_group('thumbnail options')
|
||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
||||||
@@ -1376,7 +1383,8 @@ def add_thumbnail(ap):
|
|||||||
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
|
||||||
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
||||||
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
|
||||||
|
ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
|
||||||
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
|
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 16, 19)
|
VERSION = (1, 17, 2)
|
||||||
CODENAME = "COPYparty"
|
CODENAME = "mixtape.m3u"
|
||||||
BUILD_DT = (2025, 4, 8)
|
BUILD_DT = (2025, 5, 27)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -357,7 +357,6 @@ class VFS(object):
|
|||||||
self.flags = flags # config options
|
self.flags = flags # config options
|
||||||
self.root = self
|
self.root = self
|
||||||
self.dev = 0 # st_dev
|
self.dev = 0 # st_dev
|
||||||
self.badcfg1 = False
|
|
||||||
self.nodes: dict[str, VFS] = {} # child nodes
|
self.nodes: dict[str, VFS] = {} # child nodes
|
||||||
self.histtab: dict[str, str] = {} # all realpath->histpath
|
self.histtab: dict[str, str] = {} # all realpath->histpath
|
||||||
self.dbpaths: dict[str, str] = {} # all realpath->dbpath
|
self.dbpaths: dict[str, str] = {} # all realpath->dbpath
|
||||||
@@ -877,6 +876,7 @@ class AuthSrv(object):
|
|||||||
self.warn_anonwrite = warn_anonwrite
|
self.warn_anonwrite = warn_anonwrite
|
||||||
self.line_ctr = 0
|
self.line_ctr = 0
|
||||||
self.indent = ""
|
self.indent = ""
|
||||||
|
self.is_lxc = args.c == ["/z/initcfg"]
|
||||||
|
|
||||||
# fwd-decl
|
# fwd-decl
|
||||||
self.vfs = VFS(log_func, "", "", "", AXS(), {})
|
self.vfs = VFS(log_func, "", "", "", AXS(), {})
|
||||||
@@ -887,6 +887,8 @@ class AuthSrv(object):
|
|||||||
self.defpw: dict[str, str] = {}
|
self.defpw: dict[str, str] = {}
|
||||||
self.grps: dict[str, list[str]] = {}
|
self.grps: dict[str, list[str]] = {}
|
||||||
self.re_pwd: Optional[re.Pattern] = None
|
self.re_pwd: Optional[re.Pattern] = None
|
||||||
|
self.cfg_files_loaded: list[str] = []
|
||||||
|
self.badcfg1 = False
|
||||||
|
|
||||||
# all volumes observed since last restart
|
# all volumes observed since last restart
|
||||||
self.idp_vols: dict[str, str] = {} # vpath->abspath
|
self.idp_vols: dict[str, str] = {} # vpath->abspath
|
||||||
@@ -1482,8 +1484,10 @@ class AuthSrv(object):
|
|||||||
daxs: dict[str, AXS] = {}
|
daxs: dict[str, AXS] = {}
|
||||||
mflags: dict[str, dict[str, Any]] = {} # vpath:flags
|
mflags: dict[str, dict[str, Any]] = {} # vpath:flags
|
||||||
mount: dict[str, tuple[str, str]] = {} # dst:src (vp:(ap,vp0))
|
mount: dict[str, tuple[str, str]] = {} # dst:src (vp:(ap,vp0))
|
||||||
|
cfg_files_loaded: list[str] = []
|
||||||
|
|
||||||
self.idp_vols = {} # yolo
|
self.idp_vols = {} # yolo
|
||||||
|
self.badcfg1 = False
|
||||||
|
|
||||||
if self.args.a:
|
if self.args.a:
|
||||||
# list of username:password
|
# list of username:password
|
||||||
@@ -1544,6 +1548,7 @@ class AuthSrv(object):
|
|||||||
zst = [(max(0, len(x) - 2) * " ") + "└" + x[-1] for x in zstt]
|
zst = [(max(0, len(x) - 2) * " ") + "└" + x[-1] for x in zstt]
|
||||||
t = "loaded {} config files:\n{}"
|
t = "loaded {} config files:\n{}"
|
||||||
self.log(t.format(len(zst), "\n".join(zst)))
|
self.log(t.format(len(zst), "\n".join(zst)))
|
||||||
|
cfg_files_loaded = zst
|
||||||
|
|
||||||
except:
|
except:
|
||||||
lns = lns[: self.line_ctr]
|
lns = lns[: self.line_ctr]
|
||||||
@@ -1568,9 +1573,14 @@ class AuthSrv(object):
|
|||||||
if not mount and not self.args.idp_h_usr:
|
if not mount and not self.args.idp_h_usr:
|
||||||
# -h says our defaults are CWD at root and read/write for everyone
|
# -h says our defaults are CWD at root and read/write for everyone
|
||||||
axs = AXS(["*"], ["*"], None, None)
|
axs = AXS(["*"], ["*"], None, None)
|
||||||
if os.path.exists("/z/initcfg"):
|
if self.is_lxc:
|
||||||
t = "Read-access has been disabled due to failsafe: Docker detected, but the config does not define any volumes. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
|
t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
|
||||||
self.log(t, 1)
|
if len(cfg_files_loaded) == 1:
|
||||||
|
self.log(t % ("no config-file was provided",), 1)
|
||||||
|
t = "it is strongly recommended to add a config-file instead, for example based on https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/basic-docker-compose/copyparty.conf"
|
||||||
|
self.log(t, 3)
|
||||||
|
else:
|
||||||
|
self.log(t % ("the config does not define any volumes",), 1)
|
||||||
axs = AXS()
|
axs = AXS()
|
||||||
elif self.args.c:
|
elif self.args.c:
|
||||||
t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
|
t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
|
||||||
@@ -1578,7 +1588,7 @@ class AuthSrv(object):
|
|||||||
axs = AXS()
|
axs = AXS()
|
||||||
vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
|
vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
|
||||||
if not axs.uread:
|
if not axs.uread:
|
||||||
vfs.badcfg1 = True
|
self.badcfg1 = True
|
||||||
elif "" not in mount:
|
elif "" not in mount:
|
||||||
# there's volumes but no root; make root inaccessible
|
# there's volumes but no root; make root inaccessible
|
||||||
zsd = {"d2d": True, "tcolor": self.args.tcolor}
|
zsd = {"d2d": True, "tcolor": self.args.tcolor}
|
||||||
@@ -2074,6 +2084,10 @@ class AuthSrv(object):
|
|||||||
if len(zs) == 3: # fc5 => ffcc55
|
if len(zs) == 3: # fc5 => ffcc55
|
||||||
vol.flags["tcolor"] = "".join([x * 2 for x in zs])
|
vol.flags["tcolor"] = "".join([x * 2 for x in zs])
|
||||||
|
|
||||||
|
# volflag syntax currently doesn't allow for ':' in value
|
||||||
|
zs = vol.flags["put_name"]
|
||||||
|
vol.flags["put_name2"] = zs.replace("{now.", "{now:.")
|
||||||
|
|
||||||
if vol.flags.get("neversymlink"):
|
if vol.flags.get("neversymlink"):
|
||||||
vol.flags["hardlinkonly"] = True # was renamed
|
vol.flags["hardlinkonly"] = True # was renamed
|
||||||
if vol.flags.get("hardlinkonly"):
|
if vol.flags.get("hardlinkonly"):
|
||||||
@@ -2429,6 +2443,7 @@ class AuthSrv(object):
|
|||||||
self.defpw = defpw
|
self.defpw = defpw
|
||||||
self.grps = grps
|
self.grps = grps
|
||||||
self.iacct = {v: k for k, v in acct.items()}
|
self.iacct = {v: k for k, v in acct.items()}
|
||||||
|
self.cfg_files_loaded = cfg_files_loaded
|
||||||
|
|
||||||
self.load_sessions()
|
self.load_sessions()
|
||||||
|
|
||||||
@@ -2559,6 +2574,7 @@ class AuthSrv(object):
|
|||||||
"idxh": int(self.args.ih),
|
"idxh": int(self.args.ih),
|
||||||
"themes": self.args.themes,
|
"themes": self.args.themes,
|
||||||
"turbolvl": self.args.turbo,
|
"turbolvl": self.args.turbo,
|
||||||
|
"nosubtle": self.args.nosubtle,
|
||||||
"u2j": self.args.u2j,
|
"u2j": self.args.u2j,
|
||||||
"u2sz": self.args.u2sz,
|
"u2sz": self.args.u2sz,
|
||||||
"u2ts": vf["u2ts"],
|
"u2ts": vf["u2ts"],
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import calendar
|
import calendar
|
||||||
import errno
|
import errno
|
||||||
import filecmp
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .__init__ import ANYWIN
|
from .__init__ import ANYWIN
|
||||||
from .util import Netdev, load_resource, runcmd, wrename, wunlink
|
from .util import Netdev, atomic_move, load_resource, runcmd, wunlink
|
||||||
|
|
||||||
HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
|
HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
|
||||||
|
|
||||||
@@ -122,7 +120,7 @@ def _gen_ca(log: "RootLogger", args):
|
|||||||
wunlink(nlog, bname + ".key", VF)
|
wunlink(nlog, bname + ".key", VF)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
wrename(nlog, bname + "-key.pem", bname + ".key", VF)
|
atomic_move(nlog, bname + "-key.pem", bname + ".key", VF)
|
||||||
wunlink(nlog, bname + ".csr", VF)
|
wunlink(nlog, bname + ".csr", VF)
|
||||||
|
|
||||||
log("cert", "new ca OK", 2)
|
log("cert", "new ca OK", 2)
|
||||||
@@ -215,7 +213,7 @@ def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]):
|
|||||||
wunlink(nlog, bname + ".key", VF)
|
wunlink(nlog, bname + ".key", VF)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
wrename(nlog, bname + "-key.pem", bname + ".key", VF)
|
atomic_move(nlog, bname + "-key.pem", bname + ".key", VF)
|
||||||
wunlink(nlog, bname + ".csr", VF)
|
wunlink(nlog, bname + ".csr", VF)
|
||||||
|
|
||||||
with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
|
with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ def vf_vmap() -> dict[str, str]:
|
|||||||
"th_x3": "th3x",
|
"th_x3": "th3x",
|
||||||
}
|
}
|
||||||
for k in (
|
for k in (
|
||||||
|
"bup_ck",
|
||||||
"dbd",
|
"dbd",
|
||||||
"forget_ip",
|
"forget_ip",
|
||||||
"hsortn",
|
"hsortn",
|
||||||
@@ -95,6 +96,8 @@ def vf_vmap() -> dict[str, str]:
|
|||||||
"og_title_i",
|
"og_title_i",
|
||||||
"og_tpl",
|
"og_tpl",
|
||||||
"og_ua",
|
"og_ua",
|
||||||
|
"put_ck",
|
||||||
|
"put_name",
|
||||||
"mv_retry",
|
"mv_retry",
|
||||||
"rm_retry",
|
"rm_retry",
|
||||||
"sort",
|
"sort",
|
||||||
@@ -165,6 +168,9 @@ flagcats = {
|
|||||||
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
||||||
"nosub": "forces all uploads into the top folder of the vfs",
|
"nosub": "forces all uploads into the top folder of the vfs",
|
||||||
"magic": "enables filetype detection for nameless uploads",
|
"magic": "enables filetype detection for nameless uploads",
|
||||||
|
"put_name": "fallback filename for nameless uploads",
|
||||||
|
"put_ck": "default checksum-hasher for PUT/WebDAV uploads",
|
||||||
|
"bup_ck": "default checksum-hasher for bup/basic uploads",
|
||||||
"gz": "allows server-side gzip compression of uploads with ?gz",
|
"gz": "allows server-side gzip compression of uploads with ?gz",
|
||||||
"xz": "allows server-side lzma compression of uploads with ?xz",
|
"xz": "allows server-side lzma compression of uploads with ?xz",
|
||||||
"pk": "forces server-side compression, optional arg: xz,9",
|
"pk": "forces server-side compression, optional arg: xz,9",
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ from .util import (
|
|||||||
vol_san,
|
vol_san,
|
||||||
vroots,
|
vroots,
|
||||||
vsplit,
|
vsplit,
|
||||||
wrename,
|
|
||||||
wunlink,
|
wunlink,
|
||||||
yieldfile,
|
yieldfile,
|
||||||
)
|
)
|
||||||
@@ -190,11 +189,11 @@ class HttpCli(object):
|
|||||||
self.log_src = conn.log_src # mypy404
|
self.log_src = conn.log_src # mypy404
|
||||||
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
|
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
|
||||||
self.tls: bool = hasattr(self.s, "cipher")
|
self.tls: bool = hasattr(self.s, "cipher")
|
||||||
|
self.is_vproxied = bool(self.args.R)
|
||||||
|
|
||||||
# placeholders; assigned by run()
|
# placeholders; assigned by run()
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
self.is_https = False
|
self.is_https = False
|
||||||
self.is_vproxied = False
|
|
||||||
self.in_hdr_recv = True
|
self.in_hdr_recv = True
|
||||||
self.headers: dict[str, str] = {}
|
self.headers: dict[str, str] = {}
|
||||||
self.mode = " " # http verb
|
self.mode = " " # http verb
|
||||||
@@ -402,7 +401,6 @@ class HttpCli(object):
|
|||||||
self.bad_xff = True
|
self.bad_xff = True
|
||||||
else:
|
else:
|
||||||
self.ip = cli_ip
|
self.ip = cli_ip
|
||||||
self.is_vproxied = bool(self.args.R)
|
|
||||||
self.log_src = self.conn.set_rproxy(self.ip)
|
self.log_src = self.conn.set_rproxy(self.ip)
|
||||||
self.host = self.headers.get("x-forwarded-host") or self.host
|
self.host = self.headers.get("x-forwarded-host") or self.host
|
||||||
trusted_xff = True
|
trusted_xff = True
|
||||||
@@ -535,6 +533,7 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
t = "incorrect --rp-loc or webserver config; expected vpath starting with %r but got %r"
|
t = "incorrect --rp-loc or webserver config; expected vpath starting with %r but got %r"
|
||||||
self.log(t % (self.args.R, vpath), 1)
|
self.log(t % (self.args.R, vpath), 1)
|
||||||
|
self.is_vproxied = False
|
||||||
|
|
||||||
self.ouparam = uparam.copy()
|
self.ouparam = uparam.copy()
|
||||||
|
|
||||||
@@ -1206,11 +1205,6 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
return self.tx_res(res_path)
|
return self.tx_res(res_path)
|
||||||
|
|
||||||
if res_path != undot(res_path):
|
|
||||||
t = "malicious user; attempted path traversal; req(%r) vp(%r) => %r"
|
|
||||||
self.log(t % (self.req, "/" + self.vpath, res_path), 1)
|
|
||||||
self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal")
|
|
||||||
|
|
||||||
self.tx_404()
|
self.tx_404()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1239,10 +1233,19 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
return self.tx_404(True)
|
return self.tx_404(True)
|
||||||
else:
|
else:
|
||||||
vfs = self.asrv.vfs
|
if (
|
||||||
if vfs.badcfg1:
|
self.asrv.badcfg1
|
||||||
t = "<h2>access denied due to failsafe; check server log</h2>"
|
and "h" not in self.ouparam
|
||||||
html = self.j2s("splash", this=self, msg=t)
|
and "hc" not in self.ouparam
|
||||||
|
):
|
||||||
|
zs1 = "copyparty refused to start due to a failsafe: invalid server config; check server log"
|
||||||
|
zs2 = 'you may <a href="/?h">access the controlpanel</a> but nothing will work until you shutdown the copyparty container and %s config-file (or provide the configuration as command-line arguments)'
|
||||||
|
if self.asrv.is_lxc and len(self.asrv.cfg_files_loaded) == 1:
|
||||||
|
zs2 = zs2 % ("add a",)
|
||||||
|
else:
|
||||||
|
zs2 = zs2 % ("fix the",)
|
||||||
|
|
||||||
|
html = self.j2s("msg", h1=zs1, h2=zs2)
|
||||||
self.reply(html.encode("utf-8", "replace"), 500)
|
self.reply(html.encode("utf-8", "replace"), 500)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1418,7 +1421,7 @@ class HttpCli(object):
|
|||||||
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
|
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
|
||||||
desc = html_escape(desc, True, True) if desc else title
|
desc = html_escape(desc, True, True) if desc else title
|
||||||
mime = html_escape(guess_mime(title))
|
mime = html_escape(guess_mime(title))
|
||||||
lmod = formatdate(i["ts"])
|
lmod = formatdate(max(0, i["ts"]))
|
||||||
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
|
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
|
||||||
zs = (
|
zs = (
|
||||||
"""\
|
"""\
|
||||||
@@ -1575,7 +1578,7 @@ class HttpCli(object):
|
|||||||
for x in fgen:
|
for x in fgen:
|
||||||
rp = vjoin(vtop, x["vp"])
|
rp = vjoin(vtop, x["vp"])
|
||||||
st: os.stat_result = x["st"]
|
st: os.stat_result = x["st"]
|
||||||
mtime = st.st_mtime
|
mtime = max(0, st.st_mtime)
|
||||||
if stat.S_ISLNK(st.st_mode):
|
if stat.S_ISLNK(st.st_mode):
|
||||||
try:
|
try:
|
||||||
st = bos.stat(os.path.join(tap, x["vp"]))
|
st = bos.stat(os.path.join(tap, x["vp"]))
|
||||||
@@ -2105,8 +2108,7 @@ class HttpCli(object):
|
|||||||
suffix = "-{:.6f}-{}".format(time.time(), self.dip())
|
suffix = "-{:.6f}-{}".format(time.time(), self.dip())
|
||||||
nameless = not fn
|
nameless = not fn
|
||||||
if nameless:
|
if nameless:
|
||||||
suffix += ".bin"
|
fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
|
||||||
fn = "put" + suffix
|
|
||||||
|
|
||||||
params = {"suffix": suffix, "fdir": fdir}
|
params = {"suffix": suffix, "fdir": fdir}
|
||||||
if self.args.nw:
|
if self.args.nw:
|
||||||
@@ -2186,28 +2188,26 @@ class HttpCli(object):
|
|||||||
# small toctou, but better than clobbering a hardlink
|
# small toctou, but better than clobbering a hardlink
|
||||||
wunlink(self.log, path, vfs.flags)
|
wunlink(self.log, path, vfs.flags)
|
||||||
|
|
||||||
halg = "sha512"
|
|
||||||
hasher = None
|
hasher = None
|
||||||
copier = hashcopy
|
copier = hashcopy
|
||||||
if "ck" in self.ouparam or "ck" in self.headers:
|
halg = self.ouparam.get("ck") or self.headers.get("ck") or vfs.flags["put_ck"]
|
||||||
halg = zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
|
if halg == "sha512":
|
||||||
if not zs or zs == "no":
|
pass
|
||||||
copier = justcopy
|
elif halg == "no":
|
||||||
halg = ""
|
copier = justcopy
|
||||||
elif zs == "md5":
|
halg = ""
|
||||||
hasher = hashlib.md5(**USED4SEC)
|
elif halg == "md5":
|
||||||
elif zs == "sha1":
|
hasher = hashlib.md5(**USED4SEC)
|
||||||
hasher = hashlib.sha1(**USED4SEC)
|
elif halg == "sha1":
|
||||||
elif zs == "sha256":
|
hasher = hashlib.sha1(**USED4SEC)
|
||||||
hasher = hashlib.sha256(**USED4SEC)
|
elif halg == "sha256":
|
||||||
elif zs in ("blake2", "b2"):
|
hasher = hashlib.sha256(**USED4SEC)
|
||||||
hasher = hashlib.blake2b(**USED4SEC)
|
elif halg in ("blake2", "b2"):
|
||||||
elif zs in ("blake2s", "b2s"):
|
hasher = hashlib.blake2b(**USED4SEC)
|
||||||
hasher = hashlib.blake2s(**USED4SEC)
|
elif halg in ("blake2s", "b2s"):
|
||||||
elif zs == "sha512":
|
hasher = hashlib.blake2s(**USED4SEC)
|
||||||
pass
|
else:
|
||||||
else:
|
raise Pebkac(500, "unknown hash alg")
|
||||||
raise Pebkac(500, "unknown hash alg")
|
|
||||||
|
|
||||||
f, fn = ren_open(fn, *open_a, **params)
|
f, fn = ren_open(fn, *open_a, **params)
|
||||||
try:
|
try:
|
||||||
@@ -2596,10 +2596,6 @@ class HttpCli(object):
|
|||||||
x = self.conn.hsrv.broker.ask("up2k.handle_json", body, self.u2fh.aps)
|
x = self.conn.hsrv.broker.ask("up2k.handle_json", body, self.u2fh.aps)
|
||||||
ret = x.get()
|
ret = x.get()
|
||||||
|
|
||||||
if self.is_vproxied:
|
|
||||||
if "purl" in ret:
|
|
||||||
ret["purl"] = self.args.SR + ret["purl"]
|
|
||||||
|
|
||||||
if self.args.shr and self.vpath.startswith(self.args.shr1):
|
if self.args.shr and self.vpath.startswith(self.args.shr1):
|
||||||
# strip common suffix (uploader's folder structure)
|
# strip common suffix (uploader's folder structure)
|
||||||
vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
|
vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
|
||||||
@@ -2609,6 +2605,10 @@ class HttpCli(object):
|
|||||||
raise Pebkac(500, t % zt)
|
raise Pebkac(500, t % zt)
|
||||||
ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
|
ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
|
||||||
|
|
||||||
|
if self.is_vproxied:
|
||||||
|
if "purl" in ret:
|
||||||
|
ret["purl"] = self.args.SR + ret["purl"]
|
||||||
|
|
||||||
ret = json.dumps(ret)
|
ret = json.dumps(ret)
|
||||||
self.log(ret)
|
self.log(ret)
|
||||||
self.reply(ret.encode("utf-8"), mime="application/json")
|
self.reply(ret.encode("utf-8"), mime="application/json")
|
||||||
@@ -2936,7 +2936,8 @@ class HttpCli(object):
|
|||||||
self.parser.drop()
|
self.parser.drop()
|
||||||
|
|
||||||
self.log("logout " + self.uname)
|
self.log("logout " + self.uname)
|
||||||
self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
|
if not self.uname.startswith("s_"):
|
||||||
|
self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
|
||||||
self.get_pwd_cookie("x")
|
self.get_pwd_cookie("x")
|
||||||
|
|
||||||
dst = self.args.SRS + "?h"
|
dst = self.args.SRS + "?h"
|
||||||
@@ -3089,15 +3090,18 @@ class HttpCli(object):
|
|||||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||||
self._assert_safe_rem(rem)
|
self._assert_safe_rem(rem)
|
||||||
|
|
||||||
halg = "sha512"
|
|
||||||
hasher = None
|
hasher = None
|
||||||
copier = hashcopy
|
|
||||||
if nohash:
|
if nohash:
|
||||||
halg = ""
|
halg = ""
|
||||||
copier = justcopy
|
copier = justcopy
|
||||||
elif "ck" in self.ouparam or "ck" in self.headers:
|
else:
|
||||||
halg = self.ouparam.get("ck") or self.headers.get("ck") or ""
|
copier = hashcopy
|
||||||
if not halg or halg == "no":
|
halg = (
|
||||||
|
self.ouparam.get("ck") or self.headers.get("ck") or vfs.flags["bup_ck"]
|
||||||
|
)
|
||||||
|
if halg == "sha512":
|
||||||
|
pass
|
||||||
|
elif halg == "no":
|
||||||
copier = justcopy
|
copier = justcopy
|
||||||
halg = ""
|
halg = ""
|
||||||
elif halg == "md5":
|
elif halg == "md5":
|
||||||
@@ -3110,8 +3114,6 @@ class HttpCli(object):
|
|||||||
hasher = hashlib.blake2b(**USED4SEC)
|
hasher = hashlib.blake2b(**USED4SEC)
|
||||||
elif halg in ("blake2s", "b2s"):
|
elif halg in ("blake2s", "b2s"):
|
||||||
hasher = hashlib.blake2s(**USED4SEC)
|
hasher = hashlib.blake2s(**USED4SEC)
|
||||||
elif halg == "sha512":
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
raise Pebkac(500, "unknown hash alg")
|
raise Pebkac(500, "unknown hash alg")
|
||||||
|
|
||||||
@@ -3574,7 +3576,7 @@ class HttpCli(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if dp:
|
if dp:
|
||||||
wrename(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
|
atomic_move(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
|
||||||
|
|
||||||
assert self.parser.gen # !rm
|
assert self.parser.gen # !rm
|
||||||
p_field, _, p_data = next(self.parser.gen)
|
p_field, _, p_data = next(self.parser.gen)
|
||||||
@@ -3985,7 +3987,7 @@ class HttpCli(object):
|
|||||||
if ptop is not None:
|
if ptop is not None:
|
||||||
assert job and ap_data # type: ignore # !rm
|
assert job and ap_data # type: ignore # !rm
|
||||||
sz = job["size"]
|
sz = job["size"]
|
||||||
file_ts = job["lmod"]
|
file_ts = max(0, job["lmod"])
|
||||||
editions["plain"] = (ap_data, sz)
|
editions["plain"] = (ap_data, sz)
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -4251,6 +4253,7 @@ class HttpCli(object):
|
|||||||
self.log(t % (data_end / M, lower / M, upper / M), 6)
|
self.log(t % (data_end / M, lower / M, upper / M), 6)
|
||||||
with self.u2mutex:
|
with self.u2mutex:
|
||||||
if data_end > self.u2fh.aps.get(ap_data, data_end):
|
if data_end > self.u2fh.aps.get(ap_data, data_end):
|
||||||
|
fhs: Optional[set[typing.BinaryIO]] = None
|
||||||
try:
|
try:
|
||||||
fhs = self.u2fh.cache[ap_data].all_fhs
|
fhs = self.u2fh.cache[ap_data].all_fhs
|
||||||
for fh in fhs:
|
for fh in fhs:
|
||||||
@@ -4258,7 +4261,11 @@ class HttpCli(object):
|
|||||||
self.u2fh.aps[ap_data] = data_end
|
self.u2fh.aps[ap_data] = data_end
|
||||||
self.log("pipe: flushed %d up2k-FDs" % (len(fhs),))
|
self.log("pipe: flushed %d up2k-FDs" % (len(fhs),))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("pipe: u2fh flush failed: %r" % (ex,))
|
if fhs is None:
|
||||||
|
err = "file is not being written to right now"
|
||||||
|
else:
|
||||||
|
err = repr(ex)
|
||||||
|
self.log("pipe: u2fh flush failed: " + err)
|
||||||
|
|
||||||
if lower >= data_end:
|
if lower >= data_end:
|
||||||
if data_end:
|
if data_end:
|
||||||
@@ -5504,6 +5511,7 @@ class HttpCli(object):
|
|||||||
raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
|
raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
|
||||||
|
|
||||||
pw = req.get("pw") or ""
|
pw = req.get("pw") or ""
|
||||||
|
pw = self.asrv.ah.hash(pw)
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
sexp = req["exp"]
|
sexp = req["exp"]
|
||||||
exp = int(sexp) if sexp else 0
|
exp = int(sexp) if sexp else 0
|
||||||
@@ -6121,7 +6129,7 @@ class HttpCli(object):
|
|||||||
margin = "-"
|
margin = "-"
|
||||||
|
|
||||||
sz = inf.st_size
|
sz = inf.st_size
|
||||||
zd = datetime.fromtimestamp(linf.st_mtime, UTC)
|
zd = datetime.fromtimestamp(max(0, linf.st_mtime), UTC)
|
||||||
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
|
||||||
zd.year,
|
zd.year,
|
||||||
zd.month,
|
zd.month,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ if True: # pylint: disable=using-constant-test
|
|||||||
from .util import NamedLogger
|
from .util import NamedLogger
|
||||||
|
|
||||||
|
|
||||||
|
TAR_NO_OPUS = set("aac|m4a|mp3|oga|ogg|opus|wma".split("|"))
|
||||||
|
|
||||||
|
|
||||||
class StreamArc(object):
|
class StreamArc(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -82,9 +85,7 @@ def enthumb(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
rem = f["vp"]
|
rem = f["vp"]
|
||||||
ext = rem.rsplit(".", 1)[-1].lower()
|
ext = rem.rsplit(".", 1)[-1].lower()
|
||||||
if (fmt == "mp3" and ext == "mp3") or (
|
if (fmt == "mp3" and ext == "mp3") or (fmt == "opus" and ext in TAR_NO_OPUS):
|
||||||
fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|")
|
|
||||||
):
|
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
vp = vjoin(vtop, rem.split("/", 1)[1])
|
vp = vjoin(vtop, rem.split("/", 1)[1])
|
||||||
|
|||||||
@@ -253,6 +253,14 @@ class SvcHub(object):
|
|||||||
setattr(args, "ipu_iu", iu)
|
setattr(args, "ipu_iu", iu)
|
||||||
setattr(args, "ipu_nm", nm)
|
setattr(args, "ipu_nm", nm)
|
||||||
|
|
||||||
|
for zs in "ah_salt fk_salt dk_salt".split():
|
||||||
|
if getattr(args, "show_%s" % (zs,)):
|
||||||
|
self.log("root", "effective %s is %s" % (zs, getattr(args, zs)))
|
||||||
|
|
||||||
|
if args.ah_cli or args.ah_gen:
|
||||||
|
args.no_ses = True
|
||||||
|
args.shr = ""
|
||||||
|
|
||||||
if not self.args.no_ses:
|
if not self.args.no_ses:
|
||||||
self.setup_session_db()
|
self.setup_session_db()
|
||||||
|
|
||||||
@@ -770,6 +778,7 @@ class SvcHub(object):
|
|||||||
self.log("optional-dependencies", t, 6)
|
self.log("optional-dependencies", t, 6)
|
||||||
|
|
||||||
def _check_env(self) -> None:
|
def _check_env(self) -> None:
|
||||||
|
al = self.args
|
||||||
try:
|
try:
|
||||||
files = os.listdir(E.cfg)
|
files = os.listdir(E.cfg)
|
||||||
except:
|
except:
|
||||||
@@ -786,6 +795,21 @@ class SvcHub(object):
|
|||||||
if self.args.bauth_last:
|
if self.args.bauth_last:
|
||||||
self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
|
self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
|
||||||
|
|
||||||
|
have_tcp = False
|
||||||
|
for zs in al.i:
|
||||||
|
if not zs.startswith("unix:"):
|
||||||
|
have_tcp = True
|
||||||
|
if not have_tcp:
|
||||||
|
zb = False
|
||||||
|
zs = "z zm zm4 zm6 zmv zmvv zs zsv zv"
|
||||||
|
for zs in zs.split():
|
||||||
|
if getattr(al, zs, False):
|
||||||
|
setattr(al, zs, False)
|
||||||
|
zb = True
|
||||||
|
if zb:
|
||||||
|
t = "only listening on unix-sockets; cannot enable zeroconf/mdns/ssdp as requested"
|
||||||
|
self.log("root", t, 3)
|
||||||
|
|
||||||
if not self.args.no_dav:
|
if not self.args.no_dav:
|
||||||
from .dxml import DXML_OK
|
from .dxml import DXML_OK
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ def gen_fdesc(sz: int, crc32: int, z64: bool) -> bytes:
|
|||||||
|
|
||||||
def gen_hdr(
|
def gen_hdr(
|
||||||
h_pos: Optional[int],
|
h_pos: Optional[int],
|
||||||
|
z64: bool,
|
||||||
fn: str,
|
fn: str,
|
||||||
sz: int,
|
sz: int,
|
||||||
lastmod: int,
|
lastmod: int,
|
||||||
@@ -70,7 +71,6 @@ def gen_hdr(
|
|||||||
# appnote 4.5 / zip 3.0 (2008) / unzip 6.0 (2009) says to add z64
|
# appnote 4.5 / zip 3.0 (2008) / unzip 6.0 (2009) says to add z64
|
||||||
# extinfo for values which exceed H, but that becomes an off-by-one
|
# extinfo for values which exceed H, but that becomes an off-by-one
|
||||||
# (can't tell if it was clamped or exactly maxval), make it obvious
|
# (can't tell if it was clamped or exactly maxval), make it obvious
|
||||||
z64 = sz >= 0xFFFFFFFF
|
|
||||||
z64v = [sz, sz] if z64 else []
|
z64v = [sz, sz] if z64 else []
|
||||||
if h_pos and h_pos >= 0xFFFFFFFF:
|
if h_pos and h_pos >= 0xFFFFFFFF:
|
||||||
# central, also consider ptr to original header
|
# central, also consider ptr to original header
|
||||||
@@ -244,6 +244,7 @@ class StreamZip(StreamArc):
|
|||||||
|
|
||||||
sz = st.st_size
|
sz = st.st_size
|
||||||
ts = st.st_mtime
|
ts = st.st_mtime
|
||||||
|
h_pos = self.pos
|
||||||
|
|
||||||
crc = 0
|
crc = 0
|
||||||
if self.pre_crc:
|
if self.pre_crc:
|
||||||
@@ -252,8 +253,12 @@ class StreamZip(StreamArc):
|
|||||||
|
|
||||||
crc &= 0xFFFFFFFF
|
crc &= 0xFFFFFFFF
|
||||||
|
|
||||||
h_pos = self.pos
|
# some unzip-programs expect a 64bit data-descriptor
|
||||||
buf = gen_hdr(None, name, sz, ts, self.utf8, crc, self.pre_crc)
|
# even if the only 32bit-exceeding value is the offset,
|
||||||
|
# so force that by placeholdering the filesize too
|
||||||
|
z64 = h_pos >= 0xFFFFFFFF or sz >= 0xFFFFFFFF
|
||||||
|
|
||||||
|
buf = gen_hdr(None, z64, name, sz, ts, self.utf8, crc, self.pre_crc)
|
||||||
yield self._ct(buf)
|
yield self._ct(buf)
|
||||||
|
|
||||||
for buf in yieldfile(src, self.args.iobuf):
|
for buf in yieldfile(src, self.args.iobuf):
|
||||||
@@ -266,8 +271,6 @@ class StreamZip(StreamArc):
|
|||||||
|
|
||||||
self.items.append((name, sz, ts, crc, h_pos))
|
self.items.append((name, sz, ts, crc, h_pos))
|
||||||
|
|
||||||
z64 = sz >= 4 * 1024 * 1024 * 1024
|
|
||||||
|
|
||||||
if z64 or not self.pre_crc:
|
if z64 or not self.pre_crc:
|
||||||
buf = gen_fdesc(sz, crc, z64)
|
buf = gen_fdesc(sz, crc, z64)
|
||||||
yield self._ct(buf)
|
yield self._ct(buf)
|
||||||
@@ -306,7 +309,8 @@ class StreamZip(StreamArc):
|
|||||||
|
|
||||||
cdir_pos = self.pos
|
cdir_pos = self.pos
|
||||||
for name, sz, ts, crc, h_pos in self.items:
|
for name, sz, ts, crc, h_pos in self.items:
|
||||||
buf = gen_hdr(h_pos, name, sz, ts, self.utf8, crc, self.pre_crc)
|
z64 = h_pos >= 0xFFFFFFFF or sz >= 0xFFFFFFFF
|
||||||
|
buf = gen_hdr(h_pos, z64, name, sz, ts, self.utf8, crc, self.pre_crc)
|
||||||
mbuf += self._ct(buf)
|
mbuf += self._ct(buf)
|
||||||
if len(mbuf) >= 16384:
|
if len(mbuf) >= 16384:
|
||||||
yield mbuf
|
yield mbuf
|
||||||
|
|||||||
@@ -566,7 +566,7 @@ class TcpSrv(object):
|
|||||||
ip = None
|
ip = None
|
||||||
ips = list(t1) + list(t2)
|
ips = list(t1) + list(t2)
|
||||||
qri = self.args.qri
|
qri = self.args.qri
|
||||||
if self.args.zm and not qri:
|
if self.args.zm and not qri and ips:
|
||||||
name = self.args.name + ".local"
|
name = self.args.name + ".local"
|
||||||
t1[name] = next(v for v in (t1 or t2).values())
|
t1[name] = next(v for v in (t1 or t2).values())
|
||||||
ips = [name] + ips
|
ips = [name] + ips
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ class Tftpd(object):
|
|||||||
if not ptn or not ptn.match(fn.lower()):
|
if not ptn or not ptn.match(fn.lower()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
tsdt = datetime.fromtimestamp
|
||||||
vn, rem = self.asrv.vfs.get(vpath, "*", True, False)
|
vn, rem = self.asrv.vfs.get(vpath, "*", True, False)
|
||||||
fsroot, vfs_ls, vfs_virt = vn.ls(
|
fsroot, vfs_ls, vfs_virt = vn.ls(
|
||||||
rem,
|
rem,
|
||||||
@@ -296,7 +297,7 @@ class Tftpd(object):
|
|||||||
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
|
||||||
fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
|
fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
|
||||||
real1 = dirs1 + fils1
|
real1 = dirs1 + fils1
|
||||||
realt = [(datetime.fromtimestamp(mt, UTC), sz, fn) for mt, sz, fn in real1]
|
realt = [(tsdt(max(0, mt), UTC), sz, fn) for mt, sz, fn in real1]
|
||||||
reals = [
|
reals = [
|
||||||
(
|
(
|
||||||
"%04d-%02d-%02d %02d:%02d:%02d"
|
"%04d-%02d-%02d %02d:%02d:%02d"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -19,16 +20,17 @@ from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
|
|||||||
from .util import BytesIO # type: ignore
|
from .util import BytesIO # type: ignore
|
||||||
from .util import (
|
from .util import (
|
||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
|
VF_CAREFUL,
|
||||||
Cooldown,
|
Cooldown,
|
||||||
Daemon,
|
Daemon,
|
||||||
afsenc,
|
afsenc,
|
||||||
|
atomic_move,
|
||||||
fsenc,
|
fsenc,
|
||||||
min_ex,
|
min_ex,
|
||||||
runcmd,
|
runcmd,
|
||||||
statdir,
|
statdir,
|
||||||
ub64enc,
|
ub64enc,
|
||||||
vsplit,
|
vsplit,
|
||||||
wrename,
|
|
||||||
wunlink,
|
wunlink,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ HAVE_WEBP = False
|
|||||||
|
|
||||||
EXTS_TH = set(["jpg", "webp", "png"])
|
EXTS_TH = set(["jpg", "webp", "png"])
|
||||||
EXTS_AC = set(["opus", "owa", "caf", "mp3"])
|
EXTS_AC = set(["opus", "owa", "caf", "mp3"])
|
||||||
|
EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
|
||||||
|
|
||||||
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
|
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
|
||||||
|
|
||||||
@@ -167,12 +170,15 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.busy: dict[str, list[threading.Condition]] = {}
|
self.busy: dict[str, list[threading.Condition]] = {}
|
||||||
|
self.untemp: dict[str, list[str]] = {}
|
||||||
self.ram: dict[str, float] = {}
|
self.ram: dict[str, float] = {}
|
||||||
self.memcond = threading.Condition(self.mutex)
|
self.memcond = threading.Condition(self.mutex)
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.rm_nullthumbs = True # forget failed conversions on startup
|
self.rm_nullthumbs = True # forget failed conversions on startup
|
||||||
self.nthr = max(1, self.args.th_mt)
|
self.nthr = max(1, self.args.th_mt)
|
||||||
|
|
||||||
|
self.exts_spec_unsafe = set(self.args.th_spec_cnv.split(","))
|
||||||
|
|
||||||
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
self.q: Queue[Optional[tuple[str, str, str, VFS]]] = Queue(self.nthr * 4)
|
||||||
for n in range(self.nthr):
|
for n in range(self.nthr):
|
||||||
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
||||||
@@ -406,17 +412,25 @@ class ThumbSrv(object):
|
|||||||
wunlink(self.log, ap_unpk, vn.flags)
|
wunlink(self.log, ap_unpk, vn.flags)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wrename(self.log, ttpath, tpath, vn.flags)
|
atomic_move(self.log, ttpath, tpath, vn.flags)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if not os.path.exists(tpath):
|
if not os.path.exists(tpath):
|
||||||
t = "failed to move [%s] to [%s]: %r"
|
t = "failed to move [%s] to [%s]: %r"
|
||||||
self.log(t % (ttpath, tpath, ex), 3)
|
self.log(t % (ttpath, tpath, ex), 3)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
untemp = []
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
subs = self.busy[tpath]
|
subs = self.busy[tpath]
|
||||||
del self.busy[tpath]
|
del self.busy[tpath]
|
||||||
self.ram.pop(ttpath, None)
|
self.ram.pop(ttpath, None)
|
||||||
|
untemp = self.untemp.pop(ttpath, None) or []
|
||||||
|
|
||||||
|
for ap in untemp:
|
||||||
|
try:
|
||||||
|
wunlink(self.log, ap, VF_CAREFUL)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
for x in subs:
|
for x in subs:
|
||||||
with x:
|
with x:
|
||||||
@@ -663,22 +677,50 @@ class ThumbSrv(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
wrename(self.log, wtpath, tpath, vn.flags)
|
atomic_move(self.log, wtpath, tpath, vn.flags)
|
||||||
|
|
||||||
def conv_spec(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
def conv_spec(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
|
||||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if "ac" not in ret:
|
if "ac" not in ret:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
|
fext = abspath.split(".")[-1].lower()
|
||||||
|
|
||||||
# https://trac.ffmpeg.org/ticket/10797
|
# https://trac.ffmpeg.org/ticket/10797
|
||||||
# expect 1 GiB every 600 seconds when duration is tricky;
|
# expect 1 GiB every 600 seconds when duration is tricky;
|
||||||
# simple filetypes are generally safer so let's special-case those
|
# simple filetypes are generally safer so let's special-case those
|
||||||
safe = ("flac", "wav", "aif", "aiff", "opus")
|
coeff = 1800 if fext in EXTS_SPEC_SAFE else 600
|
||||||
coeff = 1800 if abspath.split(".")[-1].lower() in safe else 600
|
dur = ret[".dur"][1] if ".dur" in ret else 900
|
||||||
dur = ret[".dur"][1] if ".dur" in ret else 300
|
|
||||||
need = 0.2 + dur / coeff
|
need = 0.2 + dur / coeff
|
||||||
self.wait4ram(need, tpath)
|
self.wait4ram(need, tpath)
|
||||||
|
|
||||||
|
infile = abspath
|
||||||
|
if dur >= 900 or fext in self.exts_spec_unsafe:
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".spec.flac", delete=False) as f:
|
||||||
|
f.write(b"h")
|
||||||
|
infile = f.name
|
||||||
|
try:
|
||||||
|
self.untemp[tpath].append(infile)
|
||||||
|
except:
|
||||||
|
self.untemp[tpath] = [infile]
|
||||||
|
|
||||||
|
# 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"-ac", b"1",
|
||||||
|
b"-ar", b"48000",
|
||||||
|
b"-sample_fmt", b"s16",
|
||||||
|
b"-t", b"900",
|
||||||
|
b"-y", fsenc(infile),
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
|
fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
|
||||||
if "3" in fmt:
|
if "3" in fmt:
|
||||||
fc += "1280x1024,crop=1420:1056:70:48[o]"
|
fc += "1280x1024,crop=1420:1056:70:48[o]"
|
||||||
@@ -698,7 +740,7 @@ class ThumbSrv(object):
|
|||||||
b"-nostdin",
|
b"-nostdin",
|
||||||
b"-v", b"error",
|
b"-v", b"error",
|
||||||
b"-hide_banner",
|
b"-hide_banner",
|
||||||
b"-i", fsenc(abspath),
|
b"-i", fsenc(infile),
|
||||||
b"-filter_complex", fc.encode("utf-8"),
|
b"-filter_complex", fc.encode("utf-8"),
|
||||||
b"-map", b"[o]",
|
b"-map", b"[o]",
|
||||||
b"-frames:v", b"1",
|
b"-frames:v", b"1",
|
||||||
|
|||||||
@@ -1119,7 +1119,7 @@ class Up2k(object):
|
|||||||
ft = "\033[0;32m{}{:.0}"
|
ft = "\033[0;32m{}{:.0}"
|
||||||
ff = "\033[0;35m{}{:.0}"
|
ff = "\033[0;35m{}{:.0}"
|
||||||
fv = "\033[0;36m{}:\033[90m{}"
|
fv = "\033[0;36m{}:\033[90m{}"
|
||||||
zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
|
zs = "ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
|
||||||
fx = set(zs.split())
|
fx = set(zs.split())
|
||||||
fd = vf_bmap()
|
fd = vf_bmap()
|
||||||
fd.update(vf_cmap())
|
fd.update(vf_cmap())
|
||||||
@@ -2120,11 +2120,12 @@ class Up2k(object):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
w = bw[:-1].decode("ascii")
|
w = bw[:-1].decode("ascii")
|
||||||
|
w16 = w[:16]
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
try:
|
try:
|
||||||
q = "select rd, fn, ip, at from up where substr(w,1,16)=? and +w=?"
|
q = "select rd, fn, ip, at from up where substr(w,1,16)=? and +w=?"
|
||||||
rd, fn, ip, at = cur.execute(q, (w[:16], w)).fetchone()
|
rd, fn, ip, at = cur.execute(q, (w16, w)).fetchone()
|
||||||
except:
|
except:
|
||||||
# file modified/deleted since spooling
|
# file modified/deleted since spooling
|
||||||
continue
|
continue
|
||||||
@@ -2133,8 +2134,12 @@ class Up2k(object):
|
|||||||
rd, fn = s3dec(rd, fn)
|
rd, fn = s3dec(rd, fn)
|
||||||
|
|
||||||
if "mtp" in flags:
|
if "mtp" in flags:
|
||||||
|
q = "select 1 from mt where w=? and +k='t:mtp' limit 1"
|
||||||
|
if cur.execute(q, (w16,)).fetchone():
|
||||||
|
continue
|
||||||
|
|
||||||
q = "insert into mt values (?,'t:mtp','a')"
|
q = "insert into mt values (?,'t:mtp','a')"
|
||||||
cur.execute(q, (w[:16],))
|
cur.execute(q, (w16,))
|
||||||
|
|
||||||
abspath = djoin(ptop, rd, fn)
|
abspath = djoin(ptop, rd, fn)
|
||||||
self.pp.msg = "c%d %s" % (nq, abspath)
|
self.pp.msg = "c%d %s" % (nq, abspath)
|
||||||
@@ -2190,7 +2195,7 @@ class Up2k(object):
|
|||||||
return tf, -1
|
return tf, -1
|
||||||
|
|
||||||
if flt == 1:
|
if flt == 1:
|
||||||
q = "select w from mt where w = ?"
|
q = "select 1 from mt where w=? and +k != 't:mtp'"
|
||||||
if c2.execute(q, (row[0][:16],)).fetchone():
|
if c2.execute(q, (row[0][:16],)).fetchone():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -3231,7 +3236,7 @@ class Up2k(object):
|
|||||||
if hr.get("reloc"):
|
if hr.get("reloc"):
|
||||||
x = pathmod(self.vfs, dst, vp, hr["reloc"])
|
x = pathmod(self.vfs, dst, vp, hr["reloc"])
|
||||||
if x:
|
if x:
|
||||||
zvfs = vfs
|
ud1 = (vfs.vpath, job["prel"], job["name"])
|
||||||
pdir, _, job["name"], (vfs, rem) = x
|
pdir, _, job["name"], (vfs, rem) = x
|
||||||
dst = os.path.join(pdir, job["name"])
|
dst = os.path.join(pdir, job["name"])
|
||||||
job["vcfg"] = vfs.flags
|
job["vcfg"] = vfs.flags
|
||||||
@@ -3239,7 +3244,8 @@ class Up2k(object):
|
|||||||
job["vtop"] = vfs.vpath
|
job["vtop"] = vfs.vpath
|
||||||
job["prel"] = rem
|
job["prel"] = rem
|
||||||
job["name"] = sanitize_fn(job["name"], "")
|
job["name"] = sanitize_fn(job["name"], "")
|
||||||
if zvfs.vpath != vfs.vpath:
|
ud2 = (vfs.vpath, job["prel"], job["name"])
|
||||||
|
if ud1 != ud2:
|
||||||
# print(json.dumps(job, sort_keys=True, indent=4))
|
# print(json.dumps(job, sort_keys=True, indent=4))
|
||||||
job["hash"] = cj["hash"]
|
job["hash"] = cj["hash"]
|
||||||
self.log("xbu reloc1:%d..." % (depth,), 6)
|
self.log("xbu reloc1:%d..." % (depth,), 6)
|
||||||
@@ -4994,14 +5000,15 @@ class Up2k(object):
|
|||||||
if hr.get("reloc"):
|
if hr.get("reloc"):
|
||||||
x = pathmod(self.vfs, ap_chk, vp_chk, hr["reloc"])
|
x = pathmod(self.vfs, ap_chk, vp_chk, hr["reloc"])
|
||||||
if x:
|
if x:
|
||||||
zvfs = vfs
|
ud1 = (vfs.vpath, job["prel"], job["name"])
|
||||||
pdir, _, job["name"], (vfs, rem) = x
|
pdir, _, job["name"], (vfs, rem) = x
|
||||||
job["vcfg"] = vf = vfs.flags
|
job["vcfg"] = vf = vfs.flags
|
||||||
job["ptop"] = vfs.realpath
|
job["ptop"] = vfs.realpath
|
||||||
job["vtop"] = vfs.vpath
|
job["vtop"] = vfs.vpath
|
||||||
job["prel"] = rem
|
job["prel"] = rem
|
||||||
job["name"] = sanitize_fn(job["name"], "")
|
job["name"] = sanitize_fn(job["name"], "")
|
||||||
if zvfs.vpath != vfs.vpath:
|
ud2 = (vfs.vpath, job["prel"], job["name"])
|
||||||
|
if ud1 != ud2:
|
||||||
self.log("xbu reloc2:%d..." % (depth,), 6)
|
self.log("xbu reloc2:%d..." % (depth,), 6)
|
||||||
return self._handle_json(job, depth + 1)
|
return self._handle_json(job, depth + 1)
|
||||||
|
|
||||||
|
|||||||
@@ -472,6 +472,8 @@ FN_EMB = set([".prologue.html", ".epilogue.html", "readme.md", "preadme.md"])
|
|||||||
|
|
||||||
|
|
||||||
def read_ram() -> tuple[float, float]:
|
def read_ram() -> tuple[float, float]:
|
||||||
|
# NOTE: apparently no need to consider /sys/fs/cgroup/memory.max
|
||||||
|
# (cgroups2) since the limit is synced to /proc/meminfo
|
||||||
a = b = 0
|
a = b = 0
|
||||||
try:
|
try:
|
||||||
with open("/proc/meminfo", "rb", 0x10000) as f:
|
with open("/proc/meminfo", "rb", 0x10000) as f:
|
||||||
@@ -2581,6 +2583,11 @@ def _fs_mvrm(
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
if ex.errno == errno.ENOENT:
|
if ex.errno == errno.ENOENT:
|
||||||
return False
|
return False
|
||||||
|
if not attempt and ex.errno == errno.EXDEV:
|
||||||
|
t = "using copy+delete (%s)\n %s\n %s"
|
||||||
|
log(t % (ex.strerror, src, dst))
|
||||||
|
osfun = shutil.move
|
||||||
|
continue
|
||||||
if now - t0 > maxtime or attempt == 90209:
|
if now - t0 > maxtime or attempt == 90209:
|
||||||
raise
|
raise
|
||||||
if not attempt:
|
if not attempt:
|
||||||
@@ -2605,15 +2612,18 @@ def atomic_move(log: "NamedLogger", src: str, dst: str, flags: dict[str, Any]) -
|
|||||||
elif flags.get("mv_re_t"):
|
elif flags.get("mv_re_t"):
|
||||||
_fs_mvrm(log, src, dst, True, flags)
|
_fs_mvrm(log, src, dst, True, flags)
|
||||||
else:
|
else:
|
||||||
os.replace(bsrc, bdst)
|
try:
|
||||||
|
os.replace(bsrc, bdst)
|
||||||
|
except OSError as ex:
|
||||||
def wrename(log: "NamedLogger", src: str, dst: str, flags: dict[str, Any]) -> bool:
|
if ex.errno != errno.EXDEV:
|
||||||
if not flags.get("mv_re_t"):
|
raise
|
||||||
os.rename(fsenc(src), fsenc(dst))
|
t = "using copy+delete (%s);\n %s\n %s"
|
||||||
return True
|
log(t % (ex.strerror, src, dst))
|
||||||
|
try:
|
||||||
return _fs_mvrm(log, src, dst, False, flags)
|
os.unlink(bdst)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
shutil.move(bsrc, bdst)
|
||||||
|
|
||||||
|
|
||||||
def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool:
|
def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool:
|
||||||
|
|||||||
@@ -1151,17 +1151,17 @@ html.y #widget.open {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
background: var(--bg-u3);
|
background: var(--bg-u3);
|
||||||
}
|
}
|
||||||
#wfs, #wfm, #wzip, #wnp {
|
#wfs, #wfm, #wzip, #wnp, #wm3u {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#wfs, #wzip, #wnp {
|
#wfs, #wzip, #wnp, #wm3u {
|
||||||
margin-right: .2em;
|
margin-right: .2em;
|
||||||
padding-right: .2em;
|
padding-right: .2em;
|
||||||
border: 1px solid var(--bg-u5);
|
border: 1px solid var(--bg-u5);
|
||||||
border-width: 0 .1em 0 0;
|
border-width: 0 .1em 0 0;
|
||||||
}
|
}
|
||||||
#wfm.act+#wzip,
|
#wfm.act+#wzip1+#wzip,
|
||||||
#wfm.act+#wzip+#wnp {
|
#wfm.act+#wzip1+#wzip+#wnp {
|
||||||
margin-left: .2em;
|
margin-left: .2em;
|
||||||
padding-left: .2em;
|
padding-left: .2em;
|
||||||
border-left-width: .1em;
|
border-left-width: .1em;
|
||||||
@@ -1175,14 +1175,18 @@ html.y #widget.open {
|
|||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
#wtoggle.sel #wzip,
|
#wtoggle.sel #wzip,
|
||||||
|
#wtoggle.m3u #wm3u,
|
||||||
#wtoggle.np #wnp {
|
#wtoggle.np #wnp {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
#wtoggle.sel #wzip1,
|
||||||
#wtoggle.sel.np #wnp {
|
#wtoggle.sel.np #wnp {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#wfm a,
|
#wfm a,
|
||||||
#wnp a,
|
#wnp a,
|
||||||
|
#wm3u a,
|
||||||
|
#zip1,
|
||||||
#wzip a {
|
#wzip a {
|
||||||
font-size: .5em;
|
font-size: .5em;
|
||||||
padding: 0 .3em;
|
padding: 0 .3em;
|
||||||
@@ -1190,6 +1194,13 @@ html.y #widget.open {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
#zip1 {
|
||||||
|
font-size: .38em;
|
||||||
|
}
|
||||||
|
#wm3u a {
|
||||||
|
margin: -.2em .1em;
|
||||||
|
font-size: .45em;
|
||||||
|
}
|
||||||
#wfs {
|
#wfs {
|
||||||
font-size: .36em;
|
font-size: .36em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -1198,13 +1209,22 @@ html.y #widget.open {
|
|||||||
border-width: 0 .25em 0 0;
|
border-width: 0 .25em 0 0;
|
||||||
}
|
}
|
||||||
#wfm span,
|
#wfm span,
|
||||||
|
#wm3u span,
|
||||||
|
#zip1 span,
|
||||||
#wnp span {
|
#wnp span {
|
||||||
font-size: .6em;
|
font-size: .6em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
#zip1 span {
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
#wnp span {
|
#wnp span {
|
||||||
font-size: .7em;
|
font-size: .7em;
|
||||||
}
|
}
|
||||||
|
#wm3u span {
|
||||||
|
font-size: .77em;
|
||||||
|
padding-top: .2em;
|
||||||
|
}
|
||||||
#wfm a:not(.en) {
|
#wfm a:not(.en) {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
color: var(--fm-off);
|
color: var(--fm-off);
|
||||||
|
|||||||
@@ -140,10 +140,13 @@ var Ls = {
|
|||||||
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
||||||
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
||||||
"wt_selinv": "invert selection",
|
"wt_selinv": "invert selection",
|
||||||
|
"wt_zip1": "download this folder as archive",
|
||||||
"wt_selzip": "download selection as archive",
|
"wt_selzip": "download selection as archive",
|
||||||
"wt_seldl": "download selection as separate files$NHotkey: Y",
|
"wt_seldl": "download selection as separate files$NHotkey: Y",
|
||||||
"wt_npirc": "copy irc-formatted track info",
|
"wt_npirc": "copy irc-formatted track info",
|
||||||
"wt_nptxt": "copy plaintext track info",
|
"wt_nptxt": "copy plaintext track info",
|
||||||
|
"wt_m3ua": "add to m3u playlist (click <code>📻copy</code> later)",
|
||||||
|
"wt_m3uc": "copy m3u playlist to clipboard",
|
||||||
"wt_grid": "toggle grid / list view$NHotkey: G",
|
"wt_grid": "toggle grid / list view$NHotkey: G",
|
||||||
"wt_prev": "previous track$NHotkey: J",
|
"wt_prev": "previous track$NHotkey: J",
|
||||||
"wt_play": "play / pause$NHotkey: P",
|
"wt_play": "play / pause$NHotkey: P",
|
||||||
@@ -246,6 +249,8 @@ var Ls = {
|
|||||||
|
|
||||||
"cut_mt": "use multithreading to accelerate file hashing$N$Nthis uses web-workers and requires$Nmore RAM (up to 512 MiB extra)$N$Nmakes https 30% faster, http 4.5x faster\">mt",
|
"cut_mt": "use multithreading to accelerate file hashing$N$Nthis uses web-workers and requires$Nmore RAM (up to 512 MiB extra)$N$Nmakes https 30% faster, http 4.5x faster\">mt",
|
||||||
|
|
||||||
|
"cut_wasm": "use wasm instead of the browser's built-in hasher; improves speed on chrome-based browsers but increases CPU load, and many older versions of chrome have bugs which makes the browser consume all RAM and crash if this is enabled\">wasm",
|
||||||
|
|
||||||
"cft_text": "favicon text (blank and refresh to disable)",
|
"cft_text": "favicon text (blank and refresh to disable)",
|
||||||
"cft_fg": "foreground color",
|
"cft_fg": "foreground color",
|
||||||
"cft_bg": "background color",
|
"cft_bg": "background color",
|
||||||
@@ -271,6 +276,7 @@ var Ls = {
|
|||||||
"ml_eq": "audio equalizer",
|
"ml_eq": "audio equalizer",
|
||||||
"ml_drc": "dynamic range compressor",
|
"ml_drc": "dynamic range compressor",
|
||||||
|
|
||||||
|
"mt_loop": "loop/repeat one song\">🔁",
|
||||||
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
||||||
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
||||||
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
||||||
@@ -279,6 +285,7 @@ var Ls = {
|
|||||||
"mt_fau": "on phones, prevent music from stopping if the next song doesn't preload fast enough (can make tags display glitchy)\">☕️",
|
"mt_fau": "on phones, prevent music from stopping if the next song doesn't preload fast enough (can make tags display glitchy)\">☕️",
|
||||||
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
|
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
|
||||||
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
|
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
|
||||||
|
"mt_m3u_c": "show buttons for clipboarding the$Nselected songs as m3u8 playlist entries\">📻",
|
||||||
"mt_octl": "os integration (media hotkeys / osd)\">os-ctl",
|
"mt_octl": "os integration (media hotkeys / osd)\">os-ctl",
|
||||||
"mt_oseek": "allow seeking through os integration$N$Nnote: on some devices (iPhones),$Nthis replaces the next-song button\">seek",
|
"mt_oseek": "allow seeking through os integration$N$Nnote: on some devices (iPhones),$Nthis replaces the next-song button\">seek",
|
||||||
"mt_oscv": "show album cover in osd\">art",
|
"mt_oscv": "show album cover in osd\">art",
|
||||||
@@ -304,6 +311,7 @@ var Ls = {
|
|||||||
|
|
||||||
"mb_play": "play",
|
"mb_play": "play",
|
||||||
"mm_hashplay": "play this audio file?",
|
"mm_hashplay": "play this audio file?",
|
||||||
|
"mm_m3u": "press <code>Enter/OK</code> to Play\npress <code>ESC/Cancel</code> to Edit",
|
||||||
"mp_breq": "need firefox 82+ or chrome 73+ or iOS 15+",
|
"mp_breq": "need firefox 82+ or chrome 73+ or iOS 15+",
|
||||||
"mm_bload": "now loading...",
|
"mm_bload": "now loading...",
|
||||||
"mm_bconv": "converting to {0}, please wait...",
|
"mm_bconv": "converting to {0}, please wait...",
|
||||||
@@ -331,6 +339,7 @@ var Ls = {
|
|||||||
"f_bigtxt": "this file is {0} MiB large -- really view as text?",
|
"f_bigtxt": "this file is {0} MiB large -- really view as text?",
|
||||||
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
||||||
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
||||||
|
"f_anota": "only {0} of the {1} items were selected;\nto select the full folder, first scroll to the bottom",
|
||||||
|
|
||||||
"f_dls": 'the file links in the current folder have\nbeen changed into download links',
|
"f_dls": 'the file links in the current folder have\nbeen changed into download links',
|
||||||
|
|
||||||
@@ -433,6 +442,10 @@ var Ls = {
|
|||||||
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
|
"m3u_add1": "song added to m3u playlist",
|
||||||
|
"m3u_addn": "{0} songs added to m3u playlist",
|
||||||
|
"m3u_clip": "m3u playlist now copied to clipboard\n\nyou should create a new textfile named something.m3u and paste the playlist in that document; this will make it playable",
|
||||||
|
|
||||||
"gt_vau": "don't show videos, just play the audio\">🎧",
|
"gt_vau": "don't show videos, just play the audio\">🎧",
|
||||||
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
||||||
"gt_crop": "center-crop thumbnails\">crop",
|
"gt_crop": "center-crop thumbnails\">crop",
|
||||||
@@ -744,10 +757,13 @@ var Ls = {
|
|||||||
"wt_pst": "lim inn filer (som tidligere ble klippet ut / kopiert et annet sted)$NSnarvei: ctrl-V",
|
"wt_pst": "lim inn filer (som tidligere ble klippet ut / kopiert et annet sted)$NSnarvei: ctrl-V",
|
||||||
"wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på en fil)",
|
"wt_selall": "velg alle filer$NSnarvei: ctrl-A (mens fokus er på en fil)",
|
||||||
"wt_selinv": "inverter utvalg",
|
"wt_selinv": "inverter utvalg",
|
||||||
|
"wt_zip1": "last ned denne mappen som et arkiv",
|
||||||
"wt_selzip": "last ned de valgte filene som et arkiv",
|
"wt_selzip": "last ned de valgte filene som et arkiv",
|
||||||
"wt_seldl": "last ned de valgte filene$NSnarvei: Y",
|
"wt_seldl": "last ned de valgte filene$NSnarvei: Y",
|
||||||
"wt_npirc": "kopiér sang-info (irc-formatert)",
|
"wt_npirc": "kopiér sang-info (irc-formatert)",
|
||||||
"wt_nptxt": "kopiér sang-info",
|
"wt_nptxt": "kopiér sang-info",
|
||||||
|
"wt_m3ua": "legg til sang i m3u-spilleliste$N(husk å klikke på <code>📻copy</code> senere)",
|
||||||
|
"wt_m3uc": "kopiér m3u-spillelisten til utklippstavlen",
|
||||||
"wt_grid": "bytt mellom ikoner og listevisning$NSnarvei: G",
|
"wt_grid": "bytt mellom ikoner og listevisning$NSnarvei: G",
|
||||||
"wt_prev": "forrige sang$NSnarvei: J",
|
"wt_prev": "forrige sang$NSnarvei: J",
|
||||||
"wt_play": "play / pause$NSnarvei: P",
|
"wt_play": "play / pause$NSnarvei: P",
|
||||||
@@ -850,6 +866,8 @@ var Ls = {
|
|||||||
|
|
||||||
"cut_mt": "raskere befaring ved å bruke hele CPU'en$N$Ndenne funksjonen anvender web-workers$Nog krever mer RAM (opptil 512 MiB ekstra)$N$Ngjør https 30% raskere, http 4.5x raskere\">mt",
|
"cut_mt": "raskere befaring ved å bruke hele CPU'en$N$Ndenne funksjonen anvender web-workers$Nog krever mer RAM (opptil 512 MiB ekstra)$N$Ngjør https 30% raskere, http 4.5x raskere\">mt",
|
||||||
|
|
||||||
|
"cut_wasm": "bruk wasm istedenfor nettleserens sha512-funksjon; gir bedre ytelse på chrome-baserte nettlesere, men bruker mere CPU, og eldre versjoner av chrome tåler det ikke (spiser opp all RAM og krasjer)\">wasm",
|
||||||
|
|
||||||
"cft_text": "ikontekst (blank ut og last siden på nytt for å deaktivere)",
|
"cft_text": "ikontekst (blank ut og last siden på nytt for å deaktivere)",
|
||||||
"cft_fg": "farge",
|
"cft_fg": "farge",
|
||||||
"cft_bg": "bakgrunnsfarge",
|
"cft_bg": "bakgrunnsfarge",
|
||||||
@@ -875,6 +893,7 @@ var Ls = {
|
|||||||
"ml_eq": "audio equalizer (tonejustering)",
|
"ml_eq": "audio equalizer (tonejustering)",
|
||||||
"ml_drc": "compressor (volum-utjevning)",
|
"ml_drc": "compressor (volum-utjevning)",
|
||||||
|
|
||||||
|
"mt_loop": "spill den samme sangen om og om igjen\">🔁",
|
||||||
"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
|
"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
|
||||||
"mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URLen bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶",
|
"mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URLen bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶",
|
||||||
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
|
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
|
||||||
@@ -883,6 +902,7 @@ var Ls = {
|
|||||||
"mt_fau": "for telefoner: forhindre at avspilling stopper hvis nettet er for tregt til å laste neste sang i tide. Hvis påskrudd, kan forårsake at sang-info ikke vises korrekt i OS'et\">☕️",
|
"mt_fau": "for telefoner: forhindre at avspilling stopper hvis nettet er for tregt til å laste neste sang i tide. Hvis påskrudd, kan forårsake at sang-info ikke vises korrekt i OS'et\">☕️",
|
||||||
"mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s",
|
"mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s",
|
||||||
"mt_npclip": "vis knapper for å kopiere info om sangen du hører på\">/np",
|
"mt_npclip": "vis knapper for å kopiere info om sangen du hører på\">/np",
|
||||||
|
"mt_m3u_c": "vis knapper for å kopiere de valgte$Nsangene som innslag i en m3u8 spilleliste\">📻",
|
||||||
"mt_octl": "integrering med operativsystemet (fjernkontroll, info-skjerm)\">os-ctl",
|
"mt_octl": "integrering med operativsystemet (fjernkontroll, info-skjerm)\">os-ctl",
|
||||||
"mt_oseek": "tillat spoling med fjernkontroll$N$Nmerk: på noen enheter (iPhones) så vil$Ndette erstatte knappen for neste sang\">spoling",
|
"mt_oseek": "tillat spoling med fjernkontroll$N$Nmerk: på noen enheter (iPhones) så vil$Ndette erstatte knappen for neste sang\">spoling",
|
||||||
"mt_oscv": "vis album-cover på infoskjermen\">bilde",
|
"mt_oscv": "vis album-cover på infoskjermen\">bilde",
|
||||||
@@ -908,6 +928,7 @@ var Ls = {
|
|||||||
|
|
||||||
"mb_play": "lytt",
|
"mb_play": "lytt",
|
||||||
"mm_hashplay": "spill denne sangen?",
|
"mm_hashplay": "spill denne sangen?",
|
||||||
|
"mm_m3u": "trykk <code>Enter/OK</code> for å spille\ntrykk <code>ESC/Avbryt</code> for å redigere",
|
||||||
"mp_breq": "krever firefox 82+, chrome 73+, eller iOS 15+",
|
"mp_breq": "krever firefox 82+, chrome 73+, eller iOS 15+",
|
||||||
"mm_bload": "laster inn...",
|
"mm_bload": "laster inn...",
|
||||||
"mm_bconv": "konverterer til {0}, vent litt...",
|
"mm_bconv": "konverterer til {0}, vent litt...",
|
||||||
@@ -935,6 +956,7 @@ var Ls = {
|
|||||||
"f_bigtxt": "denne filen er hele {0} MiB -- vis som tekst?",
|
"f_bigtxt": "denne filen er hele {0} MiB -- vis som tekst?",
|
||||||
"fbd_more": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_more">vis {2}</a> eller <a href="#" id="bd_all">vis alle</a></div>',
|
"fbd_more": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_more">vis {2}</a> eller <a href="#" id="bd_all">vis alle</a></div>',
|
||||||
"fbd_all": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_all">vis alle</a></div>',
|
"fbd_all": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_all">vis alle</a></div>',
|
||||||
|
"f_anota": "kun {0} av totalt {1} elementer ble markert;\nfor å velge alt må du bla til bunnen av mappen først",
|
||||||
|
|
||||||
"f_dls": 'linkene i denne mappen er nå\nomgjort til nedlastningsknapper',
|
"f_dls": 'linkene i denne mappen er nå\nomgjort til nedlastningsknapper',
|
||||||
|
|
||||||
@@ -1037,6 +1059,10 @@ var Ls = {
|
|||||||
"tvt_sel": "markér filen ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
|
"tvt_sel": "markér filen ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
|
||||||
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
|
||||||
|
|
||||||
|
"m3u_add1": "sangen ble lagt til i m3u-spillelisten",
|
||||||
|
"m3u_addn": "{0} sanger ble lagt til i m3u-spillelisten",
|
||||||
|
"m3u_clip": "m3u-spillelisten ble kopiert til utklippstavlen\n\nneste steg er å opprette et tekstdokument med filnavn som slutter på <code>.m3u</code> og lime inn spillelisten der",
|
||||||
|
|
||||||
"gt_vau": "ikke vis videofiler, bare spill lyden\">🎧",
|
"gt_vau": "ikke vis videofiler, bare spill lyden\">🎧",
|
||||||
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering",
|
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering",
|
||||||
"gt_crop": "beskjær ikonene så de passer bedre\">✂",
|
"gt_crop": "beskjær ikonene så de passer bedre\">✂",
|
||||||
@@ -1348,10 +1374,13 @@ var Ls = {
|
|||||||
"wt_pst": "粘贴之前剪切/复制的选择$N快捷键: ctrl-V",
|
"wt_pst": "粘贴之前剪切/复制的选择$N快捷键: ctrl-V",
|
||||||
"wt_selall": "选择所有文件$N快捷键: ctrl-A(当文件被聚焦时)",
|
"wt_selall": "选择所有文件$N快捷键: ctrl-A(当文件被聚焦时)",
|
||||||
"wt_selinv": "反转选择",
|
"wt_selinv": "反转选择",
|
||||||
|
"wt_zip1": "将此文件夹下载为归档文件", //m
|
||||||
"wt_selzip": "将选择下载为归档文件",
|
"wt_selzip": "将选择下载为归档文件",
|
||||||
"wt_seldl": "将选择下载为单独的文件$N快捷键: Y",
|
"wt_seldl": "将选择下载为单独的文件$N快捷键: Y",
|
||||||
"wt_npirc": "复制 IRC 格式的曲目信息",
|
"wt_npirc": "复制 IRC 格式的曲目信息",
|
||||||
"wt_nptxt": "复制纯文本格式的曲目信息",
|
"wt_nptxt": "复制纯文本格式的曲目信息",
|
||||||
|
"wt_m3ua": "添加到 m3u 播放列表(稍后点击 <code>📻copy</code>)", //m
|
||||||
|
"wt_m3uc": "复制 m3u 播放列表到剪贴板", //m
|
||||||
"wt_grid": "切换网格/列表视图$N快捷键: G",
|
"wt_grid": "切换网格/列表视图$N快捷键: G",
|
||||||
"wt_prev": "上一曲$N快捷键: J",
|
"wt_prev": "上一曲$N快捷键: J",
|
||||||
"wt_play": "播放/暂停$N快捷键: P",
|
"wt_play": "播放/暂停$N快捷键: P",
|
||||||
@@ -1454,6 +1483,8 @@ var Ls = {
|
|||||||
|
|
||||||
"cut_mt": "使用多线程加速文件哈希$N$N这使用 Web Worker 并且需要更多内存(额外最多 512 MiB)$N$N这使得 https 快 30%,http 快 4.5 倍\">mt",
|
"cut_mt": "使用多线程加速文件哈希$N$N这使用 Web Worker 并且需要更多内存(额外最多 512 MiB)$N$N这使得 https 快 30%,http 快 4.5 倍\">mt",
|
||||||
|
|
||||||
|
"cut_wasm": "使用基于 WASM 的哈希计算器代替浏览器内置的哈希功能;这可以提升在基于 Chrome 的浏览器上的速度,但会增加 CPU 使用率,而且许多旧版本的 Chrome 存在漏洞,启用此功能会导致浏览器占用所有内存并崩溃。\">wasm", //m
|
||||||
|
|
||||||
"cft_text": "网站图标文本(为空并刷新以禁用)",
|
"cft_text": "网站图标文本(为空并刷新以禁用)",
|
||||||
"cft_fg": "前景色",
|
"cft_fg": "前景色",
|
||||||
"cft_bg": "背景色",
|
"cft_bg": "背景色",
|
||||||
@@ -1479,6 +1510,7 @@ var Ls = {
|
|||||||
"ml_eq": "音频均衡器",
|
"ml_eq": "音频均衡器",
|
||||||
"ml_drc": "动态范围压缩器",
|
"ml_drc": "动态范围压缩器",
|
||||||
|
|
||||||
|
"mt_loop": "循环播放当前的歌曲\">🔁", //m
|
||||||
"mt_shuf": "在每个文件夹中随机播放歌曲\">🔀",
|
"mt_shuf": "在每个文件夹中随机播放歌曲\">🔀",
|
||||||
"mt_aplay": "如果链接中有歌曲 ID,则自动播放,禁用此选项将停止在播放音乐时更新页面 URL 中的歌曲 ID,以防止在设置丢失但 URL 保留时自动播放\">自动播放▶",
|
"mt_aplay": "如果链接中有歌曲 ID,则自动播放,禁用此选项将停止在播放音乐时更新页面 URL 中的歌曲 ID,以防止在设置丢失但 URL 保留时自动播放\">自动播放▶",
|
||||||
"mt_preload": "在歌曲快结束时开始加载下一首歌,以实现无缝播放\">预加载",
|
"mt_preload": "在歌曲快结束时开始加载下一首歌,以实现无缝播放\">预加载",
|
||||||
@@ -1487,6 +1519,7 @@ var Ls = {
|
|||||||
"mt_fau": "在手机上,如果下一首歌未能快速预加载,防止音乐停止(可能导致标签显示异常)\">☕️",
|
"mt_fau": "在手机上,如果下一首歌未能快速预加载,防止音乐停止(可能导致标签显示异常)\">☕️",
|
||||||
"mt_waves": "波形进度条:$N显示音频幅度\">进度条",
|
"mt_waves": "波形进度条:$N显示音频幅度\">进度条",
|
||||||
"mt_npclip": "显示当前播放歌曲的剪贴板按钮\">♪剪切板",
|
"mt_npclip": "显示当前播放歌曲的剪贴板按钮\">♪剪切板",
|
||||||
|
"mt_m3u_c": "显示按钮以将所选歌曲$N复制为 m3u8 播放列表条目\">📻", //m
|
||||||
"mt_octl": "操作系统集成(媒体快捷键 / OSD)\">OSD",
|
"mt_octl": "操作系统集成(媒体快捷键 / OSD)\">OSD",
|
||||||
"mt_oseek": "允许通过操作系统集成进行跳转$N$N注意:在某些设备(如 iPhone)上,$N这将替代下一首歌按钮\">seek",
|
"mt_oseek": "允许通过操作系统集成进行跳转$N$N注意:在某些设备(如 iPhone)上,$N这将替代下一首歌按钮\">seek",
|
||||||
"mt_oscv": "在 OSD 中显示专辑封面\">封面",
|
"mt_oscv": "在 OSD 中显示专辑封面\">封面",
|
||||||
@@ -1512,6 +1545,7 @@ var Ls = {
|
|||||||
|
|
||||||
"mb_play": "播放",
|
"mb_play": "播放",
|
||||||
"mm_hashplay": "播放这个音频文件?",
|
"mm_hashplay": "播放这个音频文件?",
|
||||||
|
"mm_m3u": "按 <code>Enter/确定</code> 播放\n按 <code>ESC/取消</code> 编辑", //m
|
||||||
"mp_breq": "需要 Firefox 82+ 或 Chrome 73+ 或 iOS 15+",
|
"mp_breq": "需要 Firefox 82+ 或 Chrome 73+ 或 iOS 15+",
|
||||||
"mm_bload": "正在加载...",
|
"mm_bload": "正在加载...",
|
||||||
"mm_bconv": "正在转换为 {0},请稍等...",
|
"mm_bconv": "正在转换为 {0},请稍等...",
|
||||||
@@ -1539,6 +1573,7 @@ var Ls = {
|
|||||||
"f_bigtxt": "这个文件大小为 {0} MiB -- 真的以文本形式查看?",
|
"f_bigtxt": "这个文件大小为 {0} MiB -- 真的以文本形式查看?",
|
||||||
"fbd_more": '<div id="blazy">显示 <code>{0}</code> 个文件中的 <code>{1}</code> 个;<a href="#" id="bd_more">显示 {2}</a> 或 <a href="#" id="bd_all">显示全部</a></div>',
|
"fbd_more": '<div id="blazy">显示 <code>{0}</code> 个文件中的 <code>{1}</code> 个;<a href="#" id="bd_more">显示 {2}</a> 或 <a href="#" id="bd_all">显示全部</a></div>',
|
||||||
"fbd_all": '<div id="blazy">显示 <code>{0}</code> 个文件中的 <code>{1}</code> 个;<a href="#" id="bd_all">显示全部</a></div>',
|
"fbd_all": '<div id="blazy">显示 <code>{0}</code> 个文件中的 <code>{1}</code> 个;<a href="#" id="bd_all">显示全部</a></div>',
|
||||||
|
"f_anota": "仅选择了 {0} 个项目,共 {1} 个;\n要选择整个文件夹,请先滚动到底部", //m
|
||||||
|
|
||||||
"f_dls": '当前文件夹中的文件链接已\n更改为下载链接',
|
"f_dls": '当前文件夹中的文件链接已\n更改为下载链接',
|
||||||
|
|
||||||
@@ -1572,7 +1607,7 @@ var Ls = {
|
|||||||
"fs_tsrc": "共享的文件或文件夹",
|
"fs_tsrc": "共享的文件或文件夹",
|
||||||
"fs_ppwd": "密码可选",
|
"fs_ppwd": "密码可选",
|
||||||
"fs_w8": "正在创建文件共享...",
|
"fs_w8": "正在创建文件共享...",
|
||||||
"fs_ok": "按 <code>Enter/OK</code> 复制到剪贴板\n按 <code>ESC/Cancel</code> 关闭",
|
"fs_ok": "按 <code>Enter/确定</code> 复制到剪贴板\n按 <code>ESC/取消</code> 关闭",
|
||||||
|
|
||||||
"frt_dec": "可能修复一些损坏的文件名\">url-decode",
|
"frt_dec": "可能修复一些损坏的文件名\">url-decode",
|
||||||
"frt_rst": "将修改后的文件名重置为原始文件名\">↺ 重置",
|
"frt_rst": "将修改后的文件名重置为原始文件名\">↺ 重置",
|
||||||
@@ -1641,6 +1676,10 @@ var Ls = {
|
|||||||
"tvt_sel": "选择文件 (用于剪切/删除/...)$N快捷键: S\">选择",
|
"tvt_sel": "选择文件 (用于剪切/删除/...)$N快捷键: S\">选择",
|
||||||
"tvt_edit": "在文本编辑器中打开文件$N快捷键: E\">✏️ 编辑",
|
"tvt_edit": "在文本编辑器中打开文件$N快捷键: E\">✏️ 编辑",
|
||||||
|
|
||||||
|
"m3u_add1": "歌曲已添加到 m3u 播放列表", //m
|
||||||
|
"m3u_addn": "已添加 {0} 首歌曲到 m3u 播放列表", //m
|
||||||
|
"m3u_clip": "m3u 播放列表已复制到剪贴板\n\n请创建一个以 <code>.m3u</code> 结尾的文本文件,\n并将播放列表粘贴到该文件中;\n这样就可以播放了", //m
|
||||||
|
|
||||||
"gt_vau": "不显示视频,仅播放音频\">🎧",
|
"gt_vau": "不显示视频,仅播放音频\">🎧",
|
||||||
"gt_msel": "启用文件选择;按住 ctrl 键点击文件以覆盖$N$N<em>当启用时:双击文件/文件夹以打开它</em>$N$N快捷键:S\">多选",
|
"gt_msel": "启用文件选择;按住 ctrl 键点击文件以覆盖$N$N<em>当启用时:双击文件/文件夹以打开它</em>$N$N快捷键:S\">多选",
|
||||||
"gt_crop": "中心裁剪缩略图\">裁剪",
|
"gt_crop": "中心裁剪缩略图\">裁剪",
|
||||||
@@ -1878,6 +1917,8 @@ ebi('widget').innerHTML = (
|
|||||||
' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
|
' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
|
||||||
' href="#" id="fcpy" tt="' + L.wt_cpy + '">⧉<span>copy</span></a><a' +
|
' href="#" id="fcpy" tt="' + L.wt_cpy + '">⧉<span>copy</span></a><a' +
|
||||||
' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
|
' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
|
||||||
|
'</span><span id="wzip1"><a' +
|
||||||
|
' href="#" id="zip1" tt="' + L.wt_zip1 + '">📦<span>zip</span></a>' +
|
||||||
'</span><span id="wzip"><a' +
|
'</span><span id="wzip"><a' +
|
||||||
' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
|
' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
|
||||||
' href="#" id="selinv" tt="' + L.wt_selinv + '">sel.<br />inv.</a><a' +
|
' href="#" id="selinv" tt="' + L.wt_selinv + '">sel.<br />inv.</a><a' +
|
||||||
@@ -1886,6 +1927,9 @@ ebi('widget').innerHTML = (
|
|||||||
'</span><span id="wnp"><a' +
|
'</span><span id="wnp"><a' +
|
||||||
' href="#" id="npirc" tt="' + L.wt_npirc + '">📋<span>irc</span></a><a' +
|
' href="#" id="npirc" tt="' + L.wt_npirc + '">📋<span>irc</span></a><a' +
|
||||||
' href="#" id="nptxt" tt="' + L.wt_nptxt + '">📋<span>txt</span></a>' +
|
' href="#" id="nptxt" tt="' + L.wt_nptxt + '">📋<span>txt</span></a>' +
|
||||||
|
'</span><span id="wm3u"><a' +
|
||||||
|
' href="#" id="m3ua" tt="' + L.wt_m3ua + '">📻<span>add</span></a><a' +
|
||||||
|
' href="#" id="m3uc" tt="' + L.wt_m3uc + '">📻<span>copy</span></a>' +
|
||||||
'</span><a' +
|
'</span><a' +
|
||||||
' href="#" id="wtgrid" tt="' + L.wt_grid + '">田</a><a' +
|
' href="#" id="wtgrid" tt="' + L.wt_grid + '">田</a><a' +
|
||||||
' href="#" id="wtico">♫</a>' +
|
' href="#" id="wtico">♫</a>' +
|
||||||
@@ -2052,6 +2096,7 @@ ebi('op_cfg').innerHTML = (
|
|||||||
' <a id="u2ts" class="tgl btn" href="#" tt="' + L.ut_u2ts + '</a>\n' +
|
' <a id="u2ts" class="tgl btn" href="#" tt="' + L.ut_u2ts + '</a>\n' +
|
||||||
' <a id="umod" class="tgl btn" href="#" tt="' + L.cut_umod + '</a>\n' +
|
' <a id="umod" class="tgl btn" href="#" tt="' + L.cut_umod + '</a>\n' +
|
||||||
' <a id="hashw" class="tgl btn" href="#" tt="' + L.cut_mt + '</a>\n' +
|
' <a id="hashw" class="tgl btn" href="#" tt="' + L.cut_mt + '</a>\n' +
|
||||||
|
' <a id="nosubtle" class="tgl btn" href="#" tt="' + L.cut_wasm + '</a>\n' +
|
||||||
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '</a>\n' +
|
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '</a>\n' +
|
||||||
' <a id="u2tdate" class="tgl btn ttb" href="#" tt="' + L.cut_datechk + '</a>\n' +
|
' <a id="u2tdate" class="tgl btn ttb" href="#" tt="' + L.cut_datechk + '</a>\n' +
|
||||||
' <input type="text" id="u2szg" value="" ' + NOAC + ' style="width:3em" tt="' + L.cut_u2sz + '" />' +
|
' <input type="text" id="u2szg" value="" ' + NOAC + ' style="width:3em" tt="' + L.cut_u2sz + '" />' +
|
||||||
@@ -2292,6 +2337,7 @@ var mpl = (function () {
|
|||||||
|
|
||||||
ebi('op_player').innerHTML = (
|
ebi('op_player').innerHTML = (
|
||||||
'<div><h3>' + L.cl_opts + '</h3><div>' +
|
'<div><h3>' + L.cl_opts + '</h3><div>' +
|
||||||
|
'<a href="#" class="tgl btn" id="au_loop" tt="' + L.mt_loop + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
|
'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_aplay" tt="' + L.mt_aplay + '</a>' +
|
'<a href="#" class="tgl btn" id="au_aplay" tt="' + L.mt_aplay + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
|
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
|
||||||
@@ -2300,6 +2346,7 @@ var mpl = (function () {
|
|||||||
'<a href="#" class="tgl btn" id="au_fau" tt="' + L.mt_fau + '</a>' +
|
'<a href="#" class="tgl btn" id="au_fau" tt="' + L.mt_fau + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
|
'<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
|
'<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
|
||||||
|
'<a href="#" class="tgl btn" id="au_m3u_c" tt="' + L.mt_m3u_c + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_os_ctl" tt="' + L.mt_octl + '</a>' +
|
'<a href="#" class="tgl btn" id="au_os_ctl" tt="' + L.mt_octl + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_os_seek" tt="' + L.mt_oseek + '</a>' +
|
'<a href="#" class="tgl btn" id="au_os_seek" tt="' + L.mt_oseek + '</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_osd_cv" tt="' + L.mt_oscv + '</a>' +
|
'<a href="#" class="tgl btn" id="au_osd_cv" tt="' + L.mt_oscv + '</a>' +
|
||||||
@@ -2342,7 +2389,12 @@ var mpl = (function () {
|
|||||||
"pb_mode": (sread('pb_mode', ['loop', 'next']) || 'next').split('-')[0],
|
"pb_mode": (sread('pb_mode', ['loop', 'next']) || 'next').split('-')[0],
|
||||||
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
||||||
'traversals': 0,
|
'traversals': 0,
|
||||||
|
'm3ut': '#EXTM3U\n',
|
||||||
};
|
};
|
||||||
|
bcfg_bind(r, 'loop', 'au_loop', false, function (v) {
|
||||||
|
if (mp.au)
|
||||||
|
mp.au.loop = v;
|
||||||
|
});
|
||||||
bcfg_bind(r, 'shuf', 'au_shuf', false, function () {
|
bcfg_bind(r, 'shuf', 'au_shuf', false, function () {
|
||||||
mp.read_order(); // don't bind
|
mp.read_order(); // don't bind
|
||||||
});
|
});
|
||||||
@@ -2367,6 +2419,9 @@ var mpl = (function () {
|
|||||||
bcfg_bind(r, 'clip', 'au_npclip', false, function (v) {
|
bcfg_bind(r, 'clip', 'au_npclip', false, function (v) {
|
||||||
clmod(ebi('wtoggle'), 'np', v && mp.au);
|
clmod(ebi('wtoggle'), 'np', v && mp.au);
|
||||||
});
|
});
|
||||||
|
bcfg_bind(r, 'm3uen', 'au_m3u_c', false, function (v) {
|
||||||
|
clmod(ebi('wtoggle'), 'm3u', v && (mp.au || msel.getsel().length));
|
||||||
|
});
|
||||||
bcfg_bind(r, 'follow', 'au_follow', false, setaufollow);
|
bcfg_bind(r, 'follow', 'au_follow', false, setaufollow);
|
||||||
bcfg_bind(r, 'ac_flac', 'ac_flac', true);
|
bcfg_bind(r, 'ac_flac', 'ac_flac', true);
|
||||||
bcfg_bind(r, 'ac_aac', 'ac_aac', false);
|
bcfg_bind(r, 'ac_aac', 'ac_aac', false);
|
||||||
@@ -2434,7 +2489,7 @@ var mpl = (function () {
|
|||||||
c = r.ac_flac;
|
c = r.ac_flac;
|
||||||
else if (/\.(aac|m4a)$/i.exec(cs))
|
else if (/\.(aac|m4a)$/i.exec(cs))
|
||||||
c = r.ac_aac;
|
c = r.ac_aac;
|
||||||
else if (/\.(ogg|opus)$/i.exec(cs) && (!can_ogg || mpl.ac2 == 'mp3'))
|
else if (/\.(oga|ogg|opus)$/i.exec(cs) && (!can_ogg || mpl.ac2 == 'mp3'))
|
||||||
c = true;
|
c = true;
|
||||||
else if (re_au_native.exec(cs))
|
else if (re_au_native.exec(cs))
|
||||||
c = false;
|
c = false;
|
||||||
@@ -2555,7 +2610,7 @@ var mpl = (function () {
|
|||||||
ebi('np_artist').textContent = np.artist || (fns.length > 1 ? fns[0] : '');
|
ebi('np_artist').textContent = np.artist || (fns.length > 1 ? fns[0] : '');
|
||||||
ebi('np_title').textContent = np.title || '';
|
ebi('np_title').textContent = np.title || '';
|
||||||
ebi('np_dur').textContent = np['.dur'] || '';
|
ebi('np_dur').textContent = np['.dur'] || '';
|
||||||
ebi('np_url').textContent = get_vpath() + np.file.split('?')[0];
|
ebi('np_url').textContent = uricom_dec(get_evpath()) + np.file.split('?')[0];
|
||||||
if (!MOBILE && cover)
|
if (!MOBILE && cover)
|
||||||
ebi('np_img').setAttribute('src', cover);
|
ebi('np_img').setAttribute('src', cover);
|
||||||
else
|
else
|
||||||
@@ -2620,8 +2675,9 @@ if (can_owa && APPLE && / OS ([1-9]|1[0-7])_/.test(UA))
|
|||||||
mpl.init_ac2();
|
mpl.init_ac2();
|
||||||
|
|
||||||
|
|
||||||
var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i,
|
var re_m3u = /\.(m3u8?)$/i;
|
||||||
re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4a|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i;
|
var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|oga|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i,
|
||||||
|
re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4a|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|oga|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i;
|
||||||
|
|
||||||
|
|
||||||
// extract songs + add play column
|
// extract songs + add play column
|
||||||
@@ -2646,9 +2702,9 @@ function MPlayer() {
|
|||||||
|
|
||||||
link = link[link.length - 1];
|
link = link[link.length - 1];
|
||||||
var url = link.getAttribute('href'),
|
var url = link.getAttribute('href'),
|
||||||
m = re_audio.exec(url.split('?')[0]);
|
fn = url.split('?')[0];
|
||||||
|
|
||||||
if (m) {
|
if (re_audio.exec(fn)) {
|
||||||
var tid = link.getAttribute('id');
|
var tid = link.getAttribute('id');
|
||||||
r.order.push(tid);
|
r.order.push(tid);
|
||||||
r.tracks[tid] = url;
|
r.tracks[tid] = url;
|
||||||
@@ -2656,6 +2712,11 @@ function MPlayer() {
|
|||||||
ebi('a' + tid).onclick = ev_play;
|
ebi('a' + tid).onclick = ev_play;
|
||||||
clmod(trs[a], 'au', 1);
|
clmod(trs[a], 'au', 1);
|
||||||
}
|
}
|
||||||
|
else if (re_m3u.exec(fn)) {
|
||||||
|
var tid = link.getAttribute('id');
|
||||||
|
tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">' + L.mb_play + '</a></td>';
|
||||||
|
ebi('a' + tid).onclick = ev_load_m3u;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.vol = clamp(fcfg_get('vol', IPHONE ? 1 : dvol / 100), 0, 1);
|
r.vol = clamp(fcfg_get('vol', IPHONE ? 1 : dvol / 100), 0, 1);
|
||||||
@@ -2821,6 +2882,14 @@ function MPlayer() {
|
|||||||
r.fau.loop = true;
|
r.fau.loop = true;
|
||||||
r.fau.play();
|
r.fau.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
r.set_ev = function () {
|
||||||
|
mp.au.onended = evau_end;
|
||||||
|
mp.au.onerror = evau_error;
|
||||||
|
mp.au.onprogress = pbar.drawpos;
|
||||||
|
mp.au.onplaying = mpui.progress_updater;
|
||||||
|
mp.au.onloadeddata = mp.au.onloadedmetadata = mp.nopause;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2862,6 +2931,8 @@ var widget = (function () {
|
|||||||
wtico = ebi('wtico'),
|
wtico = ebi('wtico'),
|
||||||
nptxt = ebi('nptxt'),
|
nptxt = ebi('nptxt'),
|
||||||
npirc = ebi('npirc'),
|
npirc = ebi('npirc'),
|
||||||
|
m3ua = ebi('m3ua'),
|
||||||
|
m3uc = ebi('m3uc'),
|
||||||
touchmode = false,
|
touchmode = false,
|
||||||
was_paused = true;
|
was_paused = true;
|
||||||
|
|
||||||
@@ -2920,6 +2991,49 @@ var widget = (function () {
|
|||||||
toast.ok(1, L.clipped, null, 'top');
|
toast.ok(1, L.clipped, null, 'top');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
m3ua.onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
var el,
|
||||||
|
files = [],
|
||||||
|
sel = msel.getsel();
|
||||||
|
|
||||||
|
for (var a = 0; a < sel.length; a++) {
|
||||||
|
el = ebi(sel[a].id).closest('tr');
|
||||||
|
if (clgot(el, 'au'))
|
||||||
|
files.push(el);
|
||||||
|
}
|
||||||
|
el = QS('#files tr.play');
|
||||||
|
if (!sel.length && el)
|
||||||
|
files.push(el);
|
||||||
|
|
||||||
|
for (var a = 0; a < files.length; a++) {
|
||||||
|
var md = ft2dict(files[a])[0],
|
||||||
|
dur = md['.dur'] || '1',
|
||||||
|
tag = '';
|
||||||
|
|
||||||
|
if (md.artist && md.title)
|
||||||
|
tag = md.artist + ' - ' + md.title;
|
||||||
|
else if (md.artist)
|
||||||
|
tag = md.artist + ' - ' + md.file;
|
||||||
|
else if (md.title)
|
||||||
|
tag = md.title;
|
||||||
|
|
||||||
|
if (dur.indexOf(':') > 0) {
|
||||||
|
dur = dur.split(':');
|
||||||
|
dur = 60 * parseInt(dur[0]) + parseInt(dur[1]);
|
||||||
|
}
|
||||||
|
else dur = parseInt(dur);
|
||||||
|
|
||||||
|
mpl.m3ut += '#EXTINF:' + dur + ',' + tag + '\n' + uricom_dec(get_evpath()) + md.file + '\n';
|
||||||
|
}
|
||||||
|
toast.ok(2, files.length == 1 ? L.m3u_add1 : L.m3u_addn.format(files.length), null, 'top');
|
||||||
|
};
|
||||||
|
m3uc.onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
cliptxt(mpl.m3ut, function () {
|
||||||
|
toast.ok(15, L.m3u_clip, null, 'top');
|
||||||
|
});
|
||||||
|
};
|
||||||
r.set(sread('au_open') == 1);
|
r.set(sread('au_open') == 1);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
clmod(widget, 'anim', 1);
|
clmod(widget, 'anim', 1);
|
||||||
@@ -4058,10 +4172,7 @@ function play(tid, is_ev, seek) {
|
|||||||
else {
|
else {
|
||||||
mp.au = new Audio();
|
mp.au = new Audio();
|
||||||
mp.au2 = new Audio();
|
mp.au2 = new Audio();
|
||||||
mp.au.onerror = evau_error;
|
mp.set_ev();
|
||||||
mp.au.onprogress = pbar.drawpos;
|
|
||||||
mp.au.onplaying = mpui.progress_updater;
|
|
||||||
mp.au.onended = next_song;
|
|
||||||
widget.open();
|
widget.open();
|
||||||
}
|
}
|
||||||
mp.init_fau();
|
mp.init_fau();
|
||||||
@@ -4074,13 +4185,9 @@ function play(tid, is_ev, seek) {
|
|||||||
var t = mp.au;
|
var t = mp.au;
|
||||||
mp.au = mp.au2;
|
mp.au = mp.au2;
|
||||||
mp.au2 = t;
|
mp.au2 = t;
|
||||||
t.onerror = t.onprogress = t.onended = null;
|
t.onerror = t.onprogress = t.onended = t.loop = null;
|
||||||
t.ld = 0; //owa
|
t.ld = 0; //owa
|
||||||
mp.au.onerror = evau_error;
|
mp.set_ev();
|
||||||
mp.au.onprogress = pbar.drawpos;
|
|
||||||
mp.au.onplaying = mpui.progress_updater;
|
|
||||||
mp.au.onloadeddata = mp.au.onloadedmetadata = mp.nopause;
|
|
||||||
mp.au.onended = next_song;
|
|
||||||
t = mp.au.currentTime;
|
t = mp.au.currentTime;
|
||||||
if (isNum(t) && t > 0.1)
|
if (isNum(t) && t > 0.1)
|
||||||
mp.au.currentTime = 0;
|
mp.au.currentTime = 0;
|
||||||
@@ -4110,6 +4217,7 @@ function play(tid, is_ev, seek) {
|
|||||||
clmod(ebi(oid), 'act', 1);
|
clmod(ebi(oid), 'act', 1);
|
||||||
clmod(ebi(oid).closest('tr'), 'play', 1);
|
clmod(ebi(oid).closest('tr'), 'play', 1);
|
||||||
clmod(ebi('wtoggle'), 'np', mpl.clip);
|
clmod(ebi('wtoggle'), 'np', mpl.clip);
|
||||||
|
clmod(ebi('wtoggle'), 'm3u', mpl.m3uen);
|
||||||
if (thegrid)
|
if (thegrid)
|
||||||
thegrid.loadsel();
|
thegrid.loadsel();
|
||||||
|
|
||||||
@@ -4118,6 +4226,7 @@ function play(tid, is_ev, seek) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
mp.nopause();
|
mp.nopause();
|
||||||
|
mp.au.loop = mpl.loop;
|
||||||
if (mpl.aplay || is_ev !== -1)
|
if (mpl.aplay || is_ev !== -1)
|
||||||
mp.au.play();
|
mp.au.play();
|
||||||
|
|
||||||
@@ -4162,6 +4271,15 @@ function scroll2playing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function evau_end(e) {
|
||||||
|
if (!mpl.loop)
|
||||||
|
return next_song(e);
|
||||||
|
ev(e);
|
||||||
|
mp.au.currentTime = 0;
|
||||||
|
mp.au.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// event from the audio object if something breaks
|
// event from the audio object if something breaks
|
||||||
function evau_error(e) {
|
function evau_error(e) {
|
||||||
var err = '',
|
var err = '',
|
||||||
@@ -4374,6 +4492,11 @@ function eval_hash() {
|
|||||||
goto(v.slice(3));
|
goto(v.slice(3));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (v.startsWith("#m3u=")) {
|
||||||
|
load_m3u(v.slice(5));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -4433,7 +4556,8 @@ function eval_hash() {
|
|||||||
|
|
||||||
function read_dsort(txt) {
|
function read_dsort(txt) {
|
||||||
dnsort = dnsort ? 1 : 0;
|
dnsort = dnsort ? 1 : 0;
|
||||||
clmod(ebi('nsort'), 'on', (sread('nsort') || dnsort) == 1);
|
ENATSORT = NATSORT && (sread('nsort') || dnsort) == 1;
|
||||||
|
clmod(ebi('nsort'), 'on', ENATSORT);
|
||||||
try {
|
try {
|
||||||
var zt = (('' + txt).trim() || 'href').split(/,+/g);
|
var zt = (('' + txt).trim() || 'href').split(/,+/g);
|
||||||
dsort = [];
|
dsort = [];
|
||||||
@@ -4479,9 +4603,6 @@ function sortfiles(nodes) {
|
|||||||
|
|
||||||
sopts = sopts && sopts.length ? sopts : jcp(dsort);
|
sopts = sopts && sopts.length ? sopts : jcp(dsort);
|
||||||
|
|
||||||
var collator = !clgot(ebi('nsort'), 'on') ? null :
|
|
||||||
new Intl.Collator([], {numeric: true});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var is_srch = false;
|
var is_srch = false;
|
||||||
if (nodes[0]['rp']) {
|
if (nodes[0]['rp']) {
|
||||||
@@ -4533,8 +4654,9 @@ function sortfiles(nodes) {
|
|||||||
}
|
}
|
||||||
if (v2 === undefined) return 1 * rev;
|
if (v2 === undefined) return 1 * rev;
|
||||||
|
|
||||||
var ret = rev * (typ == 'int' ? (v1 - v2) : collator ?
|
var ret = rev * (typ == 'int' ? (v1 - v2) :
|
||||||
collator.compare(v1, v2) : v1.localeCompare(v2));
|
ENATSORT ? NATSORT.compare(v1, v2) :
|
||||||
|
v1.localeCompare(v2));
|
||||||
|
|
||||||
if (ret === 0)
|
if (ret === 0)
|
||||||
ret = onodes.indexOf(n1) - onodes.indexOf(n2);
|
ret = onodes.indexOf(n1) - onodes.indexOf(n2);
|
||||||
@@ -4716,6 +4838,7 @@ var fileman = (function () {
|
|||||||
clmod(bshr, 'hide', hshr);
|
clmod(bshr, 'hide', hshr);
|
||||||
|
|
||||||
clmod(ebi('wfm'), 'act', QS('#wfm a.en:not(.hide)'));
|
clmod(ebi('wfm'), 'act', QS('#wfm a.en:not(.hide)'));
|
||||||
|
clmod(ebi('wtoggle'), 'm3u', mpl.m3uen && (nsel || (mp && mp.au)));
|
||||||
|
|
||||||
var wfs = ebi('wfs'), h = '';
|
var wfs = ebi('wfs'), h = '';
|
||||||
try {
|
try {
|
||||||
@@ -5972,7 +6095,8 @@ var showfile = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.mktree = function () {
|
r.mktree = function () {
|
||||||
var html = ['<li class="bn">' + L.tv_lst + '<br />' + linksplit(get_vpath()).join('<span>/</span>') + '</li>'];
|
var crumbs = linksplit(get_evpath()).join('<span>/</span>'),
|
||||||
|
html = ['<li class="bn">' + L.tv_lst + '<br />' + crumbs + '</li>'];
|
||||||
for (var a = 0; a < r.files.length; a++) {
|
for (var a = 0; a < r.files.length; a++) {
|
||||||
var file = r.files[a];
|
var file = r.files[a];
|
||||||
html.push('<li><a href="?doc=' +
|
html.push('<li><a href="?doc=' +
|
||||||
@@ -6547,8 +6671,8 @@ function tree_scrolltoo(q) {
|
|||||||
var ctr = ebi('tree'),
|
var ctr = ebi('tree'),
|
||||||
em = parseFloat(getComputedStyle(act).fontSize),
|
em = parseFloat(getComputedStyle(act).fontSize),
|
||||||
top = act.offsetTop + ul.offsetTop,
|
top = act.offsetTop + ul.offsetTop,
|
||||||
min = top - 11 * em,
|
min = top - 20 * em,
|
||||||
max = top - (ctr.offsetHeight - 10 * em);
|
max = top - (ctr.offsetHeight - 16 * em);
|
||||||
|
|
||||||
if (ctr.scrollTop > min)
|
if (ctr.scrollTop > min)
|
||||||
ctr.scrollTop = Math.floor(min);
|
ctr.scrollTop = Math.floor(min);
|
||||||
@@ -6719,7 +6843,8 @@ var ahotkeys = function (e) {
|
|||||||
return ebi('griden').click();
|
return ebi('griden').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((aet == 'tr' || aet == 'td') && ae.closest('#files')) {
|
var in_ftab = (aet == 'tr' || aet == 'td') && ae.closest('#files');
|
||||||
|
if (in_ftab) {
|
||||||
var d = '', rem = 0;
|
var d = '', rem = 0;
|
||||||
if (aet == 'td') ae = ae.closest('tr'); //ie11
|
if (aet == 'td') ae = ae.closest('tr'); //ie11
|
||||||
if (k == 'ArrowUp' || k == 'Up') d = 'previous';
|
if (k == 'ArrowUp' || k == 'Up') d = 'previous';
|
||||||
@@ -6736,12 +6861,19 @@ var ahotkeys = function (e) {
|
|||||||
msel.selui();
|
msel.selui();
|
||||||
return ev(e);
|
return ev(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (in_ftab || !aet || (ae && ae.closest('#ggrid'))) {
|
||||||
if ((k == 'KeyA' || k == 'a') && ctrl(e)) {
|
if ((k == 'KeyA' || k == 'a') && ctrl(e)) {
|
||||||
var sel = msel.getsel(),
|
var ntot = treectl.lsc.files.length + treectl.lsc.dirs.length,
|
||||||
|
sel = msel.getsel(),
|
||||||
all = msel.getall();
|
all = msel.getall();
|
||||||
|
|
||||||
msel.evsel(e, sel.length < all.length);
|
msel.evsel(e, sel.length < all.length);
|
||||||
msel.origin_id(null);
|
msel.origin_id(null);
|
||||||
|
if (ntot > all.length)
|
||||||
|
toast.warn(10, L.f_anota.format(all.length, ntot), L.f_anota);
|
||||||
|
else if (toast.tag == L.f_anota)
|
||||||
|
toast.hide();
|
||||||
return ev(e);
|
return ev(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6871,7 +7003,7 @@ var ahotkeys = function (e) {
|
|||||||
|
|
||||||
|
|
||||||
// search
|
// search
|
||||||
(function () {
|
var search_ui = (function () {
|
||||||
var sconf = [
|
var sconf = [
|
||||||
[
|
[
|
||||||
L.s_sz,
|
L.s_sz,
|
||||||
@@ -6906,7 +7038,8 @@ var ahotkeys = function (e) {
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
var trs = [],
|
var r = {},
|
||||||
|
trs = [],
|
||||||
orig_url = null,
|
orig_url = null,
|
||||||
orig_html = null,
|
orig_html = null,
|
||||||
cap = 125;
|
cap = 125;
|
||||||
@@ -7113,13 +7246,19 @@ var ahotkeys = function (e) {
|
|||||||
search_in_progress = 0;
|
search_in_progress = 0;
|
||||||
srch_msg(false, '');
|
srch_msg(false, '');
|
||||||
|
|
||||||
var res = JSON.parse(this.responseText),
|
var res = JSON.parse(this.responseText);
|
||||||
tagord = res.tag_order;
|
r.render(res, this, true);
|
||||||
|
}
|
||||||
|
|
||||||
sortfiles(res.hits);
|
r.render = function (res, xhr, sort) {
|
||||||
|
var tagord = res.tag_order;
|
||||||
|
|
||||||
|
srch_msg(false, '');
|
||||||
|
if (sort)
|
||||||
|
sortfiles(res.hits);
|
||||||
|
|
||||||
var ofiles = ebi('files');
|
var ofiles = ebi('files');
|
||||||
if (ofiles.getAttribute('ts') > this.ts)
|
if (xhr && ofiles.getAttribute('ts') > xhr.ts)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
treectl.hide();
|
treectl.hide();
|
||||||
@@ -7171,19 +7310,21 @@ var ahotkeys = function (e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ofiles = set_files_html(html.join('\n'));
|
ofiles = set_files_html(html.join('\n'));
|
||||||
ofiles.setAttribute("ts", this.ts);
|
ofiles.setAttribute("ts", xhr ? xhr.ts : 1);
|
||||||
ofiles.setAttribute("q_raw", this.q_raw);
|
ofiles.setAttribute("q_raw", xhr ? xhr.q_raw : 'playlist');
|
||||||
set_vq();
|
set_vq();
|
||||||
mukey.render();
|
mukey.render();
|
||||||
reload_browser();
|
reload_browser();
|
||||||
filecols.set_style(['File Name']);
|
filecols.set_style(['File Name']);
|
||||||
|
|
||||||
sethash('q=' + uricom_enc(this.q_raw));
|
if (xhr)
|
||||||
|
sethash('q=' + uricom_enc(xhr.q_raw));
|
||||||
|
|
||||||
ebi('unsearch').onclick = unsearch;
|
ebi('unsearch').onclick = unsearch;
|
||||||
var m = ebi('moar');
|
var m = ebi('moar');
|
||||||
if (m)
|
if (m)
|
||||||
m.onclick = moar;
|
m.onclick = moar;
|
||||||
}
|
};
|
||||||
|
|
||||||
function unsearch(e) {
|
function unsearch(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
@@ -7200,9 +7341,98 @@ var ahotkeys = function (e) {
|
|||||||
cap *= 2;
|
cap *= 2;
|
||||||
do_search();
|
do_search();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function ev_load_m3u(e) {
|
||||||
|
ev(e);
|
||||||
|
var id = this.getAttribute('id').slice(1),
|
||||||
|
url = ebi(id).getAttribute('href').split('?')[0];
|
||||||
|
|
||||||
|
modal.confirm(L.mm_m3u,
|
||||||
|
function () { load_m3u(url); },
|
||||||
|
function () {
|
||||||
|
if (has(perms, 'write') && has(perms, 'delete'))
|
||||||
|
window.location = url + '?edit';
|
||||||
|
else
|
||||||
|
showfile.show(url);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function load_m3u(url) {
|
||||||
|
var xhr = new XHR();
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.onload = render_m3u;
|
||||||
|
xhr.url = url;
|
||||||
|
xhr.send();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function render_m3u() {
|
||||||
|
if (!xhrchk(this, L.tv_xe1, L.tv_xe2))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var evp = get_evpath(),
|
||||||
|
m3u = this.responseText,
|
||||||
|
xtd = m3u.slice(0, 12).indexOf('#EXTM3U') + 1,
|
||||||
|
lines = m3u.replace(/\r/g, '\n').split('\n'),
|
||||||
|
dur = 1,
|
||||||
|
artist = '',
|
||||||
|
title = '',
|
||||||
|
ret = {'hits': [], 'tag_order': ['artist', 'title', '.dur'], 'trunc': false};
|
||||||
|
|
||||||
|
for (var a = 0; a < lines.length; a++) {
|
||||||
|
var ln = lines[a].trim();
|
||||||
|
if (xtd && ln.startsWith('#')) {
|
||||||
|
var m = /^#EXTINF:([0-9]+)[, ](.*)/.exec(ln);
|
||||||
|
if (m) {
|
||||||
|
dur = m[1];
|
||||||
|
title = m[2];
|
||||||
|
var ofs = title.indexOf(' - ');
|
||||||
|
if (ofs > 0) {
|
||||||
|
artist = title.slice(0, ofs);
|
||||||
|
title = title.slice(ofs + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ln.indexOf('.') < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var n = ret.hits.length + 1,
|
||||||
|
url = ln;
|
||||||
|
|
||||||
|
if (url.indexOf(':\\')) // C:\
|
||||||
|
url = url.split(/\\/g).pop();
|
||||||
|
|
||||||
|
url = url.replace(/\\/g, '/');
|
||||||
|
url = uricom_enc(url).replace(/%2f/gi, '/')
|
||||||
|
|
||||||
|
if (!url.startsWith('/'))
|
||||||
|
url = vjoin(evp, url);
|
||||||
|
|
||||||
|
ret.hits.push({
|
||||||
|
"ts": 946684800 + n,
|
||||||
|
"sz": 100000 + n,
|
||||||
|
"rp": url,
|
||||||
|
"tags": {".dur": dur, "artist": artist, "title": title}
|
||||||
|
});
|
||||||
|
dur = 1;
|
||||||
|
artist = title = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
search_ui.render(ret, null, false);
|
||||||
|
sethash('m3u=' + this.url.split('?')[0].split('/').pop());
|
||||||
|
goto();
|
||||||
|
|
||||||
|
var el = QS('#files>tbody>tr.au>td>a.play');
|
||||||
|
if (el)
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function aligngriditems() {
|
function aligngriditems() {
|
||||||
if (!treectl)
|
if (!treectl)
|
||||||
return;
|
return;
|
||||||
@@ -7267,6 +7497,7 @@ var treectl = (function () {
|
|||||||
treesz = clamp(icfg_get('treesz', 16), 10, 50);
|
treesz = clamp(icfg_get('treesz', 16), 10, 50);
|
||||||
|
|
||||||
var resort = function () {
|
var resort = function () {
|
||||||
|
ENATSORT = NATSORT && clgot(ebi('nsort'), 'on');
|
||||||
treectl.gentab(get_evpath(), treectl.lsc);
|
treectl.gentab(get_evpath(), treectl.lsc);
|
||||||
};
|
};
|
||||||
bcfg_bind(r, 'ireadme', 'ireadme', true);
|
bcfg_bind(r, 'ireadme', 'ireadme', true);
|
||||||
@@ -7595,8 +7826,8 @@ var treectl = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function reload_tree() {
|
function reload_tree() {
|
||||||
var cdir = r.nextdir || get_vpath(),
|
var cevp = get_evpath(),
|
||||||
cevp = get_evpath(),
|
cdir = r.nextdir || uricom_dec(cevp),
|
||||||
links = QSA('#treeul a+a'),
|
links = QSA('#treeul a+a'),
|
||||||
nowrap = QS('#tree.nowrap') && QS('#hovertree.on'),
|
nowrap = QS('#tree.nowrap') && QS('#hovertree.on'),
|
||||||
act = null;
|
act = null;
|
||||||
@@ -8103,7 +8334,7 @@ var treectl = (function () {
|
|||||||
document.documentElement.scrollLeft = 0;
|
document.documentElement.scrollLeft = 0;
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
r.gentab(get_evpath(), r.lsc);
|
r.gentab(get_evpath(), r.lsc);
|
||||||
ebi('wrap').style.opacity = 'unset';
|
ebi('wrap').style.opacity = CLOSEST ? 'unset' : 1;
|
||||||
}, 1);
|
}, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8139,9 +8370,16 @@ var treectl = (function () {
|
|||||||
}
|
}
|
||||||
delete res['a'];
|
delete res['a'];
|
||||||
var keys = Object.keys(res);
|
var keys = Object.keys(res);
|
||||||
keys.sort(function (a, b) { return a.localeCompare(b); });
|
for (var a = 0; a < keys.length; a++)
|
||||||
|
keys[a] = [uricom_dec(keys[a]), keys[a]];
|
||||||
|
|
||||||
|
if (ENATSORT)
|
||||||
|
keys.sort(function (a, b) { return NATSORT.compare(a[0], b[0]); });
|
||||||
|
else
|
||||||
|
keys.sort(function (a, b) { return a[0].localeCompare(b[0]); });
|
||||||
|
|
||||||
for (var a = 0; a < keys.length; a++) {
|
for (var a = 0; a < keys.length; a++) {
|
||||||
var kk = keys[a],
|
var kk = keys[a][1],
|
||||||
m = /(\?k=[^\n]+)/.exec(kk),
|
m = /(\?k=[^\n]+)/.exec(kk),
|
||||||
kdk = m ? m[1] : '',
|
kdk = m ? m[1] : '',
|
||||||
ks = kk.replace(kdk, '').slice(1),
|
ks = kk.replace(kdk, '').slice(1),
|
||||||
@@ -8249,7 +8487,7 @@ var wfp_debounce = (function () {
|
|||||||
if (--r.n <= 0) {
|
if (--r.n <= 0) {
|
||||||
r.n = 0;
|
r.n = 0;
|
||||||
clearTimeout(r.t);
|
clearTimeout(r.t);
|
||||||
ebi('wfp').style.opacity = 'unset';
|
ebi('wfp').style.opacity = CLOSEST ? 'unset' : 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
r.reset = function () {
|
r.reset = function () {
|
||||||
@@ -8856,6 +9094,15 @@ var arcfmt = (function () {
|
|||||||
}
|
}
|
||||||
ebi('selzip').textContent = fmt.split('_')[0];
|
ebi('selzip').textContent = fmt.split('_')[0];
|
||||||
ebi('selzip').setAttribute('fmt', arg);
|
ebi('selzip').setAttribute('fmt', arg);
|
||||||
|
|
||||||
|
QS('#zip1 span').textContent = fmt.split('_')[0];
|
||||||
|
ebi('zip1').setAttribute("href",
|
||||||
|
get_evpath() + (dk ? '?k=' + dk + '&': '?') + arg);
|
||||||
|
|
||||||
|
if (!have_zip) {
|
||||||
|
ebi('zip1').style.display = 'none';
|
||||||
|
ebi('selzip').style.display = 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function try_render() {
|
function try_render() {
|
||||||
@@ -9094,7 +9341,10 @@ var msel = (function () {
|
|||||||
r.selui(true);
|
r.selui(true);
|
||||||
arcfmt.render();
|
arcfmt.render();
|
||||||
fileman.render();
|
fileman.render();
|
||||||
ebi('selzip').style.display = is_srch ? 'none' : '';
|
|
||||||
|
var zipvis = (is_srch || !have_zip) ? 'none' : '';
|
||||||
|
ebi('selzip').style.display = zipvis;
|
||||||
|
ebi('zip1').style.display = zipvis;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
@@ -9420,6 +9670,11 @@ function sandbox(tgt, rules, allow, cls, html) {
|
|||||||
clmod(tgt, 'sb');
|
clmod(tgt, 'sb');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!CLOSEST) {
|
||||||
|
tgt.textContent = html;
|
||||||
|
clmod(tgt, 'sb');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
clmod(tgt, 'sb', 1);
|
clmod(tgt, 'sb', 1);
|
||||||
|
|
||||||
var tid = tgt.getAttribute('id'),
|
var tid = tgt.getAttribute('id'),
|
||||||
@@ -9487,7 +9742,7 @@ window.addEventListener("message", function (e) {
|
|||||||
el.parentNode.removeChild(el.previousSibling);
|
el.parentNode.removeChild(el.previousSibling);
|
||||||
|
|
||||||
el.style.height = (parseInt(t[2]) + SBH) + 'px';
|
el.style.height = (parseInt(t[2]) + SBH) + 'px';
|
||||||
el.style.visibility = 'unset';
|
el.style.visibility = CLOSEST ? 'unset' : 'block';
|
||||||
wfp_debounce.show();
|
wfp_debounce.show();
|
||||||
}
|
}
|
||||||
else if (t[0] == 'iscroll') {
|
else if (t[0] == 'iscroll') {
|
||||||
@@ -9781,7 +10036,7 @@ function wintitle(txt, noname) {
|
|||||||
if (s_name && !noname)
|
if (s_name && !noname)
|
||||||
txt = s_name + ' ' + txt;
|
txt = s_name + ' ' + txt;
|
||||||
|
|
||||||
txt += get_vpath().slice(1, -1).split('/').pop();
|
txt += uricom_dec(get_evpath()).slice(1, -1).split('/').pop();
|
||||||
|
|
||||||
document.title = txt;
|
document.title = txt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<input type="hidden" id="la" name="act" value="login" />
|
<input type="hidden" id="la" name="act" value="login" />
|
||||||
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||||
<input type="hidden" name="uhash" id="uhash" value="x" />
|
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||||
<input type="submit" id="ls" value="Login" />
|
<input type="submit" id="ls" value="login" />
|
||||||
{% if chpw %}
|
{% if chpw %}
|
||||||
<a id="x" href="#">change password</a>
|
<a id="x" href="#">change password</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@
|
|||||||
gio mount -a dav{{ s }}://{{ ep }}/{{ rvp }}
|
gio mount -a dav{{ s }}://{{ ep }}/{{ rvp }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</pre>
|
</pre>
|
||||||
|
<p>on KDE Dolphin, use <code>webdav{{ s }}://{{ ep }}/{{ rvp }}</code></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="os mac">
|
<div class="os mac">
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var x = sread('nosubtle');
|
||||||
|
if (x === '0' || x === '1')
|
||||||
|
nosubtle = parseInt(x);
|
||||||
|
if ((nosubtle > 1 && !CHROME && !FIREFOX) ||
|
||||||
|
(nosubtle > 2 && !CHROME) ||
|
||||||
|
(CHROME && nosubtle > VCHROME) ||
|
||||||
|
!WebAssembly)
|
||||||
|
nosubtle = 0;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
function goto_up2k() {
|
function goto_up2k() {
|
||||||
if (up2k === false)
|
if (up2k === false)
|
||||||
return goto('bup');
|
return goto('bup');
|
||||||
@@ -23,7 +35,7 @@ var up2k = null,
|
|||||||
m = 'will use ' + sha_js + ' instead of native sha512 due to';
|
m = 'will use ' + sha_js + ' instead of native sha512 due to';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (sread('nosubtle') || window.nosubtle)
|
if (nosubtle)
|
||||||
throw 'chickenbit';
|
throw 'chickenbit';
|
||||||
var cf = crypto.subtle || crypto.webkitSubtle;
|
var cf = crypto.subtle || crypto.webkitSubtle;
|
||||||
cf.digest('SHA-512', new Uint8Array(1)).then(
|
cf.digest('SHA-512', new Uint8Array(1)).then(
|
||||||
@@ -825,7 +837,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
qsr('#u2depmsg');
|
qsr('#u2depmsg');
|
||||||
var o = mknod('div', 'u2depmsg');
|
var o = mknod('div', 'u2depmsg');
|
||||||
o.innerHTML = m;
|
o.innerHTML = nosubtle ? '' : m;
|
||||||
ebi('u2foot').appendChild(o);
|
ebi('u2foot').appendChild(o);
|
||||||
}
|
}
|
||||||
loading_deps = true;
|
loading_deps = true;
|
||||||
@@ -881,7 +893,8 @@ function up2k_init(subtle) {
|
|||||||
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo);
|
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo);
|
||||||
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null);
|
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null);
|
||||||
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
|
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
|
||||||
bcfg_bind(uc, 'hashw', 'hashw', !!WebAssembly && !(CHROME && MOBILE) && (!subtle || !CHROME), set_hashw);
|
bcfg_bind(uc, 'hashw', 'hashw', !!WebAssembly && !(CHROME && MOBILE) && (!subtle || !CHROME || VCHROME > 136), set_hashw);
|
||||||
|
bcfg_bind(uc, 'hwasm', 'nosubtle', nosubtle, set_nosubtle);
|
||||||
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
||||||
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
||||||
|
|
||||||
@@ -1415,7 +1428,7 @@ function up2k_init(subtle) {
|
|||||||
if (FIREFOX && good_files.length > 3000)
|
if (FIREFOX && good_files.length > 3000)
|
||||||
msg.push(L.u_ff_many + "\n\n");
|
msg.push(L.u_ff_many + "\n\n");
|
||||||
|
|
||||||
msg.push(L.u_asku.format(good_files.length, esc(get_vpath())) + '<ul>');
|
msg.push(L.u_asku.format(good_files.length, esc(uricom_dec(get_evpath()))) + '<ul>');
|
||||||
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
|
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
|
||||||
msg.push('<li>' + esc(good_files[a][1]) + '</li>');
|
msg.push('<li>' + esc(good_files[a][1]) + '</li>');
|
||||||
|
|
||||||
@@ -1442,9 +1455,16 @@ function up2k_init(subtle) {
|
|||||||
if (CHROME) {
|
if (CHROME) {
|
||||||
// chrome-bug 383568268 // #124
|
// chrome-bug 383568268 // #124
|
||||||
nw = Math.max(1, (nw > 4 ? 4 : (nw - 1)));
|
nw = Math.max(1, (nw > 4 ? 4 : (nw - 1)));
|
||||||
|
if (VCHROME < 137)
|
||||||
nw = (subtle && !MOBILE && nw > 2) ? 2 : nw;
|
nw = (subtle && !MOBILE && nw > 2) ? 2 : nw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var x = sread('u2hashers') || window.u2hashers;
|
||||||
|
if (x) {
|
||||||
|
console.log('u2hashers is overriding default-value ' + nw);
|
||||||
|
nw = parseInt(x);
|
||||||
|
}
|
||||||
|
|
||||||
for (var a = 0; a < nw; a++)
|
for (var a = 0; a < nw; a++)
|
||||||
hws.push(new Worker(SR + '/.cpr/w.hash.js?_=' + TS));
|
hws.push(new Worker(SR + '/.cpr/w.hash.js?_=' + TS));
|
||||||
|
|
||||||
@@ -2213,6 +2233,7 @@ function up2k_init(subtle) {
|
|||||||
reading = 0,
|
reading = 0,
|
||||||
max_readers = 1,
|
max_readers = 1,
|
||||||
opt_readers = 2,
|
opt_readers = 2,
|
||||||
|
failed = false,
|
||||||
free = [],
|
free = [],
|
||||||
busy = {},
|
busy = {},
|
||||||
nbusy = 0,
|
nbusy = 0,
|
||||||
@@ -2262,6 +2283,14 @@ function up2k_init(subtle) {
|
|||||||
tasker();
|
tasker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function go_fail() {
|
||||||
|
failed = true;
|
||||||
|
if (nbusy)
|
||||||
|
return;
|
||||||
|
apop(st.busy.hash, t);
|
||||||
|
st.bytes.finished += t.size;
|
||||||
|
}
|
||||||
|
|
||||||
function onmsg(d) {
|
function onmsg(d) {
|
||||||
d = d.data;
|
d = d.data;
|
||||||
var k = d[0];
|
var k = d[0];
|
||||||
@@ -2276,6 +2305,12 @@ function up2k_init(subtle) {
|
|||||||
return vis_exh(d[1], 'up2k.js', '', '', d[1]);
|
return vis_exh(d[1], 'up2k.js', '', '', d[1]);
|
||||||
|
|
||||||
if (k == "fail") {
|
if (k == "fail") {
|
||||||
|
var nchunk = d[1];
|
||||||
|
free.push(busy[nchunk]);
|
||||||
|
delete busy[nchunk];
|
||||||
|
nbusy--;
|
||||||
|
reading--;
|
||||||
|
|
||||||
pvis.seth(t.n, 1, d[1]);
|
pvis.seth(t.n, 1, d[1]);
|
||||||
pvis.seth(t.n, 2, d[2]);
|
pvis.seth(t.n, 2, d[2]);
|
||||||
console.log(d[1], d[2]);
|
console.log(d[1], d[2]);
|
||||||
@@ -2283,9 +2318,7 @@ function up2k_init(subtle) {
|
|||||||
got_oserr();
|
got_oserr();
|
||||||
|
|
||||||
pvis.move(t.n, 'ng');
|
pvis.move(t.n, 'ng');
|
||||||
apop(st.busy.hash, t);
|
return go_fail();
|
||||||
st.bytes.finished += t.size;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k == "ferr")
|
if (k == "ferr")
|
||||||
@@ -2318,6 +2351,9 @@ function up2k_init(subtle) {
|
|||||||
t.hash.push(nchunk);
|
t.hash.push(nchunk);
|
||||||
pvis.hashed(t);
|
pvis.hashed(t);
|
||||||
|
|
||||||
|
if (failed)
|
||||||
|
return go_fail();
|
||||||
|
|
||||||
if (t.hash.length < nchunks)
|
if (t.hash.length < nchunks)
|
||||||
return nbusy < opt_readers && go_next();
|
return nbusy < opt_readers && go_next();
|
||||||
|
|
||||||
@@ -3269,6 +3305,12 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_nosubtle(v) {
|
||||||
|
if (!WebAssembly)
|
||||||
|
return toast.err(10, L.u_nowork);
|
||||||
|
modal.confirm(L.lang_set, location.reload.bind(location), null);
|
||||||
|
}
|
||||||
|
|
||||||
function set_upnag(en) {
|
function set_upnag(en) {
|
||||||
function nopenag() {
|
function nopenag() {
|
||||||
bcfg_set('upnag', uc.upnag = false);
|
bcfg_set('upnag', uc.upnag = false);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ try {
|
|||||||
|
|
||||||
CHROME = navigator.userAgentData.brands.find(function (d) { return d.brand == 'Chromium' });
|
CHROME = navigator.userAgentData.brands.find(function (d) { return d.brand == 'Chromium' });
|
||||||
if (CHROME)
|
if (CHROME)
|
||||||
VCHROME = CHROME.version;
|
VCHROME = parseInt(CHROME.version);
|
||||||
else
|
else
|
||||||
VCHROME = 0;
|
VCHROME = 0;
|
||||||
|
|
||||||
@@ -364,7 +364,8 @@ if (!Element.prototype.matches)
|
|||||||
Element.prototype.mozMatchesSelector ||
|
Element.prototype.mozMatchesSelector ||
|
||||||
Element.prototype.webkitMatchesSelector;
|
Element.prototype.webkitMatchesSelector;
|
||||||
|
|
||||||
if (!Element.prototype.closest)
|
var CLOSEST = !!Element.prototype.closest;
|
||||||
|
if (!CLOSEST)
|
||||||
Element.prototype.closest = function (s) {
|
Element.prototype.closest = function (s) {
|
||||||
var el = this;
|
var el = this;
|
||||||
do {
|
do {
|
||||||
@@ -461,6 +462,13 @@ function namesan(txt, win, fslash) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var NATSORT, ENATSORT;
|
||||||
|
try {
|
||||||
|
NATSORT = new Intl.Collator([], {numeric: true});
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
|
||||||
|
|
||||||
var crctab = (function () {
|
var crctab = (function () {
|
||||||
var c, tab = [];
|
var c, tab = [];
|
||||||
for (var n = 0; n < 256; n++) {
|
for (var n = 0; n < 256; n++) {
|
||||||
@@ -614,6 +622,33 @@ function showsort(tab) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function st_cmp_num(a, b) {
|
||||||
|
a = a[0];
|
||||||
|
b = b[0];
|
||||||
|
return (
|
||||||
|
a === null ? -1 :
|
||||||
|
b === null ? 1 :
|
||||||
|
(a - b)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function st_cmp_nat(a, b) {
|
||||||
|
a = a[0];
|
||||||
|
b = b[0];
|
||||||
|
return (
|
||||||
|
a === null ? -1 :
|
||||||
|
b === null ? 1 :
|
||||||
|
NATSORT.compare(a, b)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function st_cmp_gen(a, b) {
|
||||||
|
a = a[0];
|
||||||
|
b = b[0];
|
||||||
|
return (
|
||||||
|
a === null ? -1 :
|
||||||
|
b === null ? 1 :
|
||||||
|
a.localeCompare(b)
|
||||||
|
);
|
||||||
|
}
|
||||||
function sortTable(table, col, cb) {
|
function sortTable(table, col, cb) {
|
||||||
var tb = table.tBodies[0],
|
var tb = table.tBodies[0],
|
||||||
th = table.tHead.rows[0].cells,
|
th = table.tHead.rows[0].cells,
|
||||||
@@ -659,19 +694,17 @@ function sortTable(table, col, cb) {
|
|||||||
}
|
}
|
||||||
vl.push([v, a]);
|
vl.push([v, a]);
|
||||||
}
|
}
|
||||||
vl.sort(function (a, b) {
|
|
||||||
a = a[0];
|
|
||||||
b = b[0];
|
|
||||||
if (a === null)
|
|
||||||
return -1;
|
|
||||||
if (b === null)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (stype == 'int') {
|
if (stype == 'int')
|
||||||
return reverse * (a - b);
|
vl.sort(st_cmp_num);
|
||||||
}
|
else if (ENATSORT)
|
||||||
return reverse * (a.localeCompare(b));
|
vl.sort(st_cmp_nat);
|
||||||
});
|
else
|
||||||
|
vl.sort(st_cmp_gen);
|
||||||
|
|
||||||
|
if (reverse < 0)
|
||||||
|
vl.reverse();
|
||||||
|
|
||||||
if (sread('dir1st') !== '0') {
|
if (sread('dir1st') !== '0') {
|
||||||
var r1 = [], r2 = [];
|
var r1 = [], r2 = [];
|
||||||
for (var i = 0; i < tr.length; i++) {
|
for (var i = 0; i < tr.length; i++) {
|
||||||
@@ -857,11 +890,6 @@ function get_evpath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function get_vpath() {
|
|
||||||
return uricom_dec(get_evpath());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function noq_href(el) {
|
function noq_href(el) {
|
||||||
return el.getAttribute('href').split('?')[0];
|
return el.getAttribute('href').split('?')[0];
|
||||||
}
|
}
|
||||||
@@ -1201,7 +1229,7 @@ function dl_file(url) {
|
|||||||
function cliptxt(txt, ok) {
|
function cliptxt(txt, ok) {
|
||||||
var fb = function () {
|
var fb = function () {
|
||||||
console.log('clip-fb');
|
console.log('clip-fb');
|
||||||
var o = mknod('input');
|
var o = mknod('textarea');
|
||||||
o.value = txt;
|
o.value = txt;
|
||||||
document.body.appendChild(o);
|
document.body.appendChild(o);
|
||||||
o.focus();
|
o.focus();
|
||||||
@@ -1211,6 +1239,8 @@ function cliptxt(txt, ok) {
|
|||||||
ok();
|
ok();
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
if (!window.isSecureContext)
|
||||||
|
throw 1;
|
||||||
navigator.clipboard.writeText(txt).then(ok, fb);
|
navigator.clipboard.writeText(txt).then(ok, fb);
|
||||||
}
|
}
|
||||||
catch (ex) { fb(); }
|
catch (ex) { fb(); }
|
||||||
|
|||||||
@@ -4,6 +4,16 @@
|
|||||||
function hex2u8(txt) {
|
function hex2u8(txt) {
|
||||||
return new Uint8Array(txt.match(/.{2}/g).map(function (b) { return parseInt(b, 16); }));
|
return new Uint8Array(txt.match(/.{2}/g).map(function (b) { return parseInt(b, 16); }));
|
||||||
}
|
}
|
||||||
|
function esc(txt) {
|
||||||
|
return txt.replace(/[&"<>]/g, function (c) {
|
||||||
|
return {
|
||||||
|
'&': '&',
|
||||||
|
'"': '"',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>'
|
||||||
|
}[c];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var subtle = null;
|
var subtle = null;
|
||||||
@@ -19,6 +29,8 @@ catch (ex) {
|
|||||||
}
|
}
|
||||||
function load_fb() {
|
function load_fb() {
|
||||||
subtle = null;
|
subtle = null;
|
||||||
|
if (self.hashwasm)
|
||||||
|
return;
|
||||||
importScripts('deps/sha512.hw.js');
|
importScripts('deps/sha512.hw.js');
|
||||||
console.log('using fallback hasher');
|
console.log('using fallback hasher');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,151 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0518-2234 `v1.17.1` as seen on archlinux
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* new toolbar button to zip/tar the currently open folder 256dad8c
|
||||||
|
* new options to specify the default checksum algorithm for PUT/bup/WebDAV uploads 0de09860
|
||||||
|
* #164 new option `--put-name` to specify the filename of nameless uploads 5dcd88a6
|
||||||
|
* the default is still `put-TIMESTAMP-IPADDRESS.bin`
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* #162 password-protected shares was incompatible with password-hashing c3ef3fdc
|
||||||
|
* #161 m3u playlist creation was only possible over https 94352f27
|
||||||
|
* when relocating/redirecting an upload from an xbu hook (execute-before-upload), could miss an already existing file at the destination and create another copy 0a9a8077
|
||||||
|
* some edgecases when moving files between filesystems f425ff51
|
||||||
|
* improve tagscan-resume after a server restart (primarily for dupes) 41fa6b25
|
||||||
|
* support prehistoric timestamps in fat16 vhd-drives on windows 261236e3
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* #159 the nixos module was improved (thx @gabevenberg and @chinponya!) d1bca1f5
|
||||||
|
* an archlinux maintainer adopted the aur package; copyparty is now [officially in arch](https://archlinux.org/packages/extra/any/copyparty/) b9ba783c
|
||||||
|
* #162 add KDE Dolphin instructions to the conect-page d4a8071d
|
||||||
|
* audioplayer now knows that `.oga` means `.ogg`
|
||||||
|
|
||||||
|
## 🌠 fun facts
|
||||||
|
|
||||||
|
* this release contains code [pair-programmed during an anime rave](https://a.ocv.me/pub/g/nerd-stuff/PXL_20250503_222654610.jpg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0426-2149 `v1.17.0` mixtape.m3u
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* [m3u playlists](https://github.com/9001/copyparty/#playlists) 897f9d32 ad200f2b 4195762d fff45552
|
||||||
|
* create and play m3u / m3u8 files
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* improve support for ie11 (yes, internet explorer 11) 3090c748 95157d02
|
||||||
|
* now possible to launch the password-hasher cli while another instance is running dbfc899d
|
||||||
|
* in preparation of #157 / #159
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* make better decisions when running in a VM with less than 1 GiB RAM dc3b7a27
|
||||||
|
|
||||||
|
## 🌠 fun facts
|
||||||
|
|
||||||
|
* this release contains code written [less than 1masl](https://a.ocv.me/pub/g/nerd-stuff/PXL_20250425_170037812.jpg) and was gonna be named [hash again](https://www.youtube.com/watch?v=twUFbqyul_M) since it was originally just the password-hasher fix, but then kipun suggested adding playlist support (thx kipun)
|
||||||
|
* [donations](https://github.com/9001/) are now also possible through github -- good alternative to paypal (y)
|
||||||
|
* and thanks a lot for the support (and kind words therein) so far, appreciate it :>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0420-1836 `v1.16.21` unzip-compat
|
||||||
|
|
||||||
|
a couple guys have been asking if I accept donations -- thanks a lot!! added a few options on [my github page](https://github.com/9001/) :>
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* #156 add button to loop/repeat music 71c55659
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* #155 download-as-zip: increase compatibility with the unix `unzip` command db33d68d
|
||||||
|
* this unfortunately reduces support for huge zipfiles on old software (WinXP and such)
|
||||||
|
* and makes it less safe to stream zips into unzippers, so use tar.gz instead
|
||||||
|
* and is perhaps not even a copyparty bug; see commit-message for the full story
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* show warning on Ctrl-A in lazy-loaded folders 5b3a5fe7
|
||||||
|
* docker: hide keepalive pings from logs d5a9bd80
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0413-2151 `v1.16.20` all sorted
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* when enabled, natural-sort will now also apply to tags, not just filenames 7b2bd6da
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* some sorting-related stuff 7b2bd6da
|
||||||
|
* folders with non-ascii names would sort incorrectly in the navpane/sidebar
|
||||||
|
* natural-sort didn't apply correctly after changing the sort order
|
||||||
|
* workaround [ffmpeg-bug 10797](https://trac.ffmpeg.org/ticket/10797) 98dcaee2
|
||||||
|
* reduces ram usage from 1534 to 230 MiB when generating spectrograms of s3xmodit songs (amiga chiptunes)
|
||||||
|
* disable mdns if only listening on uds (unix-sockets) ffc16109 361aebf8
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* hotkey CTRL-A will now select all files in gridview 233075ae
|
||||||
|
* and it toggles (just like in list-view) so try pressing it again
|
||||||
|
* copyparty.exe: upgrade to pillow v11.2.1 c7aa1a35
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2025-0408-2132 `v1.16.19` GHOST
|
||||||
|
|
||||||
|
did you know that every song named `GHOST` is a banger? it's true! [ghost](https://www.youtube.com/watch?v=NoUAwC4yiAw) // [ghost](https://www.youtube.com/watch?v=IKKar5SS29E) // [ghost](https://www.youtube.com/watch?v=tFSFlgm_tsw)
|
||||||
|
|
||||||
|
## 🧪 new features
|
||||||
|
|
||||||
|
* option to store markdown backups out-of-volume fc883418
|
||||||
|
* the default is still a subfolder named `.hist` next to the markdown file
|
||||||
|
* `--md-hist v` puts them in the volume's hist-folder instead
|
||||||
|
* `--md-hist n` disables markdown-backups entirely
|
||||||
|
* #149 option to store the volume sqlite databases at a custom locations outside the hist-folder e1b9ac63
|
||||||
|
* new option `--dbpath` works like `--hist` but it only moves the database file, not the thumbnails
|
||||||
|
* they can be combined, in which case `--hist` is applied to thumbnails, `--dbpath` to the db
|
||||||
|
* useful when you're squeezing every last drop of performance out of your filesystem (see the issue)
|
||||||
|
* actively prevent sharing certain databases (sessions/shares) between multiple copyparty instances acfaacbd
|
||||||
|
* an errormessage was added to explain some different alternatives for doing this safely
|
||||||
|
* for example by setting `XDG_CONFIG_HOME` which now works on all platforms b17ccc38
|
||||||
|
|
||||||
|
## 🩹 bugfixes
|
||||||
|
|
||||||
|
* #151 mkdir did not work in locations outside the volume root (via symlinks) 2b50fc20
|
||||||
|
* improve the ui feedback when trying to play an audio file which failed to transcode f9954bc4
|
||||||
|
* also helps with server-filesystem issues, including image-thumbs
|
||||||
|
|
||||||
|
## 🔧 other changes
|
||||||
|
|
||||||
|
* #152 custom fonts are also applied to textboxes and buttons (thx @thaddeuskkr) d450f615
|
||||||
|
* be more careful with the shares-db 8e0364ef
|
||||||
|
* be less careful with the sessions-db 8e0364ef
|
||||||
|
* update deps c0becc64
|
||||||
|
* web: dompurify
|
||||||
|
* copyparty.exe: python 3.12.10
|
||||||
|
* rephrase `-j0` warning on windows to also mention that Microsoft Defender will freak out c0becc64
|
||||||
|
* #149 add [a script](https://github.com/9001/copyparty/tree/hovudstraum/contrib#zfs-tunepy) to optimize the sqlite databases for storage on zfs 4f397b9b
|
||||||
|
* block `GoogleOther` (another recalcitrant bot) from zip-downloads c2034f7b
|
||||||
|
* rephrase `-j0` warning on windows to also mention that Microsoft Defender will freak out c0becc64
|
||||||
|
* update [contributing.md](https://github.com/9001/copyparty/blob/hovudstraum/CONTRIBUTING.md) with a section regarding LLM/AI-written code cec3bee0
|
||||||
|
* the [helptext](https://ocv.me/copyparty/helptext.html) will also be uploaded to each github release from now on, [permalink](https://github.com/9001/copyparty/releases/latest/download/helptext.html)
|
||||||
|
* add review from ixbt forums b383c08c
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2025-0323-2216 `v1.16.18` zlib-ng
|
# 2025-0323-2216 `v1.16.18` zlib-ng
|
||||||
|
|
||||||
|
|||||||
@@ -281,8 +281,11 @@ on writing your own [hooks](../README.md#event-hooks)
|
|||||||
hooks can cause intentional side-effects, such as redirecting an upload into another location, or creating+indexing additional files, or deleting existing files, by returning json on stdout
|
hooks can cause intentional side-effects, such as redirecting an upload into another location, or creating+indexing additional files, or deleting existing files, by returning json on stdout
|
||||||
|
|
||||||
* `reloc` can redirect uploads before/after uploading has finished, based on filename, extension, file contents, uploader ip/name etc.
|
* `reloc` can redirect uploads before/after uploading has finished, based on filename, extension, file contents, uploader ip/name etc.
|
||||||
|
* example: [reloc-by-ext](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reloc-by-ext.py)
|
||||||
* `idx` informs copyparty about a new file to index as a consequence of this upload
|
* `idx` informs copyparty about a new file to index as a consequence of this upload
|
||||||
|
* example: [podcast-normalizer.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/podcast-normalizer.py)
|
||||||
* `del` tells copyparty to delete an unrelated file by vpath
|
* `del` tells copyparty to delete an unrelated file by vpath
|
||||||
|
* example: ( ´・ω・) nyoro~n
|
||||||
|
|
||||||
for these to take effect, the hook must be defined with the `c1` flag; see example [reloc-by-ext](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reloc-by-ext.py)
|
for these to take effect, the hook must be defined with the `c1` flag; see example [reloc-by-ext](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/reloc-by-ext.py)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ services:
|
|||||||
|
|
||||||
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
|
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget --spider -q 127.0.0.1:3923/?reset"]
|
# hide it from logs with "/._" so it matches the default --lf-url filter
|
||||||
|
test: ["CMD-SHELL", "wget --spider -q 127.0.0.1:3923/?reset=/._"]
|
||||||
interval: 1m
|
interval: 1m
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
@@ -22,12 +22,6 @@ services:
|
|||||||
- 'traefik.http.routers.fs.rule=Host(`fs.example.com`)'
|
- 'traefik.http.routers.fs.rule=Host(`fs.example.com`)'
|
||||||
- 'traefik.http.routers.fs.entrypoints=http'
|
- 'traefik.http.routers.fs.entrypoints=http'
|
||||||
#- 'traefik.http.routers.fs.middlewares=authelia@docker' # TODO: ???
|
#- 'traefik.http.routers.fs.middlewares=authelia@docker' # TODO: ???
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "wget --spider -q 127.0.0.1:3923/?reset"]
|
|
||||||
interval: 1m
|
|
||||||
timeout: 2s
|
|
||||||
retries: 5
|
|
||||||
start_period: 15s
|
|
||||||
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
|
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
|
||||||
environment:
|
environment:
|
||||||
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
|
||||||
|
|||||||
8
flake.lock
generated
8
flake.lock
generated
@@ -17,16 +17,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680334310,
|
"lastModified": 1748162331,
|
||||||
"narHash": "sha256-ISWz16oGxBhF7wqAxefMPwFag6SlsA9up8muV79V9ck=",
|
"narHash": "sha256-rqc2RKYTxP3tbjA+PB3VMRQNnjesrT0pEofXQTrMsS8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "884e3b68be02ff9d61a042bc9bd9dd2a358f95da",
|
"rev": "7c43f080a7f28b2774f3b3f43234ca11661bf334",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"id": "nixpkgs",
|
||||||
"ref": "nixos-22.11",
|
"ref": "nixos-25.05",
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-22.11";
|
nixpkgs.url = "nixpkgs/nixos-25.05";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
config = {
|
||||||
|
allowAliases = false;
|
||||||
|
};
|
||||||
overlays = [ self.overlays.default ];
|
overlays = [ self.overlays.default ];
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ RUN apk add -U !pyc \
|
|||||||
&& apk add -t .bd \
|
&& apk add -t .bd \
|
||||||
bash wget gcc g++ make cmake patchelf \
|
bash wget gcc g++ make cmake patchelf \
|
||||||
python3-dev ffmpeg-dev fftw-dev libsndfile-dev \
|
python3-dev ffmpeg-dev fftw-dev libsndfile-dev \
|
||||||
py3-wheel py3-numpy-dev \
|
py3-wheel py3-numpy-dev libffi-dev \
|
||||||
vamp-sdk-dev \
|
vamp-sdk-dev \
|
||||||
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
|
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
|
||||||
&& python3 -m pip install pyvips \
|
&& python3 -m pip install pyvips \
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN apk add -U !pyc \
|
|||||||
vips-jxl vips-heif vips-poppler vips-magick \
|
vips-jxl vips-heif vips-poppler vips-magick \
|
||||||
&& apk add -t .bd \
|
&& apk add -t .bd \
|
||||||
bash wget gcc g++ make cmake patchelf \
|
bash wget gcc g++ make cmake patchelf \
|
||||||
python3-dev py3-wheel \
|
python3-dev py3-wheel libffi-dev \
|
||||||
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
|
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
|
||||||
&& python3 -m pip install pyvips \
|
&& python3 -m pip install pyvips \
|
||||||
&& apk del py3-pip .bd
|
&& apk del py3-pip .bd
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ all:
|
|||||||
|
|
||||||
docker image ls
|
docker image ls
|
||||||
|
|
||||||
|
min:
|
||||||
|
rm -rf i
|
||||||
|
mkdir i
|
||||||
|
tar -cC../.. dist/copyparty-sfx.py bin/mtag | tar -xvCi
|
||||||
|
|
||||||
|
podman build --squash --pull=always -t copyparty/min:latest -f Dockerfile.min .
|
||||||
|
echo 'scale=1;'`podman save copyparty/min:latest | pigz -c | wc -c`/1024/1024 | bc
|
||||||
|
|
||||||
push:
|
push:
|
||||||
docker push copyparty/min
|
docker push copyparty/min
|
||||||
docker push copyparty/im
|
docker push copyparty/im
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ ac96786e5d35882e0c5b724794329c9125c2b86ae7847f17acfc49f0d294312c6afc1c3f248655de
|
|||||||
6df21f0da408a89f6504417c7cdf9aaafe4ed88cfa13e9b8fa8414f604c0401f885a04bbad0484dc51a29284af5d1548e33c6cc6bfb9896d9992c1b1074f332d MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl
|
6df21f0da408a89f6504417c7cdf9aaafe4ed88cfa13e9b8fa8414f604c0401f885a04bbad0484dc51a29284af5d1548e33c6cc6bfb9896d9992c1b1074f332d MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl
|
||||||
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
||||||
0203ec2551c4836696cfab0b2c9fff603352f03fa36e7476e2e1ca7ec57a3a0c24bd791fcd92f342bf817f0887854d9f072e0271c643de4b313d8c9569ba8813 packaging-24.1-py3-none-any.whl
|
0203ec2551c4836696cfab0b2c9fff603352f03fa36e7476e2e1ca7ec57a3a0c24bd791fcd92f342bf817f0887854d9f072e0271c643de4b313d8c9569ba8813 packaging-24.1-py3-none-any.whl
|
||||||
12d7921dc7dfd8a4b0ea0fa2bae8f1354fcdd59ece3d7f4e075aed631f9ba791dc142c70b1ccd1e6287c43139df1db26bd57a7a217c8da3a77326036495cdb57 pillow-11.1.0-cp312-cp312-win_amd64.whl
|
c9051daaf34ec934962c743a5ac2dbe55a9b0cababb693a8cde0001d24d4a50b67bd534d714d935def6ca7b898ec0a352e58bd9ccdce01c54eaf2281b18e478d pillow-11.2.1-cp312-cp312-win_amd64.whl
|
||||||
f0463895e9aee97f31a2003323de235fed1b26289766dc0837261e3f4a594a31162b69e9adbb0e9a31e2e2d4b5f25c762ed1669553df7dc89a8ba4f85d297873 pyinstaller-6.11.1-py3-none-win_amd64.whl
|
f0463895e9aee97f31a2003323de235fed1b26289766dc0837261e3f4a594a31162b69e9adbb0e9a31e2e2d4b5f25c762ed1669553df7dc89a8ba4f85d297873 pyinstaller-6.11.1-py3-none-win_amd64.whl
|
||||||
d550a0a14428386945533de2220c4c2e37c0c890fc51a600f626c6ca90a32d39572c121ec04c157ba3a8d6601cb021f8433d871b5c562a3d342c804fffec90c1 pyinstaller_hooks_contrib-2024.11-py3-none-any.whl
|
d550a0a14428386945533de2220c4c2e37c0c890fc51a600f626c6ca90a32d39572c121ec04c157ba3a8d6601cb021f8433d871b5c562a3d342c804fffec90c1 pyinstaller_hooks_contrib-2024.11-py3-none-any.whl
|
||||||
4f9a4d9f65c93e2d851e2674057343a9599f30f5dc582ffca485522237d4fcf43653b3d393ed5eb11e518c4ba93714a07134bbb13a97d421cce211e1da34682e python-3.12.10-amd64.exe
|
4f9a4d9f65c93e2d851e2674057343a9599f30f5dc582ffca485522237d4fcf43653b3d393ed5eb11e518c4ba93714a07134bbb13a97d421cce211e1da34682e python-3.12.10-amd64.exe
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ fns=(
|
|||||||
MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl
|
MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl
|
||||||
mutagen-1.47.0-py3-none-any.whl
|
mutagen-1.47.0-py3-none-any.whl
|
||||||
packaging-24.1-py3-none-any.whl
|
packaging-24.1-py3-none-any.whl
|
||||||
pillow-11.1.0-cp312-cp312-win_amd64.whl
|
pillow-11.2.1-cp312-cp312-win_amd64.whl
|
||||||
pyinstaller-6.10.0-py3-none-win_amd64.whl
|
pyinstaller-6.10.0-py3-none-win_amd64.whl
|
||||||
pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
||||||
python-3.12.10-amd64.exe
|
python-3.12.10-amd64.exe
|
||||||
|
|||||||
@@ -226,10 +226,13 @@ var tl_browser = {
|
|||||||
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
|
||||||
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
|
||||||
"wt_selinv": "invert selection",
|
"wt_selinv": "invert selection",
|
||||||
|
"wt_zip1": "download this folder as archive",
|
||||||
"wt_selzip": "download selection as archive",
|
"wt_selzip": "download selection as archive",
|
||||||
"wt_seldl": "download selection as separate files$NHotkey: Y",
|
"wt_seldl": "download selection as separate files$NHotkey: Y",
|
||||||
"wt_npirc": "copy irc-formatted track info",
|
"wt_npirc": "copy irc-formatted track info",
|
||||||
"wt_nptxt": "copy plaintext track info",
|
"wt_nptxt": "copy plaintext track info",
|
||||||
|
"wt_m3ua": "add to m3u playlist (click <code>📻copy</code> later)",
|
||||||
|
"wt_m3uc": "copy m3u playlist to clipboard",
|
||||||
"wt_grid": "toggle grid / list view$NHotkey: G",
|
"wt_grid": "toggle grid / list view$NHotkey: G",
|
||||||
"wt_prev": "previous track$NHotkey: J",
|
"wt_prev": "previous track$NHotkey: J",
|
||||||
"wt_play": "play / pause$NHotkey: P",
|
"wt_play": "play / pause$NHotkey: P",
|
||||||
@@ -332,6 +335,8 @@ var tl_browser = {
|
|||||||
|
|
||||||
"cut_mt": "use multithreading to accelerate file hashing$N$Nthis uses web-workers and requires$Nmore RAM (up to 512 MiB extra)$N$Nmakes https 30% faster, http 4.5x faster\">mt",
|
"cut_mt": "use multithreading to accelerate file hashing$N$Nthis uses web-workers and requires$Nmore RAM (up to 512 MiB extra)$N$Nmakes https 30% faster, http 4.5x faster\">mt",
|
||||||
|
|
||||||
|
"cut_wasm": "use wasm instead of the browser's built-in hasher; improves speed on chrome-based browsers but increases CPU load, and many older versions of chrome have bugs which makes the browser consume all RAM and crash if this is enabled\">wasm",
|
||||||
|
|
||||||
"cft_text": "favicon text (blank and refresh to disable)",
|
"cft_text": "favicon text (blank and refresh to disable)",
|
||||||
"cft_fg": "foreground color",
|
"cft_fg": "foreground color",
|
||||||
"cft_bg": "background color",
|
"cft_bg": "background color",
|
||||||
@@ -357,6 +362,7 @@ var tl_browser = {
|
|||||||
"ml_eq": "audio equalizer",
|
"ml_eq": "audio equalizer",
|
||||||
"ml_drc": "dynamic range compressor",
|
"ml_drc": "dynamic range compressor",
|
||||||
|
|
||||||
|
"mt_loop": "loop/repeat one song\">🔁",
|
||||||
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
"mt_shuf": "shuffle the songs in each folder\">🔀",
|
||||||
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
|
||||||
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
"mt_preload": "start loading the next song near the end for gapless playback\">preload",
|
||||||
@@ -365,6 +371,7 @@ var tl_browser = {
|
|||||||
"mt_fau": "on phones, prevent music from stopping if the next song doesn't preload fast enough (can make tags display glitchy)\">☕️",
|
"mt_fau": "on phones, prevent music from stopping if the next song doesn't preload fast enough (can make tags display glitchy)\">☕️",
|
||||||
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
|
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
|
||||||
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
|
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
|
||||||
|
"mt_m3u_c": "show buttons for clipboarding the$Nselected songs as m3u8 playlist entries\">📻",
|
||||||
"mt_octl": "os integration (media hotkeys / osd)\">os-ctl",
|
"mt_octl": "os integration (media hotkeys / osd)\">os-ctl",
|
||||||
"mt_oseek": "allow seeking through os integration$N$Nnote: on some devices (iPhones),$Nthis replaces the next-song button\">seek",
|
"mt_oseek": "allow seeking through os integration$N$Nnote: on some devices (iPhones),$Nthis replaces the next-song button\">seek",
|
||||||
"mt_oscv": "show album cover in osd\">art",
|
"mt_oscv": "show album cover in osd\">art",
|
||||||
@@ -390,6 +397,7 @@ var tl_browser = {
|
|||||||
|
|
||||||
"mb_play": "play",
|
"mb_play": "play",
|
||||||
"mm_hashplay": "play this audio file?",
|
"mm_hashplay": "play this audio file?",
|
||||||
|
"mm_m3u": "press <code>Enter/OK</code> to Play\npress <code>ESC/Cancel</code> to Edit",
|
||||||
"mp_breq": "need firefox 82+ or chrome 73+ or iOS 15+",
|
"mp_breq": "need firefox 82+ or chrome 73+ or iOS 15+",
|
||||||
"mm_bload": "now loading...",
|
"mm_bload": "now loading...",
|
||||||
"mm_bconv": "converting to {0}, please wait...",
|
"mm_bconv": "converting to {0}, please wait...",
|
||||||
@@ -402,6 +410,7 @@ var tl_browser = {
|
|||||||
"mm_eunk": "Unknown Errol",
|
"mm_eunk": "Unknown Errol",
|
||||||
"mm_e404": "Could not play audio; error 404: File not found.",
|
"mm_e404": "Could not play audio; error 404: File not found.",
|
||||||
"mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out",
|
"mm_e403": "Could not play audio; error 403: Access denied.\n\nTry pressing F5 to reload, maybe you got logged out",
|
||||||
|
"mm_e500": "Could not play audio; error 500: Check server logs.",
|
||||||
"mm_e5xx": "Could not play audio; server error ",
|
"mm_e5xx": "Could not play audio; server error ",
|
||||||
"mm_nof": "not finding any more audio files nearby",
|
"mm_nof": "not finding any more audio files nearby",
|
||||||
"mm_prescan": "Looking for music to play next...",
|
"mm_prescan": "Looking for music to play next...",
|
||||||
@@ -416,6 +425,7 @@ var tl_browser = {
|
|||||||
"f_bigtxt": "this file is {0} MiB large -- really view as text?",
|
"f_bigtxt": "this file is {0} MiB large -- really view as text?",
|
||||||
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
||||||
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
||||||
|
"f_anota": "only {0} of the {1} items were selected;\nto select the full folder, first scroll to the bottom",
|
||||||
|
|
||||||
"f_dls": 'the file links in the current folder have\nbeen changed into download links',
|
"f_dls": 'the file links in the current folder have\nbeen changed into download links',
|
||||||
|
|
||||||
@@ -518,6 +528,10 @@ var tl_browser = {
|
|||||||
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
"tvt_sel": "select file ( for cut / copy / delete / ... )$NHotkey: S\">sel",
|
||||||
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
|
||||||
|
|
||||||
|
"m3u_add1": "song added to m3u playlist",
|
||||||
|
"m3u_addn": "{0} songs added to m3u playlist",
|
||||||
|
"m3u_clip": "m3u playlist now copied to clipboard\n\nyou should create a new textfile named something.m3u and paste the playlist in that document; this will make it playable",
|
||||||
|
|
||||||
"gt_vau": "don't show videos, just play the audio\">🎧",
|
"gt_vau": "don't show videos, just play the audio\">🎧",
|
||||||
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
|
||||||
"gt_crop": "center-crop thumbnails\">crop",
|
"gt_crop": "center-crop thumbnails\">crop",
|
||||||
|
|||||||
@@ -82,6 +82,19 @@ def get_ramdisk():
|
|||||||
return subdir(vol)
|
return subdir(vol)
|
||||||
|
|
||||||
if os.path.exists("/Volumes"):
|
if os.path.exists("/Volumes"):
|
||||||
|
sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sck.bind(("127.0.0.1", 2775))
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
print("waiting for 2775")
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
v = "/Volumes/cptd"
|
||||||
|
if os.path.exists(v):
|
||||||
|
return subdir(v)
|
||||||
|
|
||||||
# hdiutil eject /Volumes/cptd/
|
# hdiutil eject /Volumes/cptd/
|
||||||
devname, _ = chkcmd("hdiutil attach -nomount ram://131072".split())
|
devname, _ = chkcmd("hdiutil attach -nomount ram://131072".split())
|
||||||
devname = devname.strip()
|
devname = devname.strip()
|
||||||
@@ -97,6 +110,7 @@ def get_ramdisk():
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
sck.close()
|
||||||
return subdir("/Volumes/cptd")
|
return subdir("/Volumes/cptd")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(repr(ex))
|
print(repr(ex))
|
||||||
@@ -144,7 +158,7 @@ class Cfg(Namespace):
|
|||||||
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt ups_who zip_who"
|
ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt ups_who zip_who"
|
||||||
ka.update(**{k: 9 for k in ex.split()})
|
ka.update(**{k: 9 for k in ex.split()})
|
||||||
|
|
||||||
ex = "db_act forget_ip k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
ex = "db_act forget_ip k304 loris no304 nosubtle re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR"
|
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src zipmaxt R RS SR"
|
||||||
@@ -166,6 +180,7 @@ class Cfg(Namespace):
|
|||||||
v=v or [],
|
v=v or [],
|
||||||
c=c,
|
c=c,
|
||||||
E=E,
|
E=E,
|
||||||
|
bup_ck="sha512",
|
||||||
dbd="wal",
|
dbd="wal",
|
||||||
dk_salt="b" * 16,
|
dk_salt="b" * 16,
|
||||||
fk_salt="a" * 16,
|
fk_salt="a" * 16,
|
||||||
@@ -178,6 +193,8 @@ class Cfg(Namespace):
|
|||||||
mte={"a": True},
|
mte={"a": True},
|
||||||
mth={},
|
mth={},
|
||||||
mtp=[],
|
mtp=[],
|
||||||
|
put_ck="sha512",
|
||||||
|
put_name="put-{now.6f}-{cip}.bin",
|
||||||
mv_retry="0/0",
|
mv_retry="0/0",
|
||||||
rm_retry="0/0",
|
rm_retry="0/0",
|
||||||
s_rd_sz=256 * 1024,
|
s_rd_sz=256 * 1024,
|
||||||
|
|||||||
Reference in New Issue
Block a user