Compare commits

..

60 Commits

Author SHA1 Message Date
ed
dc2ea20959 v1.2.8 2022-04-30 02:16:34 +02:00
ed
8eaea2bd17 ux 2022-04-30 00:37:31 +02:00
ed
58e559918f fix dynamic tree sizing 2022-04-30 00:04:06 +02:00
ed
f38a3fca5b case-insensitive cover check 2022-04-29 23:39:16 +02:00
ed
1ea145b384 wow when did that break 2022-04-29 23:37:38 +02:00
ed
0d9567575a avoid hashing busy uploads during rescan 2022-04-29 23:16:23 +02:00
ed
e82f176289 fix deadlock on rescan during upload 2022-04-29 23:14:51 +02:00
ed
d4b51c040e doc + ux 2022-04-29 23:13:37 +02:00
ed
125d0efbd8 good stuff 2022-04-29 02:06:56 +02:00
ed
3215afc504 immediately search on enter key 2022-04-28 22:53:37 +02:00
ed
c73ff3ce1b avoid sqlite deadlock on windows 2022-04-28 22:46:53 +02:00
ed
f9c159a051 add option to force up2k turbo + hide warning 2022-04-28 21:57:37 +02:00
ed
2ab1325c90 add option to load more search results 2022-04-28 21:55:01 +02:00
ed
5b0f7ff506 perfect 2022-04-28 10:36:56 +02:00
ed
9269bc84f2 skip more stuff windows doesn't like 2022-04-28 10:31:10 +02:00
ed
4e8b651e18 too much effort into this joke 2022-04-28 10:29:54 +02:00
ed
65b4f79534 add themes "vice" and "hot dog stand" 2022-04-27 22:33:01 +02:00
ed
5dd43dbc45 ignore bugs in chrome v102 2022-04-27 22:32:11 +02:00
ed
5f73074c7e fix audio playback on first visit 2022-04-27 22:31:33 +02:00
ed
f5d6ba27b2 handle invalid headers better 2022-04-27 22:30:19 +02:00
ed
73fa70b41f fix mostly-harmless xss 2022-04-27 22:29:16 +02:00
ed
2a1cda42e7 avoid deadlocks on windows 2022-04-27 22:27:49 +02:00
ed
1bd7e31466 more theme porting 2022-04-26 00:42:00 +02:00
ed
eb49e1fb4a conditional up2k column sizes depending on card 2022-04-24 23:48:23 +02:00
ed
9838c2f0ce golf 2022-04-24 23:47:15 +02:00
ed
6041df8370 start replacing class-scopes with css variables 2022-04-24 23:46:38 +02:00
ed
2933dce3ef mtime blank uploads + helptext 2022-04-24 22:58:11 +02:00
ed
dab377d37b v1.2.7 2022-04-16 23:44:28 +02:00
ed
f35e41baf1 allow unposting with write-only access 2022-04-16 23:35:04 +02:00
ed
c4083a2942 v1.2.6 2022-04-15 20:09:50 +02:00
ed
36c20bbe53 fix setting mtime on windows 2022-04-15 20:08:55 +02:00
ed
e34634f5af v1.2.5 2022-04-15 19:42:40 +02:00
ed
cba9e5b669 add hardlinks (symlink alternative) for up2k dedup 2022-04-15 19:13:53 +02:00
ed
1f3c46a6b0 forgot some css files 2022-04-15 17:11:46 +02:00
ed
799a5ffa47 v1.2.4 2022-04-14 21:45:22 +02:00
ed
b000707c10 detect poor ffmpeg builds 2022-04-14 18:20:48 +02:00
ed
feba4de1d6 make gallery linkable 2022-04-14 17:12:56 +02:00
ed
951fdb27ca dont scan orphaned volumes 2022-04-14 17:11:51 +02:00
ed
9697fb3d84 option to disable thumbnails per volume 2022-04-14 17:11:26 +02:00
ed
2dbed4500a add flat theme 2022-04-14 16:57:51 +02:00
ed
fd9d0e433d thumbnails: try FFmpeg for images too 2022-04-11 10:38:57 +02:00
ed
f096f3ef81 thumbnails: disable pdf because too scary 2022-04-10 23:02:09 +02:00
ed
cc4a063695 thumbnails: per-decoder filetype config 2022-04-10 22:59:45 +02:00
ed
b64cabc3c9 thumbnails: add pyvips as alt/supp. to pillow 2022-04-10 14:16:09 +02:00
ed
3dd460717c add flat theme 2022-04-09 23:05:54 +02:00
ed
bf658a522b naming 2022-04-09 20:41:08 +02:00
ed
e9be7e712d futureproof clipboard function 2022-04-09 19:38:05 +02:00
ed
e40cd2a809 optimize window resizing 2022-04-09 19:20:09 +02:00
ed
dbabeb9692 gallery: add animation preferences 2022-04-09 17:23:54 +02:00
ed
8dd37d76b0 fix drifting resize 2022-04-09 14:37:25 +02:00
ed
fd475aa358 textviewer: translate basic ansi/sgr colors 2022-04-09 00:50:54 +02:00
ed
f0988c0e32 filter some volflags from up2k dump 2022-04-08 21:56:24 +02:00
ed
0632f09bff rhel8 ignores flock and kills us anyways 2022-04-08 21:29:31 +02:00
ed
ba599aaca0 explain systemd jank 2022-04-08 20:39:22 +02:00
ed
ff05919e89 support mpc/musepack audio (streaming + thumbnailing) 2022-04-02 22:17:16 +02:00
ed
52e63fa101 dont crash when mediaplayer config is changed while music isnt playing 2022-03-28 23:17:02 +02:00
ed
96ceccd12a v1.2.3 2022-03-24 02:35:53 +01:00
ed
87994fe006 retry failed uploads with backoff 2022-03-24 02:29:59 +01:00
ed
fa12c81a03 zip-download files older than 1980-01-01 2022-03-24 01:31:50 +01:00
ed
344ce63455 basic-browser is implicitly not js 2022-03-21 01:20:47 +01:00
40 changed files with 2048 additions and 1217 deletions

View File

@@ -63,6 +63,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md) * [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
* [upload events](#upload-events) - trigger a script/program on each upload * [upload events](#upload-events) - trigger a script/program on each upload
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed * [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
* [themes](#themes)
* [complete examples](#complete-examples) * [complete examples](#complete-examples)
* [browser support](#browser-support) - TLDR: yes * [browser support](#browser-support) - TLDR: yes
* [client examples](#client-examples) - interact with copyparty using non-browser clients * [client examples](#client-examples) - interact with copyparty using non-browser clients
@@ -174,7 +175,7 @@ feature summary
* ☑ image gallery with webm player * ☑ image gallery with webm player
* ☑ textfile browser with syntax hilighting * ☑ textfile browser with syntax hilighting
* ☑ [thumbnails](#thumbnails) * ☑ [thumbnails](#thumbnails)
* ☑ ...of images using Pillow * ☑ ...of images using Pillow, pyvips, or FFmpeg
* ☑ ...of videos using FFmpeg * ☑ ...of videos using FFmpeg
* ☑ ...of audio (spectrograms) using FFmpeg * ☑ ...of audio (spectrograms) using FFmpeg
* ☑ cache eviction (max-age; maybe max-size eventually) * ☑ cache eviction (max-age; maybe max-size eventually)
@@ -247,6 +248,8 @@ some improvement ideas
## not my bugs ## not my bugs
* [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11) * iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume * *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
* "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day... * "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day...
@@ -403,7 +406,9 @@ press `g` to toggle grid-view instead of the file listing, and `t` toggles icon
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png) ![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png)
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are it does static images with Pillow / pyvips / FFmpeg, and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
* pyvips is 3x faster than Pillow, Pillow is 3x faster than FFmpeg
* disable thumbnails for specific volumes with volflag `dthumb` for all, or `dvthumb` / `dathumb` / `dithumb` for video/audio/images only
audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`) audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`)
@@ -804,6 +809,29 @@ tell search engines you dont wanna be indexed, either using the good old [robot
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
## themes
you can change the default theme with `--theme 2`, and add your own themes by modifying `browser.css` or providing your own css to `--css-browser`, then telling copyparty they exist by increasing `--themes`
<table><tr><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864907-17e2ac7d-319d-4f25-8718-2f376f614b51.png"><img src="https://user-images.githubusercontent.com/241032/165867551-fceb35dd-38f0-42bb-bef3-25ba651ca69b.png"></a>
0. classic dark</td><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864904-c5b67ddd-f383-4b9e-9f5a-a3bde183d256.png"><img src="https://user-images.githubusercontent.com/241032/165867556-077b6068-2488-4fae-bf88-1fce40e719bc.png"></a>
2. flat dark</td><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864901-db13a429-a5da-496d-8bc6-ce838547f69d.png"><img src="https://user-images.githubusercontent.com/241032/165867560-aa834aef-58dc-4abe-baef-7e562b647945.png"></a>
4. vice</td></tr><tr><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864905-692682eb-6fb4-4d40-b6fe-27d2c7d3e2a7.png"><img src="https://user-images.githubusercontent.com/241032/165867555-080b73b6-6d85-41bb-a7c6-ad277c608365.png"></a>
1. classic light</td><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864903-7fba1cb9-036b-4f11-90d5-28b7c0724353.png"><img src="https://user-images.githubusercontent.com/241032/165867557-b5cc0010-d880-48b1-8156-9c84f7bbc521.png"></a>
3. flat light
</td><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864898-10ce7052-a117-4fcf-845b-b56c91687908.png"><img src="https://user-images.githubusercontent.com/241032/165867562-f3003d45-dd2a-4564-8aae-fed44c1ae064.png"></a>
5. <a href="https://blog.codinghorror.com/a-tribute-to-the-windows-31-hot-dog-stand-color-scheme/">hotdog stand</a></td></tr></table>
the classname of the HTML tag is set according to the selected theme, which is used to set colors as css variables ++
* each theme *generally* has a dark theme (even numbers) and a light theme (odd numbers), showing in pairs
* the first theme (theme 0 and 1) is `html.a`, second theme (2 and 3) is `html.b`
* if a light theme is selected, `html.y` is set, otherwise `html.z` is
* so if the dark edition of the 2nd theme is selected, you use any of `html.b`, `html.z`, `html.bz` to specify rules
see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where the color variables are set, and there's layout-specific stuff near the bottom
## complete examples ## complete examples
* read-only music server with bpm and key scanning * read-only music server with bpm and key scanning
@@ -1096,10 +1124,13 @@ enable music tags:
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users) * or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
enable [thumbnails](#thumbnails) of... enable [thumbnails](#thumbnails) of...
* **images:** `Pillow` (requires py2.7 or py3.5+) * **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
* **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH` * **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler) * **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
* **AVIF pictures:** `pillow-avif-plugin` * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin`
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
## install recommended deps ## install recommended deps
@@ -1173,7 +1204,7 @@ python3 -m venv .venv
pip install jinja2 # mandatory pip install jinja2 # mandatory
pip install mutagen # audio metadata pip install mutagen # audio metadata
pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails
pip install black bandit pylint flake8 # vscode tooling pip install black==21.12b0 bandit pylint flake8 # vscode tooling
``` ```

View File

@@ -14,7 +14,6 @@ save one of these as `.epilogue.html` inside a folder to customize it:
## example browser-css ## example browser-css
point `--css-browser` to one of these by URL: point `--css-browser` to one of these by URL:
* [`browser.css`](browser.css) changes the background
* [`browser-icons.css`](browser-icons.css) adds filetype icons * [`browser-icons.css`](browser-icons.css) adds filetype icons

View File

@@ -1,30 +0,0 @@
html {
background: #222 url('/wp/wallhaven-mdjrqy.jpg') center / cover no-repeat fixed;
}
#files th {
background: rgba(32, 32, 32, 0.9) !important;
}
#ops,
#tree,
#files td {
background: rgba(32, 32, 32, 0.3) !important;
}
html.light {
background: #eee url('/wp/wallhaven-dpxl6l.png') center / cover no-repeat fixed;
}
html.light #files th {
background: rgba(255, 255, 255, 0.9) !important;
}
html.light .logue,
html.light #ops,
html.light #tree,
html.light #files td {
background: rgba(248, 248, 248, 0.8) !important;
}
#files * {
background: transparent !important;
}

View File

@@ -12,6 +12,8 @@
# change '/mnt::rw' to another location or permission-set # change '/mnt::rw' to another location or permission-set
# remove '-p 80,443,3923' to only listen on port 3923 # remove '-p 80,443,3923' to only listen on port 3923
# add '-i 127.0.0.1' to only allow local connections # add '-i 127.0.0.1' to only allow local connections
# add '-e2dsa' to enable filesystem scanning + indexing
# add '-e2ts' to enable metadata indexing
# #
# with `Type=notify`, copyparty will signal systemd when it is ready to # with `Type=notify`, copyparty will signal systemd when it is ready to
# accept connections; correctly delaying units depending on copyparty. # accept connections; correctly delaying units depending on copyparty.
@@ -22,6 +24,8 @@
# if you remove -q to enable logging, you may also want to remove the # if you remove -q to enable logging, you may also want to remove the
# following line to enable buffering (slightly better performance): # following line to enable buffering (slightly better performance):
# Environment=PYTHONUNBUFFERED=x # Environment=PYTHONUNBUFFERED=x
#
# keep ExecStartPre before ExecStart, at least on rhel8
[Unit] [Unit]
Description=copyparty file server Description=copyparty file server
@@ -32,7 +36,7 @@ SyslogIdentifier=copyparty
Environment=PYTHONUNBUFFERED=x Environment=PYTHONUNBUFFERED=x
ExecReload=/bin/kill -s USR1 $MAINPID ExecReload=/bin/kill -s USR1 $MAINPID
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -v /mnt::rw ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -e2d -v /mnt::rw
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -291,9 +291,9 @@ def run_argparse(argv, formatter):
dedent( dedent(
""" """
-a takes username:password, -a takes username:password,
-v takes src:dst:perm1:perm2:permN:volflag1:volflag2:volflagN:... -v takes src:dst:\033[33mperm\033[0m1:\033[33mperm\033[0m2:\033[33mperm\033[0mN:\033[32mvolflag\033[0m1:\033[32mvolflag\033[0m2:\033[32mvolflag\033[0mN:...
where "perm" is "permissions,username1,username2,..." * "\033[33mperm\033[0m" is "permissions,username1,username2,..."
and "volflag" is config flags to set on this volume * "\033[32mvolflag\033[0m" is config flags to set on this volume
list of permissions: list of permissions:
"r" (read): list folder contents, download files "r" (read): list folder contents, download files
@@ -365,6 +365,17 @@ def run_argparse(argv, formatter):
generate ".bpm" tags from uploads (f = overwrite tags) generate ".bpm" tags from uploads (f = overwrite tags)
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once \033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
\033[0mthumbnails:
\033[36mdthumb\033[35m disables all thumbnails
\033[36mdvthumb\033[35m disables video thumbnails
\033[36mdathumb\033[35m disables audio thumbnails (spectrograms)
\033[36mdithumb\033[35m disables image thumbnails
\033[0mclient and ux:
\033[36mhtml_head=TXT\033[35m includes TXT in the <head>
\033[36mrobots\033[35m allows indexing by search engines (default)
\033[36mnorobots\033[35m kindly asks search engines to leave
\033[0mothers: \033[0mothers:
\033[36mfk=8\033[35m generates per-file accesskeys, \033[36mfk=8\033[35m generates per-file accesskeys,
which will then be required at the "g" permission which will then be required at the "g" permission
@@ -373,7 +384,7 @@ def run_argparse(argv, formatter):
], ],
[ [
"urlform", "urlform",
"", "how to handle url-form POSTs",
dedent( dedent(
""" """
values for --urlform: values for --urlform:
@@ -412,38 +423,41 @@ def run_argparse(argv, formatter):
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file") ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
ap2.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients") ap2.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all") ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, USER:PASS; example [ed:wark") ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, USER:PASS; example [ed:wark]")
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed") ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, SRC:DST:FLAG; examples [.::r], [/mnt/nas/music:/music:r:aed]")
ap2.add_argument("-ed", action="store_true", help="enable ?dots") ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files")
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins") ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate") ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]") ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see --help-urlform")
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example '$ip-10.1.2.' or '$ip-'") ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example '$ip-10.1.2.' or '$ip-'")
ap2 = ap.add_argument_group('upload options') ap2 = ap.add_argument_group('upload options')
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads") ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless -ed")
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)") 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("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled") ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled")
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload") ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload")
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even if copyparty thinks you're better off without") ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even if copyparty thinks you're better off without -- probably useful on nfs and cow filesystems (zfs, btrfs)")
ap2.add_argument("--no-symlink", action="store_true", help="duplicate file contents instead") 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("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d") ap2.add_argument("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d")
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 = ap.add_argument_group('network options') 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.)") ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)") ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy") ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes") ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="socket write delay in seconds") ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="response delay in seconds") ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
ap2 = ap.add_argument_group('SSL/TLS options') ap2 = ap.add_argument_group('SSL/TLS options')
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls") ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
ap2.add_argument("--https-only", action="store_true", help="disable plaintext") ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
ap2.add_argument("--ssl-ver", metavar="LIST", type=u, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe") ap2.add_argument("--ssl-ver", metavar="LIST", type=u, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
ap2.add_argument("--ciphers", metavar="LIST", type=u, help="set allowed ssl/tls ciphers; [help] shows available ciphers") ap2.add_argument("--ciphers", metavar="LIST", type=u, help="set allowed ssl/tls ciphers; [help] shows available ciphers")
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info") ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets") ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets for later decryption in wireshark")
ap2 = ap.add_argument_group('FTP options') ap2 = ap.add_argument_group('FTP options')
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example 3921") ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example 3921")
@@ -453,25 +467,25 @@ def run_argparse(argv, formatter):
ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example 12000-13000") ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example 12000-13000")
ap2 = ap.add_argument_group('opt-outs') ap2 = ap.add_argument_group('opt-outs')
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)") ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows") ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection which will deadlock copyparty)")
ap2.add_argument("--no-del", action="store_true", help="disable delete operations") ap2.add_argument("--no-del", action="store_true", help="disable delete operations")
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations") ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
ap2.add_argument("-nih", action="store_true", help="no info hostname") ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
ap2.add_argument("-nid", action="store_true", help="no info disk-usage") ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar") ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (lifetime volflag)") ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (lifetime volflag)")
ap2 = ap.add_argument_group('safety options') ap2 = ap.add_argument_group('safety options')
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]") ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt") ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt; used to generate unpredictable internal identifiers for uploads -- doesn't really matter")
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt") ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files -- this one DOES matter")
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles") ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile") ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings") ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings") ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme.md into directory listings")
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)") ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
ap2.add_argument("--force-js", action="store_true", help="don't send HTML folder listings, force clients to use the embedded json instead") ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore --no-robots")
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything") ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything")
ap2 = ap.add_argument_group('yolo options') ap2 = ap.add_argument_group('yolo options')
@@ -482,8 +496,8 @@ def run_argparse(argv, formatter):
ap2.add_argument("-q", action="store_true", help="quiet") ap2.add_argument("-q", action="store_true", help="quiet")
ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz") ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup") ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs") ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
ap2.add_argument("--log-htp", action="store_true", help="print http-server threadpool scaling") ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header") ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching") ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
@@ -500,45 +514,56 @@ def run_argparse(argv, formatter):
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails") ap2.add_argument("--th-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for generating thumbnails")
ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds") ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds")
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image") ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output") ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output") ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs") ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs")
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs") ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs")
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown") ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than SEC seconds")
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled") ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age") ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds")
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for") ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for")
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# https://github.com/libvips/libvips
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="bmp,dib,gif,icns,ico,jpg,jpeg,jp2,jpx,pcx,png,pbm,pgm,ppm,pnm,sgi,tga,tif,tiff,webp,xbm,dds,xpm,heif,heifs,heic,heics,avif,avifs", help="image formats to decode using pillow")
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="jpg,jpeg,jp2,jpx,jxl,tif,tiff,png,webp,heic,avif,fit,fits,fts,exr,svg,hdr,ppm,pgm,pfm,gif,nii", help="image formats to decode using pyvips")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="av1,asf,avi,flv,m4v,mkv,mjpeg,mjpg,mpg,mpeg,mpg2,mpeg2,h264,avc,mts,h265,hevc,mov,3gp,mp4,ts,mpegts,nut,ogv,ogm,rm,vob,webm,wmv", help="video formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,m4a,ogg,opus,flac,alac,mp3,mp2,ac3,dts,wma,ra,wav,aif,aiff,au,alaw,ulaw,mulaw,amr,gsm,ape,tak,tta,wv,mpc", help="audio formats to decode using ffmpeg")
ap2 = ap.add_argument_group('transcoding options') ap2 = ap.add_argument_group('transcoding options')
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding") ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete transcode output after SEC seconds") ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after SEC seconds")
ap2 = ap.add_argument_group('general db options') ap2 = ap.add_argument_group('general db options')
ap2.add_argument("-e2d", action="store_true", help="enable up2k database") ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplocation")
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d") 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="scan all folders (for search), sets -e2ds") ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets -e2ds")
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)") 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-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("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans")
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("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval, 0=off, can be set per-volume with the 'scan' volflag")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline") ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline -- terminate searches running for more than SEC seconds")
ap2.add_argument("--srch-hits", metavar="N", type=int, default=1000, help="max search results") 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")
ap2 = ap.add_argument_group('metadata db options') ap2 = ap.add_argument_group('metadata db options')
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing") ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...")
ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t") ap2.add_argument("-e2ts", action="store_true", help="scan existing files on startup; sets -e2t")
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts") ap2.add_argument("-e2tsr", action="store_true", help="delete all metadata from DB and do a full rescan; sets -e2ts")
ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead") ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead; will catch more tags")
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader") ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader; is probably safer")
ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for tag scanning") ap2.add_argument("--mtag-mt", metavar="CORES", type=int, default=cores, help="num cpu cores to use for tag scanning")
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping") ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)", ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash") default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)", ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
default=".vq,.aq,vc,ac,res,.fps") default=".vq,.aq,vc,ac,res,.fps")
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin") ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
ap2 = ap.add_argument_group('ui options') ap2 = ap.add_argument_group('ui options')
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
ap2.add_argument("--themes", metavar="NUM", type=int, default=6, help="number of themes installed")
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include") ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include") ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages") ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
@@ -546,9 +571,9 @@ def run_argparse(argv, formatter):
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents") ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty", help="title / service-name to show in html documents")
ap2 = ap.add_argument_group('debug options') ap2 = ap.add_argument_group('debug options')
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile") ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile; instead using a traditional file read loop")
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir") ap2.add_argument("--no-scandir", action="store_true", help="disable scandir; instead using listdir + stat on each file")
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing") ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing before starting the httpd")
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead") ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second") ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC") ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (1, 2, 2) VERSION = (1, 2, 8)
CODENAME = "ftp btw" CODENAME = "ftp btw"
BUILD_DT = (2022, 3, 20) BUILD_DT = (2022, 4, 30)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -11,12 +11,13 @@ import hashlib
import threading import threading
from datetime import datetime from datetime import datetime
from .__init__ import WINDOWS from .__init__ import ANYWIN, WINDOWS
from .util import ( from .util import (
IMPLICATIONS, IMPLICATIONS,
META_NOBOTS, META_NOBOTS,
uncyg, uncyg,
undot, undot,
relchk,
unhumanize, unhumanize,
absreal, absreal,
Pebkac, Pebkac,
@@ -335,6 +336,12 @@ class VFS(object):
): ):
# type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str] # type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str]
"""returns [vfsnode,fs_remainder] if user has the requested permissions""" """returns [vfsnode,fs_remainder] if user has the requested permissions"""
if ANYWIN:
mod = relchk(vpath)
if mod:
self.log("vfs", "invalid relpath [{}]".format(vpath))
raise Pebkac(404)
vn, rem = self._find(vpath) vn, rem = self._find(vpath)
c = vn.axs c = vn.axs
@@ -741,10 +748,10 @@ class AuthSrv(object):
unames = ["*"] + list(acct.keys()) unames = ["*"] + list(acct.keys())
umap = {x: [] for x in unames} umap = {x: [] for x in unames}
for usr in unames: for usr in unames:
for mp, vol in vfs.all_vols.items(): for vp, vol in vfs.all_vols.items():
axs = getattr(vol.axs, axs_key) axs = getattr(vol.axs, axs_key)
if usr in axs or "*" in axs: if usr in axs or "*" in axs:
umap[usr].append(mp) umap[usr].append(vp)
umap[usr].sort() umap[usr].sort()
setattr(vfs, "a" + perm, umap) setattr(vfs, "a" + perm, umap)
@@ -875,6 +882,17 @@ class AuthSrv(object):
vol.flags["html_head"] = "\n".join([x for x in h if x]) vol.flags["html_head"] = "\n".join([x for x in h if x])
for vol in vfs.all_vols.values():
if self.args.no_vthumb:
vol.flags["dvthumb"] = True
if self.args.no_athumb:
vol.flags["dathumb"] = True
if self.args.no_thumb or vol.flags.get("dthumb", False):
vol.flags["dthumb"] = True
vol.flags["dvthumb"] = True
vol.flags["dathumb"] = True
vol.flags["dithumb"] = True
for vol in vfs.all_vols.values(): for vol in vfs.all_vols.values():
fk = vol.flags.get("fk") fk = vol.flags.get("fk")
if fk: if fk:

View File

@@ -121,6 +121,12 @@ class HttpCli(object):
try: try:
self.mode, self.req, self.http_ver = headerlines[0].split(" ") self.mode, self.req, self.http_ver = headerlines[0].split(" ")
# normalize incoming headers to lowercase;
# outgoing headers however are Correct-Case
for header_line in headerlines[1:]:
k, v = header_line.split(":", 1)
self.headers[k.lower()] = v.strip()
except: except:
msg = " ]\n#[ ".join(headerlines) msg = " ]\n#[ ".join(headerlines)
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]") raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
@@ -137,12 +143,6 @@ class HttpCli(object):
if self.args.rsp_slp: if self.args.rsp_slp:
time.sleep(self.args.rsp_slp) time.sleep(self.args.rsp_slp)
# normalize incoming headers to lowercase;
# outgoing headers however are Correct-Case
for header_line in headerlines[1:]:
k, v = header_line.split(":", 1)
self.headers[k.lower()] = v.strip()
v = self.headers.get("connection", "").lower() v = self.headers.get("connection", "").lower()
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0" self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
self.is_https = (self.headers.get("x-forwarded-proto", "").lower() == "https" or self.tls) self.is_https = (self.headers.get("x-forwarded-proto", "").lower() == "https" or self.tls)
@@ -211,6 +211,14 @@ class HttpCli(object):
self.cookies = cookies self.cookies = cookies
self.vpath = unquotep(vpath) # not query, so + means + self.vpath = unquotep(vpath) # not query, so + means +
ok = "\x00" not in self.vpath
if ANYWIN:
ok = ok and not relchk(self.vpath)
if not ok:
self.log("invalid relpath [{}]".format(self.vpath))
return self.tx_404() and self.keepalive
pwd = None pwd = None
ba = self.headers.get("authorization") ba = self.headers.get("authorization")
if ba: if ba:
@@ -344,8 +352,11 @@ class HttpCli(object):
return body return body
def loud_reply(self, body, *args, **kwargs): def loud_reply(self, body, *args, **kwargs):
if not kwargs.get("mime"):
kwargs["mime"] = "text/plain; charset=utf-8"
self.log(body.rstrip()) self.log(body.rstrip())
self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs) self.reply(body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
def urlq(self, add, rm): def urlq(self, add, rm):
""" """
@@ -864,8 +875,9 @@ class HttpCli(object):
else: else:
# search by query params # search by query params
q = body["q"] q = body["q"]
self.log("qj: " + q) n = body.get("n", self.args.srch_hits)
hits, taglist = idx.search(vols, q) self.log("qj: {} |{}|".format(q, n))
hits, taglist = idx.search(vols, q, n)
msg = len(hits) msg = len(hits)
idx.p_end = time.time() idx.p_end = time.time()
@@ -1046,6 +1058,7 @@ class HttpCli(object):
raise Pebkac(500, min_ex()) raise Pebkac(500, min_ex())
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/") vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
self.out_headers["X-New-Dir"] = quotep(sanitized)
self.redirect(vpath) self.redirect(vpath)
return True return True
@@ -2097,9 +2110,7 @@ class HttpCli(object):
thp = None thp = None
if self.thumbcli: if self.thumbcli:
thp = self.thumbcli.get( thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
dbv.realpath, vrem, int(st.st_mtime), th_fmt
)
if thp: if thp:
return self.tx_file(thp) return self.tx_file(thp)
@@ -2149,12 +2160,11 @@ class HttpCli(object):
free = humansize(sv.f_frsize * sv.f_bfree, True) free = humansize(sv.f_frsize * sv.f_bfree, True)
total = humansize(sv.f_frsize * sv.f_blocks, True) total = humansize(sv.f_frsize * sv.f_blocks, True)
srv_info.append(free + " free") srv_info.append("{} free of {}".format(free, total))
srv_info.append(total)
except: except:
pass pass
srv_info = "</span> /// <span>".join(srv_info) srv_info = "</span> // <span>".join(srv_info)
perms = [] perms = []
if self.can_read: if self.can_read:
@@ -2175,6 +2185,7 @@ class HttpCli(object):
tpl = "browser" tpl = "browser"
if "b" in self.uparam: if "b" in self.uparam:
tpl = "browser2" tpl = "browser2"
is_js = False
logues = ["", ""] logues = ["", ""]
if not self.args.no_logues: if not self.args.no_logues:
@@ -2226,6 +2237,9 @@ class HttpCli(object):
"readme": readme, "readme": readme,
"title": html_escape(self.vpath, crlf=True), "title": html_escape(self.vpath, crlf=True),
"srv_info": srv_info, "srv_info": srv_info,
"dtheme": self.args.theme,
"themes": self.args.themes,
"turbolvl": self.args.turbo,
} }
if not self.can_read: if not self.can_read:
if is_ls: if is_ls:

View File

@@ -17,7 +17,7 @@ from .util import Unrecv
from .httpcli import HttpCli from .httpcli import HttpCli
from .u2idx import U2idx from .u2idx import U2idx
from .th_cli import ThumbCli from .th_cli import ThumbCli
from .th_srv import HAVE_PIL from .th_srv import HAVE_PIL, HAVE_VIPS
from .ico import Ico from .ico import Ico
@@ -38,7 +38,7 @@ class HttpConn(object):
self.cert_path = hsrv.cert_path self.cert_path = hsrv.cert_path
self.u2fh = hsrv.u2fh self.u2fh = hsrv.u2fh
enth = HAVE_PIL and not self.args.no_thumb enth = (HAVE_PIL or HAVE_VIPS) and not self.args.no_thumb
self.thumbcli = ThumbCli(hsrv) if enth else None self.thumbcli = ThumbCli(hsrv) if enth else None
self.ico = Ico(self.args) self.ico = Ico(self.args)

View File

@@ -70,6 +70,12 @@ class HttpSrv(object):
self.cb_ts = 0 self.cb_ts = 0
self.cb_v = 0 self.cb_v = 0
try:
x = self.broker.put(True, "thumbsrv.getcfg")
self.th_cfg = x.get()
except:
pass
env = jinja2.Environment() env = jinja2.Environment()
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web")) env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
self.j2 = { self.j2 = {

View File

@@ -477,13 +477,13 @@ class MTag(object):
env["PYTHONPATH"] = pypath env["PYTHONPATH"] = pypath
ret = {} ret = {}
for tagname, mp in parsers.items(): for tagname, parser in parsers.items():
try: try:
cmd = [mp.bin, abspath] cmd = [parser.bin, abspath]
if mp.bin.endswith(".py"): if parser.bin.endswith(".py"):
cmd = [sys.executable] + cmd cmd = [sys.executable] + cmd
args = {"env": env, "timeout": mp.timeout} args = {"env": env, "timeout": parser.timeout}
if WINDOWS: if WINDOWS:
args["creationflags"] = 0x4000 args["creationflags"] = 0x4000

View File

@@ -5,7 +5,7 @@ import tarfile
import threading import threading
from .sutil import errdesc from .sutil import errdesc
from .util import Queue, fsenc from .util import Queue, fsenc, min_ex
from .bos import bos from .bos import bos
@@ -88,8 +88,9 @@ class StreamTar(object):
try: try:
self.ser(f) self.ser(f)
except Exception as ex: except Exception:
errors.append([f["vp"], repr(ex)]) ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if errors: if errors:
self.errf, txt = errdesc(errors) self.errf, txt = errdesc(errors)

View File

@@ -17,7 +17,7 @@ from .util import mp, start_log_thrs, start_stackmon, min_ex, ansi_re
from .authsrv import AuthSrv from .authsrv import AuthSrv
from .tcpsrv import TcpSrv from .tcpsrv import TcpSrv
from .up2k import Up2k from .up2k import Up2k
from .th_srv import ThumbSrv, HAVE_PIL, HAVE_WEBP from .th_srv import ThumbSrv, HAVE_PIL, HAVE_VIPS, HAVE_WEBP
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
@@ -70,6 +70,13 @@ class SvcHub(object):
self.log("root", m, c=3) self.log("root", m, c=3)
bri = "zy"[args.theme % 2 :][:1]
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
args.theme = "{0}{1} {0} {1}".format(ch, bri)
if not args.hardlink and args.never_symlink:
args.no_dedup = True
# initiate all services to manage # initiate all services to manage
self.asrv = AuthSrv(self.args, self.log) self.asrv = AuthSrv(self.args, self.log)
if args.ls: if args.ls:
@@ -78,20 +85,30 @@ class SvcHub(object):
self.tcpsrv = TcpSrv(self) self.tcpsrv = TcpSrv(self)
self.up2k = Up2k(self) self.up2k = Up2k(self)
decs = {k: 1 for k in self.args.th_dec.split(",")}
if not HAVE_VIPS:
decs.pop("vips", None)
if not HAVE_PIL:
decs.pop("pil", None)
if not HAVE_FFMPEG or not HAVE_FFPROBE:
decs.pop("ff", None)
self.args.th_dec = list(decs.keys())
self.thumbsrv = None self.thumbsrv = None
if not args.no_thumb: if not args.no_thumb:
if HAVE_PIL: m = "decoder preference: {}".format(", ".join(self.args.th_dec))
if not HAVE_WEBP: self.log("thumb", m)
args.th_no_webp = True
msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
self.log("thumb", msg, c=3)
if "pil" in self.args.th_dec and not HAVE_WEBP:
msg = "disabling webp thumbnails because either libwebp is not available or your Pillow is too old"
self.log("thumb", msg, c=3)
if self.args.th_dec:
self.thumbsrv = ThumbSrv(self) self.thumbsrv = ThumbSrv(self)
else: else:
msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n" msg = "need either Pillow, pyvips, or FFmpeg to create thumbnails; for example:\n{0}{1} -m pip install --user Pillow\n{0}{1} -m pip install --user pyvips\n{0}apt install ffmpeg"
self.log( msg = msg.format(" " * 37, os.path.basename(sys.executable))
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3 self.log("thumb", msg, c=3)
)
if not args.no_acode and args.no_thumb: if not args.no_acode and args.no_thumb:
msg = "setting --no-acode because --no-thumb (sorry)" msg = "setting --no-acode because --no-thumb (sorry)"

View File

@@ -1,13 +1,12 @@
# coding: utf-8 # coding: utf-8
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os
import time import time
import zlib import zlib
from datetime import datetime from datetime import datetime
from .sutil import errdesc from .sutil import errdesc
from .util import yieldfile, sanitize_fn, spack, sunpack from .util import yieldfile, sanitize_fn, spack, sunpack, min_ex
from .bos import bos from .bos import bos
@@ -36,7 +35,10 @@ def unixtime2dos(ts):
bd = ((dy - 1980) << 9) + (dm << 5) + dd bd = ((dy - 1980) << 9) + (dm << 5) + dd
bt = (th << 11) + (tm << 5) + ts // 2 bt = (th << 11) + (tm << 5) + ts // 2
return spack(b"<HH", bt, bd) try:
return spack(b"<HH", bt, bd)
except:
return b"\x00\x00\x21\x00"
def gen_fdesc(sz, crc32, z64): def gen_fdesc(sz, crc32, z64):
@@ -244,8 +246,9 @@ class StreamZip(object):
try: try:
for x in self.ser(f): for x in self.ser(f):
yield x yield x
except Exception as ex: except Exception:
errors.append([f["vp"], repr(ex)]) ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append([f["vp"], ex])
if errors: if errors:
errf, txt = errdesc(errors) errf, txt = errdesc(errors)

View File

@@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
import os import os
from .util import Cooldown from .util import Cooldown
from .th_srv import thumb_path, THUMBABLE, FMT_FFV, FMT_FFA from .th_srv import thumb_path, HAVE_WEBP
from .bos import bos from .bos import bos
@@ -18,30 +18,53 @@ class ThumbCli(object):
# cache on both sides for less broker spam # cache on both sides for less broker spam
self.cooldown = Cooldown(self.args.th_poke) self.cooldown = Cooldown(self.args.th_poke)
try:
c = hsrv.th_cfg
except:
c = {k: {} for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]}
self.thumbable = c["thumbable"]
self.fmt_pil = c["pil"]
self.fmt_vips = c["vips"]
self.fmt_ffi = c["ffi"]
self.fmt_ffv = c["ffv"]
self.fmt_ffa = c["ffa"]
# defer args.th_ff_jpg, can change at runtime
d = next((x for x in self.args.th_dec if x in ("vips", "pil")), None)
self.can_webp = HAVE_WEBP or d == "vips"
def log(self, msg, c=0): def log(self, msg, c=0):
self.log_func("thumbcli", msg, c) self.log_func("thumbcli", msg, c)
def get(self, ptop, rem, mtime, fmt): def get(self, dbv, rem, mtime, fmt):
ptop = dbv.realpath
ext = rem.rsplit(".")[-1].lower() ext = rem.rsplit(".")[-1].lower()
if ext not in THUMBABLE: if ext not in self.thumbable or "dthumb" in dbv.flags:
return None return None
is_vid = ext in FMT_FFV is_vid = ext in self.fmt_ffv
if is_vid and self.args.no_vthumb: if is_vid and "dvthumb" in dbv.flags:
return None return None
want_opus = fmt in ("opus", "caf") want_opus = fmt in ("opus", "caf")
is_au = ext in FMT_FFA is_au = ext in self.fmt_ffa
if is_au: if is_au:
if want_opus: if want_opus:
if self.args.no_acode: if self.args.no_acode:
return None return None
else: else:
if self.args.no_athumb: if "dathumb" in dbv.flags:
return None return None
elif want_opus: elif want_opus:
return None return None
is_img = not is_vid and not is_au
if is_img and "dithumb" in dbv.flags:
return None
preferred = self.args.th_dec[0] if self.args.th_dec else ""
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]: if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
return os.path.join(ptop, rem) return os.path.join(ptop, rem)
@@ -49,7 +72,11 @@ class ThumbCli(object):
fmt = "w" fmt = "w"
if fmt == "w": if fmt == "w":
if self.args.th_no_webp or ((is_vid or is_au) and self.args.th_ff_jpg): if (
self.args.th_no_webp
or (is_img and not self.can_webp)
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
):
fmt = "j" fmt = "j"
histpath = self.asrv.vfs.histtab.get(ptop) histpath = self.asrv.vfs.histtab.get(ptop)
@@ -58,15 +85,23 @@ class ThumbCli(object):
return None return None
tpath = thumb_path(histpath, rem, mtime, fmt) tpath = thumb_path(histpath, rem, mtime, fmt)
tpaths = [tpath]
if fmt == "w":
# also check for jpg (maybe webp is unavailable)
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
ret = None ret = None
try: abort = False
st = bos.stat(tpath) for tp in tpaths:
if st.st_size: try:
ret = tpath st = bos.stat(tp)
else: if st.st_size:
return None ret = tpath = tp
except: fmt = ret.rsplit(".")[1]
pass else:
abort = True
except:
pass
if ret: if ret:
tdir = os.path.dirname(tpath) tdir = os.path.dirname(tpath)
@@ -80,5 +115,8 @@ class ThumbCli(object):
return ret return ret
if abort:
return None
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt) x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
return x.get() return x.get()

View File

@@ -47,31 +47,12 @@ try:
except: except:
pass pass
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html try:
# ffmpeg -formats import pyvips
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
FMT_FFV = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
FMT_FFA = "aac m4a ogg opus flac alac mp3 mp2 ac3 dts wma ra wav aif aiff au alaw ulaw mulaw amr gsm ape tak tta wv"
if HAVE_HEIF: HAVE_VIPS = True
FMT_PIL += " heif heifs heic heics" except:
HAVE_VIPS = False
if HAVE_AVIF:
FMT_PIL += " avif avifs"
FMT_PIL, FMT_FFV, FMT_FFA = [
{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FFV, FMT_FFA]
]
THUMBABLE = {}
if HAVE_PIL:
THUMBABLE.update(FMT_PIL)
if HAVE_FFMPEG and HAVE_FFPROBE:
THUMBABLE.update(FMT_FFV)
THUMBABLE.update(FMT_FFA)
def thumb_path(histpath, rem, mtime, fmt): def thumb_path(histpath, rem, mtime, fmt):
@@ -141,6 +122,37 @@ class ThumbSrv(object):
t.daemon = True t.daemon = True
t.start() t.start()
self.fmt_pil, self.fmt_vips, self.fmt_ffi, self.fmt_ffv, self.fmt_ffa = [
{x: True for x in y.split(",")}
for y in [
self.args.th_r_pil,
self.args.th_r_vips,
self.args.th_r_ffi,
self.args.th_r_ffv,
self.args.th_r_ffa,
]
]
if not HAVE_HEIF:
for f in "heif heifs heic heics".split(" "):
self.fmt_pil.pop(f, None)
if not HAVE_AVIF:
for f in "avif avifs".split(" "):
self.fmt_pil.pop(f, None)
self.thumbable = {}
if "pil" in self.args.th_dec:
self.thumbable.update(self.fmt_pil)
if "vips" in self.args.th_dec:
self.thumbable.update(self.fmt_vips)
if "ff" in self.args.th_dec:
for t in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
self.thumbable.update(t)
def log(self, msg, c=0): def log(self, msg, c=0):
self.log_func("thumb", msg, c) self.log_func("thumb", msg, c)
@@ -201,6 +213,16 @@ class ThumbSrv(object):
return None return None
def getcfg(self):
return {
"thumbable": self.thumbable,
"pil": self.fmt_pil,
"vips": self.fmt_vips,
"ffi": self.fmt_ffi,
"ffv": self.fmt_ffv,
"ffa": self.fmt_ffa,
}
def worker(self): def worker(self):
while not self.stopping: while not self.stopping:
task = self.q.get() task = self.q.get()
@@ -211,15 +233,20 @@ class ThumbSrv(object):
ext = abspath.split(".")[-1].lower() ext = abspath.split(".")[-1].lower()
fun = None fun = None
if not bos.path.exists(tpath): if not bos.path.exists(tpath):
if ext in FMT_PIL: for lib in self.args.th_dec:
fun = self.conv_pil if fun:
elif ext in FMT_FFV: break
fun = self.conv_ffmpeg elif lib == "pil" and ext in self.fmt_pil:
elif ext in FMT_FFA: fun = self.conv_pil
if tpath.endswith(".opus") or tpath.endswith(".caf"): elif lib == "vips" and ext in self.fmt_vips:
fun = self.conv_opus fun = self.conv_vips
else: elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
fun = self.conv_spec fun = self.conv_ffmpeg
elif lib == "ff" and ext in self.fmt_ffa:
if tpath.endswith(".opus") or tpath.endswith(".caf"):
fun = self.conv_opus
else:
fun = self.conv_spec
if fun: if fun:
try: try:
@@ -296,11 +323,29 @@ class ThumbSrv(object):
im.save(tpath, **args) im.save(tpath, **args)
def conv_vips(self, abspath, tpath):
crops = ["centre", "none"]
if self.args.th_no_crop:
crops = ["none"]
w, h = self.res
kw = {"height": h, "size": "down", "intent": "relative"}
for c in crops:
try:
kw["crop"] = c
img = pyvips.Image.thumbnail(abspath, w, **kw)
break
except:
pass
img.write_to_file(tpath, Q=40)
def conv_ffmpeg(self, abspath, tpath): def conv_ffmpeg(self, abspath, tpath):
ret, _ = ffprobe(abspath) ret, _ = ffprobe(abspath)
ext = abspath.rsplit(".")[-1] ext = abspath.rsplit(".")[-1].lower()
if ext in ["h264", "h265"]: if ext in ["h264", "h265"] or ext in self.fmt_ffi:
seek = [] seek = []
else: else:
dur = ret[".dur"][1] if ".dur" in ret else 4 dur = ret[".dur"][1] if ".dur" in ret else 4
@@ -350,11 +395,31 @@ class ThumbSrv(object):
def _run_ff(self, cmd): def _run_ff(self, cmd):
# self.log((b" ".join(cmd)).decode("utf-8")) # self.log((b" ".join(cmd)).decode("utf-8"))
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt) ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
if ret != 0: if not ret:
m = "FFmpeg failed (probably a corrupt video file):\n" return
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
self.log(m, c="1;30") c = "1;30"
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1])) m = "FFmpeg failed (probably a corrupt video file):\n"
if cmd[-1].lower().endswith(b".webp") and (
"Error selecting an encoder" in serr
or "Automatic encoder selection failed" in serr
or "Default encoder for format webp" in serr
or "Please choose an encoder manually" in serr
):
self.args.th_ff_jpg = True
m = "FFmpeg failed because it was compiled without libwebp; enabling --th-ff-jpg to force jpeg output:\n"
c = 1
if (
"Requested resampling engine is unavailable" in serr
or "output pad on Parsed_aresample_" in serr
):
m = "FFmpeg failed because it was compiled without libsox; you must set --th-ff-swr to force swr resampling:\n"
c = 1
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
self.log(m, c=c)
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
def conv_spec(self, abspath, tpath): def conv_spec(self, abspath, tpath):
ret, _ = ffprobe(abspath) ret, _ = ffprobe(abspath)

View File

@@ -21,6 +21,12 @@ except:
HAVE_SQLITE3 = False HAVE_SQLITE3 = False
try:
from pathlib import Path
except:
pass
class U2idx(object): class U2idx(object):
def __init__(self, conn): def __init__(self, conn):
self.log_func = conn.log_func self.log_func = conn.log_func
@@ -55,7 +61,7 @@ class U2idx(object):
uv = [wark[:16], wark] uv = [wark[:16], wark]
try: try:
return self.run_query(vols, uq, uv, True, False)[0] return self.run_query(vols, uq, uv, True, False, 99999)[0]
except: except:
raise Pebkac(500, min_ex()) raise Pebkac(500, min_ex())
@@ -76,11 +82,26 @@ class U2idx(object):
if not bos.path.exists(db_path): if not bos.path.exists(db_path):
return None return None
cur = sqlite3.connect(db_path, 2).cursor() cur = None
if ANYWIN:
uri = ""
try:
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
cur = sqlite3.connect(uri, 2, uri=True).cursor()
self.log("ro: {}".format(db_path))
except:
self.log("could not open read-only: {}\n{}".format(uri, min_ex()))
if not cur:
# on windows, this steals the write-lock from up2k.deferred_init --
# seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2
cur = sqlite3.connect(db_path, 2).cursor()
self.log("opened {}".format(db_path))
self.cur[ptop] = cur self.cur[ptop] = cur
return cur return cur
def search(self, vols, uq): def search(self, vols, uq, lim):
"""search by query params""" """search by query params"""
if not HAVE_SQLITE3: if not HAVE_SQLITE3:
return [] return []
@@ -222,11 +243,11 @@ class U2idx(object):
q += " lower({}) {} ? ) ".format(field, oper) q += " lower({}) {} ? ) ".format(field, oper)
try: try:
return self.run_query(vols, q, va, have_up, have_mt) return self.run_query(vols, q, va, have_up, have_mt, lim)
except Exception as ex: except Exception as ex:
raise Pebkac(500, repr(ex)) raise Pebkac(500, repr(ex))
def run_query(self, vols, uq, uv, have_up, have_mt): def run_query(self, vols, uq, uv, have_up, have_mt, lim):
done_flag = [] done_flag = []
self.active_id = "{:.6f}_{}".format( self.active_id = "{:.6f}_{}".format(
time.time(), threading.current_thread().ident time.time(), threading.current_thread().ident
@@ -255,7 +276,7 @@ class U2idx(object):
self.log("qs: {!r} {!r}".format(uq, uv)) self.log("qs: {!r} {!r}".format(uq, uv))
ret = [] ret = []
lim = int(self.args.srch_hits) lim = min(lim, int(self.args.srch_hits))
taglist = {} taglist = {}
for (vtop, ptop, flags) in vols: for (vtop, ptop, flags) in vols:
cur = self.get_cur(ptop) cur = self.get_cur(ptop)
@@ -278,7 +299,7 @@ class U2idx(object):
for hit in c: for hit in c:
w, ts, sz, rd, fn, ip, at = hit[:7] w, ts, sz, rd, fn, ip, at = hit[:7]
lim -= 1 lim -= 1
if lim <= 0: if lim < 0:
break break
if rd.startswith("//") or fn.startswith("//"): if rd.startswith("//") or fn.startswith("//"):

View File

@@ -95,7 +95,7 @@ class Up2k(object):
if ANYWIN: if ANYWIN:
# usually fails to set lastmod too quickly # usually fails to set lastmod too quickly
self.lastmod_q = Queue() self.lastmod_q = []
thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod") thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod")
thr.daemon = True thr.daemon = True
thr.start() thr.start()
@@ -470,9 +470,11 @@ class Up2k(object):
ft = "\033[0;32m{}{:.0}" ft = "\033[0;32m{}{:.0}"
ff = "\033[0;35m{}{:.0}" ff = "\033[0;35m{}{:.0}"
fv = "\033[0;36m{}:\033[1;30m{}" fv = "\033[0;36m{}:\033[1;30m{}"
fx = set(("html_head",))
a = [ a = [
(ft if v is True else ff if v is False else fv).format(k, str(v)) (ft if v is True else ff if v is False else fv).format(k, str(v))
for k, v in flags.items() for k, v in flags.items()
if k not in fx
] ]
if a: if a:
vpath = "?" vpath = "?"
@@ -581,9 +583,11 @@ class Up2k(object):
self.pp.msg = "a{} {}".format(self.pp.n, cdir) self.pp.msg = "a{} {}".format(self.pp.n, cdir)
histpath = self.asrv.vfs.histtab[top] histpath = self.asrv.vfs.histtab[top]
ret = 0 ret = 0
seen_files = {} seen_files = {} # != inames; files-only for dropcheck
g = statdir(self.log_func, not self.args.no_scandir, False, cdir) g = statdir(self.log_func, not self.args.no_scandir, False, cdir)
for iname, inf in sorted(g): g = sorted(g)
inames = {x[0]: 1 for x in g}
for iname, inf in g:
abspath = os.path.join(cdir, iname) abspath = os.path.join(cdir, iname)
if rei and rei.search(abspath): if rei and rei.search(abspath):
continue continue
@@ -594,6 +598,9 @@ class Up2k(object):
if stat.S_ISDIR(inf.st_mode): if stat.S_ISDIR(inf.st_mode):
if abspath in excl or abspath == histpath: if abspath in excl or abspath == histpath:
continue continue
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
# abandoned or foreign, skip
continue
# self.log(" dir: {}".format(abspath)) # self.log(" dir: {}".format(abspath))
try: try:
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen) ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)
@@ -607,6 +614,17 @@ class Up2k(object):
if WINDOWS: if WINDOWS:
rp = rp.replace("\\", "/").strip("/") rp = rp.replace("\\", "/").strip("/")
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
# rescan during upload
continue
if not sz and (
"{}.PARTIAL".format(iname) in inames
or ".{}.PARTIAL".format(iname) in inames
):
# placeholder for unfinished upload
continue
rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp] rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
sql = "select w, mt, sz from up where rd = ? and fn = ?" sql = "select w, mt, sz from up where rd = ? and fn = ?"
try: try:
@@ -776,6 +794,7 @@ class Up2k(object):
if self.mtag.prefer_mt and self.args.mtag_mt > 1: if self.mtag.prefer_mt and self.args.mtag_mt > 1:
mpool = self._start_mpool() mpool = self._start_mpool()
# TODO blocks writes to registry cursor; do chunks instead
conn = sqlite3.connect(db_path, timeout=15) conn = sqlite3.connect(db_path, timeout=15)
cur = conn.cursor() cur = conn.cursor()
c2 = conn.cursor() c2 = conn.cursor()
@@ -801,8 +820,8 @@ class Up2k(object):
n_tags = self._tag_file(c3, *args) n_tags = self._tag_file(c3, *args)
else: else:
mpool.put(["mtag"] + args) mpool.put(["mtag"] + args)
with self.mutex: # not registry cursor; do not self.mutex:
n_tags = len(self._flush_mpool(c3)) n_tags = len(self._flush_mpool(c3))
n_add += n_tags n_add += n_tags
n_buf += n_tags n_buf += n_tags
@@ -825,9 +844,6 @@ class Up2k(object):
cur.close() cur.close()
conn.close() conn.close()
with self.mutex:
gcur.connection.commit()
return n_add, n_rm, True return n_add, n_rm, True
def _flush_mpool(self, wcur): def _flush_mpool(self, wcur):
@@ -1106,7 +1122,8 @@ class Up2k(object):
return ret return ret
def _orz(self, db_path): def _orz(self, db_path):
return sqlite3.connect(db_path, check_same_thread=False).cursor() timeout = int(max(self.args.srch_time, 5) * 1.2)
return sqlite3.connect(db_path, timeout, check_same_thread=False).cursor()
# x.set_trace_callback(trace) # x.set_trace_callback(trace)
def _open_db(self, db_path): def _open_db(self, db_path):
@@ -1235,6 +1252,11 @@ class Up2k(object):
wark = self._get_wark(cj) wark = self._get_wark(cj)
now = time.time() now = time.time()
job = None job = None
try:
dev = bos.stat(os.path.join(cj["ptop"], cj["prel"])).st_dev
except:
dev = 0
with self.mutex: with self.mutex:
cur = self.cur.get(cj["ptop"]) cur = self.cur.get(cj["ptop"])
reg = self.registry[cj["ptop"]] reg = self.registry[cj["ptop"]]
@@ -1246,37 +1268,42 @@ class Up2k(object):
q = r"select * from up where substr(w,1,16) = ? and w = ?" q = r"select * from up where substr(w,1,16) = ? and w = ?"
argv = (wark[:16], wark) argv = (wark[:16], wark)
alts = []
cur = cur.execute(q, argv) cur = cur.execute(q, argv)
for _, dtime, dsize, dp_dir, dp_fn, ip, at in cur: for _, dtime, dsize, dp_dir, dp_fn, ip, at in cur:
if dp_dir.startswith("//") or dp_fn.startswith("//"): if dp_dir.startswith("//") or dp_fn.startswith("//"):
dp_dir, dp_fn = s3dec(dp_dir, dp_fn) dp_dir, dp_fn = s3dec(dp_dir, dp_fn)
if job and (dp_dir != cj["prel"] or dp_fn != cj["name"]): dp_abs = "/".join([cj["ptop"], dp_dir, dp_fn])
try:
st = bos.stat(dp_abs)
if stat.S_ISLNK(st.st_mode):
# broken symlink
raise Exception()
except:
continue continue
dp_abs = "/".join([cj["ptop"], dp_dir, dp_fn]) j = {
# relying on this to fail on broken symlinks "name": dp_fn,
try: "prel": dp_dir,
sz = bos.path.getsize(dp_abs) "vtop": cj["vtop"],
except: "ptop": cj["ptop"],
sz = 0 "size": dsize,
"lmod": dtime,
if sz: "addr": ip,
# self.log("--- " + wark + " " + dp_abs + " found file", 4) "at": at,
job = { "hash": [],
"name": dp_fn, "need": [],
"prel": dp_dir, "busy": {},
"vtop": cj["vtop"], }
"ptop": cj["ptop"], score = (
"size": dsize, (3 if st.st_dev == dev else 0)
"lmod": dtime, + (2 if dp_dir == cj["prel"] else 0)
"addr": ip, + (1 if dp_fn == cj["name"] else 0)
"at": at, )
"hash": [], alts.append([score, -len(alts), j])
"need": [],
"busy": {},
}
job = sorted(alts, reverse=True)[0][2] if alts else None
if job and wark in reg: if job and wark in reg:
# self.log("pop " + wark + " " + job["name"] + " handle_json db", 4) # self.log("pop " + wark + " " + job["name"] + " handle_json db", 4)
del reg[wark] del reg[wark]
@@ -1417,14 +1444,14 @@ class Up2k(object):
linked = False linked = False
try: try:
if self.args.no_symlink: if self.args.no_dedup:
raise Exception("disabled in config") raise Exception("disabled in config")
lsrc = src lsrc = src
ldst = dst ldst = dst
fs1 = bos.stat(os.path.dirname(src)).st_dev fs1 = bos.stat(os.path.dirname(src)).st_dev
fs2 = bos.stat(os.path.dirname(dst)).st_dev fs2 = bos.stat(os.path.dirname(dst)).st_dev
if fs1 == 0: if fs1 == 0 or fs2 == 0:
# py2 on winxp or other unsupported combination # py2 on winxp or other unsupported combination
raise OSError() raise OSError()
elif fs1 == fs2: elif fs1 == fs2:
@@ -1445,16 +1472,27 @@ class Up2k(object):
lsrc = nsrc[nc:] lsrc = nsrc[nc:]
hops = len(ndst[nc:]) - 1 hops = len(ndst[nc:]) - 1
lsrc = "../" * hops + "/".join(lsrc) lsrc = "../" * hops + "/".join(lsrc)
os.symlink(fsenc(lsrc), fsenc(ldst))
linked = True try:
if self.args.hardlink:
os.link(fsenc(src), fsenc(dst))
linked = True
except Exception as ex:
self.log("cannot hardlink: " + repr(ex))
if self.args.never_symlink:
raise Exception("symlink-fallback disabled in cfg")
if not linked:
os.symlink(fsenc(lsrc), fsenc(ldst))
linked = True
except Exception as ex: except Exception as ex:
self.log("cannot symlink; creating copy: " + repr(ex)) self.log("cannot link; creating copy: " + repr(ex))
shutil.copy2(fsenc(src), fsenc(dst)) shutil.copy2(fsenc(src), fsenc(dst))
if lmod and (not linked or SYMTIME): if lmod and (not linked or SYMTIME):
times = (int(time.time()), int(lmod)) times = (int(time.time()), int(lmod))
if ANYWIN: if ANYWIN:
self.lastmod_q.put([dst, 0, times]) self.lastmod_q.append([dst, 0, times])
else: else:
bos.utime(dst, times, False) bos.utime(dst, times, False)
@@ -1548,9 +1586,15 @@ class Up2k(object):
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4) # self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
atomic_move(src, dst) atomic_move(src, dst)
times = (int(time.time()), int(job["lmod"]))
if ANYWIN: if ANYWIN:
a = [dst, job["size"], (int(time.time()), int(job["lmod"]))] a = [dst, job["size"], times]
self.lastmod_q.put(a) self.lastmod_q.append(a)
elif not job["hash"]:
try:
bos.utime(dst, times)
except:
pass
a = [job[x] for x in "ptop wark prel name lmod size addr".split()] a = [job[x] for x in "ptop wark prel name lmod size addr".split()]
a += [job.get("at") or time.time()] a += [job.get("at") or time.time()]
@@ -1649,12 +1693,12 @@ class Up2k(object):
vn, rem = vn.get_dbv(rem) vn, rem = vn.get_dbv(rem)
unpost = False unpost = False
except: except:
# unpost with missing permissions? try read+write and verify with db # unpost with missing permissions? verify with db
if not self.args.unpost: if not self.args.unpost:
raise Pebkac(400, "the unpost feature is disabled in server config") raise Pebkac(400, "the unpost feature is disabled in server config")
unpost = True unpost = True
permsets = [[True, True]] permsets = [[False, True]]
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0]) vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
vn, rem = vn.get_dbv(rem) vn, rem = vn.get_dbv(rem)
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem) _, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
@@ -2038,9 +2082,8 @@ class Up2k(object):
def _lastmodder(self): def _lastmodder(self):
while True: while True:
ready = [] ready = self.lastmod_q
while not self.lastmod_q.empty(): self.lastmod_q = []
ready.append(self.lastmod_q.get())
# self.log("lmod: got {}".format(len(ready))) # self.log("lmod: got {}".format(len(ready)))
time.sleep(5) time.sleep(5)
@@ -2049,7 +2092,8 @@ class Up2k(object):
try: try:
bos.utime(path, times, False) bos.utime(path, times, False)
except: except:
self.log("lmod: failed to utime ({}, {})".format(path, times)) m = "lmod: failed to utime ({}, {}):\n{}"
self.log(m.format(path, times, min_ex()))
if self.args.sparse and self.args.sparse * 1024 * 1024 <= sz: if self.args.sparse and self.args.sparse * 1024 * 1024 <= sz:
try: try:

View File

@@ -67,7 +67,7 @@ if WINDOWS and PY2:
FS_ENCODING = "utf-8" FS_ENCODING = "utf-8"
SYMTIME = sys.version_info >= (3, 6) and os.supports_follow_symlinks SYMTIME = sys.version_info >= (3, 6) and os.utime in os.supports_follow_symlinks
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT" HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
@@ -485,13 +485,13 @@ def vol_san(vols, txt):
return txt return txt
def min_ex(): def min_ex(max_lines=8, reverse=False):
et, ev, tb = sys.exc_info() et, ev, tb = sys.exc_info()
tb = traceback.extract_tb(tb) tb = traceback.extract_tb(tb)
fmt = "{} @ {} <{}>: {}" fmt = "{} @ {} <{}>: {}"
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb] ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
ex.append("[{}] {}".format(et.__name__, ev)) ex.append("[{}] {}".format(et.__name__, ev))
return "\n".join(ex[-8:]) return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
@contextlib.contextmanager @contextlib.contextmanager
@@ -912,6 +912,9 @@ def sanitize_fn(fn, ok, bad):
if "/" not in ok: if "/" not in ok:
fn = fn.replace("\\", "/").split("/")[-1] fn = fn.replace("\\", "/").split("/")[-1]
if fn.lower() in bad:
fn = "_" + fn
if ANYWIN: if ANYWIN:
remap = [ remap = [
["<", ""], ["<", ""],
@@ -927,16 +930,26 @@ def sanitize_fn(fn, ok, bad):
for a, b in [x for x in remap if x[0] not in ok]: for a, b in [x for x in remap if x[0] not in ok]:
fn = fn.replace(a, b) fn = fn.replace(a, b)
bad.extend(["con", "prn", "aux", "nul"]) bad = ["con", "prn", "aux", "nul"]
for n in range(1, 10): for n in range(1, 10):
bad += "com{0} lpt{0}".format(n).split(" ") bad += "com{0} lpt{0}".format(n).split(" ")
if fn.lower() in bad: if fn.lower().split(".")[0] in bad:
fn = "_" + fn fn = "_" + fn
return fn.strip() return fn.strip()
def relchk(rp):
if ANYWIN:
if "\n" in rp or "\r" in rp:
return "x\nx"
p = re.sub(r'[\\:*?"<>|]', "", rp)
if p != rp:
return "[{}]".format(p)
def absreal(fpath): def absreal(fpath):
try: try:
return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath)))) return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath))))
@@ -1235,7 +1248,7 @@ def statdir(logger, scandir, lstat, top):
if lstat and ANYWIN: if lstat and ANYWIN:
lstat = False lstat = False
if lstat and not os.supports_follow_symlinks: if lstat and (PY2 or os.stat not in os.supports_follow_symlinks):
scandir = False scandir = False
try: try:

View File

@@ -17,12 +17,11 @@ window.baguetteBox = (function () {
titleTag: false, titleTag: false,
async: false, async: false,
preload: 2, preload: 2,
animation: 'slideIn',
afterShow: null, afterShow: null,
afterHide: null, afterHide: null,
onChange: null, onChange: null,
}, },
overlay, slider, btnPrev, btnNext, btnHelp, btnRotL, btnRotR, btnSel, btnVmode, btnClose, overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnVmode, btnClose,
currentGallery = [], currentGallery = [],
currentIndex = 0, currentIndex = 0,
isOverlayVisible = false, isOverlayVisible = false,
@@ -30,6 +29,7 @@ window.baguetteBox = (function () {
touchFlag = false, // busy touchFlag = false, // busy
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i, re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
re_v = /.+\.(webm|mp4)(\?|$)/i, re_v = /.+\.(webm|mp4)(\?|$)/i,
anims = ['slideIn', 'fadeIn', 'none'],
data = {}, // all galleries data = {}, // all galleries
imagesElements = [], imagesElements = [],
documentLastFocus = null, documentLastFocus = null,
@@ -178,6 +178,7 @@ window.baguetteBox = (function () {
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' + '<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' +
'<div id="bbox-btns">' + '<div id="bbox-btns">' +
'<button id="bbox-help" type="button">?</button>' + '<button id="bbox-help" type="button">?</button>' +
'<button id="bbox-anim" type="button" tt="a">-</button>' +
'<button id="bbox-rotl" type="button">↶</button>' + '<button id="bbox-rotl" type="button">↶</button>' +
'<button id="bbox-rotr" type="button">↷</button>' + '<button id="bbox-rotr" type="button">↷</button>' +
'<button id="bbox-tsel" type="button">sel</button>' + '<button id="bbox-tsel" type="button">sel</button>' +
@@ -193,6 +194,7 @@ window.baguetteBox = (function () {
btnPrev = ebi('bbox-prev'); btnPrev = ebi('bbox-prev');
btnNext = ebi('bbox-next'); btnNext = ebi('bbox-next');
btnHelp = ebi('bbox-help'); btnHelp = ebi('bbox-help');
btnAnim = ebi('bbox-anim');
btnRotL = ebi('bbox-rotl'); btnRotL = ebi('bbox-rotl');
btnRotR = ebi('bbox-rotr'); btnRotR = ebi('bbox-rotr');
btnSel = ebi('bbox-tsel'); btnSel = ebi('bbox-tsel');
@@ -284,6 +286,16 @@ window.baguetteBox = (function () {
rotn(e.shiftKey ? -1 : 1); rotn(e.shiftKey ? -1 : 1);
} }
function anim() {
var i = (anims.indexOf(options.animation) + 1) % anims.length,
o = options;
swrite('ganim', anims[i]);
options = {};
setOptions(o);
if (tt.en)
tt.show.bind(this)();
}
function setVmode() { function setVmode() {
var v = vid(); var v = vid();
ebi('bbox-vmode').style.display = v ? '' : 'none'; ebi('bbox-vmode').style.display = v ? '' : 'none';
@@ -397,6 +409,7 @@ window.baguetteBox = (function () {
bind(btnClose, 'click', hideOverlay); bind(btnClose, 'click', hideOverlay);
bind(btnVmode, 'click', tglVmode); bind(btnVmode, 'click', tglVmode);
bind(btnHelp, 'click', halp); bind(btnHelp, 'click', halp);
bind(btnAnim, 'click', anim);
bind(btnRotL, 'click', rotl); bind(btnRotL, 'click', rotl);
bind(btnRotR, 'click', rotr); bind(btnRotR, 'click', rotr);
bind(btnSel, 'click', tglsel); bind(btnSel, 'click', tglsel);
@@ -414,6 +427,7 @@ window.baguetteBox = (function () {
unbind(btnClose, 'click', hideOverlay); unbind(btnClose, 'click', hideOverlay);
unbind(btnVmode, 'click', tglVmode); unbind(btnVmode, 'click', tglVmode);
unbind(btnHelp, 'click', halp); unbind(btnHelp, 'click', halp);
unbind(btnAnim, 'click', anim);
unbind(btnRotL, 'click', rotl); unbind(btnRotL, 'click', rotl);
unbind(btnRotR, 'click', rotr); unbind(btnRotR, 'click', rotr);
unbind(btnSel, 'click', tglsel); unbind(btnSel, 'click', tglsel);
@@ -459,7 +473,12 @@ window.baguetteBox = (function () {
if (typeof newOptions[item] !== 'undefined') if (typeof newOptions[item] !== 'undefined')
options[item] = newOptions[item]; options[item] = newOptions[item];
} }
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
var an = options.animation = sread('ganim') || anims[ANIM ? 0 : 2];
btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)];
btnAnim.setAttribute('tt', 'animation: ' + an);
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .3s ease' :
options.animation === 'slideIn' ? '' : 'none'); options.animation === 'slideIn' ? '' : 'none');
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1)) if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
@@ -520,6 +539,7 @@ window.baguetteBox = (function () {
if (overlay.style.display === 'none') if (overlay.style.display === 'none')
return; return;
sethash('');
unbind(document, 'keydown', keyDownHandler); unbind(document, 'keydown', keyDownHandler);
unbind(document, 'keyup', keyUpHandler); unbind(document, 'keyup', keyUpHandler);
unbind(document, 'fullscreenchange', onFSC); unbind(document, 'fullscreenchange', onFSC);
@@ -806,7 +826,7 @@ window.baguetteBox = (function () {
slider.style.transform = 'translate3d(' + offset + ',0,0)' : slider.style.transform = 'translate3d(' + offset + ',0,0)' :
slider.style.left = offset; slider.style.left = offset;
slider.style.opacity = 1; slider.style.opacity = 1;
}, 400); }, 100);
} else { } else {
xform ? xform ?
slider.style.transform = 'translate3d(' + offset + ',0,0)' : slider.style.transform = 'translate3d(' + offset + ',0,0)' :

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@
<input type="file" name="f" multiple /><br /> <input type="file" name="f" multiple /><br />
<input type="submit" value="start upload"> <input type="submit" value="start upload">
</form> </form>
<a id="bbsw" href="?b=u"><br />switch to basic browser</a>
</div> </div>
<div id="op_mkdir" class="opview opbox act"> <div id="op_mkdir" class="opview opbox act">
@@ -134,6 +135,9 @@
<script> <script>
var acct = "{{ acct }}", var acct = "{{ acct }}",
perms = {{ perms }}, perms = {{ perms }},
themes = {{ themes }},
dtheme = "{{ dtheme }}",
srvinf = "{{ srv_info }}",
def_hcols = {{ def_hcols|tojson }}, def_hcols = {{ def_hcols|tojson }},
have_up2k_idx = {{ have_up2k_idx|tojson }}, have_up2k_idx = {{ have_up2k_idx|tojson }},
have_tags_idx = {{ have_tags_idx|tojson }}, have_tags_idx = {{ have_tags_idx|tojson }},
@@ -142,14 +146,16 @@
have_del = {{ have_del|tojson }}, have_del = {{ have_del|tojson }},
have_unpost = {{ have_unpost|tojson }}, have_unpost = {{ have_unpost|tojson }},
have_zip = {{ have_zip|tojson }}, have_zip = {{ have_zip|tojson }},
turbolvl = {{ turbolvl|tojson }},
txt_ext = "{{ txt_ext }}", txt_ext = "{{ txt_ext }}",
{% if no_prism %}no_prism = 1,{% endif %} {% if no_prism %}no_prism = 1,{% endif %}
readme = {{ readme|tojson }}, readme = {{ readme|tojson }},
ls0 = {{ ls0|tojson }}; ls0 = {{ ls0|tojson }};
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark"); document.documentElement.className = localStorage.theme || dtheme;
</script> </script>
<script src="/.cpr/util.js?_={{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script>
<script src="/.cpr/browser.js?_={{ ts }}"></script> <script src="/.cpr/browser.js?_={{ ts }}"></script>
<script src="/.cpr/up2k.js?_={{ ts }}"></script> <script src="/.cpr/up2k.js?_={{ ts }}"></script>
{%- if js %} {%- if js %}

View File

@@ -7,16 +7,16 @@ function dbg(msg) {
// toolbar // toolbar
ebi('ops').innerHTML = ( ebi('ops').innerHTML = (
'<a href="#" data-dest="" tt="close submenu">--</a>\n' + '<a href="#" data-dest="" tt="close submenu">--</a>' +
'<a href="#" data-perm="read" data-dep="idx" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = start with yana and be an opus file$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = contain exactly «try unite»">🔎</a>\n' + '<a href="#" data-perm="read" data-dep="idx" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = start with yana and be an opus file$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = contain exactly «try unite»">🔎</a>' +
(have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>\n' : '') + (have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>' : '') +
'<a href="#" data-dest="up2k">🚀</a>\n' + '<a href="#" data-dest="up2k">🚀</a>' +
'<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>\n' + '<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>' +
'<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>\n' + '<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>' +
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>\n' + '<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>' +
'<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>\n' + '<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>' +
'<a href="#" data-dest="player" tt="media player options">🎺</a>\n' + '<a href="#" data-dest="player" tt="media player options">🎺</a>' +
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>\n' + '<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>' +
'<div id="opdesc"></div>' '<div id="opdesc"></div>'
); );
@@ -58,7 +58,7 @@ ebi('op_up2k').innerHTML = (
' <td class="c"><br />parallel uploads:</td>\n' + ' <td class="c"><br />parallel uploads:</td>\n' +
' <td class="c" rowspan="2">\n' + ' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="multitask" />\n' + ' <input type="checkbox" id="multitask" />\n' +
' <label for="multitask" tt="continue hashing other files while uploading">🏃</label>\n' + ' <label for="multitask" tt="continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck">🏃</label>\n' +
' </td>\n' + ' </td>\n' +
' <td class="c" rowspan="2">\n' + ' <td class="c" rowspan="2">\n' +
' <input type="checkbox" id="ask_up" />\n' + ' <input type="checkbox" id="ask_up" />\n' +
@@ -74,7 +74,7 @@ ebi('op_up2k').innerHTML = (
' <tr>\n' + ' <tr>\n' +
' <td class="c">\n' + ' <td class="c">\n' +
' <a href="#" class="b" id="nthread_sub">&ndash;</a><input\n' + ' <a href="#" class="b" id="nthread_sub">&ndash;</a><input\n' +
' class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' + ' class="txtbox" id="nthread" value="2" tt="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"/><a\n' +
' href="#" class="b" id="nthread_add">+</a><br />&nbsp;\n' + ' href="#" class="b" id="nthread_add">+</a><br />&nbsp;\n' +
' </td>\n' + ' </td>\n' +
' </tr>\n' + ' </tr>\n' +
@@ -84,10 +84,9 @@ ebi('op_up2k').innerHTML = (
'<div id="u2btn_ct">\n' + '<div id="u2btn_ct">\n' +
' <div id="u2btn">\n' + ' <div id="u2btn">\n' +
' <span id="u2bm"></span><br />\n' + ' <span id="u2bm"></span>\n' +
' drag/drop files<br />\n' + ' drop files / folders<br />\n' +
' and folders here<br />\n' + ' here (or click me)\n' +
' (or click me)\n' +
' </div>\n' + ' </div>\n' +
'</div>\n' + '</div>\n' +
@@ -121,8 +120,7 @@ ebi('op_up2k').innerHTML = (
'</table></div>\n' + '</table></div>\n' +
'<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' + '<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' +
'<p id="u2foot"></p>\n' + '<p id="u2foot"></p>'
'<p id="u2footfoot" data-perm="write">( you can use the <a href="#" id="u2nope">basic uploader</a> if you don\'t need lastmod timestamps, resumable uploads, or progress bars )</p>'
); );
@@ -146,7 +144,6 @@ ebi('op_cfg').innerHTML = (
' <h3>switches</h3>\n' + ' <h3>switches</h3>\n' +
' <div>\n' + ' <div>\n' +
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' + ' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' +
' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' +
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' + ' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' + ' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' + ' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
@@ -154,6 +151,11 @@ ebi('op_cfg').innerHTML = (
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' + ' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
' </div>\n' + ' </div>\n' +
'</div>\n' + '</div>\n' +
'<div>\n' +
' <h3>themes</h3>\n' +
' <div id="themes">\n' +
' </div>\n' +
'</div>\n' +
(have_zip ? ( (have_zip ? (
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n' '<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
) : '') + ) : '') +
@@ -262,6 +264,8 @@ function goto(dest) {
fn(); fn();
} }
clmod(document.documentElement, 'op_open', dest);
if (window['treectl']) if (window['treectl'])
treectl.onscroll(); treectl.onscroll();
} }
@@ -427,7 +431,7 @@ var mpl = (function () {
}; };
function announce() { function announce() {
if (!r.os_ctl) if (!r.os_ctl || !mp.au)
return; return;
var np = get_np()[0], var np = get_np()[0],
@@ -448,7 +452,7 @@ var mpl = (function () {
cover = null; cover = null;
for (var a = 0, aa = files.length; a < aa; a++) { for (var a = 0, aa = files.length; a < aa; a++) {
if (/^(cover|folder)\.(jpe?g|png|gif)$/.test(files[a].textContent)) { if (/^(cover|folder)\.(jpe?g|png|gif)$/i.test(files[a].textContent)) {
cover = noq_href(files[a]); cover = noq_href(files[a]);
break; break;
} }
@@ -509,7 +513,7 @@ catch (ex) { }
var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i : var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i :
have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i, have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i,
re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv)$/i; re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv|mpc)$/i;
// extract songs + add play column // extract songs + add play column
@@ -662,7 +666,7 @@ function ft2dict(tr) {
for (var a = 1, aa = th.length; a < aa; a++) { for (var a = 1, aa = th.length; a < aa; a++) {
var tv = tr.cells[a].textContent, var tv = tr.cells[a].textContent,
tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').pop(), tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').pop().toLowerCase(),
vis = th[a].className.indexOf('min') === -1; vis = th[a].className.indexOf('min') === -1;
if (!tv) if (!tv)
@@ -703,8 +707,12 @@ var widget = (function () {
return false; return false;
clmod(document.documentElement, 'np_open', is_open); clmod(document.documentElement, 'np_open', is_open);
widget.className = is_open ? 'open' : ''; clmod(widget, 'open', is_open);
bcfg_set('au_open', r.is_open = is_open); bcfg_set('au_open', r.is_open = is_open);
if (window.vbar) {
pbar.onresize();
vbar.onresize();
}
return true; return true;
}; };
r.toggle = function (e) { r.toggle = function (e) {
@@ -743,17 +751,29 @@ var widget = (function () {
o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9'; o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
o.value = m; o.value = m;
document.body.appendChild(o); document.body.appendChild(o);
o.focus();
o.select(); var cln = function () {
document.execCommand("copy"); o.value = 'copied to clipboard ';
o.value = 'copied to clipboard '; setTimeout(function () {
setTimeout(function () { document.body.removeChild(o);
document.body.removeChild(o); }, 500);
}, 500); };
var fb = function () {
console.log('fb');
o.focus();
o.select();
document.execCommand("copy");
cln();
};
try {
// https only
navigator.clipboard.writeText(m).then(cln, fb);
}
catch (ex) { fb(); }
}; };
r.set(sread('au_open') == 1); r.set(sread('au_open') == 1);
setTimeout(function () { setTimeout(function () {
clmod(ebi('widget'), 'anim', 1); clmod(widget, 'anim', 1);
}, 10); }, 10);
return r; return r;
})(); })();
@@ -794,6 +814,9 @@ var pbar = (function () {
grad; grad;
r.onresize = function () { r.onresize = function () {
if (!widget.is_open && r.buf)
return;
r.buf = canvas_cfg(ebi('barbuf')); r.buf = canvas_cfg(ebi('barbuf'));
r.pos = canvas_cfg(ebi('barpos')); r.pos = canvas_cfg(ebi('barpos'));
r.drawbuf(); r.drawbuf();
@@ -895,6 +918,9 @@ var vbar = (function () {
can, ctx, w, h, grad1, grad2; can, ctx, w, h, grad1, grad2;
r.onresize = function () { r.onresize = function () {
if (!widget.is_open && r.can)
return;
r.can = canvas_cfg(ebi('pvol')); r.can = canvas_cfg(ebi('pvol'));
can = r.can.can; can = r.can.can;
ctx = r.can.ctx; ctx = r.can.ctx;
@@ -1388,7 +1414,7 @@ var audio_eq = (function () {
// plays the tid'th audio file on the page // plays the tid'th audio file on the page
function play(tid, is_ev, seek, call_depth) { function play(tid, is_ev, seek) {
if (mp.order.length == 0) if (mp.order.length == 0)
return console.log('no audio found wait what'); return console.log('no audio found wait what');
@@ -1567,25 +1593,62 @@ function autoplay_blocked(seek) {
} }
function scan_hash(v) {
if (!v)
return null;
var m = /^#([ag])(f-[0-9a-f]{8,16})(&.+)?/.exec(v + '');
if (!m)
return null;
var mtype = m[1],
id = m[2],
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);
}
}
return [mtype, id, ts];
}
function eval_hash() { function eval_hash() {
var v = hash0; var v = hash0;
hash0 = null; hash0 = null;
if (!v) if (!v)
return; return;
if (v.indexOf('#af-') === 0) { var media = scan_hash(v);
var id = v.slice(2).split('&'); if (media) {
if (id[0].length < 10) var mtype = media[0],
return; id = media[1],
ts = media[2];
if (id.length == 1) if (mtype == 'a') {
return play(id[0]); if (!ts)
return play(id);
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]); return play(id, false, ts);
if (!m) }
return play(id[0]);
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0)); if (mtype == 'g') {
if (!thegrid.en)
ebi('griden').click();
var t = setInterval(function () {
if (!thegrid.bbox)
return;
clearInterval(t);
var im = QS('#ggrid a[ref="' + id + '"]');
im.click();
im.scrollIntoView();
}, 50);
}
} }
if (v.indexOf('#q=') === 0) { if (v.indexOf('#q=') === 0) {
@@ -1850,9 +1913,6 @@ var fileman = (function () {
if (!md.hasOwnProperty(k)) if (!md.hasOwnProperty(k))
continue; continue;
md[k.toLowerCase()] = md[k];
k = k.toLowerCase();
if (k.startsWith('.')) if (k.startsWith('.'))
md[k.slice(1)] = md[k]; md[k.slice(1)] = md[k];
} }
@@ -2314,10 +2374,12 @@ var showfile = (function () {
'.bas': 'basic', '.bas': 'basic',
'.bat': 'batch', '.bat': 'batch',
'.cxx': 'cpp', '.cxx': 'cpp',
'.diz': 'ans',
'.h': 'c', '.h': 'c',
'.hpp': 'cpp', '.hpp': 'cpp',
'.htm': 'html', '.htm': 'html',
'.hxx': 'cpp', '.hxx': 'cpp',
'.log': 'ans',
'.patch': 'diff', '.patch': 'diff',
'.ps1': 'powershell', '.ps1': 'powershell',
'.psm1': 'powershell', '.psm1': 'powershell',
@@ -2334,7 +2396,7 @@ var showfile = (function () {
'cmakelists.txt': 'cmake', 'cmakelists.txt': 'cmake',
'dockerfile': 'docker' 'dockerfile': 'docker'
}; };
var x = txt_ext + ' c cfg conf cpp cs css diff go html ini java js json jsx kt kts less latex lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml'; var x = txt_ext + ' ans c cfg conf cpp cs css diff go html ini java js json jsx kt kts latex less lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml';
x = x.split(/ +/g); x = x.split(/ +/g);
for (var a = 0; a < x.length; a++) for (var a = 0; a < x.length; a++)
r.map["." + x[a]] = x[a]; r.map["." + x[a]] = x[a];
@@ -2441,7 +2503,10 @@ var showfile = (function () {
if (lnh.slice(0, 5) == '#doc.') if (lnh.slice(0, 5) == '#doc.')
sethash(lnh.slice(1)); sethash(lnh.slice(1));
Prism.highlightElement(el || QS('#doc>code')); el = el || QS('#doc>code');
Prism.highlightElement(el);
if (el.className == 'language-ans')
r.ansify(el);
} }
catch (ex) { } catch (ex) { }
} }
@@ -2461,7 +2526,7 @@ var showfile = (function () {
el.textContent = txt; el.textContent = txt;
el.innerHTML = '<code>' + el.innerHTML + '</code>'; el.innerHTML = '<code>' + el.innerHTML + '</code>';
if (!window['no_prism']) { if (!window['no_prism']) {
el.setAttribute('class', 'prism linkable-line-numbers line-numbers language-' + lang); el.className = 'prism linkable-line-numbers line-numbers language-' + lang;
if (!defer) if (!defer)
fun(el.firstChild); fun(el.firstChild);
else else
@@ -2489,6 +2554,65 @@ var showfile = (function () {
tree_scrollto(); tree_scrollto();
} }
r.ansify = function (el) {
var ctab = (light ?
'bfbfbf d30253 497600 b96900 006fbb a50097 288276 2d2d2d 9f9f9f 943b55 3a5600 7f4f00 00507d 683794 004343 000000' :
'404040 f03669 b8e346 ffa402 02a2ff f65be3 3da698 d2d2d2 606060 c75b79 c8e37e ffbe4a 71cbff b67fe3 9cf0ed ffffff').split(/ /g),
src = el.innerHTML.split(/\x1b\[/g),
out = ['<span>'], fg = 7, bg = null, bfg = 0, bbg = 0, inv = 0, bold = 0;
for (var a = 0; a < src.length; a++) {
var m = /^([0-9;]+)m/.exec(src[a]);
if (!m) {
if (a || src[a])
out.push('\x1b[' + src[a]);
continue;
}
var cs = m[1].split(/;/g),
txt = src[a].slice(m[1].length + 1);
for (var b = 0; b < cs.length; b++) {
var c = parseInt(cs[b]);
if (c == 0) {
fg = 7;
bg = null;
bfg = bbg = bold = inv = 0;
}
if (c == 1) bfg = bold = 1;
if (c == 7) inv = 1;
if (c == 22) bfg = bold = 0;
if (c == 27) inv = 0;
if (c >= 30 && c <= 37) fg = c - 30;
if (c >= 40 && c <= 47) bg = c - 40;
if (c >= 90 && c <= 97) {
fg = c - 90;
bfg = 1;
}
if (c >= 100 && c <= 107) {
bg = c - 100;
bbg = 1;
}
}
var cfg = fg, cbg = bg;
if (inv) {
cbg = fg;
cfg = bg || 0;
}
var s = '</span><span style="color:#' + ctab[cfg + bfg * 8];
if (cbg !== null)
s += ';background:#' + ctab[cbg + bbg * 8];
if (bold)
s += ';font-weight:bold';
out.push(s + '">' + txt);
}
el.innerHTML = out.join('');
};
r.mktree = function () { r.mktree = function () {
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>']; var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
for (var a = 0; a < r.files.length; a++) { for (var a = 0; a < r.files.length; a++) {
@@ -2532,7 +2656,7 @@ var showfile = (function () {
}; };
var bdoc = ebi('bdoc'); var bdoc = ebi('bdoc');
bdoc.setAttribute('class', 'line-numbers'); bdoc.className = 'line-numbers';
bdoc.innerHTML = ( bdoc.innerHTML = (
'<div id="hdoc" class="ghead">\n' + '<div id="hdoc" class="ghead">\n' +
'<a href="#" class="btn" id="xdoc" tt="return to folder view$NHotkey: M">❌ close</a>\n' + '<a href="#" class="btn" id="xdoc" tt="return to folder view$NHotkey: M">❌ close</a>\n' +
@@ -2727,19 +2851,24 @@ var thegrid = (function () {
for (var a = 0, aa = ths.length; a < aa; a++) { for (var a = 0, aa = ths.length; a < aa; a++) {
var tr = ebi(ths[a].getAttribute('ref')).closest('tr'), var tr = ebi(ths[a].getAttribute('ref')).closest('tr'),
cl = tr.getAttribute('class') || ''; cl = tr.className || '';
if (noq_href(ths[a]).endsWith('/')) if (noq_href(ths[a]).endsWith('/'))
cl += ' dir'; cl += ' dir';
ths[a].setAttribute('class', cl); ths[a].className = cl;
} }
var uns = QS('#ggrid a[ref="unsearch"]');
if (uns) var sp = ['unsearch', 'moar'];
uns.onclick = function (e) { for (var a = 0; a < sp.length; a++)
ev(e); (function (a) {
ebi('unsearch').click(); var o = QS('#ggrid a[ref="' + sp[a] + '"]');
}; if (o)
o.onclick = function (e) {
ev(e);
ebi(sp[a]).click();
};
})(a);
}; };
r.tippen = function () { r.tippen = function () {
@@ -2860,6 +2989,9 @@ var thegrid = (function () {
return '<a download href="' + h + return '<a download href="' + h +
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' + '">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
esc(uricom_dec(h.split('/').pop())[0]) + '</a>'; esc(uricom_dec(h.split('/').pop())[0]) + '</a>';
},
onChange: function (i) {
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
} }
})[0]; })[0];
}; };
@@ -2873,10 +3005,6 @@ var thegrid = (function () {
}); });
ebi('wtgrid').onclick = ebi('griden').onclick; ebi('wtgrid').onclick = ebi('griden').onclick;
setTimeout(function () {
import_js('/.cpr/baguettebox.js', r.bagit);
}, 1);
return r; return r;
})(); })();
@@ -3158,7 +3286,8 @@ document.onkeydown = function (e) {
var trs = [], var trs = [],
orig_url = null, orig_url = null,
orig_html = null; orig_html = null,
cap = 125;
for (var a = 0; a < sconf.length; a++) { for (var a = 0; a < sconf.length; a++) {
var html = ['<tr><td><br />' + sconf[a][0] + '</td>']; var html = ['<tr><td><br />' + sconf[a][0] + '</td>'];
@@ -3187,12 +3316,13 @@ document.onkeydown = function (e) {
var o = QSA('#op_search input'); var o = QSA('#op_search input');
for (var a = 0; a < o.length; a++) { for (var a = 0; a < o.length; a++) {
o[a].oninput = ev_search_input; o[a].oninput = ev_search_input;
o[a].onkeydown = ev_search_keydown;
} }
function srch_msg(err, txt) { function srch_msg(err, txt) {
var o = ebi('srch_q'); var o = ebi('srch_q');
o.textContent = txt; o.textContent = txt;
o.style.color = err ? '#f09' : '#c90'; clmod(o, 'err', err);
} }
var search_timeout, var search_timeout,
@@ -3212,12 +3342,18 @@ document.onkeydown = function (e) {
encode_query(); encode_query();
set_vq(); set_vq();
cap = 125;
clearTimeout(defer_timeout); clearTimeout(defer_timeout);
defer_timeout = setTimeout(try_search, 2000); defer_timeout = setTimeout(try_search, 2000);
try_search(v); try_search(v);
} }
function ev_search_keydown(e) {
if (e.key == 'Enter')
do_search();
}
function try_search(v) { function try_search(v) {
if (Date.now() - search_in_progress > 30 * 1000) { if (Date.now() - search_in_progress > 30 * 1000) {
clearTimeout(defer_timeout); clearTimeout(defer_timeout);
@@ -3246,8 +3382,6 @@ document.onkeydown = function (e) {
vs = ebi('srch_' + k + 'v').value, vs = ebi('srch_' + k + 'v').value,
tvs = []; tvs = [];
if (k == 'name')
console.log('a');
while (vs) { while (vs) {
vs = vs.trim(); vs = vs.trim();
if (!vs) if (!vs)
@@ -3336,7 +3470,7 @@ document.onkeydown = function (e) {
xhr.onreadystatechange = xhr_search_results; xhr.onreadystatechange = xhr_search_results;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.q_raw = ebi('q_raw').value; xhr.q_raw = ebi('q_raw').value;
xhr.send(JSON.stringify({ "q": xhr.q_raw })); xhr.send(JSON.stringify({ "q": xhr.q_raw, "n": cap }));
} }
function xhr_search_results() { function xhr_search_results() {
@@ -3369,7 +3503,9 @@ document.onkeydown = function (e) {
var html = mk_files_header(tagord), seen = {}; var html = mk_files_header(tagord), seen = {};
html.push('<tbody>'); html.push('<tbody>');
html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch"><big style="font-weight:bold">[❌] close search results</big></a></td></tr>'); html.push('<tr class="srch_hdr"><td>-</td><td><a href="#" id="unsearch"><big style="font-weight:bold">[❌] close search results</big></a> -- showing ' +
res.hits.length + ' hits' + (res.hits.length == cap ? ' -- <a href="#" id="moar">load more</a>' : '') + '</td></tr>');
for (var a = 0; a < res.hits.length; a++) { for (var a = 0; a < res.hits.length; a++) {
var r = res.hits[a], var r = res.hits[a],
ts = parseInt(r.ts), ts = parseInt(r.ts),
@@ -3421,6 +3557,9 @@ document.onkeydown = function (e) {
sethash('q=' + uricom_enc(this.q_raw)); sethash('q=' + uricom_enc(this.q_raw));
ebi('unsearch').onclick = unsearch; ebi('unsearch').onclick = unsearch;
var m = ebi('moar');
if (m)
m.onclick = moar;
} }
function unsearch(e) { function unsearch(e) {
@@ -3432,6 +3571,12 @@ document.onkeydown = function (e) {
sethash(''); sethash('');
reload_browser(); reload_browser();
} }
function moar(e) {
ev(e);
cap *= 2;
do_search();
}
})(); })();
@@ -3612,7 +3757,7 @@ var treectl = (function () {
treeh = winh - atop; treeh = winh - atop;
tree.style.top = top + 'px'; tree.style.top = top + 'px';
tree.style.height = treeh < 10 ? '' : Math.floor(treeh - 2) + 'px'; tree.style.height = treeh < 10 ? '' : Math.floor(treeh) + 'px';
} }
} }
timer.add(onscroll2, true); timer.add(onscroll2, true);
@@ -3718,7 +3863,6 @@ var treectl = (function () {
QS('#treeul>li>a+a').textContent = '[root]'; QS('#treeul>li>a+a').textContent = '[root]';
despin('#tree'); despin('#tree');
reload_tree(); reload_tree();
onresize();
var fun = r.dir_cb; var fun = r.dir_cb;
if (fun) { if (fun) {
@@ -3750,7 +3894,7 @@ var treectl = (function () {
cl = 'par'; cl = 'par';
} }
links[a].setAttribute('class', cl); links[a].className = cl;
links[a].onclick = treego; links[a].onclick = treego;
links[a].onmouseenter = nowrap ? menter : null; links[a].onmouseenter = nowrap ? menter : null;
links[a].onmouseleave = nowrap ? mleave : null; links[a].onmouseleave = nowrap ? mleave : null;
@@ -3771,6 +3915,7 @@ var treectl = (function () {
catch (ex) { } catch (ex) { }
r.pdir.shift(); r.pdir.shift();
r.pdirw = -1; r.pdirw = -1;
onresize();
} }
function compy() { function compy() {
@@ -3817,7 +3962,7 @@ var treectl = (function () {
return true; return true;
ev(e); ev(e);
if (this.getAttribute('class') == 'hl' && if (this.className == 'hl' &&
this.previousSibling.textContent == '-') { this.previousSibling.textContent == '-') {
treegrow.call(this.previousSibling, e); treegrow.call(this.previousSibling, e);
return; return;
@@ -3880,7 +4025,8 @@ var treectl = (function () {
return; return;
} }
ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>'; srvinf = res.srvinf;
ebi('srv_info').innerHTML = ebi('srv_info2').innerHTML = '<span>' + res.srvinf + '</span>';
if (this.hpush && !showfile.active()) if (this.hpush && !showfile.active())
hist_push(this.top); hist_push(this.top);
@@ -3902,7 +4048,6 @@ var treectl = (function () {
r.ls_cb = null; r.ls_cb = null;
fun(); fun();
} }
eval_hash();
} }
r.gentab = function (top, res) { r.gentab = function (top, res) {
@@ -3954,21 +4099,43 @@ var treectl = (function () {
html = html.join('\n'); html = html.join('\n');
set_files_html(html); set_files_html(html);
filecols.set_style(); function asdf() {
showfile.mktree(); filecols.set_style();
mukey.render(); showfile.mktree();
reload_tree(); mukey.render();
reload_browser(); reload_tree();
tree_scrollto(); reload_browser();
if (res.acct) { tree_scrollto();
acct = res.acct; if (res.acct) {
have_up2k_idx = res.idx; acct = res.acct;
apply_perms(res.perms); have_up2k_idx = res.idx;
fileman.render(); apply_perms(res.perms);
fileman.render();
}
setTimeout(eval_hash, 1);
} }
var m = scan_hash(hash0),
url = null;
if (m) {
url = ebi(m[1]);
if (url) {
url = url.href;
var mt = m[0] == 'a' ? 'audio' : /\.(webm|mkv)($|\?)/i.exec(url) ? 'video' : 'image'
if (mt == 'image') {
url += url.indexOf('?') < 0 ? '?cache' : '&cache';
console.log(url);
new Image().src = url;
}
}
}
if (url) setTimeout(asdf, 1); else asdf();
} }
r.hydrate = function () { r.hydrate = function () {
qsr('#bbsw');
if (ls0 === null) { if (ls0 === null) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', '/?am_js', true); xhr.open('GET', '/?am_js', true);
@@ -3978,12 +4145,9 @@ var treectl = (function () {
} }
r.gentab(get_evpath(), ls0); r.gentab(get_evpath(), ls0);
reload_browser();
pbar.onresize(); pbar.onresize();
vbar.onresize(); vbar.onresize();
mukey.render();
showfile.addlinks(); showfile.addlinks();
thegrid.setdirty();
setTimeout(eval_hash, 1); setTimeout(eval_hash, 1);
}; };
@@ -4064,7 +4228,7 @@ var treectl = (function () {
function enspin(sel) { function enspin(sel) {
despin(sel); despin(sel);
var d = mknod('div'); var d = mknod('div');
d.setAttribute('class', 'dumb_loader_thing'); d.className = 'dumb_loader_thing';
d.innerHTML = '🌲'; d.innerHTML = '🌲';
var tgt = QS(sel); var tgt = QS(sel);
tgt.insertBefore(d, tgt.childNodes[0]); tgt.insertBefore(d, tgt.childNodes[0]);
@@ -4082,14 +4246,16 @@ function apply_perms(newperms) {
perms = newperms || []; perms = newperms || [];
var a = QS('#ops a[data-dest="up2k"]'); var a = QS('#ops a[data-dest="up2k"]');
var suf = 'multithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader';
if (have_up2k_idx) { if (have_up2k_idx) {
a.removeAttribute('data-perm'); a.removeAttribute('data-perm');
a.setAttribute('tt', 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server'); a.setAttribute('tt', '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, ' + suf);
} }
else { else {
a.setAttribute('data-perm', 'write'); a.setAttribute('data-perm', 'write');
a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)'); a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$N' + suf);
} }
a.style.display = '';
tt.att(QS('#ops')); tt.att(QS('#ops'));
var axs = [], var axs = [],
@@ -4106,10 +4272,11 @@ function apply_perms(newperms) {
axs += '-Only'; axs += '-Only';
} }
ebi('acc_info').innerHTML = '<span' + aclass + axs + ' access</span>' + (acct != '*' ? ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
'<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>'); '</span></span><span' + aclass + axs + ' access</span>' + (acct != '*' ?
'<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>');
var o = QSA('#ops>a[data-perm], #u2footfoot'); var o = QSA('#ops>a[data-perm]');
for (var a = 0; a < o.length; a++) { for (var a = 0; a < o.length; a++) {
var display = ''; var display = '';
var needed = o[a].getAttribute('data-perm').split(' '); var needed = o[a].getAttribute('data-perm').split(' ');
@@ -4167,7 +4334,7 @@ function find_file_col(txt) {
for (var a = 0; a < tds.length; a++) { for (var a = 0; a < tds.length; a++) {
var spans = tds[a].getElementsByTagName('span'); var spans = tds[a].getElementsByTagName('span');
if (spans.length && spans[0].textContent == txt) { if (spans.length && spans[0].textContent == txt) {
min = (tds[a].getAttribute('class') || '').indexOf('min') !== -1; min = (tds[a].className || '').indexOf('min') !== -1;
i = a; i = a;
break; break;
} }
@@ -4313,7 +4480,7 @@ var filecols = (function () {
tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')'); tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')');
for (var b = 0, bb = tds.length; b < bb; b++) for (var b = 0, bb = tds.length; b < bb; b++)
tds[b].setAttribute('class', cls); tds[b].className = cls;
} }
if (window['tt']) { if (window['tt']) {
tt.att(ebi('hcols')); tt.att(ebi('hcols'));
@@ -4464,20 +4631,55 @@ var mukey = (function () {
})(); })();
var light; var light, theme;
(function () { var settheme = (function () {
var ax = 'abcdefghijklmnopqrstuvwx';
theme = sread('theme') || 'a';
if (!/^[a-x][yz]/.exec(theme))
theme = dtheme;
light = !!(theme.indexOf('y') + 1);
function freshen() { function freshen() {
clmod(document.documentElement, "light", light); var cl = document.documentElement.className;
clmod(document.documentElement, "dark", !light); cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' ');
document.documentElement.className = cl + ' ' + theme + ' ';
pbar.drawbuf(); pbar.drawbuf();
pbar.drawpos(); pbar.drawpos();
vbar.draw(); vbar.draw();
showfile.setstyle(); showfile.setstyle();
var html = [], itheme = ax.indexOf(theme.charAt(0)) * 2 + (light ? 1 : 0),
names = ['classic dark', 'classic light', 'flat dark', 'flat light', 'vice', 'hotdog stand'];
for (var a = 0; a < themes; a++)
html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') +
'" tt="' + (names[a] || 'custom') + '">' + a + '</a>');
ebi('themes').innerHTML = html.join('');
var btns = QSA('#themes a');
for (var a = 0; a < themes; a++)
btns[a].onclick = settheme;
bcfg_set('light', light);
tt.att(ebi('themes'));
} }
bcfg_bind(window, 'light', 'lightmode', false, freshen); function settheme(e) {
var i = e;
try { ev(e); i = e.target.textContent; } catch (ex) { }
light = i % 2 == 1;
var c = ax.charAt(Math.floor(i / 2)),
l = light ? 'y' : 'z';
theme = c + l + ' ' + c + ' ' + l;
swrite('theme', theme);
freshen();
}
freshen(); freshen();
return settheme;
})(); })();
@@ -4724,7 +4926,10 @@ var msel = (function () {
tb.value = ''; tb.value = '';
clmod(sf, 'vis'); clmod(sf, 'vis');
sf.textContent = ''; sf.textContent = '';
treectl.goto(this.vp + uricom_enc(this.dn) + '/', true);
var dn = this.getResponseHeader('X-New-Dir');
dn = dn || uricom_enc(this.dn);
treectl.goto(this.vp + dn + '/', true);
} }
})(); })();
@@ -4896,7 +5101,7 @@ var unpost = (function () {
html.push("<table><thead><tr><td></td><td>time</td><td>size</td><td>file</td></tr></thead><tbody>"); html.push("<table><thead><tr><td></td><td>time</td><td>size</td><td>file</td></tr></thead><tbody>");
} }
else else
html.push("<p>sike! no uploads " + (filt.value ? 'matching that filter' : '') + " are sufficiently recent</p>"); html.push("sike! no uploads " + (filt.value ? 'matching that filter' : '') + " are sufficiently recent");
var mods = [1000, 100, 10]; var mods = [1000, 100, 10];
for (var a = 0; a < res.length; a++) { for (var a = 0; a < res.length; a++) {

View File

@@ -161,7 +161,7 @@ blink {
height: 1.05em; height: 1.05em;
margin: -.2em .3em -.2em -.4em; margin: -.2em .3em -.2em -.4em;
display: inline-block; display: inline-block;
border: 1px solid rgba(0,0,0,0.2); border: 1px solid rgba(154,154,154,0.6);
border-width: .2em .2em 0 0; border-width: .2em .2em 0 0;
transform: rotate(45deg); transform: rotate(45deg);
} }
@@ -219,48 +219,45 @@ blink {
html.dark, html.z,
html.dark body { html.z body {
background: #222; background: #222;
color: #ccc; color: #ccc;
} }
html.dark #toc a { html.z #toc a {
color: #ccc; color: #ccc;
border-left: .4em solid #444; border-left: .4em solid #444;
border-bottom: .1em solid #333; border-bottom: .1em solid #333;
} }
html.dark #toc a.act { html.z #toc a.act {
color: #fff; color: #fff;
border-left: .4em solid #3ad; border-left: .4em solid #3ad;
} }
html.dark #toc li { html.z #toc li {
border-width: 0; border-width: 0;
} }
html.dark #mn a:not(:last-child)::after { html.z #mn a {
border-color: rgba(255,255,255,0.3);
}
html.dark #mn a {
color: #ccc; color: #ccc;
} }
html.dark #mn { html.z #mn {
border-bottom: 1px solid #333; border-bottom: 1px solid #333;
} }
html.dark #mn, html.z #mn,
html.dark #mh { html.z #mh {
background: #222; background: #222;
} }
html.dark #mh a { html.z #mh a {
color: #ccc; color: #ccc;
background: none; background: none;
} }
html.dark #mh a:hover { html.z #mh a:hover {
background: #333; background: #333;
color: #fff; color: #fff;
} }
html.dark #toolsbox { html.z #toolsbox {
background: #222; background: #222;
} }
html.dark #toolsbox.open { html.z #toolsbox.open {
box-shadow: 0 .2em .2em #069; box-shadow: 0 .2em .2em #069;
border-radius: 0 0 .4em .4em; border-radius: 0 0 .4em .4em;
} }
@@ -308,23 +305,23 @@ blink {
html.dark #toc { html.z #toc {
background: #282828; background: #282828;
border-top: 1px solid #2c2c2c; border-top: 1px solid #2c2c2c;
box-shadow: 0 0 1em #181818; box-shadow: 0 0 1em #181818;
} }
html.dark #toc, html.z #toc,
html.dark #mw { html.z #mw {
scrollbar-color: #b80 #282828; scrollbar-color: #b80 #282828;
} }
html.dark #toc::-webkit-scrollbar-track { html.z #toc::-webkit-scrollbar-track {
background: #282828; background: #282828;
} }
html.dark #toc::-webkit-scrollbar { html.z #toc::-webkit-scrollbar {
background: #282828; background: #282828;
width: .8em; width: .8em;
} }
html.dark #toc::-webkit-scrollbar-thumb { html.z #toc::-webkit-scrollbar-thumb {
background: #b80; background: #b80;
} }
} }
@@ -432,16 +429,16 @@ blink {
html.dark .mdo a { html.z .mdo a {
color: #000; color: #000;
} }
html.dark .mdo pre, html.z .mdo pre,
html.dark .mdo code { html.z .mdo code {
color: #240; color: #240;
} }
html.dark .mdo p>em, html.z .mdo p>em,
html.dark .mdo li>em, html.z .mdo li>em,
html.dark .mdo td>em { html.z .mdo td>em {
color: #940; color: #940;
} }
} }

View File

@@ -136,13 +136,13 @@ var md_opt = {
(function () { (function () {
var l = localStorage, var l = localStorage,
drk = l.lightmode != 1, drk = l.light != 1,
btn = document.getElementById("lightswitch"), btn = document.getElementById("lightswitch"),
f = function (e) { f = function (e) {
if (e) { e.preventDefault(); drk = !drk; } if (e) { e.preventDefault(); drk = !drk; }
document.documentElement.setAttribute("class", drk? "dark":"light"); document.documentElement.className = drk? "z":"y";
btn.innerHTML = "go " + (drk ? "light":"dark"); btn.innerHTML = "go " + (drk ? "light":"dark");
l.lightmode = drk? 0:1; l.light = drk? 0:1;
}; };
btn.onclick = f; btn.onclick = f;

View File

@@ -278,7 +278,7 @@ function convert_markdown(md_text, dest_dom) {
if (!txt) if (!txt)
nodes[a].textContent = href; nodes[a].textContent = href;
else if (href !== txt) else if (href !== txt)
nodes[a].setAttribute('class', 'vis'); nodes[a].className = 'vis';
} }
// todo-lists (should probably be a marked extension) // todo-lists (should probably be a marked extension)
@@ -294,7 +294,7 @@ function convert_markdown(md_text, dest_dom) {
var clas = done ? 'done' : 'pend'; var clas = done ? 'done' : 'pend';
var char = done ? 'Y' : 'N'; var char = done ? 'Y' : 'N';
dom_li.setAttribute('class', 'task-list-item'); dom_li.className = 'task-list-item';
dom_li.style.listStyleType = 'none'; dom_li.style.listStyleType = 'none';
var html = dom_li.innerHTML; var html = dom_li.innerHTML;
dom_li.innerHTML = dom_li.innerHTML =
@@ -468,11 +468,11 @@ function init_toc() {
for (var a = 0; a < anchors.length; a++) { for (var a = 0; a < anchors.length; a++) {
if (anchors[a].active) { if (anchors[a].active) {
anchors[a].active = false; anchors[a].active = false;
links[a].setAttribute('class', ''); links[a].className = '';
} }
} }
anchors[hit].active = true; anchors[hit].active = true;
links[hit].setAttribute('class', 'act'); links[hit].className = 'act';
} }
var pane_height = parseInt(getComputedStyle(dom_toc).height); var pane_height = parseInt(getComputedStyle(dom_toc).height);

View File

@@ -61,7 +61,7 @@
position: relative; position: relative;
scrollbar-color: #eb0 #f7f7f7; scrollbar-color: #eb0 #f7f7f7;
} }
html.dark #mt { html.z #mt {
color: #eee; color: #eee;
background: #222; background: #222;
border: 1px solid #777; border: 1px solid #777;
@@ -77,7 +77,7 @@ html.dark #mt {
background: #f97; background: #f97;
border-radius: .15em; border-radius: .15em;
} }
html.dark #save.force-save { html.z #save.force-save {
color: #fca; color: #fca;
background: #720; background: #720;
} }
@@ -102,7 +102,7 @@ html.dark #save.force-save {
#helpclose { #helpclose {
display: block; display: block;
} }
html.dark #helpbox { html.z #helpbox {
box-shadow: 0 .5em 2em #444; box-shadow: 0 .5em 2em #444;
background: #222; background: #222;
border: 1px solid #079; border: 1px solid #079;

View File

@@ -144,16 +144,16 @@ redraw = (function () {
map_pre = genmap(dom_pre, map_pre); map_pre = genmap(dom_pre, map_pre);
} }
function setsbs() { function setsbs() {
dom_wrap.setAttribute('class', ''); dom_wrap.className = '';
dom_swrap.setAttribute('class', ''); dom_swrap.className = '';
onresize(); onresize();
} }
function modetoggle() { function modetoggle() {
var mode = dom_nsbs.innerHTML; var mode = dom_nsbs.innerHTML;
dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor'; dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
mode += ' single'; mode += ' single';
dom_wrap.setAttribute('class', mode); dom_wrap.className = mode;
dom_swrap.setAttribute('class', mode); dom_swrap.className = mode;
onresize(); onresize();
} }
@@ -309,7 +309,7 @@ var modpoll = new Modpoll();
window.onbeforeunload = function (e) { window.onbeforeunload = function (e) {
if ((ebi("save").getAttribute('class') + '').indexOf('disabled') >= 0) if ((ebi("save").className + '').indexOf('disabled') >= 0)
return; //nice (todo) return; //nice (todo)
e.preventDefault(); //ff e.preventDefault(); //ff
@@ -321,7 +321,7 @@ window.onbeforeunload = function (e) {
function save(e) { function save(e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
var save_btn = ebi("save"), var save_btn = ebi("save"),
save_cls = save_btn.getAttribute('class') + ''; save_cls = save_btn.className + '';
if (save_cls.indexOf('disabled') >= 0) if (save_cls.indexOf('disabled') >= 0)
return toast.inf(2, "no changes"); return toast.inf(2, "no changes");
@@ -678,7 +678,7 @@ function reLastIndexOf(txt, ptn, end) {
// table formatter // table formatter
function fmt_table(e) { function fmt_table(e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
//dom_tbox.setAttribute('class', ''); //dom_tbox.className = '';
var txt = dom_src.value, var txt = dom_src.value,
ofs = dom_src.selectionStart, ofs = dom_src.selectionStart,
@@ -829,7 +829,7 @@ function fmt_table(e) {
// show unicode // show unicode
function mark_uni(e) { function mark_uni(e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
dom_tbox.setAttribute('class', ''); dom_tbox.className = '';
var txt = dom_src.value, var txt = dom_src.value,
ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g'), ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g'),
@@ -989,14 +989,14 @@ var set_lno = (function () {
ebi('tools').onclick = function (e) { ebi('tools').onclick = function (e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
var is_open = dom_tbox.getAttribute('class') != 'open'; var is_open = dom_tbox.className != 'open';
dom_tbox.setAttribute('class', is_open ? 'open' : ''); dom_tbox.className = is_open ? 'open' : '';
}; };
ebi('help').onclick = function (e) { ebi('help').onclick = function (e) {
if (e) e.preventDefault(); if (e) e.preventDefault();
dom_tbox.setAttribute('class', ''); dom_tbox.className = '';
var dom = ebi('helpbox'); var dom = ebi('helpbox');
var dtxt = dom.getElementsByTagName('textarea'); var dtxt = dom.getElementsByTagName('textarea');

View File

@@ -84,24 +84,24 @@ html .editor-toolbar>button.save.force-save {
/* darkmode */ /* darkmode */
html.dark .mdo, html.z .mdo,
html.dark .CodeMirror { html.z .CodeMirror {
border-color: #222; border-color: #222;
} }
html.dark, html.z,
html.dark body, html.z body,
html.dark .CodeMirror { html.z .CodeMirror {
background: #222; background: #222;
color: #ccc; color: #ccc;
} }
html.dark .CodeMirror-cursor { html.z .CodeMirror-cursor {
border-color: #fff; border-color: #fff;
} }
html.dark .CodeMirror-selected { html.z .CodeMirror-selected {
box-shadow: 0 0 1px #0cf inset; box-shadow: 0 0 1px #0cf inset;
} }
html.dark .CodeMirror-selected, html.z .CodeMirror-selected,
html.dark .CodeMirror-selectedtext { html.z .CodeMirror-selectedtext {
border-radius: .1em; border-radius: .1em;
background: #246; background: #246;
color: #fff; color: #fff;
@@ -109,37 +109,37 @@ html.dark .CodeMirror-selectedtext {
html.dark #mn a { html.z #mn a {
color: #ccc; color: #ccc;
} }
html.dark #mn a:not(:last-child):after { html.z #mn a:not(:last-child):after {
border-color: rgba(255,255,255,0.3); border-color: rgba(255,255,255,0.3);
} }
html.dark .editor-toolbar { html.z .editor-toolbar {
border-color: #2c2c2c; border-color: #2c2c2c;
background: #1c1c1c; background: #1c1c1c;
} }
html.dark .editor-toolbar>i.separator { html.z .editor-toolbar>i.separator {
border-left: 1px solid #444; border-left: 1px solid #444;
border-right: 1px solid #111; border-right: 1px solid #111;
} }
html.dark .editor-toolbar>button { html.z .editor-toolbar>button {
margin-left: -1px; border: 1px solid rgba(255,255,255,0.1); margin-left: -1px; border: 1px solid rgba(255,255,255,0.1);
color: #aaa; color: #aaa;
} }
html.dark .editor-toolbar>button:hover { html.z .editor-toolbar>button:hover {
color: #333; color: #333;
} }
html.dark .editor-toolbar>button.active { html.z .editor-toolbar>button.active {
color: #333; color: #333;
border-color: #ec1; border-color: #ec1;
background: #c90; background: #c90;
} }
html.dark .editor-toolbar::after, html.z .editor-toolbar::after,
html.dark .editor-toolbar::before { html.z .editor-toolbar::before {
background: none; background: none;
} }
@@ -150,6 +150,6 @@ html.dark .editor-toolbar::before {
padding: 1em; padding: 1em;
background: #f7f7f7; background: #f7f7f7;
} }
html.dark .mdo { html.z .mdo {
background: #1c1c1c; background: #1c1c1c;
} }

View File

@@ -34,11 +34,11 @@ var md_opt = {
var lightswitch = (function () { var lightswitch = (function () {
var l = localStorage, var l = localStorage,
drk = l.lightmode != 1, drk = l.light != 1,
f = function (e) { f = function (e) {
if (e) drk = !drk; if (e) drk = !drk;
document.documentElement.setAttribute("class", drk? "dark":"light"); document.documentElement.className = drk? "z":"y";
l.lightmode = drk? 0:1; l.light = drk? 0:1;
}; };
f(); f();
return f; return f;

View File

@@ -88,27 +88,27 @@ blockquote {
} }
html.dark, html.z,
html.dark body, html.z body,
html.dark #wrap { html.z #wrap {
background: #222; background: #222;
color: #ccc; color: #ccc;
} }
html.dark h1 { html.z h1 {
border-color: #777; border-color: #777;
} }
html.dark a { html.z a {
color: #fff; color: #fff;
background: #057; background: #057;
border-color: #37a; border-color: #37a;
} }
html.dark .logout, html.z .logout,
html.dark .btns a, html.z .btns a,
html.dark a.r { html.z a.r {
background: #804; background: #804;
border-color: #c28; border-color: #c28;
} }
html.dark input { html.z input {
color: #fff; color: #fff;
background: #626; background: #626;
border: 1px solid #c2c; border: 1px solid #c2c;
@@ -117,6 +117,6 @@ html.dark input {
padding: .5em .7em; padding: .5em .7em;
margin: 0 .5em 0 0; margin: 0 .5em 0 0;
} }
html.dark .num { html.z .num {
border-color: #777; border-color: #777;
} }

View File

@@ -97,7 +97,7 @@
<a href="#" id="repl">π</a> <a href="#" id="repl">π</a>
<script> <script>
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark"); document.documentElement.className = localStorage.light == 1 ? "y" : "z";
</script> </script>
<script src="/.cpr/util.js?_={{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>

View File

@@ -157,23 +157,24 @@ html {
#tt em { #tt em {
color: #f6a; color: #f6a;
} }
html.light #tt { html.y #tt {
color: #333;
background: #fff; background: #fff;
border-color: #888 #000 #777 #000; border-color: #888 #000 #777 #000;
} }
html.light #tt, html.y #tt,
html.light #toast { html.y #toast {
box-shadow: 0 .3em 1em rgba(0,0,0,0.4); box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
} }
#modalc code, #modalc code,
html.light #tt code { html.y #tt code {
background: #060; background: #060;
color: #fff; color: #fff;
} }
html.light #tt em { html.y #tt em {
color: #d38; color: #d38;
} }
html.light #tth { html.y #tth {
color: #000; color: #000;
background: #fff; background: #fff;
} }
@@ -273,9 +274,9 @@ html.light #tth {
box-shadow: 0 .1em .2em #fc0 inset; box-shadow: 0 .1em .2em #fc0 inset;
border-radius: .2em; border-radius: .2em;
} }
html.light *:focus, html.y *:focus,
html.light #pctl *:focus, html.y #pctl *:focus,
html.light .btn:focus { html.y .btn:focus {
box-shadow: 0 .1em .2em #037 inset; box-shadow: 0 .1em .2em #037 inset;
} }
input[type="text"]:focus, input[type="text"]:focus,
@@ -283,9 +284,9 @@ input:not([type]):focus,
textarea:focus { textarea:focus {
box-shadow: 0 .1em .3em #fc0, 0 -.1em .3em #fc0; box-shadow: 0 .1em .3em #fc0, 0 -.1em .3em #fc0;
} }
html.light input[type="text"]:focus, html.y input[type="text"]:focus,
html.light input:not([type]):focus, html.y input:not([type]):focus,
html.light textarea:focus { html.y textarea:focus {
box-shadow: 0 .1em .3em #037, 0 -.1em .3em #037; box-shadow: 0 .1em .3em #037, 0 -.1em .3em #037;
} }
@@ -414,7 +415,7 @@ html.light textarea:focus {
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; /*ie*/ word-wrap: break-word; /*ie*/
} }
html.light .mdo a, html.y .mdo a,
.mdo a { .mdo a {
color: #fff; color: #fff;
background: #39b; background: #39b;
@@ -443,48 +444,48 @@ html.light textarea:focus {
html.dark .mdo a { html.z .mdo a {
background: #057; background: #057;
} }
html.dark .mdo h1 a, html.dark .mdo h4 a, html.z .mdo h1 a, html.z .mdo h4 a,
html.dark .mdo h2 a, html.dark .mdo h5 a, html.z .mdo h2 a, html.z .mdo h5 a,
html.dark .mdo h3 a, html.dark .mdo h6 a { html.z .mdo h3 a, html.z .mdo h6 a {
color: inherit; color: inherit;
background: none; background: none;
} }
html.dark .mdo pre, html.z .mdo pre,
html.dark .mdo code { html.z .mdo code {
color: #8c0; color: #8c0;
background: #1a1a1a; background: #1a1a1a;
border: .07em solid #333; border: .07em solid #333;
} }
html.dark .mdo ul, html.z .mdo ul,
html.dark .mdo ol { html.z .mdo ol {
border-color: #444; border-color: #444;
} }
html.dark .mdo strong { html.z .mdo strong {
color: #fff; color: #fff;
} }
html.dark .mdo p>em, html.z .mdo p>em,
html.dark .mdo li>em, html.z .mdo li>em,
html.dark .mdo td>em { html.z .mdo td>em {
color: #f94; color: #f94;
border-color: #666; border-color: #666;
} }
html.dark .mdo h1 { html.z .mdo h1 {
background: #383838; background: #383838;
border-top: .4em solid #b80; border-top: .4em solid #b80;
border-bottom: .4em solid #4c4c4c; border-bottom: .4em solid #4c4c4c;
} }
html.dark .mdo h2 { html.z .mdo h2 {
background: #444; background: #444;
border-bottom: .22em solid #555; border-bottom: .22em solid #555;
} }
html.dark .mdo td, html.z .mdo td,
html.dark .mdo th { html.z .mdo th {
border-color: #444; border-color: #444;
} }
html.dark .mdo blockquote { html.z .mdo blockquote {
background: #282828; background: #282828;
border: .07em dashed #444; border: .07em dashed #444;
} }

View File

@@ -135,7 +135,7 @@ function up2k_flagbus() {
} }
function U2pvis(act, btns) { function U2pvis(act, btns, uc) {
var r = this; var r = this;
r.act = act; r.act = act;
r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 }; r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
@@ -425,7 +425,9 @@ function U2pvis(act, btns) {
html.push(r.genrow(a, true).replace(/><td>/, "><td>b ")); html.push(r.genrow(a, true).replace(/><td>/, "><td>b "));
} }
} }
ebi('u2tab').tBodies[0].innerHTML = html.join('\n'); var el = ebi('u2tab');
el.tBodies[0].innerHTML = html.join('\n');
el.className = (uc.fsearch ? 'srch ' : 'up ') + r.act;
}; };
r.genrow = function (nfile, as_html) { r.genrow = function (nfile, as_html) {
@@ -624,11 +626,11 @@ function up2k_init(subtle) {
function setmsg(msg, type) { function setmsg(msg, type) {
if (msg !== undefined) { if (msg !== undefined) {
ebi('u2err').setAttribute('class', type); ebi('u2err').className = type;
ebi('u2err').innerHTML = msg; ebi('u2err').innerHTML = msg;
} }
else { else {
ebi('u2err').setAttribute('class', ''); ebi('u2err').className = '';
ebi('u2err').innerHTML = ''; ebi('u2err').innerHTML = '';
} }
if (msg == suggest_up2k) { if (msg == suggest_up2k) {
@@ -644,12 +646,6 @@ function up2k_init(subtle) {
return false; return false;
} }
ebi('u2nope').onclick = function (e) {
ev(e);
setmsg(suggest_up2k, 'msg');
goto('bup');
};
setmsg(suggest_up2k, 'msg'); setmsg(suggest_up2k, 'msg');
if (!String.prototype.format) { if (!String.prototype.format) {
@@ -671,8 +667,8 @@ function up2k_init(subtle) {
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, 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, 'flag_en', 'flag_en', false, apply_flag_cfg);
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false); bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
bcfg_bind(uc, 'turbo', 'u2turbo', false, draw_turbo, false); bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo, false);
bcfg_bind(uc, 'datechk', 'u2tdate', true, null, false); bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null, false);
var st = { var st = {
"files": [], "files": [],
@@ -711,7 +707,7 @@ function up2k_init(subtle) {
}); });
} }
var pvis = new U2pvis("bz", '#u2cards'), var pvis = new U2pvis("bz", '#u2cards', uc),
donut = new Donut(uc, st); donut = new Donut(uc, st);
var bobslice = null; var bobslice = null;
@@ -1173,7 +1169,7 @@ function up2k_init(subtle) {
var t = st.todo.handshake[0], var t = st.todo.handshake[0],
cd = t.cooldown; cd = t.cooldown;
if (cd && cd - Date.now() > 0) if (cd && cd > Date.now())
return false; return false;
// keepalive or verify // keepalive or verify
@@ -1370,6 +1366,14 @@ function up2k_init(subtle) {
return taskerd; return taskerd;
})(); })();
function chill(t) {
var now = Date.now();
if ((t.coolmul || 0) < 2 || now - t.cooldown < t.coolmul * 700)
t.coolmul = Math.min((t.coolmul || 0.5) * 2, 32);
t.cooldown = Math.max(t.cooldown || 1, Date.now() + t.coolmul * 1000);
}
///// /////
//// ////
/// hashing /// hashing
@@ -1468,7 +1472,6 @@ function up2k_init(subtle) {
min_filebuf = 1; min_filebuf = 1;
var td = Date.now() - t0; var td = Date.now() - t0;
if (td > 50) { if (td > 50) {
ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
min_filebuf = 32 * 1024 * 1024; min_filebuf = 32 * 1024 * 1024;
} }
} }
@@ -1756,8 +1759,12 @@ function up2k_init(subtle) {
pvis.move(t.n, 'ok'); pvis.move(t.n, 'ok');
} }
else t.t_uploaded = undefined; else {
if (t.t_uploaded)
chill(t);
t.t_uploaded = undefined;
}
tasker(); tasker();
} }
else { else {
@@ -1869,7 +1876,8 @@ function up2k_init(subtle) {
else { else {
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format( toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
xhr.status, t.name) + (txt || "no further information")); xhr.status, t.name) + (txt || "no further information"));
return;
chill(t);
} }
orz2(xhr); orz2(xhr);
} }
@@ -1920,6 +1928,7 @@ function up2k_init(subtle) {
// //
function onresize(e) { function onresize(e) {
// 10x faster than matchMedia('(min-width
var bar = ebi('ops'), var bar = ebi('ops'),
wpx = window.innerWidth, wpx = window.innerWidth,
fpx = parseInt(getComputedStyle(bar)['font-size']), fpx = parseInt(getComputedStyle(bar)['font-size']),
@@ -1929,22 +1938,19 @@ function up2k_init(subtle) {
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'), parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
btn = ebi('u2btn'); btn = ebi('u2btn');
//console.log([wpx, fpx, wem]);
if (btn.parentNode !== parent) { if (btn.parentNode !== parent) {
parent.appendChild(btn); parent.appendChild(btn);
ebi('u2conf').setAttribute('class', wide); ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
ebi('u2cards').setAttribute('class', wide);
ebi('u2etaw').setAttribute('class', wide);
} }
wide = write && wem > 78 ? 'ww' : wide; wide = write && wem > 78 ? 'ww' : wide;
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t'); parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
var its = [ebi('u2etaw'), ebi('u2cards')]; var its = [ebi('u2etaw'), ebi('u2cards')];
if (its[0].parentNode !== parent) { if (its[0].parentNode !== parent) {
ebi('u2conf').setAttribute('class', wide); ebi('u2conf').className = wide;
for (var a = 0; a < 2; a++) { for (var a = 0; a < 2; a++) {
parent.appendChild(its[a]); parent.appendChild(its[a]);
its[a].setAttribute('class', wide); its[a].className = wide;
} }
} }
} }
@@ -2018,9 +2024,12 @@ function up2k_init(subtle) {
html = ebi('u2foot').innerHTML, html = ebi('u2foot').innerHTML,
ohtml = html; ohtml = html;
if (uc.turbo && html.indexOf(msg) === -1) if (turbolvl || !uc.turbo)
msg = null;
if (msg && html.indexOf(msg) === -1)
html = html.replace(omsg, '') + msg; html = html.replace(omsg, '') + msg;
else if (!uc.turbo) else if (!msg)
html = html.replace(msgu, '').replace(msgs, ''); html = html.replace(msgu, '').replace(msgs, '');
if (html !== ohtml) if (html !== ohtml)
@@ -2057,13 +2066,15 @@ function up2k_init(subtle) {
try { try {
var ico = uc.fsearch ? '🔎' : '🚀', var ico = uc.fsearch ? '🔎' : '🚀',
desc = uc.fsearch ? 'Search' : 'Upload'; desc = uc.fsearch ? 'S E A R C H' : 'U P L O A D';
clmod(ebi('op_up2k'), 'srch', uc.fsearch); clmod(ebi('op_up2k'), 'srch', uc.fsearch);
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>'; ebi('u2bm').innerHTML = ico + '&nbsp; <sup>' + desc + '</sup>';
} }
catch (ex) { } catch (ex) { }
ebi('u2tab').className = (uc.fsearch ? 'srch ' : 'up ') + pvis.act;
draw_turbo(); draw_turbo();
onresize(); onresize();
} }

View File

@@ -89,6 +89,9 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
if ((msg + '').indexOf('l2d.js') !== -1) if ((msg + '').indexOf('l2d.js') !== -1)
return; // `t` undefined in tapEvent -> hitTestSimpleCustom return; // `t` undefined in tapEvent -> hitTestSimpleCustom
if (!/\.js($|\?)/.exec('' + url))
return; // chrome debugger
var ekey = url + '\n' + lineNo + '\n' + msg; var ekey = url + '\n' + lineNo + '\n' + msg;
if (ignexd[ekey] || crashed) if (ignexd[ekey] || crashed)
return; return;
@@ -327,11 +330,21 @@ function clgot(el, cls) {
if (el.classList) if (el.classList)
return el.classList.contains(cls); return el.classList.contains(cls);
var lst = (el.getAttribute('class') + '').split(/ /g); var lst = (el.className + '').split(/ /g);
return has(lst, cls); return has(lst, cls);
} }
var ANIM = true;
if (window.matchMedia) {
var mq = window.matchMedia('(prefers-reduced-motion: reduce)');
mq.onchange = function () {
ANIM = !mq.matches;
};
ANIM = !mq.matches;
}
function showsort(tab) { function showsort(tab) {
var v, vn, v1, v2, th = tab.tHead, var v, vn, v1, v2, th = tab.tHead,
sopts = jread('fsort', [["href", 1, ""]]); sopts = jread('fsort', [["href", 1, ""]]);
@@ -872,7 +885,7 @@ var tt = (function () {
}; };
r.getmsg = function (el) { r.getmsg = function (el) {
if (QS('body.bbox-open')) if (IPHONE && QS('body.bbox-open'))
return; return;
var cfg = sread('tooltips'); var cfg = sread('tooltips');

View File

@@ -4,7 +4,7 @@ ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
ver_hashwasm=4.9.0 \ ver_hashwasm=4.9.0 \
ver_marked=4.0.12 \ ver_marked=4.0.12 \
ver_mde=2.16.1 \ ver_mde=2.16.1 \
ver_codemirror=5.65.2 \ ver_codemirror=5.65.3 \
ver_fontawesome=5.13.0 \ ver_fontawesome=5.13.0 \
ver_zopfli=1.0.3 ver_zopfli=1.0.3

View File

@@ -342,14 +342,15 @@ def get_payload():
def utime(top): def utime(top):
# avoid cleaners
i = 0 i = 0
files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df] files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
while WINDOWS: while WINDOWS or os.path.exists("/etc/systemd"):
t = int(time.time()) t = int(time.time())
if i: if i:
msg("utime {}, {}".format(i, t)) msg("utime {}, {}".format(i, t))
for f in files: for f in [top] + files:
os.utime(f, (t, t)) os.utime(f, (t, t))
i += 1 i += 1
@@ -374,16 +375,6 @@ def run(tmp, j2, ftp):
msg("sfxdir:", tmp) msg("sfxdir:", tmp)
msg() msg()
# block systemd-tmpfiles-clean.timer
try:
import fcntl
fd = os.open(tmp, os.O_RDONLY)
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except Exception as ex:
if not WINDOWS:
msg("\033[31mflock:{!r}\033[0m".format(ex))
t = threading.Thread(target=utime, args=(tmp,)) t = threading.Thread(target=utime, args=(tmp,))
t.daemon = True t.daemon = True
t.start() t.start()

View File

@@ -114,9 +114,10 @@ args = {
"install_requires": ["jinja2"], "install_requires": ["jinja2"],
"extras_require": { "extras_require": {
"thumbnails": ["Pillow"], "thumbnails": ["Pillow"],
"thumbnails2": ["pyvips"],
"audiotags": ["mutagen"], "audiotags": ["mutagen"],
"ftpd": ["pyftpdlib"], "ftpd": ["pyftpdlib"],
"ftps": ["pyopenssl"], "ftps": ["pyftpdlib", "pyopenssl"],
}, },
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]}, "entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
"scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"], "scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"],

View File

@@ -38,6 +38,9 @@ class Cfg(Namespace):
no_mv=False, no_mv=False,
no_del=False, no_del=False,
no_zip=False, no_zip=False,
no_thumb=False,
no_athumb=False,
no_vthumb=False,
no_voldump=True, no_voldump=True,
no_scandir=False, no_scandir=False,
no_sendfile=True, no_sendfile=True,
@@ -53,6 +56,9 @@ class Cfg(Namespace):
textfiles="", textfiles="",
doctitle="", doctitle="",
html_head="", html_head="",
theme=0,
themes=0,
turbo=0,
hist=None, hist=None,
no_idx=None, no_idx=None,
no_hash=None, no_hash=None,

View File

@@ -17,7 +17,7 @@ from copyparty import util
class Cfg(Namespace): class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None): def __init__(self, a=None, v=None, c=None):
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots" 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()} ex = {k: False for k in ex.split()}
ex2 = { ex2 = {
"mtp": [], "mtp": [],
@@ -36,6 +36,9 @@ class Cfg(Namespace):
"rsp_slp": 0, "rsp_slp": 0,
"s_wr_slp": 0, "s_wr_slp": 0,
"s_wr_sz": 512 * 1024, "s_wr_sz": 512 * 1024,
"theme": 0,
"themes": 0,
"turbo": 0,
} }
ex.update(ex2) ex.update(ex2)
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex) super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)