Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f42237f2c | ||
|
|
68289cfa54 | ||
|
|
42ea30270f | ||
|
|
ebbbbf3d82 | ||
|
|
27516e2d16 | ||
|
|
84bb6f915e | ||
|
|
46752f758a | ||
|
|
34c4c22e61 | ||
|
|
af2d0b8421 | ||
|
|
638b05a49a | ||
|
|
7a13e8a7fc | ||
|
|
d9fa74711d | ||
|
|
41867f578f | ||
|
|
0bf41ed4ef | ||
|
|
d080b4a731 | ||
|
|
ca4232ada9 | ||
|
|
ad348f91c9 | ||
|
|
990f915f42 | ||
|
|
53d720217b | ||
|
|
7a06ff480d | ||
|
|
3ef551f788 | ||
|
|
f0125cdc36 | ||
|
|
ed5f6736df | ||
|
|
15d8be0fae | ||
|
|
46f3e61360 | ||
|
|
87ad8c98d4 | ||
|
|
9bbdc4100f | ||
|
|
c80307e8ff | ||
|
|
c1d77e1041 | ||
|
|
d9e83650dc | ||
|
|
f6d635acd9 | ||
|
|
0dbd8a01ff | ||
|
|
8d755d41e0 | ||
|
|
190473bd32 | ||
|
|
030d1ec254 | ||
|
|
5a2b91a084 | ||
|
|
a50a05e4e7 | ||
|
|
6cb5a87c79 | ||
|
|
b9f89ca552 | ||
|
|
26c9fd5dea | ||
|
|
e81a9b6fe0 | ||
|
|
452450e451 | ||
|
|
419dd2d1c7 | ||
|
|
ee86b06676 | ||
|
|
953183f16d | ||
|
|
228f71708b | ||
|
|
621471a7cb | ||
|
|
8b58e951e3 | ||
|
|
1db489a0aa | ||
|
|
be65c3c6cf | ||
|
|
46e7fa31fe | ||
|
|
66e21bd499 | ||
|
|
8cab4c01fd | ||
|
|
d52038366b | ||
|
|
4fcfd87f5b | ||
|
|
f893c6baa4 | ||
|
|
9a45549b66 | ||
|
|
ae3a01038b | ||
|
|
e47a2a4ca2 | ||
|
|
95ea6d5f78 | ||
|
|
7d290f6b8f | ||
|
|
9db617ed5a | ||
|
|
514456940a | ||
|
|
33feefd9cd | ||
|
|
65e14cf348 | ||
|
|
1d61bcc4f3 | ||
|
|
c38bbaca3c | ||
|
|
246d245ebc | ||
|
|
f269a710e2 | ||
|
|
051998429c | ||
|
|
432cdd640f | ||
|
|
9ed9b0964e | ||
|
|
6a97b3526d | ||
|
|
451d757996 | ||
|
|
f9e9eba3b1 | ||
|
|
2a9a6aebd9 | ||
|
|
adbb6c449e | ||
|
|
3993605324 | ||
|
|
0ae574ec2c |
196
README.md
196
README.md
@@ -16,11 +16,6 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [thumbnails](#thumbnails) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [thumbnails](#thumbnails) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
|
||||||
|
|
||||||
|
|
||||||
## breaking changes \o/
|
|
||||||
|
|
||||||
this is the readme for v0.12 which has a different expression for volume permissions (`-v`); see [the v0.11.x readme](https://github.com/9001/copyparty/tree/15b59822112dda56cee576df30f331252fc62628#readme) for stuff regarding the [current stable release](https://github.com/9001/copyparty/releases/tag/v0.11.47)
|
|
||||||
|
|
||||||
|
|
||||||
## readme toc
|
## readme toc
|
||||||
|
|
||||||
* top
|
* top
|
||||||
@@ -41,11 +36,13 @@ this is the readme for v0.12 which has a different expression for volume permiss
|
|||||||
* [uploading](#uploading)
|
* [uploading](#uploading)
|
||||||
* [file-search](#file-search)
|
* [file-search](#file-search)
|
||||||
* [file manager](#file-manager)
|
* [file manager](#file-manager)
|
||||||
|
* [batch rename](#batch-rename)
|
||||||
* [markdown viewer](#markdown-viewer)
|
* [markdown viewer](#markdown-viewer)
|
||||||
* [other tricks](#other-tricks)
|
* [other tricks](#other-tricks)
|
||||||
* [searching](#searching)
|
* [searching](#searching)
|
||||||
* [search configuration](#search-configuration)
|
* [server config](#server-config)
|
||||||
* [database location](#database-location)
|
* [upload rules](#upload-rules)
|
||||||
|
* [database location](#database-location)upload rules
|
||||||
* [metadata from audio files](#metadata-from-audio-files)
|
* [metadata from audio files](#metadata-from-audio-files)
|
||||||
* [file parser plugins](#file-parser-plugins)
|
* [file parser plugins](#file-parser-plugins)
|
||||||
* [complete examples](#complete-examples)
|
* [complete examples](#complete-examples)
|
||||||
@@ -125,30 +122,31 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
* ☑ [accounts](#accounts-and-volumes)
|
* ☑ [accounts](#accounts-and-volumes)
|
||||||
* upload
|
* upload
|
||||||
* ☑ basic: plain multipart, ie6 support
|
* ☑ basic: plain multipart, ie6 support
|
||||||
* ☑ up2k: js, resumable, multithreaded
|
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
||||||
* ☑ stash: simple PUT filedropper
|
* ☑ stash: simple PUT filedropper
|
||||||
* ☑ unpost: undo/delete accidental uploads
|
* ☑ unpost: undo/delete accidental uploads
|
||||||
* ☑ symlink/discard existing files (content-matching)
|
* ☑ symlink/discard existing files (content-matching)
|
||||||
* download
|
* download
|
||||||
* ☑ single files in browser
|
* ☑ single files in browser
|
||||||
* ☑ folders as zip / tar files
|
* ☑ [folders as zip / tar files](#zip-downloads)
|
||||||
* ☑ FUSE client (read-only)
|
* ☑ FUSE client (read-only)
|
||||||
* browser
|
* browser
|
||||||
* ☑ navpane (directory tree sidebar)
|
* ☑ navpane (directory tree sidebar)
|
||||||
|
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
||||||
* ☑ audio player (with OS media controls)
|
* ☑ audio player (with OS media controls)
|
||||||
* ☑ thumbnails
|
* ☑ image gallery with webm player
|
||||||
|
* ☑ [thumbnails](#thumbnails)
|
||||||
* ☑ ...of images using Pillow
|
* ☑ ...of images using Pillow
|
||||||
* ☑ ...of videos using FFmpeg
|
* ☑ ...of videos using FFmpeg
|
||||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||||
* ☑ image gallery with webm player
|
|
||||||
* ☑ SPA (browse while uploading)
|
* ☑ SPA (browse while uploading)
|
||||||
* if you use the navpane to navigate, not folders in the file list
|
* if you use the navpane to navigate, not folders in the file list
|
||||||
* server indexing
|
* server indexing
|
||||||
* ☑ locate files by contents
|
* ☑ [locate files by contents](#file-search)
|
||||||
* ☑ search by name/path/date/size
|
* ☑ search by name/path/date/size
|
||||||
* ☑ search by ID3-tags etc.
|
* ☑ [search by ID3-tags etc.](#searching)
|
||||||
* markdown
|
* markdown
|
||||||
* ☑ viewer
|
* ☑ [viewer](#markdown-viewer)
|
||||||
* ☑ editor (sure why not)
|
* ☑ editor (sure why not)
|
||||||
|
|
||||||
|
|
||||||
@@ -181,7 +179,7 @@ small collection of user feedback
|
|||||||
* this is an msys2 bug, the regular windows edition of python is fine
|
* this is an msys2 bug, the regular windows edition of python is fine
|
||||||
|
|
||||||
* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
|
* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
|
||||||
* use `--hist` or the `hist` volflag (`-v [...]:chist=/tmp/foo`) to place the db inside the vm instead
|
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db inside the vm instead
|
||||||
|
|
||||||
|
|
||||||
# accounts and volumes
|
# accounts and volumes
|
||||||
@@ -250,13 +248,15 @@ the browser has the following hotkeys (assumes qwerty, ignores actual layout)
|
|||||||
* when viewing images / playing videos:
|
* when viewing images / playing videos:
|
||||||
* `J/L, Left/Right` prev/next file
|
* `J/L, Left/Right` prev/next file
|
||||||
* `Home/End` first/last file
|
* `Home/End` first/last file
|
||||||
|
* `S` toggle selection
|
||||||
|
* `R` rotate clockwise (shift=ccw)
|
||||||
* `Esc` close viewer
|
* `Esc` close viewer
|
||||||
* videos:
|
* videos:
|
||||||
* `U/O` skip 10sec back/forward
|
* `U/O` skip 10sec back/forward
|
||||||
* `P/K/Space` play/pause
|
* `P/K/Space` play/pause
|
||||||
* `F` fullscreen
|
* `F` fullscreen
|
||||||
* `C` continue playing next video
|
* `C` continue playing next video
|
||||||
* `R` loop
|
* `V` loop
|
||||||
* `M` mute
|
* `M` mute
|
||||||
* when the navpane is open:
|
* when the navpane is open:
|
||||||
* `A/D` adjust tree width
|
* `A/D` adjust tree width
|
||||||
@@ -373,6 +373,53 @@ if you have the required permissions, you can cut/paste, rename, and delete file
|
|||||||
you can move files across browser tabs (cut in one tab, paste in another)
|
you can move files across browser tabs (cut in one tab, paste in another)
|
||||||
|
|
||||||
|
|
||||||
|
## batch rename
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
select some files and press F2 to bring up the rename UI
|
||||||
|
|
||||||
|
quick explanation of the buttons,
|
||||||
|
* `[✅ apply rename]` confirms and begins renaming
|
||||||
|
* `[❌ cancel]` aborts and closes the rename window
|
||||||
|
* `[↺ reset]` reverts any filename changes back to the original name
|
||||||
|
* `[decode]` does a URL-decode on the filename, fixing stuff like `&` and `%20`
|
||||||
|
* `[advanced]` toggles advanced mode
|
||||||
|
|
||||||
|
advanced mode: rename files based on rules to decide the new names, based on the original name (regex), or based on the tags collected from the file (artist/title/...), or a mix of both
|
||||||
|
|
||||||
|
in advanced mode,
|
||||||
|
* `[case]` toggles case-sensitive regex
|
||||||
|
* `regex` is the regex pattern to apply to the original filename; any files which don't match will be skipped
|
||||||
|
* `format` is the new filename, taking values from regex capturing groups and/or from file tags
|
||||||
|
* very loosely based on foobar2000 syntax
|
||||||
|
* `presets` lets you save rename rules for later
|
||||||
|
|
||||||
|
available functions:
|
||||||
|
* `$lpad(text, length, pad_char)`
|
||||||
|
* `$rpad(text, length, pad_char)`
|
||||||
|
|
||||||
|
so,
|
||||||
|
|
||||||
|
say you have a file named [`meganeko - Eclipse - 07 Sirius A.mp3`](https://www.youtube.com/watch?v=-dtb0vDPruI) (absolutely fantastic album btw) and the tags are: `Album:Eclipse`, `Artist:meganeko`, `Title:Sirius A`, `tn:7`
|
||||||
|
|
||||||
|
you could use just regex to rename it:
|
||||||
|
* `regex` = `(.*) - (.*) - ([0-9]{2}) (.*)`
|
||||||
|
* `format` = `(3). (1) - (4)`
|
||||||
|
* `output` = `07. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
or you could use just tags:
|
||||||
|
* `format` = `$lpad((tn),2,0). (artist) - (title).(ext)`
|
||||||
|
* `output` = `7. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
or a mix of both:
|
||||||
|
* `regex` = ` - ([0-9]{2}) `
|
||||||
|
* `format` = `(1). (artist) - (title).(ext)`
|
||||||
|
* `output` = `07. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
the metadata keys you can use in the format field are the ones in the file-browser table header (whatever is collected with `-mte` and `-mtp`)
|
||||||
|
|
||||||
|
|
||||||
## markdown viewer
|
## markdown viewer
|
||||||
|
|
||||||

|

|
||||||
@@ -387,7 +434,7 @@ you can move files across browser tabs (cut in one tab, paste in another)
|
|||||||
* if you are using media hotkeys to switch songs and are getting tired of seeing the OSD popup which Windows doesn't let you disable, consider https://ocv.me/dev/?media-osd-bgone.ps1
|
* if you are using media hotkeys to switch songs and are getting tired of seeing the OSD popup which Windows doesn't let you disable, consider https://ocv.me/dev/?media-osd-bgone.ps1
|
||||||
|
|
||||||
|
|
||||||
# searching
|
## searching
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -399,12 +446,12 @@ path/name queries are space-separated, AND'ed together, and words are negated wi
|
|||||||
* path: `shibayan -bossa` finds all files where one of the folders contain `shibayan` but filters out any results where `bossa` exists somewhere in the path
|
* path: `shibayan -bossa` finds all files where one of the folders contain `shibayan` but filters out any results where `bossa` exists somewhere in the path
|
||||||
* name: `demetori styx` gives you [good stuff](https://www.youtube.com/watch?v=zGh0g14ZJ8I&list=PL3A147BD151EE5218&index=9)
|
* name: `demetori styx` gives you [good stuff](https://www.youtube.com/watch?v=zGh0g14ZJ8I&list=PL3A147BD151EE5218&index=9)
|
||||||
|
|
||||||
add `-e2ts` to also scan/index tags from music files:
|
add the argument `-e2ts` to also scan/index tags from music files, which brings us over to:
|
||||||
|
|
||||||
|
|
||||||
## search configuration
|
# server config
|
||||||
|
|
||||||
searching relies on two databases, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`). Configuration can be done through arguments, volume flags, or a mix of both.
|
file indexing relies on two databases, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`). Configuration can be done through arguments, volume flags, or a mix of both.
|
||||||
|
|
||||||
through arguments:
|
through arguments:
|
||||||
* `-e2d` enables file indexing on upload
|
* `-e2d` enables file indexing on upload
|
||||||
@@ -415,20 +462,60 @@ through arguments:
|
|||||||
* `-e2tsr` deletes all existing tags, does a full reindex
|
* `-e2tsr` deletes all existing tags, does a full reindex
|
||||||
|
|
||||||
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
|
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
|
||||||
* `-v ~/music::r:ce2dsa:ce2tsr` does a full reindex of everything on startup
|
* `-v ~/music::r:c,e2dsa:c,e2tsr` does a full reindex of everything on startup
|
||||||
* `-v ~/music::r:cd2d` disables **all** indexing, even if any `-e2*` are on
|
* `-v ~/music::r:c,d2d` disables **all** indexing, even if any `-e2*` are on
|
||||||
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
|
* `-v ~/music::r:c,d2t` disables all `-e2t*` (tags), does not affect `-e2d*`
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
||||||
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
|
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
|
||||||
|
|
||||||
you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `cdhash`, this has the following consequences:
|
you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `:c,dhash`, this has the following consequences:
|
||||||
* initial indexing is way faster, especially when the volume is on a network disk
|
* initial indexing is way faster, especially when the volume is on a network disk
|
||||||
* makes it impossible to [file-search](#file-search)
|
* makes it impossible to [file-search](#file-search)
|
||||||
* if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
|
* if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
|
||||||
|
|
||||||
if you set `--no-hash`, you can enable hashing for specific volumes using flag `cehash`
|
if you set `--no-hash`, you can enable hashing for specific volumes using flag `:c,ehash`
|
||||||
|
|
||||||
|
|
||||||
|
## upload rules
|
||||||
|
|
||||||
|
you can set upload rules using volume flags, some examples:
|
||||||
|
|
||||||
|
* `:c,sz=1k-3m` sets allowed filesize between 1 KiB and 3 MiB inclusive (suffixes: b, k, m, g)
|
||||||
|
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
|
||||||
|
* `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1)
|
||||||
|
* `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format
|
||||||
|
* if someone uploads to `/foo/bar` the path would be rewritten to `/foo/bar/2021/08/06/23` for example
|
||||||
|
* but the actual value is not verified, just the structure, so the uploader can choose any values which conform to the format string
|
||||||
|
* just to avoid additional complexity in up2k which is enough of a mess already
|
||||||
|
* `:c,lifetime=300` delete uploaded files when they become 5 minutes old
|
||||||
|
|
||||||
|
you can also set transaction limits which apply per-IP and per-volume, but these assume `-j 1` (default) otherwise the limits will be off, for example `-j 4` would allow anywhere between 1x and 4x the limits you set depending on which processing node the client gets routed to
|
||||||
|
|
||||||
|
* `:c,maxn=250,3600` allows 250 files over 1 hour from each IP (tracked per-volume)
|
||||||
|
* `:c,maxb=1g,300` allows 1 GiB total over 5 minutes from each IP (tracked per-volume)
|
||||||
|
|
||||||
|
|
||||||
|
## compress uploads
|
||||||
|
|
||||||
|
files can be autocompressed on upload, either on user-request (if config allows) or forced by server-config
|
||||||
|
|
||||||
|
* volume flag `gz` allows gz compression
|
||||||
|
* volume flag `xz` allows lzma compression
|
||||||
|
* volume flag `pk` **forces** compression on all files
|
||||||
|
* url parameter `pk` requests compression with server-default algorithm
|
||||||
|
* url parameter `gz` or `xz` requests compression with a specific algorithm
|
||||||
|
* url parameter `xz` requests xz compression
|
||||||
|
|
||||||
|
things to note,
|
||||||
|
* the `gz` and `xz` arguments take a single optional argument, the compression level (range 0 to 9)
|
||||||
|
* the `pk` volume flag takes the optional argument `ALGORITHM,LEVEL` which will then be forced for all uploads, for example `gz,9` or `xz,0`
|
||||||
|
* default compression is gzip level 9
|
||||||
|
* all upload methods except up2k are supported
|
||||||
|
* the files will be indexed after compression, so dupe-detection and file-search will not work as expected
|
||||||
|
|
||||||
|
some examples,
|
||||||
|
|
||||||
|
|
||||||
## database location
|
## database location
|
||||||
@@ -436,7 +523,7 @@ if you set `--no-hash`, you can enable hashing for specific volumes using flag `
|
|||||||
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
||||||
|
|
||||||
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
||||||
* `--hist ~/.cache/copyparty -v ~/music::r:chist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
* `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* markdown edits are always stored in a local `.hist` subdirectory
|
* markdown edits are always stored in a local `.hist` subdirectory
|
||||||
@@ -447,10 +534,12 @@ note:
|
|||||||
## metadata from audio files
|
## metadata from audio files
|
||||||
|
|
||||||
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
||||||
* `-v ~/music::r:cmte=title,artist` indexes and displays *title* followed by *artist*
|
* `-v ~/music::r:c,mte=title,artist` indexes and displays *title* followed by *artist*
|
||||||
|
|
||||||
if you add/remove a tag from `mte` you will need to run with `-e2tsr` once to rebuild the database, otherwise only new files will be affected
|
if you add/remove a tag from `mte` you will need to run with `-e2tsr` once to rebuild the database, otherwise only new files will be affected
|
||||||
|
|
||||||
|
but instead of using `-mte`, `-mth` is a better way to hide tags in the browser: these tags will not be displayed by default, but they still get indexed and become searchable, and users can choose to unhide them in the settings pane
|
||||||
|
|
||||||
`-mtm` can be used to add or redefine a metadata mapping, say you have media files with `foo` and `bar` tags and you want them to display as `qux` in the browser (preferring `foo` if both are present), then do `-mtm qux=foo,bar` and now you can `-mte artist,title,qux`
|
`-mtm` can be used to add or redefine a metadata mapping, say you have media files with `foo` and `bar` tags and you want them to display as `qux` in the browser (preferring `foo` if both are present), then do `-mtm qux=foo,bar` and now you can `-mte artist,title,qux`
|
||||||
|
|
||||||
tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric value
|
tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric value
|
||||||
@@ -471,7 +560,7 @@ copyparty can invoke external programs to collect additional metadata for files
|
|||||||
|
|
||||||
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
|
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
|
||||||
* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
|
* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
|
||||||
* `-v ~/music::r:cmtp=.bpm=~/bin/audio-bpm.py:cmtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
|
* `-v ~/music::r:c,mtp=.bpm=~/bin/audio-bpm.py:c,mtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
|
||||||
|
|
||||||
*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare)
|
*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare)
|
||||||
|
|
||||||
@@ -491,28 +580,32 @@ copyparty can invoke external programs to collect additional metadata for files
|
|||||||
|
|
||||||
`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
|
`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
|
||||||
|
|
||||||
| feature | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
| feature | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||||
| --------------- | --- | --- | ---- | ---- | ----- | ---- | --- | ---- |
|
| --------------- | --- | ---- | ---- | ---- | ----- | ---- | --- | ---- |
|
||||||
| browse files | yep | yep | yep | yep | yep | yep | yep | yep |
|
| browse files | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| basic uploader | yep | yep | yep | yep | yep | yep | yep | yep |
|
| thumbnail view | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| make directory | yep | yep | yep | yep | yep | yep | yep | yep |
|
| basic uploader | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| send message | yep | yep | yep | yep | yep | yep | yep | yep |
|
| up2k | - | - | `*1` | `*1` | yep | yep | yep | yep |
|
||||||
| set sort order | - | yep | yep | yep | yep | yep | yep | yep |
|
| make directory | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| zip selection | - | yep | yep | yep | yep | yep | yep | yep |
|
| send message | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| navpane | - | - | `*1` | yep | yep | yep | yep | yep |
|
| set sort order | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| up2k | - | - | yep | yep | yep | yep | yep | yep |
|
| zip selection | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| markdown editor | - | - | yep | yep | yep | yep | yep | yep |
|
| file rename | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| markdown viewer | - | - | yep | yep | yep | yep | yep | yep |
|
| file cut/paste | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| play mp3/m4a | - | yep | yep | yep | yep | yep | yep | yep |
|
| navpane | - | `*2` | yep | yep | yep | yep | yep | yep |
|
||||||
| play ogg/opus | - | - | - | - | yep | yep | `*2` | yep |
|
| image viewer | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| thumbnail view | - | - | - | - | yep | yep | yep | yep |
|
| video player | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
| image viewer | - | - | - | - | yep | yep | yep | yep |
|
| markdown editor | - | - | yep | yep | yep | yep | yep | yep |
|
||||||
| **= feature =** | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
| markdown viewer | - | - | yep | yep | yep | yep | yep | yep |
|
||||||
|
| play mp3/m4a | - | yep | yep | yep | yep | yep | yep | yep |
|
||||||
|
| play ogg/opus | - | - | - | - | yep | yep | `*3` | yep |
|
||||||
|
| **= feature =** | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||||
|
|
||||||
* internet explorer 6 to 8 behave the same
|
* internet explorer 6 to 8 behave the same
|
||||||
* firefox 52 and chrome 49 are the last winxp versions
|
* firefox 52 and chrome 49 are the last winxp versions
|
||||||
* `*1` only public folders (login session is dropped) and no history / back-button
|
* `*1` yes, but extremely slow (ie10: 1 MiB/s, ie11: 270 KiB/s)
|
||||||
* `*2` using a wasm decoder which can sometimes get stuck and consumes a bit more power
|
* `*2` causes a full-page refresh on each navigation
|
||||||
|
* `*3` using a wasm decoder which can sometimes get stuck and consumes a bit more power
|
||||||
|
|
||||||
quick summary of more eccentric web-browsers trying to view a directory index:
|
quick summary of more eccentric web-browsers trying to view a directory index:
|
||||||
|
|
||||||
@@ -524,6 +617,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
|
|||||||
| **lynx** (2.8.9/macports) | can browse, login, upload/mkdir/msg |
|
| **lynx** (2.8.9/macports) | can browse, login, upload/mkdir/msg |
|
||||||
| **w3m** (0.5.3/macports) | can browse, login, upload at 100kB/s, mkdir/msg |
|
| **w3m** (0.5.3/macports) | can browse, login, upload at 100kB/s, mkdir/msg |
|
||||||
| **netsurf** (3.10/arch) | is basically ie6 with much better css (javascript has almost no effect) |
|
| **netsurf** (3.10/arch) | is basically ie6 with much better css (javascript has almost no effect) |
|
||||||
|
| **opera** (11.60/winxp) | OK: thumbnails, image-viewer, zip-selection, rename/cut/paste. NG: up2k, navpane, markdown, audio |
|
||||||
| **ie4** and **netscape** 4.0 | can browse (text is yellow on white), upload with `?b=u` |
|
| **ie4** and **netscape** 4.0 | can browse (text is yellow on white), upload with `?b=u` |
|
||||||
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
|
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
|
||||||
|
|
||||||
@@ -535,11 +629,11 @@ quick summary of more eccentric web-browsers trying to view a directory index:
|
|||||||
* `var xhr = new XMLHttpRequest(); xhr.open('POST', 'https://127.0.0.1:3923/msgs?raw'); xhr.send('foo');`
|
* `var xhr = new XMLHttpRequest(); xhr.open('POST', 'https://127.0.0.1:3923/msgs?raw'); xhr.send('foo');`
|
||||||
|
|
||||||
* curl/wget: upload some files (post=file, chunk=stdin)
|
* curl/wget: upload some files (post=file, chunk=stdin)
|
||||||
* `post(){ curl -b cppwd=wark http://127.0.0.1:3923/ -F act=bput -F f=@"$1";}`
|
* `post(){ curl -b cppwd=wark -F act=bput -F f=@"$1" http://127.0.0.1:3923/;}`
|
||||||
`post movie.mkv`
|
`post movie.mkv`
|
||||||
* `post(){ wget --header='Cookie: cppwd=wark' http://127.0.0.1:3923/?raw --post-file="$1" -O-;}`
|
* `post(){ wget --header='Cookie: cppwd=wark' --post-file="$1" -O- http://127.0.0.1:3923/?raw;}`
|
||||||
`post movie.mkv`
|
`post movie.mkv`
|
||||||
* `chunk(){ curl -b cppwd=wark http://127.0.0.1:3923/ -T-;}`
|
* `chunk(){ curl -b cppwd=wark -T- http://127.0.0.1:3923/;}`
|
||||||
`chunk <movie.mkv`
|
`chunk <movie.mkv`
|
||||||
|
|
||||||
* FUSE: mount a copyparty server as a local filesystem
|
* FUSE: mount a copyparty server as a local filesystem
|
||||||
|
|||||||
@@ -61,3 +61,8 @@ cd /mnt/nas/music/.hist
|
|||||||
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy key
|
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy key
|
||||||
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
|
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# [`prisonparty.sh`](prisonparty.sh)
|
||||||
|
* run copyparty in a chroot, preventing any accidental file access
|
||||||
|
* creates bindmounts for /bin, /lib, and so on, see `sysdirs=`
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ some of these rely on libraries which are not MIT-compatible
|
|||||||
|
|
||||||
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
||||||
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
||||||
|
* [media-hash.py](./media-hash.py) generates checksums for audio and video streams; uses FFmpeg (LGPL or GPL)
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
@@ -18,7 +19,10 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
|||||||
|
|
||||||
# usage from copyparty
|
# usage from copyparty
|
||||||
|
|
||||||
`copyparty -e2dsa -e2ts -mtp key=f,audio-key.py -mtp .bpm=f,audio-bpm.py`
|
`copyparty -e2dsa -e2ts` followed by any combination of these:
|
||||||
|
* `-mtp key=f,audio-key.py`
|
||||||
|
* `-mtp .bpm=f,audio-bpm.py`
|
||||||
|
* `-mtp ahash,vhash=f,media-hash.py`
|
||||||
|
|
||||||
* `f,` makes the detected value replace any existing values
|
* `f,` makes the detected value replace any existing values
|
||||||
* the `.` in `.bpm` indicates numeric value
|
* the `.` in `.bpm` indicates numeric value
|
||||||
@@ -29,6 +33,9 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
|||||||
## usage with volume-flags
|
## usage with volume-flags
|
||||||
|
|
||||||
instead of affecting all volumes, you can set the options for just one volume like so:
|
instead of affecting all volumes, you can set the options for just one volume like so:
|
||||||
```
|
|
||||||
copyparty -v /mnt/nas/music:/music:r:cmtp=key=f,audio-key.py:cmtp=.bpm=f,audio-bpm.py:ce2dsa:ce2ts
|
`copyparty -v /mnt/nas/music:/music:r:c,e2dsa:c,e2ts` immediately followed by any combination of these:
|
||||||
```
|
|
||||||
|
* `:c,mtp=key=f,audio-key.py`
|
||||||
|
* `:c,mtp=.bpm=f,audio-bpm.py`
|
||||||
|
* `:c,mtp=ahash,vhash=f,media-hash.py`
|
||||||
|
|||||||
73
bin/mtag/media-hash.py
Normal file
73
bin/mtag/media-hash.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
try:
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
except:
|
||||||
|
|
||||||
|
def fsenc(p):
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
dep: ffmpeg
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def det():
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-i", fsenc(sys.argv[1]),
|
||||||
|
"-f", "framemd5",
|
||||||
|
"-"
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
p = sp.Popen(cmd, stdout=sp.PIPE)
|
||||||
|
# ps = io.TextIOWrapper(p.stdout, encoding="utf-8")
|
||||||
|
ps = p.stdout
|
||||||
|
|
||||||
|
chans = {}
|
||||||
|
for ln in ps:
|
||||||
|
if ln.startswith(b"#stream#"):
|
||||||
|
break
|
||||||
|
|
||||||
|
m = re.match(r"^#media_type ([0-9]): ([a-zA-Z])", ln.decode("utf-8"))
|
||||||
|
if m:
|
||||||
|
chans[m.group(1)] = m.group(2)
|
||||||
|
|
||||||
|
hashers = [hashlib.sha512(), hashlib.sha512()]
|
||||||
|
for ln in ps:
|
||||||
|
n = int(ln[:1])
|
||||||
|
v = ln.rsplit(b",", 1)[-1].strip()
|
||||||
|
hashers[n].update(v)
|
||||||
|
|
||||||
|
r = {}
|
||||||
|
for k, v in chans.items():
|
||||||
|
dg = hashers[int(k)].digest()[:12]
|
||||||
|
dg = base64.urlsafe_b64encode(dg).decode("ascii")
|
||||||
|
r[v[0].lower() + "hash"] = dg
|
||||||
|
|
||||||
|
print(json.dumps(r, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
det()
|
||||||
|
except:
|
||||||
|
pass # mute
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
39
bin/mtag/res/yt-ipr.conf
Normal file
39
bin/mtag/res/yt-ipr.conf
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# example config file to use copyparty as a youtube manifest collector,
|
||||||
|
# use with copyparty like: python copyparty.py -c yt-ipr.conf
|
||||||
|
#
|
||||||
|
# see docs/example.conf for a better explanation of the syntax, but
|
||||||
|
# newlines are block separators, so adding blank lines inside a volume definition is bad
|
||||||
|
# (use comments as separators instead)
|
||||||
|
|
||||||
|
|
||||||
|
# create user ed, password wark
|
||||||
|
u ed:wark
|
||||||
|
|
||||||
|
|
||||||
|
# create a volume at /ytm which stores files at ./srv/ytm
|
||||||
|
./srv/ytm
|
||||||
|
/ytm
|
||||||
|
# write-only, but read-write for user ed
|
||||||
|
w
|
||||||
|
rw ed
|
||||||
|
# rescan the volume on startup
|
||||||
|
c e2dsa
|
||||||
|
# collect tags from all new files since last scan
|
||||||
|
c e2ts
|
||||||
|
# optionally enable compression to make the files 50% smaller
|
||||||
|
c pk
|
||||||
|
# only allow uploads which are between 16k and 1m large
|
||||||
|
c sz=16k-1m
|
||||||
|
# allow up to 10 uploads over 5 minutes from each ip
|
||||||
|
c maxn=10,300
|
||||||
|
# move uploads into subfolders: YEAR-MONTH / DAY-HOUR / <upload>
|
||||||
|
c rotf=%Y-%m/%d-%H
|
||||||
|
# delete uploads when they are 24 hours old
|
||||||
|
c lifetime=86400
|
||||||
|
# add the parser and tell copyparty what tags it can expect from it
|
||||||
|
c mtp=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires=bin/mtag/yt-ipr.py
|
||||||
|
# decide which tags we want to index and in what order
|
||||||
|
c mte=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires
|
||||||
|
|
||||||
|
|
||||||
|
# create any other volumes you'd like down here, or merge this with an existing config file
|
||||||
47
bin/mtag/res/yt-ipr.user.js
Normal file
47
bin/mtag/res/yt-ipr.user.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name youtube-playerdata-hub
|
||||||
|
// @match https://youtube.com/*
|
||||||
|
// @match https://*.youtube.com/*
|
||||||
|
// @version 1.0
|
||||||
|
// @grant GM_addStyle
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
var server = 'https://127.0.0.1:3923/ytm?pw=wark',
|
||||||
|
interval = 60; // sec
|
||||||
|
|
||||||
|
var sent = {};
|
||||||
|
function send(txt, mf_url, desc) {
|
||||||
|
if (sent[mf_url])
|
||||||
|
return;
|
||||||
|
|
||||||
|
fetch(server + '&_=' + Date.now(), { method: "PUT", body: txt });
|
||||||
|
console.log('[yt-pdh] yeet %d bytes, %s', txt.length, desc);
|
||||||
|
sent[mf_url] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collect() {
|
||||||
|
try {
|
||||||
|
var pd = document.querySelector('ytd-watch-flexy');
|
||||||
|
if (!pd)
|
||||||
|
return console.log('[yt-pdh] no video found');
|
||||||
|
|
||||||
|
pd = pd.playerData;
|
||||||
|
var mu = pd.streamingData.dashManifestUrl || pd.streamingData.hlsManifestUrl;
|
||||||
|
if (!mu || !mu.length)
|
||||||
|
return console.log('[yt-pdh] no manifest found');
|
||||||
|
|
||||||
|
var desc = pd.videoDetails.videoId + ', ' + pd.videoDetails.title;
|
||||||
|
send(JSON.stringify(pd), mu, desc);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log("[yt-pdh]", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setInterval(collect, interval * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var scr = document.createElement('script');
|
||||||
|
scr.textContent = '(' + main.toString() + ')();';
|
||||||
|
(document.head || document.getElementsByTagName('head')[0]).appendChild(scr);
|
||||||
|
console.log('[yt-pdh] a');
|
||||||
198
bin/mtag/yt-ipr.py
Normal file
198
bin/mtag/yt-ipr.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import gzip
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import string
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
"""
|
||||||
|
youtube initial player response
|
||||||
|
|
||||||
|
it's probably best to use this through a config file; see res/yt-ipr.conf
|
||||||
|
|
||||||
|
but if you want to use plain arguments instead then:
|
||||||
|
-v srv/ytm:ytm:w:rw,ed
|
||||||
|
:c,e2ts:c,e2dsa
|
||||||
|
:c,sz=16k-1m:c,maxn=10,300:c,rotf=%Y-%m/%d-%H
|
||||||
|
:c,mtp=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires=bin/mtag/yt-ipr.py
|
||||||
|
:c,mte=yt-id,yt-title,yt-author,yt-channel,yt-views,yt-private,yt-manifest,yt-expires
|
||||||
|
|
||||||
|
see res/yt-ipr.user.js for the example userscript to go with this
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
with gzip.open(sys.argv[1], "rt", encoding="utf-8", errors="replace") as f:
|
||||||
|
txt = f.read()
|
||||||
|
except:
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8", errors="replace") as f:
|
||||||
|
txt = f.read()
|
||||||
|
|
||||||
|
txt = "{" + txt.split("{", 1)[1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
pd = json.loads(txt)
|
||||||
|
except json.decoder.JSONDecodeError as ex:
|
||||||
|
pd = json.loads(txt[: ex.pos])
|
||||||
|
|
||||||
|
# print(json.dumps(pd, indent=2))
|
||||||
|
|
||||||
|
if "videoDetails" in pd:
|
||||||
|
parse_youtube(pd)
|
||||||
|
else:
|
||||||
|
parse_freg(pd)
|
||||||
|
|
||||||
|
|
||||||
|
def get_expiration(url):
|
||||||
|
et = re.search(r"[?&]expire=([0-9]+)", url).group(1)
|
||||||
|
et = datetime.utcfromtimestamp(int(et))
|
||||||
|
return et.strftime("%Y-%m-%d, %H:%M")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_youtube(pd):
|
||||||
|
vd = pd["videoDetails"]
|
||||||
|
sd = pd["streamingData"]
|
||||||
|
|
||||||
|
et = sd["adaptiveFormats"][0]["url"]
|
||||||
|
et = get_expiration(et)
|
||||||
|
|
||||||
|
mf = []
|
||||||
|
if "dashManifestUrl" in sd:
|
||||||
|
mf.append("dash")
|
||||||
|
if "hlsManifestUrl" in sd:
|
||||||
|
mf.append("hls")
|
||||||
|
|
||||||
|
r = {
|
||||||
|
"yt-id": vd["videoId"],
|
||||||
|
"yt-title": vd["title"],
|
||||||
|
"yt-author": vd["author"],
|
||||||
|
"yt-channel": vd["channelId"],
|
||||||
|
"yt-views": vd["viewCount"],
|
||||||
|
"yt-private": vd["isPrivate"],
|
||||||
|
# "yt-expires": sd["expiresInSeconds"],
|
||||||
|
"yt-manifest": ",".join(mf),
|
||||||
|
"yt-expires": et,
|
||||||
|
}
|
||||||
|
print(json.dumps(r))
|
||||||
|
|
||||||
|
freg_conv(pd)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_freg(pd):
|
||||||
|
md = pd["metadata"]
|
||||||
|
r = {
|
||||||
|
"yt-id": md["id"],
|
||||||
|
"yt-title": md["title"],
|
||||||
|
"yt-author": md["channelName"],
|
||||||
|
"yt-channel": md["channelURL"].strip("/").split("/")[-1],
|
||||||
|
"yt-expires": get_expiration(list(pd["video"].values())[0]),
|
||||||
|
}
|
||||||
|
print(json.dumps(r))
|
||||||
|
|
||||||
|
|
||||||
|
def freg_conv(pd):
|
||||||
|
# based on getURLs.js v1.5 (2021-08-07)
|
||||||
|
# fmt: off
|
||||||
|
priority = {
|
||||||
|
"video": [
|
||||||
|
337, 315, 266, 138, # 2160p60
|
||||||
|
313, 336, # 2160p
|
||||||
|
308, # 1440p60
|
||||||
|
271, 264, # 1440p
|
||||||
|
335, 303, 299, # 1080p60
|
||||||
|
248, 169, 137, # 1080p
|
||||||
|
334, 302, 298, # 720p60
|
||||||
|
247, 136 # 720p
|
||||||
|
],
|
||||||
|
"audio": [
|
||||||
|
251, 141, 171, 140, 250, 249, 139
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
vid_id = pd["videoDetails"]["videoId"]
|
||||||
|
chan_id = pd["videoDetails"]["channelId"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
thumb_url = pd["microformat"]["playerMicroformatRenderer"]["thumbnail"]["thumbnails"][0]["url"]
|
||||||
|
start_ts = pd["microformat"]["playerMicroformatRenderer"]["liveBroadcastDetails"]["startTimestamp"]
|
||||||
|
except:
|
||||||
|
thumb_url = f"https://img.youtube.com/vi/{vid_id}/maxresdefault.jpg"
|
||||||
|
start_ts = ""
|
||||||
|
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
"title": pd["videoDetails"]["title"],
|
||||||
|
"id": vid_id,
|
||||||
|
"channelName": pd["videoDetails"]["author"],
|
||||||
|
"channelURL": "https://www.youtube.com/channel/" + chan_id,
|
||||||
|
"description": pd["videoDetails"]["shortDescription"],
|
||||||
|
"thumbnailUrl": thumb_url,
|
||||||
|
"startTimestamp": start_ts,
|
||||||
|
}
|
||||||
|
|
||||||
|
if [x for x in vid_id if x not in string.ascii_letters + string.digits + "_-"]:
|
||||||
|
print(f"malicious json", file=sys.stderr)
|
||||||
|
return
|
||||||
|
|
||||||
|
basepath = os.path.dirname(sys.argv[1])
|
||||||
|
|
||||||
|
thumb_fn = f"{basepath}/{vid_id}.jpg"
|
||||||
|
tmp_fn = f"{thumb_fn}.{os.getpid()}"
|
||||||
|
if not os.path.exists(thumb_fn) and (
|
||||||
|
thumb_url.startswith("https://img.youtube.com/vi/")
|
||||||
|
or thumb_url.startswith("https://i.ytimg.com/vi/")
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(thumb_url) as fi:
|
||||||
|
with open(tmp_fn, "wb") as fo:
|
||||||
|
fo.write(fi.read())
|
||||||
|
|
||||||
|
os.rename(tmp_fn, thumb_fn)
|
||||||
|
except:
|
||||||
|
if os.path.exists(tmp_fn):
|
||||||
|
os.unlink(tmp_fn)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(thumb_fn, "rb") as f:
|
||||||
|
thumb = base64.b64encode(f.read()).decode("ascii")
|
||||||
|
except:
|
||||||
|
thumb = "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k="
|
||||||
|
|
||||||
|
metadata["thumbnail"] = "data:image/jpeg;base64," + thumb
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
"metadata": metadata,
|
||||||
|
"version": "1.5",
|
||||||
|
"createTime": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for stream, itags in priority.items():
|
||||||
|
for itag in itags:
|
||||||
|
url = None
|
||||||
|
for afmt in pd["streamingData"]["adaptiveFormats"]:
|
||||||
|
if itag == afmt["itag"]:
|
||||||
|
url = afmt["url"]
|
||||||
|
break
|
||||||
|
|
||||||
|
if url:
|
||||||
|
ret[stream] = {itag: url}
|
||||||
|
break
|
||||||
|
|
||||||
|
fn = f"{basepath}/{vid_id}.urls.json"
|
||||||
|
with open(fn, "w", encoding="utf-8", errors="replace") as f:
|
||||||
|
f.write(json.dumps(ret, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except:
|
||||||
|
# raise
|
||||||
|
pass
|
||||||
99
bin/prisonparty.sh
Normal file
99
bin/prisonparty.sh
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# runs copyparty (or any other program really) in a chroot
|
||||||
|
#
|
||||||
|
# assumption: these directories, and everything within, are owned by root
|
||||||
|
sysdirs=( /bin /lib /lib32 /lib64 /sbin /usr )
|
||||||
|
|
||||||
|
|
||||||
|
# error-handler
|
||||||
|
help() { cat <<'EOF'
|
||||||
|
|
||||||
|
usage:
|
||||||
|
./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- copyparty-sfx.py [...]"
|
||||||
|
|
||||||
|
example:
|
||||||
|
./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- copyparty-sfx.py -v /mnt/nas/music::rwmd"
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# read arguments
|
||||||
|
trap help EXIT
|
||||||
|
jail="$(realpath "$1")"; shift
|
||||||
|
uid="$1"; shift
|
||||||
|
gid="$1"; shift
|
||||||
|
|
||||||
|
vols=()
|
||||||
|
while true; do
|
||||||
|
v="$1"; shift
|
||||||
|
[ "$v" = -- ] && break # end of volumes
|
||||||
|
[ "$#" -eq 0 ] && break # invalid usage
|
||||||
|
vols+=( "$(realpath "$v")" )
|
||||||
|
done
|
||||||
|
pybin="$1"; shift
|
||||||
|
pybin="$(realpath "$pybin")"
|
||||||
|
cpp="$1"; shift
|
||||||
|
cpp="$(realpath "$cpp")"
|
||||||
|
cppdir="$(dirname "$cpp")"
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
|
|
||||||
|
# debug/vis
|
||||||
|
echo
|
||||||
|
echo "chroot-dir = $jail"
|
||||||
|
echo "user:group = $uid:$gid"
|
||||||
|
echo " copyparty = $cpp"
|
||||||
|
echo
|
||||||
|
printf '\033[33m%s\033[0m\n' "copyparty can access these folders and all their subdirectories:"
|
||||||
|
for v in "${vols[@]}"; do
|
||||||
|
printf '\033[36m ├─\033[0m %s \033[36m ── added by (You)\033[0m\n' "$v"
|
||||||
|
done
|
||||||
|
printf '\033[36m ├─\033[0m %s \033[36m ── where the copyparty binary is\033[0m\n' "$cppdir"
|
||||||
|
printf '\033[36m ╰─\033[0m %s \033[36m ── the folder you are currently in\033[0m\n' "$PWD"
|
||||||
|
vols+=("$cppdir" "$PWD")
|
||||||
|
echo
|
||||||
|
|
||||||
|
|
||||||
|
# remove any trailing slashes
|
||||||
|
jail="${jail%/}"
|
||||||
|
cppdir="${cppdir%/}"
|
||||||
|
|
||||||
|
|
||||||
|
# bind-mount system directories and volumes
|
||||||
|
printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | LC_ALL=C sort |
|
||||||
|
while IFS= read -r v; do
|
||||||
|
[ -e "$v" ] || {
|
||||||
|
# printf '\033[1;31mfolder does not exist:\033[0m %s\n' "/$v"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i1=$(stat -c%D.%i "$v" 2>/dev/null || echo a)
|
||||||
|
i2=$(stat -c%D.%i "$jail$v" 2>/dev/null || echo b)
|
||||||
|
[ $i1 = $i2 ] && continue
|
||||||
|
|
||||||
|
mkdir -p "$jail$v"
|
||||||
|
mount --bind "$v" "$jail$v"
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# create a tmp
|
||||||
|
mkdir -p "$jail/tmp"
|
||||||
|
chmod 777 "$jail/tmp"
|
||||||
|
|
||||||
|
|
||||||
|
# run copyparty
|
||||||
|
/sbin/chroot --userspec=$uid:$gid "$jail" "$pybin" "$cpp" "$@" && rv=0 || rv=$?
|
||||||
|
|
||||||
|
|
||||||
|
# cleanup if not in use
|
||||||
|
lsof "$jail" | grep -qF "$jail" &&
|
||||||
|
echo "chroot is in use, will not cleanup" ||
|
||||||
|
{
|
||||||
|
mount | grep -qF " on $jail" |
|
||||||
|
awk '{sub(/ type .*/,"");sub(/.* on /,"");print}' |
|
||||||
|
LC_ALL=C sort -r | tee /dev/stderr | tr '\n' '\0' | xargs -r0 umount
|
||||||
|
}
|
||||||
|
exit $rv
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
#
|
#
|
||||||
# you may want to:
|
# you may want to:
|
||||||
# change '/usr/bin/python' to another interpreter
|
# change '/usr/bin/python' to another interpreter
|
||||||
# change '/mnt::a' to another location or permission-set
|
# change '/mnt::rw' to another location or permission-set
|
||||||
|
|
||||||
name="$SVCNAME"
|
name="$SVCNAME"
|
||||||
command_background=true
|
command_background=true
|
||||||
pidfile="/var/run/$SVCNAME.pid"
|
pidfile="/var/run/$SVCNAME.pid"
|
||||||
|
|
||||||
command="/usr/bin/python /usr/local/bin/copyparty-sfx.py"
|
command="/usr/bin/python /usr/local/bin/copyparty-sfx.py"
|
||||||
command_args="-q -v /mnt::a"
|
command_args="-q -v /mnt::rw"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
# you may want to:
|
# you may want to:
|
||||||
# change '/usr/bin/python' to another interpreter
|
# change '/usr/bin/python' to another interpreter
|
||||||
# change '/mnt::a' to another location or permission-set
|
# change '/mnt::rw' to another location or permission-set
|
||||||
#
|
#
|
||||||
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
# with `Type=notify`, copyparty will signal systemd when it is ready to
|
||||||
# accept connections; correctly delaying units depending on copyparty.
|
# accept connections; correctly delaying units depending on copyparty.
|
||||||
@@ -15,8 +15,11 @@
|
|||||||
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
||||||
#
|
#
|
||||||
# enable line-buffering for realtime logging (slight performance cost):
|
# enable line-buffering for realtime logging (slight performance cost):
|
||||||
# modify ExecStart and prefix it with `/bin/stdbuf -oL` like so:
|
# modify ExecStart and prefix it with `/usr/bin/stdbuf -oL` like so:
|
||||||
# ExecStart=/bin/stdbuf -oL /usr/bin/python3 [...]
|
# ExecStart=/usr/bin/stdbuf -oL /usr/bin/python3 [...]
|
||||||
|
# but some systemd versions require this instead (higher performance cost):
|
||||||
|
# inside the [Service] block, add the following line:
|
||||||
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=copyparty file server
|
Description=copyparty file server
|
||||||
@@ -24,7 +27,7 @@ Description=copyparty file server
|
|||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
SyslogIdentifier=copyparty
|
SyslogIdentifier=copyparty
|
||||||
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::a
|
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::rw
|
||||||
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
27
contrib/systemd/prisonparty.service
Normal file
27
contrib/systemd/prisonparty.service
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# this will start `/usr/local/bin/copyparty-sfx.py`
|
||||||
|
# in a chroot, preventing accidental access elsewhere
|
||||||
|
# and share '/mnt' with anonymous read+write
|
||||||
|
#
|
||||||
|
# installation:
|
||||||
|
# 1) put copyparty-sfx.py and prisonparty.sh in /usr/local/bin
|
||||||
|
# 2) cp -pv prisonparty.service /etc/systemd/system && systemctl enable --now prisonparty
|
||||||
|
#
|
||||||
|
# you may want to:
|
||||||
|
# change '/mnt::rw' to another location or permission-set
|
||||||
|
# (remember to change the '/mnt' chroot arg too)
|
||||||
|
#
|
||||||
|
# enable line-buffering for realtime logging (slight performance cost):
|
||||||
|
# inside the [Service] block, add the following line:
|
||||||
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=copyparty file server
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
SyslogIdentifier=prisonparty
|
||||||
|
WorkingDirectory=/usr/local/bin
|
||||||
|
ExecStart=/bin/bash /usr/local/bin/prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt -- \
|
||||||
|
/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::rw
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -25,6 +25,28 @@ ANYWIN = WINDOWS or sys.platform in ["msys"]
|
|||||||
MACOS = platform.system() == "Darwin"
|
MACOS = platform.system() == "Darwin"
|
||||||
|
|
||||||
|
|
||||||
|
def get_unix_home():
|
||||||
|
try:
|
||||||
|
v = os.environ["XDG_CONFIG_HOME"]
|
||||||
|
if not v:
|
||||||
|
raise Exception()
|
||||||
|
ret = os.path.normpath(v)
|
||||||
|
os.listdir(ret)
|
||||||
|
return ret
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
v = os.path.expanduser("~/.config")
|
||||||
|
if v.startswith("~"):
|
||||||
|
raise Exception()
|
||||||
|
ret = os.path.normpath(v)
|
||||||
|
os.listdir(ret)
|
||||||
|
return ret
|
||||||
|
except:
|
||||||
|
return "/tmp"
|
||||||
|
|
||||||
|
|
||||||
class EnvParams(object):
|
class EnvParams(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
@@ -37,10 +59,7 @@ class EnvParams(object):
|
|||||||
elif sys.platform == "darwin":
|
elif sys.platform == "darwin":
|
||||||
self.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
|
self.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
|
||||||
else:
|
else:
|
||||||
self.cfg = os.path.normpath(
|
self.cfg = get_unix_home() + "/copyparty"
|
||||||
os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
|
||||||
+ "/copyparty"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cfg = self.cfg.replace("\\", "/")
|
self.cfg = self.cfg.replace("\\", "/")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ from textwrap import dedent
|
|||||||
from .__init__ import E, WINDOWS, VT100, PY2, unicode
|
from .__init__ import E, WINDOWS, VT100, PY2, unicode
|
||||||
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
from .util import py_desc, align_tab, IMPLICATIONS
|
from .util import py_desc, align_tab, IMPLICATIONS, ansi_re
|
||||||
|
from .authsrv import re_vol
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
try:
|
try:
|
||||||
@@ -66,8 +67,12 @@ class Dodge11874(RiceFormatter):
|
|||||||
def lprint(*a, **ka):
|
def lprint(*a, **ka):
|
||||||
global printed
|
global printed
|
||||||
|
|
||||||
printed += " ".join(unicode(x) for x in a) + ka.get("end", "\n")
|
txt = " ".join(unicode(x) for x in a) + ka.get("end", "\n")
|
||||||
print(*a, **ka)
|
printed += txt
|
||||||
|
if not VT100:
|
||||||
|
txt = ansi_re.sub("", txt)
|
||||||
|
|
||||||
|
print(txt, **ka)
|
||||||
|
|
||||||
|
|
||||||
def warn(msg):
|
def warn(msg):
|
||||||
@@ -196,12 +201,18 @@ def run_argparse(argv, formatter):
|
|||||||
formatter_class=formatter,
|
formatter_class=formatter,
|
||||||
prog="copyparty",
|
prog="copyparty",
|
||||||
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
||||||
epilog=dedent(
|
)
|
||||||
"""
|
|
||||||
|
sects = [
|
||||||
|
[
|
||||||
|
"accounts",
|
||||||
|
"accounts and volumes",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
-a takes username:password,
|
-a takes username:password,
|
||||||
-v takes src:dst:perm1:perm2:permN:cflag1:cflag2:cflagN:...
|
-v takes src:dst:perm1:perm2:permN:volflag1:volflag2:volflagN:...
|
||||||
where "perm" is "accesslevels,username1,username2,..."
|
where "perm" is "accesslevels,username1,username2,..."
|
||||||
and "cflag" is config flags to set on this volume
|
and "volflag" is config flags to set on this volume
|
||||||
|
|
||||||
list of accesslevels:
|
list of accesslevels:
|
||||||
"r" (read): list folder contents, download files
|
"r" (read): list folder contents, download files
|
||||||
@@ -209,11 +220,7 @@ def run_argparse(argv, formatter):
|
|||||||
"m" (move): move files and folders; need "w" at destination
|
"m" (move): move files and folders; need "w" at destination
|
||||||
"d" (delete): permanently delete files and folders
|
"d" (delete): permanently delete files and folders
|
||||||
|
|
||||||
list of cflags:
|
too many volflags to list here, see the other sections
|
||||||
"c,nodupe" rejects existing files (instead of symlinking them)
|
|
||||||
"c,e2d" sets -e2d (all -e2* args can be set using ce2* cflags)
|
|
||||||
"c,d2t" disables metadata collection, overrides -e2t*
|
|
||||||
"c,d2d" disables all database stuff, overrides -e2*
|
|
||||||
|
|
||||||
example:\033[35m
|
example:\033[35m
|
||||||
-a ed:hunter2 -v .::r:rw,ed -v ../inc:dump:w:rw,ed:c,nodupe \033[36m
|
-a ed:hunter2 -v .::r:rw,ed -v ../inc:dump:w:rw,ed:c,nodupe \033[36m
|
||||||
@@ -230,29 +237,86 @@ def run_argparse(argv, formatter):
|
|||||||
|
|
||||||
consider the config file for more flexible account/volume management,
|
consider the config file for more flexible account/volume management,
|
||||||
including dynamic reload at runtime (and being more readable w)
|
including dynamic reload at runtime (and being more readable w)
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"flags",
|
||||||
|
"list of volflags",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
volflags are appended to volume definitions, for example,
|
||||||
|
to create a write-only volume with the \033[33mnodupe\033[0m and \033[32mnosub\033[0m flags:
|
||||||
|
\033[35m-v /mnt/inc:/inc:w\033[33m:c,nodupe\033[32m:c,nosub
|
||||||
|
|
||||||
|
\033[0muploads, general:
|
||||||
|
\033[36mnodupe\033[35m rejects existing files (instead of symlinking them)
|
||||||
|
\033[36mnosub\033[35m forces all uploads into the top folder of the vfs
|
||||||
|
\033[36mgz\033[35m allows server-side gzip of uploads with ?gz (also c,xz)
|
||||||
|
\033[36mpk\033[35m forces server-side compression, optional arg: xz,9
|
||||||
|
|
||||||
|
\033[0mupload rules:
|
||||||
|
\033[36mmaxn=250,600\033[35m max 250 uploads over 15min
|
||||||
|
\033[36mmaxb=1g,300\033[35m max 1 GiB over 5min (suffixes: b, k, m, g)
|
||||||
|
\033[36msz=1k-3m\033[35m allow filesizes between 1 KiB and 3MiB
|
||||||
|
|
||||||
|
\033[0mupload rotation:
|
||||||
|
(moves all uploads into the specified folder structure)
|
||||||
|
\033[36mrotn=100,3\033[35m 3 levels of subfolders with 100 entries in each
|
||||||
|
\033[36mrotf=%Y-%m/%d-%H\033[35m date-formatted organizing
|
||||||
|
\033[36mlifetime=3600\033[35m uploads are deleted after 1 hour
|
||||||
|
|
||||||
|
\033[0mdatabase, general:
|
||||||
|
\033[36me2d\033[35m sets -e2d (all -e2* args can be set using ce2* volflags)
|
||||||
|
\033[36md2t\033[35m disables metadata collection, overrides -e2t*
|
||||||
|
\033[36md2d\033[35m disables all database stuff, overrides -e2*
|
||||||
|
\033[36mdhash\033[35m disables file hashing on initial scans, also ehash
|
||||||
|
\033[36mhist=/tmp/cdb\033[35m puts thumbnails and indexes at that location
|
||||||
|
\033[36mscan=60\033[35m scan for new files every 60sec, same as --re-maxage
|
||||||
|
|
||||||
|
\033[0mdatabase, audio tags:
|
||||||
|
"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...
|
||||||
|
\033[36mmtp=.bpm=f,audio-bpm.py\033[35m uses the "audio-bpm.py" program to
|
||||||
|
generate ".bpm" tags from uploads (f = overwrite tags)
|
||||||
|
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
|
||||||
|
\033[0m"""
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"urlform",
|
||||||
|
"",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
values for --urlform:
|
values for --urlform:
|
||||||
"stash" dumps the data to file and returns length + checksum
|
\033[36mstash\033[35m dumps the data to file and returns length + checksum
|
||||||
"save,get" dumps to file and returns the page like a GET
|
\033[36msave,get\033[35m dumps to file and returns the page like a GET
|
||||||
"print,get" prints the data in the log and returns GET
|
\033[36mprint,get\033[35m prints the data in the log and returns GET
|
||||||
(leave out the ",get" to return an error instead)
|
(leave out the ",get" to return an error instead)
|
||||||
|
"""
|
||||||
values for --ls:
|
),
|
||||||
"USR" is a user to browse as; * is anonymous, ** is all users
|
],
|
||||||
"VOL" is a single volume to scan, default is * (all vols)
|
[
|
||||||
"FLAG" is flags;
|
"ls",
|
||||||
"v" in addition to realpaths, print usernames and vpaths
|
"volume inspection",
|
||||||
"ln" only prints symlinks leaving the volume mountpoint
|
dedent(
|
||||||
"p" exits 1 if any such symlinks are found
|
"""
|
||||||
"r" resumes startup after the listing
|
\033[35m--ls USR,VOL,FLAGS
|
||||||
|
\033[36mUSR\033[0m is a user to browse as; * is anonymous, ** is all users
|
||||||
|
\033[36mVOL\033[0m is a single volume to scan, default is * (all vols)
|
||||||
|
\033[36mFLAG\033[0m is flags;
|
||||||
|
\033[36mv\033[0m in addition to realpaths, print usernames and vpaths
|
||||||
|
\033[36mln\033[0m only prints symlinks leaving the volume mountpoint
|
||||||
|
\033[36mp\033[0m exits 1 if any such symlinks are found
|
||||||
|
\033[36mr\033[0m resumes startup after the listing
|
||||||
examples:
|
examples:
|
||||||
--ls '**' # list all files which are possible to read
|
--ls '**' # list all files which are possible to read
|
||||||
--ls '**,*,ln' # check for dangerous symlinks
|
--ls '**,*,ln' # check for dangerous symlinks
|
||||||
--ls '**,*,ln,p,r' # check, then start normally if safe
|
--ls '**,*,ln,p,r' # check, then start normally if safe
|
||||||
\033[0m
|
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
|
]
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
u = unicode
|
u = unicode
|
||||||
ap2 = ap.add_argument_group('general options')
|
ap2 = ap.add_argument_group('general options')
|
||||||
@@ -291,6 +355,7 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-nih", action="store_true", help="no info hostname")
|
ap2.add_argument("-nih", action="store_true", help="no info hostname")
|
||||||
ap2.add_argument("-nid", action="store_true", help="no info disk-usage")
|
ap2.add_argument("-nid", action="store_true", help="no info disk-usage")
|
||||||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||||
|
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (lifetime volflag)")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('safety options')
|
ap2 = ap.add_argument_group('safety options')
|
||||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
||||||
@@ -313,6 +378,7 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
|
||||||
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
|
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
|
||||||
|
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=0, help="max num cpu cores to use, 0=all")
|
||||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
|
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
|
||||||
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
|
||||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||||
@@ -326,10 +392,10 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
||||||
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
||||||
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
|
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
|
||||||
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
||||||
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
|
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
|
||||||
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag")
|
||||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('metadata db options')
|
ap2 = ap.add_argument_group('metadata db options')
|
||||||
@@ -341,7 +407,9 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
||||||
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
||||||
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
||||||
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
|
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
|
||||||
|
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
||||||
|
default=".vq,.aq,vc,ac,res,.fps")
|
||||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('appearance options')
|
ap2 = ap.add_argument_group('appearance options')
|
||||||
@@ -354,10 +422,22 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
||||||
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
|
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
|
||||||
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")
|
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")
|
||||||
|
|
||||||
return ap.parse_args(args=argv[1:])
|
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
ap2 = ap.add_argument_group("help sections")
|
||||||
|
for k, h, _ in sects:
|
||||||
|
ap2.add_argument("--help-" + k, action="store_true", help=h)
|
||||||
|
|
||||||
|
ret = ap.parse_args(args=argv[1:])
|
||||||
|
for k, h, t in sects:
|
||||||
|
k2 = "help_" + k.replace("-", "_")
|
||||||
|
if vars(ret)[k2]:
|
||||||
|
lprint("# {} help page".format(k))
|
||||||
|
lprint(t + "\033[0m")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
time.strptime("19970815", "%Y%m%d") # python#7980
|
time.strptime("19970815", "%Y%m%d") # python#7980
|
||||||
@@ -396,10 +476,16 @@ def main(argv=None):
|
|||||||
nstrs = []
|
nstrs = []
|
||||||
anymod = False
|
anymod = False
|
||||||
for ostr in al.v or []:
|
for ostr in al.v or []:
|
||||||
|
m = re_vol.match(ostr)
|
||||||
|
if not m:
|
||||||
|
# not our problem
|
||||||
|
nstrs.append(ostr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
src, dst, perms = m.groups()
|
||||||
|
na = [src, dst]
|
||||||
mod = False
|
mod = False
|
||||||
oa = ostr.split(":")
|
for opt in perms.split(":"):
|
||||||
na = oa[:2]
|
|
||||||
for opt in oa[2:]:
|
|
||||||
if re.match("c[^,]", opt):
|
if re.match("c[^,]", opt):
|
||||||
mod = True
|
mod = True
|
||||||
na.append("c," + opt[1:])
|
na.append("c," + opt[1:])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (0, 12, 9)
|
VERSION = (0, 13, 5)
|
||||||
CODENAME = "fil\033[33med"
|
CODENAME = "future-proof"
|
||||||
BUILD_DT = (2021, 8, 1)
|
BUILD_DT = (2021, 8, 16)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -5,15 +5,29 @@ import re
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import stat
|
import stat
|
||||||
|
import time
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import threading
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from .__init__ import WINDOWS
|
from .__init__ import WINDOWS
|
||||||
from .util import IMPLICATIONS, uncyg, undot, absreal, Pebkac, fsdec, fsenc, statdir
|
from .util import (
|
||||||
|
IMPLICATIONS,
|
||||||
|
uncyg,
|
||||||
|
undot,
|
||||||
|
unhumanize,
|
||||||
|
absreal,
|
||||||
|
Pebkac,
|
||||||
|
fsenc,
|
||||||
|
statdir,
|
||||||
|
)
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
|
LEELOO_DALLAS = "leeloo_dallas"
|
||||||
|
|
||||||
|
|
||||||
class AXS(object):
|
class AXS(object):
|
||||||
def __init__(self, uread=None, uwrite=None, umove=None, udel=None):
|
def __init__(self, uread=None, uwrite=None, umove=None, udel=None):
|
||||||
self.uread = {} if uread is None else {k: 1 for k in uread}
|
self.uread = {} if uread is None else {k: 1 for k in uread}
|
||||||
@@ -30,6 +44,156 @@ class AXS(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Lim(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.nups = {} # num tracker
|
||||||
|
self.bups = {} # byte tracker list
|
||||||
|
self.bupc = {} # byte tracker cache
|
||||||
|
|
||||||
|
self.nosub = False # disallow subdirectories
|
||||||
|
|
||||||
|
self.smin = None # filesize min
|
||||||
|
self.smax = None # filesize max
|
||||||
|
|
||||||
|
self.bwin = None # bytes window
|
||||||
|
self.bmax = None # bytes max
|
||||||
|
self.nwin = None # num window
|
||||||
|
self.nmax = None # num max
|
||||||
|
|
||||||
|
self.rotn = None # rot num files
|
||||||
|
self.rotl = None # rot depth
|
||||||
|
self.rotf = None # rot datefmt
|
||||||
|
self.rot_re = None # rotf check
|
||||||
|
|
||||||
|
def set_rotf(self, fmt):
|
||||||
|
self.rotf = fmt
|
||||||
|
r = re.escape(fmt).replace("%Y", "[0-9]{4}").replace("%j", "[0-9]{3}")
|
||||||
|
r = re.sub("%[mdHMSWU]", "[0-9]{2}", r)
|
||||||
|
self.rot_re = re.compile("(^|/)" + r + "$")
|
||||||
|
|
||||||
|
def all(self, ip, rem, sz, abspath):
|
||||||
|
self.chk_nup(ip)
|
||||||
|
self.chk_bup(ip)
|
||||||
|
self.chk_rem(rem)
|
||||||
|
if sz != -1:
|
||||||
|
self.chk_sz(sz)
|
||||||
|
|
||||||
|
ap2, vp2 = self.rot(abspath)
|
||||||
|
if abspath == ap2:
|
||||||
|
return ap2, rem
|
||||||
|
|
||||||
|
return ap2, ("{}/{}".format(rem, vp2) if rem else vp2)
|
||||||
|
|
||||||
|
def chk_sz(self, sz):
|
||||||
|
if self.smin is not None and sz < self.smin:
|
||||||
|
raise Pebkac(400, "file too small")
|
||||||
|
|
||||||
|
if self.smax is not None and sz > self.smax:
|
||||||
|
raise Pebkac(400, "file too big")
|
||||||
|
|
||||||
|
def chk_rem(self, rem):
|
||||||
|
if self.nosub and rem:
|
||||||
|
raise Pebkac(500, "no subdirectories allowed")
|
||||||
|
|
||||||
|
def rot(self, path):
|
||||||
|
if not self.rotf and not self.rotn:
|
||||||
|
return path, ""
|
||||||
|
|
||||||
|
if self.rotf:
|
||||||
|
path = path.rstrip("/\\")
|
||||||
|
if self.rot_re.search(path.replace("\\", "/")):
|
||||||
|
return path, ""
|
||||||
|
|
||||||
|
suf = datetime.utcnow().strftime(self.rotf)
|
||||||
|
if path:
|
||||||
|
path += "/"
|
||||||
|
|
||||||
|
return path + suf, suf
|
||||||
|
|
||||||
|
ret = self.dive(path, self.rotl)
|
||||||
|
if not ret:
|
||||||
|
raise Pebkac(500, "no available slots in volume")
|
||||||
|
|
||||||
|
d = ret[len(path) :].strip("/\\").replace("\\", "/")
|
||||||
|
return ret, d
|
||||||
|
|
||||||
|
def dive(self, path, lvs):
|
||||||
|
items = bos.listdir(path)
|
||||||
|
|
||||||
|
if not lvs:
|
||||||
|
# at leaf level
|
||||||
|
return None if len(items) >= self.rotn else ""
|
||||||
|
|
||||||
|
dirs = [int(x) for x in items if x and all(y in "1234567890" for y in x)]
|
||||||
|
dirs.sort()
|
||||||
|
|
||||||
|
if not dirs:
|
||||||
|
# no branches yet; make one
|
||||||
|
sub = os.path.join(path, "0")
|
||||||
|
bos.mkdir(sub)
|
||||||
|
else:
|
||||||
|
# try newest branch only
|
||||||
|
sub = os.path.join(path, str(dirs[-1]))
|
||||||
|
|
||||||
|
ret = self.dive(sub, lvs - 1)
|
||||||
|
if ret is not None:
|
||||||
|
return os.path.join(sub, ret)
|
||||||
|
|
||||||
|
if len(dirs) >= self.rotn:
|
||||||
|
# full branch or root
|
||||||
|
return None
|
||||||
|
|
||||||
|
# make a branch
|
||||||
|
sub = os.path.join(path, str(dirs[-1] + 1))
|
||||||
|
bos.mkdir(sub)
|
||||||
|
ret = self.dive(sub, lvs - 1)
|
||||||
|
if ret is None:
|
||||||
|
raise Pebkac(500, "rotation bug")
|
||||||
|
|
||||||
|
return os.path.join(sub, ret)
|
||||||
|
|
||||||
|
def nup(self, ip):
|
||||||
|
try:
|
||||||
|
self.nups[ip].append(time.time())
|
||||||
|
except:
|
||||||
|
self.nups[ip] = [time.time()]
|
||||||
|
|
||||||
|
def bup(self, ip, nbytes):
|
||||||
|
v = [time.time(), nbytes]
|
||||||
|
try:
|
||||||
|
self.bups[ip].append(v)
|
||||||
|
self.bupc[ip] += nbytes
|
||||||
|
except:
|
||||||
|
self.bups[ip] = [v]
|
||||||
|
self.bupc[ip] = nbytes
|
||||||
|
|
||||||
|
def chk_nup(self, ip):
|
||||||
|
if not self.nmax or ip not in self.nups:
|
||||||
|
return
|
||||||
|
|
||||||
|
nups = self.nups[ip]
|
||||||
|
cutoff = time.time() - self.nwin
|
||||||
|
while nups and nups[0] < cutoff:
|
||||||
|
nups.pop(0)
|
||||||
|
|
||||||
|
if len(nups) >= self.nmax:
|
||||||
|
raise Pebkac(429, "too many uploads")
|
||||||
|
|
||||||
|
def chk_bup(self, ip):
|
||||||
|
if not self.bmax or ip not in self.bups:
|
||||||
|
return
|
||||||
|
|
||||||
|
bups = self.bups[ip]
|
||||||
|
cutoff = time.time() - self.bwin
|
||||||
|
mark = self.bupc[ip]
|
||||||
|
while bups and bups[0][0] < cutoff:
|
||||||
|
mark -= bups.pop(0)[1]
|
||||||
|
|
||||||
|
self.bupc[ip] = mark
|
||||||
|
if mark >= self.bmax:
|
||||||
|
raise Pebkac(429, "ingress saturated")
|
||||||
|
|
||||||
|
|
||||||
class VFS(object):
|
class VFS(object):
|
||||||
"""single level in the virtual fs"""
|
"""single level in the virtual fs"""
|
||||||
|
|
||||||
@@ -42,6 +206,7 @@ class VFS(object):
|
|||||||
self.nodes = {} # child nodes
|
self.nodes = {} # child nodes
|
||||||
self.histtab = None # all realpath->histpath
|
self.histtab = None # all realpath->histpath
|
||||||
self.dbv = None # closest full/non-jump parent
|
self.dbv = None # closest full/non-jump parent
|
||||||
|
self.lim = None # type: Lim # upload limits; only set for dbv
|
||||||
|
|
||||||
if realpath:
|
if realpath:
|
||||||
self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
|
self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
|
||||||
@@ -165,13 +330,14 @@ class VFS(object):
|
|||||||
[will_move, c.umove, "move"],
|
[will_move, c.umove, "move"],
|
||||||
[will_del, c.udel, "delete"],
|
[will_del, c.udel, "delete"],
|
||||||
]:
|
]:
|
||||||
if req and (uname not in d and "*" not in d):
|
if req and (uname not in d and "*" not in d) and uname != LEELOO_DALLAS:
|
||||||
m = "you don't have {}-access for this location"
|
m = "you don't have {}-access for this location"
|
||||||
raise Pebkac(403, m.format(msg))
|
raise Pebkac(403, m.format(msg))
|
||||||
|
|
||||||
return vn, rem
|
return vn, rem
|
||||||
|
|
||||||
def get_dbv(self, vrem):
|
def get_dbv(self, vrem):
|
||||||
|
# type: (str) -> tuple[VFS, str]
|
||||||
dbv = self.dbv
|
dbv = self.dbv
|
||||||
if not dbv:
|
if not dbv:
|
||||||
return self, vrem
|
return self, vrem
|
||||||
@@ -310,6 +476,12 @@ class VFS(object):
|
|||||||
yield f
|
yield f
|
||||||
|
|
||||||
|
|
||||||
|
if WINDOWS:
|
||||||
|
re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
|
||||||
|
else:
|
||||||
|
re_vol = re.compile(r"^([^:]*):([^:]*):(.*)$")
|
||||||
|
|
||||||
|
|
||||||
class AuthSrv(object):
|
class AuthSrv(object):
|
||||||
"""verifies users against given paths"""
|
"""verifies users against given paths"""
|
||||||
|
|
||||||
@@ -319,11 +491,6 @@ class AuthSrv(object):
|
|||||||
self.warn_anonwrite = warn_anonwrite
|
self.warn_anonwrite = warn_anonwrite
|
||||||
self.line_ctr = 0
|
self.line_ctr = 0
|
||||||
|
|
||||||
if WINDOWS:
|
|
||||||
self.re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
|
|
||||||
else:
|
|
||||||
self.re_vol = re.compile(r"^([^:]*):([^:]*):(.*)$")
|
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
@@ -390,6 +557,9 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
def _read_vol_str(self, lvl, uname, axs, flags):
|
def _read_vol_str(self, lvl, uname, axs, flags):
|
||||||
# type: (str, str, AXS, any) -> None
|
# type: (str, str, AXS, any) -> None
|
||||||
|
if lvl.strip("crwmd"):
|
||||||
|
raise Exception("invalid volume flag: {},{}".format(lvl, uname))
|
||||||
|
|
||||||
if lvl == "c":
|
if lvl == "c":
|
||||||
cval = True
|
cval = True
|
||||||
if "=" in uname:
|
if "=" in uname:
|
||||||
@@ -453,7 +623,7 @@ class AuthSrv(object):
|
|||||||
# list of src:dst:permset:permset:...
|
# list of src:dst:permset:permset:...
|
||||||
# permset is <rwmd>[,username][,username] or <c>,<flag>[=args]
|
# permset is <rwmd>[,username][,username] or <c>,<flag>[=args]
|
||||||
for v_str in self.args.v:
|
for v_str in self.args.v:
|
||||||
m = self.re_vol.match(v_str)
|
m = re_vol.match(v_str)
|
||||||
if not m:
|
if not m:
|
||||||
raise Exception("invalid -v argument: [{}]".format(v_str))
|
raise Exception("invalid -v argument: [{}]".format(v_str))
|
||||||
|
|
||||||
@@ -545,6 +715,9 @@ class AuthSrv(object):
|
|||||||
)
|
)
|
||||||
raise Exception("invalid config")
|
raise Exception("invalid config")
|
||||||
|
|
||||||
|
if LEELOO_DALLAS in all_users:
|
||||||
|
raise Exception("sorry, reserved username: " + LEELOO_DALLAS)
|
||||||
|
|
||||||
promote = []
|
promote = []
|
||||||
demote = []
|
demote = []
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_vols.values():
|
||||||
@@ -603,6 +776,51 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
vfs.histtab = {v.realpath: v.histpath for v in vfs.all_vols.values()}
|
vfs.histtab = {v.realpath: v.histpath for v in vfs.all_vols.values()}
|
||||||
|
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
lim = Lim()
|
||||||
|
use = False
|
||||||
|
|
||||||
|
if vol.flags.get("nosub"):
|
||||||
|
use = True
|
||||||
|
lim.nosub = True
|
||||||
|
|
||||||
|
v = vol.flags.get("sz")
|
||||||
|
if v:
|
||||||
|
use = True
|
||||||
|
lim.smin, lim.smax = [unhumanize(x) for x in v.split("-")]
|
||||||
|
|
||||||
|
v = vol.flags.get("rotn")
|
||||||
|
if v:
|
||||||
|
use = True
|
||||||
|
lim.rotn, lim.rotl = [int(x) for x in v.split(",")]
|
||||||
|
|
||||||
|
v = vol.flags.get("rotf")
|
||||||
|
if v:
|
||||||
|
use = True
|
||||||
|
lim.set_rotf(v)
|
||||||
|
|
||||||
|
v = vol.flags.get("maxn")
|
||||||
|
if v:
|
||||||
|
use = True
|
||||||
|
lim.nmax, lim.nwin = [int(x) for x in v.split(",")]
|
||||||
|
|
||||||
|
v = vol.flags.get("maxb")
|
||||||
|
if v:
|
||||||
|
use = True
|
||||||
|
lim.bmax, lim.bwin = [unhumanize(x) for x in v.split(",")]
|
||||||
|
|
||||||
|
if use:
|
||||||
|
vol.lim = lim
|
||||||
|
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
|
||||||
|
vol.flags["gz"] = False # def.pk
|
||||||
|
|
||||||
|
if "scan" in vol.flags:
|
||||||
|
vol.flags["scan"] = int(vol.flags["scan"])
|
||||||
|
elif self.args.re_maxage:
|
||||||
|
vol.flags["scan"] = self.args.re_maxage
|
||||||
|
|
||||||
all_mte = {}
|
all_mte = {}
|
||||||
errors = False
|
errors = False
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_vols.values():
|
||||||
@@ -624,9 +842,11 @@ class AuthSrv(object):
|
|||||||
if k1 in vol.flags:
|
if k1 in vol.flags:
|
||||||
vol.flags[k2] = True
|
vol.flags[k2] = True
|
||||||
|
|
||||||
# default tag-list if unset
|
# default tag cfgs if unset
|
||||||
if "mte" not in vol.flags:
|
if "mte" not in vol.flags:
|
||||||
vol.flags["mte"] = self.args.mte
|
vol.flags["mte"] = self.args.mte
|
||||||
|
if "mth" not in vol.flags:
|
||||||
|
vol.flags["mth"] = self.args.mth
|
||||||
|
|
||||||
# append parsers from argv to volume-flags
|
# append parsers from argv to volume-flags
|
||||||
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
from copyparty.authsrv import AuthSrv
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
@@ -9,6 +8,7 @@ import threading
|
|||||||
from .broker_util import ExceptionalQueue
|
from .broker_util import ExceptionalQueue
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .util import FAKE_MP
|
from .util import FAKE_MP
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
|
||||||
|
|
||||||
class MpWorker(object):
|
class MpWorker(object):
|
||||||
|
|||||||
@@ -13,10 +13,15 @@ import ctypes
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
|
try:
|
||||||
|
import lzma
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
from .__init__ import E, PY2, WINDOWS, ANYWIN, unicode
|
from .__init__ import E, PY2, WINDOWS, ANYWIN, unicode
|
||||||
from .util import * # noqa # pylint: disable=unused-wildcard-import
|
from .util import * # noqa # pylint: disable=unused-wildcard-import
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv, Lim
|
||||||
from .szip import StreamZip
|
from .szip import StreamZip
|
||||||
from .star import StreamTar
|
from .star import StreamTar
|
||||||
|
|
||||||
@@ -107,7 +112,7 @@ class HttpCli(object):
|
|||||||
self.http_ver = "HTTP/1.1"
|
self.http_ver = "HTTP/1.1"
|
||||||
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
self.loud_reply(unicode(ex), status=ex.code)
|
self.loud_reply(unicode(ex), status=ex.code, volsan=True)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
|
|
||||||
# time.sleep(0.4)
|
# time.sleep(0.4)
|
||||||
@@ -180,6 +185,9 @@ class HttpCli(object):
|
|||||||
if kc in cookies and ku not in uparam:
|
if kc in cookies and ku not in uparam:
|
||||||
uparam[ku] = cookies[kc]
|
uparam[ku] = cookies[kc]
|
||||||
|
|
||||||
|
if len(uparam) > 10 or len(cookies) > 50:
|
||||||
|
raise Pebkac(400, "u wot m8")
|
||||||
|
|
||||||
self.uparam = uparam
|
self.uparam = uparam
|
||||||
self.cookies = cookies
|
self.cookies = cookies
|
||||||
self.vpath = unquotep(vpath) # not query, so + means +
|
self.vpath = unquotep(vpath) # not query, so + means +
|
||||||
@@ -216,19 +224,24 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
|
raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Exception as ex:
|
||||||
|
pex = ex
|
||||||
|
if not hasattr(ex, "code"):
|
||||||
|
pex = Pebkac(500)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# self.log("pebkac at httpcli.run #2: " + repr(ex))
|
|
||||||
post = self.mode in ["POST", "PUT"] or "content-length" in self.headers
|
post = self.mode in ["POST", "PUT"] or "content-length" in self.headers
|
||||||
if not self._check_nonfatal(ex, post):
|
if not self._check_nonfatal(pex, post):
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
|
|
||||||
self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
|
msg = str(ex) if pex == ex else min_ex()
|
||||||
|
self.log("{}\033[0m, {}".format(msg, self.vpath), 3)
|
||||||
|
|
||||||
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
||||||
if self.hint:
|
if self.hint:
|
||||||
msg += "hint: {}\r\n".format(self.hint)
|
msg += "hint: {}\r\n".format(self.hint)
|
||||||
|
|
||||||
self.reply(msg.encode("utf-8", "replace"), status=ex.code)
|
self.reply(msg.encode("utf-8", "replace"), status=pex.code, volsan=True)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
return False
|
return False
|
||||||
@@ -261,8 +274,12 @@ class HttpCli(object):
|
|||||||
except:
|
except:
|
||||||
raise Pebkac(400, "client d/c while replying headers")
|
raise Pebkac(400, "client d/c while replying headers")
|
||||||
|
|
||||||
def reply(self, body, status=200, mime=None, headers=None):
|
def reply(self, body, status=200, mime=None, headers=None, volsan=False):
|
||||||
# TODO something to reply with user-supplied values safely
|
# TODO something to reply with user-supplied values safely
|
||||||
|
|
||||||
|
if volsan:
|
||||||
|
body = vol_san(self.asrv.vfs.all_vols.values(), body)
|
||||||
|
|
||||||
self.send_headers(len(body), status, mime, headers)
|
self.send_headers(len(body), status, mime, headers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -491,7 +508,11 @@ class HttpCli(object):
|
|||||||
def dump_to_file(self):
|
def dump_to_file(self):
|
||||||
reader, remains = self.get_body_reader()
|
reader, remains = self.get_body_reader()
|
||||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||||
|
lim = vfs.get_dbv(rem)[0].lim
|
||||||
fdir = os.path.join(vfs.realpath, rem)
|
fdir = os.path.join(vfs.realpath, rem)
|
||||||
|
if lim:
|
||||||
|
fdir, rem = lim.all(self.ip, rem, remains, fdir)
|
||||||
|
bos.makedirs(fdir)
|
||||||
|
|
||||||
addr = self.ip.replace(":", ".")
|
addr = self.ip.replace(":", ".")
|
||||||
fn = "put-{:.6f}-{}.bin".format(time.time(), addr)
|
fn = "put-{:.6f}-{}.bin".format(time.time(), addr)
|
||||||
@@ -499,9 +520,70 @@ class HttpCli(object):
|
|||||||
if self.args.nw:
|
if self.args.nw:
|
||||||
path = os.devnull
|
path = os.devnull
|
||||||
|
|
||||||
with open(fsenc(path), "wb", 512 * 1024) as f:
|
open_f = open
|
||||||
|
open_a = [fsenc(path), "wb", 512 * 1024]
|
||||||
|
open_ka = {}
|
||||||
|
|
||||||
|
# user-request || config-force
|
||||||
|
if ("gz" in vfs.flags or "xz" in vfs.flags) and (
|
||||||
|
"pk" in vfs.flags
|
||||||
|
or "pk" in self.uparam
|
||||||
|
or "gz" in self.uparam
|
||||||
|
or "xz" in self.uparam
|
||||||
|
):
|
||||||
|
fb = {"gz": 9, "xz": 0} # default/fallback level
|
||||||
|
lv = {} # selected level
|
||||||
|
alg = None # selected algo (gz=preferred)
|
||||||
|
|
||||||
|
# user-prefs first
|
||||||
|
if "gz" in self.uparam or "pk" in self.uparam: # def.pk
|
||||||
|
alg = "gz"
|
||||||
|
if "xz" in self.uparam:
|
||||||
|
alg = "xz"
|
||||||
|
if alg:
|
||||||
|
v = self.uparam.get(alg)
|
||||||
|
lv[alg] = fb[alg] if v is None else int(v)
|
||||||
|
|
||||||
|
if alg not in vfs.flags:
|
||||||
|
alg = "gz" if "gz" in vfs.flags else "xz"
|
||||||
|
|
||||||
|
# then server overrides
|
||||||
|
pk = vfs.flags.get("pk")
|
||||||
|
if pk is not None:
|
||||||
|
# config-forced on
|
||||||
|
alg = alg or "gz" # def.pk
|
||||||
|
try:
|
||||||
|
# config-forced opts
|
||||||
|
alg, lv = pk.split(",")
|
||||||
|
lv[alg] = int(lv)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
lv[alg] = lv.get(alg) or fb.get(alg)
|
||||||
|
|
||||||
|
self.log("compressing with {} level {}".format(alg, lv.get(alg)))
|
||||||
|
if alg == "gz":
|
||||||
|
open_f = gzip.GzipFile
|
||||||
|
open_a = [fsenc(path), "wb", lv[alg], None, 0x5FEE6600] # 2021-01-01
|
||||||
|
elif alg == "xz":
|
||||||
|
open_f = lzma.open
|
||||||
|
open_a = [fsenc(path), "wb"]
|
||||||
|
open_ka = {"preset": lv[alg]}
|
||||||
|
else:
|
||||||
|
self.log("fallthrough? thats a bug", 1)
|
||||||
|
|
||||||
|
with open_f(*open_a, **open_ka) as f:
|
||||||
post_sz, _, sha_b64 = hashcopy(reader, f)
|
post_sz, _, sha_b64 = hashcopy(reader, f)
|
||||||
|
|
||||||
|
if lim:
|
||||||
|
lim.nup(self.ip)
|
||||||
|
lim.bup(self.ip, post_sz)
|
||||||
|
try:
|
||||||
|
lim.chk_sz(post_sz)
|
||||||
|
except:
|
||||||
|
bos.unlink(path)
|
||||||
|
raise
|
||||||
|
|
||||||
if not self.args.nw:
|
if not self.args.nw:
|
||||||
vfs, vrem = vfs.get_dbv(rem)
|
vfs, vrem = vfs.get_dbv(rem)
|
||||||
self.conn.hsrv.broker.put(
|
self.conn.hsrv.broker.put(
|
||||||
@@ -583,7 +665,7 @@ class HttpCli(object):
|
|||||||
try:
|
try:
|
||||||
remains = int(self.headers["content-length"])
|
remains = int(self.headers["content-length"])
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "you must supply a content-length for JSON POST")
|
raise Pebkac(411)
|
||||||
|
|
||||||
if remains > 1024 * 1024:
|
if remains > 1024 * 1024:
|
||||||
raise Pebkac(413, "json 2big")
|
raise Pebkac(413, "json 2big")
|
||||||
@@ -614,12 +696,9 @@ class HttpCli(object):
|
|||||||
if self.vpath.endswith(k):
|
if self.vpath.endswith(k):
|
||||||
self.vpath = self.vpath[: -len(k)]
|
self.vpath = self.vpath[: -len(k)]
|
||||||
|
|
||||||
sub = None
|
|
||||||
name = undot(body["name"])
|
name = undot(body["name"])
|
||||||
if "/" in name:
|
if "/" in name:
|
||||||
sub, name = name.rsplit("/", 1)
|
raise Pebkac(400, "your client is old; press CTRL-SHIFT-R and try again")
|
||||||
self.vpath = "/".join([self.vpath, sub]).strip("/")
|
|
||||||
body["name"] = name
|
|
||||||
|
|
||||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||||
dbv, vrem = vfs.get_dbv(rem)
|
dbv, vrem = vfs.get_dbv(rem)
|
||||||
@@ -630,7 +709,7 @@ class HttpCli(object):
|
|||||||
body["addr"] = self.ip
|
body["addr"] = self.ip
|
||||||
body["vcfg"] = dbv.flags
|
body["vcfg"] = dbv.flags
|
||||||
|
|
||||||
if sub:
|
if rem:
|
||||||
try:
|
try:
|
||||||
dst = os.path.join(vfs.realpath, rem)
|
dst = os.path.join(vfs.realpath, rem)
|
||||||
if not bos.path.isdir(dst):
|
if not bos.path.isdir(dst):
|
||||||
@@ -650,9 +729,6 @@ class HttpCli(object):
|
|||||||
|
|
||||||
x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body)
|
x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body)
|
||||||
ret = x.get()
|
ret = x.get()
|
||||||
if sub:
|
|
||||||
ret["name"] = "/".join([sub, ret["name"]])
|
|
||||||
|
|
||||||
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")
|
||||||
@@ -880,6 +956,15 @@ 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)
|
||||||
|
|
||||||
|
upload_vpath = self.vpath
|
||||||
|
lim = vfs.get_dbv(rem)[0].lim
|
||||||
|
fdir_base = os.path.join(vfs.realpath, rem)
|
||||||
|
if lim:
|
||||||
|
fdir_base, rem = lim.all(self.ip, rem, -1, fdir_base)
|
||||||
|
upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
|
||||||
|
if not nullwrite:
|
||||||
|
bos.makedirs(fdir_base)
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
errmsg = ""
|
errmsg = ""
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@@ -889,12 +974,9 @@ class HttpCli(object):
|
|||||||
self.log("discarding incoming file without filename")
|
self.log("discarding incoming file without filename")
|
||||||
# fallthrough
|
# fallthrough
|
||||||
|
|
||||||
|
fdir = fdir_base
|
||||||
|
fname = sanitize_fn(p_file, "", [".prologue.html", ".epilogue.html"])
|
||||||
if p_file and not nullwrite:
|
if p_file and not nullwrite:
|
||||||
fdir = os.path.join(vfs.realpath, rem)
|
|
||||||
fname = sanitize_fn(
|
|
||||||
p_file, "", [".prologue.html", ".epilogue.html"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if not bos.path.isdir(fdir):
|
if not bos.path.isdir(fdir):
|
||||||
raise Pebkac(404, "that folder does not exist")
|
raise Pebkac(404, "that folder does not exist")
|
||||||
|
|
||||||
@@ -905,27 +987,41 @@ class HttpCli(object):
|
|||||||
fname = os.devnull
|
fname = os.devnull
|
||||||
fdir = ""
|
fdir = ""
|
||||||
|
|
||||||
|
if lim:
|
||||||
|
lim.chk_bup(self.ip)
|
||||||
|
lim.chk_nup(self.ip)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with ren_open(fname, "wb", 512 * 1024, **open_args) as f:
|
with ren_open(fname, "wb", 512 * 1024, **open_args) as f:
|
||||||
f, fname = f["orz"]
|
f, fname = f["orz"]
|
||||||
self.log("writing to {}/{}".format(fdir, fname))
|
abspath = os.path.join(fdir, fname)
|
||||||
|
self.log("writing to {}".format(abspath))
|
||||||
sz, sha512_hex, _ = hashcopy(p_data, f)
|
sz, sha512_hex, _ = hashcopy(p_data, f)
|
||||||
if sz == 0:
|
if sz == 0:
|
||||||
raise Pebkac(400, "empty files in post")
|
raise Pebkac(400, "empty files in post")
|
||||||
|
|
||||||
files.append([sz, sha512_hex, p_file, fname])
|
if lim:
|
||||||
dbv, vrem = vfs.get_dbv(rem)
|
lim.nup(self.ip)
|
||||||
self.conn.hsrv.broker.put(
|
lim.bup(self.ip, sz)
|
||||||
False,
|
try:
|
||||||
"up2k.hash_file",
|
lim.chk_sz(sz)
|
||||||
dbv.realpath,
|
except:
|
||||||
dbv.flags,
|
bos.unlink(abspath)
|
||||||
vrem,
|
raise
|
||||||
fname,
|
|
||||||
self.ip,
|
files.append([sz, sha512_hex, p_file, fname])
|
||||||
time.time(),
|
dbv, vrem = vfs.get_dbv(rem)
|
||||||
)
|
self.conn.hsrv.broker.put(
|
||||||
self.conn.nbyte += sz
|
False,
|
||||||
|
"up2k.hash_file",
|
||||||
|
dbv.realpath,
|
||||||
|
dbv.flags,
|
||||||
|
vrem,
|
||||||
|
fname,
|
||||||
|
self.ip,
|
||||||
|
time.time(),
|
||||||
|
)
|
||||||
|
self.conn.nbyte += sz
|
||||||
|
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
if fname != os.devnull:
|
if fname != os.devnull:
|
||||||
@@ -944,7 +1040,9 @@ class HttpCli(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
errmsg = unicode(ex)
|
errmsg = vol_san(
|
||||||
|
self.asrv.vfs.all_vols.values(), unicode(ex).encode("utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
|
||||||
td = max(0.1, time.time() - t0)
|
td = max(0.1, time.time() - t0)
|
||||||
sz_total = sum(x[0] for x in files)
|
sz_total = sum(x[0] for x in files)
|
||||||
@@ -964,7 +1062,7 @@ class HttpCli(object):
|
|||||||
errmsg = "ERROR: " + errmsg
|
errmsg = "ERROR: " + errmsg
|
||||||
|
|
||||||
for sz, sha512, ofn, lfn in files:
|
for sz, sha512, ofn, lfn in files:
|
||||||
vpath = (self.vpath + "/" if self.vpath else "") + lfn
|
vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
|
||||||
msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a>\n'.format(
|
msg += 'sha512: {} // {} bytes // <a href="/{}">{}</a>\n'.format(
|
||||||
sha512[:56], sz, quotep(vpath), html_escape(ofn, crlf=True)
|
sha512[:56], sz, quotep(vpath), html_escape(ofn, crlf=True)
|
||||||
)
|
)
|
||||||
@@ -1023,6 +1121,20 @@ 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)
|
||||||
|
|
||||||
|
clen = int(self.headers.get("content-length", -1))
|
||||||
|
if clen == -1:
|
||||||
|
raise Pebkac(411)
|
||||||
|
|
||||||
|
rp, fn = vsplit(rem)
|
||||||
|
fp = os.path.join(vfs.realpath, rp)
|
||||||
|
lim = vfs.get_dbv(rem)[0].lim
|
||||||
|
if lim:
|
||||||
|
fp, rp = lim.all(self.ip, rp, clen, fp)
|
||||||
|
bos.makedirs(fp)
|
||||||
|
|
||||||
|
fp = os.path.join(fp, fn)
|
||||||
|
rem = "{}/{}".format(rp, fn).strip("/")
|
||||||
|
|
||||||
if not rem.endswith(".md"):
|
if not rem.endswith(".md"):
|
||||||
raise Pebkac(400, "only markdown pls")
|
raise Pebkac(400, "only markdown pls")
|
||||||
|
|
||||||
@@ -1034,7 +1146,6 @@ class HttpCli(object):
|
|||||||
self.reply(response.encode("utf-8"))
|
self.reply(response.encode("utf-8"))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
fp = os.path.join(vfs.realpath, rem)
|
|
||||||
srv_lastmod = srv_lastmod3 = -1
|
srv_lastmod = srv_lastmod3 = -1
|
||||||
try:
|
try:
|
||||||
st = bos.stat(fp)
|
st = bos.stat(fp)
|
||||||
@@ -1088,6 +1199,15 @@ class HttpCli(object):
|
|||||||
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
||||||
sz, sha512, _ = hashcopy(p_data, f)
|
sz, sha512, _ = hashcopy(p_data, f)
|
||||||
|
|
||||||
|
if lim:
|
||||||
|
lim.nup(self.ip)
|
||||||
|
lim.bup(self.ip, sz)
|
||||||
|
try:
|
||||||
|
lim.chk_sz(sz)
|
||||||
|
except:
|
||||||
|
bos.unlink(fp)
|
||||||
|
raise
|
||||||
|
|
||||||
new_lastmod = bos.stat(fp).st_mtime
|
new_lastmod = bos.stat(fp).st_mtime
|
||||||
new_lastmod3 = int(new_lastmod * 1000)
|
new_lastmod3 = int(new_lastmod * 1000)
|
||||||
sha512 = sha512[:56]
|
sha512 = sha512[:56]
|
||||||
@@ -1579,7 +1699,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
q = "select sz, rd, fn, at from up where ip=? and at>?"
|
q = "select sz, rd, fn, at from up where ip=? and at>?"
|
||||||
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
|
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
|
||||||
vp = "/" + "/".join([rd, fn]).strip("/")
|
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
||||||
if filt and filt not in vp:
|
if filt and filt not in vp:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1755,7 +1875,7 @@ class HttpCli(object):
|
|||||||
"acct": self.uname,
|
"acct": self.uname,
|
||||||
"perms": json.dumps(perms),
|
"perms": json.dumps(perms),
|
||||||
"taglist": [],
|
"taglist": [],
|
||||||
"tag_order": [],
|
"def_hcols": [],
|
||||||
"have_up2k_idx": ("e2d" in vn.flags),
|
"have_up2k_idx": ("e2d" in vn.flags),
|
||||||
"have_tags_idx": ("e2t" in vn.flags),
|
"have_tags_idx": ("e2t" in vn.flags),
|
||||||
"have_mv": (not self.args.no_mv),
|
"have_mv": (not self.args.no_mv),
|
||||||
@@ -1952,8 +2072,8 @@ class HttpCli(object):
|
|||||||
j2a["logues"] = logues
|
j2a["logues"] = logues
|
||||||
j2a["taglist"] = taglist
|
j2a["taglist"] = taglist
|
||||||
|
|
||||||
if "mte" in vn.flags:
|
if "mth" in vn.flags:
|
||||||
j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
|
j2a["def_hcols"] = vn.flags["mth"].split(",")
|
||||||
|
|
||||||
if self.args.css_browser:
|
if self.args.css_browser:
|
||||||
j2a["css"] = self.args.css_browser
|
j2a["css"] = self.args.css_browser
|
||||||
|
|||||||
@@ -174,25 +174,26 @@ class HttpSrv(object):
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if now - (self.tp_time or now) > 300:
|
if now - (self.tp_time or now) > 300:
|
||||||
|
m = "httpserver threadpool died: tpt {:.2f}, now {:.2f}, nthr {}, ncli {}"
|
||||||
|
self.log(self.name, m.format(self.tp_time, now, self.tp_nthr, self.ncli), 1)
|
||||||
|
self.tp_time = None
|
||||||
self.tp_q = None
|
self.tp_q = None
|
||||||
|
|
||||||
if self.tp_q:
|
with self.mutex:
|
||||||
self.tp_q.put((sck, addr))
|
self.ncli += 1
|
||||||
with self.mutex:
|
if self.tp_q:
|
||||||
self.ncli += 1
|
|
||||||
self.tp_time = self.tp_time or now
|
self.tp_time = self.tp_time or now
|
||||||
self.tp_ncli = max(self.tp_ncli, self.ncli + 1)
|
self.tp_ncli = max(self.tp_ncli, self.ncli)
|
||||||
if self.tp_nthr < self.ncli + 4:
|
if self.tp_nthr < self.ncli + 4:
|
||||||
self.start_threads(8)
|
self.start_threads(8)
|
||||||
return
|
|
||||||
|
self.tp_q.put((sck, addr))
|
||||||
|
return
|
||||||
|
|
||||||
if not self.args.no_htp:
|
if not self.args.no_htp:
|
||||||
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
||||||
self.log(self.name, m, 1)
|
self.log(self.name, m, 1)
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
self.ncli += 1
|
|
||||||
|
|
||||||
thr = threading.Thread(
|
thr = threading.Thread(
|
||||||
target=self.thr_client,
|
target=self.thr_client,
|
||||||
args=(sck, addr),
|
args=(sck, addr),
|
||||||
|
|||||||
@@ -434,7 +434,15 @@ class MTag(object):
|
|||||||
try:
|
try:
|
||||||
v = getattr(md.info, attr)
|
v = getattr(md.info, attr)
|
||||||
except:
|
except:
|
||||||
continue
|
if k != "ac":
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
v = str(md.info).split(".")[1]
|
||||||
|
if v.startswith("ogg"):
|
||||||
|
v = v[3:]
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
if not v:
|
if not v:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ def errdesc(errors):
|
|||||||
tf_path = tf.name
|
tf_path = tf.name
|
||||||
tf.write("\r\n".join(report).encode("utf-8", "replace"))
|
tf.write("\r\n".join(report).encode("utf-8", "replace"))
|
||||||
|
|
||||||
dt = datetime.utcfromtimestamp(time.time())
|
dt = datetime.utcnow().strftime("%Y-%m%d-%H%M%S")
|
||||||
dt = dt.strftime("%Y-%m%d-%H%M%S")
|
|
||||||
|
|
||||||
bos.chmod(tf_path, 0o444)
|
bos.chmod(tf_path, 0o444)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from datetime import datetime, timedelta
|
|||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
from .__init__ import E, PY2, WINDOWS, ANYWIN, MACOS, VT100, unicode
|
from .__init__ import E, PY2, WINDOWS, ANYWIN, MACOS, VT100, unicode
|
||||||
from .util import mp, start_log_thrs, start_stackmon, min_ex
|
from .util import mp, start_log_thrs, start_stackmon, min_ex, ansi_re
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
@@ -41,7 +41,6 @@ class SvcHub(object):
|
|||||||
self.stop_cond = threading.Condition()
|
self.stop_cond = threading.Condition()
|
||||||
self.httpsrv_up = 0
|
self.httpsrv_up = 0
|
||||||
|
|
||||||
self.ansi_re = re.compile("\033\\[[^m]*m")
|
|
||||||
self.log_mutex = threading.Lock()
|
self.log_mutex = threading.Lock()
|
||||||
self.next_day = 0
|
self.next_day = 0
|
||||||
|
|
||||||
@@ -111,7 +110,7 @@ class SvcHub(object):
|
|||||||
thr.start()
|
thr.start()
|
||||||
|
|
||||||
def _logname(self):
|
def _logname(self):
|
||||||
dt = datetime.utcfromtimestamp(time.time())
|
dt = datetime.utcnow()
|
||||||
fn = self.args.lo
|
fn = self.args.lo
|
||||||
for fs in "YmdHMS":
|
for fs in "YmdHMS":
|
||||||
fs = "%" + fs
|
fs = "%" + fs
|
||||||
@@ -244,8 +243,7 @@ class SvcHub(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
ts = datetime.utcfromtimestamp(time.time())
|
ts = datetime.utcnow().strftime("%Y-%m%d-%H%M%S.%f")[:-3]
|
||||||
ts = ts.strftime("%Y-%m%d-%H%M%S.%f")[:-3]
|
|
||||||
self.logf.write("@{} [{}] {}\n".format(ts, src, msg))
|
self.logf.write("@{} [{}] {}\n".format(ts, src, msg))
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -257,7 +255,7 @@ class SvcHub(object):
|
|||||||
self.logf.close()
|
self.logf.close()
|
||||||
self._setup_logfile("")
|
self._setup_logfile("")
|
||||||
|
|
||||||
dt = datetime.utcfromtimestamp(time.time())
|
dt = datetime.utcnow()
|
||||||
|
|
||||||
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
||||||
day_now = dt.day
|
day_now = dt.day
|
||||||
@@ -280,9 +278,9 @@ class SvcHub(object):
|
|||||||
if not VT100:
|
if not VT100:
|
||||||
fmt = "{} {:21} {}\n"
|
fmt = "{} {:21} {}\n"
|
||||||
if "\033" in msg:
|
if "\033" in msg:
|
||||||
msg = self.ansi_re.sub("", msg)
|
msg = ansi_re.sub("", msg)
|
||||||
if "\033" in src:
|
if "\033" in src:
|
||||||
src = self.ansi_re.sub("", src)
|
src = ansi_re.sub("", src)
|
||||||
elif c:
|
elif c:
|
||||||
if isinstance(c, int):
|
if isinstance(c, int):
|
||||||
msg = "\033[3{}m{}".format(c, msg)
|
msg = "\033[3{}m{}".format(c, msg)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ HAVE_AVIF = False
|
|||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps, ExifTags
|
||||||
|
|
||||||
HAVE_PIL = True
|
HAVE_PIL = True
|
||||||
try:
|
try:
|
||||||
@@ -105,7 +105,10 @@ class ThumbSrv(object):
|
|||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.busy = {}
|
self.busy = {}
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.nthr = os.cpu_count() if hasattr(os, "cpu_count") else 4
|
self.nthr = self.args.th_mt
|
||||||
|
if not self.nthr:
|
||||||
|
self.nthr = os.cpu_count() if hasattr(os, "cpu_count") else 4
|
||||||
|
|
||||||
self.q = Queue(self.nthr * 4)
|
self.q = Queue(self.nthr * 4)
|
||||||
for n in range(self.nthr):
|
for n in range(self.nthr):
|
||||||
t = threading.Thread(
|
t = threading.Thread(
|
||||||
@@ -205,8 +208,8 @@ class ThumbSrv(object):
|
|||||||
try:
|
try:
|
||||||
fun(abspath, tpath)
|
fun(abspath, tpath)
|
||||||
except:
|
except:
|
||||||
msg = "{} failed on {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
self.log(msg.format(fun.__name__, abspath, min_ex()), 3)
|
self.log(msg.format(fun.__name__, abspath, min_ex()), "1;30")
|
||||||
with open(tpath, "wb") as _:
|
with open(tpath, "wb") as _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -221,21 +224,38 @@ class ThumbSrv(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.nthr -= 1
|
self.nthr -= 1
|
||||||
|
|
||||||
|
def fancy_pillow(self, im):
|
||||||
|
# exif_transpose is expensive (loads full image + unconditional copy)
|
||||||
|
r = max(*self.res) * 2
|
||||||
|
im.thumbnail((r, r), resample=Image.LANCZOS)
|
||||||
|
try:
|
||||||
|
k = next(k for k, v in ExifTags.TAGS.items() if v == "Orientation")
|
||||||
|
exif = im.getexif()
|
||||||
|
rot = int(exif[k])
|
||||||
|
del exif[k]
|
||||||
|
except:
|
||||||
|
rot = 1
|
||||||
|
|
||||||
|
rots = {8: Image.ROTATE_90, 3: Image.ROTATE_180, 6: Image.ROTATE_270}
|
||||||
|
if rot in rots:
|
||||||
|
im = im.transpose(rots[rot])
|
||||||
|
|
||||||
|
if self.args.th_no_crop:
|
||||||
|
im.thumbnail(self.res, resample=Image.LANCZOS)
|
||||||
|
else:
|
||||||
|
iw, ih = im.size
|
||||||
|
dw, dh = self.res
|
||||||
|
res = (min(iw, dw), min(ih, dh))
|
||||||
|
im = ImageOps.fit(im, res, method=Image.LANCZOS)
|
||||||
|
|
||||||
|
return im
|
||||||
|
|
||||||
def conv_pil(self, abspath, tpath):
|
def conv_pil(self, abspath, tpath):
|
||||||
with Image.open(fsenc(abspath)) as im:
|
with Image.open(fsenc(abspath)) as im:
|
||||||
crop = not self.args.th_no_crop
|
|
||||||
res2 = self.res
|
|
||||||
if crop:
|
|
||||||
res2 = (res2[0] * 2, res2[1] * 2)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
im.thumbnail(res2, resample=Image.LANCZOS)
|
im = self.fancy_pillow(im)
|
||||||
if crop:
|
except Exception as ex:
|
||||||
iw, ih = im.size
|
self.log("fancy_pillow {}".format(ex), "1;30")
|
||||||
dw, dh = self.res
|
|
||||||
res = (min(iw, dw), min(ih, dh))
|
|
||||||
im = ImageOps.fit(im, res, method=Image.LANCZOS)
|
|
||||||
except:
|
|
||||||
im.thumbnail(self.res)
|
im.thumbnail(self.res)
|
||||||
|
|
||||||
fmts = ["RGB", "L"]
|
fmts = ["RGB", "L"]
|
||||||
@@ -286,8 +306,10 @@ class ThumbSrv(object):
|
|||||||
cmd += seek
|
cmd += seek
|
||||||
cmd += [
|
cmd += [
|
||||||
b"-i", fsenc(abspath),
|
b"-i", fsenc(abspath),
|
||||||
|
b"-map", b"0:v:0",
|
||||||
b"-vf", scale,
|
b"-vf", scale,
|
||||||
b"-vframes", b"1",
|
b"-frames:v", b"1",
|
||||||
|
b"-metadata:s:v:0", b"rotate=0",
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@@ -305,11 +327,13 @@ class ThumbSrv(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
|
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||||
|
|
||||||
ret, sout, serr = runcmd(cmd)
|
ret, sout, serr = runcmd(cmd)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
msg = ["ff: {}".format(x) for x in serr.split("\n")]
|
m = "FFmpeg failed (probably a corrupt video file):\n"
|
||||||
self.log("FFmpeg failed:\n" + "\n".join(msg), c="1;30")
|
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
|
||||||
|
self.log(m, c="1;30")
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
def poke(self, tdir):
|
def poke(self, tdir):
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class U2idx(object):
|
|||||||
is_date = False
|
is_date = False
|
||||||
kw_key = ["(", ")", "and ", "or ", "not "]
|
kw_key = ["(", ")", "and ", "or ", "not "]
|
||||||
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
|
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
|
||||||
ptn_mt = re.compile(r"^\.?[a-z]+$")
|
ptn_mt = re.compile(r"^\.?[a-z_-]+$")
|
||||||
mt_ctr = 0
|
mt_ctr = 0
|
||||||
mt_keycmp = "substr(up.w,1,16)"
|
mt_keycmp = "substr(up.w,1,16)"
|
||||||
mt_keycmp2 = None
|
mt_keycmp2 = None
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from .util import (
|
|||||||
min_ex,
|
min_ex,
|
||||||
)
|
)
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv, LEELOO_DALLAS
|
||||||
from .mtag import MTag, MParser
|
from .mtag import MTag, MParser
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -176,36 +176,71 @@ class Up2k(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _sched_rescan(self):
|
def _sched_rescan(self):
|
||||||
maxage = self.args.re_maxage
|
|
||||||
volage = {}
|
volage = {}
|
||||||
while True:
|
while True:
|
||||||
time.sleep(self.args.re_int)
|
time.sleep(self.args.re_int)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
vpaths = list(sorted(self.asrv.vfs.all_vols.keys()))
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
if maxage:
|
for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
|
||||||
for vp in vpaths:
|
maxage = vol.flags.get("scan")
|
||||||
if vp not in volage:
|
if not maxage:
|
||||||
volage[vp] = now
|
continue
|
||||||
|
|
||||||
if now - volage[vp] >= maxage:
|
if vp not in volage:
|
||||||
self.need_rescan[vp] = 1
|
volage[vp] = now
|
||||||
|
|
||||||
if not self.need_rescan:
|
if now - volage[vp] >= maxage:
|
||||||
continue
|
self.need_rescan[vp] = 1
|
||||||
|
|
||||||
vols = list(sorted(self.need_rescan.keys()))
|
vols = list(sorted(self.need_rescan.keys()))
|
||||||
self.need_rescan = {}
|
self.need_rescan = {}
|
||||||
|
|
||||||
err = self.rescan(self.asrv.vfs.all_vols, vols)
|
if vols:
|
||||||
if err:
|
err = self.rescan(self.asrv.vfs.all_vols, vols)
|
||||||
for v in vols:
|
if err:
|
||||||
self.need_rescan[v] = True
|
for v in vols:
|
||||||
|
self.need_rescan[v] = True
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
for v in vols:
|
||||||
|
volage[v] = now
|
||||||
|
|
||||||
|
if self.args.no_lifetime:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for v in vols:
|
for vp, vol in sorted(self.asrv.vfs.all_vols.items()):
|
||||||
volage[v] = now
|
lifetime = vol.flags.get("lifetime")
|
||||||
|
if not lifetime:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur = self.cur.get(vol.realpath)
|
||||||
|
if not cur:
|
||||||
|
continue
|
||||||
|
|
||||||
|
nrm = 0
|
||||||
|
deadline = time.time() - int(lifetime)
|
||||||
|
q = "select rd, fn from up where at > 0 and at < ? limit 100"
|
||||||
|
while True:
|
||||||
|
with self.mutex:
|
||||||
|
hits = cur.execute(q, (deadline,)).fetchall()
|
||||||
|
|
||||||
|
if not hits:
|
||||||
|
break
|
||||||
|
|
||||||
|
for rd, fn in hits:
|
||||||
|
if rd.startswith("//") or fn.startswith("//"):
|
||||||
|
rd, fn = s3dec(rd, fn)
|
||||||
|
|
||||||
|
fvp = "{}/{}".format(rd, fn).strip("/")
|
||||||
|
if vp:
|
||||||
|
fvp = "{}/{}".format(vp, fvp)
|
||||||
|
|
||||||
|
self._handle_rm(LEELOO_DALLAS, None, fvp)
|
||||||
|
nrm += 1
|
||||||
|
|
||||||
|
if nrm:
|
||||||
|
self.log("{} files graduated in {}".format(nrm, vp))
|
||||||
|
|
||||||
def _vis_job_progress(self, job):
|
def _vis_job_progress(self, job):
|
||||||
perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
|
perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"]))
|
||||||
@@ -1148,6 +1183,16 @@ class Up2k(object):
|
|||||||
cur.connection.commit()
|
cur.connection.commit()
|
||||||
|
|
||||||
if not job:
|
if not job:
|
||||||
|
vfs = self.asrv.vfs.all_vols[cj["vtop"]]
|
||||||
|
if vfs.lim:
|
||||||
|
ap1 = os.path.join(cj["ptop"], cj["prel"])
|
||||||
|
ap2, cj["prel"] = vfs.lim.all(
|
||||||
|
cj["addr"], cj["prel"], cj["size"], ap1
|
||||||
|
)
|
||||||
|
bos.makedirs(ap2)
|
||||||
|
vfs.lim.nup(cj["addr"])
|
||||||
|
vfs.lim.bup(cj["addr"], cj["size"])
|
||||||
|
|
||||||
job = {
|
job = {
|
||||||
"wark": wark,
|
"wark": wark,
|
||||||
"t0": now,
|
"t0": now,
|
||||||
@@ -1178,8 +1223,12 @@ class Up2k(object):
|
|||||||
|
|
||||||
self._new_upload(job)
|
self._new_upload(job)
|
||||||
|
|
||||||
|
purl = "{}/{}".format(job["vtop"], job["prel"]).strip("/")
|
||||||
|
purl = "/{}/".format(purl) if purl else "/"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": job["name"],
|
"name": job["name"],
|
||||||
|
"purl": purl,
|
||||||
"size": job["size"],
|
"size": job["size"],
|
||||||
"lmod": job["lmod"],
|
"lmod": job["lmod"],
|
||||||
"hash": job["need"],
|
"hash": job["need"],
|
||||||
@@ -1230,7 +1279,7 @@ class Up2k(object):
|
|||||||
hops = len(ndst[nc:]) - 1
|
hops = len(ndst[nc:]) - 1
|
||||||
lsrc = "../" * hops + "/".join(lsrc)
|
lsrc = "../" * hops + "/".join(lsrc)
|
||||||
os.symlink(fsenc(lsrc), fsenc(ldst))
|
os.symlink(fsenc(lsrc), fsenc(ldst))
|
||||||
except (AttributeError, OSError) as ex:
|
except Exception as ex:
|
||||||
self.log("cannot symlink; creating copy: " + repr(ex))
|
self.log("cannot symlink; creating copy: " + repr(ex))
|
||||||
shutil.copy2(fsenc(src), fsenc(dst))
|
shutil.copy2(fsenc(src), fsenc(dst))
|
||||||
|
|
||||||
@@ -1422,9 +1471,10 @@ class Up2k(object):
|
|||||||
if not srem:
|
if not srem:
|
||||||
raise Pebkac(400, "mv: cannot move a mountpoint")
|
raise Pebkac(400, "mv: cannot move a mountpoint")
|
||||||
|
|
||||||
st = bos.stat(sabs)
|
st = bos.lstat(sabs)
|
||||||
if stat.S_ISREG(st.st_mode):
|
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
|
||||||
return self._mv_file(uname, svp, dvp)
|
with self.mutex:
|
||||||
|
return self._mv_file(uname, svp, dvp)
|
||||||
|
|
||||||
jail = svn.get_dbv(srem)[0]
|
jail = svn.get_dbv(srem)[0]
|
||||||
permsets = [[True, False, True]]
|
permsets = [[True, False, True]]
|
||||||
@@ -1449,7 +1499,8 @@ class Up2k(object):
|
|||||||
raise Pebkac(500, "mv: bug at {}, top {}".format(svpf, svp))
|
raise Pebkac(500, "mv: bug at {}, top {}".format(svpf, svp))
|
||||||
|
|
||||||
dvpf = dvp + svpf[len(svp) :]
|
dvpf = dvp + svpf[len(svp) :]
|
||||||
self._mv_file(uname, svpf, dvpf)
|
with self.mutex:
|
||||||
|
self._mv_file(uname, svpf, dvpf)
|
||||||
|
|
||||||
rmdirs(self.log_func, scandir, True, sabs)
|
rmdirs(self.log_func, scandir, True, sabs)
|
||||||
return "k"
|
return "k"
|
||||||
@@ -1541,13 +1592,15 @@ class Up2k(object):
|
|||||||
self.log("forgetting {}".format(vrem))
|
self.log("forgetting {}".format(vrem))
|
||||||
if wark:
|
if wark:
|
||||||
self.log("found {} in db".format(wark))
|
self.log("found {} in db".format(wark))
|
||||||
if self._relink(wark, ptop, vrem, None):
|
if drop_tags:
|
||||||
drop_tags = False
|
if self._relink(wark, ptop, vrem, None):
|
||||||
|
drop_tags = False
|
||||||
|
|
||||||
if drop_tags:
|
if drop_tags:
|
||||||
q = "delete from mt where w=?"
|
q = "delete from mt where w=?"
|
||||||
cur.execute(q, (wark[:16],))
|
cur.execute(q, (wark[:16],))
|
||||||
self.db_rm(cur, srd, sfn)
|
|
||||||
|
self.db_rm(cur, srd, sfn)
|
||||||
|
|
||||||
reg = self.registry.get(ptop)
|
reg = self.registry.get(ptop)
|
||||||
if reg:
|
if reg:
|
||||||
@@ -1599,7 +1652,7 @@ class Up2k(object):
|
|||||||
# deleting final remaining full copy; swap it with a symlink
|
# deleting final remaining full copy; swap it with a symlink
|
||||||
slabs = list(sorted(links.keys()))[0]
|
slabs = list(sorted(links.keys()))[0]
|
||||||
ptop, rem = links.pop(slabs)
|
ptop, rem = links.pop(slabs)
|
||||||
self.log("linkswap [{}] and [{}]".format(sabs, dabs))
|
self.log("linkswap [{}] and [{}]".format(sabs, slabs))
|
||||||
bos.unlink(slabs)
|
bos.unlink(slabs)
|
||||||
bos.rename(sabs, slabs)
|
bos.rename(sabs, slabs)
|
||||||
self._symlink(slabs, sabs, False)
|
self._symlink(slabs, sabs, False)
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ except:
|
|||||||
return struct.unpack(f.decode("ascii"), *a, **ka)
|
return struct.unpack(f.decode("ascii"), *a, **ka)
|
||||||
|
|
||||||
|
|
||||||
|
ansi_re = re.compile("\033\\[[^m]*m")
|
||||||
|
|
||||||
|
|
||||||
surrogateescape.register_surrogateescape()
|
surrogateescape.register_surrogateescape()
|
||||||
FS_ENCODING = sys.getfilesystemencoding()
|
FS_ENCODING = sys.getfilesystemencoding()
|
||||||
if WINDOWS and PY2:
|
if WINDOWS and PY2:
|
||||||
@@ -77,6 +80,7 @@ HTTPCODE = {
|
|||||||
403: "Forbidden",
|
403: "Forbidden",
|
||||||
404: "Not Found",
|
404: "Not Found",
|
||||||
405: "Method Not Allowed",
|
405: "Method Not Allowed",
|
||||||
|
411: "Length Required",
|
||||||
413: "Payload Too Large",
|
413: "Payload Too Large",
|
||||||
416: "Requested Range Not Satisfiable",
|
416: "Requested Range Not Satisfiable",
|
||||||
422: "Unprocessable Entity",
|
422: "Unprocessable Entity",
|
||||||
@@ -340,6 +344,13 @@ def log_thrs(log, ival, name):
|
|||||||
log(name, "\033[0m \033[33m".join(tv), 3)
|
log(name, "\033[0m \033[33m".join(tv), 3)
|
||||||
|
|
||||||
|
|
||||||
|
def vol_san(vols, txt):
|
||||||
|
for vol in vols:
|
||||||
|
txt = txt.replace(vol.realpath.encode("utf-8"), vol.vpath.encode("utf-8"))
|
||||||
|
|
||||||
|
return txt
|
||||||
|
|
||||||
|
|
||||||
def min_ex():
|
def min_ex():
|
||||||
et, ev, tb = sys.exc_info()
|
et, ev, tb = sys.exc_info()
|
||||||
tb = traceback.extract_tb(tb)
|
tb = traceback.extract_tb(tb)
|
||||||
@@ -684,6 +695,17 @@ def humansize(sz, terse=False):
|
|||||||
return ret.replace("iB", "").replace(" ", "")
|
return ret.replace("iB", "").replace(" ", "")
|
||||||
|
|
||||||
|
|
||||||
|
def unhumanize(sz):
|
||||||
|
try:
|
||||||
|
return float(sz)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
mul = sz[-1:].lower()
|
||||||
|
mul = {"k": 1024, "m": 1024 * 1024, "g": 1024 * 1024 * 1024}.get(mul, 1)
|
||||||
|
return float(sz[:-1]) * mul
|
||||||
|
|
||||||
|
|
||||||
def get_spd(nbyte, t0, t=None):
|
def get_spd(nbyte, t0, t=None):
|
||||||
if t is None:
|
if t is None:
|
||||||
t = time.time()
|
t = time.time()
|
||||||
@@ -1065,7 +1087,7 @@ def statdir(logger, scandir, lstat, top):
|
|||||||
def rmdirs(logger, scandir, lstat, top):
|
def rmdirs(logger, scandir, lstat, top):
|
||||||
if not os.path.exists(fsenc(top)) or not os.path.isdir(fsenc(top)):
|
if not os.path.exists(fsenc(top)) or not os.path.isdir(fsenc(top)):
|
||||||
top = os.path.dirname(top)
|
top = os.path.dirname(top)
|
||||||
|
|
||||||
dirs = statdir(logger, scandir, lstat, top)
|
dirs = statdir(logger, scandir, lstat, top)
|
||||||
dirs = [x[0] for x in dirs if stat.S_ISDIR(x[1].st_mode)]
|
dirs = [x[0] for x in dirs if stat.S_ISDIR(x[1].st_mode)]
|
||||||
dirs = [os.path.join(top, x) for x in dirs]
|
dirs = [os.path.join(top, x) for x in dirs]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ window.baguetteBox = (function () {
|
|||||||
afterHide: null,
|
afterHide: null,
|
||||||
onChange: null,
|
onChange: null,
|
||||||
},
|
},
|
||||||
overlay, slider, btnPrev, btnNext, btnHelp, btnVmode, btnClose,
|
overlay, slider, btnPrev, btnNext, btnHelp, btnRotL, btnRotR, btnSel, btnVmode, btnClose,
|
||||||
currentGallery = [],
|
currentGallery = [],
|
||||||
currentIndex = 0,
|
currentIndex = 0,
|
||||||
isOverlayVisible = false,
|
isOverlayVisible = false,
|
||||||
@@ -49,7 +49,7 @@ window.baguetteBox = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var touchstartHandler = function (e) {
|
var touchstartHandler = function (e) {
|
||||||
touch.count++;
|
touch.count = e.touches.length;
|
||||||
if (touch.count > 1)
|
if (touch.count > 1)
|
||||||
touch.multitouch = true;
|
touch.multitouch = true;
|
||||||
|
|
||||||
@@ -72,8 +72,11 @@ window.baguetteBox = (function () {
|
|||||||
hideOverlay();
|
hideOverlay();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var touchendHandler = function () {
|
var touchendHandler = function (e) {
|
||||||
touch.count--;
|
touch.count--;
|
||||||
|
if (e && e.touches)
|
||||||
|
touch.count = e.touches.length;
|
||||||
|
|
||||||
if (touch.count <= 0)
|
if (touch.count <= 0)
|
||||||
touch.multitouch = false;
|
touch.multitouch = false;
|
||||||
|
|
||||||
@@ -175,6 +178,9 @@ window.baguetteBox = (function () {
|
|||||||
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">></button>' +
|
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">></button>' +
|
||||||
'<div id="bbox-btns">' +
|
'<div id="bbox-btns">' +
|
||||||
'<button id="bbox-help" type="button">?</button>' +
|
'<button id="bbox-help" type="button">?</button>' +
|
||||||
|
'<button id="bbox-rotl" type="button">↶</button>' +
|
||||||
|
'<button id="bbox-rotr" type="button">↷</button>' +
|
||||||
|
'<button id="bbox-tsel" type="button">sel</button>' +
|
||||||
'<button id="bbox-vmode" type="button" tt="a"></button>' +
|
'<button id="bbox-vmode" type="button" tt="a"></button>' +
|
||||||
'<button id="bbox-close" type="button" aria-label="Close">X</button>' +
|
'<button id="bbox-close" type="button" aria-label="Close">X</button>' +
|
||||||
'</div></div>'
|
'</div></div>'
|
||||||
@@ -187,6 +193,9 @@ window.baguetteBox = (function () {
|
|||||||
btnPrev = ebi('bbox-prev');
|
btnPrev = ebi('bbox-prev');
|
||||||
btnNext = ebi('bbox-next');
|
btnNext = ebi('bbox-next');
|
||||||
btnHelp = ebi('bbox-help');
|
btnHelp = ebi('bbox-help');
|
||||||
|
btnRotL = ebi('bbox-rotl');
|
||||||
|
btnRotR = ebi('bbox-rotr');
|
||||||
|
btnSel = ebi('bbox-tsel');
|
||||||
btnVmode = ebi('bbox-vmode');
|
btnVmode = ebi('bbox-vmode');
|
||||||
btnClose = ebi('bbox-close');
|
btnClose = ebi('bbox-close');
|
||||||
bindEvents();
|
bindEvents();
|
||||||
@@ -203,11 +212,13 @@ window.baguetteBox = (function () {
|
|||||||
['right, L', 'next file'],
|
['right, L', 'next file'],
|
||||||
['home', 'first file'],
|
['home', 'first file'],
|
||||||
['end', 'last file'],
|
['end', 'last file'],
|
||||||
|
['R', 'rotate (shift=ccw)'],
|
||||||
|
['S', 'toggle file selection'],
|
||||||
['space, P, K', 'video: play / pause'],
|
['space, P, K', 'video: play / pause'],
|
||||||
['U', 'video: seek 10sec back'],
|
['U', 'video: seek 10sec back'],
|
||||||
['P', 'video: seek 10sec ahead'],
|
['P', 'video: seek 10sec ahead'],
|
||||||
['M', 'video: toggle mute'],
|
['M', 'video: toggle mute'],
|
||||||
['R', 'video: toggle loop'],
|
['V', 'video: toggle loop'],
|
||||||
['C', 'video: toggle auto-next'],
|
['C', 'video: toggle auto-next'],
|
||||||
['F', 'video: toggle fullscreen'],
|
['F', 'video: toggle fullscreen'],
|
||||||
],
|
],
|
||||||
@@ -249,7 +260,7 @@ window.baguetteBox = (function () {
|
|||||||
v.muted = vmute = !vmute;
|
v.muted = vmute = !vmute;
|
||||||
mp_ctl();
|
mp_ctl();
|
||||||
}
|
}
|
||||||
else if (k == "KeyR" && v) {
|
else if (k == "KeyV" && v) {
|
||||||
vloop = !vloop;
|
vloop = !vloop;
|
||||||
vnext = vnext && !vloop;
|
vnext = vnext && !vloop;
|
||||||
setVmode();
|
setVmode();
|
||||||
@@ -267,6 +278,10 @@ window.baguetteBox = (function () {
|
|||||||
v.requestFullscreen();
|
v.requestFullscreen();
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
else if (k == "KeyS")
|
||||||
|
tglsel();
|
||||||
|
else if (k == "KeyR")
|
||||||
|
rotn(e.shiftKey ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVmode() {
|
function setVmode() {
|
||||||
@@ -279,7 +294,7 @@ window.baguetteBox = (function () {
|
|||||||
if (vloop) {
|
if (vloop) {
|
||||||
lbl = 'Loop';
|
lbl = 'Loop';
|
||||||
msg += 'repeat it';
|
msg += 'repeat it';
|
||||||
tts = '$NHotkey: R';
|
tts = '$NHotkey: V';
|
||||||
}
|
}
|
||||||
else if (vnext) {
|
else if (vnext) {
|
||||||
lbl = 'Cont';
|
lbl = 'Cont';
|
||||||
@@ -314,6 +329,40 @@ window.baguetteBox = (function () {
|
|||||||
tt.show.bind(this)();
|
tt.show.bind(this)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tglsel() {
|
||||||
|
var thumb = currentGallery[currentIndex].imageElement,
|
||||||
|
name = vsplit(thumb.href)[1],
|
||||||
|
files = msel.getall();
|
||||||
|
|
||||||
|
for (var a = 0; a < files.length; a++)
|
||||||
|
if (vsplit(files[a].vp)[1] == name)
|
||||||
|
clmod(ebi(files[a].id).closest('tr'), 'sel', 't');
|
||||||
|
|
||||||
|
msel.selui();
|
||||||
|
selbg();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selbg() {
|
||||||
|
var img = vidimg(),
|
||||||
|
thumb = currentGallery[currentIndex].imageElement,
|
||||||
|
name = vsplit(thumb.href)[1],
|
||||||
|
files = msel.getsel(),
|
||||||
|
sel = false;
|
||||||
|
|
||||||
|
for (var a = 0; a < files.length; a++)
|
||||||
|
if (vsplit(files[a].vp)[1] == name)
|
||||||
|
sel = true;
|
||||||
|
|
||||||
|
ebi('bbox-overlay').style.background = sel ?
|
||||||
|
'rgba(153,34,85,0.7)' : '';
|
||||||
|
|
||||||
|
img.style.borderRadius = sel ? '1em' : '';
|
||||||
|
btnSel.style.color = sel ? '#fff' : '';
|
||||||
|
btnSel.style.background = sel ? '#d48' : '';
|
||||||
|
btnSel.style.textShadow = sel ? '1px 1px 0 #b38' : '';
|
||||||
|
btnSel.style.boxShadow = sel ? '.15em .15em 0 #502' : '';
|
||||||
|
}
|
||||||
|
|
||||||
function keyUpHandler(e) {
|
function keyUpHandler(e) {
|
||||||
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||||
return;
|
return;
|
||||||
@@ -348,6 +397,9 @@ window.baguetteBox = (function () {
|
|||||||
bind(btnClose, 'click', hideOverlay);
|
bind(btnClose, 'click', hideOverlay);
|
||||||
bind(btnVmode, 'click', tglVmode);
|
bind(btnVmode, 'click', tglVmode);
|
||||||
bind(btnHelp, 'click', halp);
|
bind(btnHelp, 'click', halp);
|
||||||
|
bind(btnRotL, 'click', rotl);
|
||||||
|
bind(btnRotR, 'click', rotr);
|
||||||
|
bind(btnSel, 'click', tglsel);
|
||||||
bind(slider, 'contextmenu', contextmenuHandler);
|
bind(slider, 'contextmenu', contextmenuHandler);
|
||||||
bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
|
bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
|
||||||
bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
|
bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
|
||||||
@@ -362,11 +414,15 @@ window.baguetteBox = (function () {
|
|||||||
unbind(btnClose, 'click', hideOverlay);
|
unbind(btnClose, 'click', hideOverlay);
|
||||||
unbind(btnVmode, 'click', tglVmode);
|
unbind(btnVmode, 'click', tglVmode);
|
||||||
unbind(btnHelp, 'click', halp);
|
unbind(btnHelp, 'click', halp);
|
||||||
|
unbind(btnRotL, 'click', rotl);
|
||||||
|
unbind(btnRotR, 'click', rotr);
|
||||||
|
unbind(btnSel, 'click', tglsel);
|
||||||
unbind(slider, 'contextmenu', contextmenuHandler);
|
unbind(slider, 'contextmenu', contextmenuHandler);
|
||||||
unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
|
unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
|
||||||
unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
|
unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
|
||||||
unbind(overlay, 'touchend', touchendHandler);
|
unbind(overlay, 'touchend', touchendHandler);
|
||||||
unbind(document, 'focus', trapFocusInsideOverlay, true);
|
unbind(document, 'focus', trapFocusInsideOverlay, true);
|
||||||
|
timer.rm(rotn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareOverlay(gallery, userOptions) {
|
function prepareOverlay(gallery, userOptions) {
|
||||||
@@ -617,10 +673,91 @@ window.baguetteBox = (function () {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prev_cw = 0, prev_ch = 0, unrot_timer = null;
|
||||||
|
function rotn(n) {
|
||||||
|
var el = vidimg(),
|
||||||
|
orot = parseInt(el.getAttribute('rot') || 0),
|
||||||
|
frot = orot + (n || 0) * 90;
|
||||||
|
|
||||||
|
if (!frot && !orot)
|
||||||
|
return; // reflow noop
|
||||||
|
|
||||||
|
var co = ebi('bbox-overlay'),
|
||||||
|
cw = co.clientWidth,
|
||||||
|
ch = co.clientHeight;
|
||||||
|
|
||||||
|
if (!n && prev_cw === cw && prev_ch === ch)
|
||||||
|
return; // reflow noop
|
||||||
|
|
||||||
|
prev_cw = cw;
|
||||||
|
prev_ch = ch;
|
||||||
|
var rot = frot,
|
||||||
|
iw = el.naturalWidth || el.videoWidth,
|
||||||
|
ih = el.naturalHeight || el.videoHeight,
|
||||||
|
magic = 4, // idk, works in enough browsers
|
||||||
|
dl = el.closest('div').querySelector('figcaption a'),
|
||||||
|
vw = cw,
|
||||||
|
vh = ch - dl.offsetHeight + magic,
|
||||||
|
pmag = Math.min(1, Math.min(vw / ih, vh / iw)),
|
||||||
|
wmag = Math.min(1, Math.min(vw / iw, vh / ih));
|
||||||
|
|
||||||
|
while (rot < 0) rot += 360;
|
||||||
|
while (rot >= 360) rot -= 360;
|
||||||
|
var q = rot == 90 || rot == 270 ? 1 : 0,
|
||||||
|
mag = q ? pmag : wmag;
|
||||||
|
|
||||||
|
el.style.cssText = 'max-width:none; max-height:none; position:absolute; display:block; margin:0';
|
||||||
|
if (!orot) {
|
||||||
|
el.style.width = iw * wmag + 'px';
|
||||||
|
el.style.height = ih * wmag + 'px';
|
||||||
|
el.style.left = (vw - iw * wmag) / 2 + 'px';
|
||||||
|
el.style.top = (vh - ih * wmag) / 2 - magic + 'px';
|
||||||
|
q = el.offsetHeight;
|
||||||
|
}
|
||||||
|
el.style.width = iw * mag + 'px';
|
||||||
|
el.style.height = ih * mag + 'px';
|
||||||
|
el.style.left = (vw - iw * mag) / 2 + 'px';
|
||||||
|
el.style.top = (vh - ih * mag) / 2 - magic + 'px';
|
||||||
|
el.style.transform = 'rotate(' + frot + 'deg)';
|
||||||
|
el.setAttribute('rot', frot);
|
||||||
|
timer.add(rotn);
|
||||||
|
if (!rot) {
|
||||||
|
clearTimeout(unrot_timer);
|
||||||
|
unrot_timer = setTimeout(unrot, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function rotl() {
|
||||||
|
rotn(-1);
|
||||||
|
}
|
||||||
|
function rotr() {
|
||||||
|
rotn(1);
|
||||||
|
}
|
||||||
|
function unrot() {
|
||||||
|
var el = vidimg(),
|
||||||
|
orot = el.getAttribute('rot'),
|
||||||
|
rot = parseInt(orot || 0);
|
||||||
|
|
||||||
|
while (rot < 0) rot += 360;
|
||||||
|
while (rot >= 360) rot -= 360;
|
||||||
|
if (rot || orot === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
clmod(el, 'nt', 1);
|
||||||
|
el.removeAttribute('rot');
|
||||||
|
el.removeAttribute("style");
|
||||||
|
rot = el.offsetHeight;
|
||||||
|
clmod(el, 'nt');
|
||||||
|
timer.rm(rotn);
|
||||||
|
}
|
||||||
|
|
||||||
function vid() {
|
function vid() {
|
||||||
return imagesElements[currentIndex].querySelector('video');
|
return imagesElements[currentIndex].querySelector('video');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vidimg() {
|
||||||
|
return imagesElements[currentIndex].querySelector('img, video');
|
||||||
|
}
|
||||||
|
|
||||||
function playvid(play) {
|
function playvid(play) {
|
||||||
if (vid())
|
if (vid())
|
||||||
vid()[play ? 'play' : 'pause']();
|
vid()[play ? 'play' : 'pause']();
|
||||||
@@ -662,15 +799,21 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateOffset() {
|
function updateOffset() {
|
||||||
var offset = -currentIndex * 100 + '%';
|
var offset = -currentIndex * 100 + '%',
|
||||||
|
xform = slider.style.perspective !== undefined;
|
||||||
|
|
||||||
if (options.animation === 'fadeIn') {
|
if (options.animation === 'fadeIn') {
|
||||||
slider.style.opacity = 0;
|
slider.style.opacity = 0;
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
xform ?
|
||||||
|
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||||
|
slider.style.left = offset;
|
||||||
slider.style.opacity = 1;
|
slider.style.opacity = 1;
|
||||||
}, 400);
|
}, 400);
|
||||||
} else {
|
} else {
|
||||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
xform ?
|
||||||
|
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||||
|
slider.style.left = offset;
|
||||||
}
|
}
|
||||||
playvid(false);
|
playvid(false);
|
||||||
var v = vid();
|
var v = vid();
|
||||||
@@ -679,8 +822,21 @@ window.baguetteBox = (function () {
|
|||||||
v.muted = vmute;
|
v.muted = vmute;
|
||||||
v.loop = vloop;
|
v.loop = vloop;
|
||||||
}
|
}
|
||||||
|
selbg();
|
||||||
mp_ctl();
|
mp_ctl();
|
||||||
setVmode();
|
setVmode();
|
||||||
|
|
||||||
|
var el = vidimg();
|
||||||
|
if (el.getAttribute('rot'))
|
||||||
|
timer.add(rotn);
|
||||||
|
else
|
||||||
|
timer.rm(rotn);
|
||||||
|
|
||||||
|
var prev = QS('.full-image.vis');
|
||||||
|
if (prev)
|
||||||
|
clmod(prev, 'vis');
|
||||||
|
|
||||||
|
clmod(el.closest('div'), 'vis', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function preloadNext(index) {
|
function preloadNext(index) {
|
||||||
|
|||||||
@@ -22,128 +22,9 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
pre, code, tt {
|
pre, code, tt {
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#tt, #toast {
|
|
||||||
position: fixed;
|
|
||||||
max-width: 34em;
|
|
||||||
background: #222;
|
|
||||||
border: 0 solid #777;
|
|
||||||
box-shadow: 0 .2em .5em #222;
|
|
||||||
border-radius: .4em;
|
|
||||||
z-index: 9001;
|
|
||||||
}
|
|
||||||
#tt {
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 1em;
|
|
||||||
padding: 0 1.3em;
|
|
||||||
height: 0;
|
|
||||||
opacity: .1;
|
|
||||||
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
|
||||||
}
|
|
||||||
#toast {
|
|
||||||
bottom: 5em;
|
|
||||||
right: -1em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
padding: 1em 1.3em;
|
|
||||||
border-width: .4em 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
transition:
|
|
||||||
transform .4s cubic-bezier(.2, 1.2, .5, 1),
|
|
||||||
right .4s cubic-bezier(.2, 1.2, .5, 1);
|
|
||||||
text-shadow: 1px 1px 0 #000;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
#toastc {
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
overflow: hidden;
|
|
||||||
left: 0;
|
|
||||||
width: 0;
|
|
||||||
opacity: 0;
|
|
||||||
padding: .3em 0;
|
|
||||||
margin: -.3em 0 0 0;
|
|
||||||
line-height: 1.5em;
|
|
||||||
color: #000;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
text-shadow: none;
|
|
||||||
border-radius: .5em 0 0 .5em;
|
|
||||||
transition: left .3s, width .3s, padding .3s, opacity .3s;
|
|
||||||
}
|
|
||||||
#toast pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#toast.vis {
|
|
||||||
right: 1.3em;
|
|
||||||
transform: unset;
|
|
||||||
}
|
|
||||||
#toast.vis #toastc {
|
|
||||||
left: -2em;
|
|
||||||
width: .4em;
|
|
||||||
padding: .3em .8em;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#toast.inf {
|
|
||||||
background: #07a;
|
|
||||||
border-color: #0be;
|
|
||||||
}
|
|
||||||
#toast.inf #toastc {
|
|
||||||
background: #0be;
|
|
||||||
}
|
|
||||||
#toast.ok {
|
|
||||||
background: #4a0;
|
|
||||||
border-color: #8e4;
|
|
||||||
}
|
|
||||||
#toast.ok #toastc {
|
|
||||||
background: #8e4;
|
|
||||||
}
|
|
||||||
#toast.warn {
|
|
||||||
background: #970;
|
|
||||||
border-color: #fc0;
|
|
||||||
}
|
|
||||||
#toast.warn #toastc {
|
|
||||||
background: #fc0;
|
|
||||||
}
|
|
||||||
#toast.err {
|
|
||||||
background: #900;
|
|
||||||
border-color: #d06;
|
|
||||||
}
|
|
||||||
#toast.err #toastc {
|
|
||||||
background: #d06;
|
|
||||||
}
|
|
||||||
#tt.b {
|
|
||||||
padding: 0 2em;
|
|
||||||
border-radius: .5em;
|
|
||||||
box-shadow: 0 .2em 1em #000;
|
|
||||||
}
|
|
||||||
#tt.show {
|
|
||||||
padding: 1em 1.3em;
|
|
||||||
border-width: .4em 0;
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#tt.show.b {
|
|
||||||
padding: 1.5em 2em;
|
|
||||||
border-width: .5em 0;
|
|
||||||
}
|
|
||||||
#tt code {
|
|
||||||
background: #3c3c3c;
|
|
||||||
padding: .1em .3em;
|
|
||||||
border-top: 1px solid #777;
|
|
||||||
border-radius: .3em;
|
|
||||||
line-height: 1.7em;
|
|
||||||
}
|
|
||||||
#tt em {
|
|
||||||
color: #f6a;
|
|
||||||
}
|
|
||||||
#path,
|
#path,
|
||||||
#path * {
|
#path * {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@@ -536,17 +417,10 @@ html.light #ggrid a.sel {
|
|||||||
html.light #wfm a:not(.en) {
|
html.light #wfm a:not(.en) {
|
||||||
color: #c4a;
|
color: #c4a;
|
||||||
}
|
}
|
||||||
#files tbody tr.c1 td {
|
#files tbody tr.fcut td {
|
||||||
animation: fcut1 .5s ease-out;
|
animation: fcut .5s ease-out;
|
||||||
}
|
}
|
||||||
#files tbody tr.c2 td {
|
@keyframes fcut {
|
||||||
animation: fcut2 .5s ease-out;
|
|
||||||
}
|
|
||||||
@keyframes fcut1 {
|
|
||||||
0% {opacity:0}
|
|
||||||
100% {opacity:1}
|
|
||||||
}
|
|
||||||
@keyframes fcut2 {
|
|
||||||
0% {opacity:0}
|
0% {opacity:0}
|
||||||
100% {opacity:1}
|
100% {opacity:1}
|
||||||
}
|
}
|
||||||
@@ -810,6 +684,7 @@ input.eq_gain {
|
|||||||
#wrap {
|
#wrap {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
min-height: 90vh;
|
min-height: 90vh;
|
||||||
|
padding-bottom: 5em;
|
||||||
}
|
}
|
||||||
#tree {
|
#tree {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -827,6 +702,10 @@ input.eq_gain {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
height: 2.2em;
|
||||||
|
line-height: 2.2em;
|
||||||
|
border-bottom: 1px solid #555;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
#thx_ff {
|
#thx_ff {
|
||||||
padding: 5em 0;
|
padding: 5em 0;
|
||||||
@@ -1014,6 +893,7 @@ html.light #ghead {
|
|||||||
}
|
}
|
||||||
#ggrid a {
|
#ggrid a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
width: 10em;
|
||||||
width: var(--grid-sz);
|
width: var(--grid-sz);
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
@@ -1027,7 +907,9 @@ html.light #ghead {
|
|||||||
}
|
}
|
||||||
#ggrid a img {
|
#ggrid a img {
|
||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
|
max-width: 10em;
|
||||||
max-width: var(--grid-sz);
|
max-width: var(--grid-sz);
|
||||||
|
max-height: 8em;
|
||||||
max-height: calc(var(--grid-sz)/1.25);
|
max-height: calc(var(--grid-sz)/1.25);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -1077,6 +959,7 @@ html.light #ggrid a:hover {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: calc(100% - 2em);
|
max-height: calc(100% - 2em);
|
||||||
border-bottom: .5em solid #999;
|
border-bottom: .5em solid #999;
|
||||||
|
box-shadow: 0 0 5em rgba(0,0,0,0.8);
|
||||||
background: #333;
|
background: #333;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
z-index: 765;
|
z-index: 765;
|
||||||
@@ -1089,14 +972,31 @@ html.light #rui {
|
|||||||
}
|
}
|
||||||
#rui table {
|
#rui table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
#rui td {
|
#rui td+td {
|
||||||
padding: .2em .5em;
|
padding: .2em 0 .2em .5em;
|
||||||
|
}
|
||||||
|
#rn_vadv input {
|
||||||
|
font-family: monospace, monospace;
|
||||||
}
|
}
|
||||||
#rui td+td,
|
#rui td+td,
|
||||||
#rui td input {
|
#rui td input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#rn_f.m td:first-child {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#rn_f.m td+td {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
#rn_f .err td {
|
||||||
|
background: #c00;
|
||||||
|
}
|
||||||
|
#rn_f .err input[readonly] {
|
||||||
|
background: #600;
|
||||||
|
color: #fc0;
|
||||||
|
}
|
||||||
#rui input[readonly] {
|
#rui input[readonly] {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #444;
|
background: #444;
|
||||||
@@ -1112,6 +1012,7 @@ html.light #rui {
|
|||||||
#barbuf,
|
#barbuf,
|
||||||
#barpos,
|
#barpos,
|
||||||
#u2conf label,
|
#u2conf label,
|
||||||
|
#rui label,
|
||||||
#ops {
|
#ops {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
@@ -1133,6 +1034,11 @@ html.light #rui {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1143,21 +1049,6 @@ html.light {
|
|||||||
background: #eee;
|
background: #eee;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
html.light #tt {
|
|
||||||
background: #fff;
|
|
||||||
border-color: #888 #000 #777 #000;
|
|
||||||
}
|
|
||||||
html.light #tt,
|
|
||||||
html.light #toast {
|
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
html.light #tt code {
|
|
||||||
background: #060;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
html.light #tt em {
|
|
||||||
color: #d38;
|
|
||||||
}
|
|
||||||
html.light #ops,
|
html.light #ops,
|
||||||
html.light .opbox,
|
html.light .opbox,
|
||||||
html.light #srch_form {
|
html.light #srch_form {
|
||||||
@@ -1360,6 +1251,7 @@ html.light #files tr.sel a:hover {
|
|||||||
}
|
}
|
||||||
html.light #treeh {
|
html.light #treeh {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
|
border-color: #ddd;
|
||||||
}
|
}
|
||||||
html.light #tree {
|
html.light #tree {
|
||||||
scrollbar-color: #a70 #ddd;
|
scrollbar-color: #a70 #ddd;
|
||||||
@@ -1374,12 +1266,36 @@ html.light #tree::-webkit-scrollbar {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* bbox */
|
||||||
|
|
||||||
#bbox-overlay {
|
#bbox-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
touch-action: none;
|
touch-action: pinch-zoom;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1413,6 +1329,14 @@ html.light #tree::-webkit-scrollbar {
|
|||||||
max-height: calc(100% - 1.4em);
|
max-height: calc(100% - 1.4em);
|
||||||
margin-bottom: 1.4em;
|
margin-bottom: 1.4em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
transition: transform .23s, left .23s, top .23s, width .23s, height .23s;
|
||||||
|
}
|
||||||
|
.full-image img.nt,
|
||||||
|
.full-image video.nt {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
.full-image.vis img,
|
||||||
|
.full-image.vis video {
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
.full-image video {
|
.full-image video {
|
||||||
@@ -1426,6 +1350,7 @@ html.light #tree::-webkit-scrollbar {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
#bbox-overlay figcaption a {
|
#bbox-overlay figcaption a {
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
@@ -1491,6 +1416,7 @@ html.light #bbox-overlay figcaption a {
|
|||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
font-variant: small-caps;
|
||||||
}
|
}
|
||||||
#bbox-overlay button:focus,
|
#bbox-overlay button:focus,
|
||||||
#bbox-overlay button:hover {
|
#bbox-overlay button:hover {
|
||||||
@@ -1552,3 +1478,331 @@ html.light #bbox-overlay figcaption a {
|
|||||||
0%, 100% {transform: scale(0)}
|
0%, 100% {transform: scale(0)}
|
||||||
50% {transform: scale(1)}
|
50% {transform: scale(1)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* upload.css */
|
||||||
|
|
||||||
|
#op_up2k {
|
||||||
|
padding: 0 1em 1em 1em;
|
||||||
|
}
|
||||||
|
#u2form {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#u2form input {
|
||||||
|
background: #444;
|
||||||
|
border: 0px solid #444;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#u2err.err {
|
||||||
|
color: #f87;
|
||||||
|
padding: .5em;
|
||||||
|
}
|
||||||
|
#u2err.msg {
|
||||||
|
color: #999;
|
||||||
|
padding: .5em;
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
|
#u2btn {
|
||||||
|
color: #eee;
|
||||||
|
background: #555;
|
||||||
|
background: -moz-linear-gradient(top, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
||||||
|
background: -webkit-linear-gradient(top, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
||||||
|
background: linear-gradient(to bottom, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#489', endColorstr='#38788a', GradientType=0);
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1.3em;
|
||||||
|
border: 1px solid #222;
|
||||||
|
border-radius: .4em;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: .5em auto;
|
||||||
|
padding: .8em 0;
|
||||||
|
width: 16em;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: .4em .4em 0 #111;
|
||||||
|
}
|
||||||
|
#op_up2k.srch #u2btn {
|
||||||
|
background: linear-gradient(to bottom, #ca3 0%, #fd8 50%, #fc6 51%, #b92 100%);
|
||||||
|
text-shadow: 1px 1px 1px #fc6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
#u2conf #u2btn {
|
||||||
|
margin: -1.5em 0;
|
||||||
|
padding: .8em 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 12em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#u2conf #u2btn_cw {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
#u2notbtn {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
background: #333;
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
#u2notbtn * {
|
||||||
|
line-height: 1.3em;
|
||||||
|
}
|
||||||
|
#u2tab {
|
||||||
|
margin: 3em auto;
|
||||||
|
width: calc(100% - 2em);
|
||||||
|
max-width: 100em;
|
||||||
|
}
|
||||||
|
#op_up2k.srch #u2tab {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
#u2tab td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-width: 0 0px 1px 0;
|
||||||
|
padding: .1em .3em;
|
||||||
|
}
|
||||||
|
#u2tab td:nth-child(2) {
|
||||||
|
width: 5em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#u2tab td:nth-child(3) {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
#op_up2k.srch td.prog {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
#u2tab tbody tr:hover td {
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
#u2cards {
|
||||||
|
padding: 1em 0 .3em 1em;
|
||||||
|
margin: 1.5em auto -2.5em auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#u2cards.w {
|
||||||
|
width: 45em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
#u2cards a {
|
||||||
|
padding: .2em 1em;
|
||||||
|
border: 1px solid #777;
|
||||||
|
border-width: 0 0 1px 0;
|
||||||
|
background: linear-gradient(to bottom, #333, #222);
|
||||||
|
}
|
||||||
|
#u2cards a:first-child {
|
||||||
|
border-radius: .4em 0 0 0;
|
||||||
|
}
|
||||||
|
#u2cards a:last-child {
|
||||||
|
border-radius: 0 .4em 0 0;
|
||||||
|
}
|
||||||
|
#u2cards a.act {
|
||||||
|
padding-bottom: .5em;
|
||||||
|
border-width: 1px 1px .1em 1px;
|
||||||
|
border-radius: .3em .3em 0 0;
|
||||||
|
margin-left: -1px;
|
||||||
|
background: linear-gradient(to bottom, #464, #333 80%);
|
||||||
|
box-shadow: 0 -.17em .67em #280;
|
||||||
|
border-color: #7c5 #583 #333 #583;
|
||||||
|
position: relative;
|
||||||
|
color: #fd7;
|
||||||
|
}
|
||||||
|
#u2cards span {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#u2conf {
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 30em;
|
||||||
|
}
|
||||||
|
#u2conf.has_btn {
|
||||||
|
width: 48em;
|
||||||
|
}
|
||||||
|
#u2conf * {
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#u2conf .txtbox {
|
||||||
|
width: 3em;
|
||||||
|
color: #fff;
|
||||||
|
background: #444;
|
||||||
|
border: 1px solid #777;
|
||||||
|
font-size: 1.2em;
|
||||||
|
padding: .15em 0;
|
||||||
|
height: 1.05em;
|
||||||
|
}
|
||||||
|
#u2conf .txtbox.err {
|
||||||
|
background: #922;
|
||||||
|
}
|
||||||
|
#u2conf a {
|
||||||
|
color: #fff;
|
||||||
|
background: #c38;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: .1em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
padding: .1em 0;
|
||||||
|
margin: 0 -1px;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1em;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
bottom: -0.08em;
|
||||||
|
}
|
||||||
|
#u2conf input+a {
|
||||||
|
background: #d80;
|
||||||
|
}
|
||||||
|
#u2conf label {
|
||||||
|
font-size: 1.6em;
|
||||||
|
width: 2em;
|
||||||
|
height: 1em;
|
||||||
|
padding: .4em 0;
|
||||||
|
display: block;
|
||||||
|
border-radius: .25em;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"] {
|
||||||
|
position: relative;
|
||||||
|
opacity: .02;
|
||||||
|
top: 2em;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"]+label {
|
||||||
|
position: relative;
|
||||||
|
background: #603;
|
||||||
|
border-bottom: .2em solid #a16;
|
||||||
|
box-shadow: 0 .1em .3em #a00 inset;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"]:checked+label {
|
||||||
|
background: #6a1;
|
||||||
|
border-bottom: .2em solid #efa;
|
||||||
|
box-shadow: 0 .1em .5em #0c0;
|
||||||
|
}
|
||||||
|
#u2conf input[type="checkbox"]+label:hover {
|
||||||
|
box-shadow: 0 .1em .3em #fb0;
|
||||||
|
border-color: #fb0;
|
||||||
|
}
|
||||||
|
#op_up2k.srch #u2conf td:nth-child(1)>*,
|
||||||
|
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
||||||
|
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
||||||
|
background: #777;
|
||||||
|
border-color: #ccc;
|
||||||
|
box-shadow: none;
|
||||||
|
opacity: .2;
|
||||||
|
}
|
||||||
|
#u2foot {
|
||||||
|
color: #fff;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#u2foot .warn {
|
||||||
|
font-size: 1.3em;
|
||||||
|
padding: .5em .8em;
|
||||||
|
margin: 1em -.6em;
|
||||||
|
color: #f74;
|
||||||
|
background: #322;
|
||||||
|
border: 1px solid #633;
|
||||||
|
border-width: .1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#u2foot .warn span {
|
||||||
|
color: #f86;
|
||||||
|
}
|
||||||
|
html.light #u2foot .warn {
|
||||||
|
color: #b00;
|
||||||
|
background: #fca;
|
||||||
|
border-color: #f70;
|
||||||
|
}
|
||||||
|
html.light #u2foot .warn span {
|
||||||
|
color: #930;
|
||||||
|
}
|
||||||
|
#u2foot span {
|
||||||
|
color: #999;
|
||||||
|
font-size: .9em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
#u2footfoot {
|
||||||
|
margin-bottom: -1em;
|
||||||
|
}
|
||||||
|
.prog {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
}
|
||||||
|
#u2tab a>span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
color: #fff;
|
||||||
|
padding-left: .2em;
|
||||||
|
}
|
||||||
|
#u2cleanup {
|
||||||
|
float: right;
|
||||||
|
margin-bottom: -.3em;
|
||||||
|
}
|
||||||
|
.fsearch_explain {
|
||||||
|
padding-left: .7em;
|
||||||
|
font-size: 1.1em;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
html.light #u2btn {
|
||||||
|
box-shadow: .4em .4em 0 #ccc;
|
||||||
|
}
|
||||||
|
html.light #u2cards span {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
html.light #u2cards a {
|
||||||
|
background: linear-gradient(to bottom, #eee, #fff);
|
||||||
|
}
|
||||||
|
html.light #u2cards a.act {
|
||||||
|
color: #037;
|
||||||
|
background: inherit;
|
||||||
|
box-shadow: 0 -.17em .67em #0ad;
|
||||||
|
border-color: #09c #05a #eee #05a;
|
||||||
|
}
|
||||||
|
html.light #u2conf .txtbox {
|
||||||
|
background: #fff;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
html.light #u2conf .txtbox.err {
|
||||||
|
background: #f96;
|
||||||
|
color: #300;
|
||||||
|
}
|
||||||
|
html.light #op_up2k.srch #u2btn {
|
||||||
|
border-color: #a80;
|
||||||
|
}
|
||||||
|
html.light #u2foot {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
html.light #u2tab tbody tr:hover td {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<title>⇆🎉 {{ title }}</title>
|
<title>⇆🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
||||||
{%- if css %}
|
{%- if css %}
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="{{ css }}?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ css }}?_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var acct = "{{ acct }}",
|
var acct = "{{ acct }}",
|
||||||
perms = {{ perms }},
|
perms = {{ perms }},
|
||||||
tag_order_cfg = {{ tag_order }},
|
def_hcols = {{ def_hcols|tojson }},
|
||||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
have_mv = {{ have_mv|tojson }},
|
have_mv = {{ have_mv|tojson }},
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ ebi('tree').innerHTML = (
|
|||||||
' <a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: A">+</a>\n' +
|
' <a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: A">+</a>\n' +
|
||||||
' <a href="#" class="btn" step="-2" id="twig" tt="Hotkey: D">–</a>\n' +
|
' <a href="#" class="btn" step="-2" id="twig" tt="Hotkey: D">–</a>\n' +
|
||||||
' <a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>\n' +
|
' <a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>\n' +
|
||||||
|
' <a href="#" class="btn" id="visdir" tt="scroll to selected folder">v</a>\n' +
|
||||||
'</div>\n' +
|
'</div>\n' +
|
||||||
'<ul id="treeul"></ul>\n' +
|
'<ul id="treeul"></ul>\n' +
|
||||||
'<div id="thx_ff"> </div>'
|
'<div id="thx_ff"> </div>'
|
||||||
@@ -921,14 +922,16 @@ function playpause(e) {
|
|||||||
var mpui = (function () {
|
var mpui = (function () {
|
||||||
var r = {},
|
var r = {},
|
||||||
nth = 0,
|
nth = 0,
|
||||||
timeout = null,
|
|
||||||
preloaded = null;
|
preloaded = null;
|
||||||
|
|
||||||
r.progress_updater = function () {
|
r.progress_updater = function () {
|
||||||
clearTimeout(timeout);
|
timer.add(updater_impl, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
function updater_impl() {
|
||||||
if (!mp.au) {
|
if (!mp.au) {
|
||||||
widget.paused(true);
|
widget.paused(true);
|
||||||
|
timer.rm(updater_impl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,9 +973,9 @@ var mpui = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mp.au.paused)
|
if (mp.au.paused)
|
||||||
timeout = setTimeout(r.progress_updater, 100);
|
timer.rm(updater_impl);
|
||||||
};
|
}
|
||||||
r.progress_updater();
|
r.progress_updater();
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
@@ -1283,11 +1286,12 @@ function play(tid, is_ev, seek, call_depth) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (call_depth !== undefined)
|
if (call_depth !== undefined)
|
||||||
return alert('failed to load ogv.js');
|
return toast.err(0, 'failed to load ogv.js:\ncannot play ogg/opus in this browser\n(try a non-apple device)');
|
||||||
|
|
||||||
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
toast.inf(0, '<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
||||||
|
|
||||||
import_js('/.cpr/deps/ogv.js', function () {
|
import_js('/.cpr/deps/ogv.js', function () {
|
||||||
|
toast.hide();
|
||||||
play(tid, false, seek, 1);
|
play(tid, false, seek, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1383,7 +1387,7 @@ function evau_error(e) {
|
|||||||
|
|
||||||
err += '\n\nFile: «' + uricom_dec(eplaya.src.split('/').slice(-1)[0])[0] + '»';
|
err += '\n\nFile: «' + uricom_dec(eplaya.src.split('/').slice(-1)[0])[0] + '»';
|
||||||
|
|
||||||
alert(err);
|
toast.warn(15, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1460,6 +1464,182 @@ function play_linked() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function sortfiles(nodes) {
|
||||||
|
var sopts = jread('fsort', [["href", 1, ""]]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var is_srch = false;
|
||||||
|
if (nodes[0]['rp']) {
|
||||||
|
is_srch = true;
|
||||||
|
for (var b = 0, bb = nodes.length; b < bb; b++)
|
||||||
|
nodes[b].ext = nodes[b].rp.split('.').pop();
|
||||||
|
for (var b = 0; b < sopts.length; b++)
|
||||||
|
if (sopts[b][0] == 'href')
|
||||||
|
sopts[b][0] = 'rp';
|
||||||
|
}
|
||||||
|
for (var a = sopts.length - 1; a >= 0; a--) {
|
||||||
|
var name = sopts[a][0], rev = sopts[a][1], typ = sopts[a][2];
|
||||||
|
if (!name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (name == 'ts')
|
||||||
|
typ = 'int';
|
||||||
|
|
||||||
|
if (name.indexOf('tags/') === 0) {
|
||||||
|
name = name.slice(5);
|
||||||
|
for (var b = 0, bb = nodes.length; b < bb; b++)
|
||||||
|
nodes[b]._sv = nodes[b].tags[name];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (var b = 0, bb = nodes.length; b < bb; b++) {
|
||||||
|
var v = nodes[b][name];
|
||||||
|
|
||||||
|
if ((v + '').indexOf('<a ') === 0)
|
||||||
|
v = v.split('>')[1];
|
||||||
|
else if (name == "href" && v) {
|
||||||
|
if (v.slice(-1) == '/')
|
||||||
|
v = '\t' + v;
|
||||||
|
|
||||||
|
v = uricom_dec(v)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes[b]._sv = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var onodes = nodes.map(function (x) { return x; });
|
||||||
|
nodes.sort(function (n1, n2) {
|
||||||
|
var v1 = n1._sv,
|
||||||
|
v2 = n2._sv;
|
||||||
|
|
||||||
|
if (v1 === undefined) {
|
||||||
|
if (v2 === undefined) {
|
||||||
|
return onodes.indexOf(n1) - onodes.indexOf(n2);
|
||||||
|
}
|
||||||
|
return -1 * rev;
|
||||||
|
}
|
||||||
|
if (v2 === undefined) return 1 * rev;
|
||||||
|
|
||||||
|
var ret = rev * (typ == 'int' ? (v1 - v2) : (v1.localeCompare(v2)));
|
||||||
|
if (ret === 0)
|
||||||
|
ret = onodes.indexOf(n1) - onodes.indexOf(n2);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (var b = 0, bb = nodes.length; b < bb; b++) {
|
||||||
|
delete nodes[b]._sv;
|
||||||
|
if (is_srch)
|
||||||
|
delete nodes[b].ext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log("failed to apply sort config: " + ex);
|
||||||
|
console.log("resetting fsort " + sread('fsort'))
|
||||||
|
localStorage.removeItem('fsort');
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function fmt_ren(re, md, fmt) {
|
||||||
|
var ptr = 0;
|
||||||
|
function dive(stop_ch) {
|
||||||
|
var ret = '', ng = 0;
|
||||||
|
while (ptr < fmt.length) {
|
||||||
|
var dbg = fmt.slice(ptr),
|
||||||
|
ch = fmt[ptr++];
|
||||||
|
|
||||||
|
if (ch == '\\') {
|
||||||
|
ret += fmt[ptr++];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == ')' || ch == ']' || ch == stop_ch)
|
||||||
|
return [ng, ret];
|
||||||
|
|
||||||
|
if (ch == '[') {
|
||||||
|
var r2 = dive();
|
||||||
|
if (r2[0] == 0)
|
||||||
|
ret += r2[1];
|
||||||
|
}
|
||||||
|
else if (ch == '(') {
|
||||||
|
var end = fmt.indexOf(')', ptr);
|
||||||
|
if (end < 0)
|
||||||
|
throw 'the ( was never closed: ' + fmt.slice(0, ptr);
|
||||||
|
|
||||||
|
var arg = fmt.slice(ptr, end), v = null;
|
||||||
|
ptr = end + 1;
|
||||||
|
|
||||||
|
if (arg != parseInt(arg))
|
||||||
|
v = md[arg];
|
||||||
|
else {
|
||||||
|
arg = parseInt(arg);
|
||||||
|
if (arg >= re.length)
|
||||||
|
throw 'matching group ' + arg + ' exceeds ' + (re.length - 0);
|
||||||
|
|
||||||
|
v = re[arg];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v !== null && v !== undefined)
|
||||||
|
ret += v;
|
||||||
|
else
|
||||||
|
ng++;
|
||||||
|
}
|
||||||
|
else if (ch == '$') {
|
||||||
|
ch = fmt[ptr++];
|
||||||
|
var end = fmt.indexOf('(', ptr);
|
||||||
|
if (end < 0)
|
||||||
|
throw 'no function name after the $ here: ' + fmt.slice(0, ptr);
|
||||||
|
|
||||||
|
var fun = fmt.slice(ptr - 1, end);
|
||||||
|
ptr = end + 1;
|
||||||
|
|
||||||
|
if (fun == "lpad") {
|
||||||
|
var str = dive(',')[1];
|
||||||
|
var len = dive(',')[1];
|
||||||
|
var chr = dive()[1];
|
||||||
|
if (!len || !chr)
|
||||||
|
throw 'invalid arguments to ' + fun;
|
||||||
|
|
||||||
|
if (!str.length)
|
||||||
|
ng += 1;
|
||||||
|
|
||||||
|
while (str.length < len)
|
||||||
|
str = chr + str;
|
||||||
|
|
||||||
|
ret += str;
|
||||||
|
}
|
||||||
|
else if (fun == "rpad") {
|
||||||
|
var str = dive(',')[1];
|
||||||
|
var len = dive(',')[1];
|
||||||
|
var chr = dive()[1];
|
||||||
|
if (!len || !chr)
|
||||||
|
throw 'invalid arguments to ' + fun;
|
||||||
|
|
||||||
|
if (!str.length)
|
||||||
|
ng += 1;
|
||||||
|
|
||||||
|
while (str.length < len)
|
||||||
|
str += chr;
|
||||||
|
|
||||||
|
ret += str;
|
||||||
|
}
|
||||||
|
else throw 'function not implemented: "' + fun + '"';
|
||||||
|
}
|
||||||
|
else ret += ch;
|
||||||
|
}
|
||||||
|
return [ng, ret];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return [true, dive()[1]];
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return [false, ex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var fileman = (function () {
|
var fileman = (function () {
|
||||||
var bren = ebi('fren'),
|
var bren = ebi('fren'),
|
||||||
bdel = ebi('fdel'),
|
bdel = ebi('fdel'),
|
||||||
@@ -1468,14 +1648,17 @@ var fileman = (function () {
|
|||||||
r = {};
|
r = {};
|
||||||
|
|
||||||
r.clip = null;
|
r.clip = null;
|
||||||
r.bus = new BroadcastChannel("fileman_bus");
|
try {
|
||||||
|
r.bus = new BroadcastChannel("fileman_bus");
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
|
||||||
r.render = function () {
|
r.render = function () {
|
||||||
if (r.clip === null)
|
if (r.clip === null)
|
||||||
r.clip = jread('fman_clip', []);
|
r.clip = jread('fman_clip', []);
|
||||||
|
|
||||||
var nsel = msel.getsel().length;
|
var nsel = msel.getsel().length;
|
||||||
clmod(bren, 'en', nsel == 1);
|
clmod(bren, 'en', nsel);
|
||||||
clmod(bdel, 'en', nsel);
|
clmod(bdel, 'en', nsel);
|
||||||
clmod(bcut, 'en', nsel);
|
clmod(bcut, 'en', nsel);
|
||||||
clmod(bpst, 'en', r.clip && r.clip.length);
|
clmod(bpst, 'en', r.clip && r.clip.length);
|
||||||
@@ -1493,16 +1676,47 @@ var fileman = (function () {
|
|||||||
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
|
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
|
||||||
|
|
||||||
var sel = msel.getsel();
|
var sel = msel.getsel();
|
||||||
if (sel.length !== 1)
|
if (!sel.length)
|
||||||
return toast.err(3, 'select exactly 1 item to rename');
|
return toast.err(3, 'select at least one item to rename');
|
||||||
|
|
||||||
var src = sel[0].vp;
|
var f = [],
|
||||||
if (src.endsWith('/'))
|
base = vsplit(sel[0].vp)[0],
|
||||||
src = src.slice(0, -1);
|
mkeys;
|
||||||
|
|
||||||
var vsp = vsplit(src),
|
for (var a = 0; a < sel.length; a++) {
|
||||||
base = vsp[0],
|
var vp = sel[a].vp;
|
||||||
ofn = uricom_dec(vsp[1])[0];
|
if (vp.endsWith('/'))
|
||||||
|
vp = vp.slice(0, -1);
|
||||||
|
|
||||||
|
var vsp = vsplit(vp);
|
||||||
|
if (base != vsp[0])
|
||||||
|
return toast.err(0, 'bug:\n' + base + '\n' + vsp[0]);
|
||||||
|
|
||||||
|
var vars = ft2dict(ebi(sel[a].id).closest('tr'));
|
||||||
|
mkeys = vars[1].concat(vars[2]);
|
||||||
|
|
||||||
|
var md = vars[0];
|
||||||
|
for (var k in md) {
|
||||||
|
if (!md.hasOwnProperty(k))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
md[k.toLowerCase()] = md[k];
|
||||||
|
k = k.toLowerCase();
|
||||||
|
|
||||||
|
if (k.startsWith('.'))
|
||||||
|
md[k.slice(1)] = md[k];
|
||||||
|
}
|
||||||
|
md.t = md.ext;
|
||||||
|
md.date = md.ts;
|
||||||
|
md.size = md.sz;
|
||||||
|
|
||||||
|
f.push({
|
||||||
|
"src": vp,
|
||||||
|
"ofn": uricom_dec(vsp[1])[0],
|
||||||
|
"md": vars[0],
|
||||||
|
"ok": true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var rui = ebi('rui');
|
var rui = ebi('rui');
|
||||||
if (!rui) {
|
if (!rui) {
|
||||||
@@ -1510,62 +1724,222 @@ var fileman = (function () {
|
|||||||
rui.setAttribute('id', 'rui');
|
rui.setAttribute('id', 'rui');
|
||||||
document.body.appendChild(rui);
|
document.body.appendChild(rui);
|
||||||
}
|
}
|
||||||
var html = [
|
|
||||||
'<h1>rename file</h1>',
|
var html = sel.length > 1 ? ['<div>'] : [
|
||||||
'<div><table>',
|
|
||||||
'<tr><td>old:</td><td><input type="text" id="rn_old" readonly /></td></tr>',
|
|
||||||
'<tr><td>new:</td><td><input type="text" id="rn_new" /></td></tr>',
|
|
||||||
'</table></div>',
|
|
||||||
'<div>',
|
'<div>',
|
||||||
'<button id="rn_dec">url-decode</button>',
|
'<button class="rn_dec" n="0" tt="may fix some cases of broken filenames">url-decode</button>',
|
||||||
'|',
|
'//',
|
||||||
'<button id="rn_reset">↺ reset</button>',
|
'<button class="rn_reset" n="0" tt="reset modified filenames back to the original ones">↺ reset</button>'
|
||||||
'<button id="rn_cancel">❌ cancel</button>',
|
|
||||||
'<button id="rn_apply">✅ apply rename</button>',
|
|
||||||
'</div>',
|
|
||||||
'<div><table>'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var vars = ft2dict(ebi(sel[0].id).closest('tr')),
|
html = html.concat([
|
||||||
keys = vars[1].concat(vars[2]);
|
'<button id="rn_cancel" tt="abort and close this window">❌ cancel</button>',
|
||||||
|
'<button id="rn_apply">✅ apply rename</button>',
|
||||||
vars = vars[0];
|
'<a id="rn_adv" class="tgl btn" href="#" tt="batch / metadata / pattern renaming">advanced</a>',
|
||||||
for (var a = 0; a < keys.length; a++)
|
'<a id="rn_case" class="tgl btn" href="#" tt="case-sensitive regex">case</a>',
|
||||||
html.push('<tr><td>' + esc(keys[a]) + '</td><td><input type="text" readonly value="' + esc(vars[keys[a]]) + '" /></td></tr>');
|
'</div>',
|
||||||
|
'<div id="rn_vadv"><table>',
|
||||||
|
'<tr><td>regex</td><td><input type="text" id="rn_re" tt="regex search pattern to apply to original filenames; capturing groups can be referenced in the format field below like <code>(1)</code> and <code>(2)</code> and so on" /></td></tr>',
|
||||||
|
'<tr><td>format</td><td><input type="text" id="rn_fmt" tt="inspired by foobar2000:$N<code>(title)</code> is replaced by song title,$N<code>[(artist) - ](title)</code> skips the first part if artist is blank$N<code>$lpad((tn),2,0)</code> pads tracknumber to 2 digits" /></td></tr>',
|
||||||
|
'<tr><td>preset</td><td><select id="rn_pre"></select>',
|
||||||
|
'<button id="rn_pdel">❌ delete</button>',
|
||||||
|
'<button id="rn_pnew">💾 save as</button>',
|
||||||
|
'</td></tr>',
|
||||||
|
'</table></div>'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (sel.length == 1)
|
||||||
|
html.push(
|
||||||
|
'<div><table id="rn_f">\n' +
|
||||||
|
'<tr><td>old:</td><td><input type="text" id="rn_old" n="0" readonly /></td></tr>\n' +
|
||||||
|
'<tr><td>new:</td><td><input type="text" id="rn_new" n="0" /></td></tr>');
|
||||||
|
else {
|
||||||
|
html.push(
|
||||||
|
'<div><table id="rn_f" class="m">' +
|
||||||
|
'<tr><td></td><td>new name</td><td>old name</td></tr>');
|
||||||
|
for (var a = 0; a < f.length; a++)
|
||||||
|
html.push(
|
||||||
|
'<tr><td>' +
|
||||||
|
'<button class="rn_dec" n="' + a + '">decode</button>',
|
||||||
|
'<button class="rn_reset" n="' + a + '">↺ reset</button></td>',
|
||||||
|
'<td><input type="text" id="rn_new" n="' + a + '" /></td>' +
|
||||||
|
'<td><input type="text" id="rn_old" n="' + a + '" readonly /></td></tr>');
|
||||||
|
}
|
||||||
html.push('</table></div>');
|
html.push('</table></div>');
|
||||||
rui.innerHTML = html.join('\n');
|
|
||||||
var iold = ebi('rn_old'),
|
|
||||||
inew = ebi('rn_new');
|
|
||||||
|
|
||||||
function rn_reset() {
|
if (sel.length == 1) {
|
||||||
inew.value = iold.value;
|
html.push('<div><p style="margin:.6em 0">tags for the selected file (read-only, just for reference):</p><table>');
|
||||||
inew.focus();
|
for (var a = 0; a < mkeys.length; a++)
|
||||||
inew.setSelectionRange(0, inew.value.lastIndexOf('.'), "forward");
|
html.push('<tr><td>' + esc(mkeys[a]) + '</td><td><input type="text" readonly value="' + esc(f[0].md[mkeys[a]]) + '" /></td></tr>');
|
||||||
|
|
||||||
|
html.push('</table></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
rui.innerHTML = html.join('\n');
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
var k = '[n="' + a + '"]';
|
||||||
|
f[a].iold = QS('#rn_old' + k);
|
||||||
|
f[a].inew = QS('#rn_new' + k);
|
||||||
|
f[a].inew.value = f[a].iold.value = f[a].ofn;
|
||||||
|
|
||||||
|
(function (a) {
|
||||||
|
f[a].inew.onkeydown = function (e) {
|
||||||
|
rn_ok(a, true);
|
||||||
|
|
||||||
|
if (e.key == 'Escape')
|
||||||
|
return rn_cancel();
|
||||||
|
|
||||||
|
if (e.key == 'Enter')
|
||||||
|
return rn_apply();
|
||||||
|
};
|
||||||
|
QS('.rn_dec' + k).onclick = function () {
|
||||||
|
f[a].inew.value = uricom_dec(f[a].inew.value)[0];
|
||||||
|
};
|
||||||
|
QS('.rn_reset' + k).onclick = function () {
|
||||||
|
rn_reset(a);
|
||||||
|
};
|
||||||
|
})(a);
|
||||||
|
}
|
||||||
|
rn_reset(0);
|
||||||
|
tt.att(rui);
|
||||||
|
|
||||||
|
var adv = bcfg_get('rn_adv', false),
|
||||||
|
cs = bcfg_get('rn_case', false);
|
||||||
|
|
||||||
|
function sadv() {
|
||||||
|
ebi('rn_vadv').style.display = ebi('rn_case').style.display = adv ? '' : 'none';
|
||||||
|
}
|
||||||
|
sadv();
|
||||||
|
|
||||||
|
function rn_ok(n, ok) {
|
||||||
|
f[n].ok = ok;
|
||||||
|
clmod(f[n].inew.closest('tr'), 'err', !ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rn_reset(n) {
|
||||||
|
f[n].inew.value = f[n].iold.value = f[n].ofn;
|
||||||
|
f[n].inew.focus();
|
||||||
|
f[n].inew.setSelectionRange(0, f[n].inew.value.lastIndexOf('.'), "forward");
|
||||||
}
|
}
|
||||||
function rn_cancel() {
|
function rn_cancel() {
|
||||||
rui.parentNode.removeChild(rui);
|
rui.parentNode.removeChild(rui);
|
||||||
}
|
}
|
||||||
|
|
||||||
inew.onkeydown = function (e) {
|
ebi('rn_cancel').onclick = rn_cancel;
|
||||||
|
ebi('rn_apply').onclick = rn_apply;
|
||||||
|
ebi('rn_adv').onclick = function () {
|
||||||
|
adv = !adv;
|
||||||
|
bcfg_set('rn_adv', adv);
|
||||||
|
sadv();
|
||||||
|
};
|
||||||
|
ebi('rn_case').onclick = function () {
|
||||||
|
cs = !cs;
|
||||||
|
bcfg_set('rn_case', cs);
|
||||||
|
};
|
||||||
|
|
||||||
|
var ire = ebi('rn_re'),
|
||||||
|
ifmt = ebi('rn_fmt'),
|
||||||
|
ipre = ebi('rn_pre'),
|
||||||
|
idel = ebi('rn_pdel'),
|
||||||
|
inew = ebi('rn_pnew'),
|
||||||
|
defp = '$lpad((tn),2,0). [(artist) - ](title).(ext)';
|
||||||
|
|
||||||
|
var presets = {};
|
||||||
|
presets[defp] = ['', defp];
|
||||||
|
presets = jread("rn_pre", presets);
|
||||||
|
|
||||||
|
function spresets() {
|
||||||
|
var keys = Object.keys(presets), o;
|
||||||
|
keys.sort();
|
||||||
|
ipre.innerHTML = '<option value=""></option>';
|
||||||
|
for (var a = 0; a < keys.length; a++) {
|
||||||
|
o = mknod('option');
|
||||||
|
o.setAttribute('value', keys[a]);
|
||||||
|
o.textContent = keys[a];
|
||||||
|
ipre.appendChild(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inew.onclick = function () {
|
||||||
|
modal.prompt('provide a name for your new preset', ifmt.value, function (name) {
|
||||||
|
if (!name)
|
||||||
|
return toast.warn(3, 'aborted');
|
||||||
|
|
||||||
|
presets[name] = [ire.value, ifmt.value];
|
||||||
|
jwrite('rn_pre', presets);
|
||||||
|
spresets();
|
||||||
|
ipre.value = name;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
idel.onclick = function () {
|
||||||
|
delete presets[ipre.value];
|
||||||
|
jwrite('rn_pre', presets);
|
||||||
|
spresets();
|
||||||
|
};
|
||||||
|
ipre.oninput = function () {
|
||||||
|
var cfg = presets[ipre.value];
|
||||||
|
if (cfg) {
|
||||||
|
ire.value = cfg[0];
|
||||||
|
ifmt.value = cfg[1];
|
||||||
|
}
|
||||||
|
ifmt.oninput();
|
||||||
|
};
|
||||||
|
spresets();
|
||||||
|
|
||||||
|
ire.onkeydown = ifmt.onkeydown = function (e) {
|
||||||
if (e.key == 'Escape')
|
if (e.key == 'Escape')
|
||||||
return rn_cancel();
|
return rn_cancel();
|
||||||
|
|
||||||
if (e.key == 'Enter')
|
if (e.key == 'Enter')
|
||||||
return rn_apply();
|
return rn_apply();
|
||||||
};
|
};
|
||||||
ebi('rn_cancel').onclick = rn_cancel;
|
|
||||||
ebi('rn_reset').onclick = rn_reset;
|
ire.oninput = ifmt.oninput = function (e) {
|
||||||
ebi('rn_apply').onclick = rn_apply;
|
var ptn = ire.value,
|
||||||
ebi('rn_dec').onclick = function () {
|
fmt = ifmt.value,
|
||||||
inew.value = uricom_dec(inew.value)[0];
|
re = null;
|
||||||
|
|
||||||
|
if (!fmt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ptn)
|
||||||
|
re = new RegExp(ptn, cs ? 'i' : '');
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return toast.err(5, 'invalid regex:\n' + ex);
|
||||||
|
}
|
||||||
|
toast.hide();
|
||||||
|
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
var m = re ? re.exec(f[a].ofn) : null,
|
||||||
|
ok, txt = '';
|
||||||
|
|
||||||
|
if (re && !m) {
|
||||||
|
txt = 'regex did not match';
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var ret = fmt_ren(m, f[a].md, fmt);
|
||||||
|
ok = ret[0];
|
||||||
|
txt = ret[1];
|
||||||
|
}
|
||||||
|
rn_ok(a, ok);
|
||||||
|
f[a].inew.value = (ok ? '' : 'ERROR: ') + txt;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
iold.value = ofn;
|
|
||||||
rn_reset();
|
|
||||||
|
|
||||||
function rn_apply() {
|
function rn_apply() {
|
||||||
var dst = base + uricom_enc(inew.value, false);
|
while (f.length && (!f[0].ok || f[0].ofn == f[0].inew.value))
|
||||||
|
f.shift();
|
||||||
|
|
||||||
|
if (!f.length) {
|
||||||
|
toast.ok(2, 'rename OK');
|
||||||
|
treectl.goto(get_evpath());
|
||||||
|
return rn_cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.inf(0, 'renaming ' + f.length + ' items\n\n' + f[0].ofn);
|
||||||
|
var dst = base + uricom_enc(f[0].inew.value, false);
|
||||||
|
|
||||||
function rename_cb() {
|
function rename_cb() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
@@ -1576,15 +1950,16 @@ var fileman = (function () {
|
|||||||
toast.err(9, 'rename failed:\n' + msg);
|
toast.err(9, 'rename failed:\n' + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.ok(2, 'rename OK');
|
|
||||||
treectl.goto(get_evpath());
|
f.shift().inew.value = '( OK )';
|
||||||
rn_cancel();
|
return rn_apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', src + '?move=' + dst, true);
|
xhr.open('GET', f[0].src + '?move=' + dst, true);
|
||||||
xhr.onreadystatechange = rename_cb;
|
xhr.onreadystatechange = rename_cb;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
r.delete = function (e) {
|
r.delete = function (e) {
|
||||||
@@ -1601,12 +1976,6 @@ var fileman = (function () {
|
|||||||
if (!sel.length)
|
if (!sel.length)
|
||||||
return toast.err(3, 'select at least 1 item to delete');
|
return toast.err(3, 'select at least 1 item to delete');
|
||||||
|
|
||||||
if (!confirm('===== DANGER =====\nDELETE these ' + vps.length + ' items?\n\n' + vps.join('\n')))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!confirm('Last chance! Delete?'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
function deleter() {
|
function deleter() {
|
||||||
var xhr = new XMLHttpRequest(),
|
var xhr = new XMLHttpRequest(),
|
||||||
vp = vps.shift();
|
vp = vps.shift();
|
||||||
@@ -1633,7 +2002,10 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
deleter();
|
deleter();
|
||||||
}
|
}
|
||||||
deleter();
|
|
||||||
|
modal.confirm('===== DANGER =====\nDELETE these ' + vps.length + ' items?\n\n' + uricom_adec(vps).join('\n'), function () {
|
||||||
|
modal.confirm('Last chance! Delete?', deleter, null);
|
||||||
|
}, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
r.cut = function (e) {
|
r.cut = function (e) {
|
||||||
@@ -1647,16 +2019,21 @@ var fileman = (function () {
|
|||||||
if (!sel.length)
|
if (!sel.length)
|
||||||
return toast.err(3, 'select at least 1 item to cut');
|
return toast.err(3, 'select at least 1 item to cut');
|
||||||
|
|
||||||
|
var els = [];
|
||||||
for (var a = 0; a < sel.length; a++) {
|
for (var a = 0; a < sel.length; a++) {
|
||||||
vps.push(sel[a].vp);
|
vps.push(sel[a].vp);
|
||||||
var cl = ebi(sel[a].id).closest('tr').classList,
|
if (sel.length < 100) {
|
||||||
inv = cl.contains('c1');
|
els.push(ebi(sel[a].id).closest('tr'));
|
||||||
|
clmod(els[a], 'fcut');
|
||||||
cl.remove(inv ? 'c1' : 'c2');
|
}
|
||||||
cl.add(inv ? 'c2' : 'c1');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.inf(1, 'cut ' + sel.length + ' items');
|
setTimeout(function () {
|
||||||
|
for (var a = 0; a < els.length; a++)
|
||||||
|
clmod(els[a], 'fcut', 1);
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
toast.inf(1.5, 'cut ' + sel.length + ' items');
|
||||||
jwrite('fman_clip', vps);
|
jwrite('fman_clip', vps);
|
||||||
r.tx(1);
|
r.tx(1);
|
||||||
};
|
};
|
||||||
@@ -1691,14 +2068,11 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (exists.length)
|
if (exists.length)
|
||||||
alert('these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + uricom_adec(exists).join('\n'));
|
toast.warn(30, 'these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + uricom_adec(exists).join('\n'));
|
||||||
|
|
||||||
if (!req.length)
|
if (!req.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!confirm('paste these ' + req.length + ' items here?\n\n' + uricom_adec(req).join('\n')))
|
|
||||||
return;
|
|
||||||
|
|
||||||
function paster() {
|
function paster() {
|
||||||
var xhr = new XMLHttpRequest(),
|
var xhr = new XMLHttpRequest(),
|
||||||
vp = req.shift();
|
vp = req.shift();
|
||||||
@@ -1728,20 +2102,29 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
paster();
|
paster();
|
||||||
}
|
}
|
||||||
paster();
|
|
||||||
|
|
||||||
jwrite('fman_clip', []);
|
modal.confirm('paste these ' + req.length + ' items here?\n\n' + uricom_adec(req).join('\n'), function () {
|
||||||
|
paster();
|
||||||
|
jwrite('fman_clip', []);
|
||||||
|
}, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
r.bus.onmessage = function (e) {
|
function onmsg(msg) {
|
||||||
r.clip = null;
|
r.clip = null;
|
||||||
r.render();
|
r.render();
|
||||||
var me = get_evpath();
|
if (msg == get_evpath())
|
||||||
if (e && e.data == me)
|
treectl.goto(msg);
|
||||||
treectl.goto(e.data);
|
}
|
||||||
};
|
|
||||||
|
if (r.bus)
|
||||||
|
r.bus.onmessage = function (e) {
|
||||||
|
onmsg(e ? e.data : 1)
|
||||||
|
};
|
||||||
|
|
||||||
r.tx = function (msg) {
|
r.tx = function (msg) {
|
||||||
|
if (!r.bus)
|
||||||
|
return onmsg(msg);
|
||||||
|
|
||||||
r.bus.postMessage(msg);
|
r.bus.postMessage(msg);
|
||||||
r.bus.onmessage();
|
r.bus.onmessage();
|
||||||
};
|
};
|
||||||
@@ -1865,7 +2248,7 @@ var thegrid = (function () {
|
|||||||
var oth = ebi(this.getAttribute('ref')),
|
var oth = ebi(this.getAttribute('ref')),
|
||||||
href = this.getAttribute('href'),
|
href = this.getAttribute('href'),
|
||||||
aplay = ebi('a' + oth.getAttribute('id')),
|
aplay = ebi('a' + oth.getAttribute('id')),
|
||||||
is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
|
is_img = /\.(gif|jpe?g|png|webp|webm|mp4)(\?|$)/i.test(href),
|
||||||
in_tree = null,
|
in_tree = null,
|
||||||
have_sel = QS('#files tr.sel'),
|
have_sel = QS('#files tr.sel'),
|
||||||
td = oth.closest('td').nextSibling,
|
td = oth.closest('td').nextSibling,
|
||||||
@@ -2013,7 +2396,8 @@ var thegrid = (function () {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
function tree_scrollto() {
|
function tree_scrollto(e) {
|
||||||
|
ev(e);
|
||||||
var act = QS('#treeul a.hl'),
|
var act = QS('#treeul a.hl'),
|
||||||
ul = act ? act.offsetParent : null;
|
ul = act ? act.offsetParent : null;
|
||||||
|
|
||||||
@@ -2473,7 +2857,7 @@ var treectl = (function () {
|
|||||||
var treectl = {
|
var treectl = {
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"ls_cb": null,
|
"ls_cb": null,
|
||||||
"dir_cb": null
|
"dir_cb": tree_scrollto
|
||||||
},
|
},
|
||||||
entreed = false,
|
entreed = false,
|
||||||
fixedpos = false,
|
fixedpos = false,
|
||||||
@@ -2526,7 +2910,7 @@ var treectl = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onscroll() {
|
function onscroll() {
|
||||||
if (!entreed || treectl.hidden)
|
if (!entreed || treectl.hidden || document.visibilityState == 'hidden')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var tree = ebi('tree'),
|
var tree = ebi('tree'),
|
||||||
@@ -2562,12 +2946,7 @@ var treectl = (function () {
|
|||||||
tree.style.height = treeh < 10 ? '' : treeh + 'px';
|
tree.style.height = treeh < 10 ? '' : treeh + 'px';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
timer.add(onscroll, true);
|
||||||
function periodic() {
|
|
||||||
onscroll();
|
|
||||||
setTimeout(periodic, document.visibilityState ? 100 : 5000);
|
|
||||||
}
|
|
||||||
periodic();
|
|
||||||
|
|
||||||
function onresize(e) {
|
function onresize(e) {
|
||||||
if (!entreed || treectl.hidden)
|
if (!entreed || treectl.hidden)
|
||||||
@@ -2782,7 +3161,13 @@ var treectl = (function () {
|
|||||||
}
|
}
|
||||||
html.push('</tbody>');
|
html.push('</tbody>');
|
||||||
html = html.join('\n');
|
html = html.join('\n');
|
||||||
ebi('files').innerHTML = html;
|
try {
|
||||||
|
ebi('files').innerHTML = html;
|
||||||
|
}
|
||||||
|
catch (ex) { //ie9
|
||||||
|
window.location.href = this.top;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.hpush)
|
if (this.hpush)
|
||||||
hist_push(this.top);
|
hist_push(this.top);
|
||||||
@@ -2866,6 +3251,7 @@ var treectl = (function () {
|
|||||||
|
|
||||||
ebi('entree').onclick = treectl.entree;
|
ebi('entree').onclick = treectl.entree;
|
||||||
ebi('detree').onclick = treectl.detree;
|
ebi('detree').onclick = treectl.detree;
|
||||||
|
ebi('visdir').onclick = tree_scrollto;
|
||||||
ebi('dotfiles').onclick = tdots;
|
ebi('dotfiles').onclick = tdots;
|
||||||
ebi('dyntree').onclick = dyntree;
|
ebi('dyntree').onclick = dyntree;
|
||||||
ebi('twig').onclick = scaletree;
|
ebi('twig').onclick = scaletree;
|
||||||
@@ -3029,6 +3415,8 @@ var filecols = (function () {
|
|||||||
"q": "quality / bitrate",
|
"q": "quality / bitrate",
|
||||||
"Ac": "audio codec",
|
"Ac": "audio codec",
|
||||||
"Vc": "video codec",
|
"Vc": "video codec",
|
||||||
|
"Ahash": "audio checksum",
|
||||||
|
"Vhash": "video checksum",
|
||||||
"Res": "resolution",
|
"Res": "resolution",
|
||||||
"T": "filetype",
|
"T": "filetype",
|
||||||
"aq": "audio quality / bitrate",
|
"aq": "audio quality / bitrate",
|
||||||
@@ -3040,6 +3428,21 @@ var filecols = (function () {
|
|||||||
"hz": "sample rate"
|
"hz": "sample rate"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (JSON.stringify(def_hcols) != sread('hfilecols')) {
|
||||||
|
console.log("applying default hidden-cols");
|
||||||
|
jwrite('hfilecols', def_hcols);
|
||||||
|
for (var a = 0; a < def_hcols.length; a++) {
|
||||||
|
var t = def_hcols[a];
|
||||||
|
t = t.slice(0, 1).toUpperCase() + t.slice(1);
|
||||||
|
if (t.startsWith("."))
|
||||||
|
t = t.slice(1);
|
||||||
|
|
||||||
|
if (hidden.indexOf(t) == -1)
|
||||||
|
hidden.push(t);
|
||||||
|
}
|
||||||
|
jwrite("filecols", hidden);
|
||||||
|
}
|
||||||
|
|
||||||
var add_btns = function () {
|
var add_btns = function () {
|
||||||
var ths = QSA('#files th>span');
|
var ths = QSA('#files th>span');
|
||||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||||
@@ -3382,6 +3785,16 @@ var msel = (function () {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
r.sel = [];
|
r.sel = [];
|
||||||
|
if (r.all && r.all.length) {
|
||||||
|
for (var a = 0; a < r.all.length; a++) {
|
||||||
|
var ao = r.all[a];
|
||||||
|
ao.sel = clgot(ebi(ao.id).closest('tr'), 'sel');
|
||||||
|
if (ao.sel)
|
||||||
|
r.sel.push(ao);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
r.all = [];
|
r.all = [];
|
||||||
var links = QSA('#files tbody td:nth-child(2) a:last-child'),
|
var links = QSA('#files tbody td:nth-child(2) a:last-child'),
|
||||||
vbase = get_evpath();
|
vbase = get_evpath();
|
||||||
@@ -3391,7 +3804,7 @@ var msel = (function () {
|
|||||||
item = {};
|
item = {};
|
||||||
|
|
||||||
item.id = links[a].getAttribute('id');
|
item.id = links[a].getAttribute('id');
|
||||||
item.sel = links[a].closest('tr').classList.contains('sel');
|
item.sel = clgot(links[a].closest('tr'), 'sel');
|
||||||
item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
|
item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
|
||||||
|
|
||||||
r.all.push(item);
|
r.all.push(item);
|
||||||
@@ -3410,8 +3823,11 @@ var msel = (function () {
|
|||||||
r.load();
|
r.load();
|
||||||
return r.all;
|
return r.all;
|
||||||
};
|
};
|
||||||
r.selui = function () {
|
r.selui = function (reset) {
|
||||||
r.sel = r.all = null;
|
r.sel = null;
|
||||||
|
if (reset)
|
||||||
|
r.all = null;
|
||||||
|
|
||||||
clmod(ebi('wtoggle'), 'sel', r.getsel().length);
|
clmod(ebi('wtoggle'), 'sel', r.getsel().length);
|
||||||
thegrid.loadsel();
|
thegrid.loadsel();
|
||||||
fileman.render();
|
fileman.render();
|
||||||
@@ -3470,7 +3886,7 @@ var msel = (function () {
|
|||||||
for (var a = 0, aa = tds.length; a < aa; a++) {
|
for (var a = 0, aa = tds.length; a < aa; a++) {
|
||||||
tds[a].onclick = r.seltgl;
|
tds[a].onclick = r.seltgl;
|
||||||
}
|
}
|
||||||
r.selui();
|
r.selui(true);
|
||||||
arcfmt.render();
|
arcfmt.render();
|
||||||
fileman.render();
|
fileman.render();
|
||||||
ebi('selzip').style.display = ebi('unsearch') ? 'none' : '';
|
ebi('selzip').style.display = ebi('unsearch') ? 'none' : '';
|
||||||
|
|||||||
@@ -8,140 +8,6 @@ html, body {
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#tt, #toast {
|
|
||||||
position: fixed;
|
|
||||||
max-width: 34em;
|
|
||||||
background: #222;
|
|
||||||
border: 0 solid #777;
|
|
||||||
box-shadow: 0 .2em .5em #222;
|
|
||||||
border-radius: .4em;
|
|
||||||
z-index: 9001;
|
|
||||||
}
|
|
||||||
#tt {
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 1em;
|
|
||||||
padding: 0 1.3em;
|
|
||||||
height: 0;
|
|
||||||
opacity: .1;
|
|
||||||
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
|
||||||
}
|
|
||||||
#toast {
|
|
||||||
top: 1.4em;
|
|
||||||
right: -1em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
padding: 1em 1.3em;
|
|
||||||
border-width: .4em 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
transition:
|
|
||||||
transform .4s cubic-bezier(.2, 1.2, .5, 1),
|
|
||||||
right .4s cubic-bezier(.2, 1.2, .5, 1);
|
|
||||||
text-shadow: 1px 1px 0 #000;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
#toast pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#toastc {
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
overflow: hidden;
|
|
||||||
left: 0;
|
|
||||||
width: 0;
|
|
||||||
opacity: 0;
|
|
||||||
padding: .3em 0;
|
|
||||||
margin: -.3em 0 0 0;
|
|
||||||
line-height: 1.5em;
|
|
||||||
color: #000;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
text-shadow: none;
|
|
||||||
border-radius: .5em 0 0 .5em;
|
|
||||||
transition: left .3s, width .3s, padding .3s, opacity .3s;
|
|
||||||
}
|
|
||||||
#toast.vis {
|
|
||||||
right: 1.3em;
|
|
||||||
transform: unset;
|
|
||||||
}
|
|
||||||
#toast.vis #toastc {
|
|
||||||
left: -2em;
|
|
||||||
width: .4em;
|
|
||||||
padding: .3em .8em;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#toast.inf {
|
|
||||||
background: #07a;
|
|
||||||
border-color: #0be;
|
|
||||||
}
|
|
||||||
#toast.inf #toastc {
|
|
||||||
background: #0be;
|
|
||||||
}
|
|
||||||
#toast.ok {
|
|
||||||
background: #4a0;
|
|
||||||
border-color: #8e4;
|
|
||||||
}
|
|
||||||
#toast.ok #toastc {
|
|
||||||
background: #8e4;
|
|
||||||
}
|
|
||||||
#toast.warn {
|
|
||||||
background: #970;
|
|
||||||
border-color: #fc0;
|
|
||||||
}
|
|
||||||
#toast.warn #toastc {
|
|
||||||
background: #fc0;
|
|
||||||
}
|
|
||||||
#toast.err {
|
|
||||||
background: #900;
|
|
||||||
border-color: #d06;
|
|
||||||
}
|
|
||||||
#toast.err #toastc {
|
|
||||||
background: #d06;
|
|
||||||
}
|
|
||||||
#tt.b {
|
|
||||||
padding: 0 2em;
|
|
||||||
border-radius: .5em;
|
|
||||||
box-shadow: 0 .2em 1em #000;
|
|
||||||
}
|
|
||||||
#tt.show {
|
|
||||||
padding: 1em 1.3em;
|
|
||||||
border-width: .4em 0;
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#tt.show.b {
|
|
||||||
padding: 1.5em 2em;
|
|
||||||
border-width: .5em 0;
|
|
||||||
}
|
|
||||||
#tt code {
|
|
||||||
background: #3c3c3c;
|
|
||||||
padding: .1em .3em;
|
|
||||||
border-top: 1px solid #777;
|
|
||||||
border-radius: .3em;
|
|
||||||
line-height: 1.7em;
|
|
||||||
}
|
|
||||||
#tt em {
|
|
||||||
color: #f6a;
|
|
||||||
}
|
|
||||||
html.light #tt {
|
|
||||||
background: #fff;
|
|
||||||
border-color: #888 #000 #777 #000;
|
|
||||||
}
|
|
||||||
html.light #tt,
|
|
||||||
html.light #toast {
|
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
html.light #tt code {
|
|
||||||
background: #060;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
html.light #tt em {
|
|
||||||
color: #d38;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#mtw {
|
#mtw {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -149,6 +15,10 @@ html.light #tt em {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 1.5em;
|
padding: 0 1.5em;
|
||||||
}
|
}
|
||||||
|
#toast {
|
||||||
|
bottom: auto;
|
||||||
|
top: 1.4em;
|
||||||
|
}
|
||||||
pre, code, a {
|
pre, code, a {
|
||||||
color: #480;
|
color: #480;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<!DOCTYPE html><html><head>
|
<!DOCTYPE html><html><head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>📝🎉 {{ title }}</title> <!-- 📜 -->
|
<title>📝🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<link href="/.cpr/md.css?_={{ ts }}" rel="stylesheet">
|
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
|
<link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}">
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<link href="/.cpr/md2.css?_={{ ts }}" rel="stylesheet">
|
<link rel="stylesheet" href="/.cpr/md2.css?_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ function md_plug_err(ex, js) {
|
|||||||
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
|
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
|
||||||
errbox.textContent = msg;
|
errbox.textContent = msg;
|
||||||
errbox.onclick = function () {
|
errbox.onclick = function () {
|
||||||
alert('' + ex.stack);
|
modal.alert('<pre>' + ex.stack + '</pre>');
|
||||||
};
|
};
|
||||||
if (o) {
|
if (o) {
|
||||||
errbox.appendChild(o);
|
errbox.appendChild(o);
|
||||||
|
|||||||
@@ -326,26 +326,32 @@ function save(e) {
|
|||||||
return toast.inf(2, "no changes");
|
return toast.inf(2, "no changes");
|
||||||
|
|
||||||
var force = (save_cls.indexOf('force-save') >= 0);
|
var force = (save_cls.indexOf('force-save') >= 0);
|
||||||
if (force && !confirm('confirm that you wish to lose the changes made on the server since you opened this document'))
|
function save2() {
|
||||||
return toast.inf(3, 'aborted');
|
var txt = dom_src.value,
|
||||||
|
fd = new FormData();
|
||||||
|
|
||||||
var txt = dom_src.value;
|
fd.append("act", "tput");
|
||||||
|
fd.append("lastmod", (force ? -1 : last_modified));
|
||||||
|
fd.append("body", txt);
|
||||||
|
|
||||||
var fd = new FormData();
|
var url = (document.location + '').split('?')[0];
|
||||||
fd.append("act", "tput");
|
var xhr = new XMLHttpRequest();
|
||||||
fd.append("lastmod", (force ? -1 : last_modified));
|
xhr.open('POST', url, true);
|
||||||
fd.append("body", txt);
|
xhr.responseType = 'text';
|
||||||
|
xhr.onreadystatechange = save_cb;
|
||||||
|
xhr.btn = save_btn;
|
||||||
|
xhr.txt = txt;
|
||||||
|
|
||||||
var url = (document.location + '').split('?')[0];
|
modpoll.skip_one = true; // skip one iteration while we save
|
||||||
var xhr = new XMLHttpRequest();
|
xhr.send(fd);
|
||||||
xhr.open('POST', url, true);
|
}
|
||||||
xhr.responseType = 'text';
|
|
||||||
xhr.onreadystatechange = save_cb;
|
|
||||||
xhr.btn = save_btn;
|
|
||||||
xhr.txt = txt;
|
|
||||||
|
|
||||||
modpoll.skip_one = true; // skip one iteration while we save
|
if (!force)
|
||||||
xhr.send(fd);
|
save2();
|
||||||
|
else
|
||||||
|
modal.confirm('confirm that you wish to lose the changes made on the server since you opened this document', save2, function () {
|
||||||
|
toast.inf(3, 'aborted');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_cb() {
|
function save_cb() {
|
||||||
@@ -353,19 +359,19 @@ function save_cb() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.status !== 200)
|
if (this.status !== 200)
|
||||||
return alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
|
||||||
var r;
|
var r;
|
||||||
try {
|
try {
|
||||||
r = JSON.parse(this.responseText);
|
r = JSON.parse(this.responseText);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
return alert('Failed to parse reply from server:\n\n' + this.responseText);
|
return toast.err(0, 'Failed to parse reply from server:\n\n' + this.responseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
if (!this.btn.classList.contains('force-save')) {
|
if (!clgot(this.btn, 'force-save')) {
|
||||||
this.btn.classList.add('force-save');
|
clmod(this.btn, 'force-save', 1);
|
||||||
var msg = [
|
var msg = [
|
||||||
'This file has been modified since you started editing it!\n',
|
'This file has been modified since you started editing it!\n',
|
||||||
'if you really want to overwrite, press save again.\n',
|
'if you really want to overwrite, press save again.\n',
|
||||||
@@ -375,15 +381,13 @@ function save_cb() {
|
|||||||
r.lastmod + ' lastmod on the server now,',
|
r.lastmod + ' lastmod on the server now,',
|
||||||
r.now + ' server time now,\n',
|
r.now + ' server time now,\n',
|
||||||
];
|
];
|
||||||
alert(msg.join('\n'));
|
return toast.err(0, msg.join('\n'));
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
alert('Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
return toast.err(0, 'Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.btn.classList.remove('force-save');
|
clmod(this.btn, 'force-save');
|
||||||
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
||||||
|
|
||||||
run_savechk(r.lastmod, this.txt, this.btn, 0);
|
run_savechk(r.lastmod, this.txt, this.btn, 0);
|
||||||
@@ -407,10 +411,8 @@ function savechk_cb() {
|
|||||||
if (this.readyState != XMLHttpRequest.DONE)
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200)
|
||||||
alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
||||||
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
||||||
@@ -423,12 +425,12 @@ function savechk_cb() {
|
|||||||
}, 100);
|
}, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
alert(
|
modal.alert(
|
||||||
'Error! The document on the server does not appear to have saved correctly (your editor contents and the server copy is not identical). Place the document on your clipboard for now and check the server logs for hints\n\n' +
|
'Error! The document on the server does not appear to have saved correctly (your editor contents and the server copy is not identical). Place the document on your clipboard for now and check the server logs for hints\n\n' +
|
||||||
'Length: yours=' + doc1.length + ', server=' + doc2.length
|
'Length: yours=' + doc1.length + ', server=' + doc2.length
|
||||||
);
|
);
|
||||||
alert('yours, ' + doc1.length + ' byte:\n[' + doc1 + ']');
|
modal.alert('yours, ' + doc1.length + ' byte:\n[' + doc1 + ']');
|
||||||
alert('server, ' + doc2.length + ' byte:\n[' + doc2 + ']');
|
modal.alert('server, ' + doc2.length + ' byte:\n[' + doc2 + ']');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,12 +867,10 @@ function iter_uni(e) {
|
|||||||
function cfg_uni(e) {
|
function cfg_uni(e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
|
|
||||||
var reply = prompt("unicode whitelist", esc_uni_whitelist);
|
modal.prompt("unicode whitelist", esc_uni_whitelist, function (reply) {
|
||||||
if (reply === null)
|
esc_uni_whitelist = reply;
|
||||||
return;
|
js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\'');
|
||||||
|
}, null);
|
||||||
esc_uni_whitelist = reply;
|
|
||||||
js_uni_whitelist = eval('\'' + esc_uni_whitelist + '\'');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ html, body {
|
|||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
#toast {
|
||||||
|
bottom: auto;
|
||||||
|
top: 1.4em;
|
||||||
|
}
|
||||||
#mn {
|
#mn {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin: 1.3em 0 .7em 1em;
|
margin: 1.3em 0 .7em 1em;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
<title>📝🎉 {{ title }}</title>
|
<title>📝🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<link href="/.cpr/mde.css?_={{ ts }}" rel="stylesheet">
|
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
<link href="/.cpr/deps/mini-fa.css?_={{ ts }}" rel="stylesheet">
|
<link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}">
|
||||||
<link href="/.cpr/deps/easymde.css?_={{ ts }}" rel="stylesheet">
|
<link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||||
|
<link rel="stylesheet" href="/.cpr/deps/easymde.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mw">
|
<div id="mw">
|
||||||
|
|||||||
@@ -96,39 +96,41 @@ function md_changed(mde, on_srv) {
|
|||||||
var md_now = mde.value();
|
var md_now = mde.value();
|
||||||
var save_btn = QS('.editor-toolbar button.save');
|
var save_btn = QS('.editor-toolbar button.save');
|
||||||
|
|
||||||
if (md_now == window.md_saved)
|
clmod(save_btn, 'disabled', md_now == window.md_saved);
|
||||||
save_btn.classList.add('disabled');
|
|
||||||
else
|
|
||||||
save_btn.classList.remove('disabled');
|
|
||||||
|
|
||||||
set_jumpto();
|
set_jumpto();
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(mde) {
|
function save(mde) {
|
||||||
var save_btn = QS('.editor-toolbar button.save');
|
var save_btn = QS('.editor-toolbar button.save');
|
||||||
if (save_btn.classList.contains('disabled'))
|
if (clgot(save_btn, 'disabled'))
|
||||||
return toast.inf(2, 'no changes');
|
return toast.inf(2, 'no changes');
|
||||||
|
|
||||||
var force = save_btn.classList.contains('force-save');
|
var force = clgot(save_btn, 'force-save');
|
||||||
if (force && !confirm('confirm that you wish to lose the changes made on the server since you opened this document'))
|
function save2() {
|
||||||
return toast.inf(3, 'aborted');
|
var txt = mde.value();
|
||||||
|
|
||||||
var txt = mde.value();
|
var fd = new FormData();
|
||||||
|
fd.append("act", "tput");
|
||||||
|
fd.append("lastmod", (force ? -1 : last_modified));
|
||||||
|
fd.append("body", txt);
|
||||||
|
|
||||||
var fd = new FormData();
|
var url = (document.location + '').split('?')[0];
|
||||||
fd.append("act", "tput");
|
var xhr = new XMLHttpRequest();
|
||||||
fd.append("lastmod", (force ? -1 : last_modified));
|
xhr.open('POST', url, true);
|
||||||
fd.append("body", txt);
|
xhr.responseType = 'text';
|
||||||
|
xhr.onreadystatechange = save_cb;
|
||||||
|
xhr.btn = save_btn;
|
||||||
|
xhr.mde = mde;
|
||||||
|
xhr.txt = txt;
|
||||||
|
xhr.send(fd);
|
||||||
|
}
|
||||||
|
|
||||||
var url = (document.location + '').split('?')[0];
|
if (!force)
|
||||||
var xhr = new XMLHttpRequest();
|
save2();
|
||||||
xhr.open('POST', url, true);
|
else
|
||||||
xhr.responseType = 'text';
|
modal.confirm('confirm that you wish to lose the changes made on the server since you opened this document', save2, function () {
|
||||||
xhr.onreadystatechange = save_cb;
|
toast.inf(3, 'aborted');
|
||||||
xhr.btn = save_btn;
|
});
|
||||||
xhr.mde = mde;
|
|
||||||
xhr.txt = txt;
|
|
||||||
xhr.send(fd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_cb() {
|
function save_cb() {
|
||||||
@@ -136,19 +138,19 @@ function save_cb() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.status !== 200)
|
if (this.status !== 200)
|
||||||
return alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
|
|
||||||
var r;
|
var r;
|
||||||
try {
|
try {
|
||||||
r = JSON.parse(this.responseText);
|
r = JSON.parse(this.responseText);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
return alert('Failed to parse reply from server:\n\n' + this.responseText);
|
return toast.err(0, 'Failed to parse reply from server:\n\n' + this.responseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
if (!this.btn.classList.contains('force-save')) {
|
if (!clgot(this.btn, 'force-save')) {
|
||||||
this.btn.classList.add('force-save');
|
clmod(this.btn, 'force-save', 1);
|
||||||
var msg = [
|
var msg = [
|
||||||
'This file has been modified since you started editing it!\n',
|
'This file has been modified since you started editing it!\n',
|
||||||
'if you really want to overwrite, press save again.\n',
|
'if you really want to overwrite, press save again.\n',
|
||||||
@@ -158,15 +160,13 @@ function save_cb() {
|
|||||||
r.lastmod + ' lastmod on the server now,',
|
r.lastmod + ' lastmod on the server now,',
|
||||||
r.now + ' server time now,\n',
|
r.now + ' server time now,\n',
|
||||||
];
|
];
|
||||||
alert(msg.join('\n'));
|
return toast.err(0, msg.join('\n'));
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
alert('Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
return toast.err(0, 'Error! Save failed. Maybe this JSON explains why:\n\n' + this.responseText);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.btn.classList.remove('force-save');
|
clmod(this.btn, 'force-save');
|
||||||
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
//alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512);
|
||||||
|
|
||||||
// download the saved doc from the server and compare
|
// download the saved doc from the server and compare
|
||||||
@@ -186,35 +186,23 @@ function save_chk() {
|
|||||||
if (this.readyState != XMLHttpRequest.DONE)
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200)
|
||||||
alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
return toast.err(0, 'Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^<pre>/, ""));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
var doc1 = this.txt.replace(/\r\n/g, "\n");
|
||||||
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
var doc2 = this.responseText.replace(/\r\n/g, "\n");
|
||||||
if (doc1 != doc2) {
|
if (doc1 != doc2) {
|
||||||
alert(
|
modal.alert(
|
||||||
'Error! The document on the server does not appear to have saved correctly (your editor contents and the server copy is not identical). Place the document on your clipboard for now and check the server logs for hints\n\n' +
|
'Error! The document on the server does not appear to have saved correctly (your editor contents and the server copy is not identical). Place the document on your clipboard for now and check the server logs for hints\n\n' +
|
||||||
'Length: yours=' + doc1.length + ', server=' + doc2.length
|
'Length: yours=' + doc1.length + ', server=' + doc2.length
|
||||||
);
|
);
|
||||||
alert('yours, ' + doc1.length + ' byte:\n[' + doc1 + ']');
|
modal.alert('yours, ' + doc1.length + ' byte:\n[' + doc1 + ']');
|
||||||
alert('server, ' + doc2.length + ' byte:\n[' + doc2 + ']');
|
modal.alert('server, ' + doc2.length + ' byte:\n[' + doc2 + ']');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_modified = this.lastmod;
|
last_modified = this.lastmod;
|
||||||
md_changed(this.mde, true);
|
md_changed(this.mde, true);
|
||||||
|
|
||||||
var ok = mknod('div');
|
toast.ok(2, 'save OK' + (this.ntry ? '\nattempt ' + this.ntry : ''));
|
||||||
ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1');
|
|
||||||
ok.innerHTML = 'OK✔️';
|
|
||||||
var parent = ebi('m');
|
|
||||||
document.documentElement.appendChild(ok);
|
|
||||||
setTimeout(function () {
|
|
||||||
ok.style.opacity = 0;
|
|
||||||
}, 500);
|
|
||||||
setTimeout(function () {
|
|
||||||
ok.parentNode.removeChild(ok);
|
|
||||||
}, 750);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
#box {
|
#box {
|
||||||
padding: .5em 1em;
|
padding: .5em 1em;
|
||||||
background: #2c2c2c;
|
background: #2c2c2c;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>copyparty</title>
|
<title>copyparty</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>copyparty</title>
|
<title>copyparty</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
203
copyparty/web/ui.css
Normal file
203
copyparty/web/ui.css
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
#tt, #toast {
|
||||||
|
position: fixed;
|
||||||
|
max-width: 34em;
|
||||||
|
background: #222;
|
||||||
|
border: 0 solid #777;
|
||||||
|
box-shadow: 0 .2em .5em #222;
|
||||||
|
border-radius: .4em;
|
||||||
|
z-index: 9001;
|
||||||
|
}
|
||||||
|
#tt {
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 1em;
|
||||||
|
padding: 0 1.3em;
|
||||||
|
height: 0;
|
||||||
|
opacity: .1;
|
||||||
|
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
||||||
|
}
|
||||||
|
#toast {
|
||||||
|
bottom: 5em;
|
||||||
|
right: -1em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
padding: 1em 1.3em;
|
||||||
|
border-width: .4em 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition:
|
||||||
|
transform .4s cubic-bezier(.2, 1.2, .5, 1),
|
||||||
|
right .4s cubic-bezier(.2, 1.2, .5, 1);
|
||||||
|
text-shadow: 1px 1px 0 #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#toastc {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
padding: .3em 0;
|
||||||
|
margin: -.3em 0 0 0;
|
||||||
|
line-height: 1.5em;
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
text-shadow: none;
|
||||||
|
border-radius: .5em 0 0 .5em;
|
||||||
|
transition: left .3s, width .3s, padding .3s, opacity .3s;
|
||||||
|
}
|
||||||
|
#toast pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#toast.vis {
|
||||||
|
right: 1.3em;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
#toast.vis #toastc {
|
||||||
|
left: -2em;
|
||||||
|
width: .4em;
|
||||||
|
padding: .3em .8em;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
#toast.inf {
|
||||||
|
background: #07a;
|
||||||
|
border-color: #0be;
|
||||||
|
}
|
||||||
|
#toast.inf #toastc {
|
||||||
|
background: #0be;
|
||||||
|
}
|
||||||
|
#toast.ok {
|
||||||
|
background: #380;
|
||||||
|
border-color: #8e4;
|
||||||
|
}
|
||||||
|
#toast.ok #toastc {
|
||||||
|
background: #8e4;
|
||||||
|
}
|
||||||
|
#toast.warn {
|
||||||
|
background: #970;
|
||||||
|
border-color: #fc0;
|
||||||
|
}
|
||||||
|
#toast.warn #toastc {
|
||||||
|
background: #fc0;
|
||||||
|
}
|
||||||
|
#toast.err {
|
||||||
|
background: #900;
|
||||||
|
border-color: #d06;
|
||||||
|
}
|
||||||
|
#toast.err #toastc {
|
||||||
|
background: #d06;
|
||||||
|
}
|
||||||
|
#tt.b {
|
||||||
|
padding: 0 2em;
|
||||||
|
border-radius: .5em;
|
||||||
|
box-shadow: 0 .2em 1em #000;
|
||||||
|
}
|
||||||
|
#tt.show {
|
||||||
|
padding: 1em 1.3em;
|
||||||
|
border-width: .4em 0;
|
||||||
|
height: auto;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
#tt.show.b {
|
||||||
|
padding: 1.5em 2em;
|
||||||
|
border-width: .5em 0;
|
||||||
|
}
|
||||||
|
#tt code {
|
||||||
|
background: #3c3c3c;
|
||||||
|
padding: .1em .3em;
|
||||||
|
border-top: 1px solid #777;
|
||||||
|
border-radius: .3em;
|
||||||
|
line-height: 1.7em;
|
||||||
|
}
|
||||||
|
#tt em {
|
||||||
|
color: #f6a;
|
||||||
|
}
|
||||||
|
html.light #tt {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #888 #000 #777 #000;
|
||||||
|
}
|
||||||
|
html.light #tt,
|
||||||
|
html.light #toast {
|
||||||
|
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
html.light #tt code {
|
||||||
|
background: #060;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
html.light #tt em {
|
||||||
|
color: #d38;
|
||||||
|
}
|
||||||
|
#modal {
|
||||||
|
position: fixed;
|
||||||
|
overflow: auto;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9001;
|
||||||
|
background: rgba(64,64,64,0.6);
|
||||||
|
}
|
||||||
|
#modal>table {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#modal td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#modalc {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
background: #f7f7f7;
|
||||||
|
color: #333;
|
||||||
|
text-shadow: none;
|
||||||
|
text-align: left;
|
||||||
|
margin: 3em;
|
||||||
|
padding: 1em 1.1em;
|
||||||
|
border-radius: .6em;
|
||||||
|
box-shadow: 0 .3em 3em rgba(0,0,0,0.5);
|
||||||
|
max-width: 50em;
|
||||||
|
max-height: 30em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
#modalc {
|
||||||
|
min-width: 30em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#modalb {
|
||||||
|
position: sticky;
|
||||||
|
text-align: right;
|
||||||
|
padding-top: 1em;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
#modalb a {
|
||||||
|
color: #000;
|
||||||
|
background: #ccc;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: .3em;
|
||||||
|
padding: .5em 1em;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
#modalb a:focus,
|
||||||
|
#modalb a:hover {
|
||||||
|
background: #06d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#modalb a+a {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
#modali {
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 1.25em);
|
||||||
|
margin: 1em -.1em 0 -.1em;
|
||||||
|
padding: .5em;
|
||||||
|
outline: none;
|
||||||
|
border: .25em solid #ccc;
|
||||||
|
border-radius: .4em;
|
||||||
|
}
|
||||||
|
#modali:focus {
|
||||||
|
border-color: #06d;
|
||||||
|
}
|
||||||
@@ -97,13 +97,11 @@ function up2k_flagbus() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
var do_take = function (now) {
|
var do_take = function (now) {
|
||||||
//dbg('*', 'do_take');
|
|
||||||
tx(now, "have");
|
tx(now, "have");
|
||||||
flag.owner = [flag.id, now];
|
flag.owner = [flag.id, now];
|
||||||
flag.ours = true;
|
flag.ours = true;
|
||||||
};
|
};
|
||||||
var do_want = function (now) {
|
var do_want = function (now) {
|
||||||
//dbg('*', 'do_want');
|
|
||||||
tx(now, "want");
|
tx(now, "want");
|
||||||
};
|
};
|
||||||
flag.take = function (now) {
|
flag.take = function (now) {
|
||||||
@@ -135,15 +133,16 @@ function up2k_flagbus() {
|
|||||||
|
|
||||||
|
|
||||||
function U2pvis(act, btns) {
|
function U2pvis(act, btns) {
|
||||||
this.act = act;
|
var r = this;
|
||||||
this.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
|
r.act = act;
|
||||||
this.tab = [];
|
r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
|
||||||
this.head = 0;
|
r.tab = [];
|
||||||
this.tail = -1;
|
r.head = 0;
|
||||||
this.wsz = 3;
|
r.tail = -1;
|
||||||
|
r.wsz = 3;
|
||||||
|
|
||||||
this.addfile = function (entry, sz, draw) {
|
r.addfile = function (entry, sz, draw) {
|
||||||
this.tab.push({
|
r.tab.push({
|
||||||
"hn": entry[0],
|
"hn": entry[0],
|
||||||
"ht": entry[1],
|
"ht": entry[1],
|
||||||
"hp": entry[2],
|
"hp": entry[2],
|
||||||
@@ -155,34 +154,34 @@ function U2pvis(act, btns) {
|
|||||||
"bd": 0, // bytes done
|
"bd": 0, // bytes done
|
||||||
"bd0": 0 // upload start
|
"bd0": 0 // upload start
|
||||||
});
|
});
|
||||||
this.ctr["q"]++;
|
r.ctr["q"]++;
|
||||||
if (!draw)
|
if (!draw)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.drawcard("q");
|
r.drawcard("q");
|
||||||
if (this.act == "q") {
|
if (r.act == "q") {
|
||||||
this.addrow(this.tab.length - 1);
|
r.addrow(r.tab.length - 1);
|
||||||
}
|
}
|
||||||
if (this.act == "bz") {
|
if (r.act == "bz") {
|
||||||
this.bzw();
|
r.bzw();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.is_act = function (card) {
|
r.is_act = function (card) {
|
||||||
if (this.act == "done")
|
if (r.act == "done")
|
||||||
return card == "ok" || card == "ng";
|
return card == "ok" || card == "ng";
|
||||||
|
|
||||||
return this.act == card;
|
return r.act == card;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.seth = function (nfile, field, html) {
|
r.seth = function (nfile, field, html) {
|
||||||
var fo = this.tab[nfile];
|
var fo = r.tab[nfile];
|
||||||
field = ['hn', 'ht', 'hp'][field];
|
field = ['hn', 'ht', 'hp'][field];
|
||||||
if (fo[field] === html)
|
if (fo[field] === html)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fo[field] = html;
|
fo[field] = html;
|
||||||
if (!this.is_act(fo.in))
|
if (!r.is_act(fo.in))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var obj = ebi('f{0}{1}'.format(nfile, field.slice(1)));
|
var obj = ebi('f{0}{1}'.format(nfile, field.slice(1)));
|
||||||
@@ -193,26 +192,26 @@ function U2pvis(act, btns) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setab = function (nfile, nblocks) {
|
r.setab = function (nfile, nblocks) {
|
||||||
var t = [];
|
var t = [];
|
||||||
for (var a = 0; a < nblocks; a++)
|
for (var a = 0; a < nblocks; a++)
|
||||||
t.push(0);
|
t.push(0);
|
||||||
|
|
||||||
this.tab[nfile].cb = t;
|
r.tab[nfile].cb = t;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setat = function (nfile, blocktab) {
|
r.setat = function (nfile, blocktab) {
|
||||||
this.tab[nfile].cb = blocktab;
|
r.tab[nfile].cb = blocktab;
|
||||||
|
|
||||||
var bd = 0;
|
var bd = 0;
|
||||||
for (var a = 0; a < blocktab.length; a++)
|
for (var a = 0; a < blocktab.length; a++)
|
||||||
bd += blocktab[a];
|
bd += blocktab[a];
|
||||||
|
|
||||||
this.tab[nfile].bd = bd;
|
r.tab[nfile].bd = bd;
|
||||||
this.tab[nfile].bd0 = bd;
|
r.tab[nfile].bd0 = bd;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.perc = function (bd, bd0, sz, t0) {
|
r.perc = function (bd, bd0, sz, t0) {
|
||||||
var td = Date.now() - t0,
|
var td = Date.now() - t0,
|
||||||
p = bd * 100.0 / sz,
|
p = bd * 100.0 / sz,
|
||||||
nb = bd - bd0,
|
nb = bd - bd0,
|
||||||
@@ -222,15 +221,15 @@ function U2pvis(act, btns) {
|
|||||||
return [p, s2ms(eta), spd / (1024 * 1024)];
|
return [p, s2ms(eta), spd / (1024 * 1024)];
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hashed = function (fobj) {
|
r.hashed = function (fobj) {
|
||||||
var fo = this.tab[fobj.n],
|
var fo = r.tab[fobj.n],
|
||||||
nb = fo.bt * (++fo.nh / fo.cb.length),
|
nb = fo.bt * (++fo.nh / fo.cb.length),
|
||||||
p = this.perc(nb, 0, fobj.size, fobj.t_hashing);
|
p = r.perc(nb, 0, fobj.size, fobj.t_hashing);
|
||||||
|
|
||||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||||
);
|
);
|
||||||
if (!this.is_act(fo.in))
|
if (!r.is_act(fo.in))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var obj = ebi('f{0}p'.format(fobj.n)),
|
var obj = ebi('f{0}p'.format(fobj.n)),
|
||||||
@@ -241,31 +240,31 @@ function U2pvis(act, btns) {
|
|||||||
obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
||||||
};
|
};
|
||||||
|
|
||||||
this.prog = function (fobj, nchunk, cbd) {
|
r.prog = function (fobj, nchunk, cbd) {
|
||||||
var fo = this.tab[fobj.n],
|
var fo = r.tab[fobj.n],
|
||||||
delta = cbd - fo.cb[nchunk];
|
delta = cbd - fo.cb[nchunk];
|
||||||
|
|
||||||
fo.cb[nchunk] = cbd;
|
fo.cb[nchunk] = cbd;
|
||||||
fo.bd += delta;
|
fo.bd += delta;
|
||||||
|
|
||||||
var p = this.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
|
var p = r.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
|
||||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!this.is_act(fo.in))
|
if (!r.is_act(fo.in))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var obj = ebi('f{0}p'.format(fobj.n)),
|
var obj = ebi('f{0}p'.format(fobj.n)),
|
||||||
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
||||||
|
|
||||||
if (!obj) { //} || true) {
|
if (!obj) {
|
||||||
var msg = [
|
var msg = [
|
||||||
"act", this.act,
|
"act", r.act,
|
||||||
"in", fo.in,
|
"in", fo.in,
|
||||||
"is_act", this.is_act(fo.in),
|
"is_act", r.is_act(fo.in),
|
||||||
"head", this.head,
|
"head", r.head,
|
||||||
"tail", this.tail,
|
"tail", r.tail,
|
||||||
"nfile", fobj.n,
|
"nfile", fobj.n,
|
||||||
"name", fobj.name,
|
"name", fobj.name,
|
||||||
"sz", fobj.size,
|
"sz", fobj.size,
|
||||||
@@ -283,12 +282,12 @@ function U2pvis(act, btns) {
|
|||||||
for (var a = 0, aa = ds.length; a < aa; a++) {
|
for (var a = 0, aa = ds.length; a < aa; a++) {
|
||||||
var id = ds[a].parentNode.getAttribute('id').slice(1, -1);
|
var id = ds[a].parentNode.getAttribute('id').slice(1, -1);
|
||||||
console.log("dom %d/%d = [%s] in(%s) is_act(%s) %s",
|
console.log("dom %d/%d = [%s] in(%s) is_act(%s) %s",
|
||||||
a, aa, id, this.tab[id].in, this.is_act(fo.in), ds[a].textContent);
|
a, aa, id, r.tab[id].in, r.is_act(fo.in), ds[a].textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var a = 0, aa = this.tab.length; a < aa; a++)
|
for (var a = 0, aa = r.tab.length; a < aa; a++)
|
||||||
if (this.is_act(this.tab[a].in))
|
if (r.is_act(r.tab[a].in))
|
||||||
console.log("tab %d/%d = sz %s", a, aa, this.tab[a].bt);
|
console.log("tab %d/%d = sz %s", a, aa, r.tab[a].bt);
|
||||||
|
|
||||||
throw new Error('see console');
|
throw new Error('see console');
|
||||||
}
|
}
|
||||||
@@ -298,27 +297,27 @@ function U2pvis(act, btns) {
|
|||||||
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
||||||
};
|
};
|
||||||
|
|
||||||
this.move = function (nfile, newcat) {
|
r.move = function (nfile, newcat) {
|
||||||
var fo = this.tab[nfile],
|
var fo = r.tab[nfile],
|
||||||
oldcat = fo.in,
|
oldcat = fo.in,
|
||||||
bz_act = this.act == "bz";
|
bz_act = r.act == "bz";
|
||||||
|
|
||||||
if (oldcat == newcat)
|
if (oldcat == newcat)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fo.in = newcat;
|
fo.in = newcat;
|
||||||
this.ctr[oldcat]--;
|
r.ctr[oldcat]--;
|
||||||
this.ctr[newcat]++;
|
r.ctr[newcat]++;
|
||||||
this.drawcard(oldcat);
|
r.drawcard(oldcat);
|
||||||
this.drawcard(newcat);
|
r.drawcard(newcat);
|
||||||
if (this.is_act(newcat)) {
|
if (r.is_act(newcat)) {
|
||||||
this.tail = Math.max(this.tail, nfile + 1);
|
r.tail = Math.max(r.tail, nfile + 1);
|
||||||
if (!ebi('f' + nfile))
|
if (!ebi('f' + nfile))
|
||||||
this.addrow(nfile);
|
r.addrow(nfile);
|
||||||
}
|
}
|
||||||
else if (this.is_act(oldcat)) {
|
else if (r.is_act(oldcat)) {
|
||||||
while (this.head < Math.min(this.tab.length, this.tail) && this.precard[this.tab[this.head].in])
|
while (r.head < Math.min(r.tab.length, r.tail) && r.precard[r.tab[r.head].in])
|
||||||
this.head++;
|
r.head++;
|
||||||
|
|
||||||
if (!bz_act) {
|
if (!bz_act) {
|
||||||
var tr = ebi("f" + nfile);
|
var tr = ebi("f" + nfile);
|
||||||
@@ -328,10 +327,10 @@ function U2pvis(act, btns) {
|
|||||||
else return;
|
else return;
|
||||||
|
|
||||||
if (bz_act)
|
if (bz_act)
|
||||||
this.bzw();
|
r.bzw();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.bzw = function () {
|
r.bzw = function () {
|
||||||
var first = QS('#u2tab>tbody>tr:first-child');
|
var first = QS('#u2tab>tbody>tr:first-child');
|
||||||
if (!first)
|
if (!first)
|
||||||
return;
|
return;
|
||||||
@@ -340,93 +339,93 @@ function U2pvis(act, btns) {
|
|||||||
first = parseInt(first.getAttribute('id').slice(1));
|
first = parseInt(first.getAttribute('id').slice(1));
|
||||||
last = parseInt(last.getAttribute('id').slice(1));
|
last = parseInt(last.getAttribute('id').slice(1));
|
||||||
|
|
||||||
while (this.head - first > this.wsz) {
|
while (r.head - first > r.wsz) {
|
||||||
var obj = ebi('f' + (first++));
|
var obj = ebi('f' + (first++));
|
||||||
if (obj)
|
if (obj)
|
||||||
obj.parentNode.removeChild(obj);
|
obj.parentNode.removeChild(obj);
|
||||||
}
|
}
|
||||||
while (last - this.tail < this.wsz && last < this.tab.length - 2) {
|
while (last - r.tail < r.wsz && last < r.tab.length - 2) {
|
||||||
var obj = ebi('f' + (++last));
|
var obj = ebi('f' + (++last));
|
||||||
if (!obj)
|
if (!obj)
|
||||||
this.addrow(last);
|
r.addrow(last);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.drawcard = function (cat) {
|
r.drawcard = function (cat) {
|
||||||
var cards = QSA('#u2cards>a>span');
|
var cards = QSA('#u2cards>a>span');
|
||||||
|
|
||||||
if (cat == "q") {
|
if (cat == "q") {
|
||||||
cards[4].innerHTML = this.ctr[cat];
|
cards[4].innerHTML = r.ctr[cat];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cat == "bz") {
|
if (cat == "bz") {
|
||||||
cards[3].innerHTML = this.ctr[cat];
|
cards[3].innerHTML = r.ctr[cat];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cards[2].innerHTML = this.ctr["ok"] + this.ctr["ng"];
|
cards[2].innerHTML = r.ctr["ok"] + r.ctr["ng"];
|
||||||
|
|
||||||
if (cat == "ng") {
|
if (cat == "ng") {
|
||||||
cards[1].innerHTML = this.ctr[cat];
|
cards[1].innerHTML = r.ctr[cat];
|
||||||
}
|
}
|
||||||
if (cat == "ok") {
|
if (cat == "ok") {
|
||||||
cards[0].innerHTML = this.ctr[cat];
|
cards[0].innerHTML = r.ctr[cat];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.changecard = function (card) {
|
r.changecard = function (card) {
|
||||||
this.act = card;
|
r.act = card;
|
||||||
this.precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 };
|
r.precard = has(["ok", "ng", "done"], r.act) ? {} : r.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 };
|
||||||
this.postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {};
|
r.postcard = has(["ok", "ng", "done"], r.act) ? { "bz": 1, "q": 1 } : r.act == "bz" ? { "q": 1 } : {};
|
||||||
this.head = -1;
|
r.head = -1;
|
||||||
this.tail = -1;
|
r.tail = -1;
|
||||||
var html = [];
|
var html = [];
|
||||||
for (var a = 0; a < this.tab.length; a++) {
|
for (var a = 0; a < r.tab.length; a++) {
|
||||||
var rt = this.tab[a].in;
|
var rt = r.tab[a].in;
|
||||||
if (this.is_act(rt)) {
|
if (r.is_act(rt)) {
|
||||||
html.push(this.genrow(a, true));
|
html.push(r.genrow(a, true));
|
||||||
|
|
||||||
this.tail = a;
|
r.tail = a;
|
||||||
if (this.head == -1)
|
if (r.head == -1)
|
||||||
this.head = a;
|
r.head = a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.head == -1) {
|
if (r.head == -1) {
|
||||||
for (var a = 0; a < this.tab.length; a++) {
|
for (var a = 0; a < r.tab.length; a++) {
|
||||||
var rt = this.tab[a].in;
|
var rt = r.tab[a].in;
|
||||||
if (this.precard[rt]) {
|
if (r.precard[rt]) {
|
||||||
this.head = a + 1;
|
r.head = a + 1;
|
||||||
this.tail = a;
|
r.tail = a;
|
||||||
}
|
}
|
||||||
else if (this.postcard[rt]) {
|
else if (r.postcard[rt]) {
|
||||||
this.head = a;
|
r.head = a;
|
||||||
this.tail = a - 1;
|
r.tail = a - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.head < 0)
|
if (r.head < 0)
|
||||||
this.head = 0;
|
r.head = 0;
|
||||||
|
|
||||||
if (card == "bz") {
|
if (card == "bz") {
|
||||||
for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) {
|
for (var a = r.head - 1; a >= r.head - r.wsz && a >= 0; a--) {
|
||||||
html.unshift(this.genrow(a, true).replace(/><td>/, "><td>a "));
|
html.unshift(r.genrow(a, true).replace(/><td>/, "><td>a "));
|
||||||
}
|
}
|
||||||
for (var a = this.tail + 1; a <= this.tail + this.wsz && a < this.tab.length; a++) {
|
for (var a = r.tail + 1; a <= r.tail + r.wsz && a < r.tab.length; a++) {
|
||||||
html.push(this.genrow(a, true).replace(/><td>/, "><td>b "));
|
html.push(r.genrow(a, true).replace(/><td>/, "><td>b "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ebi('u2tab').tBodies[0].innerHTML = html.join('\n');
|
ebi('u2tab').tBodies[0].innerHTML = html.join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.genrow = function (nfile, as_html) {
|
r.genrow = function (nfile, as_html) {
|
||||||
var r = this.tab[nfile],
|
var row = r.tab[nfile],
|
||||||
td1 = '<td id="f' + nfile,
|
td1 = '<td id="f' + nfile,
|
||||||
td = '</td>' + td1,
|
td = '</td>' + td1,
|
||||||
ret = td1 + 'n">' + r.hn +
|
ret = td1 + 'n">' + row.hn +
|
||||||
td + 't">' + r.ht +
|
td + 't">' + row.ht +
|
||||||
td + 'p" class="prog">' + r.hp + '</td>';
|
td + 'p" class="prog">' + row.hp + '</td>';
|
||||||
|
|
||||||
if (as_html)
|
if (as_html)
|
||||||
return '<tr id="f' + nfile + '">' + ret + '</tr>';
|
return '<tr id="f' + nfile + '">' + ret + '</tr>';
|
||||||
@@ -437,40 +436,50 @@ function U2pvis(act, btns) {
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addrow = function (nfile) {
|
r.addrow = function (nfile) {
|
||||||
var tr = this.genrow(nfile);
|
var tr = r.genrow(nfile);
|
||||||
ebi('u2tab').tBodies[0].appendChild(tr);
|
ebi('u2tab').tBodies[0].appendChild(tr);
|
||||||
};
|
};
|
||||||
|
|
||||||
var that = this;
|
|
||||||
btns = QSA(btns + '>a[act]');
|
btns = QSA(btns + '>a[act]');
|
||||||
for (var a = 0; a < btns.length; a++) {
|
for (var a = 0; a < btns.length; a++) {
|
||||||
btns[a].onclick = function (e) {
|
btns[a].onclick = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
var newtab = this.getAttribute('act');
|
var newtab = this.getAttribute('act');
|
||||||
for (var b = 0; b < btns.length; b++) {
|
function go() {
|
||||||
btns[b].className = (
|
for (var b = 0; b < btns.length; b++) {
|
||||||
btns[b].getAttribute('act') == newtab) ? 'act' : '';
|
btns[b].className = (
|
||||||
|
btns[b].getAttribute('act') == newtab) ? 'act' : '';
|
||||||
|
}
|
||||||
|
r.changecard(newtab);
|
||||||
}
|
}
|
||||||
that.changecard(newtab);
|
var nf = r.ctr[newtab];
|
||||||
|
if (nf === undefined)
|
||||||
|
nf = r.ctr["ok"] + r.ctr["ng"];
|
||||||
|
|
||||||
|
if (nf < 9000)
|
||||||
|
return go();
|
||||||
|
|
||||||
|
modal.confirm('about to show ' + nf + ' files\n\nthis may crash your browser, are you sure?', go, null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changecard(this.act);
|
r.changecard(r.act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function fsearch_explain(e) {
|
function fsearch_explain(n) {
|
||||||
ev(e);
|
if (n)
|
||||||
if (!has(perms, 'write'))
|
return toast.inf(60, 'your access to this folder is Read-Only\n\n' + (acct == '*' ? 'you are currently not logged in' : 'you are currently logged in as "' + acct + '"'));
|
||||||
return alert('your access to this folder is Read-Only\n\n' + (acct == '*' ? 'you are currently not logged in' : 'you are currently logged in as ' + acct));
|
|
||||||
|
|
||||||
alert('you are currently in file-search mode\n\nswitch to upload-mode by clicking the green magnifying glass (next to the big yellow search button), and then refresh\n\nsorry');
|
if (bcfg_get('fsearch', false))
|
||||||
|
return toast.inf(60, 'you are currently in file-search mode\n\nswitch to upload-mode by clicking the green magnifying glass (next to the big yellow search button), and then refresh\n\nsorry');
|
||||||
|
|
||||||
|
return toast.inf(60, 'refresh the page and try again, it should work now');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function up2k_init(subtle) {
|
function up2k_init(subtle) {
|
||||||
// show modal message
|
|
||||||
function showmodal(msg) {
|
function showmodal(msg) {
|
||||||
ebi('u2notbtn').innerHTML = msg;
|
ebi('u2notbtn').innerHTML = msg;
|
||||||
ebi('u2btn').style.display = 'none';
|
ebi('u2btn').style.display = 'none';
|
||||||
@@ -478,7 +487,6 @@ function up2k_init(subtle) {
|
|||||||
ebi('u2conf').style.opacity = '0.5';
|
ebi('u2conf').style.opacity = '0.5';
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide modal message
|
|
||||||
function unmodal() {
|
function unmodal() {
|
||||||
ebi('u2notbtn').style.display = 'none';
|
ebi('u2notbtn').style.display = 'none';
|
||||||
ebi('u2btn').style.display = 'block';
|
ebi('u2btn').style.display = 'block';
|
||||||
@@ -495,7 +503,6 @@ function up2k_init(subtle) {
|
|||||||
// chrome<37 firefox<34 edge<12 opera<24 safari<7
|
// chrome<37 firefox<34 edge<12 opera<24 safari<7
|
||||||
shame = 'your browser is impressively ancient';
|
shame = 'your browser is impressively ancient';
|
||||||
|
|
||||||
// upload ui hidden by default, clicking the header shows it
|
|
||||||
var got_deps = false;
|
var got_deps = false;
|
||||||
function init_deps() {
|
function init_deps() {
|
||||||
if (!got_deps && !subtle && !window.asmCrypto) {
|
if (!got_deps && !subtle && !window.asmCrypto) {
|
||||||
@@ -504,7 +511,7 @@ function up2k_init(subtle) {
|
|||||||
import_js('/.cpr/deps/' + fn, unmodal);
|
import_js('/.cpr/deps/' + fn, unmodal);
|
||||||
|
|
||||||
if (is_https)
|
if (is_https)
|
||||||
ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500kB/s at best';
|
ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500 KiB/s at best';
|
||||||
else
|
else
|
||||||
ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance <span style="color:#' +
|
ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance <span style="color:#' +
|
||||||
(sha_js == 'ac' ? 'c84">(expecting 20' : '8a5">(but dont worry too much, expect 100') + ' MiB/s)</span>';
|
(sha_js == 'ac' ? 'c84">(expecting 20' : '8a5">(but dont worry too much, expect 100') + ' MiB/s)</span>';
|
||||||
@@ -512,11 +519,9 @@ function up2k_init(subtle) {
|
|||||||
got_deps = true;
|
got_deps = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// show uploader if the user only has write-access
|
|
||||||
if (perms.length && !has(perms, 'read'))
|
if (perms.length && !has(perms, 'read'))
|
||||||
goto('up2k');
|
goto('up2k');
|
||||||
|
|
||||||
// shows or clears a message in the basic uploader ui
|
|
||||||
function setmsg(msg, type) {
|
function setmsg(msg, type) {
|
||||||
if (msg !== undefined) {
|
if (msg !== undefined) {
|
||||||
ebi('u2err').setAttribute('class', type);
|
ebi('u2err').setAttribute('class', type);
|
||||||
@@ -534,13 +539,11 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// switches to the basic uploader with msg as error message
|
|
||||||
function un2k(msg) {
|
function un2k(msg) {
|
||||||
setmsg(msg, 'err');
|
setmsg(msg, 'err');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle user intent to use the basic uploader instead
|
|
||||||
ebi('u2nope').onclick = function (e) {
|
ebi('u2nope').onclick = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
setmsg(suggest_up2k, 'msg');
|
setmsg(suggest_up2k, 'msg');
|
||||||
@@ -642,7 +645,7 @@ function up2k_init(subtle) {
|
|||||||
else files = e.target.files;
|
else files = e.target.files;
|
||||||
|
|
||||||
if (!files || !files.length)
|
if (!files || !files.length)
|
||||||
return alert('no files selected??');
|
return toast.err(0, 'no files selected??');
|
||||||
|
|
||||||
more_one_file();
|
more_one_file();
|
||||||
var bad_files = [],
|
var bad_files = [],
|
||||||
@@ -710,9 +713,9 @@ function up2k_init(subtle) {
|
|||||||
for (var a = 0; a < Math.min(20, missing.length); a++)
|
for (var a = 0; a < Math.min(20, missing.length); a++)
|
||||||
msg.push(missing[a]);
|
msg.push(missing[a]);
|
||||||
|
|
||||||
alert(msg.join('\n-- '));
|
return modal.alert(msg.join('\n-- '), function () {
|
||||||
dirs = [];
|
read_dirs(rd, [], [], good, bad, spins);
|
||||||
pf = [];
|
});
|
||||||
}
|
}
|
||||||
spins = 0;
|
spins = 0;
|
||||||
}
|
}
|
||||||
@@ -757,7 +760,6 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
ngot += 1;
|
ngot += 1;
|
||||||
});
|
});
|
||||||
// console.log("ngot: " + ngot);
|
|
||||||
if (!ngot) {
|
if (!ngot) {
|
||||||
dirs.shift();
|
dirs.shift();
|
||||||
rd = null;
|
rd = null;
|
||||||
@@ -777,16 +779,22 @@ function up2k_init(subtle) {
|
|||||||
if (good_files.length - bad_files.length <= 1 && ANDROID)
|
if (good_files.length - bad_files.length <= 1 && ANDROID)
|
||||||
msg += '\nFirefox-Android has a bug which prevents selecting multiple files. Try selecting one file at a time. For more info, see firefox bug 1456557';
|
msg += '\nFirefox-Android has a bug which prevents selecting multiple files. Try selecting one file at a time. For more info, see firefox bug 1456557';
|
||||||
|
|
||||||
alert(msg);
|
return modal.alert(msg, function () {
|
||||||
|
gotallfiles(good_files, []);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = ['upload these ' + good_files.length + ' files?'];
|
var msg = ['upload these ' + good_files.length + ' files?'];
|
||||||
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(good_files[a][1]);
|
msg.push(good_files[a][1]);
|
||||||
|
|
||||||
if (ask_up && !fsearch && !confirm(msg.join('\n')))
|
if (ask_up && !fsearch)
|
||||||
return;
|
return modal.confirm(msg.join('\n'), function () { up_them(good_files); }, null);
|
||||||
|
|
||||||
|
up_them(good_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
function up_them(good_files) {
|
||||||
var seen = {},
|
var seen = {},
|
||||||
evpath = get_evpath(),
|
evpath = get_evpath(),
|
||||||
draw_each = good_files.length < 50;
|
draw_each = good_files.length < 50;
|
||||||
@@ -796,17 +804,25 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
for (var a = 0; a < good_files.length; a++) {
|
for (var a = 0; a < good_files.length; a++) {
|
||||||
var fobj = good_files[a][0],
|
var fobj = good_files[a][0],
|
||||||
|
name = good_files[a][1],
|
||||||
|
fdir = get_evpath(),
|
||||||
now = Date.now(),
|
now = Date.now(),
|
||||||
lmod = fobj.lastModified || now;
|
lmod = fobj.lastModified || now,
|
||||||
|
ofs = name.lastIndexOf('/') + 1;
|
||||||
|
|
||||||
|
if (ofs) {
|
||||||
|
fdir += url_enc(name.slice(0, ofs));
|
||||||
|
name = name.slice(ofs);
|
||||||
|
}
|
||||||
|
|
||||||
var entry = {
|
var entry = {
|
||||||
"n": st.files.length,
|
"n": st.files.length,
|
||||||
"t0": now,
|
"t0": now,
|
||||||
"fobj": fobj,
|
"fobj": fobj,
|
||||||
"name": good_files[a][1],
|
"name": name,
|
||||||
"size": fobj.size,
|
"size": fobj.size,
|
||||||
"lmod": lmod / 1000,
|
"lmod": lmod / 1000,
|
||||||
"purl": evpath,
|
"purl": fdir,
|
||||||
"done": false,
|
"done": false,
|
||||||
"hash": []
|
"hash": []
|
||||||
},
|
},
|
||||||
@@ -907,25 +923,23 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
return handshakes_permitted() && 0 ==
|
return handshakes_permitted() && 0 ==
|
||||||
st.todo.handshake.length +
|
st.todo.handshake.length +
|
||||||
st.busy.handshake.length;
|
st.busy.handshake.length +
|
||||||
|
st.todo.upload.length +
|
||||||
|
st.busy.upload.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasker = (function () {
|
var tasker = (function () {
|
||||||
var tto = null,
|
var running = false,
|
||||||
running = false,
|
|
||||||
was_busy = false;
|
was_busy = false;
|
||||||
|
|
||||||
function defer() {
|
function defer() {
|
||||||
running = false;
|
running = false;
|
||||||
clearTimeout(tto);
|
|
||||||
tto = setTimeout(taskerd, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function taskerd() {
|
function taskerd() {
|
||||||
if (running)
|
if (running)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
clearTimeout(tto);
|
|
||||||
if (crashed)
|
if (crashed)
|
||||||
return defer();
|
return defer();
|
||||||
|
|
||||||
@@ -1016,7 +1030,7 @@ function up2k_init(subtle) {
|
|||||||
return defer();
|
return defer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
taskerd();
|
timer.add(taskerd, true);
|
||||||
return taskerd;
|
return taskerd;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -1147,7 +1161,7 @@ function up2k_init(subtle) {
|
|||||||
return tasker();
|
return tasker();
|
||||||
}
|
}
|
||||||
|
|
||||||
alert('y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err);
|
toast.err(0, 'y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err);
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(
|
reader.readAsArrayBuffer(
|
||||||
bobslice.call(t.fobj, car, cdr));
|
bobslice.call(t.fobj, car, cdr));
|
||||||
@@ -1249,7 +1263,7 @@ function up2k_init(subtle) {
|
|||||||
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open('HEAD', t.purl + t.name, true);
|
xhr.open('HEAD', t.purl + uricom_enc(t.name), true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1301,7 +1315,8 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (!response || !response.hits || !response.hits.length) {
|
if (!response || !response.hits || !response.hits.length) {
|
||||||
smsg = '404';
|
smsg = '404';
|
||||||
msg = 'not found on server <a href="#" onclick="fsearch_explain()" class="fsearch_explain">(explain)</a>';
|
msg = ('not found on server <a href="#" onclick="fsearch_explain(' +
|
||||||
|
(has(perms, 'write') ? '0' : '1') + ')" class="fsearch_explain">(explain)</a>');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
smsg = 'found';
|
smsg = 'found';
|
||||||
@@ -1325,11 +1340,13 @@ function up2k_init(subtle) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.name !== t.name) {
|
var rsp_purl = url_enc(response.purl);
|
||||||
// file exists; server renamed us
|
if (rsp_purl !== t.purl || response.name !== t.name) {
|
||||||
console.log("server-rename [" + t.name + "] to [" + response.name + "]");
|
// server renamed us (file exists / path restrictions)
|
||||||
|
console.log("server-rename [" + t.purl + "] [" + t.name + "] to [" + rsp_purl + "] [" + response.name + "]");
|
||||||
|
t.purl = rsp_purl;
|
||||||
t.name = response.name;
|
t.name = response.name;
|
||||||
pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' '));
|
pvis.seth(t.n, 0, linksplit(uricom_dec(t.purl)[0] + t.name).join(' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunksize = get_chunksize(t.size),
|
var chunksize = get_chunksize(t.size),
|
||||||
@@ -1347,7 +1364,7 @@ function up2k_init(subtle) {
|
|||||||
for (var a = 0; a < missing.length; a++) {
|
for (var a = 0; a < missing.length; a++) {
|
||||||
var idx = t.hash.indexOf(missing[a]);
|
var idx = t.hash.indexOf(missing[a]);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return alert('wtf negative index for hash "{0}" in task:\n{1}'.format(
|
return modal.alert('wtf negative index for hash "{0}" in task:\n{1}'.format(
|
||||||
missing[a], JSON.stringify(t)));
|
missing[a], JSON.stringify(t)));
|
||||||
|
|
||||||
t.postlist.push(idx);
|
t.postlist.push(idx);
|
||||||
@@ -1436,7 +1453,7 @@ function up2k_init(subtle) {
|
|||||||
tasker();
|
tasker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
alert("server broke; hs-err {0} on file [{1}]:\n".format(
|
toast.err(0, "server broke; hs-err {0} on file [{1}]:\n".format(
|
||||||
xhr.status, t.name) + (
|
xhr.status, t.name) + (
|
||||||
(xhr.response && xhr.response.err) ||
|
(xhr.response && xhr.response.err) ||
|
||||||
(xhr.responseText && xhr.responseText) ||
|
(xhr.responseText && xhr.responseText) ||
|
||||||
@@ -1456,7 +1473,7 @@ function up2k_init(subtle) {
|
|||||||
if (fsearch)
|
if (fsearch)
|
||||||
req.srch = 1;
|
req.srch = 1;
|
||||||
|
|
||||||
xhr.open('POST', t.purl + 'handshake.php', true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.send(JSON.stringify(req));
|
xhr.send(JSON.stringify(req));
|
||||||
}
|
}
|
||||||
@@ -1496,7 +1513,7 @@ function up2k_init(subtle) {
|
|||||||
console.log("ignoring dupe-segment error", t);
|
console.log("ignoring dupe-segment error", t);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alert("server broke; cu-err {0} on file [{1}]:\n".format(
|
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
|
||||||
xhr.status, t.name) + (txt || "no further information"));
|
xhr.status, t.name) + (txt || "no further information"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1524,7 +1541,7 @@ function up2k_init(subtle) {
|
|||||||
console.log('chunkpit onerror, retrying', t);
|
console.log('chunkpit onerror, retrying', t);
|
||||||
do_send();
|
do_send();
|
||||||
};
|
};
|
||||||
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
xhr.open('POST', t.purl, true);
|
||||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
||||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||||
@@ -1544,7 +1561,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
function onresize(e) {
|
function onresize(e) {
|
||||||
var bar = ebi('ops'),
|
var bar = ebi('ops'),
|
||||||
wpx = innerWidth,
|
wpx = window.innerWidth,
|
||||||
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
||||||
wem = wpx * 1.0 / fpx,
|
wem = wpx * 1.0 / fpx,
|
||||||
wide = wem > 54,
|
wide = wem > 54,
|
||||||
@@ -1687,11 +1704,10 @@ function up2k_init(subtle) {
|
|||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var fun = fsearch ? 'add' : 'remove',
|
var ico = fsearch ? '🔎' : '🚀',
|
||||||
ico = fsearch ? '🔎' : '🚀',
|
|
||||||
desc = fsearch ? 'Search' : 'Upload';
|
desc = fsearch ? 'Search' : 'Upload';
|
||||||
|
|
||||||
ebi('op_up2k').classList[fun]('srch');
|
clmod(ebi('op_up2k'), 'srch', fsearch);
|
||||||
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
|
|
||||||
#op_up2k {
|
|
||||||
padding: 0 1em 1em 1em;
|
|
||||||
}
|
|
||||||
#u2form {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 2px;
|
|
||||||
height: 2px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#u2form input {
|
|
||||||
background: #444;
|
|
||||||
border: 0px solid #444;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
#u2err.err {
|
|
||||||
color: #f87;
|
|
||||||
padding: .5em;
|
|
||||||
}
|
|
||||||
#u2err.msg {
|
|
||||||
color: #999;
|
|
||||||
padding: .5em;
|
|
||||||
font-size: .9em;
|
|
||||||
}
|
|
||||||
#u2btn {
|
|
||||||
color: #eee;
|
|
||||||
background: #555;
|
|
||||||
background: -moz-linear-gradient(top, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
|
||||||
background: -webkit-linear-gradient(top, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
|
||||||
background: linear-gradient(to bottom, #367 0%, #489 50%, #38788a 51%, #367 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#489', endColorstr='#38788a', GradientType=0);
|
|
||||||
text-decoration: none;
|
|
||||||
line-height: 1.3em;
|
|
||||||
border: 1px solid #222;
|
|
||||||
border-radius: .4em;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: .5em auto;
|
|
||||||
padding: .8em 0;
|
|
||||||
width: 16em;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: .4em .4em 0 #111;
|
|
||||||
}
|
|
||||||
#op_up2k.srch #u2btn {
|
|
||||||
background: linear-gradient(to bottom, #ca3 0%, #fd8 50%, #fc6 51%, #b92 100%);
|
|
||||||
text-shadow: 1px 1px 1px #fc6;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
#u2conf #u2btn {
|
|
||||||
margin: -1.5em 0;
|
|
||||||
padding: .8em 0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 12em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
#u2conf #u2btn_cw {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
#u2notbtn {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
background: #333;
|
|
||||||
padding-top: 1em;
|
|
||||||
}
|
|
||||||
#u2notbtn * {
|
|
||||||
line-height: 1.3em;
|
|
||||||
}
|
|
||||||
#u2tab {
|
|
||||||
margin: 3em auto;
|
|
||||||
width: calc(100% - 2em);
|
|
||||||
max-width: 100em;
|
|
||||||
}
|
|
||||||
#op_up2k.srch #u2tab {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
#u2tab td {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-width: 0 0px 1px 0;
|
|
||||||
padding: .1em .3em;
|
|
||||||
}
|
|
||||||
#u2tab td:nth-child(2) {
|
|
||||||
width: 5em;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
#u2tab td:nth-child(3) {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
#op_up2k.srch td.prog {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
#u2tab tbody tr:hover td {
|
|
||||||
background: #222;
|
|
||||||
}
|
|
||||||
#u2cards {
|
|
||||||
padding: 1em 0 .3em 1em;
|
|
||||||
margin: 1.5em auto -2.5em auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
#u2cards.w {
|
|
||||||
width: 45em;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
#u2cards a {
|
|
||||||
padding: .2em 1em;
|
|
||||||
border: 1px solid #777;
|
|
||||||
border-width: 0 0 1px 0;
|
|
||||||
background: linear-gradient(to bottom, #333, #222);
|
|
||||||
}
|
|
||||||
#u2cards a:first-child {
|
|
||||||
border-radius: .4em 0 0 0;
|
|
||||||
}
|
|
||||||
#u2cards a:last-child {
|
|
||||||
border-radius: 0 .4em 0 0;
|
|
||||||
}
|
|
||||||
#u2cards a.act {
|
|
||||||
padding-bottom: .5em;
|
|
||||||
border-width: 1px 1px .1em 1px;
|
|
||||||
border-radius: .3em .3em 0 0;
|
|
||||||
margin-left: -1px;
|
|
||||||
background: linear-gradient(to bottom, #464, #333 80%);
|
|
||||||
box-shadow: 0 -.17em .67em #280;
|
|
||||||
border-color: #7c5 #583 #333 #583;
|
|
||||||
position: relative;
|
|
||||||
color: #fd7;
|
|
||||||
}
|
|
||||||
#u2cards span {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
#u2conf {
|
|
||||||
margin: 1em auto;
|
|
||||||
width: 30em;
|
|
||||||
}
|
|
||||||
#u2conf.has_btn {
|
|
||||||
width: 48em;
|
|
||||||
}
|
|
||||||
#u2conf * {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1em;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
#u2conf .txtbox {
|
|
||||||
width: 3em;
|
|
||||||
color: #fff;
|
|
||||||
background: #444;
|
|
||||||
border: 1px solid #777;
|
|
||||||
font-size: 1.2em;
|
|
||||||
padding: .15em 0;
|
|
||||||
height: 1.05em;
|
|
||||||
}
|
|
||||||
#u2conf .txtbox.err {
|
|
||||||
background: #922;
|
|
||||||
}
|
|
||||||
#u2conf a {
|
|
||||||
color: #fff;
|
|
||||||
background: #c38;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: .1em;
|
|
||||||
font-size: 1.5em;
|
|
||||||
padding: .1em 0;
|
|
||||||
margin: 0 -1px;
|
|
||||||
width: 1.5em;
|
|
||||||
height: 1em;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
bottom: -0.08em;
|
|
||||||
}
|
|
||||||
#u2conf input+a {
|
|
||||||
background: #d80;
|
|
||||||
}
|
|
||||||
#u2conf label {
|
|
||||||
font-size: 1.6em;
|
|
||||||
width: 2em;
|
|
||||||
height: 1em;
|
|
||||||
padding: .4em 0;
|
|
||||||
display: block;
|
|
||||||
border-radius: .25em;
|
|
||||||
}
|
|
||||||
#u2conf input[type="checkbox"] {
|
|
||||||
position: relative;
|
|
||||||
opacity: .02;
|
|
||||||
top: 2em;
|
|
||||||
}
|
|
||||||
#u2conf input[type="checkbox"]+label {
|
|
||||||
position: relative;
|
|
||||||
background: #603;
|
|
||||||
border-bottom: .2em solid #a16;
|
|
||||||
box-shadow: 0 .1em .3em #a00 inset;
|
|
||||||
}
|
|
||||||
#u2conf input[type="checkbox"]:checked+label {
|
|
||||||
background: #6a1;
|
|
||||||
border-bottom: .2em solid #efa;
|
|
||||||
box-shadow: 0 .1em .5em #0c0;
|
|
||||||
}
|
|
||||||
#u2conf input[type="checkbox"]+label:hover {
|
|
||||||
box-shadow: 0 .1em .3em #fb0;
|
|
||||||
border-color: #fb0;
|
|
||||||
}
|
|
||||||
#op_up2k.srch #u2conf td:nth-child(1)>*,
|
|
||||||
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
|
||||||
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
|
||||||
background: #777;
|
|
||||||
border-color: #ccc;
|
|
||||||
box-shadow: none;
|
|
||||||
opacity: .2;
|
|
||||||
}
|
|
||||||
#u2foot {
|
|
||||||
color: #fff;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
#u2foot .warn {
|
|
||||||
font-size: 1.3em;
|
|
||||||
padding: .5em .8em;
|
|
||||||
margin: 1em -.6em;
|
|
||||||
color: #f74;
|
|
||||||
background: #322;
|
|
||||||
border: 1px solid #633;
|
|
||||||
border-width: .1em 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#u2foot .warn span {
|
|
||||||
color: #f86;
|
|
||||||
}
|
|
||||||
html.light #u2foot .warn {
|
|
||||||
color: #b00;
|
|
||||||
background: #fca;
|
|
||||||
border-color: #f70;
|
|
||||||
}
|
|
||||||
html.light #u2foot .warn span {
|
|
||||||
color: #930;
|
|
||||||
}
|
|
||||||
#u2foot span {
|
|
||||||
color: #999;
|
|
||||||
font-size: .9em;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
#u2footfoot {
|
|
||||||
margin-bottom: -1em;
|
|
||||||
}
|
|
||||||
.prog {
|
|
||||||
font-family: monospace, monospace;
|
|
||||||
}
|
|
||||||
#u2tab a>span {
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: italic;
|
|
||||||
color: #fff;
|
|
||||||
padding-left: .2em;
|
|
||||||
}
|
|
||||||
#u2cleanup {
|
|
||||||
float: right;
|
|
||||||
margin-bottom: -.3em;
|
|
||||||
}
|
|
||||||
.fsearch_explain {
|
|
||||||
padding-left: .7em;
|
|
||||||
font-size: 1.1em;
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.light #u2btn {
|
|
||||||
box-shadow: .4em .4em 0 #ccc;
|
|
||||||
}
|
|
||||||
html.light #u2cards span {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
html.light #u2cards a {
|
|
||||||
background: linear-gradient(to bottom, #eee, #fff);
|
|
||||||
}
|
|
||||||
html.light #u2cards a.act {
|
|
||||||
color: #037;
|
|
||||||
background: inherit;
|
|
||||||
box-shadow: 0 -.17em .67em #0ad;
|
|
||||||
border-color: #09c #05a #eee #05a;
|
|
||||||
}
|
|
||||||
html.light #u2conf .txtbox {
|
|
||||||
background: #fff;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
html.light #u2conf .txtbox.err {
|
|
||||||
background: #f96;
|
|
||||||
color: #300;
|
|
||||||
}
|
|
||||||
html.light #op_up2k.srch #u2btn {
|
|
||||||
border-color: #a80;
|
|
||||||
}
|
|
||||||
html.light #u2foot {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
html.light #u2tab tbody tr:hover td {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
@@ -123,11 +123,18 @@ if (!String.startsWith) {
|
|||||||
return this.substring(i, i + s.length) === s;
|
return this.substring(i, i + s.length) === s;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (!Element.prototype.matches) {
|
||||||
|
Element.prototype.matches =
|
||||||
|
Element.prototype.oMatchesSelector ||
|
||||||
|
Element.prototype.msMatchesSelector ||
|
||||||
|
Element.prototype.mozMatchesSelector ||
|
||||||
|
Element.prototype.webkitMatchesSelector;
|
||||||
|
}
|
||||||
if (!Element.prototype.closest) {
|
if (!Element.prototype.closest) {
|
||||||
Element.prototype.closest = function (s) {
|
Element.prototype.closest = function (s) {
|
||||||
var el = this;
|
var el = this;
|
||||||
do {
|
do {
|
||||||
if (el.msMatchesSelector(s)) return el;
|
if (el.matches(s)) return el;
|
||||||
el = el.parentElement || el.parentNode;
|
el = el.parentElement || el.parentNode;
|
||||||
} while (el !== null && el.nodeType === 1);
|
} while (el !== null && el.nodeType === 1);
|
||||||
}
|
}
|
||||||
@@ -140,10 +147,10 @@ function import_js(url, cb) {
|
|||||||
var script = mknod('script');
|
var script = mknod('script');
|
||||||
script.type = 'text/javascript';
|
script.type = 'text/javascript';
|
||||||
script.src = url;
|
script.src = url;
|
||||||
|
|
||||||
script.onreadystatechange = cb;
|
|
||||||
script.onload = cb;
|
script.onload = cb;
|
||||||
|
script.onerror = function () {
|
||||||
|
toast.err(0, 'Failed to load module:\n' + url);
|
||||||
|
};
|
||||||
head.appendChild(script);
|
head.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,90 +177,28 @@ function crc32(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function clmod(obj, cls, add) {
|
function clmod(el, cls, add) {
|
||||||
|
if (el.classList) {
|
||||||
|
if (add == 't')
|
||||||
|
add = el.classList.contains(cls) ? 0 : 1;
|
||||||
|
|
||||||
|
return el.classList[add ? 'add' : 'remove'](cls);
|
||||||
|
}
|
||||||
|
|
||||||
var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g');
|
var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g');
|
||||||
if (add == 't')
|
if (add == 't')
|
||||||
add = !re.test(obj.className);
|
add = !re.test(el.className);
|
||||||
|
|
||||||
obj.className = obj.className.replace(re, ' ') + (add ? ' ' + cls : '');
|
el.className = el.className.replace(re, ' ') + (add ? ' ' + cls : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function sortfiles(nodes) {
|
function clgot(el, cls) {
|
||||||
var sopts = jread('fsort', [["href", 1, ""]]);
|
if (el.classList)
|
||||||
|
return el.classList.contains(cls);
|
||||||
|
|
||||||
try {
|
var lst = (el.getAttribute('class') + '').split(/ /g);
|
||||||
var is_srch = false;
|
return has(lst, cls);
|
||||||
if (nodes[0]['rp']) {
|
|
||||||
is_srch = true;
|
|
||||||
for (var b = 0, bb = nodes.length; b < bb; b++)
|
|
||||||
nodes[b].ext = nodes[b].rp.split('.').pop();
|
|
||||||
for (var b = 0; b < sopts.length; b++)
|
|
||||||
if (sopts[b][0] == 'href')
|
|
||||||
sopts[b][0] = 'rp';
|
|
||||||
}
|
|
||||||
for (var a = sopts.length - 1; a >= 0; a--) {
|
|
||||||
var name = sopts[a][0], rev = sopts[a][1], typ = sopts[a][2];
|
|
||||||
if (!name)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (name == 'ts')
|
|
||||||
typ = 'int';
|
|
||||||
|
|
||||||
if (name.indexOf('tags/') === 0) {
|
|
||||||
name = name.slice(5);
|
|
||||||
for (var b = 0, bb = nodes.length; b < bb; b++)
|
|
||||||
nodes[b]._sv = nodes[b].tags[name];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (var b = 0, bb = nodes.length; b < bb; b++) {
|
|
||||||
var v = nodes[b][name];
|
|
||||||
|
|
||||||
if ((v + '').indexOf('<a ') === 0)
|
|
||||||
v = v.split('>')[1];
|
|
||||||
else if (name == "href" && v) {
|
|
||||||
if (v.slice(-1) == '/')
|
|
||||||
v = '\t' + v;
|
|
||||||
|
|
||||||
v = uricom_dec(v)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes[b]._sv = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var onodes = nodes.map(function (x) { return x; });
|
|
||||||
nodes.sort(function (n1, n2) {
|
|
||||||
var v1 = n1._sv,
|
|
||||||
v2 = n2._sv;
|
|
||||||
|
|
||||||
if (v1 === undefined) {
|
|
||||||
if (v2 === undefined) {
|
|
||||||
return onodes.indexOf(n1) - onodes.indexOf(n2);
|
|
||||||
}
|
|
||||||
return -1 * rev;
|
|
||||||
}
|
|
||||||
if (v2 === undefined) return 1 * rev;
|
|
||||||
|
|
||||||
var ret = rev * (typ == 'int' ? (v1 - v2) : (v1.localeCompare(v2)));
|
|
||||||
if (ret === 0)
|
|
||||||
ret = onodes.indexOf(n1) - onodes.indexOf(n2);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (var b = 0, bb = nodes.length; b < bb; b++) {
|
|
||||||
delete nodes[b]._sv;
|
|
||||||
if (is_srch)
|
|
||||||
delete nodes[b].ext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
console.log("failed to apply sort config: " + ex);
|
|
||||||
console.log("resetting fsort " + sread('fsort'))
|
|
||||||
localStorage.removeItem('fsort');
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -386,6 +331,16 @@ function uricom_enc(txt, do_fb_enc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function url_enc(txt) {
|
||||||
|
var parts = txt.split('/'),
|
||||||
|
ret = [];
|
||||||
|
|
||||||
|
for (var a = 0; a < parts.length; a++)
|
||||||
|
ret.push(uricom_enc(parts[a]));
|
||||||
|
|
||||||
|
return ret.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function uricom_dec(txt) {
|
function uricom_dec(txt) {
|
||||||
try {
|
try {
|
||||||
@@ -555,6 +510,39 @@ function hist_replace(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var timer = (function () {
|
||||||
|
var r = {};
|
||||||
|
r.q = [];
|
||||||
|
r.last = 0;
|
||||||
|
|
||||||
|
r.add = function (fun, run) {
|
||||||
|
r.rm(fun);
|
||||||
|
r.q.push(fun);
|
||||||
|
|
||||||
|
if (run)
|
||||||
|
fun();
|
||||||
|
};
|
||||||
|
|
||||||
|
r.rm = function (fun) {
|
||||||
|
apop(r.q, fun);
|
||||||
|
};
|
||||||
|
|
||||||
|
function doevents() {
|
||||||
|
if (Date.now() - r.last < 69)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var q = r.q.slice(0);
|
||||||
|
for (var a = 0; a < q.length; a++)
|
||||||
|
q[a]();
|
||||||
|
|
||||||
|
r.last = Date.now();
|
||||||
|
}
|
||||||
|
setInterval(doevents, 100);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
var tt = (function () {
|
var tt = (function () {
|
||||||
var r = {
|
var r = {
|
||||||
"tt": mknod("div"),
|
"tt": mknod("div"),
|
||||||
@@ -656,14 +644,24 @@ var tt = (function () {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function lf2br(txt) {
|
||||||
|
var html = '', hp = txt.split(/(?=<.?pre>)/i);
|
||||||
|
for (var a = 0; a < hp.length; a++)
|
||||||
|
html += hp[a].startsWith('<pre>') ? hp[a] :
|
||||||
|
hp[a].replace(/<br ?.?>\n/g, '\n').replace(/\n<br ?.?>/g, '\n').replace(/\n/g, '<br />\n');
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var toast = (function () {
|
var toast = (function () {
|
||||||
var r = {},
|
var r = {},
|
||||||
te = null,
|
te = null,
|
||||||
visible = false,
|
|
||||||
obj = mknod('div');
|
obj = mknod('div');
|
||||||
|
|
||||||
obj.setAttribute('id', 'toast');
|
obj.setAttribute('id', 'toast');
|
||||||
document.body.appendChild(obj);;
|
document.body.appendChild(obj);
|
||||||
|
r.visible = false;
|
||||||
|
|
||||||
r.hide = function (e) {
|
r.hide = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
@@ -677,12 +675,7 @@ var toast = (function () {
|
|||||||
if (ms)
|
if (ms)
|
||||||
te = setTimeout(r.hide, ms * 1000);
|
te = setTimeout(r.hide, ms * 1000);
|
||||||
|
|
||||||
var html = '', hp = txt.split(/(?=<.?pre>)/i);
|
obj.innerHTML = '<a href="#" id="toastc">x</a>' + lf2br(txt);
|
||||||
for (var a = 0; a < hp.length; a++)
|
|
||||||
html += hp[a].startsWith('<pre>') ? hp[a] :
|
|
||||||
hp[a].replace(/<br ?.?>\n/g, '\n').replace(/\n<br ?.?>/g, '\n').replace(/\n/g, '<br />\n');
|
|
||||||
|
|
||||||
obj.innerHTML = '<a href="#" id="toastc">x</a>' + html;
|
|
||||||
obj.className = cl;
|
obj.className = cl;
|
||||||
ms += obj.offsetWidth;
|
ms += obj.offsetWidth;
|
||||||
obj.className += ' vis';
|
obj.className += ' vis';
|
||||||
@@ -705,3 +698,124 @@ var toast = (function () {
|
|||||||
|
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
var modal = (function () {
|
||||||
|
var r = {},
|
||||||
|
q = [],
|
||||||
|
o = null,
|
||||||
|
cb_ok = null,
|
||||||
|
cb_ng = null;
|
||||||
|
|
||||||
|
r.busy = false;
|
||||||
|
|
||||||
|
r.show = function (html) {
|
||||||
|
o = mknod('div');
|
||||||
|
o.setAttribute('id', 'modal');
|
||||||
|
o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
|
||||||
|
document.body.appendChild(o);
|
||||||
|
document.addEventListener('keydown', onkey);
|
||||||
|
r.busy = true;
|
||||||
|
|
||||||
|
var a = ebi('modal-ng');
|
||||||
|
if (a)
|
||||||
|
a.onclick = ng;
|
||||||
|
|
||||||
|
a = ebi('modal-ok');
|
||||||
|
a.onclick = ok;
|
||||||
|
|
||||||
|
(ebi('modali') || a).focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
r.hide = function () {
|
||||||
|
o.parentNode.removeChild(o);
|
||||||
|
document.removeEventListener('keydown', onkey);
|
||||||
|
r.busy = false;
|
||||||
|
setTimeout(next, 50);
|
||||||
|
};
|
||||||
|
function ok(e) {
|
||||||
|
ev(e);
|
||||||
|
var v = ebi('modali');
|
||||||
|
v = v ? v.value : true;
|
||||||
|
r.hide();
|
||||||
|
if (cb_ok)
|
||||||
|
cb_ok(v);
|
||||||
|
}
|
||||||
|
function ng(e) {
|
||||||
|
ev(e);
|
||||||
|
r.hide();
|
||||||
|
if (cb_ng)
|
||||||
|
cb_ng(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onkey(e) {
|
||||||
|
if (e.code == 'Enter') {
|
||||||
|
var a = ebi('modal-ng');
|
||||||
|
if (a && document.activeElement == a)
|
||||||
|
return ng();
|
||||||
|
|
||||||
|
return ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.code == 'Escape')
|
||||||
|
return ng();
|
||||||
|
}
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
if (!r.busy && q.length)
|
||||||
|
q.shift()();
|
||||||
|
}
|
||||||
|
|
||||||
|
r.alert = function (html, cb) {
|
||||||
|
q.push(function () {
|
||||||
|
_alert(lf2br(html), cb);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
function _alert(html, cb) {
|
||||||
|
cb_ok = cb_ng = cb;
|
||||||
|
html += '<div id="modalb"><a href="#" id="modal-ok">OK</a></div>';
|
||||||
|
r.show(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
r.confirm = function (html, cok, cng) {
|
||||||
|
q.push(function () {
|
||||||
|
_confirm(lf2br(html), cok, cng);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
function _confirm(html, cok, cng) {
|
||||||
|
cb_ok = cok;
|
||||||
|
cb_ng = cng === undefined ? cok : null;
|
||||||
|
html += '<div id="modalb"><a href="#" id="modal-ok">OK</a><a href="#" id="modal-ng">Cancel</a></div>';
|
||||||
|
r.show(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
r.prompt = function (html, v, cok, cng) {
|
||||||
|
q.push(function () {
|
||||||
|
_prompt(lf2br(html), v, cok, cng);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
function _prompt(html, v, cok, cng) {
|
||||||
|
cb_ok = cok;
|
||||||
|
cb_ng = cng === undefined ? cok : null;
|
||||||
|
html += '<input id="modali" type="text" /><div id="modalb"><a href="#" id="modal-ok">OK</a><a href="#" id="modal-ng">Cancel</a></div>';
|
||||||
|
r.show(html);
|
||||||
|
|
||||||
|
ebi('modali').value = v || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function winpopup(txt) {
|
||||||
|
fetch(get_evpath(), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
||||||
|
},
|
||||||
|
body: 'msg=' + uricom_enc(Date.now() + ', ' + txt)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ about:config >> devtools.debugger.prefs-schema-version = -1
|
|||||||
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser,up2k}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser,up2k}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
|
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser,up2k}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser,up2k}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
|
||||||
|
|
||||||
# download all sfx versions
|
# download all sfx versions
|
||||||
curl https://api.github.com/repos/9001/copyparty/releases?per_page=100 | jq -r '.[] | .tag_name + " " + .name' | while read v t; do fn="copyparty $v $t.py"; [ -e $fn ] || curl https://github.com/9001/copyparty/releases/download/$v/copyparty-sfx.py -Lo "$fn"; done
|
curl https://api.github.com/repos/9001/copyparty/releases?per_page=100 | jq -r '.[] | .tag_name + " " + .name' | tr -d '\r' | while read v t; do fn="copyparty $v $t.py"; [ -e "$fn" ] || curl https://github.com/9001/copyparty/releases/download/$v/copyparty-sfx.py -Lo "$fn"; done
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
help() { exec cat <<'EOF'
|
||||||
|
|
||||||
# optional args:
|
# optional args:
|
||||||
#
|
#
|
||||||
@@ -26,6 +27,8 @@ echo
|
|||||||
#
|
#
|
||||||
# `no-dd` saves ~2k by removing the mouse cursor
|
# `no-dd` saves ~2k by removing the mouse cursor
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
# port install gnutar findutils gsed coreutils
|
# port install gnutar findutils gsed coreutils
|
||||||
gtar=$(command -v gtar || command -v gnutar) || true
|
gtar=$(command -v gtar || command -v gnutar) || true
|
||||||
@@ -73,6 +76,7 @@ while [ ! -z "$1" ]; do
|
|||||||
no-cm) no_cm=1 ; ;;
|
no-cm) no_cm=1 ; ;;
|
||||||
no-sh) do_sh= ; ;;
|
no-sh) do_sh= ; ;;
|
||||||
no-py) do_py= ; ;;
|
no-py) do_py= ; ;;
|
||||||
|
*) help ; ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
@@ -204,19 +208,24 @@ done
|
|||||||
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
||||||
echo h > copyparty/web/mde.html
|
echo h > copyparty/web/mde.html
|
||||||
f=copyparty/web/md.html
|
f=copyparty/web/md.html
|
||||||
sed -r '/edit2">edit \(fancy/d' <$f >t && tmv "$f"
|
sed -r '/edit2">edit \(fancy/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $no_fnt ] && {
|
[ $no_fnt ] && {
|
||||||
rm -f copyparty/web/deps/scp.woff2
|
rm -f copyparty/web/deps/scp.woff2
|
||||||
f=copyparty/web/md.css
|
f=copyparty/web/md.css
|
||||||
sed -r '/scp\.woff2/d' <$f >t && tmv "$f"
|
gzip -d "$f"
|
||||||
|
sed -r '/scp\.woff2/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $no_dd ] && {
|
[ $no_dd ] && {
|
||||||
rm -rf copyparty/web/dd
|
rm -rf copyparty/web/dd
|
||||||
f=copyparty/web/browser.css
|
f=copyparty/web/browser.css
|
||||||
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t && tmv "$f"
|
gzip -d "$f"
|
||||||
|
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $repack ] ||
|
[ $repack ] ||
|
||||||
@@ -230,7 +239,8 @@ awk '/^LOREM_IPSUM_WORDS/{o=1;print "LOREM_IPSUM_WORDS = u\"a\"";next} !o; /"""/
|
|||||||
tmv "$f"
|
tmv "$f"
|
||||||
|
|
||||||
# up2k goes from 28k to 22k laff
|
# up2k goes from 28k to 22k laff
|
||||||
echo entabbening
|
awk 'BEGIN{gensub(//,"",1)}' </dev/null &&
|
||||||
|
echo entabbening &&
|
||||||
find | grep -E '\.css$' | while IFS= read -r f; do
|
find | grep -E '\.css$' | while IFS= read -r f; do
|
||||||
awk '{
|
awk '{
|
||||||
sub(/^[ \t]+/,"");
|
sub(/^[ \t]+/,"");
|
||||||
@@ -244,6 +254,7 @@ find | grep -E '\.css$' | while IFS= read -r f; do
|
|||||||
' <$f | sed 's/;\}$/}/' >t
|
' <$f | sed 's/;\}$/}/' >t
|
||||||
tmv "$f"
|
tmv "$f"
|
||||||
done
|
done
|
||||||
|
unexpand -h 2>/dev/null &&
|
||||||
find | grep -E '\.(js|html)$' | while IFS= read -r f; do
|
find | grep -E '\.(js|html)$' | while IFS= read -r f; do
|
||||||
unexpand -t 4 --first-only <"$f" >t
|
unexpand -t 4 --first-only <"$f" >t
|
||||||
tmv "$f"
|
tmv "$f"
|
||||||
@@ -251,7 +262,7 @@ done
|
|||||||
|
|
||||||
gzres() {
|
gzres() {
|
||||||
command -v pigz &&
|
command -v pigz &&
|
||||||
pk='pigz -11 -J 34 -I 256' ||
|
pk='pigz -11 -I 256' ||
|
||||||
pk='gzip'
|
pk='gzip'
|
||||||
|
|
||||||
echo "$pk"
|
echo "$pk"
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ def confirm(rv):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
sys.exit(rv)
|
sys.exit(rv or 1)
|
||||||
|
|
||||||
|
|
||||||
def run(tmp, j2):
|
def run(tmp, j2):
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ def tc1():
|
|||||||
pdirs = [x.replace("\\", "/") for x in pdirs]
|
pdirs = [x.replace("\\", "/") for x in pdirs]
|
||||||
udirs = [x.split("/", 2)[2] for x in pdirs]
|
udirs = [x.split("/", 2)[2] for x in pdirs]
|
||||||
perms = [x.rstrip("j/")[-1] for x in pdirs]
|
perms = [x.rstrip("j/")[-1] for x in pdirs]
|
||||||
|
perms = ["rw" if x == "a" else x for x in perms]
|
||||||
for pd, ud, p in zip(pdirs, udirs, perms):
|
for pd, ud, p in zip(pdirs, udirs, perms):
|
||||||
if ud[-1] == "j":
|
if ud[-1] == "j":
|
||||||
continue
|
continue
|
||||||
@@ -124,7 +125,7 @@ def tc1():
|
|||||||
|
|
||||||
arg = "{}:{}:{}".format(pd, ud, p, hp)
|
arg = "{}:{}:{}".format(pd, ud, p, hp)
|
||||||
if hp:
|
if hp:
|
||||||
arg += ":chist=" + hp
|
arg += ":c,hist=" + hp
|
||||||
|
|
||||||
args += ["-v", arg]
|
args += ["-v", arg]
|
||||||
|
|
||||||
@@ -147,14 +148,14 @@ def tc1():
|
|||||||
u = "{}{}/a.h264".format(ub, d)
|
u = "{}{}/a.h264".format(ub, d)
|
||||||
r = requests.get(u)
|
r = requests.get(u)
|
||||||
ok = bool(r)
|
ok = bool(r)
|
||||||
if ok != (p in ["a"]):
|
if ok != (p in ["rw"]):
|
||||||
raise Exception("get {} with perm {} at {}".format(ok, p, u))
|
raise Exception("get {} with perm {} at {}".format(ok, p, u))
|
||||||
|
|
||||||
# stat filesystem
|
# stat filesystem
|
||||||
for d, p in zip(pdirs, perms):
|
for d, p in zip(pdirs, perms):
|
||||||
u = "{}/a.h264".format(d)
|
u = "{}/a.h264".format(d)
|
||||||
ok = os.path.exists(u)
|
ok = os.path.exists(u)
|
||||||
if ok != (p in ["a", "w"]):
|
if ok != (p in ["rw", "w"]):
|
||||||
raise Exception("stat {} with perm {} at {}".format(ok, p, u))
|
raise Exception("stat {} with perm {} at {}".format(ok, p, u))
|
||||||
|
|
||||||
# GET thumbnail, vreify contents
|
# GET thumbnail, vreify contents
|
||||||
@@ -162,7 +163,7 @@ def tc1():
|
|||||||
u = "{}{}/a.h264?th=j".format(ub, d)
|
u = "{}{}/a.h264?th=j".format(ub, d)
|
||||||
r = requests.get(u)
|
r = requests.get(u)
|
||||||
ok = bool(r and r.content[:3] == b"\xff\xd8\xff")
|
ok = bool(r and r.content[:3] == b"\xff\xd8\xff")
|
||||||
if ok != (p in ["a"]):
|
if ok != (p in ["rw"]):
|
||||||
raise Exception("thumb {} with perm {} at {}".format(ok, p, u))
|
raise Exception("thumb {} with perm {} at {}".format(ok, p, u))
|
||||||
|
|
||||||
# check tags
|
# check tags
|
||||||
@@ -179,10 +180,10 @@ def tc1():
|
|||||||
r_ok = bool(j)
|
r_ok = bool(j)
|
||||||
w_ok = bool(r_ok and j.get("files"))
|
w_ok = bool(r_ok and j.get("files"))
|
||||||
|
|
||||||
if not r_ok or w_ok != (p in ["a"]):
|
if not r_ok or w_ok != (p in ["rw"]):
|
||||||
raise Exception("ls {} with perm {} at {}".format(ok, p, u))
|
raise Exception("ls {} with perm {} at {}".format(ok, p, u))
|
||||||
|
|
||||||
if (tag and p != "a") or (not tag and p == "a"):
|
if (tag and p != "rw") or (not tag and p == "rw"):
|
||||||
raise Exception("tag {} with perm {} at {}".format(tag, p, u))
|
raise Exception("tag {} with perm {} at {}".format(tag, p, u))
|
||||||
|
|
||||||
if tag is not None and tag != "48x32":
|
if tag is not None and tag != "48x32":
|
||||||
|
|||||||
1
setup.py
1
setup.py
@@ -99,6 +99,7 @@ args = {
|
|||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
"Environment :: Console",
|
"Environment :: Console",
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ class Cfg(Namespace):
|
|||||||
no_scandir=False,
|
no_scandir=False,
|
||||||
no_sendfile=True,
|
no_sendfile=True,
|
||||||
no_rescan=True,
|
no_rescan=True,
|
||||||
|
re_maxage=0,
|
||||||
ihead=False,
|
ihead=False,
|
||||||
nih=True,
|
nih=True,
|
||||||
mtp=[],
|
mtp=[],
|
||||||
mte="a",
|
mte="a",
|
||||||
|
mth="",
|
||||||
hist=None,
|
hist=None,
|
||||||
no_hash=False,
|
no_hash=False,
|
||||||
css_browser=None,
|
css_browser=None,
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ class Cfg(Namespace):
|
|||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
"mte": "a",
|
"mte": "a",
|
||||||
|
"mth": "",
|
||||||
"hist": None,
|
"hist": None,
|
||||||
"no_hash": False,
|
"no_hash": False,
|
||||||
"css_browser": None,
|
"css_browser": None,
|
||||||
"no_voldump": True,
|
"no_voldump": True,
|
||||||
|
"re_maxage": 0,
|
||||||
"rproxy": 0,
|
"rproxy": 0,
|
||||||
}
|
}
|
||||||
ex.update(ex2)
|
ex.update(ex2)
|
||||||
|
|||||||
Reference in New Issue
Block a user