Compare commits

...

108 Commits

Author SHA1 Message Date
ed
eeed2a840c v1.3.10 2022-08-04 01:40:14 +02:00
ed
4aaa111925 v1.3.9 2022-08-04 00:39:37 +02:00
ed
e31248f018 include version info on startup and in crash dumps 2022-08-04 00:11:52 +02:00
ed
8b4cf022f2 bbox: tweak end-of-gallery animation 2022-08-03 22:56:51 +02:00
ed
4e7455268a tag-scanner perf 2022-08-03 22:33:20 +02:00
ed
680f8ae814 add xdev/xvol indexing guards 2022-08-03 22:20:28 +02:00
ed
90555a4cea clean-shutdown while hashing huge files 2022-08-03 21:06:10 +02:00
ed
56a62db591 force-exit by hammering ctrl-c 2022-08-03 20:58:23 +02:00
ed
cf51997680 fix make-sfx.sh on windows/msys2 2022-08-03 20:01:54 +02:00
ed
f05cc18d61 add missing polyfill 2022-08-03 19:42:42 +02:00
ed
5384c2e0f5 reentrant cleanup 2022-08-02 20:56:05 +02:00
ed
9bfbf80a0e ui: fix navpane covering files on horizontal scroll 2022-08-02 20:48:26 +02:00
ed
f874d7754f ui: toggle sorting folders before files (default-on) 2022-08-02 20:47:17 +02:00
ed
a669f79480 windows upload perf (fat32, smb) 2022-08-02 20:39:51 +02:00
ed
1c3894743a fix filekeys inside symlinked volumes 2022-08-02 20:26:51 +02:00
ed
75cdf17df4 cache sparsefile-support on windows too 2022-08-02 06:58:25 +02:00
ed
de7dd1e60a more visible upload errors on mobile 2022-08-02 06:17:13 +02:00
ed
0ee574a718 forget uploads that failed to initialize 2022-08-02 06:15:18 +02:00
ed
faac894706 oh 2022-07-29 00:13:18 +02:00
ed
dac2fad48e v1.3.8 2022-07-27 16:07:26 +02:00
ed
77f624b01e improve shumantime + use it everywhere 2022-07-27 15:07:04 +02:00
ed
e24ffebfc8 indicate write-activity on splashpage 2022-07-27 14:53:15 +02:00
ed
70d07d1609 perf 2022-07-27 14:01:30 +02:00
ed
bfb3303d87 include client total ETA in upload logs 2022-07-27 12:07:51 +02:00
ed
660705a436 defer volume reindexing on db activity 2022-07-27 11:48:47 +02:00
ed
74a3f97671 cleanup + bump deps 2022-07-27 00:15:49 +02:00
ed
b3e35bb494 async lsof w/ timeout 2022-07-26 22:38:13 +02:00
ed
76adac7c72 up2k-hook-ytid: add mp4/webm/mkv metadata scanner 2022-07-26 22:09:18 +02:00
ed
5dc75ebb67 async e2ts / e2v + forget deleted shadowed 2022-07-26 12:47:40 +02:00
ed
d686ce12b6 lsof db on stuck transaction 2022-07-25 02:07:59 +02:00
ed
d3c40a423e mutagen: support nullduration tags 2022-07-25 01:21:34 +02:00
ed
2fb1e6dab8 mute exception on zip abort 2022-07-25 01:20:38 +02:00
ed
10430b347f fix dumb prisonparty bug 2022-07-22 20:49:35 +02:00
ed
e0e3f6ac3e up2k-hook-ytid: add override 2022-07-22 10:47:10 +02:00
ed
c694cbffdc a11y: improve skip-to-files 2022-07-20 23:44:57 +02:00
ed
bdd0e5d771 a11y: enter = onclick 2022-07-20 23:32:02 +02:00
ed
aa98e427f0 audio-eq: add crossfeed 2022-07-20 01:54:59 +02:00
ed
daa6f4c94c add video hotkeys for digit-seeking 2022-07-17 23:45:02 +02:00
ed
4a76663fb2 ensure free disk space 2022-07-17 22:33:08 +02:00
ed
cebda5028a v1.3.7 2022-07-16 20:48:23 +02:00
ed
3fa377a580 sqlite diag 2022-07-16 20:43:26 +02:00
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
ed
feb1acd43a v1.3.3 2022-06-27 22:57:05 +02:00
ed
a9591db734 cleanup 2022-06-27 22:56:29 +02:00
ed
9ebf148cbe support android9 sdcardfs on sdcard 2022-06-27 22:15:35 +02:00
ed
a473e5e19a always include custom css/js 2022-06-27 17:24:30 +02:00
ed
5d3034c231 detect sparse support from st_blocks 2022-06-23 18:23:42 +02:00
ed
c3a895af64 android sdcardfs can be fat32 2022-06-23 16:27:30 +02:00
63 changed files with 3296 additions and 849 deletions

13
.gitignore vendored
View File

@@ -5,13 +5,16 @@ __pycache__/
MANIFEST.in
MANIFEST
copyparty.egg-info/
buildenv/
build/
dist/
sfx/
py2/
.venv/
/buildenv/
/build/
/dist/
/py2/
/sfx/
/unt/
/log/
# ide
*.sublime-workspace

153
README.md
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
@@ -57,8 +56,11 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
* [server config](#server-config) - using arguments or config files, or a mix of both
* [ftp-server](#ftp-server) - an FTP server can be started using `--ftp 3921`
* [file indexing](#file-indexing)
* [upload rules](#upload-rules) - set upload rules using volume flags
* [file indexing](#file-indexing) - enables dedup and music search ++
* [exclude-patterns](#exclude-patterns) - to save some time
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
* [periodic rescan](#periodic-rescan) - filesystem monitoring
* [upload rules](#upload-rules) - set upload rules using volflags
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
@@ -103,7 +105,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 +113,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 +171,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 +207,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 +246,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 +278,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 +286,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
@@ -307,7 +312,7 @@ examples:
* `u1` can open the `inc` folder, but cannot see the contents, only upload new files to it
* `u2` can browse it and move files *from* `/inc` into any folder where `u2` has write-access
* make folder `/mnt/ss` available at `/i`, read-write for u1, get-only for everyone else, and enable accesskeys: `-v /mnt/ss:i:rw,u1:g:c,fk=4`
* `c,fk=4` sets the `fk` volume-flag to 4, meaning each file gets a 4-character accesskey
* `c,fk=4` sets the `fk` volflag to 4, meaning each file gets a 4-character accesskey
* `u1` can upload files, browse the folder, and see the generated accesskeys
* other users cannot browse the folder, but can access the files if they have the full file URL with the accesskey
@@ -339,7 +344,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,17 +369,20 @@ 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
* `Esc` close viewer
* videos:
* `U/O` skip 10sec back/forward
* `0..9` jump to 0%..90%
* `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 +414,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 +456,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 +474,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 +485,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 +498,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 +609,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 +632,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
@@ -649,7 +659,9 @@ an FTP server can be started using `--ftp 3921`, and/or `--ftps` for explicit T
## file indexing
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volume flags, or a mix of both.
enables dedup and music search ++
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volflags, or a mix of both.
through arguments:
* `-e2d` enables file indexing on upload
@@ -658,8 +670,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 volflags, 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*`
@@ -671,7 +686,9 @@ note:
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
to save some time, you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash \.iso$` or the volume-flag `:c,nohash=\.iso$`, this has the following consequences:
### exclude-patterns
to save some time, you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash \.iso$` or the volflag `:c,nohash=\.iso$`, this has the following consequences:
* initial indexing is way faster, especially when the volume is on a network disk
* makes it impossible to [file-search](#file-search)
* if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
@@ -680,12 +697,29 @@ similarly, you can fully ignore files/folders using `--no-idx [...]` and `:c,noi
if you set `--no-hash [...]` globally, you can enable hashing for specific volumes using flag `:c,nohash=`
### filesystem guards
avoid traversing into other filesystems using `--xdev` / volflag `:c,xdev`, skipping any symlinks or bind-mounts to another HDD for example
and/or you can `--xvol` / `:c,xvol` to ignore all symlinks leaving the volume's top directory, but still allow bind-mounts pointing elsewhere
**NB: only affects the indexer** -- users can still access anything inside a volume, unless shadowed by another volume
### periodic rescan
filesystem monitoring; if copyparty is not the only software doing stuff on your filesystem, you may want to enable periodic rescans to keep the index up to date
argument `--re-maxage 60` will rescan all volumes every 60 sec, same as volflag `:c,scan=60` to specify it per-volume
uploads are disabled while a rescan is happening, so rescans will be delayed by `--db-act` (default 10 sec) when there is write-activity going on (uploads, renames, ...)
## upload rules
set upload rules using volume flags, some examples:
set upload rules using volflags, some examples:
* `:c,sz=1k-3m` sets allowed filesize between 1 KiB and 3 MiB inclusive (suffixes: `b`, `k`, `m`, `g`)
* `:c,df=4g` block uploads if there would be less than 4 GiB free disk space afterwards
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
* `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1)
* `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format
@@ -704,16 +738,16 @@ you can also set transaction limits which apply per-IP and per-volume, but these
files can be autocompressed on upload, either on user-request (if config allows) or forced by server-config
* volume flag `gz` allows gz compression
* volume flag `xz` allows lzma compression
* volume flag `pk` **forces** compression on all files
* volflag `gz` allows gz compression
* volflag `xz` allows lzma compression
* volflag `pk` **forces** compression on all files
* url parameter `pk` requests compression with server-default algorithm
* url parameter `gz` or `xz` requests compression with a specific algorithm
* url parameter `xz` requests xz compression
things to note,
* the `gz` and `xz` arguments take a single optional argument, the compression level (range 0 to 9)
* the `pk` volume flag takes the optional argument `ALGORITHM,LEVEL` which will then be forced for all uploads, for example `gz,9` or `xz,0`
* the `pk` volflag takes the optional argument `ALGORITHM,LEVEL` which will then be forced for all uploads, for example `gz,9` or `xz,0`
* default compression is gzip level 9
* all upload methods except up2k are supported
* the files will be indexed after compression, so dupe-detection and file-search will not work as expected
@@ -733,7 +767,7 @@ in-volume (`.hist/up2k.db`, default) or somewhere else
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volflag, or a mix of both:
* `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
note:
@@ -771,27 +805,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 volflag), 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)
@@ -807,8 +846,8 @@ if this becomes popular maybe there should be a less janky way to do it actually
tell search engines you dont wanna be indexed, either using the good old [robots.txt](https://www.robotstxt.org/robotstxt.html) or through copyparty settings:
* `--no-robots` adds HTTP (`X-Robots-Tag`) and HTML (`<meta>`) headers with `noindex, nofollow` globally
* volume-flag `[...]:c,norobots` does the same thing for that single volume
* volume-flag `[...]:c,robots` ALLOWS search-engine crawling for that volume, even if `--no-robots` is set globally
* volflag `[...]:c,norobots` does the same thing for that single volume
* volflag `[...]:c,robots` ALLOWS search-engine crawling for that volume, even if `--no-robots` is set globally
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
@@ -838,8 +877,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 +993,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 00:01:09`
* 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` and `00:01:09` 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
@@ -1014,7 +1070,7 @@ some notes on hardening
other misc notes:
* you can disable directory listings by giving permission `g` instead of `r`, only accepting direct URLs to files
* combine this with volume-flag `c,fk` to generate per-file accesskeys; users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
* combine this with volflag `c,fk` to generate per-file accesskeys; users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
## gotchas
@@ -1207,11 +1263,15 @@ if you want thumbnails, `apt -y install ffmpeg`
ideas for context to include in bug reports
in general, commandline arguments (and config file if any)
if something broke during an upload (replacing FILENAME with a part of the filename that broke):
```
journalctl -aS '48 hour ago' -u copyparty | grep -C10 FILENAME | tee bug.log
```
if there's a wall of base64 in the log (thread stacks) then please include that, especially if you run into something freezing up or getting stuck, for example `OperationalError('database is locked')` -- alternatively you can visit `/?stack` to see the stacks live, so http://127.0.0.1:3923/?stack for example
# building
@@ -1259,10 +1319,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 +1346,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

@@ -42,7 +42,7 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
* `mtp` modules will not run if a file has existing tags in the db, so clear out the tags with `-e2tsr` the first time you launch with new `mtp` options
## usage with volume-flags
## usage with volflags
instead of affecting all volumes, you can set the options for just one volume like so:

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

@@ -11,13 +11,13 @@ sysdirs=( /bin /lib /lib32 /lib64 /sbin /usr )
help() { cat <<'EOF'
usage:
./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- python3 copyparty-sfx.py [...]"
./prisonparty.sh <ROOTDIR> <UID> <GID> [VOLDIR [VOLDIR...]] -- python3 copyparty-sfx.py [...]
example:
./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 copyparty-sfx.py -v /mnt/nas/music::rwmd"
./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 copyparty-sfx.py -v /mnt/nas/music::rwmd
example for running straight from source (instead of using an sfx):
PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd"
PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail 1000 1000 /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd
note that if you have python modules installed as --user (such as bpm/key detectors),
you should add /home/foo/.local as a VOLDIR

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,209 @@
// 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
//
// also tries to find the youtube-id in the embedded metadata
//
// 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 passthru = up2k.uc.fsearch;
if (passthru)
return hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
a_up2k_namefilter(good_files, nil_files, bad_files, hooks).then(() => { });
}
function bstrpos(buf, ptn) {
var ofs = 0,
ch0 = ptn[0],
sz = buf.byteLength;
while (true) {
ofs = buf.indexOf(ch0, ofs);
if (ofs < 0 || ofs >= sz)
return -1;
for (var a = 1; a < ptn.length; a++)
if (buf[ofs + a] !== ptn[a])
break;
if (a === ptn.length)
return ofs;
++ofs;
}
}
async function a_up2k_namefilter(good_files, nil_files, bad_files, hooks) {
var t0 = Date.now(),
yt_ids = new Set(),
textdec = new TextDecoder('latin1'),
md_ptn = new TextEncoder().encode('youtube.com/watch?v='),
file_ids = [], // all IDs found for each good_files
mofs = 0,
mnchk = 0,
mfile = '';
for (var a = 0; a < good_files.length; a++) {
var [fobj, name] = good_files[a],
sz = fobj.size,
ids = [],
id_ok = false,
m;
// all IDs found in this file
file_ids.push(ids);
// look for ID in filename; reduce the
// metadata-scan intensity if the id looks safe
m = /[\[(-]([\w-]{11})[\])]?\.(?:mp4|webm|mkv)$/i.exec(name);
id_ok = !!m;
while (true) {
// fuzzy catch-all;
// some ytdl fork did %(title)-%(id).%(ext) ...
m = /(?:^|[^\w])([\w-]{11})(?:$|[^\w-])/.exec(name);
if (!m)
break;
name = name.replace(m[1], '');
yt_ids.add(m[1]);
ids.push(m[1]);
}
// look for IDs in video metadata,
if (/\.(mp4|webm|mkv)$/i.exec(name)) {
toast.show('inf r', 0, `analyzing file ${a + 1} / ${good_files.length} :\n${name}\n\nhave analysed ${++mnchk} files in ${(Date.now() - t0) / 1000} seconds, ${humantime((good_files.length - (a + 1)) * (((Date.now() - t0) / 1000) / mnchk))} remaining,\n\nbiggest offset so far is ${mofs}, in this file:\n\n${mfile}`);
// check first and last 128 MiB;
// pWxOroN5WCo.mkv @ 6edb98 (6.92M)
// Nf-nN1wF5Xo.mp4 @ 4a98034 (74.6M)
var chunksz = 1024 * 1024 * 2, // byte
aspan = id_ok ? 128 : 512; // MiB
aspan = parseInt(Math.min(sz / 2, aspan * 1024 * 1024) / chunksz) * chunksz;
for (var side = 0; side < 2; side++) {
var ofs = side ? Math.max(0, sz - aspan) : 0,
nchunks = aspan / chunksz;
for (var chunk = 0; chunk < nchunks; chunk++) {
var bchunk = await fobj.slice(ofs, ofs + chunksz + 16).arrayBuffer(),
uchunk = new Uint8Array(bchunk, 0, bchunk.byteLength),
bofs = bstrpos(uchunk, md_ptn),
absofs = Math.min(ofs + bofs, (sz - ofs) + bofs),
txt = bofs < 0 ? '' : textdec.decode(uchunk.subarray(bofs)),
m;
//console.log(`side ${ side }, chunk ${ chunk }, ofs ${ ofs }, bchunk ${ bchunk.byteLength }, txt ${ txt.length }`);
while (true) {
// mkv/webm have [a-z] immediately after url
m = /(youtube\.com\/watch\?v=[\w-]{11})/.exec(txt);
if (!m)
break;
txt = txt.replace(m[1], '');
m = m[1].slice(-11);
console.log(`found ${m} @${bofs}, ${name} `);
yt_ids.add(m);
if (!has(ids, m))
ids.push(m);
// bail after next iteration
chunk = nchunks - 1;
side = 9;
if (mofs < absofs) {
mofs = absofs;
mfile = name;
}
}
ofs += chunksz;
if (ofs >= sz)
break;
}
}
}
}
if (false) {
var msg = `finished analysing ${mnchk} files in ${(Date.now() - t0) / 1000} seconds,\n\nbiggest offset was ${mofs} in this file:\n\n${mfile}`,
mfun = function () { toast.ok(0, msg); };
mfun();
setTimeout(mfun, 200);
return hooks[0]([], [], [], hooks.slice(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}`);
process_id_list(this.responseText);
};
xhr.send(Array.from(yt_ids).join('\n'));
function process_id_list(txt) {
var wanted_ids = new Set(txt.trim().split('\n')),
wanted_names = new Set(), // basenames with a wanted ID
wanted_files = new Set(); // filedrops
for (var a = 0; a < good_files.length; a++) {
var name = good_files[a][1];
for (var b = 0; b < file_ids[a].length; b++)
if (wanted_ids.has(file_ids[a][b])) {
wanted_files.add(good_files[a]);
var m = /(.*)\.(mp4|webm|mkv)$/i.exec(name);
if (m)
wanted_names.add(m[1]);
break;
}
}
// add all files with the same basename as each explicitly wanted file
// (infojson/chatlog/etc when ID was discovered from metadata)
for (var a = 0; a < good_files.length; a++) {
var name = good_files[a][1];
for (var b = 0; b < 3; b++) {
name = name.replace(/\.[^\.]+$/, '');
if (wanted_names.has(name)) {
wanted_files.add(good_files[a]);
break;
}
}
}
function upload_filtered() {
if (!wanted_files.size)
return modal.alert('Good news -- turns out we already have all those.\n\nBut thank you for checking in!');
hooks[0](Array.from(wanted_files), nil_files, bad_files, hooks.slice(1));
}
function upload_all() {
hooks[0](good_files, nil_files, bad_files, hooks.slice(1));
}
var n_skip = good_files.length - wanted_files.size,
msg = `you added ${good_files.length} files; ${good_files.length == n_skip ? 'all' : n_skip} of them were skipped --\neither because we already have them,\nor because there is no youtube-ID in your filenames.\n\n<code>OK</code> / <code>Enter</code> = continue uploading just the ${wanted_files.size} files we definitely need\n\n<code>Cancel</code> / <code>ESC</code> = override the filter; upload ALL the files you added`;
if (!n_skip)
upload_filtered();
else
modal.confirm(msg, upload_filtered, upload_all);
};
}
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

@@ -24,7 +24,18 @@ from .__init__ import ANYWIN, PY2, VT100, WINDOWS, E, unicode
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
from .authsrv import re_vol
from .svchub import SvcHub
from .util import IMPLICATIONS, align_tab, ansi_re, min_ex, py_desc, termsize, wrap
from .util import (
IMPLICATIONS,
JINJA_VER,
PYFTPD_VER,
SQLITE_VER,
align_tab,
ansi_re,
min_ex,
py_desc,
termsize,
wrap,
)
try:
from types import FrameType
@@ -275,7 +286,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 +322,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",
@@ -382,6 +393,7 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
\033[36mmaxn=250,600\033[35m max 250 uploads over 15min
\033[36mmaxb=1g,300\033[35m max 1 GiB over 5min (suffixes: b, k, m, g)
\033[36msz=1k-3m\033[35m allow filesizes between 1 KiB and 3MiB
\033[36mdf=1g\033[35m ensure 1 GiB free disk space
\033[0mupload rotation:
(moves all uploads into the specified folder structure)
@@ -394,11 +406,14 @@ 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
\033[36mhist=/tmp/cdb\033[35m puts thumbnails and indexes at that location
\033[36mscan=60\033[35m scan for new files every 60sec, same as --re-maxage
\033[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
\033[36mxdev\033[35m do not descend into other filesystems
\033[36mxvol\033[35m skip symlinks leaving the volume root
\033[0mdatabase, audio tags:
"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...
@@ -481,9 +496,10 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem)")
ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made")
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead")
ap2.add_argument("--thickfs", metavar="REGEX", type=u, default="fat|vfat|ex.?fat|hpfs|fuse", help="filesystems which dont support sparse files")
ap2.add_argument("--df", metavar="GiB", type=float, default=0, help="ensure GiB free disk space by rejecting upload requests")
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.)")
@@ -586,10 +602,16 @@ 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")
ap2.add_argument("--xdev", action="store_true", help="do not descend into other filesystems (symlink or bind-mount to another HDD, ...)")
ap2.add_argument("--xvol", action="store_true", help="skip symlinks leaving the volume root")
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag")
ap2.add_argument("--db-act", metavar="SEC", type=float, default=10, help="defer any scheduled volume reindexing until SEC seconds after last db write (uploads, renames, ...)")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline -- terminate searches running for more than SEC seconds")
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
@@ -612,6 +634,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")
@@ -626,6 +649,7 @@ def run_argparse(argv: list[str], formatter: Any) -> argparse.Namespace:
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")
ap2.add_argument("--log-fk", metavar="REGEX", type=u, default="", help="log filekey params for files where path matches REGEX; '.' (a single dot) = all files")
# fmt: on
ap2 = ap.add_argument_group("help sections")
@@ -651,10 +675,17 @@ def main(argv: Optional[list[str]] = None) -> None:
if argv is None:
argv = sys.argv
desc = py_desc().replace("[", "\033[1;30m[")
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
lprint(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0;36m\n sqlite v{} | jinja2 v{} | pyftpd v{}\n\033[0m'
f = f.format(
S_VERSION,
CODENAME,
S_BUILD_DT,
py_desc().replace("[", "\033[1;30m["),
SQLITE_VER,
JINJA_VER,
PYFTPD_VER,
)
lprint(f)
ensure_locale()
if HAVE_SSL:
@@ -683,12 +714,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
@@ -753,6 +786,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, 2)
VERSION = (1, 3, 10)
CODENAME = "god dag"
BUILD_DT = (2022, 6, 20)
BUILD_DT = (2022, 8, 4)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -20,6 +20,8 @@ from .util import (
Pebkac,
absreal,
fsenc,
get_df,
humansize,
relchk,
statdir,
uncyg,
@@ -72,15 +74,23 @@ class AXS(object):
class Lim(object):
def __init__(self) -> None:
def __init__(self, log_func: Optional["RootLogger"]) -> None:
self.log_func = log_func
self.reg: Optional[dict[str, dict[str, Any]]] = None # up2k registry
self.nups: dict[str, list[float]] = {} # num tracker
self.bups: dict[str, list[tuple[float, int]]] = {} # byte tracker list
self.bupc: dict[str, int] = {} # byte tracker cache
self.nosub = False # disallow subdirectories
self.smin = -1 # filesize min
self.smax = -1 # filesize max
self.dfl = 0 # free disk space limit
self.dft = 0 # last-measured time
self.dfv = 0 # currently free
self.smin = 0 # filesize min
self.smax = 0 # filesize max
self.bwin = 0 # bytes window
self.bmax = 0 # bytes max
@@ -92,18 +102,34 @@ class Lim(object):
self.rotf = "" # rot datefmt
self.rot_re = re.compile("") # rotf check
def log(self, msg: str, c: Union[int, str] = 0) -> None:
if self.log_func:
self.log_func("up-lim", msg, c)
def set_rotf(self, fmt: str) -> None:
self.rotf = fmt
r = re.escape(fmt).replace("%Y", "[0-9]{4}").replace("%j", "[0-9]{3}")
r = re.sub("%[mdHMSWU]", "[0-9]{2}", r)
self.rot_re = re.compile("(^|/)" + r + "$")
def all(self, ip: str, rem: str, sz: float, abspath: str) -> tuple[str, str]:
def all(
self,
ip: str,
rem: str,
sz: int,
abspath: str,
reg: Optional[dict[str, dict[str, Any]]] = None,
) -> tuple[str, str]:
if reg is not None and self.reg is None:
self.reg = reg
self.dft = 0
self.chk_nup(ip)
self.chk_bup(ip)
self.chk_rem(rem)
if sz != -1:
self.chk_sz(sz)
self.chk_df(abspath, sz) # side effects; keep last-ish
ap2, vp2 = self.rot(abspath)
if abspath == ap2:
@@ -111,13 +137,33 @@ class Lim(object):
return ap2, ("{}/{}".format(rem, vp2) if rem else vp2)
def chk_sz(self, sz: float) -> None:
if self.smin != -1 and sz < self.smin:
def chk_sz(self, sz: int) -> None:
if sz < self.smin:
raise Pebkac(400, "file too small")
if self.smax != -1 and sz > self.smax:
if self.smax and sz > self.smax:
raise Pebkac(400, "file too big")
def chk_df(self, abspath: str, sz: int, already_written: bool = False) -> None:
if not self.dfl:
return
if self.dft < time.time():
self.dft = int(time.time()) + 300
self.dfv = get_df(abspath)[0] or 0
for j in list(self.reg.values()) if self.reg else []:
self.dfv -= int(j["size"] / len(j["hash"]) * len(j["need"]))
if already_written:
sz = 0
if self.dfv - sz < self.dfl:
self.dft = min(self.dft, int(time.time()) + 10)
t = "server HDD is full; {} free, need {}"
raise Pebkac(500, t.format(humansize(self.dfv - self.dfl), humansize(sz)))
self.dfv -= int(sz)
def chk_rem(self, rem: str) -> None:
if self.nosub and rem:
raise Pebkac(500, "no subdirectories allowed")
@@ -226,7 +272,7 @@ class VFS(object):
def __init__(
self,
log: Optional[RootLogger],
log: Optional["RootLogger"],
realpath: str,
vpath: str,
axs: AXS,
@@ -569,7 +615,7 @@ class AuthSrv(object):
def __init__(
self,
args: argparse.Namespace,
log_func: Optional[RootLogger],
log_func: Optional["RootLogger"],
warn_anonwrite: bool = True,
) -> None:
self.args = args
@@ -661,7 +707,7 @@ class AuthSrv(object):
raise Exception('invalid mountpoint "{}"'.format(vol_dst))
# cfg files override arguments and previous files
vol_src = bos.path.abspath(vol_src)
vol_src = absreal(vol_src)
vol_dst = vol_dst.strip("/")
self._map_volume(vol_src, vol_dst, mount, daxs, mflags)
continue
@@ -682,12 +728,12 @@ class AuthSrv(object):
self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any]
) -> None:
if lvl.strip("crwmdg"):
raise Exception("invalid volume flag: {},{}".format(lvl, uname))
raise Exception("invalid volflag: {},{}".format(lvl, uname))
if lvl == "c":
cval: Union[bool, str] = True
try:
# volume flag with arguments, possibly with a preceding list of bools
# volflag with arguments, possibly with a preceding list of bools
uname, cval = uname.split("=", 1)
except:
# just one or more bools
@@ -772,7 +818,7 @@ class AuthSrv(object):
src = uncyg(src)
# print("\n".join([src, dst, perms]))
src = bos.path.abspath(src)
src = absreal(src)
dst = dst.strip("/")
self._map_volume(src, dst, mount, daxs, mflags)
@@ -801,7 +847,7 @@ class AuthSrv(object):
if not mount:
# -h says our defaults are CWD at root and read/write for everyone
axs = AXS(["*"], ["*"], None, None)
vfs = VFS(self.log_func, bos.path.abspath("."), "", axs, {})
vfs = VFS(self.log_func, absreal("."), "", axs, {})
elif "" not in mount:
# there's volumes but no root; make root inaccessible
vfs = VFS(self.log_func, "", "", AXS(), {})
@@ -917,13 +963,20 @@ class AuthSrv(object):
vfs.histtab = {zv.realpath: zv.histpath for zv in vfs.all_vols.values()}
for vol in vfs.all_vols.values():
lim = Lim()
lim = Lim(self.log_func)
use = False
if vol.flags.get("nosub"):
use = True
lim.nosub = True
zs = vol.flags.get("df") or (
"{}g".format(self.args.df) if self.args.df else ""
)
if zs:
use = True
lim.dfl = unhumanize(zs)
zs = vol.flags.get("sz")
if zs:
use = True
@@ -976,10 +1029,15 @@ class AuthSrv(object):
vol.flags["dathumb"] = True
vol.flags["dithumb"] = True
have_fk = False
for vol in vfs.all_vols.values():
fk = vol.flags.get("fk")
if fk:
vol.flags["fk"] = int(fk) if fk is not True else 8
have_fk = True
if have_fk and re.match(r"^[0-9\.]+$", self.args.fk_salt):
self.log("filekey salt: {}".format(self.args.fk_salt))
for vol in vfs.all_vols.values():
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
@@ -1008,7 +1066,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", "xdev", "xvol"]:
if getattr(self.args, k):
vol.flags[k] = True
@@ -1026,11 +1084,11 @@ class AuthSrv(object):
if "mth" not in vol.flags:
vol.flags["mth"] = self.args.mth
# append parsers from argv to volume-flags
# append parsers from argv to volflags
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 +1110,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 = {}
@@ -1083,7 +1147,7 @@ class AuthSrv(object):
for mtp in local_only_mtp:
if mtp not in local_mte:
t = 'volume "/{}" defines metadata tag "{}", but doesnt use it in "-mte" (or with "cmte" in its volume-flags)'
t = 'volume "/{}" defines metadata tag "{}", but doesnt use it in "-mte" (or with "cmte" in its volflags)'
self.log(t.format(vol.vpath, mtp), 1)
errors = True
@@ -1092,7 +1156,7 @@ class AuthSrv(object):
tags = [y for x in tags for y in x.split(",")]
for mtp in tags:
if mtp not in all_mte:
t = 'metadata tag "{}" is defined by "-mtm" or "-mtp", but is not used by "-mte" (or by any "cmte" volume-flag)'
t = 'metadata tag "{}" is defined by "-mtm" or "-mtp", but is not used by "-mte" (or by any "cmte" volflag)'
self.log(t.format(mtp), 1)
errors = True
@@ -1101,6 +1165,7 @@ class AuthSrv(object):
vfs.bubble_flags()
e2vs = []
t = "volumes and permissions:\n"
for zv in vfs.all_vols.values():
if not self.warn_anonwrite:
@@ -1118,8 +1183,16 @@ class AuthSrv(object):
u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
u = u if u else "\033[36m--none--\033[0m"
t += "\n| {}: {}".format(txt, u)
if "e2v" in zv.flags:
e2vs.append(zv.vpath or "/")
t += "\n"
if e2vs:
t += "\n\033[33me2v enabled for the following volumes;\nuploads will be blocked until scan has finished:\n \033[0m"
t += " ".join(e2vs) + "\n"
if self.warn_anonwrite and not self.args.no_voldump:
self.log(t)
@@ -1127,7 +1200,7 @@ class AuthSrv(object):
zv, _ = vfs.get("/", "*", False, True)
if self.warn_anonwrite and os.getcwd() == zv.realpath:
self.warn_anonwrite = False
t = "anyone can read/write the current directory: {}\n"
t = "anyone can write to the current directory: {}\n"
self.log(t.format(zv.realpath), c=1)
except Pebkac:
self.warn_anonwrite = True

View File

@@ -42,7 +42,7 @@ class BrokerCli(object):
"""
def __init__(self) -> None:
self.log: RootLogger = None
self.log: "RootLogger" = None
self.args: argparse.Namespace = None
self.asrv: AuthSrv = None
self.httpsrv: "HttpSrv" = None

View File

@@ -1,12 +1,13 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
import ctypes
import os
import re
import time
from .__init__ import ANYWIN, MACOS
from .authsrv import AXS, VFS
from .bos import bos
from .util import chkcmd, min_ex
try:
@@ -18,9 +19,10 @@ except:
class Fstab(object):
def __init__(self, log: RootLogger):
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
@@ -28,8 +30,8 @@ class Fstab(object):
def log(self, msg: str, c: Union[int, str] = 0) -> None:
self.log_func("fstab", msg + "\033[K", c)
def get(self, path: str):
if time.time() - self.age > 600 or len(self.cache) > 9000:
def get(self, path: str) -> str:
if len(self.cache) > 9000:
self.age = time.time()
self.tab = None
self.cache = {}
@@ -38,17 +40,14 @@ class Fstab(object):
msg = "failed to determine filesystem at [{}]; assuming {}\n{}"
if ANYWIN:
fs = "vfat" # can smb do sparse files? gonna guess no
fs = "vfat"
try:
# good enough
disk = path.split(":", 1)[0]
disk = "{}:\\".format(disk).lower()
assert len(disk) == 3
path = disk
path = self._winpath(path)
except:
self.log(msg.format(path, fs, min_ex()), 3)
return fs
path = path.lstrip("/")
try:
return self.cache[path]
except:
@@ -64,7 +63,20 @@ class Fstab(object):
self.log("found {} at {}".format(fs, path))
return fs
def build_tab(self):
def _winpath(self, path: str) -> str:
# try to combine volume-label + st_dev (vsn)
path = path.replace("/", "\\")
vid = path.split(":", 1)[0].strip("\\").split("\\", 1)[0]
try:
return "{}*{}".format(vid, bos.stat(path).st_dev)
except:
return vid
def build_fallback(self) -> None:
self.tab = VFS(self.log_func, "idk", "/", AXS(), {})
self.trusted = False
def build_tab(self) -> None:
self.log("building tab")
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
@@ -79,7 +91,8 @@ class Fstab(object):
if not m:
continue
tab1.append(m.groups())
zs1, zs2 = m.groups()
tab1.append((str(zs1), str(zs2)))
tab1.sort(key=lambda x: (len(x[0]), x[0]))
path1, fs1 = tab1[0]
@@ -89,50 +102,53 @@ class Fstab(object):
self.tab = tab
def get_unix(self, path: str):
def relabel(self, path: str, nval: str) -> None:
assert self.tab
self.cache = {}
if ANYWIN:
path = self._winpath(path)
path = path.lstrip("/")
ptn = re.compile(r"^[^\\/]*")
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()))
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.build_fallback()
return self.tab._find(path)[0].realpath.split("/")[0]
assert self.tab
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):
# list mountpoints: fsutil fsinfo drives
def get_w32(self, path: str) -> str:
if not self.tab:
self.build_fallback()
from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPDWORD, LPWSTR, MAX_PATH
def echk(rc, fun, args):
if not rc:
raise ctypes.WinError(ctypes.get_last_error())
return None
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
k32.GetVolumeInformationW.errcheck = echk
k32.GetVolumeInformationW.restype = BOOL
k32.GetVolumeInformationW.argtypes = (
LPCWSTR,
LPWSTR,
DWORD,
LPDWORD,
LPDWORD,
LPDWORD,
LPWSTR,
DWORD,
)
bvolname = ctypes.create_unicode_buffer(MAX_PATH + 1)
bfstype = ctypes.create_unicode_buffer(MAX_PATH + 1)
serial = DWORD()
max_name_len = DWORD()
fs_flags = DWORD()
k32.GetVolumeInformationW(
path,
bvolname,
ctypes.sizeof(bvolname),
ctypes.byref(serial),
ctypes.byref(max_name_len),
ctypes.byref(fs_flags),
bfstype,
ctypes.sizeof(bfstype),
)
return bfstype.value
assert self.tab
ret = self.tab._find(path)[0]
return ret.realpath

View File

@@ -24,12 +24,7 @@ try:
except:
pass
try:
import ctypes
except:
pass
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E, unicode
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, E, unicode
from .authsrv import VFS # typechk
from .bos import bos
from .star import StreamTar
@@ -47,7 +42,9 @@ from .util import (
exclude_dotfiles,
fsenc,
gen_filekey,
gen_filekey_dbg,
gencookie,
get_df,
get_spd,
guess_mime,
gzip_orig_sz,
@@ -112,6 +109,7 @@ class HttpCli(object):
self.u2fh = conn.u2fh # mypy404
self.log_func = conn.log_func # mypy404
self.log_src = conn.log_src # mypy404
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
self.tls: bool = hasattr(self.s, "cipher")
# placeholders; assigned by run()
@@ -181,9 +179,14 @@ class HttpCli(object):
if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
raise Exception("that was close")
def _gen_fk(self, salt: str, fspath: str, fsize: int, inode: int) -> str:
return gen_filekey_dbg(salt, fspath, fsize, inode, self.log, self.args.log_fk)
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
@@ -378,13 +381,21 @@ class HttpCli(object):
if not self._check_nonfatal(pex, post):
self.keepalive = False
msg = str(ex) if pex == ex else min_ex()
self.log("{}\033[0m, {}".format(msg, self.vpath), 3)
em = str(ex)
msg = em if pex == ex else min_ex()
self.log(
"{}\033[0m, {}".format(msg, self.vpath),
6 if em.startswith("client d/c ") else 3,
)
msg = "{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
msg = "{}\r\nURL: {}\r\n".format(em, self.vpath)
if self.hint:
msg += "hint: {}\r\n".format(self.hint)
if "database is locked" in em:
self.conn.hsrv.broker.say("log_stacks")
msg += "hint: important info in the server log\r\n"
msg = "<pre>" + html_escape(msg)
self.reply(msg.encode("utf-8", "replace"), status=pex.code, volsan=True)
return self.keepalive
@@ -545,6 +556,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))
@@ -701,7 +716,7 @@ class HttpCli(object):
reader, remains = self.get_body_reader()
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
lim = vfs.get_dbv(rem)[0].lim
fdir = os.path.join(vfs.realpath, rem)
fdir = vfs.canonical(rem)
if lim:
fdir, rem = lim.all(self.ip, rem, remains, fdir)
@@ -803,7 +818,7 @@ class HttpCli(object):
vsuf = ""
if self.can_read and "fk" in vfs.flags:
vsuf = "?k=" + gen_filekey(
vsuf = "?k=" + self.gen_fk(
self.args.fk_salt,
path,
post_sz,
@@ -911,6 +926,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)
@@ -937,7 +955,7 @@ class HttpCli(object):
if rem:
try:
dst = os.path.join(vfs.realpath, rem)
dst = vfs.canonical(rem)
if not bos.path.isdir(dst):
bos.makedirs(dst)
except OSError as ex:
@@ -1118,8 +1136,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
@@ -1170,7 +1190,7 @@ class HttpCli(object):
sanitized = sanitize_fn(new_dir, "", [])
if not nullwrite:
fdir = os.path.join(vfs.realpath, rem)
fdir = vfs.canonical(rem)
fn = os.path.join(fdir, sanitized)
if not bos.path.isdir(fdir):
@@ -1209,7 +1229,7 @@ class HttpCli(object):
sanitized = sanitize_fn(new_file, "", [])
if not nullwrite:
fdir = os.path.join(vfs.realpath, rem)
fdir = vfs.canonical(rem)
fn = os.path.join(fdir, sanitized)
if bos.path.exists(fn):
@@ -1230,7 +1250,7 @@ class HttpCli(object):
upload_vpath = self.vpath
lim = vfs.get_dbv(rem)[0].lim
fdir_base = os.path.join(vfs.realpath, rem)
fdir_base = vfs.canonical(rem)
if lim:
fdir_base, rem = lim.all(self.ip, rem, -1, fdir_base)
upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
@@ -1271,14 +1291,19 @@ class HttpCli(object):
else:
open_args = {}
tnam = fname = os.devnull
fdir = ""
fdir = abspath = ""
if lim:
lim.chk_bup(self.ip)
lim.chk_nup(self.ip)
try:
max_sz = lim.smax if lim else 0
max_sz = 0
if lim:
v1 = lim.smax
v2 = lim.dfv - lim.dfl
max_sz = min(v1, v2) if v1 and v2 else v1 or v2
with ren_open(tnam, "wb", 512 * 1024, **open_args) as zfw:
f, tnam = zfw["orz"]
tabspath = os.path.join(fdir, tnam)
@@ -1293,16 +1318,20 @@ class HttpCli(object):
lim.nup(self.ip)
lim.bup(self.ip, sz)
try:
lim.chk_df(tabspath, sz, True)
lim.chk_sz(sz)
lim.chk_bup(self.ip)
lim.chk_nup(self.ip)
except:
bos.unlink(tabspath)
bos.unlink(abspath)
if not nullwrite:
bos.unlink(tabspath)
bos.unlink(abspath)
fname = os.devnull
raise
atomic_move(tabspath, abspath)
if not nullwrite:
atomic_move(tabspath, abspath)
files.append(
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath)
)
@@ -1352,9 +1381,9 @@ class HttpCli(object):
for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
vsuf = ""
if self.can_read and "fk" in vfs.flags:
vsuf = "?k=" + gen_filekey(
vsuf = "?k=" + self.gen_fk(
self.args.fk_salt,
abspath,
ap,
sz,
0 if ANYWIN or not ap else bos.stat(ap).st_ino,
)[: vfs.flags["fk"]]
@@ -1432,7 +1461,7 @@ class HttpCli(object):
raise Pebkac(411)
rp, fn = vsplit(rem)
fp = os.path.join(vfs.realpath, rp)
fp = vfs.canonical(rp)
lim = vfs.get_dbv(rem)[0].lim
if lim:
fp, rp = lim.all(self.ip, rp, clen, fp)
@@ -1855,6 +1884,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,
@@ -1904,7 +1935,13 @@ class HttpCli(object):
vstate = {("/" + k).rstrip("/") + "/": v for k, v in vs["volstate"].items()}
else:
vstate = {}
vs = {"scanning": None, "hashq": None, "tagq": None, "mtpq": None}
vs = {
"scanning": None,
"hashq": None,
"tagq": None,
"mtpq": None,
"dbwt": None,
}
if self.uparam.get("ls") in ["v", "t", "txt"]:
if self.uname == "*":
@@ -1914,7 +1951,7 @@ class HttpCli(object):
if vstate:
txt += "\nstatus:"
for k in ["scanning", "hashq", "tagq", "mtpq"]:
for k in ["scanning", "hashq", "tagq", "mtpq", "dbwt"]:
txt += " {}({})".format(k, vs[k])
if rvol:
@@ -1943,6 +1980,7 @@ class HttpCli(object):
hashq=vs["hashq"],
tagq=vs["tagq"],
mtpq=vs["mtpq"],
dbwt=vs["dbwt"],
url_suf=suf,
k304=self.k304(),
)
@@ -2218,7 +2256,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:
@@ -2279,7 +2318,7 @@ class HttpCli(object):
if not is_dir and (self.can_read or self.can_get):
if not self.can_read and "fk" in vn.flags:
correct = gen_filekey(
correct = self.gen_fk(
self.args.fk_salt, abspath, st.st_size, 0 if ANYWIN else st.st_ino
)[: vn.flags["fk"]]
got = self.uparam.get("k")
@@ -2303,26 +2342,14 @@ class HttpCli(object):
except:
self.log("#wow #whoa")
try:
# some fuses misbehave
if not self.args.nid:
if WINDOWS:
try:
bfree = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
)
srv_info.append(humansize(bfree.value) + " free")
except:
pass
else:
sv = os.statvfs(fsenc(abspath))
free = humansize(sv.f_frsize * sv.f_bfree, True)
total = humansize(sv.f_frsize * sv.f_blocks, True)
srv_info.append("{} free of {}".format(free, total))
except:
pass
if not self.args.nid:
free, total = get_df(abspath)
if total is not None:
h1 = humansize(free or 0)
h2 = humansize(total)
srv_info.append("{} free of {}".format(h1, h2))
elif free is not None:
srv_info.append(humansize(free, True) + " free")
srv_infot = "</span> // <span>".join(srv_info)
@@ -2398,11 +2425,21 @@ 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:
j2a["js"] = self.args.js_browser
if self.args.css_browser:
j2a["css"] = self.args.css_browser
if not self.conn.hsrv.prism:
j2a["no_prism"] = True
if not self.can_read:
if is_ls:
return self.tx_ls(ls_ret)
@@ -2505,7 +2542,7 @@ class HttpCli(object):
if add_fk:
href = "{}?k={}".format(
quotep(href),
gen_filekey(
self.gen_fk(
self.args.fk_salt, fspath, sz, 0 if ANYWIN else inf.st_ino
)[:add_fk],
)
@@ -2602,9 +2639,6 @@ class HttpCli(object):
if doctxt is not None:
j2a["doc"] = doctxt
if not self.conn.hsrv.prism:
j2a["no_prism"] = True
for d in dirs:
d["name"] += "/"
@@ -2616,19 +2650,12 @@ class HttpCli(object):
else:
j2a["files"] = dirs + files
j2a["logues"] = logues
j2a["taglist"] = taglist
j2a["txt_ext"] = self.args.textfiles.replace(",", " ")
if "mth" in vn.flags:
j2a["def_hcols"] = vn.flags["mth"].split(",")
if self.args.js_browser:
j2a["js"] = self.args.js_browser
if self.args.css_browser:
j2a["css"] = self.args.css_browser
html = self.j2s(tpl, **j2a)
self.reply(html.encode("utf-8", "replace"))
return True

View File

@@ -62,7 +62,7 @@ class HttpConn(object):
self.nreq: int = 0 # mypy404
self.nbyte: int = 0 # mypy404
self.u2idx: Optional[U2idx] = None
self.log_func: Util.RootLogger = hsrv.log # mypy404
self.log_func: "Util.RootLogger" = hsrv.log # mypy404
self.log_src: str = "httpconn" # mypy404
self.lf_url: Optional[Pattern[str]] = (
re.compile(self.args.lf_url) if self.args.lf_url else None

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"))
@@ -165,7 +165,7 @@ class HttpSrv(object):
"""listens on a shared tcp server"""
ip, port = srv_sck.getsockname()
fno = srv_sck.fileno()
msg = "subscribed @ {}:{} f{}".format(ip, port, fno)
msg = "subscribed @ {}:{} f{} p{}".format(ip, port, fno, os.getpid())
self.log(self.name, msg)
def fun() -> None:
@@ -261,8 +261,11 @@ class HttpSrv(object):
)
self.thr_client(sck, addr)
me.name = self.name + "-poolw"
except:
self.log(self.name, "thr_client: " + min_ex(), 3)
except Exception as ex:
if str(ex).startswith("client d/c "):
self.log(self.name, "thr_client: " + str(ex), 6)
else:
self.log(self.name, "thr_client: " + min_ex(), 3)
def shutdown(self) -> None:
self.stopping = True

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)
@@ -235,7 +248,7 @@ def parse_ffprobe(txt: str) -> tuple[dict[str, tuple[int, Any]], dict[str, list[
class MTag(object):
def __init__(self, log_func: RootLogger, args: argparse.Namespace) -> None:
def __init__(self, log_func: "RootLogger", args: argparse.Namespace) -> None:
self.log_func = log_func
self.args = args
self.usable = True
@@ -424,6 +437,8 @@ class MTag(object):
return r1
def get_mutagen(self, abspath: str) -> dict[str, Union[str, float]]:
ret: dict[str, tuple[int, Any]] = {}
if not bos.path.isfile(abspath):
return {}
@@ -437,7 +452,10 @@ class MTag(object):
return self.get_ffprobe(abspath) if self.can_ffprobe else {}
sz = bos.path.getsize(abspath)
ret = {".q": (0, int((sz / md.info.length) / 128))}
try:
ret[".q"] = (0, int((sz / md.info.length) / 128))
except:
pass
for attr, k, norm in [
["codec", "ac", unicode],
@@ -479,7 +497,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 +509,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 +543,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

@@ -44,7 +44,7 @@ class StreamTar(StreamArc):
def __init__(
self,
log: NamedLogger,
log: "NamedLogger",
fgen: Generator[dict[str, Any], None, None],
**kwargs: Any
):
@@ -65,17 +65,19 @@ class StreamTar(StreamArc):
w.start()
def gen(self) -> Generator[Optional[bytes], None, None]:
while True:
buf = self.qfile.q.get()
if not buf:
break
try:
while True:
buf = self.qfile.q.get()
if not buf:
break
self.co += len(buf)
yield buf
self.co += len(buf)
yield buf
yield None
if self.errf:
bos.unlink(self.errf["ap"])
yield None
finally:
if self.errf:
bos.unlink(self.errf["ap"])
def ser(self, f: dict[str, Any]) -> None:
name = f["vp"]

View File

@@ -17,7 +17,7 @@ except:
class StreamArc(object):
def __init__(
self,
log: NamedLogger,
log: "NamedLogger",
fgen: Generator[dict[str, Any], None, None],
**kwargs: Any
):

View File

@@ -2,8 +2,11 @@
from __future__ import print_function, unicode_literals
import argparse
import base64
import calendar
import gzip
import os
import re
import shlex
import signal
import socket
@@ -17,7 +20,7 @@ try:
from types import FrameType
import typing
from typing import Optional, Union
from typing import Any, Optional, Union
except:
pass
@@ -27,7 +30,15 @@ from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
from .tcpsrv import TcpSrv
from .th_srv import HAVE_PIL, HAVE_VIPS, HAVE_WEBP, ThumbSrv
from .up2k import Up2k
from .util import ansi_re, min_ex, mp, start_log_thrs, start_stackmon
from .util import (
VERSIONS,
alltrace,
ansi_re,
min_ex,
mp,
start_log_thrs,
start_stackmon,
)
class SvcHub(object):
@@ -47,15 +58,18 @@ class SvcHub(object):
self.logf: Optional[typing.TextIO] = None
self.logf_base_fn = ""
self.stop_req = False
self.reload_req = False
self.stopping = False
self.stopped = False
self.reload_req = False
self.reloading = False
self.stop_cond = threading.Condition()
self.nsigs = 3
self.retcode = 0
self.httpsrv_up = 0
self.log_mutex = threading.Lock()
self.next_day = 0
self.tstack = 0.0
if args.sss or args.s >= 3:
args.ss = True
@@ -110,6 +124,9 @@ class SvcHub(object):
if not args.hardlink and args.never_symlink:
args.no_dedup = True
if args.log_fk:
args.log_fk = re.compile(args.log_fk)
# initiate all services to manage
self.asrv = AuthSrv(self.args, self.log)
if args.ls:
@@ -280,7 +297,9 @@ class SvcHub(object):
pass
self.shutdown()
thr.join()
# cant join; eats signals on win10
while not self.stopped:
time.sleep(0.1)
else:
self.stop_thr()
@@ -316,9 +335,22 @@ class SvcHub(object):
def signal_handler(self, sig: int, frame: Optional[FrameType]) -> None:
if self.stopping:
return
if self.nsigs <= 0:
try:
threading.Thread(target=self.pr, args=("OMBO BREAKER",)).start()
time.sleep(0.1)
except:
pass
if sig == signal.SIGUSR1:
if ANYWIN:
os.system("taskkill /f /pid {}".format(os.getpid()))
else:
os.kill(os.getpid(), signal.SIGKILL)
else:
self.nsigs -= 1
return
if not ANYWIN and sig == signal.SIGUSR1:
self.reload_req = True
else:
self.stop_req = True
@@ -339,9 +371,7 @@ class SvcHub(object):
ret = 1
try:
with self.log_mutex:
print("OPYTHAT")
self.pr("OPYTHAT")
self.tcpsrv.shutdown()
self.broker.shutdown()
self.up2k.shutdown()
@@ -354,19 +384,23 @@ class SvcHub(object):
break
if n == 3:
print("waiting for thumbsrv (10sec)...")
self.pr("waiting for thumbsrv (10sec)...")
print("nailed it", end="")
self.pr("nailed it", end="")
ret = self.retcode
except:
self.pr("\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="")
sys.stderr.flush()
print("\033[0m")
self.pr("\033[0m")
if self.logf:
self.logf.close()
self.stopped = True
sys.exit(ret)
def _log_disabled(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
@@ -433,6 +467,10 @@ class SvcHub(object):
if self.logf:
self.logf.write(msg)
def pr(self, *a: Any, **ka: Any) -> None:
with self.log_mutex:
print(*a, **ka)
def check_mp_support(self) -> str:
vmin = sys.version_info[1]
if WINDOWS:
@@ -497,3 +535,16 @@ class SvcHub(object):
sck.sendall(b"READY=1")
except:
self.log("sd_notify", min_ex())
def log_stacks(self) -> None:
td = time.time() - self.tstack
if td < 300:
self.log("stacks", "cooldown {}".format(td))
return
self.tstack = time.time()
zs = "{}\n{}".format(VERSIONS, alltrace())
zb = zs.encode("utf-8", "replace")
zb = gzip.compress(zb)
zs = base64.b64encode(zb).decode("ascii")
self.log("stacks", zs)

View File

@@ -218,7 +218,7 @@ def gen_ecdr64_loc(ecdr64_pos: int) -> bytes:
class StreamZip(StreamArc):
def __init__(
self,
log: NamedLogger,
log: "NamedLogger",
fgen: Generator[dict[str, Any], None, None],
utf8: bool = False,
pre_crc: bool = False,
@@ -272,41 +272,44 @@ class StreamZip(StreamArc):
def gen(self) -> Generator[bytes, None, None]:
errors = []
for f in self.fgen:
if "err" in f:
errors.append((f["vp"], f["err"]))
continue
try:
for f in self.fgen:
if "err" in f:
errors.append((f["vp"], f["err"]))
continue
try:
for x in self.ser(f):
try:
for x in self.ser(f):
yield x
except GeneratorExit:
raise
except:
ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append((f["vp"], ex))
if errors:
errf, txt = errdesc(errors)
self.log("\n".join(([repr(errf)] + txt[1:])))
for x in self.ser(errf):
yield x
except:
ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append((f["vp"], ex))
if errors:
errf, txt = errdesc(errors)
self.log("\n".join(([repr(errf)] + txt[1:])))
for x in self.ser(errf):
yield x
cdir_pos = self.pos
for name, sz, ts, crc, h_pos in self.items:
buf = gen_hdr(h_pos, name, sz, ts, self.utf8, crc, self.pre_crc)
yield self._ct(buf)
cdir_end = self.pos
cdir_pos = self.pos
for name, sz, ts, crc, h_pos in self.items:
buf = gen_hdr(h_pos, name, sz, ts, self.utf8, crc, self.pre_crc)
yield self._ct(buf)
cdir_end = self.pos
_, need_64 = gen_ecdr(self.items, cdir_pos, cdir_end)
if need_64:
ecdir64_pos = self.pos
buf = gen_ecdr64(self.items, cdir_pos, cdir_end)
yield self._ct(buf)
_, need_64 = gen_ecdr(self.items, cdir_pos, cdir_end)
if need_64:
ecdir64_pos = self.pos
buf = gen_ecdr64(self.items, cdir_pos, cdir_end)
yield self._ct(buf)
buf = gen_ecdr64_loc(ecdir64_pos)
yield self._ct(buf)
buf = gen_ecdr64_loc(ecdir64_pos)
yield self._ct(buf)
ecdr, _ = gen_ecdr(self.items, cdir_pos, cdir_end)
yield self._ct(ecdr)
if errors:
bos.unlink(errf["ap"])
ecdr, _ = gen_ecdr(self.items, cdir_pos, cdir_end)
yield self._ct(ecdr)
finally:
if errors:
bos.unlink(errf["ap"])

View File

@@ -1,6 +1,7 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
import os
import re
import socket
import sys
@@ -128,7 +129,7 @@ class TcpSrv(object):
srv.listen(self.args.nc)
ip, port = srv.getsockname()
fno = srv.fileno()
msg = "listening @ {}:{} f{}".format(ip, port, fno)
msg = "listening @ {}:{} f{} p{}".format(ip, port, fno, os.getpid())
self.log("tcpsrv", msg)
if self.args.q:
print(msg)

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

File diff suppressed because it is too large Load Diff

View File

@@ -22,20 +22,32 @@ from collections import Counter
from datetime import datetime
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, VT100, WINDOWS
from .__version__ import S_BUILD_DT, S_VERSION
from .stolen import surrogateescape
try:
import ctypes
except:
pass
try:
HAVE_SQLITE3 = True
import sqlite3 # pylint: disable=unused-import # typechk
except:
HAVE_SQLITE3 = False
try:
HAVE_PSUTIL = True
import psutil
except:
HAVE_PSUTIL = False
try:
import types
from collections.abc import Callable, Iterable
import typing
from typing import Any, Generator, Optional, Protocol, Union
from typing import Any, Generator, Optional, Pattern, Protocol, Union
class RootLogger(Protocol):
def __call__(self, src: str, msg: str, c: Union[int, str] = 0) -> None:
@@ -72,8 +84,6 @@ else:
from urllib import quote # pylint: disable=no-name-in-module
from urllib import unquote # pylint: disable=no-name-in-module
_: Any = (mp, BytesIO, quote, unquote)
__all__ = ["mp", "BytesIO", "quote", "unquote"]
try:
struct.unpack(b">i", b"idgi")
@@ -121,6 +131,7 @@ HTTPCODE = {
429: "Too Many Requests",
500: "Internal Server Error",
501: "Not Implemented",
503: "Service Unavailable",
}
@@ -130,6 +141,9 @@ IMPLICATIONS = [
["e2tsr", "e2ts"],
["e2ts", "e2t"],
["e2t", "e2d"],
["e2vu", "e2v"],
["e2vp", "e2v"],
["e2v", "e2d"],
]
@@ -198,6 +212,54 @@ REKOBO_KEY = {
REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()}
def py_desc() -> str:
interp = platform.python_implementation()
py_ver = ".".join([str(x) for x in sys.version_info])
ofs = py_ver.find(".final.")
if ofs > 0:
py_ver = py_ver[:ofs]
try:
bitness = struct.calcsize(b"P") * 8
except:
bitness = struct.calcsize("P") * 8
host_os = platform.system()
compiler = platform.python_compiler()
m = re.search(r"([0-9]+\.[0-9\.]+)", platform.version())
os_ver = m.group(1) if m else ""
return "{:>9} v{} on {}{} {} [{}]".format(
interp, py_ver, host_os, bitness, os_ver, compiler
)
try:
from sqlite3 import sqlite_version as SQLITE_VER
except:
SQLITE_VER = "(None)"
try:
from jinja2 import __version__ as JINJA_VER
except:
JINJA_VER = "(None)"
try:
from pyftpdlib.__init__ import __ver__ as PYFTPD_VER
except:
PYFTPD_VER = "(None)"
VERSIONS = "copyparty v{} ({})\n{}\n sqlite v{} | jinja v{} | pyftpd v{}".format(
S_VERSION, S_BUILD_DT, py_desc(), SQLITE_VER, JINJA_VER, PYFTPD_VER
)
_: Any = (mp, BytesIO, quote, unquote, SQLITE_VER, JINJA_VER, PYFTPD_VER)
__all__ = ["mp", "BytesIO", "quote", "unquote", "SQLITE_VER", "JINJA_VER", "PYFTPD_VER"]
class Cooldown(object):
def __init__(self, maxage: float) -> None:
self.maxage = maxage
@@ -233,7 +295,7 @@ class _Unrecv(object):
undo any number of socket recv ops
"""
def __init__(self, s: socket.socket, log: Optional[NamedLogger]) -> None:
def __init__(self, s: socket.socket, log: Optional["NamedLogger"]) -> None:
self.s = s
self.log = log
self.buf: bytes = b""
@@ -277,7 +339,7 @@ class _LUnrecv(object):
with expensive debug logging
"""
def __init__(self, s: socket.socket, log: Optional[NamedLogger]) -> None:
def __init__(self, s: socket.socket, log: Optional["NamedLogger"]) -> None:
self.s = s
self.log = log
self.buf = b""
@@ -589,6 +651,7 @@ def ren_open(
ext = bname[ofs:] + ext
bname = bname[:ofs]
asciified = False
b64 = ""
while True:
try:
@@ -614,11 +677,20 @@ def ren_open(
except OSError as ex_:
ex = ex_
if ex.errno == 22 and not asciified:
asciified = True
bname, fname = [
zs.encode("ascii", "replace").decode("ascii").replace("?", "_")
for zs in [bname, fname]
]
continue
if ex.errno not in [36, 63] and (not WINDOWS or ex.errno != 22):
raise
if not b64:
zs = (bname + ext).encode("utf-8", "replace")
zs = (orig_name + "\n" + suffix).encode("utf-8", "replace")
zs = hashlib.sha512(zs).digest()[:12]
b64 = base64.urlsafe_b64encode(zs).decode("utf-8")
@@ -642,7 +714,9 @@ def ren_open(
class MultipartParser(object):
def __init__(self, log_func: NamedLogger, sr: Unrecv, http_headers: dict[str, str]):
def __init__(
self, log_func: "NamedLogger", sr: Unrecv, http_headers: dict[str, str]
):
self.sr = sr
self.log = log_func
self.headers = http_headers
@@ -905,6 +979,24 @@ def gen_filekey(salt: str, fspath: str, fsize: int, inode: int) -> str:
).decode("ascii")
def gen_filekey_dbg(
salt: str,
fspath: str,
fsize: int,
inode: int,
log: "NamedLogger",
log_ptn: Optional[Pattern[str]],
) -> str:
ret = gen_filekey(salt, fspath, fsize, inode)
assert log_ptn
if log_ptn.search(fspath):
t = "fk({}) salt({}) size({}) inode({}) fspath({})"
log(t.format(ret[:8], salt, fsize, inode, fspath))
return ret
def gencookie(k: str, v: str, dur: Optional[int]) -> str:
v = v.replace(";", "")
if dur:
@@ -962,6 +1054,11 @@ def s2hms(s: float, optional_h: bool = False) -> str:
return "{}:{:02}:{:02}".format(h, m, s)
def djoin(*paths: str) -> str:
"""joins without adding a trailing slash on blank args"""
return os.path.join(*[x for x in paths if x])
def uncyg(path: str) -> str:
if len(path) < 2 or not path.startswith("/"):
return path
@@ -1164,15 +1261,30 @@ def s3enc(mem_cur: "sqlite3.Cursor", rd: str, fn: str) -> tuple[str, str]:
def s3dec(rd: str, fn: str) -> tuple[str, str]:
ret = []
for v in [rd, fn]:
if v.startswith("//"):
ret.append(w8b64dec(v[2:]))
# self.log("mojide [{}] {}".format(ret[-1], v[2:]))
else:
ret.append(v)
return (
w8b64dec(rd[2:]) if rd.startswith("//") else rd,
w8b64dec(fn[2:]) if fn.startswith("//") else fn,
)
return ret[0], ret[1]
def db_ex_chk(log: "NamedLogger", ex: Exception, db_path: str) -> bool:
if str(ex) != "database is locked":
return False
thr = threading.Thread(target=lsof, args=(log, db_path))
thr.daemon = True
thr.start()
return True
def lsof(log: "NamedLogger", abspath: str) -> None:
try:
rc, so, se = runcmd([b"lsof", b"-R", fsenc(abspath)], timeout=5)
zs = (so.strip() + "\n" + se.strip()).strip()
log("lsof {} = {}\n{}".format(abspath, rc, zs), 3)
except:
log("lsof failed; " + min_ex(), 3)
def atomic_move(usrc: str, udst: str) -> None:
@@ -1187,6 +1299,24 @@ def atomic_move(usrc: str, udst: str) -> None:
os.rename(src, dst)
def get_df(abspath: str) -> tuple[Optional[int], Optional[int]]:
try:
# some fuses misbehave
if ANYWIN:
bfree = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
)
return (bfree.value, None)
else:
sv = os.statvfs(fsenc(abspath))
free = sv.f_frsize * sv.f_bfree
total = sv.f_frsize * sv.f_blocks
return (free, total)
except:
return (None, None)
def read_socket(sr: Unrecv, total_size: int) -> Generator[bytes, None, None]:
remains = total_size
while remains > 0:
@@ -1213,7 +1343,7 @@ def read_socket_unbounded(sr: Unrecv) -> Generator[bytes, None, None]:
def read_socket_chunked(
sr: Unrecv, log: Optional[NamedLogger] = None
sr: Unrecv, log: Optional["NamedLogger"] = None
) -> Generator[bytes, None, None]:
err = "upload aborted: expected chunk length, got [{}] |{}| instead"
while True:
@@ -1291,7 +1421,7 @@ def hashcopy(
def sendfile_py(
log: NamedLogger,
log: "NamedLogger",
lower: int,
upper: int,
f: typing.BinaryIO,
@@ -1319,7 +1449,7 @@ def sendfile_py(
def sendfile_kern(
log: NamedLogger,
log: "NamedLogger",
lower: int,
upper: int,
f: typing.BinaryIO,
@@ -1360,7 +1490,7 @@ def sendfile_kern(
def statdir(
logger: Optional[RootLogger], scandir: bool, lstat: bool, top: str
logger: Optional["RootLogger"], scandir: bool, lstat: bool, top: str
) -> Generator[tuple[str, os.stat_result], None, None]:
if lstat and ANYWIN:
lstat = False
@@ -1403,9 +1533,10 @@ def statdir(
def rmdirs(
logger: RootLogger, scandir: bool, lstat: bool, top: str, depth: int
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
@@ -1429,6 +1560,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 = ""
@@ -1479,22 +1625,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]:
@@ -1521,7 +1754,7 @@ def retchk(
rc: int,
cmd: Union[list[bytes], list[str]],
serr: str,
logger: Optional[NamedLogger] = None,
logger: Optional["NamedLogger"] = None,
color: Union[int, str] = 0,
verbose: bool = False,
) -> None:
@@ -1573,29 +1806,6 @@ def gzip_orig_sz(fn: str) -> int:
return sunpack(b"I", rv)[0] # type: ignore
def py_desc() -> str:
interp = platform.python_implementation()
py_ver = ".".join([str(x) for x in sys.version_info])
ofs = py_ver.find(".final.")
if ofs > 0:
py_ver = py_ver[:ofs]
try:
bitness = struct.calcsize(b"P") * 8
except:
bitness = struct.calcsize("P") * 8
host_os = platform.system()
compiler = platform.python_compiler()
m = re.search(r"([0-9]+\.[0-9\.]+)", platform.version())
os_ver = m.group(1) if m else ""
return "{:>9} v{} on {}{} {} [{}]".format(
interp, py_ver, host_os, bitness, os_ver, compiler
)
def align_tab(lines: list[str]) -> list[str]:
rows = []
ncols = 0
@@ -1695,16 +1905,15 @@ def termsize() -> tuple[int, int]:
# from hashwalk
env = os.environ
def ioctl_GWINSZ(fd):
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, "1234"))
cr = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"1234"))
return int(cr[1]), int(cr[0])
except:
return
return cr
return None
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
@@ -1715,13 +1924,13 @@ def termsize() -> tuple[int, int]:
except:
pass
if not cr:
try:
cr = (env["LINES"], env["COLUMNS"])
except:
cr = (25, 80)
if cr:
return cr
return int(cr[1]), int(cr[0])
try:
return int(env["COLUMNS"]), int(env["LINES"])
except:
return 80, 25
class Pebkac(Exception):

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,23 +219,26 @@ 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'],
['P', 'video: seek 10sec ahead'],
['0..9', 'video: seek 0%..90%'],
['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);
};
@@ -242,7 +249,7 @@ window.baguetteBox = (function () {
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing || modal.busy)
return;
var k = e.code + '', v = vid();
var k = e.code + '', v = vid(), pos = -1;
if (k == "ArrowLeft" || k == "KeyJ")
showPreviousImage();
@@ -258,6 +265,8 @@ window.baguetteBox = (function () {
playpause();
else if (k == "KeyU" || k == "KeyO")
relseek(k == "KeyU" ? -10 : 10);
else if (k.indexOf('Digit') === 0)
vid().currentTime = vid().duration * parseInt(k.slice(-1)) * 0.1;
else if (k == "KeyM" && v) {
v.muted = vmute = !vmute;
mp_ctl();
@@ -273,19 +282,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 +361,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 +433,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 +446,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 +455,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 +468,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 +489,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 +532,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 +544,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 +560,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 +580,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 +632,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);
}
@@ -670,18 +699,12 @@ window.baguetteBox = (function () {
showOverlay(index);
return true;
}
if (index < 0) {
if (options.animation)
bounceAnimation('left');
return false;
}
if (index >= imagesElements.length) {
if (options.animation)
bounceAnimation('right');
if (index < 0)
return bounceAnimation('left');
return false;
}
if (index >= imagesElements.length)
return bounceAnimation('right');
var v = vid();
if (v) {
@@ -789,8 +812,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 +842,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) {
@@ -822,10 +887,11 @@ window.baguetteBox = (function () {
}
function bounceAnimation(direction) {
slider.className = 'bounce-from-' + direction;
slider.className = options.animation == 'slideIn' ? 'bounce-from-' + direction : 'eog';
setTimeout(function () {
slider.className = '';
}, 400);
}, 300);
return false;
}
function updateOffset() {
@@ -851,6 +917,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 +937,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 +995,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 +1007,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);
@@ -258,7 +259,7 @@ html.bz {
--bg-d2: #34384e;
--bg-d3: #34384e;
--row-alt: rgba(139, 150, 205, 0.06);
--row-alt: #181a27;
--btn-bg: #202231;
--btn-h-bg: #2d2f45;
@@ -308,7 +309,7 @@ html.c {
--a-gray: #0ae;
--tab-alt: #6ef;
--row-alt: rgba(180,0,255,0.3);
--row-alt: #47237d;
--scroll: #ff0;
--btn-fg: #fff;
@@ -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;
@@ -534,6 +544,9 @@ html.dy {
--tree-bg: #fff;
--g-sel-bg: #000;
--g-fsel-bg: #444;
--g-fsel-ts: #000;
--g-fg: a;
--g-bg: a;
--g-b1: a;
@@ -697,6 +710,7 @@ html.y #files thead th {
#files td {
margin: 0;
padding: .3em .5em;
background: var(--bg);
}
#files tr:nth-child(2n) td {
background: var(--row-alt);
@@ -1585,9 +1599,6 @@ html.y #tree.nowrap .ntree a+a:hover {
margin: .7em 0 .7em .5em;
padding-left: .5em;
}
.opwide>div.fill {
display: block;
}
.opwide>div>div>a {
line-height: 2em;
}
@@ -1898,10 +1909,13 @@ html.y #bbox-overlay figcaption a {
transition: left .2s ease, transform .2s ease;
}
.bounce-from-right {
animation: bounceFromRight .4s ease-out;
animation: bounceFromRight .3s ease-out;
}
.bounce-from-left {
animation: bounceFromLeft .4s ease-out;
animation: bounceFromLeft .3s ease-out;
}
.eog {
animation: eog .2s;
}
@keyframes bounceFromRight {
0% {margin-left: 0}
@@ -1913,6 +1927,9 @@ html.y #bbox-overlay figcaption a {
50% {margin-left: 30px}
100% {margin-left: 0}
}
@keyframes eog {
0% {filter: brightness(1.5)}
}
#bbox-next,
#bbox-prev {
top: 50%;
@@ -1923,6 +1940,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 +1993,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 +2228,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;
@@ -2217,6 +2251,7 @@ html.y #bbox-overlay figcaption a {
max-width: none;
}
#u2tab td {
word-wrap: break-word;
border: 1px solid rgba(128,128,128,0.8);
border-width: 0 0px 1px 0;
padding: .2em .3em;
@@ -2231,7 +2266,19 @@ html.y #bbox-overlay figcaption a {
#u2tab.up.ok td:nth-child(3),
#u2tab.up.bz td:nth-child(3),
#u2tab.up.q td:nth-child(3) {
width: 19em;
width: 18em;
}
@media (max-width: 65em) {
#u2tab {
font-size: .9em;
}
}
@media (max-width: 50em) {
#u2tab.up.ok td:nth-child(3),
#u2tab.up.bz td:nth-child(3),
#u2tab.up.q td:nth-child(3) {
width: 16em;
}
}
#op_up2k.srch td.prog {
font-family: sans-serif;
@@ -2331,7 +2378,7 @@ html.y #bbox-overlay figcaption a {
width: 48em;
}
#u2conf.ww {
width: 74em;
width: 78em;
}
#u2conf.ww #u2c3w {
width: 29em;
@@ -2371,6 +2418,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 +2479,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 +2497,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 +2653,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)",
@@ -105,6 +106,7 @@ var Ls = {
"ct_thumb": "in icon view, toggle icons or thumbnails$NHotkey: T",
"ct_dots": "show hidden files (if server permits)",
"ct_dir1st": "sort folders before files",
"ct_readme": "show README.md in folder listings",
"cut_turbo": "the yolo button, you probably DO NOT want to enable this:$N$Nuse this if you were uploading a huge amount of files and had to restart for some reason, and want to continue the upload ASAP$N$Nthis replaces the hash-check with a simple <em>&quot;does this have the same filesize on the server?&quot;</em> so if the file contents are different it will NOT be uploaded$N$Nyou should turn this off when the upload is done, and then &quot;upload&quot; the same files again to let the client verify them",
@@ -113,6 +115,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",
@@ -143,7 +147,7 @@ var Ls = {
"mt_caac": "convert aac / m4a to opus\">aac",
"mt_coth": "convert all others (not mp3) to opus\">oth",
"mt_tint": "background level (0-100) on the seekbar$Nto make buffering less distracting",
"mt_eq": "enables the equalizer and gain control;$Nboost 0 = unmodified 100% volume$N$Nenabling the equalizer makes gapless albums fully gapless, so leave it on with all the values at zero if you care about that",
"mt_eq": "enables the equalizer and gain control;$N$Nboost &lt;code&gt;0&lt;/code&gt; = standard 100% volume (unmodified)$N$Nwidth &lt;code&gt;1 &nbsp;&lt;/code&gt; = standard stereo (unmodified)$Nwidth &lt;code&gt;0.5&lt;/code&gt; = 50% left-right crossfeed$Nwidth &lt;code&gt;0 &nbsp;&lt;/code&gt; = mono$N$Nboost &lt;code&gt;-0.8&lt;/code&gt; &amp; width &lt;code&gt;10&lt;/code&gt; = vocal removal :^)$N$Nenabling the equalizer makes gapless albums fully gapless, so leave it on with all the values at zero (except width = 1) if you care about that",
"mb_play": "play",
"mm_hashplay": "play this audio file?",
@@ -248,6 +252,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 +289,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',
@@ -301,9 +309,11 @@ var Ls = {
"u_upping": 'uploading',
"u_cuerr": "failed to upload chunk {0} of {1};\nprobably harmless, continuing\n\nfile: {2}",
"u_cuerr2": "server rejected upload (chunk {0} of {1});\n\nfile: {2}\n\nerror ",
"u_ehstmp": "will retry; see bottom-right",
"u_ehsfin": "server rejected the request to finalize upload",
"u_ehssrch": "server rejected the request to perform search",
"u_ehsinit": "server rejected the request to initiate upload",
"u_ehsdf": "server ran out of disk space!\n\nwill keep retrying, in case someone\nfrees up enough space to continue",
"u_s404": "not found on server",
"u_expl": "explain",
"u_tu": '<p class="warn">WARNING: turbo enabled, <span>&nbsp;client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
@@ -360,8 +370,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 +397,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)",
@@ -429,6 +440,7 @@ var Ls = {
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
"ct_dots": "vis skjulte filer (gitt at serveren tillater det)",
"ct_dir1st": "sorter slik at mapper kommer foran filer",
"ct_readme": "vis README.md nedenfor filene",
"cut_turbo": "forenklet befaring ved opplastning; bør sannsynlig <em>ikke</em> skrus på:$N$Nnyttig dersom du var midt i en svær opplastning som måtte restartes av en eller annen grunn, og du vil komme igang igjen så raskt som overhodet mulig.$N$Nnår denne er skrudd på så forenkles befaringen kraftig; istedenfor å utføre en trygg sjekk på om filene finnes på serveren i god stand, så sjekkes kun om <em>filstørrelsen</em> stemmer. Så dersom en korrupt fil skulle befinne seg på serveren allerede, på samme sted med samme størrelse og navn, så blir det <em>ikke oppdaget</em>.$N$Ndet anbefales å kun benytte denne funksjonen for å komme seg raskt igjennom selve opplastningen, for så å skru den av, og til slutt &quot;laste opp&quot; de samme filene én gang til -- slik at integriteten kan verifiseres",
@@ -437,6 +449,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",
@@ -467,7 +481,7 @@ var Ls = {
"mt_caac": "konverter aac / m4a-filer til to opus\">aac",
"mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre",
"mt_tint": "nivå av bakgrunnsfarge på søkestripa (0-100),$Ngjør oppdateringer mindre distraherende",
"mt_eq": "aktiver tonekontroll og forsterker;$Nboost 0 = normal volumskala$N$Nreduserer også dødtid imellom sangfiler",
"mt_eq": "aktiver tonekontroll og forsterker;$N$Nboost &lt;code&gt;0&lt;/code&gt; = normal volumskala$N$Nwidth &lt;code&gt;1 &nbsp;&lt;/code&gt; = normal stereo$Nwidth &lt;code&gt;0.5&lt;/code&gt; = 50% blanding venstre-høyre$Nwidth &lt;code&gt;0 &nbsp;&lt;/code&gt; = mono$N$Nboost &lt;code&gt;-0.8&lt;/code&gt; &amp; width &lt;code&gt;10&lt;/code&gt; = instrumental :^)$N$Nreduserer også dødtid imellom sangfiler",
"mb_play": "lytt",
"mm_hashplay": "spill denne sangen?",
@@ -572,6 +586,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 +612,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 +623,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',
@@ -625,9 +643,11 @@ var Ls = {
"u_upping": 'sender',
"u_cuerr": "kunne ikke laste opp del {0} av {1};\nsikkert harmløst, fortsetter\n\nfil: {2}",
"u_cuerr2": "server nektet opplastningen (del {0} av {1});\n\nfile: {2}\n\nerror ",
"u_ehstmp": "prøver igjen; se mld nederst",
"u_ehsfin": "server nektet forespørselen om å ferdigstille filen",
"u_ehssrch": "server nektet forespørselen om å utføre søk",
"u_ehsinit": "server nektet forespørselen om å begynne en ny opplastning",
"u_ehsdf": "serveren er full!\n\nprøver igjen regelmessig,\ni tilfelle noen rydder litt...",
"u_s404": "ikke funnet på serveren",
"u_expl": "forklar",
"u_tu": '<p class="warn">ADVARSEL: turbo er på, <span>&nbsp;avbrutte opplastninger vil muligens ikke oppdages og gjenopptas; hold musepekeren over turbo-knappen for mer info</span></p>',
@@ -715,6 +735,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 +785,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 +794,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>'
);
@@ -800,6 +824,7 @@ ebi('op_cfg').innerHTML = (
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">田 the grid</a>\n' +
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '">🖼️ thumbs</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '">dotfiles</a>\n' +
' <a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '">📁 first</a>\n' +
' <a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '">📜 readme</a>\n' +
' </div>\n' +
'</div>\n' +
@@ -822,6 +847,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' +
@@ -835,7 +861,7 @@ ebi('op_cfg').innerHTML = (
' </div>\n' +
'</div>\n' +
'<div><h3>' + L.cl_keytype + '</h3><div id="key_notation"></div></div>\n' +
'<div class="fill"><h3>' + L.cl_hiddenc + ' <a href="#" id="hcolsr">' + L.cl_reset + '</h3><div id="hcols"></div></div>'
'<div><h3>' + L.cl_hiddenc + ' <a href="#" id="hcolsr">' + L.cl_reset + '</h3><div id="hcols"></div></div>'
);
@@ -1871,6 +1897,7 @@ var audio_eq = (function () {
"gains": [4, 3, 2, 1, 0, 0, 1, 2, 3, 4],
"filters": [],
"amp": 0,
"chw": 1,
"last_au": null,
"acst": {}
};
@@ -1922,6 +1949,7 @@ var audio_eq = (function () {
try {
r.amp = fcfg_get('au_eq_amp', r.amp);
r.chw = fcfg_get('au_eq_chw', r.chw);
var gains = jread('au_eq_gain', r.gains);
if (r.gains.length == gains.length)
r.gains = gains;
@@ -1931,12 +1959,14 @@ var audio_eq = (function () {
r.draw = function () {
jwrite('au_eq_gain', r.gains);
swrite('au_eq_amp', r.amp);
swrite('au_eq_chw', r.chw);
var txt = QSA('input.eq_gain');
for (var a = 0; a < r.bands.length; a++)
txt[a].value = r.gains[a];
QS('input.eq_gain[band="amp"]').value = r.amp;
QS('input.eq_gain[band="chw"]').value = r.chw;
};
r.stop = function () {
@@ -2006,16 +2036,47 @@ var audio_eq = (function () {
for (var a = r.filters.length - 1; a >= 0; a--)
r.filters[a].connect(a > 0 ? r.filters[a - 1] : actx.destination);
if (Math.round(r.chw * 25) != 25) {
var split = actx.createChannelSplitter(2),
merge = actx.createChannelMerger(2),
lg1 = actx.createGain(),
lg2 = actx.createGain(),
rg1 = actx.createGain(),
rg2 = actx.createGain(),
vg1 = 1 - (1 - r.chw) / 2,
vg2 = 1 - vg1;
console.log('chw', vg1, vg2);
merge.connect(r.filters[r.filters.length - 1]);
lg1.gain.value = rg2.gain.value = vg1;
lg2.gain.value = rg1.gain.value = vg2;
lg1.connect(merge, 0, 0);
rg1.connect(merge, 0, 0);
lg2.connect(merge, 0, 1);
rg2.connect(merge, 0, 1);
split.connect(lg1, 0);
split.connect(lg2, 0);
split.connect(rg1, 1);
split.connect(rg2, 1);
r.filters.push(split);
mp.acs.channelCountMode = 'explicit';
}
mp.acs.connect(r.filters[r.filters.length - 1]);
}
function eq_step(e) {
ev(e);
var band = parseInt(this.getAttribute('band')),
var sb = this.getAttribute('band'),
band = parseInt(sb),
step = parseFloat(this.getAttribute('step'));
if (isNaN(band))
if (sb == 'amp')
r.amp = Math.round((r.amp + step * 0.2) * 100) / 100;
else if (sb == 'chw')
r.chw = Math.round((r.chw + step * 0.2) * 100) / 100;
else
r.gains[band] += step;
@@ -2025,15 +2086,18 @@ var audio_eq = (function () {
function adj_band(that, step) {
var err = false;
try {
var band = parseInt(that.getAttribute('band')),
var sb = that.getAttribute('band'),
band = parseInt(sb),
vs = that.value,
v = parseFloat(vs);
if (isNaN(v) || v + '' != vs)
throw new Error('inval band');
if (isNaN(band))
if (sb == 'amp')
r.amp = Math.round((v + step * 0.2) * 100) / 100;
else if (sb == 'chw')
r.chw = Math.round((v + step * 0.2) * 100) / 100;
else
r.gains[band] = v + step;
@@ -2070,6 +2134,7 @@ var audio_eq = (function () {
vs.push([a, hz, r.gains[a]]);
}
vs.push(["amp", "boost", r.amp]);
vs.push(["chw", "width", r.chw]);
for (var a = 0; a < vs.length; a++) {
var b = vs[a][0];
@@ -2192,6 +2257,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 +2392,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 +2434,7 @@ function eval_hash() {
return;
clearInterval(t);
baguetteBox.urltime(ts);
var im = QS('#ggrid a[ref="' + id + '"]');
im.click();
im.scrollIntoView();
@@ -2393,12 +2469,11 @@ function eval_hash() {
if (a)
QS(treectl.hidden ? '#path a:nth-last-child(2)' : '#treeul a.hl').focus();
else
QS(thegrid.en ? '#ggrid a' : '#files tbody a').focus();
QS(thegrid.en ? '#ggrid a' : '#files tbody tr[tabindex]').focus();
};
})(a);
var d = mknod('div');
d.setAttribute('id', 'acc_info');
var d = mknod('div', 'acc_info');
document.body.insertBefore(d, ebi('ops'));
})();
@@ -2407,7 +2482,8 @@ function sortfiles(nodes) {
if (!nodes.length)
return nodes;
var sopts = jread('fsort', [["href", 1, ""]]);
var sopts = jread('fsort', [["href", 1, ""]]),
dir1st = sread('dir1st') !== '0';
try {
var is_srch = false;
@@ -2438,14 +2514,10 @@ function sortfiles(nodes) {
if ((v + '').indexOf('<a ') === 0)
v = v.split('>')[1];
else if (name == "href" && v) {
if (v.split('?')[0].slice(-1) == '/')
v = '\t' + v;
else if (name == "href" && v)
v = uricom_dec(v)[0];
}
nodes[b]._sv = v;
nodes[b]._sv = v
}
}
@@ -2474,6 +2546,13 @@ function sortfiles(nodes) {
if (is_srch)
delete nodes[b].ext;
}
if (dir1st) {
var r1 = [], r2 = [];
for (var b = 0, bb = nodes.length; b < bb; b++)
(nodes[b].href.split('?')[0].slice(-1) == '/' ? r1 : r2).push(nodes[b]);
nodes = r1.concat(r2);
}
}
catch (ex) {
console.log("failed to apply sort config: " + ex);
@@ -2663,8 +2742,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 +3225,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 +3341,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 +3369,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 +3505,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">' +
@@ -3931,6 +4005,9 @@ document.onkeydown = function (e) {
}
}
if (k == 'Enter' && ae && (ae.onclick || ae.hasAttribute('tabIndex')))
return ev(e) && ae.click() || true;
if (aet && aet != 'a' && aet != 'tr' && aet != 'pre')
return;
@@ -4369,6 +4446,9 @@ var treectl = (function () {
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
r.goto(get_evpath());
});
bcfg_bind(r, 'dir1st', 'dir1st', true, function (v) {
treectl.gentab(get_evpath(), treectl.lsc);
});
setwrap(bcfg_bind(r, 'wtree', 'wraptree', true, setwrap));
setwrap(bcfg_bind(r, 'parpane', 'parpane', true, onscroll));
bcfg_bind(r, 'htree', 'hovertree', false, reload_tree);
@@ -4815,6 +4895,7 @@ var treectl = (function () {
}
r.gentab = function (top, res) {
r.lsc = res;
var nodes = res.dirs.concat(res.files),
html = mk_files_header(res.taglist),
seen = {};
@@ -4827,7 +4908,6 @@ var treectl = (function () {
bhref = tn.href.split('?')[0],
fname = uricom_dec(bhref)[0],
hname = esc(fname),
sortv = (bhref.slice(-1) == '/' ? '\t' : '') + hname,
id = 'f-' + ('00000000' + crc32(fname)).slice(-8),
lang = showfile.getlang(fname);
@@ -4842,8 +4922,8 @@ var treectl = (function () {
tn.lead = '<a href="?doc=' + tn.href + '" class="doc' + (lang ? ' bri' : '') +
'" hl="' + id + '" name="' + hname + '">-txt-</a>';
var ln = ['<tr><td>' + tn.lead + '</td><td sortv="' + sortv +
'"><a href="' + top + tn.href + '" id="' + id + '">' + hname + '</a>', tn.sz];
var ln = ['<tr><td>' + tn.lead + '</td><td><a href="' +
top + tn.href + '" id="' + id + '">' + hname + '</a>', tn.sz];
for (var b = 0; b < res.taglist.length; b++) {
var k = res.taglist[b],
@@ -5910,7 +5990,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

@@ -36,6 +36,7 @@
<tr><td>hash-q</td><td>{{ hashq }}</td></tr>
<tr><td>tag-q</td><td>{{ tagq }}</td></tr>
<tr><td>mtp-q</td><td>{{ mtpq }}</td></tr>
<tr><td>db-act</td><td id="u">{{ dbwt }}</td></tr>
</table>
</td><td>
<table class="vols">
@@ -50,8 +51,8 @@
</table>
</td></tr></table>
<div class="btns">
<a id="d" href="/?stack" tt="shows the state of all active threads">dump stack</a>
<a id="e" href="/?reload=cfg" tt="reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes">reload cfg</a>
<a id="d" href="/?stack">dump stack</a>
<a id="e" href="/?reload=cfg">reload cfg</a>
</div>
{%- endif %}
@@ -97,7 +98,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

@@ -23,6 +23,12 @@ var Ls = {
"r1": "gå hjem",
".s1": "kartlegg",
"t1": "handling",
"u2": "tid siden noen sist skrev til serveren$N( opplastning / navneendring / ... )$N$N17d = 17 dager$N1h23 = 1 time 23 minutter$N4m56 = 4 minuter 56 sekunder",
},
"eng": {
"d2": "shows the state of all active threads",
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes",
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
}
},
d = Ls[sread("lang") || lang];
@@ -40,5 +46,10 @@ for (var k in (d || {})) {
}
tt.init();
if (!ebi('c'))
QS('input[name="cppwd"]').focus();
var o = QS('input[name="cppwd"]');
if (!ebi('c') && o.offsetTop + o.offsetHeight < window.innerHeight)
o.focus();
o = ebi('u');
if (o && /[0-9]+$/.exec(o.innerHTML))
o.innerHTML = shumantime(o.innerHTML);

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' + 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];
}
};
@@ -238,18 +250,13 @@ function U2pvis(act, btns, uc) {
nb = fo.bt * (++fo.nh / fo.cb.length),
p = r.perc(nb, 0, fobj.size, fobj.t_hashing);
fo.hp = '{0}%, {1}, {2} MB/s'.format(
f2f(p[0], 2), p[1], f2f(p[2], 2)
);
fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MB/s';
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) {
@@ -260,14 +267,12 @@ function U2pvis(act, btns, uc) {
fo.bd += delta;
var p = r.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
fo.hp = '{0}%, {1}, {2} MB/s'.format(
f2f(p[0], 2), p[1], f2f(p[2], 2)
);
fo.hp = f2f(p[0], 2) + '%, ' + p[1] + ', ' + f2f(p[2], 2) + ' MB/s';
if (!r.is_act(fo.in))
return;
var obj = ebi('f{0}p'.format(fobj.n)),
var obj = ebi('f' + fobj.n + 'p'),
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
if (!obj) {
@@ -304,9 +309,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 +323,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 +349,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 +362,132 @@ 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' + k + 'p');
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 +569,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 +603,7 @@ function U2pvis(act, btns, uc) {
}
r.changecard(r.act);
r.potato();
}
@@ -527,7 +655,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 +671,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}, #{2}, ".format(
f2f(v * 100 / t, 1), shumantime(r.eta), st.files.length - st.nfile.upload), true);
r.tc = 0;
}
@@ -578,6 +712,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 +752,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 +790,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 +831,16 @@ function up2k_init(subtle) {
"hashing": 0,
"uploading": 0,
"busy": 0
}
},
"eta": {
"h": "",
"u": "",
"t": ""
},
"car": 0,
"modn": 0,
"modv": 0,
"mod0": null
};
function push_t(arr, t) {
@@ -693,9 +852,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;
@@ -758,8 +921,14 @@ function up2k_init(subtle) {
catch (ex) { }
ev(e);
e.dataTransfer.dropEffect = 'copy';
e.dataTransfer.effectAllowed = 'copy';
try {
e.dataTransfer.dropEffect = 'copy';
e.dataTransfer.effectAllowed = 'copy';
}
catch (ex) {
document.body.ondragenter = document.body.ondragleave = document.body.ondragover = null;
return modal.alert('your browser does not support drag-and-drop uploading');
}
clmod(ebi('drops'), 'vis', 1);
var v = this.getAttribute('v');
if (v)
@@ -808,6 +977,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 +1077,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 +1126,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 +1180,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 +1217,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 +1235,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 +1250,7 @@ function up2k_init(subtle) {
pvis.drawcard("q");
pvis.changecard(pvis.act);
}
ebi('u2tabw').className = 'ye';
}
function more_one_file() {
@@ -1073,7 +1262,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,18 +1275,32 @@ 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;
ebi('u2tabw').style.minHeight = utw_minh + 'px';
}
if (!nhash)
ebi('u2etah').innerHTML = L.u_etadone.format(humansize(st.bytes.hashed), pvis.ctr["ok"] + pvis.ctr["ng"]);
if (!nhash) {
var h = L.u_etadone.format(humansize(st.bytes.hashed), pvis.ctr.ok + pvis.ctr.ng);
if (st.eta.h !== h)
st.eta.h = ebi('u2etah').innerHTML = h;
}
if (!nsend && !nhash)
ebi('u2etau').innerHTML = ebi('u2etat').innerHTML = (
L.u_etadone.format(humansize(st.bytes.uploaded), pvis.ctr["ok"] + pvis.ctr["ng"]));
if (!nsend && !nhash) {
var h = L.u_etadone.format(humansize(st.bytes.uploaded), pvis.ctr.ok + pvis.ctr.ng);
if (st.eta.u !== h)
st.eta.u = ebi('u2etau').innerHTML = h;
if (st.eta.t !== h)
st.eta.t = ebi('u2etat').innerHTML = h;
}
if (!st.busy.hash.length && !hashing_permitted())
nhash = 0;
@@ -1128,19 +1331,21 @@ function up2k_init(subtle) {
for (var a = 0; a < t.length; a++) {
var rem = st.bytes.total - t[a][2],
bps = t[a][1] / t[a][3],
hid = t[a][0],
eid = hid.slice(-1),
eta = Math.floor(rem / bps);
if (t[a][1] < 1024 || t[a][3] < 0.1) {
ebi(t[a][0]).innerHTML = L.u_etaprep;
ebi(hid).innerHTML = L.u_etaprep;
continue;
}
donut.eta = eta;
if (etaskip)
continue;
ebi(t[a][0]).innerHTML = '{0}, {1}/s, {2}'.format(
st.eta[eid] = '{0}, {1}/s, {2}'.format(
humansize(rem), humansize(bps, 1), humantime(eta));
if (!etaskip)
ebi(hid).innerHTML = st.eta[eid];
}
if (++etaskip > 2)
etaskip = 0;
@@ -1170,6 +1375,10 @@ function up2k_init(subtle) {
st.busy.handshake.length)
return false;
if (t.n - st.car > 8)
// prevent runahead from a stuck upload (slow server hdd)
return false;
if ((uc.multitask ? 1 : 0) <
st.todo.upload.length +
st.busy.upload.length)
@@ -1183,9 +1392,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 +1426,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 +1454,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 +1507,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 +1532,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 +1565,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 +1664,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 +1702,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 +1735,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 +1761,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 +1832,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 +1988,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)
@@ -1757,6 +2000,9 @@ function up2k_init(subtle) {
tasker();
}
else {
pvis.seth(t.n, 1, "ERROR");
pvis.seth(t.n, 2, L.u_ehstmp);
var err = "",
rsp = (xhr.responseText + ''),
ofs = rsp.lastIndexOf('\nURL: ');
@@ -1791,6 +2037,9 @@ function up2k_init(subtle) {
t.want_recheck = true;
}
}
if (rsp.indexOf('server HDD is full') + 1)
return toast.err(0, L.u_ehsdf + "\n\n" + rsp.replace(/.*; /, ''));
if (err != "") {
pvis.seth(t.n, 1, "ERROR");
pvis.seth(t.n, 2, err);
@@ -1805,7 +2054,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 +2093,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 +2141,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 +2164,9 @@ 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} {6}".format(
pvis.ctr.ok, pvis.ctr.ng, pvis.ctr.bz, pvis.ctr.q, btot, btot - bfin,
st.eta.t.split(' ').pop()));
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
if (xhr.overrideMimeType)
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
@@ -1942,7 +2198,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 +2272,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 +2331,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 +2358,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 +2388,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 +2409,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,9 @@ if (!window['console'])
};
var is_touch = 'ontouchstart' in window,
var wah = '',
HALFMAX = 8192 * 8192 * 8192 * 8192,
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 +17,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 +169,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 +230,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) {
@@ -446,6 +460,16 @@ function sortTable(table, col, cb) {
}
return reverse * (a.localeCompare(b));
});
if (sread('dir1st') !== '0') {
var r1 = [], r2 = [];
for (var i = 0; i < tr.length; i++) {
var cell = tr[vl[i][1]].cells[1],
href = cell.getAttribute('sortv') || cell.textContent.trim();
(href.split('?')[0].slice(-1) == '/' ? r1 : r2).push(vl[i]);
}
vl = r1.concat(r2);
}
for (i = 0; i < tr.length; ++i) tb.appendChild(tr[vl[i][1]]);
if (cb) cb();
}
@@ -629,7 +653,7 @@ function humansize(b, terse) {
function humantime(v) {
if (v >= 60 * 60 * 24)
return v;
return shumantime(v);
try {
return /.*(..:..:..).*/.exec(new Date(v * 1000).toUTCString())[1];
@@ -640,12 +664,39 @@ function humantime(v) {
}
function shumantime(v) {
if (v < 10)
return f2f(v, 2) + 's';
if (v < 60)
return f2f(v, 1) + 's';
v = parseInt(v);
var st = [[60 * 60 * 24, 60 * 60, 'd'], [60 * 60, 60, 'h'], [60, 1, 'm']];
for (var a = 0; a < st.length; a++) {
var m1 = st[a][0],
m2 = st[a][1],
ch = st[a][2];
if (v < m1)
continue;
var v1 = parseInt(v / m1),
v2 = ('0' + parseInt((v % m1) / m2)).slice(-2);
return v1 + ch + (v1 >= 10 ? '' : v2);
}
}
function clamp(v, a, b) {
return Math.min(Math.max(v, a), b);
}
function has(haystack, needle) {
try { return haystack.includes(needle); } catch (ex) { }
for (var a = 0; a < haystack.length; a++)
if (haystack[a] == needle)
return true;
@@ -865,8 +916,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 +925,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 +1093,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 +1177,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 +1232,8 @@ var modal = (function () {
return;
setTimeout(function () {
ebi('modal-ok').focus();
if (ctr = ebi('modal-ok'))
ctr.focus();
}, 20);
ev(e);
}
@@ -1426,12 +1474,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 +1504,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 +1519,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 +1530,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,178 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2022-0727-1407 `v1.3.8` more async
* 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 arg `--df 4` and volflag `:c,df=4g` to guarantee 4 GiB free disk space by rejecting uploads
* some features no longer block new uploads while they're processing
* `-e2v` file integrity checker
* `-e2ts` initial tag scanner
* hopefully fixes a [deadlock](https://www.youtube.com/watch?v=DkKoMveT_jo&t=3s) someone ran into (but probably doesn't)
* (the "deadlock" link is an addictive demoscene banger -- the actual issue is #10)
* reduced the impact of some features which still do
* defer `--re-maxage` reindexing if there was a write (upload/rename/...) recently
* `--db-act` sets minimum idle period before reindex can start (default 10sec)
* bbox / image-viewer: add video hotkeys 0..9 to seek 0%..90%
* audio-player: add audio crossfeed (left-right channel mixer / vocal isolation)
* splashpage (`/?h`) shows time since the most recent write
## bugfixes
* a11y:
* enter-key should always trigger onclick
* only focus password box if in-bounds
* improve skip-to-files
* prisonparty: volume labeling in root folders
* other minor stuff
* forget deleted shadowed files from the db
* be less noisy if a client disconnects mid-reply
* up2k.js less eager to thrash slow server HDDs
## other changes
* show client's upload ETA in server log
* dump stacks and issue `lsof` on the db if a transaction is stuck
* will hopefully help if there's any more deadlocks
* [up2k-hook-ytid](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/up2k-hook-ytid.js) (the overengineered up2k.js plugin example) now has an mp4/webm/mkv metadata parser
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2022-0716-1848 `v1.3.7` faster
* 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
* `up2k.js`: **improved upload speeds!**
* **...when there's many small files** (or the browser is slow)
* add [potato mode](https://user-images.githubusercontent.com/241032/179336639-8ecc01ea-2662-4cb6-8048-5be3ad599f33.png) -- lightweight UI for faster uploads from slow boxes
* enables automatically if it detects a cpu bottleneck (not very accurate)
* **...on really fast connections (LAN / fiber)**
* batch progress updates to reduce repaints
* **...when there is a mix of big and small files**
* sort the uploads by size, smallest first, for optimal cpu/network usage
* can be overridden to alphabetical order in the settings tab
* new arg `--u2sort` changes the default + overrides the override button
* improve upload pacing when alphabetical order is enabled
* mainly affecting single files that are 300 GiB +
* `up2k.js`: add [up2k hooks](https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/up2k-hooks.js)
* specify *client-side* rules to reject files as they are dropped into the browser
* not a hard-reject since people can use [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) and whatnot, more like a hint
* `up2k.py`: add file integrity checker
* new arg `-e2v` to scan volumes and verify file checksums on startup
* `-e2vu` updates the db on mismatch, `-e2vp` panics
* uploads are blocked while the scan is running -- might get fixed at some point
* for now it prints a warning
* bbox / image-viewer: doubletap a picture to enter fullscreen mode
* md-editor: `ctrl-c/x` affects current line if no selection, and `ctrl-e` is fullscreen
* tag-parser plugins:
* add support for passing metadata from one mtp to another (parser dependencies)
* the `p` flag in [vidchk](https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/vidchk.py) usage makes it run after the base parser, eating its output
* add [rclone uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/rclone-upload.py) which optionally and by default depends on vidchk
## bugfixes
* sfx would crash if it got the same PID as recently (for example across two reboots)
* audio equalizer on recent chromes
* still can't figure out why chrome sometimes drops the mediasession
* bbox: don't attach click events to videos
* up2k.py:
* more sensible behavior w/ blank files
* avoid some extra directory scans when deleting files
* faster shutdown on `ctrl-c` during volume indexing
* warning from the thumbnail cleaner if the volume has no thumbnails
* `>fixing py2 support` `>2022`
## other changes
* up2k.js:
* sends a summary of the upload queue to [the server log](https://github.com/9001/copyparty#up2k)
* shows a toast while loading huge filedrops to indicate it's still alive
* sfx: disable guru meditation unless running on windows
* avoids hanging systemd on certain crashes
* logs the state of all threads if sqlite hits a timeout
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2022-0706-0029 `v1.3.5` sup cloudflare
* 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
* detect + recover from cloudflare ddos-protection memes during upload
* while carefully avoiding any mention of "DDoS" in the JS because enterprise firewalls do not enjoy that
* new option `--favico` to specify a default favicon
* set to `🎉` by default, which also enables the fancy upload progress donut 👌
* baguettebox (image/video viewer):
* toolbar button `⛶` to enter fullscreen mode (same as hotkey `F`)
* tap middle of screen to show/hide toolbar
* tap left/right-side of pics to navigate prev/next
* hotkeys `[` and `]` to set A-B loop in videos
* and [URL parameters](https://a.ocv.me/pub/demo/pics-vids/#gf-e2e482ae&t=4.2-6) for that + [initial seekpoint](https://a.ocv.me/pub/demo/pics-vids/#gf-c04bb0f6&t=26s) (same as the audio player)
## bugfixes
* when a tag-parser hits the timeout, `pkill` all its descendants too
* and a [new mtp flag](https://github.com/9001/copyparty/#file-parser-plugins) to override that; `kt` (kill tree, default), `km` (kill main, old default), `kn` (kill none)
* cpu-wasting spin while waiting for the final handful of files to finish tag-scraping
* detection of sparse-files support inside [prisonparty](https://github.com/9001/copyparty/tree/hovudstraum/bin#prisonpartysh) and other strict jails
* baguettebox (image/video viewer):
* crash on swipe during close
* didn't reset terminal color at the end of `?ls=v`
* don't try to thumbnail empty files (harmless but dumb)
## other changes
* ux improvements
* hide the uploads table until something happens
* bump codemirror to 5.65.6
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 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

10
docs/notes.md Normal file
View File

@@ -0,0 +1,10 @@
# up2k.js
## potato detection
* tsk 0.25/8.4/31.5 bzw 1.27/22.9/18 = 77% (38.4s, 49.7s)
* 4c locale #1313, ff-102,deb-11 @ ryzen4500u wifi -> win10
* profiling shows 2sec heavy gc every 2sec
* tsk 0.41/4.1/10 bzw 1.41/9.9/7 = 73% (13.3s, 18.2s)
* 4c locale #1313, ch-103,deb-11 @ ryzen4500u wifi -> win10

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}'
##
@@ -182,7 +185,7 @@ brew install python@2
pip install virtualenv
# readme toc
cat README.md | awk 'function pr() { if (!h) {return}; if (/^ *[*!#|]/||!s) {printf "%s\n",h;h=0;return}; if (/.../) {printf "%s - %s\n",h,$0;h=0}; }; /^#/{s=1;pr()} /^#* *(file indexing|install on android|dev env setup|just the sfx|complete release|optional gpl stuff)|`$/{s=0} /^#/{lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab); h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab);next} !h{next} {sub(/ .*/,"");sub(/[:,]$/,"")} {pr()}' > toc; grep -E '^## readme toc' -B1000 -A2 <README.md >p1; grep -E '^## quickstart' -B2 -A999999 <README.md >p2; (cat p1; grep quickstart -A1000 <toc; cat p2) >README.md; rm p1 p2 toc
cat README.md | awk 'function pr() { if (!h) {return}; if (/^ *[*!#|]/||!s) {printf "%s\n",h;h=0;return}; if (/.../) {printf "%s - %s\n",h,$0;h=0}; }; /^#/{s=1;pr()} /^#* *(install on android|dev env setup|just the sfx|complete release|optional gpl stuff)|`$/{s=0} /^#/{lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab); h=sprintf("%" ((lv-1)*4+1) "s [%s](#%s)", "*",$0,bab);next} !h{next} {sub(/ .*/,"");sub(/[:;,]$/,"")} {pr()}' > toc; grep -E '^## readme toc' -B1000 -A2 <README.md >p1; grep -E '^## quickstart' -B2 -A999999 <README.md >p2; (cat p1; grep quickstart -A1000 <toc; cat p2) >README.md; rm p1 p2 toc
# fix firefox phantom breakpoints,
# suggestions from bugtracker, doesnt work (debugger is not attachable)

View File

@@ -2,9 +2,9 @@ FROM alpine:3.16
WORKDIR /z
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
ver_hashwasm=4.9.0 \
ver_marked=4.0.17 \
ver_marked=4.0.18 \
ver_mde=2.16.1 \
ver_codemirror=5.65.5 \
ver_codemirror=5.65.7 \
ver_fontawesome=5.13.0 \
ver_zopfli=1.0.3

View File

@@ -26,6 +26,11 @@ help() { exec cat <<'EOF'
# (browsers will try to use 'Consolas' instead)
#
# `no-dd` saves ~2k by removing the mouse cursor
#
# ---------------------------------------------------------------------
#
# if you are on windows, you can use msys2:
# PATH=/c/Users/$USER/AppData/Local/Programs/Python/Python310:"$PATH" ./make-sfx.sh fast
EOF
}
@@ -106,7 +111,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 ] || {
@@ -190,7 +195,7 @@ tmpdir="$(
done
# remove type hints before build instead
(cd copyparty; python3 ../../scripts/strip_hints/a.py; rm uh)
(cd copyparty; "$pybin" ../../scripts/strip_hints/a.py; rm uh)
}
ver=

View File

@@ -8,7 +8,7 @@ cmd = sys.argv[1]
if cmd == "cpp":
from copyparty.__main__ import main
argv = ["__main__", "-v", "srv::r", "-v", "../../yt:yt:r"]
argv = ["__main__", "-vsrv::r:c,e2ds,e2ts"]
main(argv=argv)
elif cmd == "test":
@@ -29,6 +29,6 @@ else:
#
# python -m vmprof -o prof --lines ./scripts/profile.py test
# linux: ~/.local/bin/vmprofshow prof tree | grep -vF '[1m 0.'
# macos: ~/Library/Python/3.9/bin/vmprofshow prof tree | grep -vF '[1m 0.'
# linux: ~/.local/bin/vmprofshow prof tree | awk '$2>1{n=5} !n{next} 1;{n--} !n{print""}'
# macos: ~/Library/Python/3.9/bin/vmprofshow prof tree
# win: %appdata%\..\Roaming\Python\Python39\Scripts\vmprofshow.exe prof tree

View File

@@ -0,0 +1,4 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from Queue import Queue, LifoQueue, PriorityQueue, Empty, Full

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

@@ -10,9 +10,10 @@ import pprint
import tarfile
import tempfile
import unittest
from argparse import Namespace
from tests import util as tu
from tests.util import Cfg
from copyparty.authsrv import AuthSrv
from copyparty.httpcli import HttpCli
@@ -22,56 +23,6 @@ def hdr(query):
return h.format(query).encode("utf-8")
class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None):
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()}
)
class TestHttpCli(unittest.TestCase):
def setUp(self):
self.td = tu.get_ramdisk()

View File

@@ -8,44 +8,14 @@ import shutil
import tempfile
import unittest
from textwrap import dedent
from argparse import Namespace
from tests import util as tu
from tests.util import Cfg
from copyparty.authsrv import AuthSrv, VFS
from copyparty import util
class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None):
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots no_thumb no_athumb no_vthumb"
ex = {k: False for k in ex.split()}
ex2 = {
"mtp": [],
"mte": "a",
"mth": "",
"doctitle": "",
"html_head": "",
"hist": None,
"no_idx": None,
"no_hash": None,
"js_browser": None,
"css_browser": None,
"no_voldump": True,
"re_maxage": 0,
"rproxy": 0,
"rsp_slp": 0,
"s_wr_slp": 0,
"s_wr_sz": 512 * 1024,
"lang": "eng",
"theme": 0,
"themes": 0,
"turbo": 0,
"logout": 573,
}
ex.update(ex2)
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)
class TestVFS(unittest.TestCase):
def setUp(self):
self.td = tu.get_ramdisk()

View File

@@ -7,6 +7,7 @@ import threading
import tempfile
import platform
import subprocess as sp
from argparse import Namespace
WINDOWS = platform.system() == "Windows"
@@ -89,6 +90,40 @@ def get_ramdisk():
return subdir(ret)
class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None):
ka = {}
ex = "e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp xdev xvol 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 nid nih nw"
ka.update(**{k: False for k in ex.split()})
ex = "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 df"
ka.update(**{k: 0 for k in ex.split()})
ex = "doctitle favico html_head mth textfiles log_fk"
ka.update(**{k: "" for k in ex.split()})
super(Cfg, self).__init__(
a=a or [],
v=v or [],
c=c,
s_wr_sz=512 * 1024,
unpost=600,
u2sort="s",
mtp=[],
mte="a",
lang="eng",
logout=573,
**ka
)
class NullBroker(object):
def say(*args):
pass