Compare commits

...

29 Commits

Author SHA1 Message Date
ed
dc2ea20959 v1.2.8 2022-04-30 02:16:34 +02:00
ed
8eaea2bd17 ux 2022-04-30 00:37:31 +02:00
ed
58e559918f fix dynamic tree sizing 2022-04-30 00:04:06 +02:00
ed
f38a3fca5b case-insensitive cover check 2022-04-29 23:39:16 +02:00
ed
1ea145b384 wow when did that break 2022-04-29 23:37:38 +02:00
ed
0d9567575a avoid hashing busy uploads during rescan 2022-04-29 23:16:23 +02:00
ed
e82f176289 fix deadlock on rescan during upload 2022-04-29 23:14:51 +02:00
ed
d4b51c040e doc + ux 2022-04-29 23:13:37 +02:00
ed
125d0efbd8 good stuff 2022-04-29 02:06:56 +02:00
ed
3215afc504 immediately search on enter key 2022-04-28 22:53:37 +02:00
ed
c73ff3ce1b avoid sqlite deadlock on windows 2022-04-28 22:46:53 +02:00
ed
f9c159a051 add option to force up2k turbo + hide warning 2022-04-28 21:57:37 +02:00
ed
2ab1325c90 add option to load more search results 2022-04-28 21:55:01 +02:00
ed
5b0f7ff506 perfect 2022-04-28 10:36:56 +02:00
ed
9269bc84f2 skip more stuff windows doesn't like 2022-04-28 10:31:10 +02:00
ed
4e8b651e18 too much effort into this joke 2022-04-28 10:29:54 +02:00
ed
65b4f79534 add themes "vice" and "hot dog stand" 2022-04-27 22:33:01 +02:00
ed
5dd43dbc45 ignore bugs in chrome v102 2022-04-27 22:32:11 +02:00
ed
5f73074c7e fix audio playback on first visit 2022-04-27 22:31:33 +02:00
ed
f5d6ba27b2 handle invalid headers better 2022-04-27 22:30:19 +02:00
ed
73fa70b41f fix mostly-harmless xss 2022-04-27 22:29:16 +02:00
ed
2a1cda42e7 avoid deadlocks on windows 2022-04-27 22:27:49 +02:00
ed
1bd7e31466 more theme porting 2022-04-26 00:42:00 +02:00
ed
eb49e1fb4a conditional up2k column sizes depending on card 2022-04-24 23:48:23 +02:00
ed
9838c2f0ce golf 2022-04-24 23:47:15 +02:00
ed
6041df8370 start replacing class-scopes with css variables 2022-04-24 23:46:38 +02:00
ed
2933dce3ef mtime blank uploads + helptext 2022-04-24 22:58:11 +02:00
ed
dab377d37b v1.2.7 2022-04-16 23:44:28 +02:00
ed
f35e41baf1 allow unposting with write-only access 2022-04-16 23:35:04 +02:00
24 changed files with 1097 additions and 949 deletions

View File

@@ -63,6 +63,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md) * [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags, also see [./bin/mtag/README.md](./bin/mtag/README.md)
* [upload events](#upload-events) - trigger a script/program on each upload * [upload events](#upload-events) - trigger a script/program on each upload
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed * [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
* [themes](#themes)
* [complete examples](#complete-examples) * [complete examples](#complete-examples)
* [browser support](#browser-support) - TLDR: yes * [browser support](#browser-support) - TLDR: yes
* [client examples](#client-examples) - interact with copyparty using non-browser clients * [client examples](#client-examples) - interact with copyparty using non-browser clients
@@ -247,6 +248,8 @@ some improvement ideas
## not my bugs ## not my bugs
* [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11) * iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume * *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
* "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day... * "future" because `AudioContext` is broken in the current iOS version (15.1), maybe one day...
@@ -806,6 +809,29 @@ tell search engines you dont wanna be indexed, either using the good old [robot
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
## themes
you can change the default theme with `--theme 2`, and add your own themes by modifying `browser.css` or providing your own css to `--css-browser`, then telling copyparty they exist by increasing `--themes`
<table><tr><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864907-17e2ac7d-319d-4f25-8718-2f376f614b51.png"><img src="https://user-images.githubusercontent.com/241032/165867551-fceb35dd-38f0-42bb-bef3-25ba651ca69b.png"></a>
0. classic dark</td><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864904-c5b67ddd-f383-4b9e-9f5a-a3bde183d256.png"><img src="https://user-images.githubusercontent.com/241032/165867556-077b6068-2488-4fae-bf88-1fce40e719bc.png"></a>
2. flat dark</td><td width="33%" align="center"><a href="https://user-images.githubusercontent.com/241032/165864901-db13a429-a5da-496d-8bc6-ce838547f69d.png"><img src="https://user-images.githubusercontent.com/241032/165867560-aa834aef-58dc-4abe-baef-7e562b647945.png"></a>
4. vice</td></tr><tr><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864905-692682eb-6fb4-4d40-b6fe-27d2c7d3e2a7.png"><img src="https://user-images.githubusercontent.com/241032/165867555-080b73b6-6d85-41bb-a7c6-ad277c608365.png"></a>
1. classic light</td><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864903-7fba1cb9-036b-4f11-90d5-28b7c0724353.png"><img src="https://user-images.githubusercontent.com/241032/165867557-b5cc0010-d880-48b1-8156-9c84f7bbc521.png"></a>
3. flat light
</td><td align="center"><a href="https://user-images.githubusercontent.com/241032/165864898-10ce7052-a117-4fcf-845b-b56c91687908.png"><img src="https://user-images.githubusercontent.com/241032/165867562-f3003d45-dd2a-4564-8aae-fed44c1ae064.png"></a>
5. <a href="https://blog.codinghorror.com/a-tribute-to-the-windows-31-hot-dog-stand-color-scheme/">hotdog stand</a></td></tr></table>
the classname of the HTML tag is set according to the selected theme, which is used to set colors as css variables ++
* each theme *generally* has a dark theme (even numbers) and a light theme (odd numbers), showing in pairs
* the first theme (theme 0 and 1) is `html.a`, second theme (2 and 3) is `html.b`
* if a light theme is selected, `html.y` is set, otherwise `html.z` is
* so if the dark edition of the 2nd theme is selected, you use any of `html.b`, `html.z`, `html.bz` to specify rules
see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where the color variables are set, and there's layout-specific stuff near the bottom
## complete examples ## complete examples
* read-only music server with bpm and key scanning * read-only music server with bpm and key scanning
@@ -1178,7 +1204,7 @@ python3 -m venv .venv
pip install jinja2 # mandatory pip install jinja2 # mandatory
pip install mutagen # audio metadata pip install mutagen # audio metadata
pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails pip install Pillow pyheif-pillow-opener pillow-avif-plugin # thumbnails
pip install black bandit pylint flake8 # vscode tooling pip install black==21.12b0 bandit pylint flake8 # vscode tooling
``` ```

View File

@@ -12,6 +12,8 @@
# change '/mnt::rw' to another location or permission-set # change '/mnt::rw' to another location or permission-set
# remove '-p 80,443,3923' to only listen on port 3923 # remove '-p 80,443,3923' to only listen on port 3923
# add '-i 127.0.0.1' to only allow local connections # add '-i 127.0.0.1' to only allow local connections
# add '-e2dsa' to enable filesystem scanning + indexing
# add '-e2ts' to enable metadata indexing
# #
# with `Type=notify`, copyparty will signal systemd when it is ready to # with `Type=notify`, copyparty will signal systemd when it is ready to
# accept connections; correctly delaying units depending on copyparty. # accept connections; correctly delaying units depending on copyparty.
@@ -34,7 +36,7 @@ SyslogIdentifier=copyparty
Environment=PYTHONUNBUFFERED=x Environment=PYTHONUNBUFFERED=x
ExecReload=/bin/kill -s USR1 $MAINPID ExecReload=/bin/kill -s USR1 $MAINPID
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf' ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -v /mnt::rw ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -p 80,443,3923 -e2d -v /mnt::rw
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

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

View File

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

View File

@@ -11,12 +11,13 @@ import hashlib
import threading import threading
from datetime import datetime from datetime import datetime
from .__init__ import WINDOWS from .__init__ import ANYWIN, WINDOWS
from .util import ( from .util import (
IMPLICATIONS, IMPLICATIONS,
META_NOBOTS, META_NOBOTS,
uncyg, uncyg,
undot, undot,
relchk,
unhumanize, unhumanize,
absreal, absreal,
Pebkac, Pebkac,
@@ -335,6 +336,12 @@ class VFS(object):
): ):
# type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str] # type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str]
"""returns [vfsnode,fs_remainder] if user has the requested permissions""" """returns [vfsnode,fs_remainder] if user has the requested permissions"""
if ANYWIN:
mod = relchk(vpath)
if mod:
self.log("vfs", "invalid relpath [{}]".format(vpath))
raise Pebkac(404)
vn, rem = self._find(vpath) vn, rem = self._find(vpath)
c = vn.axs c = vn.axs

View File

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

View File

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

View File

@@ -95,7 +95,7 @@ class Up2k(object):
if ANYWIN: if ANYWIN:
# usually fails to set lastmod too quickly # usually fails to set lastmod too quickly
self.lastmod_q = Queue() self.lastmod_q = []
thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod") thr = threading.Thread(target=self._lastmodder, name="up2k-lastmod")
thr.daemon = True thr.daemon = True
thr.start() thr.start()
@@ -583,9 +583,11 @@ class Up2k(object):
self.pp.msg = "a{} {}".format(self.pp.n, cdir) self.pp.msg = "a{} {}".format(self.pp.n, cdir)
histpath = self.asrv.vfs.histtab[top] histpath = self.asrv.vfs.histtab[top]
ret = 0 ret = 0
seen_files = {} seen_files = {} # != inames; files-only for dropcheck
g = statdir(self.log_func, not self.args.no_scandir, False, cdir) g = statdir(self.log_func, not self.args.no_scandir, False, cdir)
for iname, inf in sorted(g): g = sorted(g)
inames = {x[0]: 1 for x in g}
for iname, inf in g:
abspath = os.path.join(cdir, iname) abspath = os.path.join(cdir, iname)
if rei and rei.search(abspath): if rei and rei.search(abspath):
continue continue
@@ -612,6 +614,17 @@ class Up2k(object):
if WINDOWS: if WINDOWS:
rp = rp.replace("\\", "/").strip("/") rp = rp.replace("\\", "/").strip("/")
if rp.endswith(".PARTIAL") and time.time() - lmod < 60:
# rescan during upload
continue
if not sz and (
"{}.PARTIAL".format(iname) in inames
or ".{}.PARTIAL".format(iname) in inames
):
# placeholder for unfinished upload
continue
rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp] rd, fn = rp.rsplit("/", 1) if "/" in rp else ["", rp]
sql = "select w, mt, sz from up where rd = ? and fn = ?" sql = "select w, mt, sz from up where rd = ? and fn = ?"
try: try:
@@ -781,6 +794,7 @@ class Up2k(object):
if self.mtag.prefer_mt and self.args.mtag_mt > 1: if self.mtag.prefer_mt and self.args.mtag_mt > 1:
mpool = self._start_mpool() mpool = self._start_mpool()
# TODO blocks writes to registry cursor; do chunks instead
conn = sqlite3.connect(db_path, timeout=15) conn = sqlite3.connect(db_path, timeout=15)
cur = conn.cursor() cur = conn.cursor()
c2 = conn.cursor() c2 = conn.cursor()
@@ -806,8 +820,8 @@ class Up2k(object):
n_tags = self._tag_file(c3, *args) n_tags = self._tag_file(c3, *args)
else: else:
mpool.put(["mtag"] + args) mpool.put(["mtag"] + args)
with self.mutex: # not registry cursor; do not self.mutex:
n_tags = len(self._flush_mpool(c3)) n_tags = len(self._flush_mpool(c3))
n_add += n_tags n_add += n_tags
n_buf += n_tags n_buf += n_tags
@@ -830,9 +844,6 @@ class Up2k(object):
cur.close() cur.close()
conn.close() conn.close()
with self.mutex:
gcur.connection.commit()
return n_add, n_rm, True return n_add, n_rm, True
def _flush_mpool(self, wcur): def _flush_mpool(self, wcur):
@@ -1111,7 +1122,8 @@ class Up2k(object):
return ret return ret
def _orz(self, db_path): def _orz(self, db_path):
return sqlite3.connect(db_path, check_same_thread=False).cursor() timeout = int(max(self.args.srch_time, 5) * 1.2)
return sqlite3.connect(db_path, timeout, check_same_thread=False).cursor()
# x.set_trace_callback(trace) # x.set_trace_callback(trace)
def _open_db(self, db_path): def _open_db(self, db_path):
@@ -1480,7 +1492,7 @@ class Up2k(object):
if lmod and (not linked or SYMTIME): if lmod and (not linked or SYMTIME):
times = (int(time.time()), int(lmod)) times = (int(time.time()), int(lmod))
if ANYWIN: if ANYWIN:
self.lastmod_q.put([dst, 0, times]) self.lastmod_q.append([dst, 0, times])
else: else:
bos.utime(dst, times, False) bos.utime(dst, times, False)
@@ -1574,9 +1586,15 @@ class Up2k(object):
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4) # self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
atomic_move(src, dst) atomic_move(src, dst)
times = (int(time.time()), int(job["lmod"]))
if ANYWIN: if ANYWIN:
a = [dst, job["size"], (int(time.time()), int(job["lmod"]))] a = [dst, job["size"], times]
self.lastmod_q.put(a) self.lastmod_q.append(a)
elif not job["hash"]:
try:
bos.utime(dst, times)
except:
pass
a = [job[x] for x in "ptop wark prel name lmod size addr".split()] a = [job[x] for x in "ptop wark prel name lmod size addr".split()]
a += [job.get("at") or time.time()] a += [job.get("at") or time.time()]
@@ -1675,12 +1693,12 @@ class Up2k(object):
vn, rem = vn.get_dbv(rem) vn, rem = vn.get_dbv(rem)
unpost = False unpost = False
except: except:
# unpost with missing permissions? try read+write and verify with db # unpost with missing permissions? verify with db
if not self.args.unpost: if not self.args.unpost:
raise Pebkac(400, "the unpost feature is disabled in server config") raise Pebkac(400, "the unpost feature is disabled in server config")
unpost = True unpost = True
permsets = [[True, True]] permsets = [[False, True]]
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0]) vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
vn, rem = vn.get_dbv(rem) vn, rem = vn.get_dbv(rem)
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem) _, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
@@ -2064,9 +2082,8 @@ class Up2k(object):
def _lastmodder(self): def _lastmodder(self):
while True: while True:
ready = [] ready = self.lastmod_q
while not self.lastmod_q.empty(): self.lastmod_q = []
ready.append(self.lastmod_q.get())
# self.log("lmod: got {}".format(len(ready))) # self.log("lmod: got {}".format(len(ready)))
time.sleep(5) time.sleep(5)

View File

@@ -912,6 +912,9 @@ def sanitize_fn(fn, ok, bad):
if "/" not in ok: if "/" not in ok:
fn = fn.replace("\\", "/").split("/")[-1] fn = fn.replace("\\", "/").split("/")[-1]
if fn.lower() in bad:
fn = "_" + fn
if ANYWIN: if ANYWIN:
remap = [ remap = [
["<", ""], ["<", ""],
@@ -927,16 +930,26 @@ def sanitize_fn(fn, ok, bad):
for a, b in [x for x in remap if x[0] not in ok]: for a, b in [x for x in remap if x[0] not in ok]:
fn = fn.replace(a, b) fn = fn.replace(a, b)
bad.extend(["con", "prn", "aux", "nul"]) bad = ["con", "prn", "aux", "nul"]
for n in range(1, 10): for n in range(1, 10):
bad += "com{0} lpt{0}".format(n).split(" ") bad += "com{0} lpt{0}".format(n).split(" ")
if fn.lower() in bad: if fn.lower().split(".")[0] in bad:
fn = "_" + fn fn = "_" + fn
return fn.strip() return fn.strip()
def relchk(rp):
if ANYWIN:
if "\n" in rp or "\r" in rp:
return "x\nx"
p = re.sub(r'[\\:*?"<>|]', "", rp)
if p != rp:
return "[{}]".format(p)
def absreal(fpath): def absreal(fpath):
try: try:
return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath)))) return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath))))

File diff suppressed because it is too large Load Diff

View File

@@ -146,12 +146,13 @@
have_del = {{ have_del|tojson }}, have_del = {{ have_del|tojson }},
have_unpost = {{ have_unpost|tojson }}, have_unpost = {{ have_unpost|tojson }},
have_zip = {{ have_zip|tojson }}, have_zip = {{ have_zip|tojson }},
turbolvl = {{ turbolvl|tojson }},
txt_ext = "{{ txt_ext }}", txt_ext = "{{ txt_ext }}",
{% if no_prism %}no_prism = 1,{% endif %} {% if no_prism %}no_prism = 1,{% endif %}
readme = {{ readme|tojson }}, readme = {{ readme|tojson }},
ls0 = {{ ls0|tojson }}; ls0 = {{ ls0|tojson }};
document.documentElement.setAttribute("class", localStorage.theme || dtheme); document.documentElement.className = localStorage.theme || dtheme;
</script> </script>
<script src="/.cpr/util.js?_={{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script> <script src="/.cpr/baguettebox.js?_={{ ts }}"></script>

View File

@@ -452,7 +452,7 @@ var mpl = (function () {
cover = null; cover = null;
for (var a = 0, aa = files.length; a < aa; a++) { for (var a = 0, aa = files.length; a < aa; a++) {
if (/^(cover|folder)\.(jpe?g|png|gif)$/.test(files[a].textContent)) { if (/^(cover|folder)\.(jpe?g|png|gif)$/i.test(files[a].textContent)) {
cover = noq_href(files[a]); cover = noq_href(files[a]);
break; break;
} }
@@ -666,7 +666,7 @@ function ft2dict(tr) {
for (var a = 1, aa = th.length; a < aa; a++) { for (var a = 1, aa = th.length; a < aa; a++) {
var tv = tr.cells[a].textContent, var tv = tr.cells[a].textContent,
tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').pop(), tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').pop().toLowerCase(),
vis = th[a].className.indexOf('min') === -1; vis = th[a].className.indexOf('min') === -1;
if (!tv) if (!tv)
@@ -1414,7 +1414,7 @@ var audio_eq = (function () {
// plays the tid'th audio file on the page // plays the tid'th audio file on the page
function play(tid, is_ev, seek, call_depth) { function play(tid, is_ev, seek) {
if (mp.order.length == 0) if (mp.order.length == 0)
return console.log('no audio found wait what'); return console.log('no audio found wait what');
@@ -1913,9 +1913,6 @@ var fileman = (function () {
if (!md.hasOwnProperty(k)) if (!md.hasOwnProperty(k))
continue; continue;
md[k.toLowerCase()] = md[k];
k = k.toLowerCase();
if (k.startsWith('.')) if (k.startsWith('.'))
md[k.slice(1)] = md[k]; md[k.slice(1)] = md[k];
} }
@@ -2508,7 +2505,7 @@ var showfile = (function () {
el = el || QS('#doc>code'); el = el || QS('#doc>code');
Prism.highlightElement(el); Prism.highlightElement(el);
if (el.getAttribute('class') == 'language-ans') if (el.className == 'language-ans')
r.ansify(el); r.ansify(el);
} }
catch (ex) { } catch (ex) { }
@@ -2529,7 +2526,7 @@ var showfile = (function () {
el.textContent = txt; el.textContent = txt;
el.innerHTML = '<code>' + el.innerHTML + '</code>'; el.innerHTML = '<code>' + el.innerHTML + '</code>';
if (!window['no_prism']) { if (!window['no_prism']) {
el.setAttribute('class', 'prism linkable-line-numbers line-numbers language-' + lang); el.className = 'prism linkable-line-numbers line-numbers language-' + lang;
if (!defer) if (!defer)
fun(el.firstChild); fun(el.firstChild);
else else
@@ -2659,7 +2656,7 @@ var showfile = (function () {
}; };
var bdoc = ebi('bdoc'); var bdoc = ebi('bdoc');
bdoc.setAttribute('class', 'line-numbers'); bdoc.className = 'line-numbers';
bdoc.innerHTML = ( bdoc.innerHTML = (
'<div id="hdoc" class="ghead">\n' + '<div id="hdoc" class="ghead">\n' +
'<a href="#" class="btn" id="xdoc" tt="return to folder view$NHotkey: M">❌ close</a>\n' + '<a href="#" class="btn" id="xdoc" tt="return to folder view$NHotkey: M">❌ close</a>\n' +
@@ -2854,19 +2851,24 @@ var thegrid = (function () {
for (var a = 0, aa = ths.length; a < aa; a++) { for (var a = 0, aa = ths.length; a < aa; a++) {
var tr = ebi(ths[a].getAttribute('ref')).closest('tr'), var tr = ebi(ths[a].getAttribute('ref')).closest('tr'),
cl = tr.getAttribute('class') || ''; cl = tr.className || '';
if (noq_href(ths[a]).endsWith('/')) if (noq_href(ths[a]).endsWith('/'))
cl += ' dir'; cl += ' dir';
ths[a].setAttribute('class', cl); ths[a].className = cl;
} }
var uns = QS('#ggrid a[ref="unsearch"]');
if (uns) var sp = ['unsearch', 'moar'];
uns.onclick = function (e) { for (var a = 0; a < sp.length; a++)
ev(e); (function (a) {
ebi('unsearch').click(); var o = QS('#ggrid a[ref="' + sp[a] + '"]');
}; if (o)
o.onclick = function (e) {
ev(e);
ebi(sp[a]).click();
};
})(a);
}; };
r.tippen = function () { r.tippen = function () {
@@ -3284,7 +3286,8 @@ document.onkeydown = function (e) {
var trs = [], var trs = [],
orig_url = null, orig_url = null,
orig_html = null; orig_html = null,
cap = 125;
for (var a = 0; a < sconf.length; a++) { for (var a = 0; a < sconf.length; a++) {
var html = ['<tr><td><br />' + sconf[a][0] + '</td>']; var html = ['<tr><td><br />' + sconf[a][0] + '</td>'];
@@ -3313,12 +3316,13 @@ document.onkeydown = function (e) {
var o = QSA('#op_search input'); var o = QSA('#op_search input');
for (var a = 0; a < o.length; a++) { for (var a = 0; a < o.length; a++) {
o[a].oninput = ev_search_input; o[a].oninput = ev_search_input;
o[a].onkeydown = ev_search_keydown;
} }
function srch_msg(err, txt) { function srch_msg(err, txt) {
var o = ebi('srch_q'); var o = ebi('srch_q');
o.textContent = txt; o.textContent = txt;
o.style.color = err ? '#f09' : '#c90'; clmod(o, 'err', err);
} }
var search_timeout, var search_timeout,
@@ -3338,12 +3342,18 @@ document.onkeydown = function (e) {
encode_query(); encode_query();
set_vq(); set_vq();
cap = 125;
clearTimeout(defer_timeout); clearTimeout(defer_timeout);
defer_timeout = setTimeout(try_search, 2000); defer_timeout = setTimeout(try_search, 2000);
try_search(v); try_search(v);
} }
function ev_search_keydown(e) {
if (e.key == 'Enter')
do_search();
}
function try_search(v) { function try_search(v) {
if (Date.now() - search_in_progress > 30 * 1000) { if (Date.now() - search_in_progress > 30 * 1000) {
clearTimeout(defer_timeout); clearTimeout(defer_timeout);
@@ -3372,8 +3382,6 @@ document.onkeydown = function (e) {
vs = ebi('srch_' + k + 'v').value, vs = ebi('srch_' + k + 'v').value,
tvs = []; tvs = [];
if (k == 'name')
console.log('a');
while (vs) { while (vs) {
vs = vs.trim(); vs = vs.trim();
if (!vs) if (!vs)
@@ -3462,7 +3470,7 @@ document.onkeydown = function (e) {
xhr.onreadystatechange = xhr_search_results; xhr.onreadystatechange = xhr_search_results;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.q_raw = ebi('q_raw').value; xhr.q_raw = ebi('q_raw').value;
xhr.send(JSON.stringify({ "q": xhr.q_raw })); xhr.send(JSON.stringify({ "q": xhr.q_raw, "n": cap }));
} }
function xhr_search_results() { function xhr_search_results() {
@@ -3495,7 +3503,9 @@ document.onkeydown = function (e) {
var html = mk_files_header(tagord), seen = {}; var html = mk_files_header(tagord), seen = {};
html.push('<tbody>'); html.push('<tbody>');
html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch"><big style="font-weight:bold">[❌] close search results</big></a></td></tr>'); html.push('<tr class="srch_hdr"><td>-</td><td><a href="#" id="unsearch"><big style="font-weight:bold">[❌] close search results</big></a> -- showing ' +
res.hits.length + ' hits' + (res.hits.length == cap ? ' -- <a href="#" id="moar">load more</a>' : '') + '</td></tr>');
for (var a = 0; a < res.hits.length; a++) { for (var a = 0; a < res.hits.length; a++) {
var r = res.hits[a], var r = res.hits[a],
ts = parseInt(r.ts), ts = parseInt(r.ts),
@@ -3547,6 +3557,9 @@ document.onkeydown = function (e) {
sethash('q=' + uricom_enc(this.q_raw)); sethash('q=' + uricom_enc(this.q_raw));
ebi('unsearch').onclick = unsearch; ebi('unsearch').onclick = unsearch;
var m = ebi('moar');
if (m)
m.onclick = moar;
} }
function unsearch(e) { function unsearch(e) {
@@ -3558,6 +3571,12 @@ document.onkeydown = function (e) {
sethash(''); sethash('');
reload_browser(); reload_browser();
} }
function moar(e) {
ev(e);
cap *= 2;
do_search();
}
})(); })();
@@ -3844,7 +3863,6 @@ var treectl = (function () {
QS('#treeul>li>a+a').textContent = '[root]'; QS('#treeul>li>a+a').textContent = '[root]';
despin('#tree'); despin('#tree');
reload_tree(); reload_tree();
onresize();
var fun = r.dir_cb; var fun = r.dir_cb;
if (fun) { if (fun) {
@@ -3876,7 +3894,7 @@ var treectl = (function () {
cl = 'par'; cl = 'par';
} }
links[a].setAttribute('class', cl); links[a].className = cl;
links[a].onclick = treego; links[a].onclick = treego;
links[a].onmouseenter = nowrap ? menter : null; links[a].onmouseenter = nowrap ? menter : null;
links[a].onmouseleave = nowrap ? mleave : null; links[a].onmouseleave = nowrap ? mleave : null;
@@ -3897,6 +3915,7 @@ var treectl = (function () {
catch (ex) { } catch (ex) { }
r.pdir.shift(); r.pdir.shift();
r.pdirw = -1; r.pdirw = -1;
onresize();
} }
function compy() { function compy() {
@@ -3943,7 +3962,7 @@ var treectl = (function () {
return true; return true;
ev(e); ev(e);
if (this.getAttribute('class') == 'hl' && if (this.className == 'hl' &&
this.previousSibling.textContent == '-') { this.previousSibling.textContent == '-') {
treegrow.call(this.previousSibling, e); treegrow.call(this.previousSibling, e);
return; return;
@@ -4029,7 +4048,6 @@ var treectl = (function () {
r.ls_cb = null; r.ls_cb = null;
fun(); fun();
} }
eval_hash();
} }
r.gentab = function (top, res) { r.gentab = function (top, res) {
@@ -4094,6 +4112,7 @@ var treectl = (function () {
apply_perms(res.perms); apply_perms(res.perms);
fileman.render(); fileman.render();
} }
setTimeout(eval_hash, 1);
} }
var m = scan_hash(hash0), var m = scan_hash(hash0),
@@ -4209,7 +4228,7 @@ var treectl = (function () {
function enspin(sel) { function enspin(sel) {
despin(sel); despin(sel);
var d = mknod('div'); var d = mknod('div');
d.setAttribute('class', 'dumb_loader_thing'); d.className = 'dumb_loader_thing';
d.innerHTML = '🌲'; d.innerHTML = '🌲';
var tgt = QS(sel); var tgt = QS(sel);
tgt.insertBefore(d, tgt.childNodes[0]); tgt.insertBefore(d, tgt.childNodes[0]);
@@ -4315,7 +4334,7 @@ function find_file_col(txt) {
for (var a = 0; a < tds.length; a++) { for (var a = 0; a < tds.length; a++) {
var spans = tds[a].getElementsByTagName('span'); var spans = tds[a].getElementsByTagName('span');
if (spans.length && spans[0].textContent == txt) { if (spans.length && spans[0].textContent == txt) {
min = (tds[a].getAttribute('class') || '').indexOf('min') !== -1; min = (tds[a].className || '').indexOf('min') !== -1;
i = a; i = a;
break; break;
} }
@@ -4461,7 +4480,7 @@ var filecols = (function () {
tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')'); tds = QSA('#files>tbody>tr>td:nth-child(' + (a + 1) + ')');
for (var b = 0, bb = tds.length; b < bb; b++) for (var b = 0, bb = tds.length; b < bb; b++)
tds[b].setAttribute('class', cls); tds[b].className = cls;
} }
if (window['tt']) { if (window['tt']) {
tt.att(ebi('hcols')); tt.att(ebi('hcols'));
@@ -4623,18 +4642,21 @@ var settheme = (function () {
light = !!(theme.indexOf('y') + 1); light = !!(theme.indexOf('y') + 1);
function freshen() { function freshen() {
var cl = document.documentElement.getAttribute('class'); var cl = document.documentElement.className;
cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' '); cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' ');
document.documentElement.setAttribute('class', cl + ' ' + theme + ' '); document.documentElement.className = cl + ' ' + theme + ' ';
pbar.drawbuf(); pbar.drawbuf();
pbar.drawpos(); pbar.drawpos();
vbar.draw(); vbar.draw();
showfile.setstyle(); showfile.setstyle();
var html = [], itheme = ax.indexOf(theme.charAt(0)) * 2 + (light ? 1 : 0); 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++) for (var a = 0; a < themes; a++)
html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') + '">' + a + '</a>'); html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') +
'" tt="' + (names[a] || 'custom') + '">' + a + '</a>');
ebi('themes').innerHTML = html.join(''); ebi('themes').innerHTML = html.join('');
var btns = QSA('#themes a'); var btns = QSA('#themes a');
@@ -4642,6 +4664,7 @@ var settheme = (function () {
btns[a].onclick = settheme; btns[a].onclick = settheme;
bcfg_set('light', light); bcfg_set('light', light);
tt.att(ebi('themes'));
} }
function settheme(e) { function settheme(e) {
@@ -4903,7 +4926,10 @@ var msel = (function () {
tb.value = ''; tb.value = '';
clmod(sf, 'vis'); clmod(sf, 'vis');
sf.textContent = ''; sf.textContent = '';
treectl.goto(this.vp + uricom_enc(this.dn) + '/', true);
var dn = this.getResponseHeader('X-New-Dir');
dn = dn || uricom_enc(this.dn);
treectl.goto(this.vp + dn + '/', true);
} }
})(); })();
@@ -5075,7 +5101,7 @@ var unpost = (function () {
html.push("<table><thead><tr><td></td><td>time</td><td>size</td><td>file</td></tr></thead><tbody>"); html.push("<table><thead><tr><td></td><td>time</td><td>size</td><td>file</td></tr></thead><tbody>");
} }
else else
html.push("<p>sike! no uploads " + (filt.value ? 'matching that filter' : '') + " are sufficiently recent</p>"); html.push("sike! no uploads " + (filt.value ? 'matching that filter' : '') + " are sufficiently recent");
var mods = [1000, 100, 10]; var mods = [1000, 100, 10];
for (var a = 0; a < res.length; a++) { for (var a = 0; a < res.length; a++) {

View File

@@ -161,7 +161,7 @@ blink {
height: 1.05em; height: 1.05em;
margin: -.2em .3em -.2em -.4em; margin: -.2em .3em -.2em -.4em;
display: inline-block; display: inline-block;
border: 1px solid rgba(0,0,0,0.2); border: 1px solid rgba(154,154,154,0.6);
border-width: .2em .2em 0 0; border-width: .2em .2em 0 0;
transform: rotate(45deg); transform: rotate(45deg);
} }
@@ -236,9 +236,6 @@ blink {
html.z #toc li { html.z #toc li {
border-width: 0; border-width: 0;
} }
html.z #mn a:not(:last-child)::after {
border-color: rgba(255,255,255,0.3);
}
html.z #mn a { html.z #mn a {
color: #ccc; color: #ccc;
} }

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ var lightswitch = (function () {
drk = l.light != 1, drk = l.light != 1,
f = function (e) { f = function (e) {
if (e) drk = !drk; if (e) drk = !drk;
document.documentElement.setAttribute("class", drk? "z":"y"); document.documentElement.className = drk? "z":"y";
l.light = drk? 0:1; l.light = drk? 0:1;
}; };
f(); f();

View File

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

View File

@@ -158,6 +158,7 @@ html {
color: #f6a; color: #f6a;
} }
html.y #tt { html.y #tt {
color: #333;
background: #fff; background: #fff;
border-color: #888 #000 #777 #000; border-color: #888 #000 #777 #000;
} }

View File

@@ -135,7 +135,7 @@ function up2k_flagbus() {
} }
function U2pvis(act, btns) { function U2pvis(act, btns, uc) {
var r = this; var r = this;
r.act = act; r.act = act;
r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 }; r.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
@@ -425,7 +425,9 @@ function U2pvis(act, btns) {
html.push(r.genrow(a, true).replace(/><td>/, "><td>b ")); html.push(r.genrow(a, true).replace(/><td>/, "><td>b "));
} }
} }
ebi('u2tab').tBodies[0].innerHTML = html.join('\n'); var el = ebi('u2tab');
el.tBodies[0].innerHTML = html.join('\n');
el.className = (uc.fsearch ? 'srch ' : 'up ') + r.act;
}; };
r.genrow = function (nfile, as_html) { r.genrow = function (nfile, as_html) {
@@ -624,11 +626,11 @@ function up2k_init(subtle) {
function setmsg(msg, type) { function setmsg(msg, type) {
if (msg !== undefined) { if (msg !== undefined) {
ebi('u2err').setAttribute('class', type); ebi('u2err').className = type;
ebi('u2err').innerHTML = msg; ebi('u2err').innerHTML = msg;
} }
else { else {
ebi('u2err').setAttribute('class', ''); ebi('u2err').className = '';
ebi('u2err').innerHTML = ''; ebi('u2err').innerHTML = '';
} }
if (msg == suggest_up2k) { if (msg == suggest_up2k) {
@@ -665,8 +667,8 @@ function up2k_init(subtle) {
bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false); bcfg_bind(uc, 'ask_up', 'ask_up', true, null, false);
bcfg_bind(uc, 'flag_en', 'flag_en', false, apply_flag_cfg); bcfg_bind(uc, 'flag_en', 'flag_en', false, apply_flag_cfg);
bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false); bcfg_bind(uc, 'fsearch', 'fsearch', false, set_fsearch, false);
bcfg_bind(uc, 'turbo', 'u2turbo', false, draw_turbo, false); bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo, false);
bcfg_bind(uc, 'datechk', 'u2tdate', true, null, false); bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null, false);
var st = { var st = {
"files": [], "files": [],
@@ -705,7 +707,7 @@ function up2k_init(subtle) {
}); });
} }
var pvis = new U2pvis("bz", '#u2cards'), var pvis = new U2pvis("bz", '#u2cards', uc),
donut = new Donut(uc, st); donut = new Donut(uc, st);
var bobslice = null; var bobslice = null;
@@ -1938,19 +1940,17 @@ function up2k_init(subtle) {
if (btn.parentNode !== parent) { if (btn.parentNode !== parent) {
parent.appendChild(btn); parent.appendChild(btn);
ebi('u2conf').setAttribute('class', wide); ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
ebi('u2cards').setAttribute('class', wide);
ebi('u2etaw').setAttribute('class', wide);
} }
wide = write && wem > 78 ? 'ww' : wide; wide = write && wem > 78 ? 'ww' : wide;
parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t'); parent = ebi(wide == 'ww' && write ? 'u2c3w' : 'u2c3t');
var its = [ebi('u2etaw'), ebi('u2cards')]; var its = [ebi('u2etaw'), ebi('u2cards')];
if (its[0].parentNode !== parent) { if (its[0].parentNode !== parent) {
ebi('u2conf').setAttribute('class', wide); ebi('u2conf').className = wide;
for (var a = 0; a < 2; a++) { for (var a = 0; a < 2; a++) {
parent.appendChild(its[a]); parent.appendChild(its[a]);
its[a].setAttribute('class', wide); its[a].className = wide;
} }
} }
} }
@@ -2024,9 +2024,12 @@ function up2k_init(subtle) {
html = ebi('u2foot').innerHTML, html = ebi('u2foot').innerHTML,
ohtml = html; ohtml = html;
if (uc.turbo && html.indexOf(msg) === -1) if (turbolvl || !uc.turbo)
msg = null;
if (msg && html.indexOf(msg) === -1)
html = html.replace(omsg, '') + msg; html = html.replace(omsg, '') + msg;
else if (!uc.turbo) else if (!msg)
html = html.replace(msgu, '').replace(msgs, ''); html = html.replace(msgu, '').replace(msgs, '');
if (html !== ohtml) if (html !== ohtml)
@@ -2070,6 +2073,8 @@ function up2k_init(subtle) {
} }
catch (ex) { } catch (ex) { }
ebi('u2tab').className = (uc.fsearch ? 'srch ' : 'up ') + pvis.act;
draw_turbo(); draw_turbo();
onresize(); onresize();
} }

View File

@@ -89,6 +89,9 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
if ((msg + '').indexOf('l2d.js') !== -1) if ((msg + '').indexOf('l2d.js') !== -1)
return; // `t` undefined in tapEvent -> hitTestSimpleCustom return; // `t` undefined in tapEvent -> hitTestSimpleCustom
if (!/\.js($|\?)/.exec('' + url))
return; // chrome debugger
var ekey = url + '\n' + lineNo + '\n' + msg; var ekey = url + '\n' + lineNo + '\n' + msg;
if (ignexd[ekey] || crashed) if (ignexd[ekey] || crashed)
return; return;
@@ -327,7 +330,7 @@ function clgot(el, cls) {
if (el.classList) if (el.classList)
return el.classList.contains(cls); return el.classList.contains(cls);
var lst = (el.getAttribute('class') + '').split(/ /g); var lst = (el.className + '').split(/ /g);
return has(lst, cls); return has(lst, cls);
} }

View File

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

View File

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

View File

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