mirror of
https://github.com/9001/copyparty.git
synced 2025-11-05 06:13:20 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc2ea20959 | ||
|
|
8eaea2bd17 | ||
|
|
58e559918f | ||
|
|
f38a3fca5b | ||
|
|
1ea145b384 | ||
|
|
0d9567575a | ||
|
|
e82f176289 | ||
|
|
d4b51c040e | ||
|
|
125d0efbd8 | ||
|
|
3215afc504 | ||
|
|
c73ff3ce1b | ||
|
|
f9c159a051 | ||
|
|
2ab1325c90 | ||
|
|
5b0f7ff506 | ||
|
|
9269bc84f2 | ||
|
|
4e8b651e18 | ||
|
|
65b4f79534 | ||
|
|
5dd43dbc45 | ||
|
|
5f73074c7e | ||
|
|
f5d6ba27b2 | ||
|
|
73fa70b41f | ||
|
|
2a1cda42e7 | ||
|
|
1bd7e31466 | ||
|
|
eb49e1fb4a | ||
|
|
9838c2f0ce | ||
|
|
6041df8370 | ||
|
|
2933dce3ef | ||
|
|
dab377d37b | ||
|
|
f35e41baf1 | ||
|
|
c4083a2942 | ||
|
|
36c20bbe53 | ||
|
|
e34634f5af | ||
|
|
cba9e5b669 | ||
|
|
1f3c46a6b0 | ||
|
|
799a5ffa47 | ||
|
|
b000707c10 | ||
|
|
feba4de1d6 | ||
|
|
951fdb27ca | ||
|
|
9697fb3d84 | ||
|
|
2dbed4500a | ||
|
|
fd9d0e433d | ||
|
|
f096f3ef81 | ||
|
|
cc4a063695 | ||
|
|
b64cabc3c9 | ||
|
|
3dd460717c | ||
|
|
bf658a522b | ||
|
|
e9be7e712d | ||
|
|
e40cd2a809 | ||
|
|
dbabeb9692 | ||
|
|
8dd37d76b0 | ||
|
|
fd475aa358 | ||
|
|
f0988c0e32 | ||
|
|
0632f09bff | ||
|
|
ba599aaca0 | ||
|
|
ff05919e89 | ||
|
|
52e63fa101 | ||
|
|
96ceccd12a | ||
|
|
87994fe006 | ||
|
|
fa12c81a03 | ||
|
|
344ce63455 |
43
README.md
43
README.md
@@ -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)
|
||||
* [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
|
||||
* [themes](#themes)
|
||||
* [complete examples](#complete-examples)
|
||||
* [browser support](#browser-support) - TLDR: yes
|
||||
* [client examples](#client-examples) - interact with copyparty using non-browser clients
|
||||
@@ -174,7 +175,7 @@ feature summary
|
||||
* ☑ image gallery with webm player
|
||||
* ☑ textfile browser with syntax hilighting
|
||||
* ☑ [thumbnails](#thumbnails)
|
||||
* ☑ ...of images using Pillow
|
||||
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
||||
* ☑ ...of videos using FFmpeg
|
||||
* ☑ ...of audio (spectrograms) using FFmpeg
|
||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||
@@ -247,6 +248,8 @@ some improvement ideas
|
||||
|
||||
## 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)
|
||||
* *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...
|
||||
@@ -403,7 +406,9 @@ press `g` to toggle grid-view instead of the file listing, and `t` toggles icon
|
||||
|
||||

|
||||
|
||||
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`)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* 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)
|
||||
|
||||
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`
|
||||
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler)
|
||||
* **AVIF pictures:** `pillow-avif-plugin`
|
||||
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
|
||||
* **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
|
||||
@@ -1173,7 +1204,7 @@ python3 -m venv .venv
|
||||
pip install jinja2 # mandatory
|
||||
pip install mutagen # audio metadata
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ save one of these as `.epilogue.html` inside a folder to customize it:
|
||||
## example browser-css
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
# change '/mnt::rw' to another location or permission-set
|
||||
# remove '-p 80,443,3923' to only listen on port 3923
|
||||
# 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
|
||||
# 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
|
||||
# following line to enable buffering (slightly better performance):
|
||||
# Environment=PYTHONUNBUFFERED=x
|
||||
#
|
||||
# keep ExecStartPre before ExecStart, at least on rhel8
|
||||
|
||||
[Unit]
|
||||
Description=copyparty file server
|
||||
@@ -32,7 +36,7 @@ SyslogIdentifier=copyparty
|
||||
Environment=PYTHONUNBUFFERED=x
|
||||
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'
|
||||
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]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -291,9 +291,9 @@ def run_argparse(argv, formatter):
|
||||
dedent(
|
||||
"""
|
||||
-a takes username:password,
|
||||
-v takes src:dst:perm1:perm2:permN:volflag1:volflag2:volflagN:...
|
||||
where "perm" is "permissions,username1,username2,..."
|
||||
and "volflag" is config flags to set on this volume
|
||||
-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:...
|
||||
* "\033[33mperm\033[0m" is "permissions,username1,username2,..."
|
||||
* "\033[32mvolflag\033[0m" is config flags to set on this volume
|
||||
|
||||
list of permissions:
|
||||
"r" (read): list folder contents, download files
|
||||
@@ -365,6 +365,17 @@ def run_argparse(argv, formatter):
|
||||
generate ".bpm" tags from uploads (f = overwrite tags)
|
||||
\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[36mfk=8\033[35m generates per-file accesskeys,
|
||||
which will then be required at the "g" permission
|
||||
@@ -373,7 +384,7 @@ def run_argparse(argv, formatter):
|
||||
],
|
||||
[
|
||||
"urlform",
|
||||
"",
|
||||
"how to handle url-form POSTs",
|
||||
dedent(
|
||||
"""
|
||||
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("-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("-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("-ed", action="store_true", help="enable ?dots")
|
||||
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins")
|
||||
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; examples [.::r], [/mnt/nas/music:/music:r:aed]")
|
||||
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 -- 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("--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 = ap.add_argument_group('upload options')
|
||||
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
|
||||
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
|
||||
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="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("--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("--no-symlink", action="store_true", help="duplicate file contents instead")
|
||||
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("--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("--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.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("--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-slp", metavar="SEC", type=float, default=0, help="socket write delay in seconds")
|
||||
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="response 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="debug: response delay in seconds")
|
||||
|
||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
||||
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 -- 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("--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-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.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 = ap.add_argument_group('opt-outs')
|
||||
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
||||
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows")
|
||||
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 (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-mv", action="store_true", help="disable move/rename operations")
|
||||
ap2.add_argument("-nih", action="store_true", help="no info hostname")
|
||||
ap2.add_argument("-nid", action="store_true", help="no info disk-usage")
|
||||
ap2.add_argument("-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 -- don't show in UI")
|
||||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (lifetime volflag)")
|
||||
|
||||
ap2 = ap.add_argument_group('safety options')
|
||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
||||
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt")
|
||||
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt")
|
||||
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; 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; 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-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-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("--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 = 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("-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("--log-conn", action="store_true", help="print tcp-server msgs")
|
||||
ap2.add_argument("--log-htp", action="store_true", help="print http-server threadpool scaling")
|
||||
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
||||
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("--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-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-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-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-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-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||
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-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/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.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.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
||||
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplocation")
|
||||
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets -e2d")
|
||||
ap2.add_argument("-e2dsa", action="store_true", help="scans all folders on startup; sets -e2ds")
|
||||
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
|
||||
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans")
|
||||
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans")
|
||||
ap2.add_argument("--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-hits", metavar="N", type=int, default=1000, help="max search results")
|
||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline -- terminate searches running for more than SEC seconds")
|
||||
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
||||
|
||||
ap2 = ap.add_argument_group('metadata db options')
|
||||
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
|
||||
ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
|
||||
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
|
||||
ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead")
|
||||
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
||||
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="scan existing files on startup; sets -e2t")
|
||||
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; will catch more tags")
|
||||
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("-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.)",
|
||||
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
|
||||
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
||||
default=".vq,.aq,vc,ac,res,.fps")
|
||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
||||
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
|
||||
|
||||
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("--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")
|
||||
@@ -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 = ap.add_argument_group('debug options')
|
||||
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
||||
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
|
||||
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
|
||||
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; instead using listdir + stat on each file")
|
||||
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("--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")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 2, 2)
|
||||
VERSION = (1, 2, 8)
|
||||
CODENAME = "ftp btw"
|
||||
BUILD_DT = (2022, 3, 20)
|
||||
BUILD_DT = (2022, 4, 30)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -11,12 +11,13 @@ import hashlib
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
from .__init__ import WINDOWS
|
||||
from .__init__ import ANYWIN, WINDOWS
|
||||
from .util import (
|
||||
IMPLICATIONS,
|
||||
META_NOBOTS,
|
||||
uncyg,
|
||||
undot,
|
||||
relchk,
|
||||
unhumanize,
|
||||
absreal,
|
||||
Pebkac,
|
||||
@@ -335,6 +336,12 @@ class VFS(object):
|
||||
):
|
||||
# type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str]
|
||||
"""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)
|
||||
c = vn.axs
|
||||
|
||||
@@ -741,10 +748,10 @@ class AuthSrv(object):
|
||||
unames = ["*"] + list(acct.keys())
|
||||
umap = {x: [] for x 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)
|
||||
if usr in axs or "*" in axs:
|
||||
umap[usr].append(mp)
|
||||
umap[usr].append(vp)
|
||||
umap[usr].sort()
|
||||
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])
|
||||
|
||||
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():
|
||||
fk = vol.flags.get("fk")
|
||||
if fk:
|
||||
|
||||
@@ -121,6 +121,12 @@ class HttpCli(object):
|
||||
|
||||
try:
|
||||
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:
|
||||
msg = " ]\n#[ ".join(headerlines)
|
||||
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
|
||||
@@ -137,12 +143,6 @@ class HttpCli(object):
|
||||
if 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()
|
||||
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)
|
||||
@@ -211,6 +211,14 @@ class HttpCli(object):
|
||||
self.cookies = cookies
|
||||
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
|
||||
ba = self.headers.get("authorization")
|
||||
if ba:
|
||||
@@ -344,8 +352,11 @@ class HttpCli(object):
|
||||
return body
|
||||
|
||||
def loud_reply(self, body, *args, **kwargs):
|
||||
if not kwargs.get("mime"):
|
||||
kwargs["mime"] = "text/plain; charset=utf-8"
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -864,8 +875,9 @@ class HttpCli(object):
|
||||
else:
|
||||
# search by query params
|
||||
q = body["q"]
|
||||
self.log("qj: " + q)
|
||||
hits, taglist = idx.search(vols, q)
|
||||
n = body.get("n", self.args.srch_hits)
|
||||
self.log("qj: {} |{}|".format(q, n))
|
||||
hits, taglist = idx.search(vols, q, n)
|
||||
msg = len(hits)
|
||||
|
||||
idx.p_end = time.time()
|
||||
@@ -1046,6 +1058,7 @@ class HttpCli(object):
|
||||
raise Pebkac(500, min_ex())
|
||||
|
||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||
self.out_headers["X-New-Dir"] = quotep(sanitized)
|
||||
self.redirect(vpath)
|
||||
return True
|
||||
|
||||
@@ -2097,9 +2110,7 @@ class HttpCli(object):
|
||||
|
||||
thp = None
|
||||
if self.thumbcli:
|
||||
thp = self.thumbcli.get(
|
||||
dbv.realpath, vrem, int(st.st_mtime), th_fmt
|
||||
)
|
||||
thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
|
||||
|
||||
if thp:
|
||||
return self.tx_file(thp)
|
||||
@@ -2149,12 +2160,11 @@ class HttpCli(object):
|
||||
free = humansize(sv.f_frsize * sv.f_bfree, True)
|
||||
total = humansize(sv.f_frsize * sv.f_blocks, True)
|
||||
|
||||
srv_info.append(free + " free")
|
||||
srv_info.append(total)
|
||||
srv_info.append("{} free of {}".format(free, total))
|
||||
except:
|
||||
pass
|
||||
|
||||
srv_info = "</span> /// <span>".join(srv_info)
|
||||
srv_info = "</span> // <span>".join(srv_info)
|
||||
|
||||
perms = []
|
||||
if self.can_read:
|
||||
@@ -2175,6 +2185,7 @@ class HttpCli(object):
|
||||
tpl = "browser"
|
||||
if "b" in self.uparam:
|
||||
tpl = "browser2"
|
||||
is_js = False
|
||||
|
||||
logues = ["", ""]
|
||||
if not self.args.no_logues:
|
||||
@@ -2226,6 +2237,9 @@ class HttpCli(object):
|
||||
"readme": readme,
|
||||
"title": html_escape(self.vpath, crlf=True),
|
||||
"srv_info": srv_info,
|
||||
"dtheme": self.args.theme,
|
||||
"themes": self.args.themes,
|
||||
"turbolvl": self.args.turbo,
|
||||
}
|
||||
if not self.can_read:
|
||||
if is_ls:
|
||||
|
||||
@@ -17,7 +17,7 @@ from .util import Unrecv
|
||||
from .httpcli import HttpCli
|
||||
from .u2idx import U2idx
|
||||
from .th_cli import ThumbCli
|
||||
from .th_srv import HAVE_PIL
|
||||
from .th_srv import HAVE_PIL, HAVE_VIPS
|
||||
from .ico import Ico
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class HttpConn(object):
|
||||
self.cert_path = hsrv.cert_path
|
||||
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.ico = Ico(self.args)
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ class HttpSrv(object):
|
||||
self.cb_ts = 0
|
||||
self.cb_v = 0
|
||||
|
||||
try:
|
||||
x = self.broker.put(True, "thumbsrv.getcfg")
|
||||
self.th_cfg = x.get()
|
||||
except:
|
||||
pass
|
||||
|
||||
env = jinja2.Environment()
|
||||
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
||||
self.j2 = {
|
||||
|
||||
@@ -477,13 +477,13 @@ class MTag(object):
|
||||
env["PYTHONPATH"] = pypath
|
||||
|
||||
ret = {}
|
||||
for tagname, mp in parsers.items():
|
||||
for tagname, parser in parsers.items():
|
||||
try:
|
||||
cmd = [mp.bin, abspath]
|
||||
if mp.bin.endswith(".py"):
|
||||
cmd = [parser.bin, abspath]
|
||||
if parser.bin.endswith(".py"):
|
||||
cmd = [sys.executable] + cmd
|
||||
|
||||
args = {"env": env, "timeout": mp.timeout}
|
||||
args = {"env": env, "timeout": parser.timeout}
|
||||
|
||||
if WINDOWS:
|
||||
args["creationflags"] = 0x4000
|
||||
|
||||
@@ -5,7 +5,7 @@ import tarfile
|
||||
import threading
|
||||
|
||||
from .sutil import errdesc
|
||||
from .util import Queue, fsenc
|
||||
from .util import Queue, fsenc, min_ex
|
||||
from .bos import bos
|
||||
|
||||
|
||||
@@ -88,8 +88,9 @@ class StreamTar(object):
|
||||
|
||||
try:
|
||||
self.ser(f)
|
||||
except Exception as ex:
|
||||
errors.append([f["vp"], repr(ex)])
|
||||
except Exception:
|
||||
ex = min_ex(5, True).replace("\n", "\n-- ")
|
||||
errors.append([f["vp"], ex])
|
||||
|
||||
if errors:
|
||||
self.errf, txt = errdesc(errors)
|
||||
|
||||
@@ -17,7 +17,7 @@ from .util import mp, start_log_thrs, start_stackmon, min_ex, ansi_re
|
||||
from .authsrv import AuthSrv
|
||||
from .tcpsrv import TcpSrv
|
||||
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
|
||||
|
||||
|
||||
@@ -70,6 +70,13 @@ class SvcHub(object):
|
||||
|
||||
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
|
||||
self.asrv = AuthSrv(self.args, self.log)
|
||||
if args.ls:
|
||||
@@ -78,20 +85,30 @@ class SvcHub(object):
|
||||
self.tcpsrv = TcpSrv(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
|
||||
if not args.no_thumb:
|
||||
if HAVE_PIL:
|
||||
if not HAVE_WEBP:
|
||||
args.th_no_webp = True
|
||||
msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
|
||||
m = "decoder preference: {}".format(", ".join(self.args.th_dec))
|
||||
self.log("thumb", m)
|
||||
|
||||
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)
|
||||
else:
|
||||
msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n"
|
||||
self.log(
|
||||
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3
|
||||
)
|
||||
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"
|
||||
msg = msg.format(" " * 37, os.path.basename(sys.executable))
|
||||
self.log("thumb", msg, c=3)
|
||||
|
||||
if not args.no_acode and args.no_thumb:
|
||||
msg = "setting --no-acode because --no-thumb (sorry)"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# coding: utf-8
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
import zlib
|
||||
from datetime import datetime
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -36,7 +35,10 @@ def unixtime2dos(ts):
|
||||
|
||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||
bt = (th << 11) + (tm << 5) + ts // 2
|
||||
try:
|
||||
return spack(b"<HH", bt, bd)
|
||||
except:
|
||||
return b"\x00\x00\x21\x00"
|
||||
|
||||
|
||||
def gen_fdesc(sz, crc32, z64):
|
||||
@@ -244,8 +246,9 @@ class StreamZip(object):
|
||||
try:
|
||||
for x in self.ser(f):
|
||||
yield x
|
||||
except Exception as ex:
|
||||
errors.append([f["vp"], repr(ex)])
|
||||
except Exception:
|
||||
ex = min_ex(5, True).replace("\n", "\n-- ")
|
||||
errors.append([f["vp"], ex])
|
||||
|
||||
if errors:
|
||||
errf, txt = errdesc(errors)
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
|
||||
import os
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -18,30 +18,53 @@ class ThumbCli(object):
|
||||
# cache on both sides for less broker spam
|
||||
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):
|
||||
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()
|
||||
if ext not in THUMBABLE:
|
||||
if ext not in self.thumbable or "dthumb" in dbv.flags:
|
||||
return None
|
||||
|
||||
is_vid = ext in FMT_FFV
|
||||
if is_vid and self.args.no_vthumb:
|
||||
is_vid = ext in self.fmt_ffv
|
||||
if is_vid and "dvthumb" in dbv.flags:
|
||||
return None
|
||||
|
||||
want_opus = fmt in ("opus", "caf")
|
||||
is_au = ext in FMT_FFA
|
||||
is_au = ext in self.fmt_ffa
|
||||
if is_au:
|
||||
if want_opus:
|
||||
if self.args.no_acode:
|
||||
return None
|
||||
else:
|
||||
if self.args.no_athumb:
|
||||
if "dathumb" in dbv.flags:
|
||||
return None
|
||||
elif want_opus:
|
||||
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"]:
|
||||
return os.path.join(ptop, rem)
|
||||
|
||||
@@ -49,7 +72,11 @@ class ThumbCli(object):
|
||||
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"
|
||||
|
||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||
@@ -58,13 +85,21 @@ class ThumbCli(object):
|
||||
return None
|
||||
|
||||
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
|
||||
abort = False
|
||||
for tp in tpaths:
|
||||
try:
|
||||
st = bos.stat(tpath)
|
||||
st = bos.stat(tp)
|
||||
if st.st_size:
|
||||
ret = tpath
|
||||
ret = tpath = tp
|
||||
fmt = ret.rsplit(".")[1]
|
||||
else:
|
||||
return None
|
||||
abort = True
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -80,5 +115,8 @@ class ThumbCli(object):
|
||||
|
||||
return ret
|
||||
|
||||
if abort:
|
||||
return None
|
||||
|
||||
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
|
||||
return x.get()
|
||||
|
||||
@@ -47,31 +47,12 @@ try:
|
||||
except:
|
||||
pass
|
||||
|
||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||
# ffmpeg -formats
|
||||
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"
|
||||
try:
|
||||
import pyvips
|
||||
|
||||
if HAVE_HEIF:
|
||||
FMT_PIL += " heif heifs heic heics"
|
||||
|
||||
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)
|
||||
HAVE_VIPS = True
|
||||
except:
|
||||
HAVE_VIPS = False
|
||||
|
||||
|
||||
def thumb_path(histpath, rem, mtime, fmt):
|
||||
@@ -141,6 +122,37 @@ class ThumbSrv(object):
|
||||
t.daemon = True
|
||||
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):
|
||||
self.log_func("thumb", msg, c)
|
||||
|
||||
@@ -201,6 +213,16 @@ class ThumbSrv(object):
|
||||
|
||||
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):
|
||||
while not self.stopping:
|
||||
task = self.q.get()
|
||||
@@ -211,11 +233,16 @@ class ThumbSrv(object):
|
||||
ext = abspath.split(".")[-1].lower()
|
||||
fun = None
|
||||
if not bos.path.exists(tpath):
|
||||
if ext in FMT_PIL:
|
||||
for lib in self.args.th_dec:
|
||||
if fun:
|
||||
break
|
||||
elif lib == "pil" and ext in self.fmt_pil:
|
||||
fun = self.conv_pil
|
||||
elif ext in FMT_FFV:
|
||||
elif lib == "vips" and ext in self.fmt_vips:
|
||||
fun = self.conv_vips
|
||||
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
|
||||
fun = self.conv_ffmpeg
|
||||
elif ext in FMT_FFA:
|
||||
elif lib == "ff" and ext in self.fmt_ffa:
|
||||
if tpath.endswith(".opus") or tpath.endswith(".caf"):
|
||||
fun = self.conv_opus
|
||||
else:
|
||||
@@ -296,11 +323,29 @@ class ThumbSrv(object):
|
||||
|
||||
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):
|
||||
ret, _ = ffprobe(abspath)
|
||||
|
||||
ext = abspath.rsplit(".")[-1]
|
||||
if ext in ["h264", "h265"]:
|
||||
ext = abspath.rsplit(".")[-1].lower()
|
||||
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
|
||||
seek = []
|
||||
else:
|
||||
dur = ret[".dur"][1] if ".dur" in ret else 4
|
||||
@@ -350,10 +395,30 @@ class ThumbSrv(object):
|
||||
def _run_ff(self, cmd):
|
||||
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
|
||||
if ret != 0:
|
||||
if not ret:
|
||||
return
|
||||
|
||||
c = "1;30"
|
||||
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="1;30")
|
||||
self.log(m, c=c)
|
||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||
|
||||
def conv_spec(self, abspath, tpath):
|
||||
|
||||
@@ -21,6 +21,12 @@ except:
|
||||
HAVE_SQLITE3 = False
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class U2idx(object):
|
||||
def __init__(self, conn):
|
||||
self.log_func = conn.log_func
|
||||
@@ -55,7 +61,7 @@ class U2idx(object):
|
||||
uv = [wark[:16], wark]
|
||||
|
||||
try:
|
||||
return self.run_query(vols, uq, uv, True, False)[0]
|
||||
return self.run_query(vols, uq, uv, True, False, 99999)[0]
|
||||
except:
|
||||
raise Pebkac(500, min_ex())
|
||||
|
||||
@@ -76,11 +82,26 @@ class U2idx(object):
|
||||
if not bos.path.exists(db_path):
|
||||
return None
|
||||
|
||||
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
|
||||
return cur
|
||||
|
||||
def search(self, vols, uq):
|
||||
def search(self, vols, uq, lim):
|
||||
"""search by query params"""
|
||||
if not HAVE_SQLITE3:
|
||||
return []
|
||||
@@ -222,11 +243,11 @@ class U2idx(object):
|
||||
q += " lower({}) {} ? ) ".format(field, oper)
|
||||
|
||||
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:
|
||||
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 = []
|
||||
self.active_id = "{:.6f}_{}".format(
|
||||
time.time(), threading.current_thread().ident
|
||||
@@ -255,7 +276,7 @@ class U2idx(object):
|
||||
self.log("qs: {!r} {!r}".format(uq, uv))
|
||||
|
||||
ret = []
|
||||
lim = int(self.args.srch_hits)
|
||||
lim = min(lim, int(self.args.srch_hits))
|
||||
taglist = {}
|
||||
for (vtop, ptop, flags) in vols:
|
||||
cur = self.get_cur(ptop)
|
||||
@@ -278,7 +299,7 @@ class U2idx(object):
|
||||
for hit in c:
|
||||
w, ts, sz, rd, fn, ip, at = hit[:7]
|
||||
lim -= 1
|
||||
if lim <= 0:
|
||||
if lim < 0:
|
||||
break
|
||||
|
||||
if rd.startswith("//") or fn.startswith("//"):
|
||||
|
||||
@@ -95,7 +95,7 @@ class Up2k(object):
|
||||
|
||||
if ANYWIN:
|
||||
# usually fails to set lastmod too quickly
|
||||
self.lastmod_q = Queue()
|
||||
self.lastmod_q = []
|
||||
thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod")
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
@@ -470,9 +470,11 @@ class Up2k(object):
|
||||
ft = "\033[0;32m{}{:.0}"
|
||||
ff = "\033[0;35m{}{:.0}"
|
||||
fv = "\033[0;36m{}:\033[1;30m{}"
|
||||
fx = set(("html_head",))
|
||||
a = [
|
||||
(ft if v is True else ff if v is False else fv).format(k, str(v))
|
||||
for k, v in flags.items()
|
||||
if k not in fx
|
||||
]
|
||||
if a:
|
||||
vpath = "?"
|
||||
@@ -581,9 +583,11 @@ class Up2k(object):
|
||||
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
||||
histpath = self.asrv.vfs.histtab[top]
|
||||
ret = 0
|
||||
seen_files = {}
|
||||
seen_files = {} # != inames; files-only for dropcheck
|
||||
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)
|
||||
if rei and rei.search(abspath):
|
||||
continue
|
||||
@@ -594,6 +598,9 @@ class Up2k(object):
|
||||
if stat.S_ISDIR(inf.st_mode):
|
||||
if abspath in excl or abspath == histpath:
|
||||
continue
|
||||
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
|
||||
# abandoned or foreign, skip
|
||||
continue
|
||||
# self.log(" dir: {}".format(abspath))
|
||||
try:
|
||||
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)
|
||||
@@ -607,6 +614,17 @@ class Up2k(object):
|
||||
if WINDOWS:
|
||||
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]
|
||||
sql = "select w, mt, sz from up where rd = ? and fn = ?"
|
||||
try:
|
||||
@@ -776,6 +794,7 @@ class Up2k(object):
|
||||
if self.mtag.prefer_mt and self.args.mtag_mt > 1:
|
||||
mpool = self._start_mpool()
|
||||
|
||||
# TODO blocks writes to registry cursor; do chunks instead
|
||||
conn = sqlite3.connect(db_path, timeout=15)
|
||||
cur = conn.cursor()
|
||||
c2 = conn.cursor()
|
||||
@@ -801,7 +820,7 @@ class Up2k(object):
|
||||
n_tags = self._tag_file(c3, *args)
|
||||
else:
|
||||
mpool.put(["mtag"] + args)
|
||||
with self.mutex:
|
||||
# not registry cursor; do not self.mutex:
|
||||
n_tags = len(self._flush_mpool(c3))
|
||||
|
||||
n_add += n_tags
|
||||
@@ -825,9 +844,6 @@ class Up2k(object):
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
with self.mutex:
|
||||
gcur.connection.commit()
|
||||
|
||||
return n_add, n_rm, True
|
||||
|
||||
def _flush_mpool(self, wcur):
|
||||
@@ -1106,7 +1122,8 @@ class Up2k(object):
|
||||
return ret
|
||||
|
||||
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)
|
||||
|
||||
def _open_db(self, db_path):
|
||||
@@ -1235,6 +1252,11 @@ class Up2k(object):
|
||||
wark = self._get_wark(cj)
|
||||
now = time.time()
|
||||
job = None
|
||||
try:
|
||||
dev = bos.stat(os.path.join(cj["ptop"], cj["prel"])).st_dev
|
||||
except:
|
||||
dev = 0
|
||||
|
||||
with self.mutex:
|
||||
cur = self.cur.get(cj["ptop"])
|
||||
reg = self.registry[cj["ptop"]]
|
||||
@@ -1246,24 +1268,22 @@ class Up2k(object):
|
||||
q = r"select * from up where substr(w,1,16) = ? and w = ?"
|
||||
argv = (wark[:16], wark)
|
||||
|
||||
alts = []
|
||||
cur = cur.execute(q, argv)
|
||||
for _, dtime, dsize, dp_dir, dp_fn, ip, at in cur:
|
||||
if dp_dir.startswith("//") or dp_fn.startswith("//"):
|
||||
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
|
||||
|
||||
dp_abs = "/".join([cj["ptop"], dp_dir, dp_fn])
|
||||
# relying on this to fail on broken symlinks
|
||||
try:
|
||||
sz = bos.path.getsize(dp_abs)
|
||||
except:
|
||||
sz = 0
|
||||
|
||||
if sz:
|
||||
# self.log("--- " + wark + " " + dp_abs + " found file", 4)
|
||||
job = {
|
||||
j = {
|
||||
"name": dp_fn,
|
||||
"prel": dp_dir,
|
||||
"vtop": cj["vtop"],
|
||||
@@ -1276,7 +1296,14 @@ class Up2k(object):
|
||||
"need": [],
|
||||
"busy": {},
|
||||
}
|
||||
score = (
|
||||
(3 if st.st_dev == dev else 0)
|
||||
+ (2 if dp_dir == cj["prel"] else 0)
|
||||
+ (1 if dp_fn == cj["name"] else 0)
|
||||
)
|
||||
alts.append([score, -len(alts), j])
|
||||
|
||||
job = sorted(alts, reverse=True)[0][2] if alts else None
|
||||
if job and wark in reg:
|
||||
# self.log("pop " + wark + " " + job["name"] + " handle_json db", 4)
|
||||
del reg[wark]
|
||||
@@ -1417,14 +1444,14 @@ class Up2k(object):
|
||||
|
||||
linked = False
|
||||
try:
|
||||
if self.args.no_symlink:
|
||||
if self.args.no_dedup:
|
||||
raise Exception("disabled in config")
|
||||
|
||||
lsrc = src
|
||||
ldst = dst
|
||||
fs1 = bos.stat(os.path.dirname(src)).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
|
||||
raise OSError()
|
||||
elif fs1 == fs2:
|
||||
@@ -1445,16 +1472,27 @@ class Up2k(object):
|
||||
lsrc = nsrc[nc:]
|
||||
hops = len(ndst[nc:]) - 1
|
||||
lsrc = "../" * hops + "/".join(lsrc)
|
||||
|
||||
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:
|
||||
self.log("cannot symlink; creating copy: " + repr(ex))
|
||||
self.log("cannot link; creating copy: " + repr(ex))
|
||||
shutil.copy2(fsenc(src), fsenc(dst))
|
||||
|
||||
if lmod and (not linked or SYMTIME):
|
||||
times = (int(time.time()), int(lmod))
|
||||
if ANYWIN:
|
||||
self.lastmod_q.put([dst, 0, times])
|
||||
self.lastmod_q.append([dst, 0, times])
|
||||
else:
|
||||
bos.utime(dst, times, False)
|
||||
|
||||
@@ -1548,9 +1586,15 @@ class Up2k(object):
|
||||
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
|
||||
atomic_move(src, dst)
|
||||
|
||||
times = (int(time.time()), int(job["lmod"]))
|
||||
if ANYWIN:
|
||||
a = [dst, job["size"], (int(time.time()), int(job["lmod"]))]
|
||||
self.lastmod_q.put(a)
|
||||
a = [dst, job["size"], times]
|
||||
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.get("at") or time.time()]
|
||||
@@ -1649,12 +1693,12 @@ class Up2k(object):
|
||||
vn, rem = vn.get_dbv(rem)
|
||||
unpost = False
|
||||
except:
|
||||
# unpost with missing permissions? try read+write and verify with db
|
||||
# unpost with missing permissions? verify with db
|
||||
if not self.args.unpost:
|
||||
raise Pebkac(400, "the unpost feature is disabled in server config")
|
||||
|
||||
unpost = True
|
||||
permsets = [[True, True]]
|
||||
permsets = [[False, True]]
|
||||
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
||||
vn, rem = vn.get_dbv(rem)
|
||||
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
|
||||
@@ -2038,9 +2082,8 @@ class Up2k(object):
|
||||
|
||||
def _lastmodder(self):
|
||||
while True:
|
||||
ready = []
|
||||
while not self.lastmod_q.empty():
|
||||
ready.append(self.lastmod_q.get())
|
||||
ready = self.lastmod_q
|
||||
self.lastmod_q = []
|
||||
|
||||
# self.log("lmod: got {}".format(len(ready)))
|
||||
time.sleep(5)
|
||||
@@ -2049,7 +2092,8 @@ class Up2k(object):
|
||||
try:
|
||||
bos.utime(path, times, False)
|
||||
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:
|
||||
try:
|
||||
|
||||
@@ -67,7 +67,7 @@ if WINDOWS and PY2:
|
||||
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"
|
||||
|
||||
@@ -485,13 +485,13 @@ def vol_san(vols, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def min_ex():
|
||||
def min_ex(max_lines=8, reverse=False):
|
||||
et, ev, tb = sys.exc_info()
|
||||
tb = traceback.extract_tb(tb)
|
||||
fmt = "{} @ {} <{}>: {}"
|
||||
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
|
||||
ex.append("[{}] {}".format(et.__name__, ev))
|
||||
return "\n".join(ex[-8:])
|
||||
return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -912,6 +912,9 @@ def sanitize_fn(fn, ok, bad):
|
||||
if "/" not in ok:
|
||||
fn = fn.replace("\\", "/").split("/")[-1]
|
||||
|
||||
if fn.lower() in bad:
|
||||
fn = "_" + fn
|
||||
|
||||
if ANYWIN:
|
||||
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]:
|
||||
fn = fn.replace(a, b)
|
||||
|
||||
bad.extend(["con", "prn", "aux", "nul"])
|
||||
bad = ["con", "prn", "aux", "nul"]
|
||||
for n in range(1, 10):
|
||||
bad += "com{0} lpt{0}".format(n).split(" ")
|
||||
|
||||
if fn.lower() in bad:
|
||||
if fn.lower().split(".")[0] in bad:
|
||||
fn = "_" + fn
|
||||
|
||||
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):
|
||||
try:
|
||||
return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath))))
|
||||
@@ -1235,7 +1248,7 @@ def statdir(logger, scandir, lstat, top):
|
||||
if lstat and ANYWIN:
|
||||
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
|
||||
|
||||
try:
|
||||
|
||||
@@ -17,12 +17,11 @@ window.baguetteBox = (function () {
|
||||
titleTag: false,
|
||||
async: false,
|
||||
preload: 2,
|
||||
animation: 'slideIn',
|
||||
afterShow: null,
|
||||
afterHide: 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 = [],
|
||||
currentIndex = 0,
|
||||
isOverlayVisible = false,
|
||||
@@ -30,6 +29,7 @@ window.baguetteBox = (function () {
|
||||
touchFlag = false, // busy
|
||||
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
|
||||
re_v = /.+\.(webm|mp4)(\?|$)/i,
|
||||
anims = ['slideIn', 'fadeIn', 'none'],
|
||||
data = {}, // all galleries
|
||||
imagesElements = [],
|
||||
documentLastFocus = null,
|
||||
@@ -178,6 +178,7 @@ window.baguetteBox = (function () {
|
||||
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">></button>' +
|
||||
'<div id="bbox-btns">' +
|
||||
'<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-rotr" type="button">↷</button>' +
|
||||
'<button id="bbox-tsel" type="button">sel</button>' +
|
||||
@@ -193,6 +194,7 @@ window.baguetteBox = (function () {
|
||||
btnPrev = ebi('bbox-prev');
|
||||
btnNext = ebi('bbox-next');
|
||||
btnHelp = ebi('bbox-help');
|
||||
btnAnim = ebi('bbox-anim');
|
||||
btnRotL = ebi('bbox-rotl');
|
||||
btnRotR = ebi('bbox-rotr');
|
||||
btnSel = ebi('bbox-tsel');
|
||||
@@ -284,6 +286,16 @@ window.baguetteBox = (function () {
|
||||
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() {
|
||||
var v = vid();
|
||||
ebi('bbox-vmode').style.display = v ? '' : 'none';
|
||||
@@ -397,6 +409,7 @@ window.baguetteBox = (function () {
|
||||
bind(btnClose, 'click', hideOverlay);
|
||||
bind(btnVmode, 'click', tglVmode);
|
||||
bind(btnHelp, 'click', halp);
|
||||
bind(btnAnim, 'click', anim);
|
||||
bind(btnRotL, 'click', rotl);
|
||||
bind(btnRotR, 'click', rotr);
|
||||
bind(btnSel, 'click', tglsel);
|
||||
@@ -414,6 +427,7 @@ window.baguetteBox = (function () {
|
||||
unbind(btnClose, 'click', hideOverlay);
|
||||
unbind(btnVmode, 'click', tglVmode);
|
||||
unbind(btnHelp, 'click', halp);
|
||||
unbind(btnAnim, 'click', anim);
|
||||
unbind(btnRotL, 'click', rotl);
|
||||
unbind(btnRotR, 'click', rotr);
|
||||
unbind(btnSel, 'click', tglsel);
|
||||
@@ -459,7 +473,12 @@ window.baguetteBox = (function () {
|
||||
if (typeof newOptions[item] !== 'undefined')
|
||||
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');
|
||||
|
||||
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
|
||||
@@ -520,6 +539,7 @@ window.baguetteBox = (function () {
|
||||
if (overlay.style.display === 'none')
|
||||
return;
|
||||
|
||||
sethash('');
|
||||
unbind(document, 'keydown', keyDownHandler);
|
||||
unbind(document, 'keyup', keyUpHandler);
|
||||
unbind(document, 'fullscreenchange', onFSC);
|
||||
@@ -806,7 +826,7 @@ window.baguetteBox = (function () {
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||
slider.style.left = offset;
|
||||
slider.style.opacity = 1;
|
||||
}, 400);
|
||||
}, 100);
|
||||
} else {
|
||||
xform ?
|
||||
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@
|
||||
<input type="file" name="f" multiple /><br />
|
||||
<input type="submit" value="start upload">
|
||||
</form>
|
||||
<a id="bbsw" href="?b=u"><br />switch to basic browser</a>
|
||||
</div>
|
||||
|
||||
<div id="op_mkdir" class="opview opbox act">
|
||||
@@ -134,6 +135,9 @@
|
||||
<script>
|
||||
var acct = "{{ acct }}",
|
||||
perms = {{ perms }},
|
||||
themes = {{ themes }},
|
||||
dtheme = "{{ dtheme }}",
|
||||
srvinf = "{{ srv_info }}",
|
||||
def_hcols = {{ def_hcols|tojson }},
|
||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||
@@ -142,14 +146,16 @@
|
||||
have_del = {{ have_del|tojson }},
|
||||
have_unpost = {{ have_unpost|tojson }},
|
||||
have_zip = {{ have_zip|tojson }},
|
||||
turbolvl = {{ turbolvl|tojson }},
|
||||
txt_ext = "{{ txt_ext }}",
|
||||
{% if no_prism %}no_prism = 1,{% endif %}
|
||||
readme = {{ readme|tojson }},
|
||||
ls0 = {{ ls0|tojson }};
|
||||
|
||||
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
||||
document.documentElement.className = localStorage.theme || dtheme;
|
||||
</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/up2k.js?_={{ ts }}"></script>
|
||||
{%- if js %}
|
||||
|
||||
@@ -7,16 +7,16 @@ function dbg(msg) {
|
||||
|
||||
// toolbar
|
||||
ebi('ops').innerHTML = (
|
||||
'<a href="#" data-dest="" tt="close submenu">--</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<code>foo bar</code> = must contain both foo and bar,$N<code>foo -bar</code> = must contain foo but not bar,$N<code>^yana .opus$</code> = start with yana and be an opus file$N<code>"try unite"</code> = contain exactly «try unite»">🔎</a>\n' +
|
||||
(have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>\n' : '') +
|
||||
'<a href="#" data-dest="up2k">🚀</a>\n' +
|
||||
'<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="mkdir" tt="mkdir: create a new directory">📂</a>\n' +
|
||||
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>\n' +
|
||||
'<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>\n' +
|
||||
'<a href="#" data-dest="player" tt="media player options">🎺</a>\n' +
|
||||
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</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<code>foo bar</code> = must contain both foo and bar,$N<code>foo -bar</code> = must contain foo but not bar,$N<code>^yana .opus$</code> = start with yana and be an opus file$N<code>"try unite"</code> = contain exactly «try unite»">🔎</a>' +
|
||||
(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>' +
|
||||
'<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>' +
|
||||
'<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>' +
|
||||
'<a href="#" data-dest="player" tt="media player options">🎺</a>' +
|
||||
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>' +
|
||||
'<div id="opdesc"></div>'
|
||||
);
|
||||
|
||||
@@ -58,7 +58,7 @@ ebi('op_up2k').innerHTML = (
|
||||
' <td class="c"><br />parallel uploads:</td>\n' +
|
||||
' <td class="c" rowspan="2">\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 class="c" rowspan="2">\n' +
|
||||
' <input type="checkbox" id="ask_up" />\n' +
|
||||
@@ -74,7 +74,7 @@ ebi('op_up2k').innerHTML = (
|
||||
' <tr>\n' +
|
||||
' <td class="c">\n' +
|
||||
' <a href="#" class="b" id="nthread_sub">–</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 /> \n' +
|
||||
' </td>\n' +
|
||||
' </tr>\n' +
|
||||
@@ -84,10 +84,9 @@ ebi('op_up2k').innerHTML = (
|
||||
|
||||
'<div id="u2btn_ct">\n' +
|
||||
' <div id="u2btn">\n' +
|
||||
' <span id="u2bm"></span><br />\n' +
|
||||
' drag/drop files<br />\n' +
|
||||
' and folders here<br />\n' +
|
||||
' (or click me)\n' +
|
||||
' <span id="u2bm"></span>\n' +
|
||||
' drop files / folders<br />\n' +
|
||||
' here (or click me)\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
|
||||
@@ -121,8 +120,7 @@ ebi('op_up2k').innerHTML = (
|
||||
'</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="u2foot"></p>\n' +
|
||||
'<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>'
|
||||
'<p id="u2foot"></p>'
|
||||
);
|
||||
|
||||
|
||||
@@ -146,7 +144,6 @@ ebi('op_cfg').innerHTML = (
|
||||
' <h3>switches</h3>\n' +
|
||||
' <div>\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="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' +
|
||||
@@ -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' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
'<div>\n' +
|
||||
' <h3>themes</h3>\n' +
|
||||
' <div id="themes">\n' +
|
||||
' </div>\n' +
|
||||
'</div>\n' +
|
||||
(have_zip ? (
|
||||
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
||||
) : '') +
|
||||
@@ -262,6 +264,8 @@ function goto(dest) {
|
||||
fn();
|
||||
}
|
||||
|
||||
clmod(document.documentElement, 'op_open', dest);
|
||||
|
||||
if (window['treectl'])
|
||||
treectl.onscroll();
|
||||
}
|
||||
@@ -427,7 +431,7 @@ var mpl = (function () {
|
||||
};
|
||||
|
||||
function announce() {
|
||||
if (!r.os_ctl)
|
||||
if (!r.os_ctl || !mp.au)
|
||||
return;
|
||||
|
||||
var np = get_np()[0],
|
||||
@@ -448,7 +452,7 @@ var mpl = (function () {
|
||||
cover = null;
|
||||
|
||||
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]);
|
||||
break;
|
||||
}
|
||||
@@ -509,7 +513,7 @@ catch (ex) { }
|
||||
|
||||
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,
|
||||
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
|
||||
@@ -662,7 +666,7 @@ function ft2dict(tr) {
|
||||
|
||||
for (var a = 1, aa = th.length; a < aa; a++) {
|
||||
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;
|
||||
|
||||
if (!tv)
|
||||
@@ -703,8 +707,12 @@ var widget = (function () {
|
||||
return false;
|
||||
|
||||
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);
|
||||
if (window.vbar) {
|
||||
pbar.onresize();
|
||||
vbar.onresize();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
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.value = m;
|
||||
document.body.appendChild(o);
|
||||
o.focus();
|
||||
o.select();
|
||||
document.execCommand("copy");
|
||||
|
||||
var cln = function () {
|
||||
o.value = 'copied to clipboard ';
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(o);
|
||||
}, 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);
|
||||
setTimeout(function () {
|
||||
clmod(ebi('widget'), 'anim', 1);
|
||||
clmod(widget, 'anim', 1);
|
||||
}, 10);
|
||||
return r;
|
||||
})();
|
||||
@@ -794,6 +814,9 @@ var pbar = (function () {
|
||||
grad;
|
||||
|
||||
r.onresize = function () {
|
||||
if (!widget.is_open && r.buf)
|
||||
return;
|
||||
|
||||
r.buf = canvas_cfg(ebi('barbuf'));
|
||||
r.pos = canvas_cfg(ebi('barpos'));
|
||||
r.drawbuf();
|
||||
@@ -895,6 +918,9 @@ var vbar = (function () {
|
||||
can, ctx, w, h, grad1, grad2;
|
||||
|
||||
r.onresize = function () {
|
||||
if (!widget.is_open && r.can)
|
||||
return;
|
||||
|
||||
r.can = canvas_cfg(ebi('pvol'));
|
||||
can = r.can.can;
|
||||
ctx = r.can.ctx;
|
||||
@@ -1388,7 +1414,7 @@ var audio_eq = (function () {
|
||||
|
||||
|
||||
// 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)
|
||||
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() {
|
||||
var v = hash0;
|
||||
hash0 = null;
|
||||
if (!v)
|
||||
return;
|
||||
|
||||
if (v.indexOf('#af-') === 0) {
|
||||
var id = v.slice(2).split('&');
|
||||
if (id[0].length < 10)
|
||||
var media = scan_hash(v);
|
||||
if (media) {
|
||||
var mtype = media[0],
|
||||
id = media[1],
|
||||
ts = media[2];
|
||||
|
||||
if (mtype == 'a') {
|
||||
if (!ts)
|
||||
return play(id);
|
||||
|
||||
return play(id, false, ts);
|
||||
}
|
||||
|
||||
if (mtype == 'g') {
|
||||
if (!thegrid.en)
|
||||
ebi('griden').click();
|
||||
|
||||
var t = setInterval(function () {
|
||||
if (!thegrid.bbox)
|
||||
return;
|
||||
|
||||
if (id.length == 1)
|
||||
return play(id[0]);
|
||||
|
||||
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]);
|
||||
if (!m)
|
||||
return play(id[0]);
|
||||
|
||||
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
||||
clearInterval(t);
|
||||
var im = QS('#ggrid a[ref="' + id + '"]');
|
||||
im.click();
|
||||
im.scrollIntoView();
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
if (v.indexOf('#q=') === 0) {
|
||||
@@ -1850,9 +1913,6 @@ var fileman = (function () {
|
||||
if (!md.hasOwnProperty(k))
|
||||
continue;
|
||||
|
||||
md[k.toLowerCase()] = md[k];
|
||||
k = k.toLowerCase();
|
||||
|
||||
if (k.startsWith('.'))
|
||||
md[k.slice(1)] = md[k];
|
||||
}
|
||||
@@ -2314,10 +2374,12 @@ var showfile = (function () {
|
||||
'.bas': 'basic',
|
||||
'.bat': 'batch',
|
||||
'.cxx': 'cpp',
|
||||
'.diz': 'ans',
|
||||
'.h': 'c',
|
||||
'.hpp': 'cpp',
|
||||
'.htm': 'html',
|
||||
'.hxx': 'cpp',
|
||||
'.log': 'ans',
|
||||
'.patch': 'diff',
|
||||
'.ps1': 'powershell',
|
||||
'.psm1': 'powershell',
|
||||
@@ -2334,7 +2396,7 @@ var showfile = (function () {
|
||||
'cmakelists.txt': 'cmake',
|
||||
'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);
|
||||
for (var a = 0; a < x.length; a++)
|
||||
r.map["." + x[a]] = x[a];
|
||||
@@ -2441,7 +2503,10 @@ var showfile = (function () {
|
||||
if (lnh.slice(0, 5) == '#doc.')
|
||||
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) { }
|
||||
}
|
||||
@@ -2461,7 +2526,7 @@ var showfile = (function () {
|
||||
el.textContent = txt;
|
||||
el.innerHTML = '<code>' + el.innerHTML + '</code>';
|
||||
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)
|
||||
fun(el.firstChild);
|
||||
else
|
||||
@@ -2489,6 +2554,65 @@ var showfile = (function () {
|
||||
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 () {
|
||||
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
|
||||
for (var a = 0; a < r.files.length; a++) {
|
||||
@@ -2532,7 +2656,7 @@ var showfile = (function () {
|
||||
};
|
||||
|
||||
var bdoc = ebi('bdoc');
|
||||
bdoc.setAttribute('class', 'line-numbers');
|
||||
bdoc.className = 'line-numbers';
|
||||
bdoc.innerHTML = (
|
||||
'<div id="hdoc" class="ghead">\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++) {
|
||||
var tr = ebi(ths[a].getAttribute('ref')).closest('tr'),
|
||||
cl = tr.getAttribute('class') || '';
|
||||
cl = tr.className || '';
|
||||
|
||||
if (noq_href(ths[a]).endsWith('/'))
|
||||
cl += ' dir';
|
||||
|
||||
ths[a].setAttribute('class', cl);
|
||||
ths[a].className = cl;
|
||||
}
|
||||
var uns = QS('#ggrid a[ref="unsearch"]');
|
||||
if (uns)
|
||||
uns.onclick = function (e) {
|
||||
|
||||
var sp = ['unsearch', 'moar'];
|
||||
for (var a = 0; a < sp.length; a++)
|
||||
(function (a) {
|
||||
var o = QS('#ggrid a[ref="' + sp[a] + '"]');
|
||||
if (o)
|
||||
o.onclick = function (e) {
|
||||
ev(e);
|
||||
ebi('unsearch').click();
|
||||
ebi(sp[a]).click();
|
||||
};
|
||||
})(a);
|
||||
};
|
||||
|
||||
r.tippen = function () {
|
||||
@@ -2860,6 +2989,9 @@ var thegrid = (function () {
|
||||
return '<a download href="' + h +
|
||||
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
|
||||
esc(uricom_dec(h.split('/').pop())[0]) + '</a>';
|
||||
},
|
||||
onChange: function (i) {
|
||||
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
|
||||
}
|
||||
})[0];
|
||||
};
|
||||
@@ -2873,10 +3005,6 @@ var thegrid = (function () {
|
||||
});
|
||||
ebi('wtgrid').onclick = ebi('griden').onclick;
|
||||
|
||||
setTimeout(function () {
|
||||
import_js('/.cpr/baguettebox.js', r.bagit);
|
||||
}, 1);
|
||||
|
||||
return r;
|
||||
})();
|
||||
|
||||
@@ -3158,7 +3286,8 @@ document.onkeydown = function (e) {
|
||||
|
||||
var trs = [],
|
||||
orig_url = null,
|
||||
orig_html = null;
|
||||
orig_html = null,
|
||||
cap = 125;
|
||||
|
||||
for (var a = 0; a < sconf.length; a++) {
|
||||
var html = ['<tr><td><br />' + sconf[a][0] + '</td>'];
|
||||
@@ -3187,12 +3316,13 @@ document.onkeydown = function (e) {
|
||||
var o = QSA('#op_search input');
|
||||
for (var a = 0; a < o.length; a++) {
|
||||
o[a].oninput = ev_search_input;
|
||||
o[a].onkeydown = ev_search_keydown;
|
||||
}
|
||||
|
||||
function srch_msg(err, txt) {
|
||||
var o = ebi('srch_q');
|
||||
o.textContent = txt;
|
||||
o.style.color = err ? '#f09' : '#c90';
|
||||
clmod(o, 'err', err);
|
||||
}
|
||||
|
||||
var search_timeout,
|
||||
@@ -3212,12 +3342,18 @@ document.onkeydown = function (e) {
|
||||
encode_query();
|
||||
|
||||
set_vq();
|
||||
cap = 125;
|
||||
|
||||
clearTimeout(defer_timeout);
|
||||
defer_timeout = setTimeout(try_search, 2000);
|
||||
try_search(v);
|
||||
}
|
||||
|
||||
function ev_search_keydown(e) {
|
||||
if (e.key == 'Enter')
|
||||
do_search();
|
||||
}
|
||||
|
||||
function try_search(v) {
|
||||
if (Date.now() - search_in_progress > 30 * 1000) {
|
||||
clearTimeout(defer_timeout);
|
||||
@@ -3246,8 +3382,6 @@ document.onkeydown = function (e) {
|
||||
vs = ebi('srch_' + k + 'v').value,
|
||||
tvs = [];
|
||||
|
||||
if (k == 'name')
|
||||
console.log('a');
|
||||
while (vs) {
|
||||
vs = vs.trim();
|
||||
if (!vs)
|
||||
@@ -3336,7 +3470,7 @@ document.onkeydown = function (e) {
|
||||
xhr.onreadystatechange = xhr_search_results;
|
||||
xhr.ts = Date.now();
|
||||
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() {
|
||||
@@ -3369,7 +3503,9 @@ document.onkeydown = function (e) {
|
||||
|
||||
var html = mk_files_header(tagord), seen = {};
|
||||
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++) {
|
||||
var r = res.hits[a],
|
||||
ts = parseInt(r.ts),
|
||||
@@ -3421,6 +3557,9 @@ document.onkeydown = function (e) {
|
||||
|
||||
sethash('q=' + uricom_enc(this.q_raw));
|
||||
ebi('unsearch').onclick = unsearch;
|
||||
var m = ebi('moar');
|
||||
if (m)
|
||||
m.onclick = moar;
|
||||
}
|
||||
|
||||
function unsearch(e) {
|
||||
@@ -3432,6 +3571,12 @@ document.onkeydown = function (e) {
|
||||
sethash('');
|
||||
reload_browser();
|
||||
}
|
||||
|
||||
function moar(e) {
|
||||
ev(e);
|
||||
cap *= 2;
|
||||
do_search();
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -3612,7 +3757,7 @@ var treectl = (function () {
|
||||
treeh = winh - atop;
|
||||
|
||||
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);
|
||||
@@ -3718,7 +3863,6 @@ var treectl = (function () {
|
||||
QS('#treeul>li>a+a').textContent = '[root]';
|
||||
despin('#tree');
|
||||
reload_tree();
|
||||
onresize();
|
||||
|
||||
var fun = r.dir_cb;
|
||||
if (fun) {
|
||||
@@ -3750,7 +3894,7 @@ var treectl = (function () {
|
||||
cl = 'par';
|
||||
}
|
||||
|
||||
links[a].setAttribute('class', cl);
|
||||
links[a].className = cl;
|
||||
links[a].onclick = treego;
|
||||
links[a].onmouseenter = nowrap ? menter : null;
|
||||
links[a].onmouseleave = nowrap ? mleave : null;
|
||||
@@ -3771,6 +3915,7 @@ var treectl = (function () {
|
||||
catch (ex) { }
|
||||
r.pdir.shift();
|
||||
r.pdirw = -1;
|
||||
onresize();
|
||||
}
|
||||
|
||||
function compy() {
|
||||
@@ -3817,7 +3962,7 @@ var treectl = (function () {
|
||||
return true;
|
||||
|
||||
ev(e);
|
||||
if (this.getAttribute('class') == 'hl' &&
|
||||
if (this.className == 'hl' &&
|
||||
this.previousSibling.textContent == '-') {
|
||||
treegrow.call(this.previousSibling, e);
|
||||
return;
|
||||
@@ -3880,7 +4025,8 @@ var treectl = (function () {
|
||||
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())
|
||||
hist_push(this.top);
|
||||
@@ -3902,7 +4048,6 @@ var treectl = (function () {
|
||||
r.ls_cb = null;
|
||||
fun();
|
||||
}
|
||||
eval_hash();
|
||||
}
|
||||
|
||||
r.gentab = function (top, res) {
|
||||
@@ -3954,6 +4099,7 @@ var treectl = (function () {
|
||||
html = html.join('\n');
|
||||
set_files_html(html);
|
||||
|
||||
function asdf() {
|
||||
filecols.set_style();
|
||||
showfile.mktree();
|
||||
mukey.render();
|
||||
@@ -3966,9 +4112,30 @@ var treectl = (function () {
|
||||
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 () {
|
||||
qsr('#bbsw');
|
||||
if (ls0 === null) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/?am_js', true);
|
||||
@@ -3978,12 +4145,9 @@ var treectl = (function () {
|
||||
}
|
||||
|
||||
r.gentab(get_evpath(), ls0);
|
||||
reload_browser();
|
||||
pbar.onresize();
|
||||
vbar.onresize();
|
||||
mukey.render();
|
||||
showfile.addlinks();
|
||||
thegrid.setdirty();
|
||||
setTimeout(eval_hash, 1);
|
||||
};
|
||||
|
||||
@@ -4064,7 +4228,7 @@ var treectl = (function () {
|
||||
function enspin(sel) {
|
||||
despin(sel);
|
||||
var d = mknod('div');
|
||||
d.setAttribute('class', 'dumb_loader_thing');
|
||||
d.className = 'dumb_loader_thing';
|
||||
d.innerHTML = '🌲';
|
||||
var tgt = QS(sel);
|
||||
tgt.insertBefore(d, tgt.childNodes[0]);
|
||||
@@ -4082,14 +4246,16 @@ function apply_perms(newperms) {
|
||||
perms = newperms || [];
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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'));
|
||||
|
||||
var axs = [],
|
||||
@@ -4106,10 +4272,11 @@ function apply_perms(newperms) {
|
||||
axs += '-Only';
|
||||
}
|
||||
|
||||
ebi('acc_info').innerHTML = '<span' + aclass + axs + ' access</span>' + (acct != '*' ?
|
||||
ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
|
||||
'</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++) {
|
||||
var display = '';
|
||||
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++) {
|
||||
var spans = tds[a].getElementsByTagName('span');
|
||||
if (spans.length && spans[0].textContent == txt) {
|
||||
min = (tds[a].getAttribute('class') || '').indexOf('min') !== -1;
|
||||
min = (tds[a].className || '').indexOf('min') !== -1;
|
||||
i = a;
|
||||
break;
|
||||
}
|
||||
@@ -4313,7 +4480,7 @@ var filecols = (function () {
|
||||
tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')');
|
||||
|
||||
for (var b = 0, bb = tds.length; b < bb; b++)
|
||||
tds[b].setAttribute('class', cls);
|
||||
tds[b].className = cls;
|
||||
}
|
||||
if (window['tt']) {
|
||||
tt.att(ebi('hcols'));
|
||||
@@ -4464,20 +4631,55 @@ var mukey = (function () {
|
||||
})();
|
||||
|
||||
|
||||
var light;
|
||||
(function () {
|
||||
var light, theme;
|
||||
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() {
|
||||
clmod(document.documentElement, "light", light);
|
||||
clmod(document.documentElement, "dark", !light);
|
||||
var cl = document.documentElement.className;
|
||||
cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' ');
|
||||
document.documentElement.className = cl + ' ' + theme + ' ';
|
||||
|
||||
pbar.drawbuf();
|
||||
pbar.drawpos();
|
||||
vbar.draw();
|
||||
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();
|
||||
return settheme;
|
||||
})();
|
||||
|
||||
|
||||
@@ -4724,7 +4926,10 @@ var msel = (function () {
|
||||
tb.value = '';
|
||||
clmod(sf, 'vis');
|
||||
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>");
|
||||
}
|
||||
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];
|
||||
for (var a = 0; a < res.length; a++) {
|
||||
|
||||
@@ -161,7 +161,7 @@ blink {
|
||||
height: 1.05em;
|
||||
margin: -.2em .3em -.2em -.4em;
|
||||
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;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
@@ -219,48 +219,45 @@ blink {
|
||||
|
||||
|
||||
|
||||
html.dark,
|
||||
html.dark body {
|
||||
html.z,
|
||||
html.z body {
|
||||
background: #222;
|
||||
color: #ccc;
|
||||
}
|
||||
html.dark #toc a {
|
||||
html.z #toc a {
|
||||
color: #ccc;
|
||||
border-left: .4em solid #444;
|
||||
border-bottom: .1em solid #333;
|
||||
}
|
||||
html.dark #toc a.act {
|
||||
html.z #toc a.act {
|
||||
color: #fff;
|
||||
border-left: .4em solid #3ad;
|
||||
}
|
||||
html.dark #toc li {
|
||||
html.z #toc li {
|
||||
border-width: 0;
|
||||
}
|
||||
html.dark #mn a:not(:last-child)::after {
|
||||
border-color: rgba(255,255,255,0.3);
|
||||
}
|
||||
html.dark #mn a {
|
||||
html.z #mn a {
|
||||
color: #ccc;
|
||||
}
|
||||
html.dark #mn {
|
||||
html.z #mn {
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
html.dark #mn,
|
||||
html.dark #mh {
|
||||
html.z #mn,
|
||||
html.z #mh {
|
||||
background: #222;
|
||||
}
|
||||
html.dark #mh a {
|
||||
html.z #mh a {
|
||||
color: #ccc;
|
||||
background: none;
|
||||
}
|
||||
html.dark #mh a:hover {
|
||||
html.z #mh a:hover {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
html.dark #toolsbox {
|
||||
html.z #toolsbox {
|
||||
background: #222;
|
||||
}
|
||||
html.dark #toolsbox.open {
|
||||
html.z #toolsbox.open {
|
||||
box-shadow: 0 .2em .2em #069;
|
||||
border-radius: 0 0 .4em .4em;
|
||||
}
|
||||
@@ -308,23 +305,23 @@ blink {
|
||||
|
||||
|
||||
|
||||
html.dark #toc {
|
||||
html.z #toc {
|
||||
background: #282828;
|
||||
border-top: 1px solid #2c2c2c;
|
||||
box-shadow: 0 0 1em #181818;
|
||||
}
|
||||
html.dark #toc,
|
||||
html.dark #mw {
|
||||
html.z #toc,
|
||||
html.z #mw {
|
||||
scrollbar-color: #b80 #282828;
|
||||
}
|
||||
html.dark #toc::-webkit-scrollbar-track {
|
||||
html.z #toc::-webkit-scrollbar-track {
|
||||
background: #282828;
|
||||
}
|
||||
html.dark #toc::-webkit-scrollbar {
|
||||
html.z #toc::-webkit-scrollbar {
|
||||
background: #282828;
|
||||
width: .8em;
|
||||
}
|
||||
html.dark #toc::-webkit-scrollbar-thumb {
|
||||
html.z #toc::-webkit-scrollbar-thumb {
|
||||
background: #b80;
|
||||
}
|
||||
}
|
||||
@@ -432,16 +429,16 @@ blink {
|
||||
|
||||
|
||||
|
||||
html.dark .mdo a {
|
||||
html.z .mdo a {
|
||||
color: #000;
|
||||
}
|
||||
html.dark .mdo pre,
|
||||
html.dark .mdo code {
|
||||
html.z .mdo pre,
|
||||
html.z .mdo code {
|
||||
color: #240;
|
||||
}
|
||||
html.dark .mdo p>em,
|
||||
html.dark .mdo li>em,
|
||||
html.dark .mdo td>em {
|
||||
html.z .mdo p>em,
|
||||
html.z .mdo li>em,
|
||||
html.z .mdo td>em {
|
||||
color: #940;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +136,13 @@ var md_opt = {
|
||||
|
||||
(function () {
|
||||
var l = localStorage,
|
||||
drk = l.lightmode != 1,
|
||||
drk = l.light != 1,
|
||||
btn = document.getElementById("lightswitch"),
|
||||
f = function (e) {
|
||||
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");
|
||||
l.lightmode = drk? 0:1;
|
||||
l.light = drk? 0:1;
|
||||
};
|
||||
|
||||
btn.onclick = f;
|
||||
|
||||
@@ -278,7 +278,7 @@ function convert_markdown(md_text, dest_dom) {
|
||||
if (!txt)
|
||||
nodes[a].textContent = href;
|
||||
else if (href !== txt)
|
||||
nodes[a].setAttribute('class', 'vis');
|
||||
nodes[a].className = 'vis';
|
||||
}
|
||||
|
||||
// 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 char = done ? 'Y' : 'N';
|
||||
|
||||
dom_li.setAttribute('class', 'task-list-item');
|
||||
dom_li.className = 'task-list-item';
|
||||
dom_li.style.listStyleType = 'none';
|
||||
var html = dom_li.innerHTML;
|
||||
dom_li.innerHTML =
|
||||
@@ -468,11 +468,11 @@ function init_toc() {
|
||||
for (var a = 0; a < anchors.length; a++) {
|
||||
if (anchors[a].active) {
|
||||
anchors[a].active = false;
|
||||
links[a].setAttribute('class', '');
|
||||
links[a].className = '';
|
||||
}
|
||||
}
|
||||
anchors[hit].active = true;
|
||||
links[hit].setAttribute('class', 'act');
|
||||
links[hit].className = 'act';
|
||||
}
|
||||
|
||||
var pane_height = parseInt(getComputedStyle(dom_toc).height);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
position: relative;
|
||||
scrollbar-color: #eb0 #f7f7f7;
|
||||
}
|
||||
html.dark #mt {
|
||||
html.z #mt {
|
||||
color: #eee;
|
||||
background: #222;
|
||||
border: 1px solid #777;
|
||||
@@ -77,7 +77,7 @@ html.dark #mt {
|
||||
background: #f97;
|
||||
border-radius: .15em;
|
||||
}
|
||||
html.dark #save.force-save {
|
||||
html.z #save.force-save {
|
||||
color: #fca;
|
||||
background: #720;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ html.dark #save.force-save {
|
||||
#helpclose {
|
||||
display: block;
|
||||
}
|
||||
html.dark #helpbox {
|
||||
html.z #helpbox {
|
||||
box-shadow: 0 .5em 2em #444;
|
||||
background: #222;
|
||||
border: 1px solid #079;
|
||||
|
||||
@@ -144,16 +144,16 @@ redraw = (function () {
|
||||
map_pre = genmap(dom_pre, map_pre);
|
||||
}
|
||||
function setsbs() {
|
||||
dom_wrap.setAttribute('class', '');
|
||||
dom_swrap.setAttribute('class', '');
|
||||
dom_wrap.className = '';
|
||||
dom_swrap.className = '';
|
||||
onresize();
|
||||
}
|
||||
function modetoggle() {
|
||||
var mode = dom_nsbs.innerHTML;
|
||||
dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
|
||||
mode += ' single';
|
||||
dom_wrap.setAttribute('class', mode);
|
||||
dom_swrap.setAttribute('class', mode);
|
||||
dom_wrap.className = mode;
|
||||
dom_swrap.className = mode;
|
||||
onresize();
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ var modpoll = new Modpoll();
|
||||
|
||||
|
||||
window.onbeforeunload = function (e) {
|
||||
if ((ebi("save").getAttribute('class') + '').indexOf('disabled') >= 0)
|
||||
if ((ebi("save").className + '').indexOf('disabled') >= 0)
|
||||
return; //nice (todo)
|
||||
|
||||
e.preventDefault(); //ff
|
||||
@@ -321,7 +321,7 @@ window.onbeforeunload = function (e) {
|
||||
function save(e) {
|
||||
if (e) e.preventDefault();
|
||||
var save_btn = ebi("save"),
|
||||
save_cls = save_btn.getAttribute('class') + '';
|
||||
save_cls = save_btn.className + '';
|
||||
|
||||
if (save_cls.indexOf('disabled') >= 0)
|
||||
return toast.inf(2, "no changes");
|
||||
@@ -678,7 +678,7 @@ function reLastIndexOf(txt, ptn, end) {
|
||||
// table formatter
|
||||
function fmt_table(e) {
|
||||
if (e) e.preventDefault();
|
||||
//dom_tbox.setAttribute('class', '');
|
||||
//dom_tbox.className = '';
|
||||
|
||||
var txt = dom_src.value,
|
||||
ofs = dom_src.selectionStart,
|
||||
@@ -829,7 +829,7 @@ function fmt_table(e) {
|
||||
// show unicode
|
||||
function mark_uni(e) {
|
||||
if (e) e.preventDefault();
|
||||
dom_tbox.setAttribute('class', '');
|
||||
dom_tbox.className = '';
|
||||
|
||||
var txt = dom_src.value,
|
||||
ptn = new RegExp('([^' + js_uni_whitelist + ']+)', 'g'),
|
||||
@@ -989,14 +989,14 @@ var set_lno = (function () {
|
||||
|
||||
ebi('tools').onclick = function (e) {
|
||||
if (e) e.preventDefault();
|
||||
var is_open = dom_tbox.getAttribute('class') != 'open';
|
||||
dom_tbox.setAttribute('class', is_open ? 'open' : '');
|
||||
var is_open = dom_tbox.className != 'open';
|
||||
dom_tbox.className = is_open ? 'open' : '';
|
||||
};
|
||||
|
||||
|
||||
ebi('help').onclick = function (e) {
|
||||
if (e) e.preventDefault();
|
||||
dom_tbox.setAttribute('class', '');
|
||||
dom_tbox.className = '';
|
||||
|
||||
var dom = ebi('helpbox');
|
||||
var dtxt = dom.getElementsByTagName('textarea');
|
||||
|
||||
@@ -84,24 +84,24 @@ html .editor-toolbar>button.save.force-save {
|
||||
|
||||
|
||||
/* darkmode */
|
||||
html.dark .mdo,
|
||||
html.dark .CodeMirror {
|
||||
html.z .mdo,
|
||||
html.z .CodeMirror {
|
||||
border-color: #222;
|
||||
}
|
||||
html.dark,
|
||||
html.dark body,
|
||||
html.dark .CodeMirror {
|
||||
html.z,
|
||||
html.z body,
|
||||
html.z .CodeMirror {
|
||||
background: #222;
|
||||
color: #ccc;
|
||||
}
|
||||
html.dark .CodeMirror-cursor {
|
||||
html.z .CodeMirror-cursor {
|
||||
border-color: #fff;
|
||||
}
|
||||
html.dark .CodeMirror-selected {
|
||||
html.z .CodeMirror-selected {
|
||||
box-shadow: 0 0 1px #0cf inset;
|
||||
}
|
||||
html.dark .CodeMirror-selected,
|
||||
html.dark .CodeMirror-selectedtext {
|
||||
html.z .CodeMirror-selected,
|
||||
html.z .CodeMirror-selectedtext {
|
||||
border-radius: .1em;
|
||||
background: #246;
|
||||
color: #fff;
|
||||
@@ -109,37 +109,37 @@ html.dark .CodeMirror-selectedtext {
|
||||
|
||||
|
||||
|
||||
html.dark #mn a {
|
||||
html.z #mn a {
|
||||
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);
|
||||
}
|
||||
html.dark .editor-toolbar {
|
||||
html.z .editor-toolbar {
|
||||
border-color: #2c2c2c;
|
||||
background: #1c1c1c;
|
||||
}
|
||||
html.dark .editor-toolbar>i.separator {
|
||||
html.z .editor-toolbar>i.separator {
|
||||
border-left: 1px solid #444;
|
||||
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);
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
|
||||
|
||||
html.dark .editor-toolbar>button:hover {
|
||||
html.z .editor-toolbar>button:hover {
|
||||
color: #333;
|
||||
}
|
||||
html.dark .editor-toolbar>button.active {
|
||||
html.z .editor-toolbar>button.active {
|
||||
color: #333;
|
||||
border-color: #ec1;
|
||||
background: #c90;
|
||||
}
|
||||
html.dark .editor-toolbar::after,
|
||||
html.dark .editor-toolbar::before {
|
||||
html.z .editor-toolbar::after,
|
||||
html.z .editor-toolbar::before {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,6 @@ html.dark .editor-toolbar::before {
|
||||
padding: 1em;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
html.dark .mdo {
|
||||
html.z .mdo {
|
||||
background: #1c1c1c;
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ var md_opt = {
|
||||
|
||||
var lightswitch = (function () {
|
||||
var l = localStorage,
|
||||
drk = l.lightmode != 1,
|
||||
drk = l.light != 1,
|
||||
f = function (e) {
|
||||
if (e) drk = !drk;
|
||||
document.documentElement.setAttribute("class", drk? "dark":"light");
|
||||
l.lightmode = drk? 0:1;
|
||||
document.documentElement.className = drk? "z":"y";
|
||||
l.light = drk? 0:1;
|
||||
};
|
||||
f();
|
||||
return f;
|
||||
|
||||
@@ -88,27 +88,27 @@ blockquote {
|
||||
}
|
||||
|
||||
|
||||
html.dark,
|
||||
html.dark body,
|
||||
html.dark #wrap {
|
||||
html.z,
|
||||
html.z body,
|
||||
html.z #wrap {
|
||||
background: #222;
|
||||
color: #ccc;
|
||||
}
|
||||
html.dark h1 {
|
||||
html.z h1 {
|
||||
border-color: #777;
|
||||
}
|
||||
html.dark a {
|
||||
html.z a {
|
||||
color: #fff;
|
||||
background: #057;
|
||||
border-color: #37a;
|
||||
}
|
||||
html.dark .logout,
|
||||
html.dark .btns a,
|
||||
html.dark a.r {
|
||||
html.z .logout,
|
||||
html.z .btns a,
|
||||
html.z a.r {
|
||||
background: #804;
|
||||
border-color: #c28;
|
||||
}
|
||||
html.dark input {
|
||||
html.z input {
|
||||
color: #fff;
|
||||
background: #626;
|
||||
border: 1px solid #c2c;
|
||||
@@ -117,6 +117,6 @@ html.dark input {
|
||||
padding: .5em .7em;
|
||||
margin: 0 .5em 0 0;
|
||||
}
|
||||
html.dark .num {
|
||||
html.z .num {
|
||||
border-color: #777;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<a href="#" id="repl">π</a>
|
||||
<script>
|
||||
|
||||
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
||||
document.documentElement.className = localStorage.light == 1 ? "y" : "z";
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
|
||||
@@ -157,23 +157,24 @@ html {
|
||||
#tt em {
|
||||
color: #f6a;
|
||||
}
|
||||
html.light #tt {
|
||||
html.y #tt {
|
||||
color: #333;
|
||||
background: #fff;
|
||||
border-color: #888 #000 #777 #000;
|
||||
}
|
||||
html.light #tt,
|
||||
html.light #toast {
|
||||
html.y #tt,
|
||||
html.y #toast {
|
||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||
}
|
||||
#modalc code,
|
||||
html.light #tt code {
|
||||
html.y #tt code {
|
||||
background: #060;
|
||||
color: #fff;
|
||||
}
|
||||
html.light #tt em {
|
||||
html.y #tt em {
|
||||
color: #d38;
|
||||
}
|
||||
html.light #tth {
|
||||
html.y #tth {
|
||||
color: #000;
|
||||
background: #fff;
|
||||
}
|
||||
@@ -273,9 +274,9 @@ html.light #tth {
|
||||
box-shadow: 0 .1em .2em #fc0 inset;
|
||||
border-radius: .2em;
|
||||
}
|
||||
html.light *:focus,
|
||||
html.light #pctl *:focus,
|
||||
html.light .btn:focus {
|
||||
html.y *:focus,
|
||||
html.y #pctl *:focus,
|
||||
html.y .btn:focus {
|
||||
box-shadow: 0 .1em .2em #037 inset;
|
||||
}
|
||||
input[type="text"]:focus,
|
||||
@@ -283,9 +284,9 @@ input:not([type]):focus,
|
||||
textarea:focus {
|
||||
box-shadow: 0 .1em .3em #fc0, 0 -.1em .3em #fc0;
|
||||
}
|
||||
html.light input[type="text"]:focus,
|
||||
html.light input:not([type]):focus,
|
||||
html.light textarea:focus {
|
||||
html.y input[type="text"]:focus,
|
||||
html.y input:not([type]):focus,
|
||||
html.y textarea:focus {
|
||||
box-shadow: 0 .1em .3em #037, 0 -.1em .3em #037;
|
||||
}
|
||||
|
||||
@@ -414,7 +415,7 @@ html.light textarea:focus {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word; /*ie*/
|
||||
}
|
||||
html.light .mdo a,
|
||||
html.y .mdo a,
|
||||
.mdo a {
|
||||
color: #fff;
|
||||
background: #39b;
|
||||
@@ -443,48 +444,48 @@ html.light textarea:focus {
|
||||
|
||||
|
||||
|
||||
html.dark .mdo a {
|
||||
html.z .mdo a {
|
||||
background: #057;
|
||||
}
|
||||
html.dark .mdo h1 a, html.dark .mdo h4 a,
|
||||
html.dark .mdo h2 a, html.dark .mdo h5 a,
|
||||
html.dark .mdo h3 a, html.dark .mdo h6 a {
|
||||
html.z .mdo h1 a, html.z .mdo h4 a,
|
||||
html.z .mdo h2 a, html.z .mdo h5 a,
|
||||
html.z .mdo h3 a, html.z .mdo h6 a {
|
||||
color: inherit;
|
||||
background: none;
|
||||
}
|
||||
html.dark .mdo pre,
|
||||
html.dark .mdo code {
|
||||
html.z .mdo pre,
|
||||
html.z .mdo code {
|
||||
color: #8c0;
|
||||
background: #1a1a1a;
|
||||
border: .07em solid #333;
|
||||
}
|
||||
html.dark .mdo ul,
|
||||
html.dark .mdo ol {
|
||||
html.z .mdo ul,
|
||||
html.z .mdo ol {
|
||||
border-color: #444;
|
||||
}
|
||||
html.dark .mdo strong {
|
||||
html.z .mdo strong {
|
||||
color: #fff;
|
||||
}
|
||||
html.dark .mdo p>em,
|
||||
html.dark .mdo li>em,
|
||||
html.dark .mdo td>em {
|
||||
html.z .mdo p>em,
|
||||
html.z .mdo li>em,
|
||||
html.z .mdo td>em {
|
||||
color: #f94;
|
||||
border-color: #666;
|
||||
}
|
||||
html.dark .mdo h1 {
|
||||
html.z .mdo h1 {
|
||||
background: #383838;
|
||||
border-top: .4em solid #b80;
|
||||
border-bottom: .4em solid #4c4c4c;
|
||||
}
|
||||
html.dark .mdo h2 {
|
||||
html.z .mdo h2 {
|
||||
background: #444;
|
||||
border-bottom: .22em solid #555;
|
||||
}
|
||||
html.dark .mdo td,
|
||||
html.dark .mdo th {
|
||||
html.z .mdo td,
|
||||
html.z .mdo th {
|
||||
border-color: #444;
|
||||
}
|
||||
html.dark .mdo blockquote {
|
||||
html.z .mdo blockquote {
|
||||
background: #282828;
|
||||
border: .07em dashed #444;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ function up2k_flagbus() {
|
||||
}
|
||||
|
||||
|
||||
function U2pvis(act, btns) {
|
||||
function U2pvis(act, btns, uc) {
|
||||
var r = this;
|
||||
r.act = act;
|
||||
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 "));
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -624,11 +626,11 @@ function up2k_init(subtle) {
|
||||
|
||||
function setmsg(msg, type) {
|
||||
if (msg !== undefined) {
|
||||
ebi('u2err').setAttribute('class', type);
|
||||
ebi('u2err').className = type;
|
||||
ebi('u2err').innerHTML = msg;
|
||||
}
|
||||
else {
|
||||
ebi('u2err').setAttribute('class', '');
|
||||
ebi('u2err').className = '';
|
||||
ebi('u2err').innerHTML = '';
|
||||
}
|
||||
if (msg == suggest_up2k) {
|
||||
@@ -644,12 +646,6 @@ function up2k_init(subtle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ebi('u2nope').onclick = function (e) {
|
||||
ev(e);
|
||||
setmsg(suggest_up2k, 'msg');
|
||||
goto('bup');
|
||||
};
|
||||
|
||||
setmsg(suggest_up2k, 'msg');
|
||||
|
||||
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, 'flag_en', 'flag_en', false, apply_flag_cfg);
|
||||
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
|
||||
bcfg_bind(uc, 'turbo', 'u2turbo', false, draw_turbo, false);
|
||||
bcfg_bind(uc, 'datechk', 'u2tdate', true, null, false);
|
||||
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo, false);
|
||||
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null, false);
|
||||
|
||||
var st = {
|
||||
"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);
|
||||
|
||||
var bobslice = null;
|
||||
@@ -1173,7 +1169,7 @@ function up2k_init(subtle) {
|
||||
var t = st.todo.handshake[0],
|
||||
cd = t.cooldown;
|
||||
|
||||
if (cd && cd - Date.now() > 0)
|
||||
if (cd && cd > Date.now())
|
||||
return false;
|
||||
|
||||
// keepalive or verify
|
||||
@@ -1370,6 +1366,14 @@ function up2k_init(subtle) {
|
||||
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
|
||||
@@ -1468,7 +1472,6 @@ function up2k_init(subtle) {
|
||||
min_filebuf = 1;
|
||||
var td = Date.now() - t0;
|
||||
if (td > 50) {
|
||||
ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
|
||||
min_filebuf = 32 * 1024 * 1024;
|
||||
}
|
||||
}
|
||||
@@ -1756,8 +1759,12 @@ function up2k_init(subtle) {
|
||||
|
||||
pvis.move(t.n, 'ok');
|
||||
}
|
||||
else t.t_uploaded = undefined;
|
||||
else {
|
||||
if (t.t_uploaded)
|
||||
chill(t);
|
||||
|
||||
t.t_uploaded = undefined;
|
||||
}
|
||||
tasker();
|
||||
}
|
||||
else {
|
||||
@@ -1869,7 +1876,8 @@ function up2k_init(subtle) {
|
||||
else {
|
||||
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
|
||||
xhr.status, t.name) + (txt || "no further information"));
|
||||
return;
|
||||
|
||||
chill(t);
|
||||
}
|
||||
orz2(xhr);
|
||||
}
|
||||
@@ -1920,6 +1928,7 @@ function up2k_init(subtle) {
|
||||
//
|
||||
|
||||
function onresize(e) {
|
||||
// 10x faster than matchMedia('(min-width
|
||||
var bar = ebi('ops'),
|
||||
wpx = window.innerWidth,
|
||||
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
||||
@@ -1929,22 +1938,19 @@ function up2k_init(subtle) {
|
||||
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
|
||||
btn = ebi('u2btn');
|
||||
|
||||
//console.log([wpx, fpx, wem]);
|
||||
if (btn.parentNode !== parent) {
|
||||
parent.appendChild(btn);
|
||||
ebi('u2conf').setAttribute('class', wide);
|
||||
ebi('u2cards').setAttribute('class', wide);
|
||||
ebi('u2etaw').setAttribute('class', wide);
|
||||
ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
|
||||
}
|
||||
|
||||
wide = write && wem > 78 ? 'ww' : wide;
|
||||
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
|
||||
var its = [ebi('u2etaw'), ebi('u2cards')];
|
||||
if (its[0].parentNode !== parent) {
|
||||
ebi('u2conf').setAttribute('class', wide);
|
||||
ebi('u2conf').className = wide;
|
||||
for (var a = 0; a < 2; 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,
|
||||
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;
|
||||
else if (!uc.turbo)
|
||||
else if (!msg)
|
||||
html = html.replace(msgu, '').replace(msgs, '');
|
||||
|
||||
if (html !== ohtml)
|
||||
@@ -2057,13 +2066,15 @@ function up2k_init(subtle) {
|
||||
|
||||
try {
|
||||
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);
|
||||
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
||||
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
||||
}
|
||||
catch (ex) { }
|
||||
|
||||
ebi('u2tab').className = (uc.fsearch ? 'srch ' : 'up ') + pvis.act;
|
||||
|
||||
draw_turbo();
|
||||
onresize();
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||
if ((msg + '').indexOf('l2d.js') !== -1)
|
||||
return; // `t` undefined in tapEvent -> hitTestSimpleCustom
|
||||
|
||||
if (!/\.js($|\?)/.exec('' + url))
|
||||
return; // chrome debugger
|
||||
|
||||
var ekey = url + '\n' + lineNo + '\n' + msg;
|
||||
if (ignexd[ekey] || crashed)
|
||||
return;
|
||||
@@ -327,11 +330,21 @@ function clgot(el, cls) {
|
||||
if (el.classList)
|
||||
return el.classList.contains(cls);
|
||||
|
||||
var lst = (el.getAttribute('class') + '').split(/ /g);
|
||||
var lst = (el.className + '').split(/ /g);
|
||||
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) {
|
||||
var v, vn, v1, v2, th = tab.tHead,
|
||||
sopts = jread('fsort', [["href", 1, ""]]);
|
||||
@@ -872,7 +885,7 @@ var tt = (function () {
|
||||
};
|
||||
|
||||
r.getmsg = function (el) {
|
||||
if (QS('body.bbox-open'))
|
||||
if (IPHONE && QS('body.bbox-open'))
|
||||
return;
|
||||
|
||||
var cfg = sread('tooltips');
|
||||
|
||||
@@ -4,7 +4,7 @@ ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
|
||||
ver_hashwasm=4.9.0 \
|
||||
ver_marked=4.0.12 \
|
||||
ver_mde=2.16.1 \
|
||||
ver_codemirror=5.65.2 \
|
||||
ver_codemirror=5.65.3 \
|
||||
ver_fontawesome=5.13.0 \
|
||||
ver_zopfli=1.0.3
|
||||
|
||||
|
||||
@@ -342,14 +342,15 @@ def get_payload():
|
||||
|
||||
|
||||
def utime(top):
|
||||
# avoid cleaners
|
||||
i = 0
|
||||
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())
|
||||
if i:
|
||||
msg("utime {}, {}".format(i, t))
|
||||
|
||||
for f in files:
|
||||
for f in [top] + files:
|
||||
os.utime(f, (t, t))
|
||||
|
||||
i += 1
|
||||
@@ -374,16 +375,6 @@ def run(tmp, j2, ftp):
|
||||
msg("sfxdir:", tmp)
|
||||
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.daemon = True
|
||||
t.start()
|
||||
|
||||
3
setup.py
3
setup.py
@@ -114,9 +114,10 @@ args = {
|
||||
"install_requires": ["jinja2"],
|
||||
"extras_require": {
|
||||
"thumbnails": ["Pillow"],
|
||||
"thumbnails2": ["pyvips"],
|
||||
"audiotags": ["mutagen"],
|
||||
"ftpd": ["pyftpdlib"],
|
||||
"ftps": ["pyopenssl"],
|
||||
"ftps": ["pyftpdlib", "pyopenssl"],
|
||||
},
|
||||
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
|
||||
"scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"],
|
||||
|
||||
@@ -38,6 +38,9 @@ class Cfg(Namespace):
|
||||
no_mv=False,
|
||||
no_del=False,
|
||||
no_zip=False,
|
||||
no_thumb=False,
|
||||
no_athumb=False,
|
||||
no_vthumb=False,
|
||||
no_voldump=True,
|
||||
no_scandir=False,
|
||||
no_sendfile=True,
|
||||
@@ -53,6 +56,9 @@ class Cfg(Namespace):
|
||||
textfiles="",
|
||||
doctitle="",
|
||||
html_head="",
|
||||
theme=0,
|
||||
themes=0,
|
||||
turbo=0,
|
||||
hist=None,
|
||||
no_idx=None,
|
||||
no_hash=None,
|
||||
|
||||
@@ -17,7 +17,7 @@ from copyparty import util
|
||||
|
||||
class Cfg(Namespace):
|
||||
def __init__(self, a=None, v=None, c=None):
|
||||
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots"
|
||||
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots no_thumb no_athumb no_vthumb"
|
||||
ex = {k: False for k in ex.split()}
|
||||
ex2 = {
|
||||
"mtp": [],
|
||||
@@ -36,6 +36,9 @@ class Cfg(Namespace):
|
||||
"rsp_slp": 0,
|
||||
"s_wr_slp": 0,
|
||||
"s_wr_sz": 512 * 1024,
|
||||
"theme": 0,
|
||||
"themes": 0,
|
||||
"turbo": 0,
|
||||
}
|
||||
ex.update(ex2)
|
||||
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)
|
||||
|
||||
Reference in New Issue
Block a user