Compare commits

...

61 Commits

Author SHA1 Message Date
ed
a11c1005a8 v1.3.6 2022-07-16 03:58:58 +02:00
ed
4a6aea9328 hopefully got this right 2022-07-16 02:24:53 +02:00
ed
4ca041e93e improve autopotato accuracy 2022-07-16 02:23:50 +02:00
ed
52a866a405 batch progress writes 2022-07-16 02:12:56 +02:00
ed
8b6bd0e6ac rescue some exceptions from the promise maelstroms 2022-07-15 23:42:37 +02:00
ed
780fc4639a bbox: chrome doesnt override video onclick 2022-07-15 22:36:35 +02:00
ed
3692fc9d83 bbox: doubletap pic for fullscreen 2022-07-15 22:29:44 +02:00
ed
c2a0b1b4c6 autopotato 2022-07-15 02:39:32 +02:00
ed
21bbdb5419 fix audio-eq on recent chromes 2022-07-15 02:07:48 +02:00
ed
aa1c08962c golf 2022-07-15 02:07:13 +02:00
ed
8a5d0399dd sfx: dont hang supervisors 2022-07-15 02:04:00 +02:00
ed
f2cd0b0c4a sfx: avoid name collisions across reboots 2022-07-15 02:03:41 +02:00
ed
c2b66bbe73 add potato mode 2022-07-14 02:33:35 +02:00
ed
48b957f1d5 add -e2v (file integrity checker) 2022-07-13 00:48:39 +02:00
ed
3683984c8d abort volume indexing on ^C 2022-07-12 21:46:07 +02:00
ed
a3431512d8 push queue/status info to server 2022-07-12 21:22:02 +02:00
ed
d832b787e7 upload smallest-file-first by default 2022-07-12 20:48:38 +02:00
ed
6f75b02723 misc 2022-07-12 03:16:30 +02:00
ed
b8241710bd md-editor fixes 2022-07-12 02:53:33 +02:00
ed
d638404b6a better runahead strategy for 100 GiB+ files 2022-07-12 02:30:49 +02:00
ed
9362ca3ed9 py2 fixes 2022-07-11 23:53:18 +02:00
ed
d1a03c6d17 zerobyte semantics 2022-07-11 23:17:33 +02:00
ed
c6c31702c2 cheaper file deletion 2022-07-11 01:50:18 +02:00
ed
bd2d88c96e add another up2k-hook example 2022-07-11 00:52:59 +02:00
ed
76b1857e4e add support for up2k hooks 2022-07-09 14:02:35 +02:00
ed
095bd17d10 mtp/vidchk: grab some frames at the start too 2022-07-09 13:10:00 +02:00
ed
204bfac3fa mtp/vidchk: write ffprobe metadata to file 2022-07-09 04:33:19 +02:00
ed
ac49b0ca93 mtp: add rclone uploader 2022-07-08 23:47:27 +02:00
ed
c5b04f6fef mtp daisychaining 2022-07-08 22:29:05 +02:00
ed
5c58fda46d only clean thumbs if there are thumbs to clean 2022-07-08 21:13:10 +02:00
ed
062730c70c cleanup 2022-07-06 11:12:36 +02:00
ed
cade1990ce v1.3.5 2022-07-06 02:29:11 +02:00
ed
59b6e61816 build fstab from relabels when mtab is unreadable 2022-07-06 02:28:34 +02:00
ed
daff7ff158 v1.3.4 2022-07-06 00:12:10 +02:00
ed
0862860961 misc cleanup 2022-07-06 00:00:56 +02:00
ed
1cb24045a0 dont thumb empty files 2022-07-05 23:45:47 +02:00
ed
622358b172 flag to control mtp timeout kill behavior 2022-07-05 23:38:49 +02:00
ed
7998884a9d adopt the osd hider 2022-07-05 23:36:44 +02:00
ed
51ddecd101 improve readme 2022-07-05 23:27:48 +02:00
ed
7a35ab1d1e bbox: video seek / loop url params 2022-07-05 20:37:05 +02:00
ed
48564ba52a bbox: add A-B video loop 2022-07-05 19:53:43 +02:00
ed
49efffd740 bbox: tap left/right side of image for prev/next 2022-07-05 19:33:09 +02:00
ed
d6ac224c8f bbox: tap to show/hide buttons 2022-07-05 19:18:21 +02:00
ed
a772b8c3f2 bbox: add fullscreen for images too 2022-07-05 19:06:02 +02:00
ed
b580953dcd bbox: fix crash on swipe during close 2022-07-05 18:49:52 +02:00
ed
d86653c763 ux 2022-07-05 00:13:08 +02:00
ed
dded4fca76 option to specify favicon + default-enable it 2022-07-05 00:06:22 +02:00
ed
36365ffa6b explain the donut 2022-07-04 22:17:37 +02:00
ed
0f9aeeaa27 bump codemirror to 5.65.6 2022-07-04 22:15:52 +02:00
ed
d8ebcd0ef7 lol dpi 2022-07-04 22:13:28 +02:00
ed
6e445487b1 satisfy cloudflare DDoS protection 2022-07-03 16:04:28 +02:00
ed
6605e461c7 improve mtp section 2022-07-03 14:23:56 +02:00
ed
40ce4e2275 cleanup 2022-07-03 13:55:48 +02:00
ed
8fef9e363e recursive kill mtp on timeout 2022-07-03 04:57:15 +02:00
ed
4792c2770d fix a spin 2022-07-03 02:39:15 +02:00
ed
87bb49da36 new mtp: video integrity checker 2022-07-03 01:50:38 +02:00
ed
1c0071d9ce perf 2022-07-03 01:40:30 +02:00
ed
efded35c2e ffmpeg saying the fps is 1/0 yeah okay 2022-07-02 00:39:46 +02:00
ed
1d74240b9a ux: hide uploads table until something happens 2022-07-01 09:16:23 +02:00
ed
098184ff7b add write-only up2k ui simplifier 2022-07-01 00:55:36 +02:00
ed
4083533916 vt100 listing: reset color at eof 2022-06-29 22:41:51 +02:00
48 changed files with 1783 additions and 330 deletions

View File

@@ -10,8 +10,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* server only needs `py2.7` or `py3.3+`, all dependencies optional
* browse/upload with [IE4](#browser-support) / netscape4.0 on win3.11 (heh)
* *resumable* uploads need `firefox 34+` / `chrome 41+` / `safari 7+` for full speed
* code standard: `black`
* *resumable* uploads need `firefox 34+` / `chrome 41+` / `safari 7+`
try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running from a basement in finland
@@ -45,7 +44,7 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
* [tabs](#tabs) - the main tabs in the ui
* [hotkeys](#hotkeys) - the browser has the following hotkeys
* [navpane](#navpane) - switching between breadcrumbs or navpane
* [thumbnails](#thumbnails) - press `g` to toggle grid-view instead of the file listing
* [thumbnails](#thumbnails) - press `g` or `田` to toggle grid-view instead of the file listing
* [zip downloads](#zip-downloads) - download folders (or file selections) as `zip` or `tar` files
* [uploading](#uploading) - drag files/folders into the web-browser to upload
* [file-search](#file-search) - dropping files into the browser also lets you see if they exist on the server
@@ -103,7 +102,7 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
download **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** and you're all set!
running the sfx without arguments (for example doubleclicking it on Windows) will give everyone read/write access to the current folder; see `-h` for help if you want [accounts and volumes](#accounts-and-volumes) etc
running the sfx without arguments (for example doubleclicking it on Windows) will give everyone read/write access to the current folder; you may want [accounts and volumes](#accounts-and-volumes)
some recommended options:
* `-e2dsa` enables general [file indexing](#file-indexing)
@@ -111,7 +110,7 @@ some recommended options:
* `-v /mnt/music:/music:r:rw,foo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, and read-write for user `foo`, password `bar`
* replace `:r:rw,foo` with `:r,foo` to only make the folder readable by `foo` and nobody else
* see [accounts and volumes](#accounts-and-volumes) for the syntax and other permissions (`r`ead, `w`rite, `m`ove, `d`elete, `g`et)
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access (see `--help-ls`)
### on servers
@@ -169,7 +168,7 @@ feature summary
* download
* ☑ single files in browser
* ☑ [folders as zip / tar files](#zip-downloads)
* ☑ FUSE client (read-only)
*[FUSE client](https://github.com/9001/copyparty/tree/hovudstraum/bin#copyparty-fusepy) (read-only)
* browser
* ☑ [navpane](#navpane) (directory tree sidebar)
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
@@ -205,6 +204,7 @@ project goals / philosophy
* inverse linux philosophy -- do all the things, and do an *okay* job
* quick drop-in service to get a lot of features in a pinch
* there are probably [better alternatives](https://github.com/awesome-selfhosted/awesome-selfhosted) if you have specific/long-term needs
* but the resumable multithreaded uploads are p slick ngl
* run anywhere, support everything
* as many web-browsers and python versions as possible
* every browser should at least be able to browse, download, upload files
@@ -243,7 +243,7 @@ some improvement ideas
## general bugs
* Windows: if the up2k db is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
* Windows: if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
* probably more, pls let me know
@@ -275,7 +275,7 @@ some improvement ideas
* you can also do this with linux filesystem permissions; `chmod 111 music` will make it possible to access files and folders inside the `music` folder but not list the immediate contents -- also works with other software, not just copyparty
* can I make copyparty download a file to my server if I give it a URL?
* not officially, but there is a [terrible hack](https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/wget.py) which makes it possible
* not really, but there is a [terrible hack](https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/wget.py) which makes it possible
# accounts and volumes
@@ -283,6 +283,8 @@ some improvement ideas
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if logged in as admin)
a quick summary can be seen using `--help-accounts`
configuring accounts/volumes with arguments:
* `-a usr:pwd` adds account `usr` with password `pwd`
* `-v .::r` adds current-folder `.` as the webroot, `r`eadable by anyone
@@ -339,7 +341,7 @@ the browser has the following hotkeys (always qwerty)
* `I/K` prev/next folder
* `M` parent folder (or unexpand current)
* `V` toggle folders / textfiles in the navpane
* `G` toggle list / [grid view](#thumbnails)
* `G` toggle list / [grid view](#thumbnails) -- same as `田` bottom-right
* `T` toggle thumbnails / icons
* `ESC` close various things
* `ctrl-X` cut selected files/folders
@@ -364,6 +366,7 @@ the browser has the following hotkeys (always qwerty)
* when viewing images / playing videos:
* `J/L, Left/Right` prev/next file
* `Home/End` first/last file
* `F` toggle fullscreen
* `S` toggle selection
* `R` rotate clockwise (shift=ccw)
* `Y` download file
@@ -371,10 +374,11 @@ the browser has the following hotkeys (always qwerty)
* videos:
* `U/O` skip 10sec back/forward
* `P/K/Space` play/pause
* `F` fullscreen
* `C` continue playing next video
* `V` loop
* `M` mute
* `C` continue playing next video
* `V` loop entire file
* `[` loop range (start)
* `]` loop range (end)
* when the navpane is open:
* `A/D` adjust tree width
* in the [grid view](#thumbnails):
@@ -406,7 +410,7 @@ click the `🌲` or pressing the `B` hotkey to toggle between breadcrumbs path (
## thumbnails
press `g` to toggle grid-view instead of the file listing, and `t` toggles icons / thumbnails
press `g` or `田` to toggle grid-view instead of the file listing and `t` toggles icons / thumbnails
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png)
@@ -448,13 +452,13 @@ you can also zip a selection of files or folders by clicking them in the browser
## uploading
drag files/folders into the web-browser to upload
drag files/folders into the web-browser to upload (or use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#up2kpy))
this initiates an upload using `up2k`; there are two uploaders available:
* `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
* `[🚀] up2k`, the fancy one
* `[🚀] up2k`, the good / fancy one
you can also undo/delete uploads by using `[🧯]` [unpost](#unpost)
NB: you can undo/delete your own uploads with `[🧯]` [unpost](#unpost)
up2k has several advantages:
* you can drop folders into the browser (files are added recursively)
@@ -466,7 +470,7 @@ up2k has several advantages:
* much higher speeds than ftp/scp/tarpipe on some internet connections (mainly american ones) thanks to parallel connections
* the last-modified timestamp of the file is preserved
see [up2k](#up2k) for details on how it works
see [up2k](#up2k) for details on how it works, or watch a [demo video](https://a.ocv.me/pub/demo/pics-vids/#gf-0f6f5c0d)
![copyparty-upload-fs8](https://user-images.githubusercontent.com/241032/129635371-48fc54ca-fa91-48e3-9b1d-ba413e4b68cb.png)
@@ -477,8 +481,8 @@ see [up2k](#up2k) for details on how it works
the up2k UI is the epitome of polished inutitive experiences:
* "parallel uploads" specifies how many chunks to upload at the same time
* `[🏃]` analysis of other files should continue while one is uploading
* `[🥔]` shows a simpler UI for faster uploads from slow devices
* `[💭]` ask for confirmation before files are added to the queue
* `[💤]` sync uploading between other copyparty browser-tabs so only one is active
* `[🔎]` switch between upload and [file-search](#file-search) mode
* ignore `[🔎]` if you add files by dragging them into the browser
@@ -490,7 +494,7 @@ and then theres the tabs below it,
* plus up to 3 entries each from `[done]` and `[que]` for context
* `[que]` is all the files that are still queued
note that since up2k has to read each file twice, `[🎈 bup]` can *theoretically* be up to 2x faster in some extreme cases (files bigger than your ram, combined with an internet connection faster than the read-speed of your HDD, or if you're uploading from a cuo2duo)
note that since up2k has to read each file twice, `[🎈] bup` can *theoretically* be up to 2x faster in some extreme cases (files bigger than your ram, combined with an internet connection faster than the read-speed of your HDD, or if you're uploading from a cuo2duo)
if you are resuming a massive upload and want to skip hashing the files which already finished, you can enable `turbo` in the `[⚙️] config` tab, but please read the tooltip on that button
@@ -601,7 +605,7 @@ and there are *two* editors
* get a plaintext file listing by adding `?ls=t` to a URL, or a compact colored one with `?ls=v` (for unix terminals)
* 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 [./contrib/media-osd-bgone.ps1](contrib/#media-osd-bgoneps1)
* click the bottom-left `π` to open a javascript prompt for debugging
@@ -624,7 +628,9 @@ 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
* name: `demetori styx` gives you [good stuff](https://www.youtube.com/watch?v=zGh0g14ZJ8I&list=PL3A147BD151EE5218&index=9)
add the argument `-e2ts` to also scan/index tags from music files, which brings us over to:
the `raw` field allows for more complex stuff such as `( tags like *nhato* or tags like *taishi* ) and ( not tags like *nhato* or not tags like *taishi* )` which finds all songs by either nhato or taishi, excluding collabs (terrible example, why would you do that)
for the above example to work, add the commandline argument `-e2ts` to also scan/index tags from music files, which brings us over to:
# server config
@@ -658,8 +664,11 @@ through arguments:
* `-e2t` enables metadata indexing on upload
* `-e2ts` also scans for tags in all files that don't have tags yet
* `-e2tsr` also deletes all existing tags, doing a full reindex
* `-e2v` verfies file integrity at startup, comparing hashes from the db
* `-e2vu` patches the database with the new hashes from the filesystem
* `-e2vp` panics and kills copyparty instead
the same arguments can be set as volume flags, in addition to `d2d`, `d2ds`, `d2t`, `d2ts` for disabling:
the same arguments can be set as volume flags, in addition to `d2d`, `d2ds`, `d2t`, `d2ts`, `d2v` for disabling:
* `-v ~/music::r:c,e2dsa,e2tsr` does a full reindex of everything on startup
* `-v ~/music::r:c,d2d` disables **all** indexing, even if any `-e2*` are on
* `-v ~/music::r:c,d2t` disables all `-e2t*` (tags), does not affect `-e2d*`
@@ -771,27 +780,32 @@ see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copy
provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
copyparty can invoke external programs to collect additional metadata for files using `mtp` (either as argument or volume flag), there is a default timeout of 30sec
copyparty can invoke external programs to collect additional metadata for files using `mtp` (either as argument or volume flag), there is a default timeout of 30sec, and only files which contain audio get analyzed by default (see ay/an/ad below)
* `-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,`)
* `-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 (default), `an` only do non-audio files, or `ad` do all files (d as in dontcare)
* "audio file" also means videos btw, as long as there is an audio stream
* `-mtp ext=an,~/bin/file-ext.py` runs `~/bin/file-ext.py` to get the `ext` tag only if file is not audio (`an`)
* `-mtp arch,built,ver,orig=an,eexe,edll,~/bin/exe.py` runs `~/bin/exe.py` to get properties about windows-binaries only if file is not audio (`an`) and file extension is exe or dll
you can control how the parser is killed if it times out with option `kt` killing the entire process tree (default), `km` just the main process, or `kn` let it continue running until copyparty is terminated
if something doesn't work, try `--mtag-v` for verbose error messages
## upload events
trigger a script/program on each upload like so:
```
-v /mnt/inc:inc:w:c,mte=+a1:c,mtp=a1=ad,/usr/bin/notify-send
-v /mnt/inc:inc:w:c,mte=+x1:c,mtp=x1=ad,kn,/usr/bin/notify-send
```
so filesystem location `/mnt/inc` shared at `/inc`, write-only for everyone, appending `a1` to the list of tags to index, and using `/usr/bin/notify-send` to "provide" that tag
so filesystem location `/mnt/inc` shared at `/inc`, write-only for everyone, appending `x1` to the list of tags to index (`mte`), and using `/usr/bin/notify-send` to "provide" tag `x1` for any filetype (`ad`) with kill-on-timeout disabled (`kn`)
that'll run the command `notify-send` with the path to the uploaded file as the first and only argument (so on linux it'll show a notification on-screen)
@@ -838,8 +852,17 @@ see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where
## complete examples
* read-only music server with bpm and key scanning
`python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts -mtp .bpm=f,audio-bpm.py -mtp key=f,audio-key.py`
* read-only music server
`python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts --no-robots --force-js --theme 2`
* ...with bpm and key scanning
`-mtp .bpm=f,audio-bpm.py -mtp key=f,audio-key.py`
* ...with a read-write folder for `kevin` whose password is `okgo`
`-a kevin:okgo -v /mnt/nas/inc:/inc:rw,kevin`
* ...with logging to disk
`-lo log/cpp-%Y-%m%d-%H%M%S.txt.xz`
# browser support
@@ -945,17 +968,25 @@ quick outline of the up2k protocol, see [uploading](#uploading) for the web-clie
up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well (thanks to tls also functioning as an integrity check)
regarding the frequent server log message during uploads;
`6.0M 106M/s 2.77G 102.9M/s n948 thank 4/0/3/1 10042/7198`
* this chunk was `6 MiB`, uploaded at `106 MiB/s`
* on this http connection, `2.77 GiB` transferred, `102.9 MiB/s` average, `948` chunks handled
* client says `4` uploads OK, `0` failed, `3` busy, `1` queued, `10042 MiB` total size, `7198 MiB` left
## why chunk-hashes
a single sha512 would be better, right?
this is due to `crypto.subtle` not providing a streaming api (or the option to seed the sha512 hasher with a starting hash)
this is due to `crypto.subtle` [not yet](https://github.com/w3c/webcrypto/issues/73) providing a streaming api (or the option to seed the sha512 hasher with a starting hash)
as a result, the hashes are much less useful than they could have been (search the server by sha512, provide the sha512 in the response http headers, ...)
hashwasm would solve the streaming issue but reduces hashing speed for sha512 (xxh128 does 6 GiB/s), and it would make old browsers and [iphones](https://bugs.webkit.org/show_bug.cgi?id=228552) unsupported
* blake2 might be a better choice since xxh is non-cryptographic, but that gets ~15 MiB/s on slower androids
# performance
@@ -1259,10 +1290,7 @@ also builds the sfx so skip the sfx section above
in the `scripts` folder:
* run `make -C deps-docker` to build all dependencies
* `git tag v1.2.3 && git push origin --tags`
* upload to pypi with `make-pypi-release.(sh|bat)`
* create github release with `make-tgz-release.sh`
* create sfx with `make-sfx.sh`
* run `./rls.sh 1.2.3` which uploads to pypi + creates github release + sfx
# todo
@@ -1289,7 +1317,7 @@ roughly sorted by priority
* up2k partials ui
* feels like there isn't much point
* cache sha512 chunks on client
* too dangerous
* too dangerous -- overtaken by turbo mode
* comment field
* nah
* look into android thumbnail cache file format

View File

@@ -17,7 +17,7 @@ except:
"""
calculates various checksums for uploads,
usage: -mtp crc32,md5,sha1,sha256b=bin/mtag/cksum.py
usage: -mtp crc32,md5,sha1,sha256b=ad,bin/mtag/cksum.py
"""

View File

@@ -89,4 +89,7 @@ def main():
if __name__ == "__main__":
main()
try:
main()
except:
pass

76
bin/mtag/rclone-upload.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python
import json
import os
import subprocess as sp
import sys
import time
try:
from copyparty.util import fsenc
except:
def fsenc(p):
return p.encode("utf-8")
_ = r"""
first checks the tag "vidchk" which must be "ok" to continue,
then uploads all files to some cloud storage (RCLONE_REMOTE)
and DELETES THE ORIGINAL FILES if rclone returns 0 ("success")
deps:
rclone
usage:
-mtp x2=t43200,ay,p2,bin/mtag/rclone-upload.py
explained:
t43200: timeout 12h
ay: only process files which contain audio (including video with audio)
p2: set priority 2 (after vidchk's suggested priority of 1),
so the output of vidchk will be passed in here
complete usage example as vflags along with vidchk:
-vsrv/vidchk:vidchk:r:rw,ed:c,e2dsa,e2ts,mtp=vidchk=t600,p,bin/mtag/vidchk.py:c,mtp=rupload=t43200,ay,p2,bin/mtag/rclone-upload.py:c,mte=+vidchk,rupload
setup: see https://rclone.org/drive/
if you wanna use this script standalone / separately from copyparty,
either set CONDITIONAL_UPLOAD False or provide the following stdin:
{"vidchk":"ok"}
"""
RCLONE_REMOTE = "notmybox"
CONDITIONAL_UPLOAD = True
def main():
if CONDITIONAL_UPLOAD:
fp = sys.argv[1]
zb = sys.stdin.buffer.read()
zs = zb.decode("utf-8", "replace")
md = json.loads(zs)
chk = md.get("vidchk", None)
if chk != "ok":
print(f"vidchk={chk}", file=sys.stderr)
sys.exit(1)
dst = f"{RCLONE_REMOTE}:".encode("utf-8")
cmd = [b"rclone", b"copy", b"--", fsenc(fp), dst]
t0 = time.time()
try:
sp.check_call(cmd)
except:
print("rclone failed", file=sys.stderr)
sys.exit(1)
print(f"{time.time() - t0:.1f} sec")
os.unlink(fsenc(fp))
if __name__ == "__main__":
main()

View File

@@ -16,7 +16,7 @@ goes without saying, but this is HELLA DANGEROUS,
GIVES RCE TO ANYONE WHO HAVE UPLOAD PERMISSIONS
example copyparty config to use this:
--urlform save,get -v.::w:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,bin/mtag/very-bad-idea.py
--urlform save,get -v.::w:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,bin/mtag/very-bad-idea.py
recommended deps:
apt install xdotool libnotify-bin
@@ -64,7 +64,7 @@ EOF
chmod 755 /usr/local/bin/chromium-browser
# start the server (note: replace `-v.::rw:` with `-v.::w:` to disallow retrieving uploaded stuff)
cd ~/Downloads; python3 copyparty-sfx.py --urlform save,get -v.::rw:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,very-bad-idea.py
cd ~/Downloads; python3 copyparty-sfx.py --urlform save,get -v.::rw:c,e2d,e2t,mte=+a1:c,mtp=a1=ad,kn,very-bad-idea.py
"""

118
bin/mtag/vidchk.py Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import json
import re
import sys
import subprocess as sp
try:
from copyparty.util import fsenc
except:
def fsenc(p):
return p.encode("utf-8")
_ = r"""
inspects video files for errors and such
plus stores a bunch of metadata to filename.ff.json
usage:
-mtp vidchk=t600,ay,p,bin/mtag/vidchk.py
explained:
t600: timeout 10min
ay: only process files which contain audio (including video with audio)
p: set priority 1 (lowest priority after initial ffprobe/mutagen for base tags),
makes copyparty feed base tags into this script as json
if you wanna use this script standalone / separately from copyparty,
provide the video resolution on stdin as json: {"res":"1920x1080"}
"""
FAST = True # parse entire file at container level
# FAST = False # fully decode audio and video streams
# warnings to ignore
harmless = re.compile("^Unsupported codec with id ")
def wfilter(lines):
return [x for x in lines if not harmless.search(x)]
def errchk(so, se, rc):
if rc:
err = (so + se).decode("utf-8", "replace").split("\n", 1)
err = wfilter(err) or err
return f"ERROR {rc}: {err[0]}"
if se:
err = se.decode("utf-8", "replace").split("\n", 1)
err = wfilter(err)
if err:
return f"Warning: {err[0]}"
return None
def main():
fp = sys.argv[1]
zb = sys.stdin.buffer.read()
zs = zb.decode("utf-8", "replace")
md = json.loads(zs)
try:
w, h = [int(x) for x in md["res"].split("x")]
if not w + h:
raise Exception()
except:
return "could not determine resolution"
# grab streams/format metadata + 2 seconds of frames at the start and end
zs = "ffprobe -hide_banner -v warning -of json -show_streams -show_format -show_packets -show_data_hash crc32 -read_intervals %+2,999999%+2"
cmd = zs.encode("ascii").split(b" ") + [fsenc(fp)]
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
so, se = p.communicate()
# spaces to tabs, drops filesize from 69k to 48k
so = b"\n".join(
[
b"\t" * int((len(x) - len(x.lstrip())) / 4) + x.lstrip()
for x in (so or b"").split(b"\n")
]
)
with open(fsenc(f"{fp}.ff.json"), "wb") as f:
f.write(so)
err = errchk(so, se, p.returncode)
if err:
return err
if min(w, h) < 1080:
return "resolution too small"
zs = (
"ffmpeg -y -hide_banner -nostdin -v warning"
+ " -err_detect +crccheck+bitstream+buffer+careful+compliant+aggressive+explode"
" -xerror -i"
)
cmd = zs.encode("ascii").split(b" ") + [fsenc(fp)]
if FAST:
zs = "-c copy -f null -"
else:
zs = "-vcodec rawvideo -acodec pcm_s16le -f null -"
cmd += zs.encode("ascii").split(b" ")
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
so, se = p.communicate()
return errchk(so, se, p.returncode)
if __name__ == "__main__":
print(main() or "ok")

View File

@@ -22,6 +22,9 @@ however if your copyparty is behind a reverse-proxy, you may want to use [`share
* `URL`: full URL to the root folder (with trailing slash) followed by `$regex:1|1$`
* `pw`: password (remove `Parameters` if anon-write)
### [`media-osd-bgone.ps1`](media-osd-bgone.ps1)
* disables the [windows OSD popup](https://user-images.githubusercontent.com/241032/122821375-0e08df80-d2dd-11eb-9fd9-184e8aacf1d0.png) (the thing on the left) which appears every time you hit media hotkeys to adjust volume or change song while playing music with the copyparty web-ui, or most other audio players really
### [`explorer-nothumbs-nofoldertypes.reg`](explorer-nothumbs-nofoldertypes.reg)
* disables thumbnails and folder-type detection in windows explorer
* makes it way faster (especially for slow/networked locations (such as copyparty-fuse))

104
contrib/media-osd-bgone.ps1 Normal file
View File

@@ -0,0 +1,104 @@
# media-osd-bgone.ps1: disable media-control OSD on win10do
# v1.1, 2021-06-25, ed <irc.rizon.net>, MIT-licensed
# https://github.com/9001/copyparty/blob/hovudstraum/contrib/media-osd-bgone.ps1
#
# locates the first window that looks like the media OSD and minimizes it;
# doing this once after each reboot should do the trick
# (adjust the width/height filter if it doesn't work)
#
# ---------------------------------------------------------------------
#
# tip: save the following as "media-osd-bgone.bat" next to this script:
# start cmd /c "powershell -command ""set-executionpolicy -scope process bypass; .\media-osd-bgone.ps1"" & ping -n 2 127.1 >nul"
#
# then create a shortcut to that bat-file and move the shortcut here:
# %appdata%\Microsoft\Windows\Start Menu\Programs\Startup
#
# and now this will autorun on bootup
Add-Type -TypeDefinition @"
using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace A {
public class B : Control {
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError=true)]
static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int x;
public int y;
public int x2;
public int y2;
}
bool fa() {
RECT r;
IntPtr it = IntPtr.Zero;
while ((it = FindWindowEx(IntPtr.Zero, it, "NativeHWNDHost", "")) != IntPtr.Zero) {
if (FindWindowEx(it, IntPtr.Zero, "DirectUIHWND", "") == IntPtr.Zero)
continue;
if (!GetWindowRect(it, out r))
continue;
int w = r.x2 - r.x + 1;
int h = r.y2 - r.y + 1;
Console.WriteLine("[*] hwnd {0:x} @ {1}x{2} sz {3}x{4}", it, r.x, r.y, w, h);
if (h != 141)
continue;
ShowWindow(it, 6);
Console.WriteLine("[+] poof");
return true;
}
return false;
}
void fb() {
keybd_event((byte)Keys.VolumeMute, 0, 0, 0);
keybd_event((byte)Keys.VolumeMute, 0, 2, 0);
Thread.Sleep(500);
keybd_event((byte)Keys.VolumeMute, 0, 0, 0);
keybd_event((byte)Keys.VolumeMute, 0, 2, 0);
while (true) {
if (fa()) {
break;
}
Console.WriteLine("[!] not found");
Thread.Sleep(1000);
}
this.Invoke((MethodInvoker)delegate {
Application.Exit();
});
}
public void Run() {
Console.WriteLine("[+] hi");
new Thread(new ThreadStart(fb)).Start();
Application.Run();
Console.WriteLine("[+] bye");
}
}
}
"@ -ReferencedAssemblies System.Windows.Forms
(New-Object -TypeName A.B).Run()

View File

@@ -11,6 +11,15 @@ save one of these as `.epilogue.html` inside a folder to customize it:
## example browser-js
point `--js-browser` to one of these by URL:
* [`minimal-up2k.js`](minimal-up2k.js) is similar to the above `minimal-up2k.html` except it applies globally to all write-only folders
* [`up2k-hooks.js`](up2k-hooks.js) lets you specify a ruleset for files to skip uploading
* [`up2k-hook-ytid.js`](up2k-hook-ytid.js) is a more specific example checking youtube-IDs against some API
## example browser-css
point `--css-browser` to one of these by URL:

View File

@@ -7,7 +7,7 @@
/* make the up2k ui REALLY minimal by hiding a bunch of stuff: */
#ops, #tree, #path, #wrap>h2:last-child, /* main tabs and navigators (tree/breadcrumbs) */
#ops, #tree, #path, #epi+h2, /* main tabs and navigators (tree/breadcrumbs) */
#u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), /* most of the config options */

View File

@@ -0,0 +1,59 @@
/*
makes the up2k ui REALLY minimal by hiding a bunch of stuff
almost the same as minimal-up2k.html except this one...:
-- applies to every write-only folder when used with --js-browser
-- only applies if javascript is enabled
-- doesn't hide the total upload ETA display
-- looks slightly better
*/
var u2min = `
<style>
#ops, #path, #tree, #files, #epi+div+h2,
#u2conf td.c+.c, #u2cards, #srch_dz, #srch_zd {
display: none !important;
}
#u2conf {margin:5em auto 0 auto !important}
#u2conf.ww {width:70em}
#u2conf.w {width:50em}
#u2conf.w .c,
#u2conf.w #u2btn_cw {text-align:left}
#u2conf.w #u2btn_cw {width:70%}
#u2etaw {margin:3em auto}
#u2etaw.w {
text-align: center;
margin: -3.5em auto 5em auto;
}
#u2etaw.w #u2etas {margin-right:-37em}
#u2etaw.w #u2etas.o {margin-top:-2.2em}
#u2etaw.ww {margin:-1em auto}
#u2etaw.ww #u2etas {padding-left:4em}
#u2etas {
background: none !important;
border: none !important;
}
#wrap {margin-left:2em !important}
.logue {
border: none !important;
margin: 2em auto !important;
}
.logue:before {content:'' !important}
</style>
<a href="#" onclick="this.parentNode.innerHTML='';">show advanced options</a>
`;
if (!has(perms, 'read')) {
var e2 = mknod('div');
e2.innerHTML = u2min;
ebi('wrap').insertBefore(e2, QS('#epi+h2'));
}

View File

@@ -0,0 +1,71 @@
// way more specific example --
// assumes all files dropped into the uploader have a youtube-id somewhere in the filename,
// locates the youtube-ids and passes them to an API which returns a list of IDs which should be uploaded
//
// assumes copyparty is behind nginx as /ytq is a standalone service which must be rproxied in place
function up2k_namefilter(good_files, nil_files, bad_files, hooks) {
var filenames = [],
file_lists = [good_files, nil_files, bad_files];
for (var lst of file_lists)
for (var ent of lst)
filenames.push(ent[1]);
var yt_ids = new Set();
for (var lst of file_lists)
for (var ent of lst) {
var m, name = ent[1];
while (true) {
// some ytdl fork did %(title)-%(id).%(ext) ...
m = /(?:^|[^\w])([\w-]{11})(?:$|[^\w-])/.exec(name);
if (!m)
break;
yt_ids.add(m[1]);
name = name.replace(m[1], '');
}
}
toast.inf(5, `running query for ${yt_ids.size} videos...`);
var xhr = new XHR();
xhr.open('POST', '/ytq', true);
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.onload = xhr.onerror = function () {
if (this.status != 200)
return toast.err(0, `sorry, database query failed ;_;\n\nplease let us know so we can look at it, thx!!\n\nerror ${this.status}: ${(this.response && this.response.err) || this.responseText}`);
var new_lists = [],
ptn = new RegExp(this.responseText.trim().split('\n').join('|') || '\n'),
nothing_to_do = true,
n_skip = 0;
for (var lst of file_lists) {
var keep = [];
new_lists.push(keep);
for (var ent of lst)
if (ptn.exec(ent[1]))
keep.push(ent);
else
n_skip++;
if (keep.length)
nothing_to_do = false;
}
if (nothing_to_do)
return modal.alert('Good news -- turns out we already have all those videos.\n\nBut thank you for checking in!');
else if (n_skip)
toast.inf(0, `skipped ${n_skip} files which already exist on the server`);
[good_files, nil_files, bad_files] = new_lists;
hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
};
xhr.send(Array.from(yt_ids).join('\n'));
}
up2k_hooks.push(function () {
up2k.gotallfiles.unshift(up2k_namefilter);
});

View File

@@ -0,0 +1,45 @@
// hooks into up2k
function up2k_namefilter(good_files, nil_files, bad_files, hooks) {
// is called when stuff is dropped into the browser,
// after iterating through the directory tree and discovering all files,
// before the upload confirmation dialogue is shown
// good_files will successfully upload
// nil_files are empty files and will show an alert in the final hook
// bad_files are unreadable and cannot be uploaded
var file_lists = [good_files, nil_files, bad_files];
// build a list of filenames
var filenames = [];
for (var lst of file_lists)
for (var ent of lst)
filenames.push(ent[1]);
toast.inf(5, "running database query...");
// simulate delay while passing the list to some api for checking
setTimeout(function () {
// only keep webm files as an example
var new_lists = [];
for (var lst of file_lists) {
var keep = [];
new_lists.push(keep);
for (var ent of lst)
if (/\.webm$/.test(ent[1]))
keep.push(ent);
}
// finally, call the next hook in the chain
[good_files, nil_files, bad_files] = new_lists;
hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
}, 1000);
}
// register
up2k_hooks.push(function () {
up2k.gotallfiles.unshift(up2k_namefilter);
});

View File

@@ -275,7 +275,7 @@ def disable_quickedit() -> None:
raise ctypes.WinError(err) # type: ignore
return args
k32 = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore
k32 = ctypes.WinDLL(str("kernel32"), use_last_error=True) # type: ignore
if PY2:
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
@@ -311,7 +311,7 @@ def disable_quickedit() -> None:
cmode(True, mode | 4)
def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
def run_argparse(argv: list[str], formatter: Any, retry: bool) -> argparse.Namespace:
ap = argparse.ArgumentParser(
formatter_class=formatter,
prog="copyparty",
@@ -394,6 +394,7 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
\033[36md2ts\033[35m disables metadata collection for existing files
\033[36md2ds\033[35m disables onboot indexing, overrides -e2ds*
\033[36md2t\033[35m disables metadata collection, overrides -e2t*
\033[36md2v\033[35m disables file verification, overrides -e2v*
\033[36md2d\033[35m disables all database stuff, overrides -e2*
\033[36mnohash=\\.iso$\033[35m skips hashing file contents if path matches *.iso
\033[36mnoidx=\\.iso$\033[35m fully ignores the contents at paths matching *.iso
@@ -483,6 +484,7 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead")
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; 0 = off and warn if enabled, 1 = off, 2 = on, 3 = on and disable datecheck")
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; s=smallest-first, n=alphabetical, fs=force-s, fn=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
ap2 = ap.add_argument_group('network options')
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
@@ -585,6 +587,9 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplocation")
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets -e2d")
ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets -e2ds")
ap2.add_argument("-e2v", action="store_true", help="verify file integrity; rehash all files and compare with db")
ap2.add_argument("-e2vu", action="store_true", help="on hash mismatch: update the database with the new hash")
ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans")
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans")
@@ -611,6 +616,7 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language")
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="favicon text [ foreground [ background ] ], set blank to disable")
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
@@ -682,12 +688,14 @@ def main(argv: Optional[list[str]] = None) -> None:
except:
pass
for fmtr in [RiceFormatter, Dodge11874, BasicDodge11874]:
retry = False
for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
try:
al = run_argparse(argv, fmtr)
al = run_argparse(argv, fmtr, retry)
except SystemExit:
raise
except:
retry = True
lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex()))
assert al
@@ -752,6 +760,12 @@ def main(argv: Optional[list[str]] = None) -> None:
except:
raise Exception("invalid value for -p")
for arg, kname, okays in [["--u2sort", "u2sort", "s n fs fn"]]:
val = unicode(getattr(al, kname))
if val not in okays.split():
zs = "argument {} cannot be '{}'; try one of these: {}"
raise Exception(zs.format(arg, val, okays))
if HAVE_SSL:
if al.ssl_ver:
configure_ssl_ver(al)

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 3, 3)
VERSION = (1, 3, 6)
CODENAME = "god dag"
BUILD_DT = (2022, 6, 27)
BUILD_DT = (2022, 7, 16)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -1008,7 +1008,7 @@ class AuthSrv(object):
if ptn:
vol.flags[vf] = re.compile(ptn)
for k in ["e2t", "e2ts", "e2tsr"]:
for k in ["e2t", "e2ts", "e2tsr", "e2v", "e2vu", "e2vp"]:
if getattr(self.args, k):
vol.flags[k] = True
@@ -1030,7 +1030,7 @@ class AuthSrv(object):
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
# d2d drops all database features for a volume
for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"]]:
for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
if not vol.flags.get(grp, False):
continue
@@ -1052,6 +1052,12 @@ class AuthSrv(object):
vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)}
for grp, rm in [["d2v", "e2v"]]:
if not vol.flags.get(grp, False):
continue
vol.flags = {k: v for k, v in vol.flags.items() if not k.startswith(rm)}
# verify tags mentioned by -mt[mp] are used by -mte
local_mtp = {}
local_only_mtp = {}

View File

@@ -2,6 +2,7 @@
from __future__ import print_function, unicode_literals
import ctypes
import os
import re
import time
@@ -21,6 +22,7 @@ class Fstab(object):
def __init__(self, log: RootLogger):
self.log_func = log
self.trusted = False
self.tab: Optional[VFS] = None
self.cache: dict[str, str] = {}
self.age = 0.0
@@ -49,6 +51,7 @@ class Fstab(object):
self.log(msg.format(path, fs, min_ex()), 3)
return fs
path = path.lstrip("/")
try:
return self.cache[path]
except:
@@ -92,21 +95,44 @@ class Fstab(object):
def relabel(self, path: str, nval: str) -> None:
assert self.tab
self.cache = {}
path = path.lstrip("/")
ptn = re.compile(r"^[^\\/]*")
vn, _ = self.tab._find(path)
vn, rem = self.tab._find(path)
if not self.trusted:
# no mtab access; have to build as we go
if "/" in rem:
self.tab.add("idk", os.path.join(vn.vpath, rem.split("/")[0]))
if rem:
self.tab.add(nval, path)
else:
vn.realpath = nval
return
visit = [vn]
while visit:
vn = visit.pop()
vn.realpath = ptn.sub(nval, vn.realpath)
visit.extend(list(vn.nodes.values()))
self.cache = {}
def get_unix(self, path: str) -> str:
if not self.tab:
self.build_tab()
try:
self.build_tab()
self.trusted = True
except:
# prisonparty or other restrictive environment
self.log("failed to build tab:\n{}".format(min_ex()), 3)
self.tab = VFS(self.log_func, "idk", "/", AXS(), {})
self.trusted = False
assert self.tab
return self.tab._find(path)[0].realpath.split("/")[0]
ret = self.tab._find(path)[0]
if self.trusted or path == ret.vpath:
return ret.realpath.split("/")[0]
else:
return "idk"
def get_w32(self, path: str) -> str:
# list mountpoints: fsutil fsinfo drives

View File

@@ -184,6 +184,8 @@ class HttpCli(object):
def j2s(self, name: str, **ka: Any) -> str:
tpl = self.conn.hsrv.j2[name]
ka["ts"] = self.conn.hsrv.cachebuster()
ka["lang"] = self.args.lang
ka["favico"] = self.args.favico
ka["svcname"] = self.args.doctitle
ka["html_head"] = self.html_head
return tpl.render(**ka) # type: ignore
@@ -545,6 +547,10 @@ class HttpCli(object):
static_path = os.path.join(E.mod, "web/", self.vpath[5:])
return self.tx_file(static_path)
if "cf_challenge" in self.uparam:
self.reply(self.j2s("cf").encode("utf-8", "replace"))
return True
if not self.can_read and not self.can_write and not self.can_get:
if self.vpath:
self.log("inaccessible: [{}]".format(self.vpath))
@@ -911,6 +917,9 @@ class HttpCli(object):
except:
raise Pebkac(422, "you POSTed invalid json")
# self.reply(b" DD" + b"oS Protection ", 503)
# return True
if "srch" in self.uparam or "srch" in body:
return self.handle_search(body)
@@ -1118,8 +1127,10 @@ class HttpCli(object):
except:
self.log("failed to utime ({}, {})".format(fin_path, times))
cinf = self.headers.get("x-up2k-stat", "")
spd = self._spd(post_sz)
self.log("{} thank".format(spd))
self.log("{:70} thank {}".format(spd, cinf))
self.reply(b"thank")
return True
@@ -1855,6 +1866,8 @@ class HttpCli(object):
"edit": "edit" in self.uparam,
"title": html_escape(self.vpath, crlf=True),
"lastmod": int(ts_md * 1000),
"lang": self.args.lang,
"favico": self.args.favico,
"have_emp": self.args.emp,
"md_chk_rate": self.args.mcr,
"md": boundary,
@@ -2218,7 +2231,8 @@ class HttpCli(object):
ret = json.dumps(ls)
mime = "application/json"
self.reply(ret.encode("utf-8", "replace") + b"\n", mime=mime)
ret += "\n\033[0m" if arg == "v" else "\n"
self.reply(ret.encode("utf-8", "replace"), mime=mime)
return True
def tx_browser(self) -> bool:
@@ -2398,10 +2412,10 @@ class HttpCli(object):
"readme": readme,
"title": html_escape(self.vpath, crlf=True),
"srv_info": srv_infot,
"lang": self.args.lang,
"dtheme": self.args.theme,
"themes": self.args.themes,
"turbolvl": self.args.turbo,
"u2sort": self.args.u2sort,
}
if self.args.js_browser:

View File

@@ -81,7 +81,7 @@ class HttpSrv(object):
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
self.j2 = {
x: env.get_template(x + ".html")
for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
for x in ["splash", "browser", "browser2", "msg", "md", "mde", "cf"]
}
self.prism = os.path.exists(os.path.join(E.mod, "web", "deps", "prism.js.gz"))

View File

@@ -10,7 +10,7 @@ import sys
from .__init__ import PY2, WINDOWS, unicode
from .bos import bos
from .util import REKOBO_LKEY, fsenc, retchk, runcmd, uncyg
from .util import REKOBO_LKEY, fsenc, min_ex, retchk, runcmd, uncyg
try:
from typing import Any, Union
@@ -44,7 +44,9 @@ class MParser(object):
self.timeout = 30
self.force = False
self.kill = "t" # tree; all children recursively
self.audio = "y"
self.pri = 0 # priority; higher = later
self.ext = []
while True:
@@ -66,6 +68,10 @@ class MParser(object):
self.audio = arg[1:] # [r]equire [n]ot [d]ontcare
continue
if arg.startswith("k"):
self.kill = arg[1:] # [t]ree [m]ain [n]one
continue
if arg == "f":
self.force = True
continue
@@ -78,6 +84,10 @@ class MParser(object):
self.ext.append(arg[1:])
continue
if arg.startswith("p"):
self.pri = int(arg[1:] or "1")
continue
raise Exception()
@@ -213,7 +223,10 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[
fps = ret[".fps"]
if "/" in fps:
fa, fb = fps.split("/")
fps = int(fa) * 1.0 / int(fb)
try:
fps = int(fa) * 1.0 / int(fb)
except:
fps = 9001
if fps < 1000 and fmt.get("format_name") not in ["image2", "png_pipe"]:
ret[".fps"] = round(fps, 3)
@@ -479,7 +492,9 @@ class MTag(object):
ret, md = ffprobe(abspath)
return self.normalize_tags(ret, md)
def get_bin(self, parsers: dict[str, MParser], abspath: str) -> dict[str, Any]:
def get_bin(
self, parsers: dict[str, MParser], abspath: str, oth_tags: dict[str, Any]
) -> dict[str, Any]:
if not bos.path.isfile(abspath):
return {}
@@ -489,14 +504,19 @@ class MTag(object):
env = os.environ.copy()
env["PYTHONPATH"] = pypath
ret = {}
for tagname, parser in parsers.items():
ret: dict[str, Any] = {}
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
try:
cmd = [parser.bin, abspath]
if parser.bin.endswith(".py"):
cmd = [sys.executable] + cmd
args = {"env": env, "timeout": parser.timeout}
args = {"env": env, "timeout": parser.timeout, "kill": parser.kill}
if parser.pri:
zd = oth_tags.copy()
zd.update(ret)
args["sin"] = json.dumps(zd).encode("utf-8", "replace")
if WINDOWS:
args["creationflags"] = 0x4000
@@ -518,6 +538,8 @@ class MTag(object):
if tag and tag in zj:
ret[tag] = zj[tag]
except:
pass
if self.args.mtag_v:
t = "mtag error: tagname {}, parser {}, file {} => {}"
self.log(t.format(tagname, parser.bin, abspath, min_ex()))
return ret

View File

@@ -358,6 +358,9 @@ class SvcHub(object):
print("nailed it", end="")
ret = self.retcode
except:
print("\033[31m[ error during shutdown ]\n{}\033[0m".format(min_ex()))
raise
finally:
if self.args.wintitle:
print("\033]0;\033\\", file=sys.stderr, end="")

View File

@@ -128,5 +128,8 @@ class ThumbCli(object):
if abort:
return None
if not bos.path.getsize(os.path.join(ptop, rem)):
return None
x = self.broker.ask("thumbsrv.get", ptop, rem, mtime, fmt)
return x.get() # type: ignore

View File

@@ -559,14 +559,15 @@ class ThumbSrv(object):
def clean(self, histpath: str) -> int:
ret = 0
for cat in ["th", "ac"]:
ret += self._clean(histpath, cat, "")
top = os.path.join(histpath, cat)
if not bos.path.isdir(top):
continue
ret += self._clean(cat, top)
return ret
def _clean(self, histpath: str, cat: str, thumbpath: str) -> int:
if not thumbpath:
thumbpath = os.path.join(histpath, cat)
def _clean(self, cat: str, thumbpath: str) -> int:
# self.log("cln {}".format(thumbpath))
exts = ["jpg", "webp"] if cat == "th" else ["opus", "caf"]
maxage = getattr(self.args, cat + "_maxage")
@@ -600,7 +601,7 @@ class ThumbSrv(object):
self.log("rm -rf [{}]".format(fp))
shutil.rmtree(fp, ignore_errors=True)
else:
self._clean(histpath, cat, fp)
ndirs += self._clean(cat, fp)
continue

View File

@@ -9,6 +9,7 @@ import math
import os
import re
import shutil
import signal
import stat
import subprocess as sp
import threading
@@ -35,6 +36,7 @@ from .util import (
quotep,
ren_open,
rmdirs,
rmdirs_up,
s2hms,
s3dec,
s3enc,
@@ -67,12 +69,20 @@ class Dbw(object):
class Mpqe(object):
def __init__(self, mtp: dict[str, MParser], entags: set[str], w: str, abspath: str):
def __init__(
self,
mtp: dict[str, MParser],
entags: set[str],
w: str,
abspath: str,
oth_tags: dict[str, Any],
):
# mtp empty = mtag
self.mtp = mtp
self.entags = entags
self.w = w
self.abspath = abspath
self.oth_tags = oth_tags
class Up2k(object):
@@ -86,6 +96,7 @@ class Up2k(object):
self.r_hash = re.compile("^[0-9a-zA-Z_-]{44}$")
self.gid = 0
self.stop = False
self.mutex = threading.Lock()
self.pp: Optional[ProgressPrinter] = None
self.rescan_cond = threading.Condition()
@@ -106,6 +117,7 @@ class Up2k(object):
self.pending_tags: list[tuple[set[str], str, str, dict[str, Any]]] = []
self.hashq: Queue[tuple[str, str, str, str, float]] = Queue()
self.tagq: Queue[tuple[str, str, str, str]] = Queue()
self.tag_event = threading.Condition()
self.n_hashq = 0
self.n_tagq = 0
self.mpool_used = False
@@ -405,6 +417,9 @@ class Up2k(object):
# e2ds(a) volumes first
for vol in vols:
if self.stop:
break
en: set[str] = set()
if "mte" in vol.flags:
en = set(vol.flags["mte"].split(","))
@@ -420,16 +435,45 @@ class Up2k(object):
if vac:
need_vac[vol] = True
if "e2ts" not in vol.flags:
t = "online, idle"
else:
if "e2v" in vol.flags:
t = "online (integrity-check pending)"
elif "e2ts" in vol.flags:
t = "online (tags pending)"
else:
t = "online, idle"
self.volstate[vol.vpath] = t
# file contents verification
for vol in vols:
if self.stop:
break
if "e2v" not in vol.flags:
continue
t = "online (verifying integrity)"
self.volstate[vol.vpath] = t
self.log("{} [{}]".format(t, vol.realpath))
nmod = self._verify_integrity(vol)
if nmod:
self.log("modified {} entries in the db".format(nmod), 3)
need_vac[vol] = True
if "e2ts" in vol.flags:
t = "online (tags pending)"
else:
t = "online, idle"
self.volstate[vol.vpath] = t
# open the rest + do any e2ts(a)
needed_mutagen = False
for vol in vols:
if self.stop:
break
if "e2ts" not in vol.flags:
continue
@@ -454,6 +498,9 @@ class Up2k(object):
cur.connection.commit()
cur.execute("vacuum")
if self.stop:
return False
self.pp.end = True
msg = "{} volumes in {:.2f} sec"
@@ -516,7 +563,9 @@ class Up2k(object):
if vpath:
vpath += "/"
self.log("/{} {}".format(vpath, " ".join(sorted(a))), "35")
zs = " ".join(sorted(a))
zs = zs.replace("30mre.compile(", "30m(") # nohash
self.log("/{} {}".format(vpath, zs), "35")
reg = {}
drp = None
@@ -633,6 +682,9 @@ class Up2k(object):
gl = sorted(g)
inames = {x[0]: 1 for x in gl}
for iname, inf in gl:
if self.stop:
return -1
abspath = os.path.join(cdir, iname)
if rei and rei.search(abspath):
continue
@@ -711,7 +763,9 @@ class Up2k(object):
self.log("file: {}".format(abspath))
try:
hashes = self._hashlist_from_file(abspath)
hashes = self._hashlist_from_file(
abspath, "a{}, ".format(self.pp.n)
)
except Exception as ex:
self.log("hash: {} @ [{}]".format(repr(ex), abspath))
continue
@@ -728,6 +782,9 @@ class Up2k(object):
db.n = 0
db.t = time.time()
if self.stop:
return -1
# drop missing files
rd = cdir[len(top) + 1 :].strip("/")
if WINDOWS:
@@ -788,6 +845,106 @@ class Up2k(object):
return n_rm
def _verify_integrity(self, vol: VFS) -> int:
"""expensive; blocks database access until finished"""
ptop = vol.realpath
assert self.pp and self.mtag
cur = self.cur[ptop]
rei = vol.flags.get("noidx")
reh = vol.flags.get("nohash")
e2vu = "e2vu" in vol.flags
e2vp = "e2vp" in vol.flags
excl = [
d[len(vol.vpath) :].lstrip("/")
for d in self.asrv.vfs.all_vols
if d != vol.vpath and (d.startswith(vol.vpath + "/") or not vol.vpath)
]
qexa: list[str] = []
pexa: list[str] = []
for vpath in excl:
qexa.append("up.rd != ? and not up.rd like ?||'%'")
pexa.extend([vpath, vpath])
pex = tuple(pexa)
qex = " and ".join(qexa)
if qex:
qex = " where " + qex
rewark: list[tuple[str, str, str, int, int]] = []
with self.mutex:
b_left = 0
n_left = 0
q = "select sz from up" + qex
for (sz,) in cur.execute(q, pex):
b_left += sz # sum() can overflow according to docs
n_left += 1
q = "select w, mt, sz, rd, fn from up" + qex
for w, mt, sz, drd, dfn in cur.execute(q, pex):
if self.stop:
return -1
n_left -= 1
b_left -= sz
if drd.startswith("//") or dfn.startswith("//"):
rd, fn = s3dec(drd, dfn)
else:
rd = drd
fn = dfn
abspath = os.path.join(ptop, rd, fn)
if rei and rei.search(abspath):
continue
nohash = reh.search(abspath) if reh else False
pf = "v{}, {:.0f}+".format(n_left, b_left / 1024 / 1024)
self.pp.msg = pf + abspath
st = bos.stat(abspath)
sz2 = st.st_size
mt2 = int(st.st_mtime)
if nohash:
w2 = up2k_wark_from_metadata(self.salt, sz2, mt2, rd, fn)
else:
if sz2 > 1024 * 1024 * 32:
self.log("file: {}".format(abspath))
try:
hashes = self._hashlist_from_file(abspath, pf)
except Exception as ex:
self.log("hash: {} @ [{}]".format(repr(ex), abspath))
continue
w2 = up2k_wark_from_hashlist(self.salt, sz2, hashes)
if w == w2:
continue
rewark.append((drd, dfn, w2, sz2, mt2))
t = "hash mismatch: {}\n db: {} ({} byte, {})\n fs: {} ({} byte, {})"
t = t.format(abspath, w, sz, mt, w2, sz2, mt2)
self.log(t, 1)
if e2vp and rewark:
self.hub.retcode = 1
os.kill(os.getpid(), signal.SIGTERM)
raise Exception("{} files have incorrect hashes".format(len(rewark)))
if not e2vu:
return 0
for rd, fn, w, sz, mt in rewark:
q = "update up set w = ?, sz = ?, mt = ? where rd = ? and fn = ? limit 1"
cur.execute(q, (w, sz, int(mt), rd, fn))
return len(rewark)
def _build_tags_index(self, vol: VFS) -> tuple[int, int, bool]:
ptop = vol.realpath
with self.mutex:
@@ -854,6 +1011,9 @@ class Up2k(object):
c3 = conn.cursor()
n_left = cur.execute("select count(w) from up").fetchone()[0]
for w, rd, fn in cur.execute("select w, rd, fn from up order by rd, fn"):
if self.stop:
return -1, -1, False
n_left -= 1
q = "select w from mt where w = ?"
if c2.execute(q, (w[:16],)).fetchone():
@@ -871,7 +1031,7 @@ class Up2k(object):
if not mpool:
n_tags = self._tag_file(c3, entags, w, abspath)
else:
mpool.put(Mpqe({}, entags, w, abspath))
mpool.put(Mpqe({}, entags, w, abspath, {}))
# not registry cursor; do not self.mutex:
n_tags = len(self._flush_mpool(c3))
@@ -958,6 +1118,7 @@ class Up2k(object):
to_delete = {}
in_progress = {}
while True:
did_nothing = True
with self.mutex:
if gid != self.gid:
break
@@ -967,31 +1128,40 @@ class Up2k(object):
warks = [str(x[0]) for x in zq]
jobs = []
for w in warks:
if w in in_progress:
continue
q = "select rd, fn from up where substr(w,1,16)=? limit 1"
rd, fn = cur.execute(q, (w,)).fetchone()
rd, fn = s3dec(rd, fn)
abspath = os.path.join(ptop, rd, fn)
q = "select k from mt where w = ?"
zq2 = cur.execute(q, (w,)).fetchall()
have: dict[str, Union[str, float]] = {x[0]: 1 for x in zq2}
zq = cur.execute(q, (w,)).fetchall()
have: dict[str, Union[str, float]] = {x[0]: 1 for x in zq}
did_nothing = False
parsers = self._get_parsers(ptop, have, abspath)
if not parsers:
to_delete[w] = True
n_left -= 1
continue
if w in in_progress:
continue
if next((x for x in parsers.values() if x.pri), None):
q = "select k, v from mt where w = ?"
zq2 = cur.execute(q, (w,)).fetchall()
oth_tags = {str(k): v for k, v in zq2}
else:
oth_tags = {}
jobs.append(Mpqe(parsers, set(), w, abspath))
jobs.append(Mpqe(parsers, set(), w, abspath, oth_tags))
in_progress[w] = True
with self.mutex:
done = self._flush_mpool(wcur)
for w in done:
to_delete[w] = True
did_nothing = False
in_progress.pop(w)
n_done += 1
@@ -1004,6 +1174,10 @@ class Up2k(object):
if not warks:
break
if did_nothing:
with self.tag_event:
self.tag_event.wait(0.2)
if not jobs:
continue
@@ -1104,7 +1278,7 @@ class Up2k(object):
return
for _ in range(mpool.maxsize):
mpool.put(Mpqe({}, set(), "", ""))
mpool.put(Mpqe({}, set(), "", "", {}))
mpool.join()
@@ -1120,7 +1294,7 @@ class Up2k(object):
if not qe.mtp:
tags = self.mtag.get(qe.abspath)
else:
tags = self.mtag.get_bin(qe.mtp, qe.abspath)
tags = self.mtag.get_bin(qe.mtp, qe.abspath, qe.oth_tags)
vtags = [
"\033[36m{} \033[33m{}".format(k, v) for k, v in tags.items()
]
@@ -1132,6 +1306,10 @@ class Up2k(object):
except:
ex = traceback.format_exc()
self._log_tag_err(qe.mtp or self.mtag.backend, qe.abspath, ex)
finally:
if qe.mtp:
with self.tag_event:
self.tag_event.notify_all()
q.task_done()
@@ -1830,11 +2008,12 @@ class Up2k(object):
adir, fn = os.path.split(atop)
try:
st = bos.lstat(atop)
is_dir = stat.S_ISDIR(st.st_mode)
except:
raise Pebkac(400, "file not found on disk (already deleted?)")
scandir = not self.args.no_scandir
if stat.S_ISDIR(st.st_mode):
if is_dir:
g = vn.walk("", rem, [], uname, permsets, True, scandir, True)
if unpost:
raise Pebkac(400, "cannot unpost folders")
@@ -1869,8 +2048,14 @@ class Up2k(object):
bos.unlink(abspath)
rm = rmdirs(self.log_func, scandir, True, atop, 1)
return n_files, rm[0], rm[1]
ok: list[str] = []
ng: list[str] = []
if is_dir:
ok, ng = rmdirs(self.log_func, scandir, True, atop, 1)
ok2, ng2 = rmdirs_up(os.path.dirname(atop))
return n_files, ok + ok2, ng + ng2
def handle_mv(self, uname: str, svp: str, dvp: str) -> str:
svn, srem = self.asrv.vfs.get(svp, uname, True, False, True)
@@ -1912,6 +2097,7 @@ class Up2k(object):
self._mv_file(uname, svpf, dvpf)
rmdirs(self.log_func, scandir, True, sabs, 1)
rmdirs_up(os.path.dirname(sabs))
return "k"
def _mv_file(self, uname: str, svp: str, dvp: str) -> str:
@@ -2168,14 +2354,15 @@ class Up2k(object):
return wark
def _hashlist_from_file(self, path: str) -> list[str]:
def _hashlist_from_file(self, path: str, prefix: str = "") -> list[str]:
fsz = bos.path.getsize(path)
csz = up2k_chunksize(fsz)
ret = []
with open(fsenc(path), "rb", 512 * 1024) as f:
while fsz > 0:
if self.pp:
self.pp.msg = "{} MB, {}".format(int(fsz / 1024 / 1024), path)
mb = int(fsz / 1024 / 1024)
self.pp.msg = "{}{} MB, {}".format(prefix, mb, path)
hashobj = hashlib.sha512()
rem = min(csz, fsz)
@@ -2195,8 +2382,11 @@ class Up2k(object):
return ret
def _new_upload(self, job: dict[str, Any]) -> None:
self.registry[job["ptop"]][job["wark"]] = job
pdir = os.path.join(job["ptop"], job["prel"])
if not job["size"] and bos.path.isfile(os.path.join(pdir, job["name"])):
return
self.registry[job["ptop"]][job["wark"]] = job
job["name"] = self._untaken(pdir, job["name"], job["t0"], job["addr"])
# if len(job["name"].split(".")) > 8:
# raise Exception("aaa")
@@ -2325,6 +2515,9 @@ class Up2k(object):
except:
pass
if self.args.nw:
return
path = os.path.join(histpath, "up2k.snap")
if not reg:
if ptop not in self.snap_prev or self.snap_prev[ptop] is not None:
@@ -2371,7 +2564,7 @@ class Up2k(object):
ntags1 = len(tags)
parsers = self._get_parsers(ptop, tags, abspath)
if parsers:
tags.update(self.mtag.get_bin(parsers, abspath))
tags.update(self.mtag.get_bin(parsers, abspath, tags))
except Exception as ex:
self._log_tag_err("", abspath, ex)
continue
@@ -2425,6 +2618,7 @@ class Up2k(object):
# self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
def shutdown(self) -> None:
self.stop = True
self.log("writing snapshot")
self.do_snapshot()

View File

@@ -30,6 +30,12 @@ try:
except:
HAVE_SQLITE3 = False
try:
HAVE_PSUTIL = True
import psutil
except:
HAVE_PSUTIL = False
try:
import types
from collections.abc import Callable, Iterable
@@ -121,6 +127,7 @@ HTTPCODE = {
429: "Too Many Requests",
500: "Internal Server Error",
501: "Not Implemented",
503: "Service Unavailable",
}
@@ -130,6 +137,9 @@ IMPLICATIONS = [
["e2tsr", "e2ts"],
["e2ts", "e2t"],
["e2t", "e2d"],
["e2vu", "e2v"],
["e2vp", "e2v"],
["e2v", "e2d"],
]
@@ -1415,7 +1425,8 @@ def statdir(
def rmdirs(
logger: RootLogger, scandir: bool, lstat: bool, top: str, depth: int
) -> tuple[list[str], list[str]]:
if not os.path.exists(fsenc(top)) or not os.path.isdir(fsenc(top)):
"""rmdir all descendants, then self"""
if not os.path.isdir(fsenc(top)):
top = os.path.dirname(top)
depth -= 1
@@ -1439,6 +1450,21 @@ def rmdirs(
return ok, ng
def rmdirs_up(top: str) -> tuple[list[str], list[str]]:
"""rmdir on self, then all parents"""
try:
os.rmdir(fsenc(top))
except:
return [], [top]
par = os.path.dirname(top)
if not par:
return [top], []
ok, ng = rmdirs_up(par)
return [top] + ok, ng
def unescape_cookie(orig: str) -> str:
# mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn # qwe,rty;asd fgh+jkl%zxc&vbn
ret = ""
@@ -1489,22 +1515,109 @@ def guess_mime(url: str, fallback: str = "application/octet-stream") -> str:
return ret
def getalive(pids: list[int], pgid: int) -> list[int]:
alive = []
for pid in pids:
try:
if pgid:
# check if still one of ours
if os.getpgid(pid) == pgid:
alive.append(pid)
else:
# windows doesn't have pgroups; assume
psutil.Process(pid)
alive.append(pid)
except:
pass
return alive
def killtree(root: int) -> None:
"""still racy but i tried"""
try:
# limit the damage where possible (unixes)
pgid = os.getpgid(os.getpid())
except:
pgid = 0
if HAVE_PSUTIL:
pids = [root]
parent = psutil.Process(root)
for child in parent.children(recursive=True):
pids.append(child.pid)
child.terminate()
parent.terminate()
parent = None
elif pgid:
# linux-only
pids = []
chk = [root]
while chk:
pid = chk[0]
chk = chk[1:]
pids.append(pid)
_, t, _ = runcmd(["pgrep", "-P", str(pid)])
chk += [int(x) for x in t.strip().split("\n") if x]
pids = getalive(pids, pgid) # filter to our pgroup
for pid in pids:
os.kill(pid, signal.SIGTERM)
else:
# windows gets minimal effort sorry
os.kill(pid, signal.SIGTERM)
return
for n in range(10):
time.sleep(0.1)
pids = getalive(pids, pgid)
if not pids or n > 3 and pids == [root]:
break
for pid in pids:
try:
os.kill(pid, signal.SIGKILL)
except:
pass
def runcmd(
argv: Union[list[bytes], list[str]], timeout: Optional[int] = None, **ka: Any
) -> tuple[int, str, str]:
kill = ka.pop("kill", "t") # [t]ree [m]ain [n]one
sin = ka.pop("sin", None)
if sin:
ka["stdin"] = sp.PIPE
p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE, **ka)
if not timeout or PY2:
stdout, stderr = p.communicate()
stdout, stderr = p.communicate(sin)
else:
try:
stdout, stderr = p.communicate(timeout=timeout)
stdout, stderr = p.communicate(sin, timeout=timeout)
except sp.TimeoutExpired:
p.kill()
stdout, stderr = p.communicate()
if kill == "n":
return -18, "", "" # SIGCONT; leave it be
elif kill == "m":
p.kill()
else:
killtree(p.pid)
try:
stdout, stderr = p.communicate(timeout=1)
except:
stdout = b""
stderr = b""
stdout = stdout.decode("utf-8", "replace")
stderr = stderr.decode("utf-8", "replace")
return p.returncode, stdout, stderr
rc = p.returncode
if rc is None:
rc = -14 # SIGALRM; failed to kill
return rc, stdout, stderr
def chkcmd(argv: Union[list[bytes], list[str]], **ka: Any) -> tuple[str, str]:
@@ -1708,7 +1821,6 @@ def termsize() -> tuple[int, int]:
def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]:
try:
import fcntl
import struct
import termios
cr = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"1234"))

View File

@@ -21,7 +21,7 @@ window.baguetteBox = (function () {
afterHide: null,
onChange: null,
},
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnVmode, btnClose,
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
currentGallery = [],
currentIndex = 0,
isOverlayVisible = false,
@@ -37,6 +37,9 @@ window.baguetteBox = (function () {
vmute = false,
vloop = sread('vmode') == 'L',
vnext = sread('vmode') == 'C',
loopA = null,
loopB = null,
url_ts = null,
resume_mp = false;
var onFSC = function (e) {
@@ -182,6 +185,7 @@ window.baguetteBox = (function () {
'<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-full" type="button">⛶</button>' +
'<button id="bbox-vmode" type="button" tt="a"></button>' +
'<button id="bbox-close" type="button" aria-label="Close">X</button>' +
'</div></div>'
@@ -198,9 +202,9 @@ window.baguetteBox = (function () {
btnRotL = ebi('bbox-rotl');
btnRotR = ebi('bbox-rotr');
btnSel = ebi('bbox-tsel');
btnFull = ebi('bbox-full');
btnVmode = ebi('bbox-vmode');
btnClose = ebi('bbox-close');
bindEvents();
}
function halp() {
@@ -215,6 +219,7 @@ window.baguetteBox = (function () {
['home', 'first file'],
['end', 'last file'],
['R', 'rotate (shift=ccw)'],
['F', 'toggle fullscreen'],
['S', 'toggle file selection'],
['space, P, K', 'video: play / pause'],
['U', 'video: seek 10sec back'],
@@ -222,16 +227,17 @@ window.baguetteBox = (function () {
['M', 'video: toggle mute'],
['V', 'video: toggle loop'],
['C', 'video: toggle auto-next'],
['F', 'video: toggle fullscreen'],
['<code>[</code>, <code>]</code>', 'video: loop start / end'],
],
d = mknod('table'),
d = mknod('table', 'bbox-halp'),
html = ['<tbody>'];
for (var a = 0; a < list.length; a++)
html.push('<tr><td>' + list[a][0] + '</td><td>' + list[a][1] + '</td></tr>');
html.push('<tr><td colspan="2">tap middle of img to hide btns</td></tr>');
html.push('<tr><td colspan="2">tap left/right sides for prev/next</td></tr>');
d.innerHTML = html.join('\n') + '</tbody>';
d.setAttribute('id', 'bbox-halp');
d.onclick = function () {
overlay.removeChild(d);
};
@@ -273,19 +279,17 @@ window.baguetteBox = (function () {
setVmode();
}
else if (k == "KeyF")
try {
if (isFullscreen)
document.exitFullscreen();
else
v.requestFullscreen();
}
catch (ex) { }
tglfull();
else if (k == "KeyS")
tglsel();
else if (k == "KeyR")
rotn(e.shiftKey ? -1 : 1);
else if (k == "KeyY")
dlpic();
else if (k == "BracketLeft")
setloop(1);
else if (k == "BracketRight")
setloop(2);
}
function anim() {
@@ -354,6 +358,16 @@ window.baguetteBox = (function () {
return [name, a, files, ebi(files[a].id)];
}
function tglfull() {
try {
if (isFullscreen)
document.exitFullscreen();
else
(vid() || ebi('bbox-overlay')).requestFullscreen();
}
catch (ex) { alert(ex); }
}
function tglsel() {
var o = findfile()[3];
clmod(o.closest('tr'), 'sel', 't');
@@ -416,6 +430,9 @@ window.baguetteBox = (function () {
var nonPassiveEvent = passiveSupp ? { passive: true } : null;
function bindEvents() {
bind(document, 'keydown', keyDownHandler);
bind(document, 'keyup', keyUpHandler);
bind(document, 'fullscreenchange', onFSC);
bind(overlay, 'click', overlayClickHandler);
bind(btnPrev, 'click', showPreviousImage);
bind(btnNext, 'click', showNextImage);
@@ -426,6 +443,7 @@ window.baguetteBox = (function () {
bind(btnRotL, 'click', rotl);
bind(btnRotR, 'click', rotr);
bind(btnSel, 'click', tglsel);
bind(btnFull, 'click', tglfull);
bind(slider, 'contextmenu', contextmenuHandler);
bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
@@ -434,6 +452,9 @@ window.baguetteBox = (function () {
}
function unbindEvents() {
unbind(document, 'keydown', keyDownHandler);
unbind(document, 'keyup', keyUpHandler);
unbind(document, 'fullscreenchange', onFSC);
unbind(overlay, 'click', overlayClickHandler);
unbind(btnPrev, 'click', showPreviousImage);
unbind(btnNext, 'click', showNextImage);
@@ -444,6 +465,7 @@ window.baguetteBox = (function () {
unbind(btnRotL, 'click', rotl);
unbind(btnRotR, 'click', rotr);
unbind(btnSel, 'click', tglsel);
unbind(btnFull, 'click', tglfull);
unbind(slider, 'contextmenu', contextmenuHandler);
unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
@@ -464,9 +486,8 @@ window.baguetteBox = (function () {
var imagesFiguresIds = [];
var imagesCaptionsIds = [];
for (var i = 0, fullImage; i < gallery.length; i++) {
fullImage = mknod('div');
fullImage = mknod('div', 'baguette-img-' + i);
fullImage.className = 'full-image';
fullImage.id = 'baguette-img-' + i;
imagesElements.push(fullImage);
imagesFiguresIds.push('bbox-figure-' + i);
@@ -508,9 +529,7 @@ window.baguetteBox = (function () {
if (overlay.style.display === 'block')
return;
bind(document, 'keydown', keyDownHandler);
bind(document, 'keyup', keyUpHandler);
bind(document, 'fullscreenchange', onFSC);
bindEvents();
currentIndex = chosenImageIndex;
touch = {
count: 0,
@@ -522,6 +541,10 @@ window.baguetteBox = (function () {
preloadPrev(currentIndex);
});
clmod(ebi('bbox-btns'), 'off');
clmod(btnPrev, 'off');
clmod(btnNext, 'off');
updateOffset();
overlay.style.display = 'block';
// Fade in overlay
@@ -534,9 +557,10 @@ window.baguetteBox = (function () {
options.afterShow();
}, 50);
if (options.onChange)
if (options.onChange && !url_ts)
options.onChange(currentIndex, imagesElements.length);
url_ts = null;
documentLastFocus = document.activeElement;
btnClose.focus();
isOverlayVisible = true;
@@ -553,9 +577,13 @@ window.baguetteBox = (function () {
return;
sethash('');
unbind(document, 'keydown', keyDownHandler);
unbind(document, 'keyup', keyUpHandler);
unbind(document, 'fullscreenchange', onFSC);
unbindEvents();
try {
document.exitFullscreen();
isFullscreen = false;
}
catch (ex) { }
// Fade out and hide the overlay
overlay.className = '';
setTimeout(function () {
@@ -601,16 +629,14 @@ window.baguetteBox = (function () {
if (is_vid && index != currentIndex)
return; // no preload
var figure = mknod('figure');
figure.id = 'bbox-figure-' + index;
var figure = mknod('figure', 'bbox-figure-' + index);
figure.innerHTML = '<div class="bbox-spinner">' +
'<div class="bbox-double-bounce1"></div>' +
'<div class="bbox-double-bounce2"></div>' +
'</div>';
if (options.captions && imageCaption) {
var figcaption = mknod('figcaption');
figcaption.id = 'bbox-figcaption-' + index;
var figcaption = mknod('figcaption', 'bbox-figcaption-' + index);
figcaption.innerHTML = imageCaption;
figure.appendChild(figcaption);
}
@@ -789,8 +815,18 @@ window.baguetteBox = (function () {
}
function playvid(play) {
if (vid())
vid()[play ? 'play' : 'pause']();
if (!play) {
timer.rm(loopchk);
loopA = loopB = null;
}
var v = vid();
if (!v)
return;
v[play ? 'play' : 'pause']();
if (play && loopA !== null && v.currentTime < loopA)
v.currentTime = loopA;
}
function playpause() {
@@ -809,6 +845,38 @@ window.baguetteBox = (function () {
showNextImage();
}
function setloop(side) {
var v = vid();
if (!v)
return;
var t = v.currentTime;
if (side == 1) loopA = t;
if (side == 2) loopB = t;
if (side)
toast.inf(5, 'Loop' + (side == 1 ? 'A' : 'B') + ': ' + f2f(t, 2));
if (loopB !== null) {
timer.add(loopchk);
sethash(window.location.hash.slice(1).split('&')[0] + '&t=' + (loopA || 0) + '-' + loopB);
}
}
function loopchk() {
if (loopB === null)
return;
var v = vid();
if (!v || v.paused || v.currentTime < loopB)
return;
v.currentTime = loopA || 0;
}
function urltime(txt) {
url_ts = txt;
}
function mp_ctl() {
var v = vid();
if (!vmute && v && mp.au && !mp.au.paused) {
@@ -851,6 +919,15 @@ window.baguetteBox = (function () {
playvid(true);
v.muted = vmute;
v.loop = vloop;
if (url_ts) {
var seek = ('' + url_ts).split('-');
v.currentTime = seek[0];
if (seek.length > 1) {
loopA = parseFloat(seek[0]);
loopB = parseFloat(seek[1]);
setloop();
}
}
}
selbg();
mp_ctl();
@@ -862,6 +939,28 @@ window.baguetteBox = (function () {
else
timer.rm(rotn);
var ctime = 0;
el.onclick = v ? null : function (e) {
var rc = e.target.getBoundingClientRect(),
x = e.clientX - rc.left,
fx = x / (rc.right - rc.left);
if (fx < 0.3)
return showPreviousImage();
if (fx > 0.7)
return showNextImage();
clmod(ebi('bbox-btns'), 'off', 't');
clmod(btnPrev, 'off', 't');
clmod(btnNext, 'off', 't');
if (Date.now() - ctime <= 500)
tglfull();
ctime = Date.now();
};
var prev = QS('.full-image.vis');
if (prev)
clmod(prev, 'vis');
@@ -898,8 +997,6 @@ window.baguetteBox = (function () {
function destroyPlugin() {
unbindEvents();
clearCachedData();
unbind(document, 'keydown', keyDownHandler);
unbind(document, 'keyup', keyUpHandler);
document.getElementsByTagName('body')[0].removeChild(ebi('bbox-overlay'));
data = {};
currentGallery = [];
@@ -912,6 +1009,7 @@ window.baguetteBox = (function () {
showNext: showNextImage,
showPrevious: showPreviousImage,
relseek: relseek,
urltime: urltime,
playpause: playpause,
hide: hideOverlay,
destroy: destroyPlugin

View File

@@ -238,6 +238,7 @@ html.b {
--u2-txt-bg: transparent;
--u2-tab-1-sh: var(--bg);
--u2-b1-bg: rgba(128,128,128,0.15);
--u2-b2-bg: var(--u2-b1-bg);
--u2-o-bg: var(--btn-bg);
--u2-o-h-bg: var(--btn-h-bg);
@@ -352,6 +353,8 @@ html.cy {
--srv-1: #f00;
--op-aa-bg: #fff;
--u2-b1-bg: #f00;
--u2-b2-bg: #f00;
--u2-o-bg: #ff0;
--u2-o-1-bg: #f00;
}
@@ -412,8 +415,8 @@ html.dz {
--u2-tab-1-b2: #583;
--u2-tab-1-sh: #280;
--u2-b-fg: #fff;
--u2-b1-bg: #c38;
--u2-b2-bg: #d80;
--u2-b1-bg: #3a3;
--u2-b2-bg: #3a3;
--u2-inf-bg: #07a;
--u2-inf-b1: #0be;
--u2-ok-bg: #380;
@@ -522,6 +525,13 @@ html.dy {
--u2-tab-1-b2: a;
--u2-tab-1-fg: a;
--u2-tab-1-bg: a;
--u2-b1-bg: #000;
--u2-b2-bg: #000;
--u2-o-h-bg: #999;
--u2-o-1h-bg: #999;
--u2-o-bg: #eee;
--u2-o-1-bg: #000;
--ud-b1: a;
--sort-1: a;
@@ -1923,6 +1933,15 @@ html.y #bbox-overlay figcaption a {
.bbox-btn {
position: fixed;
}
.bbox-btn,
#bbox-btns {
opacity: 1;
animation: opacity .2s infinite ease-in-out;
}
.bbox-btn.off,
#bbox-btns.off {
opacity: 0;
}
#bbox-overlay button {
cursor: pointer;
outline: none;
@@ -1967,7 +1986,7 @@ html.y #bbox-overlay figcaption a {
#bbox-halp td {
padding: .2em .5em;
}
#bbox-halp td:first-child {
#bbox-halp td:first-child:not([colspan]) {
text-align: right;
}
.bbox-spinner {
@@ -2202,12 +2221,20 @@ html.y #bbox-overlay figcaption a {
#u2notbtn * {
line-height: 1.3em;
}
#u2mu div {
height: 1.2em;
overflow: hidden;
}
#u2tabw {
min-height: 0;
transition: min-height .2s;
margin: 2em 0;
}
#u2tabw.na>table {
display: none;
}
#u2tab {
table-layout: fixed;
border-collapse: collapse;
width: calc(100% - 2em);
max-width: 100em;
@@ -2331,7 +2358,7 @@ html.y #bbox-overlay figcaption a {
width: 48em;
}
#u2conf.ww {
width: 74em;
width: 78em;
}
#u2conf.ww #u2c3w {
width: 29em;
@@ -2371,6 +2398,9 @@ html.y #bbox-overlay figcaption a {
position: relative;
bottom: -0.08em;
}
#u2conf input+a.b {
background: var(--u2-b2-bg);
}
html.b #u2conf a.b:hover {
background: var(--btn-h-bg);
}
@@ -2429,11 +2459,11 @@ html.b #u2conf a.b:hover {
color: var(--fg-max);
font-style: italic;
text-align: center;
font-size: .9em;
margin: 1em 0;
font-size: 1.2em;
margin: .8em 0;
}
#u2foot .warn {
font-size: 1.3em;
font-size: 1.2em;
padding: .5em .8em;
margin: 1em -.6em;
border-width: .1em 0;
@@ -2447,6 +2477,9 @@ html.b #u2conf a.b:hover {
font-size: .9em;
font-weight: normal;
}
#u2foot>*+* {
margin-top: 1.5em;
}
.prog {
font-family: 'scp', monospace, monospace;
}
@@ -2600,9 +2633,6 @@ html.c #u2cards,
html.a #u2cards {
margin: 0 auto -1em auto;
}
html.a #u2conf input+a.b {
background: var(--u2-b2-bg);
}
html.c #u2foot:empty,
html.a #u2foot:empty {
margin-bottom: -1em;

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>⇆🎉 {{ title }}</title>
<title>{{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
{{ html_head }}
@@ -139,6 +139,7 @@
dtheme = "{{ dtheme }}",
srvinf = "{{ srv_info }}",
lang = "{{ lang }}",
dfavico = "{{ favico }}",
def_hcols = {{ def_hcols|tojson }},
have_up2k_idx = {{ have_up2k_idx|tojson }},
have_tags_idx = {{ have_tags_idx|tojson }},
@@ -147,7 +148,8 @@
have_del = {{ have_del|tojson }},
have_unpost = {{ have_unpost|tojson }},
have_zip = {{ have_zip|tojson }},
turbolvl = {{ turbolvl|tojson }},
turbolvl = {{ turbolvl }},
u2sort = "{{ u2sort }}",
have_emp = {{ have_emp|tojson }},
txt_ext = "{{ txt_ext }}",
{% if no_prism %}no_prism = 1,{% endif %}

View File

@@ -36,8 +36,8 @@ var Ls = {
"ot_msg": "msg: send a message to the server log",
"ot_mp": "media player options",
"ot_cfg": "configuration options",
"ot_u2i": 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server$N$Nuploads are resumable, multithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader',
"ot_u2w": 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$Nmultithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader',
"ot_u2i": 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server$N$Nuploads are resumable, multithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader<br /><br />during uploads, this icon becomes a progress indicator!',
"ot_u2w": 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$Nmultithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader<br /><br />during uploads, this icon becomes a progress indicator!',
"ab_mkdir": "make directory",
"ab_mkdoc": "new markdown doc",
@@ -63,6 +63,7 @@ var Ls = {
"ul_par": "parallel uploads:",
"ut_mt": "continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck",
"ut_ask": "ask for confirmation before upload starts",
"ut_pot": "improve upload speed on slow devices$Nby making the UI less complex",
"ut_srch": "don't actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)",
"ut_par": "pause uploads by setting it to 0$N$Nincrease if your connection is slow / high latency$N$Nkeep it 1 on LAN or if the server HDD is a bottleneck",
"ul_btn": "drop files / folders<br>here (or click me)",
@@ -113,6 +114,8 @@ var Ls = {
"cut_flag": "ensure only one tab is uploading at a time $N -- other tabs must have this enabled too $N -- only affects tabs on the same domain",
"cut_az": "upload files in alphabetical order, rather than smallest-file-first$N$Nalphabetical order can make it easier to eyeball if something went wrong on the server, but it makes uploading slightly slower on fiber / LAN",
"cft_text": "favicon text (blank and refresh to disable)",
"cft_fg": "foreground color",
"cft_bg": "background color",
@@ -248,6 +251,7 @@ var Ls = {
"md_eshow": "cannot show ",
"xhr403": "403: Access denied\n\ntry pressing F5, maybe you got logged out",
"cf_ok": "sorry about that -- DD" + wah + "oS protection kicked in\n\nthings should resume in about 30 sec\n\nif nothing happens, hit F5 to reload the page",
"tl_xe1": "could not list subfolders:\n\nerror ",
"tl_xe2": "404: Folder not found",
"fl_xe1": "could not list files in folder:\n\nerror ",
@@ -284,6 +288,9 @@ var Ls = {
"u_https2": "switch to https",
"u_https3": "for much better performance",
"u_ancient": 'your browser is impressively ancient -- maybe you should <a href="#" onclick="goto(\'bup\')">use bup instead</a>',
"u_enpot": 'switch to <a href="#">potato UI</a> (may improve upload speed)',
"u_depot": 'switch to <a href="#">fancy UI</a> (may reduce upload speed)',
"u_gotpot": 'switching to the potato UI for improved upload speed,\n\nfeel free to disagree and switch back!',
"u_ever": "this is the basic uploader; up2k needs at least<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1",
"u_su2k": 'this is the basic uploader; <a href="#" id="u2yea">up2k</a> is better',
"u_ewrite": 'you do not have write-access to this folder',
@@ -360,8 +367,8 @@ var Ls = {
"ot_msg": "msg: send en beskjed til serverloggen",
"ot_mp": "musikkspiller-instillinger",
"ot_cfg": "andre innstillinger",
"ot_u2i": 'up2k: last opp filer (hvis du har skrivetilgang) eller bytt til søkemodus for å sjekke om filene finnes et-eller-annet sted på serveren$N$Nopplastninger kan gjenopptas etter avbrudd, skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn den primitive opplasteren bup',
"ot_u2w": 'up2k: filopplastning med støtte for å gjenoppta avbrutte opplastninger -- steng ned nettleseren og dra de samme filene inn i nettleseren igjen for å plukke opp igjen der du slapp$N$Nopplastninger skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn den primitive opplasteren "bup"',
"ot_u2i": 'up2k: last opp filer (hvis du har skrivetilgang) eller bytt til søkemodus for å sjekke om filene finnes et-eller-annet sted på serveren$N$Nopplastninger kan gjenopptas etter avbrudd, skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn den primitive opplasteren bup<br /><br />mens opplastninger foregår så vises fremdriften her oppe!',
"ot_u2w": 'up2k: filopplastning med støtte for å gjenoppta avbrutte opplastninger -- steng ned nettleseren og dra de samme filene inn i nettleseren igjen for å plukke opp igjen der du slapp$N$Nopplastninger skjer stykkevis for potensielt høyere ytelse, og ivaretar datostempling -- men bruker litt mer prosessorkraft enn den primitive opplasteren "bup"<br /><br />mens opplastninger foregår så vises fremdriften her oppe!',
"ab_mkdir": "lag mappe",
"ab_mkdoc": "nytt dokument",
@@ -387,6 +394,7 @@ var Ls = {
"ul_par": "samtidige handl.:",
"ut_mt": "fortsett å befare køen mens opplastning foregår$N$Nskru denne av dersom du har en$Ntreg prosessor eller harddisk",
"ut_ask": "bekreft filutvalg før opplastning starter",
"ut_pot": "forbedre ytelsen på trege enheter ved å$Nforenkle brukergrensesnittet",
"ut_srch": "utfør søk istedenfor å laste opp --$Nleter igjennom alle mappene du har lov til å se",
"ut_par": "sett til 0 for å midlertidig stanse opplastning$N$Nhøye verdier (4 eller 8) kan gi bedre ytelse,$Nspesielt på trege internettlinjer$N$Nbør ikke være høyere enn 1 på LAN$Neller hvis serveren sin harddisk er treg",
"ul_btn": "slipp filer / mapper<br>her (eller klikk meg)",
@@ -437,6 +445,8 @@ var Ls = {
"cut_flag": "samkjører nettleserfaner slik at bare én $N kan holde på med befaring / opplastning $N -- andre faner må også ha denne skrudd på $N -- fungerer kun innenfor samme domene",
"cut_az": "last opp filer i alfabetisk rekkefølge, istedenfor minste-fil-først$N$Nalfabetisk kan gjøre det lettere å anslå om alt gikk bra, men er bittelitt tregere på fiber / LAN",
"cft_text": "ikontekst (blank ut og last siden på nytt for å deaktivere)",
"cft_fg": "farge",
"cft_bg": "bakgrunnsfarge",
@@ -572,6 +582,7 @@ var Ls = {
"md_eshow": "kan ikke vise ",
"xhr403": "403: Tilgang nektet\n\nkanskje du ble logget ut? prøv å trykk F5",
"cf_ok": "beklager -- liten tilfeldig kontroll, alt OK\n\nting skal fortsette om ca. 30 sekunder\n\nhvis ikkeno skjer, trykk F5 for å laste siden på nytt",
"tl_xe1": "kunne ikke hente undermapper:\n\nfeil ",
"tl_xe2": "404: Mappen finnes ikke",
"fl_xe1": "kunne ikke hente filer i mappen:\n\nfeil ",
@@ -597,8 +608,8 @@ var Ls = {
"un_max": "viser de første 2000 filene (bruk filteret for å innsnevre)",
"un_avail": "{0} filer kan slettes",
"un_m2": "sortert etter opplastningstid &ndash; nyeste først:",
"un_no1": "men nei, her var det jaggu ingenting",
"un_no2": "men nei, her var det jaggu ingenting som passer overens med filteret",
"un_no1": "men nei, her var det jaggu ikkeno som slettes kan",
"un_no2": "men nei, her var det jaggu ingenting som passet overens med filteret",
"un_next": "slett de neste {0} filene nedenfor",
"un_del": "slett",
"un_m3": "henter listen med nylig opplastede filer...",
@@ -608,6 +619,9 @@ var Ls = {
"u_https2": "bytte til https",
"u_https3": "for mye høyere hastighet",
"u_ancient": 'nettleseren din er prehistorisk -- mulig du burde <a href="#" onclick="goto(\'bup\')">bruke bup istedenfor</a>',
"u_enpot": 'bytt til <a href="#">enkelt UI</a> (gir sannsynlig raskere opplastning)',
"u_depot": 'bytt til <a href="#">snæsent UI</a> (gir sannsynlig tregere opplastning)',
"u_gotpot": 'byttet til et enklere UI for å laste opp raskere,\n\ndu kan gjerne bytte tilbake altså!',
"u_ever": "dette er den primitive opplasteren; up2k krever minst:<br>chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1",
"u_su2k": 'dette er den primitive opplasteren; <a href="#" id="u2yea">up2k</a> er bedre',
"u_ewrite": 'du har ikke skrivetilgang i denne mappen',
@@ -715,6 +729,10 @@ ebi('op_up2k').innerHTML = (
' <label for="multitask" tt="' + L.ut_mt + '">🏃</label>\n' +
' </td>\n' +
' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="potato" />\n' +
' <label for="potato" tt="' + L.ut_pot + '">🥔</label>\n' +
' </td>\n' +
' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="ask_up" />\n' +
' <label for="ask_up" tt="' + L.ut_ask + '">💭</label>\n' +
' </td>\n' +
@@ -761,7 +779,7 @@ ebi('op_up2k').innerHTML = (
'</div>\n' +
'<div id="u2tabw"><table id="u2tab">\n' +
'<div id="u2tabw" class="na"><table id="u2tab">\n' +
' <thead>\n' +
' <tr>\n' +
' <td>' + L.utl_name + '</td>\n' +
@@ -770,10 +788,10 @@ ebi('op_up2k').innerHTML = (
' </tr>\n' +
' </thead>\n' +
' <tbody></tbody>\n' +
'</table></div>\n' +
'</table><div id="u2mu"></div></div>\n' +
'<p id="u2flagblock"><b>' + L.ul_flagblk + '</p>\n' +
'<p id="u2foot"></p>'
'<div id="u2foot"></div>'
);
@@ -822,6 +840,7 @@ ebi('op_cfg').innerHTML = (
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '">turbo</a>\n' +
' <a id="u2tdate" class="tgl btn ttb" href="#" tt="' + L.cut_datechk + '">date-chk</a>\n' +
' <a id="flag_en" class="tgl btn" href="#" tt="' + L.cut_flag + '">💤</a>\n' +
' <a id="u2sort" class="tgl btn" href="#" tt="' + L.cut_az + '">az</a>\n' +
' </td>\n' +
' </div>\n' +
'</div>\n' +
@@ -2192,6 +2211,12 @@ function play(tid, is_ev, seek) {
if (window.thegrid)
thegrid.loadsel();
try {
if (actx.state == 'suspended')
actx.resume();
}
catch (ex) { }
try {
mp.au.play();
if (mp.au.paused)
@@ -2321,9 +2346,13 @@ function scan_hash(v) {
ts = null;
if (m.length > 3) {
m = /^&[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(m[3]);
if (m) {
ts = parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0);
var tm = /^&[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(m[3]);
if (tm) {
ts = parseInt(tm[1] || 0) * 60 + parseInt(tm[2] || 0);
}
tm = /^&[Tt=0]*([0-9\.]+)-([0-9\.]+)$/.exec(m[3]);
if (tm) {
ts = '' + tm[1] + '-' + tm[2];
}
}
@@ -2359,6 +2388,7 @@ function eval_hash() {
return;
clearInterval(t);
baguetteBox.urltime(ts);
var im = QS('#ggrid a[ref="' + id + '"]');
im.click();
im.scrollIntoView();
@@ -2397,8 +2427,7 @@ function eval_hash() {
};
})(a);
var d = mknod('div');
d.setAttribute('id', 'acc_info');
var d = mknod('div', 'acc_info');
document.body.insertBefore(d, ebi('ops'));
})();
@@ -2663,8 +2692,7 @@ var fileman = (function () {
var rui = ebi('rui');
if (!rui) {
rui = mknod('div');
rui.setAttribute('id', 'rui');
rui = mknod('div', 'rui');
document.body.appendChild(rui);
}
@@ -3147,10 +3175,9 @@ var showfile = (function () {
return;
qsr('#prism_css');
var el = mknod('link');
var el = mknod('link', 'prism_css');
el.rel = 'stylesheet';
el.href = '/.cpr/deps/prism' + (light ? '' : 'd') + '.css';
el.setAttribute('id', 'prism_css');
document.head.appendChild(el);
};
@@ -3264,8 +3291,7 @@ var showfile = (function () {
fun = function (el) { };
qsr('#doc');
var el = mknod('pre');
el.setAttribute('id', 'doc');
var el = mknod('pre', 'doc');
el.setAttribute('tabindex', '0');
clmod(ebi('wrap'), 'doc', !is_md);
if (is_md) {
@@ -3293,9 +3319,8 @@ var showfile = (function () {
hfun(get_evpath() + '?doc=' + url.split('/').pop());
qsr('#docname');
el = mknod('span');
el = mknod('span', 'docname');
el.textContent = tname;
el.setAttribute('id', 'docname');
ebi('path').appendChild(el);
r.updtree();
@@ -3430,9 +3455,8 @@ var showfile = (function () {
var thegrid = (function () {
var lfiles = ebi('files'),
gfiles = mknod('div');
gfiles = mknod('div', 'gfiles');
gfiles.setAttribute('id', 'gfiles');
gfiles.style.display = 'none';
gfiles.innerHTML = (
'<div id="ghead" class="ghead">' +
@@ -5910,7 +5934,7 @@ var unpost = (function () {
html.push("<table><thead><tr><td></td><td>time</td><td>size</td><td>file</td></tr></thead><tbody>");
}
else
html.push(filt.value ? L.un_no2 : L.un_no1);
html.push('-- <em>' + (filt.value ? L.un_no2 : L.un_no1) + '</em>');
var mods = [1000, 100, 10];
for (var a = 0; a < res.length; a++) {

27
copyparty/web/cf.html Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ svcname }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
</head>
<body>
<div id="box" style="opacity: 0; font-family: sans-serif">
<h3>please press F5 to reload the page</h3>
<p>sorry for the inconvenience</p>
</div>
<script>
setTimeout(function() {
document.getElementById('box').style.opacity = 1;
}, 500);
parent.toast.ok(30, parent.L.cf_ok);
parent.qsr('#cf_frame');
</script>
</body>
</html>

View File

@@ -13,8 +13,7 @@ audio_eq.apply = function () {
var can = ebi('fft_can');
if (!can) {
can = mknod('canvas');
can.setAttribute('id', 'fft_can');
can = mknod('canvas', 'fft_can');
can.style.cssText = 'position:absolute;left:0;bottom:5em;width:' + w + 'px;height:' + h + 'px;z-index:9001';
document.body.appendChild(can);
can.width = w;

View File

@@ -1,6 +1,6 @@
<!DOCTYPE html><html><head>
<meta charset="utf-8">
<title>📝🎉 {{ title }}</title>
<title>📝 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7">
{{ html_head }}
@@ -128,7 +128,8 @@ write markdown (most html is 🙆 too)
<script>
var last_modified = {{ lastmod }},
have_emp = {{ have_emp|tojson }};
have_emp = {{ have_emp|tojson }},
dfavico = "{{ favico }}";
var md_opt = {
link_md_as_html: false,

View File

@@ -173,8 +173,7 @@ md_plug_err = function (ex, js) {
o.textContent = lns[ln - 1];
}
}
var errbox = mknod('div');
errbox.setAttribute('id', 'md_errbox');
var errbox = mknod('div', 'md_errbox');
errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
errbox.textContent = msg;
errbox.onclick = function () {

View File

@@ -36,6 +36,11 @@
width: 55em;
width: min(55em, calc(100% - 2em));
}
#mtw.single.editor,
#mw.single.editor {
width: calc(100% - 1em);
left: .5em;
}
#mp {

View File

@@ -16,8 +16,7 @@ var dom_sbs = ebi('sbs');
var dom_nsbs = ebi('nsbs');
var dom_tbox = ebi('toolsbox');
var dom_ref = (function () {
var d = mknod('div');
d.setAttribute('id', 'mtr');
var d = mknod('div', 'mtr');
dom_swrap.appendChild(d);
d = ebi('mtr');
// hide behind the textarea (offsetTop is not computed if display:none)
@@ -509,6 +508,20 @@ function setsel(s) {
}
// cut/copy current line
function md_cut(cut) {
var s = linebounds();
if (s.car != s.cdr)
return;
dom_src.setSelectionRange(s.n1, s.n2 + 1, 'forward');
setTimeout(function () {
var i = cut ? s.n1 : s.car;
dom_src.setSelectionRange(i, i, 'forward');
}, 1);
}
// indent/dedent
function md_indent(dedent) {
var s = getsel(),
@@ -955,6 +968,10 @@ var set_lno = (function () {
md_p_jump(dn);
return false;
}
if (ev.code == "KeyX" || ev.code == "KeyC") {
md_cut(ev.code == "KeyX");
return true; //sic
}
}
else {
if (ev.code == "Tab" || kc == 9) {

View File

@@ -1,6 +1,6 @@
<!DOCTYPE html><html><head>
<meta charset="utf-8">
<title>📝🎉 {{ title }}</title>
<title>📝 {{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7">
{{ html_head }}
@@ -26,7 +26,8 @@
<script>
var last_modified = {{ lastmod }},
have_emp = {{ have_emp|tojson }};
have_emp = {{ have_emp|tojson }},
dfavico = "{{ favico }}";
var md_opt = {
link_md_as_html: false,

View File

@@ -97,7 +97,9 @@
<a href="#" id="repl">π</a>
<script>
var lang="{{ this.args.lang }}";
var lang="{{ lang }}",
dfavico="{{ favico }}";
document.documentElement.className=localStorage.theme||"{{ this.args.theme }}";
</script>

View File

@@ -190,6 +190,18 @@ html.y #tth {
color: #000;
background: #fff;
}
#cf_frame {
position: fixed;
z-index: 573;
top: 3em;
left: 50%;
width: 40em;
height: 30em;
margin-left: -20.2em;
border-radius: .4em;
border: .4em solid var(--fg);
box-shadow: 0 2em 4em 1em var(--bg-max);
}
#modal {
position: fixed;
overflow: auto;

View File

@@ -15,6 +15,7 @@ function goto_up2k() {
// chrome requires https to use crypto.subtle,
// usually it's undefined but some chromes throw on invoke
var up2k = null,
up2k_hooks = [],
sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
m = 'will use ' + sha_js + ' instead of native sha512 due to';
@@ -135,14 +136,19 @@ function up2k_flagbus() {
}
function U2pvis(act, btns, uc) {
function U2pvis(act, btns, uc, st) {
var r = this;
r.act = act;
r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
r.tab = [];
r.hq = {};
r.head = 0;
r.tail = -1;
r.wsz = 3;
r.npotato = 99;
r.modn = 0;
r.modv = 0;
r.mod0 = null;
var markup = {
'404': '<span class="err">404</span>',
@@ -180,6 +186,9 @@ function U2pvis(act, btns, uc) {
};
r.is_act = function (card) {
if (uc.potato && !uc.fsearch)
return false;
if (r.act == "done")
return card == "ok" || card == "ng";
@@ -196,11 +205,14 @@ function U2pvis(act, btns, uc) {
if (!r.is_act(fo.in))
return;
var obj = ebi('f{0}{1}'.format(nfile, field.slice(1)));
var k = 'f{0}{1}'.format(nfile, field.slice(1)),
obj = ebi(k);
obj.innerHTML = field == 'ht' ? (markup[html] || html) : html;
if (field == 'hp') {
obj.style.color = '';
obj.style.background = '';
delete r.hq[nfile];
}
};
@@ -244,12 +256,9 @@ function U2pvis(act, btns, uc) {
if (!r.is_act(fo.in))
return;
var obj = ebi('f{0}p'.format(fobj.n)),
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
var o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
obj.innerHTML = fo.hp;
obj.style.color = '#fff';
obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)';
r.hq[fobj.n] = [fo.hp, '#fff', 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)'];
};
r.prog = function (fobj, nchunk, cbd) {
@@ -304,9 +313,7 @@ function U2pvis(act, btns, uc) {
throw new Error('see console');
}
obj.innerHTML = fo.hp;
obj.style.color = '#fff';
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)';
r.hq[fobj.n] = [fo.hp, '#fff', 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #222 ' + o3 + '%, #222 99%, #555)'];
};
r.move = function (nfile, newcat) {
@@ -320,6 +327,10 @@ function U2pvis(act, btns, uc) {
fo.in = newcat;
r.ctr[oldcat]--;
r.ctr[newcat]++;
while (st.car < r.tab.length && has(['ok', 'ng'], r.tab[st.car].in))
st.car++;
r.drawcard(oldcat);
r.drawcard(newcat);
if (r.is_act(newcat)) {
@@ -342,7 +353,10 @@ function U2pvis(act, btns, uc) {
};
r.bzw = function () {
var first = QS('#u2tab>tbody>tr:first-child');
var mod = 0,
t0 = Date.now(),
first = QS('#u2tab>tbody>tr:first-child');
if (!first)
return;
@@ -352,14 +366,131 @@ function U2pvis(act, btns, uc) {
while (r.head - first > r.wsz) {
qsr('#f' + (first++));
mod++;
}
while (last - r.tail < r.wsz && last < r.tab.length - 2) {
while (last - r.tail < r.wsz && last < r.tab.length - 1) {
var obj = ebi('f' + (++last));
if (!obj)
if (!obj) {
r.addrow(last);
mod++;
}
}
if (mod && r.modn < 200 && ebi('repl').offsetTop) {
if (++r.modn >= 10) {
if (r.modn == 10)
r.mod0 = Date.now();
r.modv += Date.now() - t0;
}
if (r.modn >= 200) {
var n = r.modn - 10,
ipu = r.modv / n,
spu = (Date.now() - r.mod0) / n,
ir = spu / ipu;
console.log('bzw:', f2f(ipu, 2), ' spu:', f2f(spu, 2), ' ir:', f2f(ir, 2), ' tab:', r.tab.length);
// efficiency estimates;
// ir: 5=16% 4=50%,30% 27=100%
// ipu: 2.7=16% 2=30% 1.6=50% 1.8=100% (ng for big files)
if (ipu >= 1.5 && ir <= 9 && r.tab.length >= 1000 && r.tab[Math.floor(r.tab.length / 3)].bt <= 1024 * 1024 * 4)
r.go_potato();
}
}
};
r.potatolabels = function () {
var ode = ebi('u2depotato'),
oen = ebi('u2enpotato');
if (!ode)
return;
ode.style.display = uc.potato ? '' : 'none';
oen.style.display = uc.potato ? 'none' : '';
}
r.potato = function () {
ebi('u2tabw').style.minHeight = '';
QS('#u2cards a[act="bz"]').click();
timer[uc.potato ? "add" : "rm"](draw_potato);
timer[uc.potato ? "rm" : "add"](apply_html);
r.potatolabels();
};
r.go_potato = function () {
r.go_potato = noop;
var ode = mknod('div', 'u2depotato'),
oen = mknod('div', 'u2enpotato'),
u2f = ebi('u2foot'),
btn = ebi('potato');
ode.innerHTML = L.u_depot;
oen.innerHTML = L.u_enpot;
if (sread('potato') === null) {
btn.click();
toast.inf(30, L.u_gotpot);
localStorage.removeItem('potato');
}
u2f.appendChild(ode);
u2f.appendChild(oen);
ode.onclick = oen.onclick = btn.onclick;
r.potatolabels();
};
function draw_potato() {
if (++r.npotato < 2)
return;
r.npotato = 0;
var html = [
"<p>files: &nbsp; <b>{0}</b> finished, &nbsp; <b>{1}</b> failed, &nbsp; <b>{2}</b> busy, &nbsp; <b>{3}</b> queued</p>".format(r.ctr.ok, r.ctr.ng, r.ctr.bz, r.ctr.q),
];
while (r.head < r.tab.length && has(["ok", "ng"], r.tab[r.head].in))
r.head++;
var act = null;
if (r.head < r.tab.length)
act = r.tab[r.head];
if (act)
html.push("<p>file {0} of {1} : &nbsp; {2} &nbsp; <code>{3}</code></p>\n<div>{4}</div>".format(r.head + 1, r.tab.length, act.ht, act.hp, act.hn));
html = html.join('\n');
if (r.hpotato == html)
return;
r.hpotato = html;
ebi('u2mu').innerHTML = html;
}
function apply_html() {
var oq = {}, n = 0;
for (var k in r.hq) {
var o = ebi('f{0}p'.format(k));
if (!o)
continue;
oq[k] = o;
n++;
}
if (!n)
return;
for (var k in oq) {
var o = oq[k],
v = r.hq[k];
o.innerHTML = v[0];
o.style.color = v[1];
o.style.background = v[2];
}
r.hq = {};
}
r.drawcard = function (cat) {
var cards = QSA('#u2cards>a>span');
@@ -441,8 +572,7 @@ function U2pvis(act, btns, uc) {
if (as_html)
return '<tr id="f' + nfile + '">' + ret + '</tr>';
var obj = mknod('tr');
obj.setAttribute('id', 'f' + nfile);
var obj = mknod('tr', 'f' + nfile);
obj.innerHTML = ret;
return obj;
};
@@ -476,6 +606,7 @@ function U2pvis(act, btns, uc) {
}
r.changecard(r.act);
r.potato();
}
@@ -527,7 +658,7 @@ function Donut(uc, st) {
}
r.on = function (ya) {
r.fc = r.tc = 99;
r.fc = r.tc = r.dc = 99;
r.eta = null;
r.base = pos();
optab.innerHTML = ya ? svg() : optab.getAttribute('ico');
@@ -543,10 +674,16 @@ function Donut(uc, st) {
var t = st.bytes.total - r.base,
v = pos() - r.base,
ofs = el.style.strokeDashoffset = o - o * v / t;
ofs = o - o * v / t;
if (!uc.potato || ++r.dc >= 4) {
el.style.strokeDashoffset = ofs;
r.dc = 0;
}
if (++r.tc >= 10) {
wintitle(f2f(v * 100 / t, 1) + '%, ' + r.eta + 's, ', true);
wintitle("{0}%, {1}s, #{2}, ".format(
f2f(v * 100 / t, 1), r.eta, st.files.length - st.nfile.upload), true);
r.tc = 0;
}
@@ -578,6 +715,12 @@ function fsearch_explain(n) {
function up2k_init(subtle) {
var r = {
"init_deps": init_deps,
"set_fsearch": set_fsearch,
"gotallfiles": [gotallfiles] // hooks
};
function showmodal(msg) {
ebi('u2notbtn').innerHTML = msg;
ebi('u2btn').style.display = 'none';
@@ -612,7 +755,10 @@ function up2k_init(subtle) {
m = L.u_ancient;
setmsg('');
}
ebi('u2foot').innerHTML = '<big>' + m + '</big>';
qsr('#u2depmsg');
var o = mknod('div', 'u2depmsg');
o.innerHTML = m;
ebi('u2foot').appendChild(o);
}
loading_deps = true;
}
@@ -647,17 +793,24 @@ function up2k_init(subtle) {
var parallel_uploads = icfg_get('nthread'),
uc = {},
fdom_ctr = 0,
min_filebuf = 0;
min_filebuf = 0,
biggest_file = 0;
bcfg_bind(uc, 'multitask', 'multitask', true, null, false);
bcfg_bind(uc, 'potato', 'potato', false, set_potato, false);
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
bcfg_bind(uc, 'flag_en', 'flag_en', false, apply_flag_cfg);
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo, false);
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null, false);
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort, false);
var st = {
"files": [],
"nfile": {
"hash": 0,
"upload": 0
},
"seen": {},
"todo": {
"head": [],
@@ -681,7 +834,11 @@ function up2k_init(subtle) {
"hashing": 0,
"uploading": 0,
"busy": 0
}
},
"car": 0,
"modn": 0,
"modv": 0,
"mod0": null
};
function push_t(arr, t) {
@@ -693,9 +850,13 @@ function up2k_init(subtle) {
});
}
var pvis = new U2pvis("bz", '#u2cards', uc),
var pvis = new U2pvis("bz", '#u2cards', uc, st),
donut = new Donut(uc, st);
r.ui = pvis;
r.st = st;
r.uc = uc;
var bobslice = null;
if (window.File)
bobslice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
@@ -808,6 +969,8 @@ function up2k_init(subtle) {
if (err)
return modal.alert('sorry, ' + err);
toast.inf(0, 'Scanning files...');
if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
tgl_fsearch();
@@ -906,7 +1069,8 @@ function up2k_init(subtle) {
if (!dirs.length) {
if (!pf.length)
return gotallfiles(good, nil, bad);
// call first hook, pass list of remaining hooks to call
return r.gotallfiles[0](good, nil, bad, r.gotallfiles.slice(1));
console.log("retry pf, " + pf.length);
setTimeout(function () {
@@ -954,6 +1118,12 @@ function up2k_init(subtle) {
}
function gotallfiles(good_files, nil_files, bad_files) {
if (toast.txt == 'Scanning files...')
toast.hide();
if (uc.fsearch && !uc.turbo)
nil_files = [];
var ntot = good_files.concat(nil_files, bad_files).length;
if (bad_files.length) {
var msg = L.u_badf.format(bad_files.length, ntot);
@@ -1002,6 +1172,13 @@ function up2k_init(subtle) {
var evpath = get_evpath(),
draw_each = good_files.length < 50;
if (!uc.az)
good_files.sort(function (a, b) {
a = a[0].size;
b = b[0].size;
return a < b ? -1 : a > b ? 1 : 0;
});
for (var a = 0; a < good_files.length; a++) {
var fobj = good_files[a][0],
name = good_files[a][1],
@@ -1032,6 +1209,9 @@ function up2k_init(subtle) {
if (uc.fsearch)
entry.srch = 1;
if (biggest_file < entry.size)
biggest_file = entry.size;
try {
if (st.seen[fdir][key])
continue;
@@ -1047,9 +1227,9 @@ function up2k_init(subtle) {
entry.purl + uricom_enc(entry.name)).join(' '),
'📐 ' + L.u_hashing,
''
], fobj.size, draw_each);
], entry.size, draw_each);
st.bytes.total += fobj.size;
st.bytes.total += entry.size;
st.files.push(entry);
if (!entry.size)
push_t(st.todo.handshake, entry);
@@ -1062,6 +1242,7 @@ function up2k_init(subtle) {
pvis.drawcard("q");
pvis.changecard(pvis.act);
}
ebi('u2tabw').className = 'ye';
}
function more_one_file() {
@@ -1073,7 +1254,7 @@ function up2k_init(subtle) {
}
more_one_file();
var etaref = 0, etaskip = 0, utw_minh = 0;
var etaref = 0, etaskip = 0, utw_minh = 0, utw_read = 0;
function etafun() {
var nhash = st.busy.head.length + st.busy.hash.length + st.todo.head.length + st.todo.hash.length,
nsend = st.busy.upload.length + st.todo.upload.length,
@@ -1086,6 +1267,11 @@ function up2k_init(subtle) {
//ebi('acc_info').innerHTML = humantime(st.time.busy) + ' ' + f2f(now / 1000, 1);
if (++utw_read >= 20) {
utw_read = 0;
utw_minh = parseInt(ebi('u2tabw').style.minHeight || '0');
}
var minh = QS('#op_up2k.act') && st.is_busy ? Math.max(utw_minh, ebi('u2tab').offsetHeight + 32) : 0;
if (utw_minh < minh || !utw_minh) {
utw_minh = minh;
@@ -1093,11 +1279,11 @@ function up2k_init(subtle) {
}
if (!nhash)
ebi('u2etah').innerHTML = L.u_etadone.format(humansize(st.bytes.hashed), pvis.ctr["ok"] + pvis.ctr["ng"]);
ebi('u2etah').innerHTML = L.u_etadone.format(humansize(st.bytes.hashed), pvis.ctr.ok + pvis.ctr.ng);
if (!nsend && !nhash)
ebi('u2etau').innerHTML = ebi('u2etat').innerHTML = (
L.u_etadone.format(humansize(st.bytes.uploaded), pvis.ctr["ok"] + pvis.ctr["ng"]));
L.u_etadone.format(humansize(st.bytes.uploaded), pvis.ctr.ok + pvis.ctr.ng));
if (!st.busy.hash.length && !hashing_permitted())
nhash = 0;
@@ -1183,9 +1369,14 @@ function up2k_init(subtle) {
return false;
if (uc.multitask) {
var ahead = st.bytes.hashed - st.bytes.finished;
return ahead < 1024 * 1024 * 1024 * 4 &&
st.todo.handshake.length + st.busy.handshake.length < 16;
if (!uc.az)
return st.todo.handshake.length + st.busy.handshake.length < 2;
var ahead = st.bytes.hashed - st.bytes.finished,
nmax = ahead < biggest_file / 8 ? 32 : 16;
return ahead < biggest_file &&
st.todo.handshake.length + st.busy.handshake.length < nmax;
}
return handshakes_permitted() && 0 ==
st.todo.handshake.length +
@@ -1212,16 +1403,7 @@ function up2k_init(subtle) {
running = true;
while (true) {
var now = Date.now(),
oldest_active = Math.min( // gzip take the wheel
st.todo.head.length ? st.todo.head[0].n : st.files.length,
st.todo.hash.length ? st.todo.hash[0].n : st.files.length,
st.todo.upload.length ? st.todo.upload[0].nfile : st.files.length,
st.todo.handshake.length ? st.todo.handshake[0].n : st.files.length,
st.busy.head.length ? st.busy.head[0].n : st.files.length,
st.busy.hash.length ? st.busy.hash[0].n : st.files.length,
st.busy.upload.length ? st.busy.upload[0].nfile : st.files.length,
st.busy.handshake.length ? st.busy.handshake[0].n : st.files.length),
is_busy = oldest_active < st.files.length;
is_busy = st.car < st.files.length;
if (was_busy && !is_busy) {
for (var a = 0; a < st.files.length; a++) {
@@ -1249,25 +1431,7 @@ function up2k_init(subtle) {
donut.on(is_busy);
if (!is_busy) {
var sr = uc.fsearch,
ok = pvis.ctr["ok"],
ng = pvis.ctr["ng"],
t = uc.ask_up ? 0 : 10;
if (ok && ng)
toast.warn(t, (sr ? L.ur_sm : L.ur_um).format(ok, ng));
else if (ok > 1)
toast.ok(t, (sr ? L.ur_aso : L.ur_auo).format(ok));
else if (ok)
toast.ok(t, sr ? L.ur_1so : L.ur_1uo);
else if (ng > 1)
toast.err(t, (sr ? L.ur_asn : L.ur_aun).format(ng));
else if (ng)
toast.err(t, sr ? L.ur_1sn : L.ur_1un);
timer.rm(etafun);
timer.rm(donut.do);
utw_minh = 0;
uptoast();
}
else {
timer.add(donut.do);
@@ -1320,7 +1484,7 @@ function up2k_init(subtle) {
if (st.todo.head.length &&
st.busy.head.length < parallel_uploads &&
(!is_busy || st.todo.head[0].n - oldest_active < parallel_uploads * 2)) {
(!is_busy || st.todo.head[0].n - st.car < parallel_uploads * 2)) {
exec_head();
mou_ikkai = true;
}
@@ -1345,6 +1509,31 @@ function up2k_init(subtle) {
mou_ikkai = true;
}
if (is_busy && st.modn < 100) {
var t0 = Date.now() + (ebi('repl').offsetTop ? 0 : 0);
if (++st.modn >= 10) {
if (st.modn == 10)
st.mod0 = Date.now();
st.modv += Date.now() - t0;
}
if (st.modn >= 100) {
var n = st.modn - 10,
ipu = st.modv / n,
spu = (Date.now() - st.mod0) / n,
ir = spu / ipu;
console.log('tsk:', f2f(ipu, 2), ' spu:', f2f(spu, 2), ' ir:', f2f(ir, 2));
// efficiency estimates;
// ir: 8=16% 11=60% 16=90% 24=100%
// ipu: 1=40% .8=60% .3=100%
if (ipu >= 0.5 && ir <= 15)
pvis.go_potato();
}
}
if (!mou_ikkai || crashed)
return defer();
}
@@ -1353,6 +1542,30 @@ function up2k_init(subtle) {
return taskerd;
})();
function uptoast() {
var sr = uc.fsearch,
ok = pvis.ctr.ok,
ng = pvis.ctr.ng,
t = uc.ask_up ? 0 : 10;
console.log('toast', ok, ng);
if (ok && ng)
toast.warn(t, (sr ? L.ur_sm : L.ur_um).format(ok, ng));
else if (ok > 1)
toast.ok(t, (sr ? L.ur_aso : L.ur_auo).format(ok));
else if (ok)
toast.ok(t, sr ? L.ur_1so : L.ur_1uo);
else if (ng > 1)
toast.err(t, (sr ? L.ur_asn : L.ur_aun).format(ng));
else if (ng)
toast.err(t, sr ? L.ur_1sn : L.ur_1un);
timer.rm(etafun);
timer.rm(donut.do);
utw_minh = 0;
}
function chill(t) {
var now = Date.now();
if ((t.coolmul || 0) < 2 || now - t.cooldown < t.coolmul * 700)
@@ -1428,6 +1641,7 @@ function up2k_init(subtle) {
function exec_hash() {
var t = st.todo.hash.shift();
st.busy.hash.push(t);
st.nfile.hash = t.n;
var bpend = 0,
nchunk = 0,
@@ -1465,7 +1679,7 @@ function up2k_init(subtle) {
hash_calc(nch, e.target.result);
}
reader.onload = function (e) {
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
};
reader.onerror = function () {
var err = reader.error + '';
@@ -1498,7 +1712,7 @@ function up2k_init(subtle) {
var hash_calc = function (nch, buf) {
while (segm_next());
var hash_done = function (hashbuf) {
var orz = function (hashbuf) {
var hslice = new Uint8Array(hashbuf).subarray(0, 33),
b64str = buf2b64(hslice);
@@ -1524,6 +1738,10 @@ function up2k_init(subtle) {
tasker();
};
var hash_done = function (hashbuf) {
try { orz(hashbuf); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
};
if (subtle)
subtle.digest('SHA-512', buf).then(hash_done);
else setTimeout(function () {
@@ -1591,7 +1809,7 @@ function up2k_init(subtle) {
tasker();
};
xhr.onload = function (e) {
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
};
xhr.open('HEAD', t.purl + uricom_enc(t.name) + '?raw', true);
@@ -1747,6 +1965,8 @@ function up2k_init(subtle) {
f2f(spd1, 2), isNaN(spd2) ? '--' : f2f(spd2, 2)));
pvis.move(t.n, 'ok');
if (!pvis.ctr.bz && !pvis.ctr.q)
uptoast();
}
else {
if (t.t_uploaded)
@@ -1805,7 +2025,7 @@ function up2k_init(subtle) {
}
}
xhr.onload = function (e) {
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
try { orz(e); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
};
var req = {
@@ -1844,6 +2064,7 @@ function up2k_init(subtle) {
function exec_upload() {
var upt = st.todo.upload.shift();
st.busy.upload.push(upt);
st.nfile.upload = upt.nfile;
var npart = upt.npart,
t = st.files[upt.nfile],
@@ -1891,12 +2112,15 @@ function up2k_init(subtle) {
tasker();
}
function do_send() {
var xhr = new XMLHttpRequest();
var xhr = new XMLHttpRequest(),
bfin = Math.floor(st.bytes.finished / 1024 / 1024),
btot = Math.floor(st.bytes.total / 1024 / 1024);
xhr.upload.onprogress = function (xev) {
pvis.prog(t, npart, xev.loaded);
};
xhr.onload = function (xev) {
try { orz(xhr); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
try { orz(xhr); } catch (ex) { vis_exh(ex + '', 'up2k.js', '', '', ex); }
};
xhr.onerror = function (xev) {
if (crashed)
@@ -1911,6 +2135,8 @@ function up2k_init(subtle) {
xhr.open('POST', t.purl, true);
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
xhr.setRequestHeader("X-Up2k-Stat", "{0}/{1}/{2}/{3} {4}/{5}".format(
pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin));
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
if (xhr.overrideMimeType)
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
@@ -1942,7 +2168,7 @@ function up2k_init(subtle) {
ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
}
wide = write && wem > 78 ? 'ww' : wide;
wide = write && wem > 82 ? 'ww' : wide;
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
var its = [ebi('u2etaw'), ebi('u2cards')];
if (its[0].parentNode !== parent) {
@@ -2016,24 +2242,27 @@ function up2k_init(subtle) {
}
function draw_turbo() {
var msg = uc.fsearch ? L.u_ts : L.u_tu,
omsg = uc.fsearch ? L.u_tu : L.u_ts,
html = ebi('u2foot').innerHTML,
ohtml = html;
var msg = (turbolvl || !uc.turbo) ? null : uc.fsearch ? L.u_ts : L.u_tu,
html = ebi('u2foot').innerHTML;
if (turbolvl || !uc.turbo)
msg = null;
if (msg && html.indexOf(msg) + 1)
return;
if (msg && html.indexOf(msg) === -1)
html = html.replace(omsg, '') + msg;
else if (!msg)
html = html.replace(L.u_tu, '').replace(L.u_ts, '');
qsr('#u2turbomsg');
if (!msg)
return;
if (html !== ohtml)
ebi('u2foot').innerHTML = html;
var o = mknod('div', 'u2turbomsg');
o.innerHTML = msg;
ebi('u2foot').appendChild(o);
}
draw_turbo();
function set_potato() {
pvis.potato();
set_fsearch();
}
function set_fsearch(new_state) {
var fixed = false;
@@ -2072,6 +2301,10 @@ function up2k_init(subtle) {
ebi('u2tab').className = (uc.fsearch ? 'srch ' : 'up ') + pvis.act;
var potato = uc.potato && !uc.fsearch;
ebi('u2cards').style.display = ebi('u2tab').style.display = potato ? 'none' : '';
ebi('u2mu').style.display = potato ? '' : 'none';
draw_turbo();
onresize();
}
@@ -2095,9 +2328,12 @@ function up2k_init(subtle) {
}
}
function nop(e) {
ev(e);
this.click();
function set_u2sort() {
if (u2sort.indexOf('f') < 0)
return;
bcfg_set('u2sort', uc.az = u2sort.indexOf('n') + 1);
localStorage.removeItem('u2sort');
}
ebi('nthread_add').onclick = function (e) {
@@ -2122,7 +2358,12 @@ function up2k_init(subtle) {
if (parallel_uploads < 1)
bumpthread(1);
return { "init_deps": init_deps, "set_fsearch": set_fsearch, "ui": pvis, "st": st, "uc": uc }
setTimeout(function () {
for (var a = 0; a < up2k_hooks.length; a++)
up2k_hooks[a]();
}, 1);
return r;
}
@@ -2138,8 +2379,8 @@ favico.init();
ebi('ico1').onclick = function () {
var a = favico.txt == this.textContent;
swrite('icot', a ? 'c' : this.textContent);
swrite('icof', a ? null : '000');
swrite('icob', a ? null : '');
swrite('icof', a ? 'fc5' : '000');
swrite('icob', a ? '222' : '');
favico.init();
};

View File

@@ -6,7 +6,8 @@ if (!window['console'])
};
var is_touch = 'ontouchstart' in window,
var wah = '',
is_touch = 'ontouchstart' in window,
is_https = (window.location + '').indexOf('https:') === 0,
IPHONE = is_touch && /iPhone|iPad|iPod/i.test(navigator.userAgent),
WINDOWS = navigator.platform ? navigator.platform == 'Win32' : /Windows/.test(navigator.userAgent);
@@ -15,10 +16,18 @@ var is_touch = 'ontouchstart' in window,
var ebi = document.getElementById.bind(document),
QS = document.querySelector.bind(document),
QSA = document.querySelectorAll.bind(document),
mknod = document.createElement.bind(document),
XHR = XMLHttpRequest;
function mknod(et, eid) {
var ret = document.createElement(et);
if (eid)
ret.id = eid;
return ret;
}
function qsr(sel) {
var el = QS(sel);
if (el)
@@ -159,8 +168,7 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
try {
var exbox = ebi('exbox');
if (!exbox) {
exbox = mknod('div');
exbox.setAttribute('id', 'exbox');
exbox = mknod('div', 'exbox');
document.body.appendChild(exbox);
var s = mknod('style');
@@ -221,6 +229,11 @@ function ev(e) {
}
function noope(e) {
ev(e);
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
if (!String.prototype.endsWith)
String.prototype.endsWith = function (search, this_len) {
@@ -865,8 +878,8 @@ var timer = (function () {
var tt = (function () {
var r = {
"tt": mknod("div"),
"th": mknod("div"),
"tt": mknod("div", 'tt'),
"th": mknod("div", 'tth'),
"en": true,
"el": null,
"skip": false,
@@ -874,8 +887,6 @@ var tt = (function () {
};
r.th.innerHTML = '?';
r.tt.setAttribute('id', 'tt');
r.th.setAttribute('id', 'tth');
document.body.appendChild(r.tt);
document.body.appendChild(r.th);
@@ -1044,9 +1055,8 @@ var toast = (function () {
var r = {},
te = null,
scrolling = false,
obj = mknod('div');
obj = mknod('div', 'toast');
obj.setAttribute('id', 'toast');
document.body.appendChild(obj);
r.visible = false;
r.txt = null;
@@ -1129,8 +1139,7 @@ var modal = (function () {
r.busy = false;
r.show = function (html) {
o = mknod('div');
o.setAttribute('id', 'modal');
o = mknod('div', 'modal');
o.innerHTML = '<table><tr><td><div id="modalc">' + html + '</div></td></tr></table>';
document.body.appendChild(o);
document.addEventListener('keydown', onkey);
@@ -1185,7 +1194,8 @@ var modal = (function () {
return;
setTimeout(function () {
ebi('modal-ok').focus();
if (ctr = ebi('modal-ok'))
ctr.focus();
}, 20);
ev(e);
}
@@ -1426,12 +1436,24 @@ var favico = (function () {
var b64;
try {
b64 = btoa(svg ? svg_decl + svg : gx(r.txt));
//console.log('f1');
}
catch (ex) {
b64 = encodeURIComponent(r.txt).replace(/%([0-9A-F]{2})/g,
function x(m, v) { return String.fromCharCode('0x' + v); });
b64 = btoa(gx(unescape(encodeURIComponent(r.txt))));
catch (e1) {
try {
b64 = btoa(gx(encodeURIComponent(r.txt).replace(/%([0-9A-F]{2})/g,
function x(m, v) { return String.fromCharCode('0x' + v); })));
//console.log('f2');
}
catch (e2) {
try {
b64 = btoa(gx(unescape(encodeURIComponent(r.txt))));
//console.log('f3');
}
catch (e3) {
//console.log('fe');
return;
}
}
}
if (!r.tag) {
@@ -1444,9 +1466,13 @@ var favico = (function () {
r.init = function () {
clearTimeout(r.to);
scfg_bind(r, 'txt', 'icot', '', r.upd);
scfg_bind(r, 'fg', 'icof', 'fc5', r.upd);
scfg_bind(r, 'bg', 'icob', '222', r.upd);
var dv = (window.dfavico || '').trim().split(/ +/),
fg = dv.length < 2 ? 'fc5' : dv[1].toLowerCase() == 'none' ? '' : dv[1],
bg = dv.length < 3 ? '222' : dv[2].toLowerCase() == 'none' ? '' : dv[2];
scfg_bind(r, 'txt', 'icot', dv[0], r.upd);
scfg_bind(r, 'fg', 'icof', fg, r.upd);
scfg_bind(r, 'bg', 'icob', bg, r.upd);
r.upd();
};
@@ -1455,6 +1481,7 @@ var favico = (function () {
})();
var cf_cha_t = 0;
function xhrchk(xhr, prefix, e404) {
if (xhr.status < 400 && xhr.status >= 200)
return true;
@@ -1465,6 +1492,23 @@ function xhrchk(xhr, prefix, e404) {
if (xhr.status == 404)
return toast.err(0, prefix + e404);
return toast.err(0, prefix + xhr.status + ": " + (
(xhr.response && xhr.response.err) || xhr.responseText));
var errtxt = (xhr.response && xhr.response.err) || xhr.responseText,
fun = toast.err;
if (xhr.status == 503 && /\bDD(?:wah){0}[o]S [Pp]rote[c]tion|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser/.test(errtxt)) {
var now = Date.now(), td = now - cf_cha_t;
if (td < 15000)
return;
cf_cha_t = now;
errtxt = 'Cloudflare DD' + wah + 'oS protection kicked in\n\n<strong>trying to fix it...</strong>';
fun = toast.warn;
qsr('#cf_frame');
var fr = mknod('iframe', 'cf_frame');
fr.src = '/?cf_challenge';
document.body.appendChild(fr);
}
return fun(0, prefix + xhr.status + ": " + errtxt);
}

View File

@@ -1,3 +1,48 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2022-0627-2057 `v1.3.3` sdcardfs
* **new:** read-only demo server at https://a.ocv.me/pub/demo/
* latest gzip edition of the sfx: [v1.0.14](https://github.com/9001/copyparty/releases/tag/v1.0.14#:~:text=release-specific%20notes)
## bugfixes
* **upload:** downgrade filenames to ascii if the server filesystem requires it
* **android fix:** external sdcard seems to be UCS-2 which can't into emojis
* **upload:** accurate detection of support for sparse files
* now based on filesystem behavior rather than a list of known filesystems
* **android fix:** all storage is `sdcardfs` so the list wasn't good enough
* **ux:** custom css/js did not apply to write-only folders
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2022-0619-2331 `v1.3.2` think im out of titles
* **new:** read-only demo server at https://a.ocv.me/pub/demo/
* latest gzip edition of the sfx: [v1.0.14](https://github.com/9001/copyparty/releases/tag/v1.0.14#:~:text=release-specific%20notes)
## new features
* new option `--thickfs` to modify the list of filesystems that dont support sparse files
* default should catch most usual cases but I probably missed some
* detect and warn if filesystem was expected to support sparse files yet doesn't
## bugfixes
* nonsparse: ensure chunks are flushed on linux as well
* switching between documents
* ctrl-clicking a breadcrumb entry didn't open a new tab as expected
* renaming files based on artist/title/etc tags would create subdirectories if tags contained `/`
* not dangerous -- the server correctly prevented any path traversals -- just unexpected
* markdown stuff
* numbered lists appeared as bullet-lists
* don't crash if a plugin sets a buggy timer
* plugins didn't run when viewing `README.md` inline
## other changes
* in the `-ss` safety preset, replace `no-dot-mv, no-dot-ren` with `no-logues, no-readme`
* audio player continues into the next folder by default
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2022-0616-1956 `v1.3.1` types

View File

@@ -48,7 +48,10 @@ avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} c
## time between first and last upload
python3 -um copyparty -nw -v srv::rw -i 127.0.0.1 2>&1 | tee log
cat log | awk '!/"purl"/{next} {s=$1;sub(/[^m]+m/,"");gsub(/:/," ");t=60*(60*$1+$2)+$3} !a{a=t;sa=s} {b=t;sb=s} END {print b-a,sa,sb}'
cat log | awk '!/"purl"/{next} {s=$1;sub(/[^m]+m/,"");gsub(/:/," ");t=60*(60*$1+$2)+$3} t<p{t+=86400} !a{a=t;sa=s} {b=t;sb=s} END {print b-a,sa,sb}'
# or if the client youre measuring dies for ~15sec every once ina while and you wanna filter those out,
cat log | awk '!/"purl"/{next} {s=$1;sub(/[^m]+m/,"");gsub(/:/," ");t=60*(60*$1+$2)+$3} t<p{t+=86400} !p{a=t;p=t;r=0;next} t-p>1{printf "%.3f += %.3f - %.3f (%.3f) # %.3f -> %.3f\n",r,p,a,p-a,p,t;r+=p-a;a=t} {p=t} END {print r+p-a}'
##

View File

@@ -4,7 +4,7 @@ ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
ver_hashwasm=4.9.0 \
ver_marked=4.0.17 \
ver_mde=2.16.1 \
ver_codemirror=5.65.5 \
ver_codemirror=5.65.6 \
ver_fontawesome=5.13.0 \
ver_zopfli=1.0.3

View File

@@ -106,7 +106,7 @@ tmpdir="$(
[ $repack ] && {
old="$tmpdir/pe-copyparty"
echo "repack of files in $old"
cp -pR "$old/"*{j2,ftp,copyparty} .
cp -pR "$old/"*{py2,j2,ftp,copyparty} .
}
[ $repack ] || {

View File

@@ -22,13 +22,16 @@ v=$1
}
rm -f ../dist/copyparty-sfx.*
shift
./make-sfx.sh "$@"
f=../dist/copyparty-sfx.py
./make-sfx.sh
$f -h
[ -e $f ] ||
f=../dist/copyparty-sfx-gz.py
$f -h
while true; do
mv $f $f.$(wc -c <$f | awk '{print$1}')
./make-sfx.sh re $ar
./make-sfx.sh re "$@"
done
# git tag -d v$v; git push --delete origin v$v

View File

@@ -43,6 +43,7 @@ copyparty/web/browser.html,
copyparty/web/browser.js,
copyparty/web/browser2.html,
copyparty/web/copyparty.gif,
copyparty/web/cf.html,
copyparty/web/dd,
copyparty/web/dd/2.png,
copyparty/web/dd/3.png,

View File

@@ -224,11 +224,15 @@ def unpack():
"""unpacks the tar yielded by `data`"""
name = "pe-copyparty"
tag = "v" + str(STAMP)
withpid = "{}.{}".format(name, os.getpid())
top = tempfile.gettempdir()
opj = os.path.join
final = opj(top, name)
mine = opj(top, withpid)
for suf in range(0, 9001):
withpid = "{}.{}.{}".format(name, os.getpid(), suf)
mine = opj(top, withpid)
if not os.path.exists(mine):
break
tar = opj(mine, "tar")
try:
@@ -360,11 +364,12 @@ def utime(top):
def confirm(rv):
msg()
msg("retcode", rv if rv else traceback.format_exc())
msg("*** hit enter to exit ***")
try:
raw_input() if PY2 else input()
except:
pass
if WINDOWS:
msg("*** hit enter to exit ***")
try:
raw_input() if PY2 else input()
except:
pass
sys.exit(rv or 1)

View File

@@ -24,51 +24,34 @@ def hdr(query):
class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None):
ka = {}
ex = "e2d e2ds e2dsa e2t e2ts e2tsr ed emp force_js ihead no_acode no_athumb no_del no_logues no_mv no_readme no_robots no_scandir no_thumb no_vthumb no_zip nw"
ka.update(**{k: False for k in ex.split()})
ex = "nih no_rescan no_sendfile no_voldump"
ka.update(**{k: True for k in ex.split()})
ex = "css_browser hist js_browser no_hash no_idx"
ka.update(**{k: None for k in ex.split()})
ex = "re_maxage rproxy rsp_slp s_wr_slp theme themes turbo"
ka.update(**{k: 0 for k in ex.split()})
ex = "doctitle favico html_head mth textfiles"
ka.update(**{k: "" for k in ex.split()})
super(Cfg, self).__init__(
a=a or [],
v=v or [],
c=c,
rproxy=0,
rsp_slp=0,
s_wr_slp=0,
s_wr_sz=512 * 1024,
ed=False,
nw=False,
unpost=600,
no_mv=False,
no_del=False,
no_zip=False,
no_thumb=False,
no_athumb=False,
no_vthumb=False,
no_voldump=True,
no_scandir=False,
no_sendfile=True,
no_rescan=True,
no_logues=False,
no_readme=False,
re_maxage=0,
ihead=False,
nih=True,
mtp=[],
mte="a",
mth="",
textfiles="",
doctitle="",
html_head="",
lang="eng",
theme=0,
themes=0,
turbo=0,
logout=573,
hist=None,
no_idx=None,
no_hash=None,
force_js=False,
no_robots=False,
js_browser=None,
css_browser=None,
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr no_acode".split()}
**ka
)