Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ec4daacf9e | ||
|
|
f3e8308718 | ||
|
|
515ac5d941 | ||
|
|
954c7e7e50 | ||
|
|
67ff57f3a3 | ||
|
|
c10c70c1e5 | ||
|
|
04592a98d2 | ||
|
|
c9c4aac6cf | ||
|
|
8b2c7586ce | ||
|
|
32e22dfe84 | ||
|
|
d70b885722 | ||
|
|
ac6c4b13f5 | ||
|
|
ececdad22d | ||
|
|
bf659781b0 | ||
|
|
2c6bb195a4 | ||
|
|
c032cd08b3 | ||
|
|
39e7a7a231 | ||
|
|
6e14cd2c39 | ||
|
|
aab3baaea7 | ||
|
|
b8453c3b4f | ||
|
|
6ce0e2cd5b | ||
|
|
76beaae7f2 | ||
|
|
c1a7f9edbe | ||
|
|
b5f2fe2f0a | ||
|
|
98a90d49cb | ||
|
|
f55e982cb5 | ||
|
|
686c7defeb | ||
|
|
0b1e483c53 | ||
|
|
457d7df129 | ||
|
|
ce776a547c |
61
README.md
61
README.md
@@ -54,6 +54,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [other tricks](#other-tricks)
|
* [other tricks](#other-tricks)
|
||||||
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
|
||||||
* [server config](#server-config) - using arguments or config files, or a mix of both
|
* [server config](#server-config) - using arguments or config files, or a mix of both
|
||||||
|
* [ftp-server](#ftp-server) - an FTP server can be started using `--ftp 3921`
|
||||||
* [file indexing](#file-indexing)
|
* [file indexing](#file-indexing)
|
||||||
* [upload rules](#upload-rules) - set upload rules using volume flags
|
* [upload rules](#upload-rules) - set upload rules using volume flags
|
||||||
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
|
||||||
@@ -61,6 +62,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
||||||
* [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
|
||||||
* [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
|
||||||
@@ -82,7 +84,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
||||||
* [install recommended deps](#install-recommended-deps)
|
* [install recommended deps](#install-recommended-deps)
|
||||||
* [optional gpl stuff](#optional-gpl-stuff)
|
* [optional gpl stuff](#optional-gpl-stuff)
|
||||||
* [sfx](#sfx) - there are two self-contained "binaries"
|
* [sfx](#sfx) - the self-contained "binary"
|
||||||
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
||||||
* [install on android](#install-on-android)
|
* [install on android](#install-on-android)
|
||||||
* [reporting bugs](#reporting-bugs) - ideas for context to include in bug reports
|
* [reporting bugs](#reporting-bugs) - ideas for context to include in bug reports
|
||||||
@@ -154,6 +156,7 @@ feature summary
|
|||||||
* ☑ multiprocessing (actual multithreading)
|
* ☑ multiprocessing (actual multithreading)
|
||||||
* ☑ volumes (mountpoints)
|
* ☑ volumes (mountpoints)
|
||||||
* ☑ [accounts](#accounts-and-volumes)
|
* ☑ [accounts](#accounts-and-volumes)
|
||||||
|
* ☑ [ftp-server](#ftp-server)
|
||||||
* upload
|
* upload
|
||||||
* ☑ basic: plain multipart, ie6 support
|
* ☑ basic: plain multipart, ie6 support
|
||||||
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
||||||
@@ -171,7 +174,7 @@ feature summary
|
|||||||
* ☑ image gallery with webm player
|
* ☑ image gallery with webm player
|
||||||
* ☑ textfile browser with syntax hilighting
|
* ☑ textfile browser with syntax hilighting
|
||||||
* ☑ [thumbnails](#thumbnails)
|
* ☑ [thumbnails](#thumbnails)
|
||||||
* ☑ ...of images using Pillow
|
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
||||||
* ☑ ...of videos using FFmpeg
|
* ☑ ...of videos using FFmpeg
|
||||||
* ☑ ...of audio (spectrograms) using FFmpeg
|
* ☑ ...of audio (spectrograms) using FFmpeg
|
||||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||||
@@ -237,6 +240,8 @@ some improvement ideas
|
|||||||
|
|
||||||
## general bugs
|
## general bugs
|
||||||
|
|
||||||
|
* Windows: if the up2k db is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
|
||||||
|
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
|
||||||
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
|
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
|
||||||
* probably more, pls let me know
|
* probably more, pls let me know
|
||||||
|
|
||||||
@@ -398,7 +403,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`)
|
audio files are covnerted into spectrograms using FFmpeg unless you `--no-athumb` (and some FFmpeg builds may need `--th-ff-swr`)
|
||||||
|
|
||||||
@@ -621,6 +628,18 @@ using arguments or config files, or a mix of both:
|
|||||||
* or click the `[reload cfg]` button in the control-panel when logged in as admin
|
* or click the `[reload cfg]` button in the control-panel when logged in as admin
|
||||||
|
|
||||||
|
|
||||||
|
## ftp-server
|
||||||
|
|
||||||
|
an FTP server can be started using `--ftp 3921`, and/or `--ftps` for explicit TLS (ftpes)
|
||||||
|
|
||||||
|
* based on [pyftpdlib](https://github.com/giampaolo/pyftpdlib)
|
||||||
|
* needs a dedicated port (cannot share with the HTTP/HTTPS API)
|
||||||
|
* uploads are not resumable -- delete and restart if necessary
|
||||||
|
* runs in active mode by default, you probably want `--ftp-pr 12000-13000`
|
||||||
|
* if you enable both `ftp` and `ftps`, the port-range will be divided in half
|
||||||
|
* some older software (filezilla on debian-stable) cannot passive-mode with TLS
|
||||||
|
|
||||||
|
|
||||||
## file indexing
|
## file indexing
|
||||||
|
|
||||||
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volume flags, or a mix of both.
|
file indexing relies on two database tables, the up2k filetree (`-e2d`) and the metadata tags (`-e2t`), stored in `.hist/up2k.db`. Configuration can be done through arguments, volume flags, or a mix of both.
|
||||||
@@ -776,6 +795,17 @@ and it will occupy the parsing threads, so fork anything expensive, or if you wa
|
|||||||
if this becomes popular maybe there should be a less janky way to do it actually
|
if this becomes popular maybe there should be a less janky way to do it actually
|
||||||
|
|
||||||
|
|
||||||
|
## hiding from google
|
||||||
|
|
||||||
|
tell search engines you dont wanna be indexed, either using the good old [robots.txt](https://www.robotstxt.org/robotstxt.html) or through copyparty settings:
|
||||||
|
|
||||||
|
* `--no-robots` adds HTTP (`X-Robots-Tag`) and HTML (`<meta>`) headers with `noindex, nofollow` globally
|
||||||
|
* volume-flag `[...]:c,norobots` does the same thing for that single volume
|
||||||
|
* volume-flag `[...]:c,robots` ALLOWS search-engine crawling for that volume, even if `--no-robots` is set globally
|
||||||
|
|
||||||
|
also, `--force-js` disables the plain HTML folder listing, making things harder to parse for search engines
|
||||||
|
|
||||||
|
|
||||||
## complete examples
|
## complete examples
|
||||||
|
|
||||||
* read-only music server with bpm and key scanning
|
* read-only music server with bpm and key scanning
|
||||||
@@ -1059,15 +1089,22 @@ mandatory deps:
|
|||||||
|
|
||||||
install these to enable bonus features
|
install these to enable bonus features
|
||||||
|
|
||||||
|
enable ftp-server:
|
||||||
|
* for just plaintext FTP, `pyftpdlib` (is built into the SFX)
|
||||||
|
* with TLS encryption, `pyftpdlib pyopenssl`
|
||||||
|
|
||||||
enable music tags:
|
enable music tags:
|
||||||
* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
|
* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
|
||||||
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
|
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
|
||||||
|
|
||||||
enable [thumbnails](#thumbnails) of...
|
enable [thumbnails](#thumbnails) of...
|
||||||
* **images:** `Pillow` (requires py2.7 or py3.5+)
|
* **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
|
||||||
* **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
|
* **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
|
||||||
* **HEIF pictures:** `pyheif-pillow-opener` (requires Linux or a C compiler)
|
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
|
||||||
* **AVIF pictures:** `pillow-avif-plugin`
|
* **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin`
|
||||||
|
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
|
||||||
|
|
||||||
|
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
|
||||||
|
|
||||||
|
|
||||||
## install recommended deps
|
## install recommended deps
|
||||||
@@ -1085,13 +1122,7 @@ these are standalone programs and will never be imported / evaluated by copypart
|
|||||||
|
|
||||||
# sfx
|
# sfx
|
||||||
|
|
||||||
there are two self-contained "binaries":
|
the self-contained "binary" [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) will unpack itself and run copyparty, assuming you have python installed of course
|
||||||
* [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) -- pure python, works everywhere, **recommended**
|
|
||||||
* [copyparty-sfx.sh](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.sh) -- smaller, but only for linux and macos, kinda deprecated
|
|
||||||
|
|
||||||
launch either of them (**use sfx.py on systemd**) and it'll unpack and run copyparty, assuming you have python installed of course
|
|
||||||
|
|
||||||
pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `copyparty.py` and keep it in the same folder because `sys.path` is funky
|
|
||||||
|
|
||||||
|
|
||||||
## sfx repack
|
## sfx repack
|
||||||
@@ -1166,8 +1197,8 @@ mv /tmp/pe-copyparty/copyparty/web/deps/ copyparty/web/deps/
|
|||||||
then build the sfx using any of the following examples:
|
then build the sfx using any of the following examples:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./scripts/make-sfx.sh # both python and sh editions
|
./scripts/make-sfx.sh # regular edition
|
||||||
./scripts/make-sfx.sh no-sh gz # just python with gzip
|
./scripts/make-sfx.sh gz no-cm # gzip-compressed + no fancy markdown editor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ set -e
|
|||||||
|
|
||||||
# install dependencies for audio-*.py
|
# install dependencies for audio-*.py
|
||||||
#
|
#
|
||||||
# linux/alpine: requires {python3,ffmpeg,fftw}-dev py3-{wheel,pip} py3-numpy{,-dev} patchelf cmake
|
# linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev}
|
||||||
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
|
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
|
||||||
# win64: requires msys2-mingw64 environment
|
# win64: requires msys2-mingw64 environment
|
||||||
# macos: requires macports
|
# macos: requires macports
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -122,5 +122,7 @@ export LOGNAME="$USER"
|
|||||||
#echo "pybin [$pybin]"
|
#echo "pybin [$pybin]"
|
||||||
#echo "pyarg [$pyarg]"
|
#echo "pyarg [$pyarg]"
|
||||||
#echo "cpp [$cpp]"
|
#echo "cpp [$cpp]"
|
||||||
chroot --userspec=$uid:$gid "$jail" "$pybin" $pyarg "$cpp" "$@"
|
chroot --userspec=$uid:$gid "$jail" "$pybin" $pyarg "$cpp" "$@" &
|
||||||
|
p=$!
|
||||||
|
trap 'kill $p' INT TERM
|
||||||
|
wait
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ save one of these as `.epilogue.html` inside a folder to customize it:
|
|||||||
## example browser-css
|
## example browser-css
|
||||||
point `--css-browser` to one of these by URL:
|
point `--css-browser` to one of these by URL:
|
||||||
|
|
||||||
* [`browser.css`](browser.css) changes the background
|
|
||||||
* [`browser-icons.css`](browser-icons.css) adds filetype icons
|
* [`browser-icons.css`](browser-icons.css) adds filetype icons
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
# if you remove -q to enable logging, you may also want to remove the
|
# if you remove -q to enable logging, you may also want to remove the
|
||||||
# following line to enable buffering (slightly better performance):
|
# following line to enable buffering (slightly better performance):
|
||||||
# Environment=PYTHONUNBUFFERED=x
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
#
|
||||||
|
# keep ExecStartPre before ExecStart, at least on rhel8
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=copyparty file server
|
Description=copyparty file server
|
||||||
|
|||||||
@@ -426,7 +426,9 @@ def run_argparse(argv, formatter):
|
|||||||
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")
|
||||||
ap2.add_argument("--no-symlink", action="store_true", help="duplicate file contents instead")
|
ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (same filesystem)")
|
||||||
|
ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made")
|
||||||
|
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead")
|
||||||
ap2.add_argument("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d")
|
ap2.add_argument("--reg-cap", metavar="N", type=int, default=9000, help="max number of uploads to keep in memory when running without -e2d")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('network options')
|
ap2 = ap.add_argument_group('network options')
|
||||||
@@ -445,6 +447,13 @@ def run_argparse(argv, formatter):
|
|||||||
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")
|
||||||
|
|
||||||
|
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("--ftps", metavar="PORT", type=int, help="enable FTPS server on PORT, for example 3990")
|
||||||
|
ap2.add_argument("--ftp-dbg", action="store_true", help="enable debug logging")
|
||||||
|
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections")
|
||||||
|
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="disable writes (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")
|
||||||
@@ -464,6 +473,8 @@ def run_argparse(argv, formatter):
|
|||||||
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("--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')
|
||||||
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
||||||
@@ -491,6 +502,7 @@ 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-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 for video thumbs")
|
||||||
@@ -499,6 +511,14 @@ def run_argparse(argv, formatter):
|
|||||||
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")
|
||||||
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 for")
|
||||||
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
|
# https://github.com/libvips/libvips
|
||||||
|
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
|
||||||
|
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="bmp,dib,gif,icns,ico,jpg,jpeg,jp2,jpx,pcx,png,pbm,pgm,ppm,pnm,sgi,tga,tif,tiff,webp,xbm,dds,xpm,heif,heifs,heic,heics,avif,avifs", help="image formats to decode using pillow")
|
||||||
|
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="jpg,jpeg,jp2,jpx,jxl,tif,tiff,png,webp,heic,avif,fit,fits,fts,exr,svg,hdr,ppm,pgm,pfm,gif,nii", help="image formats to decode using pyvips")
|
||||||
|
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,dds,dib,fit,fits,fts,gif,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
|
||||||
|
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="av1,asf,avi,flv,m4v,mkv,mjpeg,mjpg,mpg,mpeg,mpg2,mpeg2,h264,avc,mts,h265,hevc,mov,3gp,mp4,ts,mpegts,nut,ogv,ogm,rm,vob,webm,wmv", help="video formats to decode using ffmpeg")
|
||||||
|
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,m4a,ogg,opus,flac,alac,mp3,mp2,ac3,dts,wma,ra,wav,aif,aiff,au,alaw,ulaw,mulaw,amr,gsm,ape,tak,tta,wv,mpc", help="audio formats to decode using ffmpeg")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('transcoding options')
|
ap2 = ap.add_argument_group('transcoding options')
|
||||||
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
||||||
@@ -513,6 +533,7 @@ def run_argparse(argv, formatter):
|
|||||||
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")
|
||||||
|
ap2.add_argument("--srch-hits", metavar="N", type=int, default=1000, help="max search results")
|
||||||
|
|
||||||
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")
|
||||||
@@ -529,8 +550,11 @@ def run_argparse(argv, formatter):
|
|||||||
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 bin")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('ui options')
|
ap2 = ap.add_argument_group('ui options')
|
||||||
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
|
||||||
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=4, 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("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
||||||
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")
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 1, 12)
|
VERSION = (1, 2, 5)
|
||||||
CODENAME = "opus"
|
CODENAME = "ftp btw"
|
||||||
BUILD_DT = (2022, 1, 18)
|
BUILD_DT = (2022, 4, 15)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from datetime import datetime
|
|||||||
from .__init__ import WINDOWS
|
from .__init__ import WINDOWS
|
||||||
from .util import (
|
from .util import (
|
||||||
IMPLICATIONS,
|
IMPLICATIONS,
|
||||||
|
META_NOBOTS,
|
||||||
uncyg,
|
uncyg,
|
||||||
undot,
|
undot,
|
||||||
unhumanize,
|
unhumanize,
|
||||||
@@ -394,6 +395,13 @@ class VFS(object):
|
|||||||
if ok:
|
if ok:
|
||||||
virt_vis[name] = vn2
|
virt_vis[name] = vn2
|
||||||
|
|
||||||
|
if ".hist" in abspath:
|
||||||
|
p = abspath.replace("\\", "/") if WINDOWS else abspath
|
||||||
|
if p.endswith("/.hist"):
|
||||||
|
real = [x for x in real if not x[0].startswith("up2k.")]
|
||||||
|
elif "/.hist/th/" in p:
|
||||||
|
real = [x for x in real if not x[0].endswith("dir.txt")]
|
||||||
|
|
||||||
return [abspath, real, virt_vis]
|
return [abspath, real, virt_vis]
|
||||||
|
|
||||||
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat):
|
def walk(self, rel, rem, seen, uname, permsets, dots, scandir, lstat):
|
||||||
@@ -444,10 +452,6 @@ class VFS(object):
|
|||||||
if flt:
|
if flt:
|
||||||
flt = {k: True for k in flt}
|
flt = {k: True for k in flt}
|
||||||
|
|
||||||
f1 = "{0}.hist{0}up2k.".format(os.sep)
|
|
||||||
f2a = os.sep + "dir.txt"
|
|
||||||
f2b = "{0}.hist{0}".format(os.sep)
|
|
||||||
|
|
||||||
# if multiselect: add all items to archive root
|
# if multiselect: add all items to archive root
|
||||||
# if single folder: the folder itself is the top-level item
|
# if single folder: the folder itself is the top-level item
|
||||||
folder = "" if flt else (vrem.split("/")[-1] or "top")
|
folder = "" if flt else (vrem.split("/")[-1] or "top")
|
||||||
@@ -483,13 +487,6 @@ class VFS(object):
|
|||||||
for x in rm:
|
for x in rm:
|
||||||
del vd[x]
|
del vd[x]
|
||||||
|
|
||||||
# up2k filetring based on actual abspath
|
|
||||||
files = [
|
|
||||||
x
|
|
||||||
for x in files
|
|
||||||
if f1 not in x[1] and (not x[1].endswith(f2a) or f2b not in x[1])
|
|
||||||
]
|
|
||||||
|
|
||||||
for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]:
|
for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]:
|
||||||
yield f
|
yield f
|
||||||
|
|
||||||
@@ -744,10 +741,10 @@ class AuthSrv(object):
|
|||||||
unames = ["*"] + list(acct.keys())
|
unames = ["*"] + list(acct.keys())
|
||||||
umap = {x: [] for x in unames}
|
umap = {x: [] for x in unames}
|
||||||
for usr in unames:
|
for usr in unames:
|
||||||
for mp, vol in vfs.all_vols.items():
|
for vp, vol in vfs.all_vols.items():
|
||||||
axs = getattr(vol.axs, axs_key)
|
axs = getattr(vol.axs, axs_key)
|
||||||
if usr in axs or "*" in axs:
|
if usr in axs or "*" in axs:
|
||||||
umap[usr].append(mp)
|
umap[usr].append(vp)
|
||||||
umap[usr].sort()
|
umap[usr].sort()
|
||||||
setattr(vfs, "a" + perm, umap)
|
setattr(vfs, "a" + perm, umap)
|
||||||
|
|
||||||
@@ -865,6 +862,30 @@ class AuthSrv(object):
|
|||||||
if use:
|
if use:
|
||||||
vol.lim = lim
|
vol.lim = lim
|
||||||
|
|
||||||
|
if self.args.no_robots:
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
# volflag "robots" overrides global "norobots", allowing indexing by search engines for this vol
|
||||||
|
if not vol.flags.get("robots"):
|
||||||
|
vol.flags["norobots"] = True
|
||||||
|
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
h = [vol.flags.get("html_head", self.args.html_head)]
|
||||||
|
if vol.flags.get("norobots"):
|
||||||
|
h.insert(0, META_NOBOTS)
|
||||||
|
|
||||||
|
vol.flags["html_head"] = "\n".join([x for x in h if x])
|
||||||
|
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
if self.args.no_vthumb:
|
||||||
|
vol.flags["dvthumb"] = True
|
||||||
|
if self.args.no_athumb:
|
||||||
|
vol.flags["dathumb"] = True
|
||||||
|
if self.args.no_thumb or vol.flags.get("dthumb", False):
|
||||||
|
vol.flags["dthumb"] = True
|
||||||
|
vol.flags["dvthumb"] = True
|
||||||
|
vol.flags["dathumb"] = True
|
||||||
|
vol.flags["dithumb"] = True
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_vols.values():
|
||||||
fk = vol.flags.get("fk")
|
fk = vol.flags.get("fk")
|
||||||
if fk:
|
if fk:
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ def listdir(p="."):
|
|||||||
return [fsdec(x) for x in os.listdir(fsenc(p))]
|
return [fsdec(x) for x in os.listdir(fsenc(p))]
|
||||||
|
|
||||||
|
|
||||||
def lstat(p):
|
|
||||||
return os.lstat(fsenc(p))
|
|
||||||
|
|
||||||
|
|
||||||
def makedirs(name, mode=0o755, exist_ok=True):
|
def makedirs(name, mode=0o755, exist_ok=True):
|
||||||
bname = fsenc(name)
|
bname = fsenc(name)
|
||||||
try:
|
try:
|
||||||
@@ -60,3 +56,12 @@ def utime(p, times=None, follow_symlinks=True):
|
|||||||
return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
|
return os.utime(fsenc(p), times, follow_symlinks=follow_symlinks)
|
||||||
else:
|
else:
|
||||||
return os.utime(fsenc(p), times)
|
return os.utime(fsenc(p), times)
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(os, "lstat"):
|
||||||
|
|
||||||
|
def lstat(p):
|
||||||
|
return os.lstat(fsenc(p))
|
||||||
|
|
||||||
|
else:
|
||||||
|
lstat = stat
|
||||||
|
|||||||
@@ -36,5 +36,9 @@ def islink(p):
|
|||||||
return os.path.islink(fsenc(p))
|
return os.path.islink(fsenc(p))
|
||||||
|
|
||||||
|
|
||||||
|
def lexists(p):
|
||||||
|
return os.path.lexists(fsenc(p))
|
||||||
|
|
||||||
|
|
||||||
def realpath(p):
|
def realpath(p):
|
||||||
return fsdec(os.path.realpath(fsenc(p)))
|
return fsdec(os.path.realpath(fsenc(p)))
|
||||||
|
|||||||
374
copyparty/ftpd.py
Normal file
374
copyparty/ftpd.py
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import stat
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from .__init__ import E, PY2
|
||||||
|
from .util import Pebkac, fsenc, exclude_dotfiles
|
||||||
|
from .bos import bos
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pyftpdlib.ioloop import IOLoop
|
||||||
|
except ImportError:
|
||||||
|
p = os.path.join(E.mod, "vend")
|
||||||
|
print("loading asynchat from " + p)
|
||||||
|
sys.path.append(p)
|
||||||
|
from pyftpdlib.ioloop import IOLoop
|
||||||
|
|
||||||
|
from pyftpdlib.authorizers import DummyAuthorizer, AuthenticationFailed
|
||||||
|
from pyftpdlib.filesystems import AbstractedFS, FilesystemError
|
||||||
|
from pyftpdlib.handlers import FTPHandler
|
||||||
|
from pyftpdlib.servers import FTPServer
|
||||||
|
from pyftpdlib.log import config_logging
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .svchub import SvcHub
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FtpAuth(DummyAuthorizer):
|
||||||
|
def __init__(self):
|
||||||
|
super(FtpAuth, self).__init__()
|
||||||
|
self.hub = None # type: SvcHub
|
||||||
|
|
||||||
|
def validate_authentication(self, username, password, handler):
|
||||||
|
asrv = self.hub.asrv
|
||||||
|
if username == "anonymous":
|
||||||
|
password = ""
|
||||||
|
|
||||||
|
uname = "*"
|
||||||
|
if password:
|
||||||
|
uname = asrv.iacct.get(password, None)
|
||||||
|
|
||||||
|
handler.username = uname
|
||||||
|
|
||||||
|
if password and not uname:
|
||||||
|
raise AuthenticationFailed("Authentication failed.")
|
||||||
|
|
||||||
|
def get_home_dir(self, username):
|
||||||
|
return "/"
|
||||||
|
|
||||||
|
def has_user(self, username):
|
||||||
|
asrv = self.hub.asrv
|
||||||
|
return username in asrv.acct
|
||||||
|
|
||||||
|
def has_perm(self, username, perm, path=None):
|
||||||
|
return True # handled at filesystem layer
|
||||||
|
|
||||||
|
def get_perms(self, username):
|
||||||
|
return "elradfmwMT"
|
||||||
|
|
||||||
|
def get_msg_login(self, username):
|
||||||
|
return "sup {}".format(username)
|
||||||
|
|
||||||
|
def get_msg_quit(self, username):
|
||||||
|
return "cya"
|
||||||
|
|
||||||
|
|
||||||
|
class FtpFs(AbstractedFS):
|
||||||
|
def __init__(self, root, cmd_channel):
|
||||||
|
self.h = self.cmd_channel = cmd_channel # type: FTPHandler
|
||||||
|
self.hub = cmd_channel.hub # type: SvcHub
|
||||||
|
self.args = cmd_channel.args
|
||||||
|
|
||||||
|
self.uname = self.hub.asrv.iacct.get(cmd_channel.password, "*")
|
||||||
|
|
||||||
|
self.cwd = "/" # pyftpdlib convention of leading slash
|
||||||
|
self.root = "/var/lib/empty"
|
||||||
|
|
||||||
|
self.listdirinfo = self.listdir
|
||||||
|
self.chdir(".")
|
||||||
|
|
||||||
|
def v2a(self, vpath, r=False, w=False, m=False, d=False):
|
||||||
|
try:
|
||||||
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
||||||
|
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
|
||||||
|
if not vfs.realpath:
|
||||||
|
raise FilesystemError("no filesystem mounted at this path")
|
||||||
|
|
||||||
|
return os.path.join(vfs.realpath, rem)
|
||||||
|
except Pebkac as ex:
|
||||||
|
raise FilesystemError(str(ex))
|
||||||
|
|
||||||
|
def rv2a(self, vpath, r=False, w=False, m=False, d=False):
|
||||||
|
return self.v2a(os.path.join(self.cwd, vpath), r, w, m, d)
|
||||||
|
|
||||||
|
def ftp2fs(self, ftppath):
|
||||||
|
# return self.v2a(ftppath)
|
||||||
|
return ftppath # self.cwd must be vpath
|
||||||
|
|
||||||
|
def fs2ftp(self, fspath):
|
||||||
|
# raise NotImplementedError()
|
||||||
|
return fspath
|
||||||
|
|
||||||
|
def validpath(self, path):
|
||||||
|
if "/.hist/" in path:
|
||||||
|
if "/up2k." in path or path.endswith("/dir.txt"):
|
||||||
|
raise FilesystemError("access to this file is forbidden")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def open(self, filename, mode):
|
||||||
|
r = "r" in mode
|
||||||
|
w = "w" in mode or "a" in mode or "+" in mode
|
||||||
|
|
||||||
|
ap = self.rv2a(filename, r, w)
|
||||||
|
if w and bos.path.exists(ap):
|
||||||
|
raise FilesystemError("cannot open existing file for writing")
|
||||||
|
|
||||||
|
self.validpath(ap)
|
||||||
|
return open(fsenc(ap), mode)
|
||||||
|
|
||||||
|
def chdir(self, path):
|
||||||
|
self.cwd = join(self.cwd, path)
|
||||||
|
x = self.hub.asrv.vfs.can_access(self.cwd.lstrip("/"), self.h.username)
|
||||||
|
self.can_read, self.can_write, self.can_move, self.can_delete, self.can_get = x
|
||||||
|
|
||||||
|
def mkdir(self, path):
|
||||||
|
ap = self.rv2a(path, w=True)
|
||||||
|
bos.mkdir(ap)
|
||||||
|
|
||||||
|
def listdir(self, path):
|
||||||
|
vpath = join(self.cwd, path).lstrip("/")
|
||||||
|
try:
|
||||||
|
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, True, False)
|
||||||
|
|
||||||
|
fsroot, vfs_ls, vfs_virt = vfs.ls(
|
||||||
|
rem, self.uname, not self.args.no_scandir, [[True], [False, True]]
|
||||||
|
)
|
||||||
|
vfs_ls = [x[0] for x in vfs_ls]
|
||||||
|
vfs_ls.extend(vfs_virt.keys())
|
||||||
|
|
||||||
|
if not self.args.ed:
|
||||||
|
vfs_ls = exclude_dotfiles(vfs_ls)
|
||||||
|
|
||||||
|
vfs_ls.sort()
|
||||||
|
return vfs_ls
|
||||||
|
except Exception as ex:
|
||||||
|
if vpath:
|
||||||
|
# display write-only folders as empty
|
||||||
|
return []
|
||||||
|
|
||||||
|
# return list of volumes
|
||||||
|
r = {x.split("/")[0]: 1 for x in self.hub.asrv.vfs.all_vols.keys()}
|
||||||
|
return list(sorted(list(r.keys())))
|
||||||
|
|
||||||
|
def rmdir(self, path):
|
||||||
|
ap = self.rv2a(path, d=True)
|
||||||
|
bos.rmdir(ap)
|
||||||
|
|
||||||
|
def remove(self, path):
|
||||||
|
if self.args.no_del:
|
||||||
|
raise FilesystemError("the delete feature is disabled in server config")
|
||||||
|
|
||||||
|
vp = join(self.cwd, path).lstrip("/")
|
||||||
|
x = self.hub.broker.put(
|
||||||
|
True, "up2k.handle_rm", self.uname, self.h.remote_ip, [vp]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
x.get()
|
||||||
|
except Exception as ex:
|
||||||
|
raise FilesystemError(str(ex))
|
||||||
|
|
||||||
|
def rename(self, src, dst):
|
||||||
|
if not self.can_move:
|
||||||
|
raise FilesystemError("not allowed for user " + self.h.username)
|
||||||
|
|
||||||
|
if self.args.no_mv:
|
||||||
|
m = "the rename/move feature is disabled in server config"
|
||||||
|
raise FilesystemError(m)
|
||||||
|
|
||||||
|
svp = join(self.cwd, src).lstrip("/")
|
||||||
|
dvp = join(self.cwd, dst).lstrip("/")
|
||||||
|
x = self.hub.broker.put(True, "up2k.handle_mv", self.uname, svp, dvp)
|
||||||
|
try:
|
||||||
|
x.get()
|
||||||
|
except Exception as ex:
|
||||||
|
raise FilesystemError(str(ex))
|
||||||
|
|
||||||
|
def chmod(self, path, mode):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stat(self, path):
|
||||||
|
try:
|
||||||
|
ap = self.rv2a(path, r=True)
|
||||||
|
return bos.stat(ap)
|
||||||
|
except:
|
||||||
|
ap = self.rv2a(path)
|
||||||
|
st = bos.stat(ap)
|
||||||
|
if not stat.S_ISDIR(st.st_mode):
|
||||||
|
raise
|
||||||
|
|
||||||
|
return st
|
||||||
|
|
||||||
|
def utime(self, path, timeval):
|
||||||
|
ap = self.rv2a(path, w=True)
|
||||||
|
return bos.utime(ap, (timeval, timeval))
|
||||||
|
|
||||||
|
def lstat(self, path):
|
||||||
|
ap = self.rv2a(path)
|
||||||
|
return bos.lstat(ap)
|
||||||
|
|
||||||
|
def isfile(self, path):
|
||||||
|
st = self.stat(path)
|
||||||
|
return stat.S_ISREG(st.st_mode)
|
||||||
|
|
||||||
|
def islink(self, path):
|
||||||
|
ap = self.rv2a(path)
|
||||||
|
return bos.path.islink(ap)
|
||||||
|
|
||||||
|
def isdir(self, path):
|
||||||
|
try:
|
||||||
|
st = self.stat(path)
|
||||||
|
return stat.S_ISDIR(st.st_mode)
|
||||||
|
except:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getsize(self, path):
|
||||||
|
ap = self.rv2a(path)
|
||||||
|
return bos.path.getsize(ap)
|
||||||
|
|
||||||
|
def getmtime(self, path):
|
||||||
|
ap = self.rv2a(path)
|
||||||
|
return bos.path.getmtime(ap)
|
||||||
|
|
||||||
|
def realpath(self, path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
def lexists(self, path):
|
||||||
|
ap = self.rv2a(path)
|
||||||
|
return bos.path.lexists(ap)
|
||||||
|
|
||||||
|
def get_user_by_uid(self, uid):
|
||||||
|
return "root"
|
||||||
|
|
||||||
|
def get_group_by_uid(self, gid):
|
||||||
|
return "root"
|
||||||
|
|
||||||
|
|
||||||
|
class FtpHandler(FTPHandler):
|
||||||
|
abstracted_fs = FtpFs
|
||||||
|
|
||||||
|
def __init__(self, conn, server, ioloop=None):
|
||||||
|
if PY2:
|
||||||
|
FTPHandler.__init__(self, conn, server, ioloop)
|
||||||
|
else:
|
||||||
|
super(FtpHandler, self).__init__(conn, server, ioloop)
|
||||||
|
|
||||||
|
# abspath->vpath mapping to resolve log_transfer paths
|
||||||
|
self.vfs_map = {}
|
||||||
|
|
||||||
|
def ftp_STOR(self, file, mode="w"):
|
||||||
|
vp = join(self.fs.cwd, file).lstrip("/")
|
||||||
|
ap = self.fs.v2a(vp)
|
||||||
|
self.vfs_map[ap] = vp
|
||||||
|
# print("ftp_STOR: {} {} => {}".format(vp, mode, ap))
|
||||||
|
ret = FTPHandler.ftp_STOR(self, file, mode)
|
||||||
|
# print("ftp_STOR: {} {} OK".format(vp, mode))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def log_transfer(self, cmd, filename, receive, completed, elapsed, bytes):
|
||||||
|
ap = filename.decode("utf-8", "replace")
|
||||||
|
vp = self.vfs_map.pop(ap, None)
|
||||||
|
# print("xfer_end: {} => {}".format(ap, vp))
|
||||||
|
if vp:
|
||||||
|
vp, fn = os.path.split(vp)
|
||||||
|
vfs, rem = self.hub.asrv.vfs.get(vp, self.username, False, True)
|
||||||
|
vfs, rem = vfs.get_dbv(rem)
|
||||||
|
self.hub.broker.put(
|
||||||
|
False,
|
||||||
|
"up2k.hash_file",
|
||||||
|
vfs.realpath,
|
||||||
|
vfs.flags,
|
||||||
|
rem,
|
||||||
|
fn,
|
||||||
|
self.remote_ip,
|
||||||
|
time.time(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return FTPHandler.log_transfer(
|
||||||
|
self, cmd, filename, receive, completed, elapsed, bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pyftpdlib.handlers import TLS_FTPHandler
|
||||||
|
|
||||||
|
class SftpHandler(FtpHandler, TLS_FTPHandler):
|
||||||
|
pass
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Ftpd(object):
|
||||||
|
def __init__(self, hub):
|
||||||
|
self.hub = hub
|
||||||
|
self.args = hub.args
|
||||||
|
|
||||||
|
hs = []
|
||||||
|
if self.args.ftp:
|
||||||
|
hs.append([FtpHandler, self.args.ftp])
|
||||||
|
if self.args.ftps:
|
||||||
|
try:
|
||||||
|
h = SftpHandler
|
||||||
|
except:
|
||||||
|
m = "\nftps requires pyopenssl;\nplease run the following:\n\n {} -m pip install --user pyopenssl\n"
|
||||||
|
print(m.format(sys.executable))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
h.certfile = os.path.join(E.cfg, "cert.pem")
|
||||||
|
h.tls_control_required = True
|
||||||
|
h.tls_data_required = True
|
||||||
|
|
||||||
|
hs.append([h, self.args.ftps])
|
||||||
|
|
||||||
|
for h in hs:
|
||||||
|
h, lp = h
|
||||||
|
h.hub = hub
|
||||||
|
h.args = hub.args
|
||||||
|
h.authorizer = FtpAuth()
|
||||||
|
h.authorizer.hub = hub
|
||||||
|
|
||||||
|
if self.args.ftp_pr:
|
||||||
|
p1, p2 = [int(x) for x in self.args.ftp_pr.split("-")]
|
||||||
|
if self.args.ftp and self.args.ftps:
|
||||||
|
# divide port range in half
|
||||||
|
d = int((p2 - p1) / 2)
|
||||||
|
if lp == self.args.ftp:
|
||||||
|
p2 = p1 + d
|
||||||
|
else:
|
||||||
|
p1 += d + 1
|
||||||
|
|
||||||
|
h.passive_ports = list(range(p1, p2 + 1))
|
||||||
|
|
||||||
|
if self.args.ftp_nat:
|
||||||
|
h.masquerade_address = self.args.ftp_nat
|
||||||
|
|
||||||
|
if self.args.ftp_dbg:
|
||||||
|
config_logging(level=logging.DEBUG)
|
||||||
|
|
||||||
|
ioloop = IOLoop()
|
||||||
|
for ip in self.args.i:
|
||||||
|
for h, lp in hs:
|
||||||
|
FTPServer((ip, int(lp)), h, ioloop)
|
||||||
|
|
||||||
|
t = threading.Thread(target=ioloop.loop)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
|
def join(p1, p2):
|
||||||
|
w = os.path.join(p1, p2.replace("\\", "/"))
|
||||||
|
return os.path.normpath(w).replace("\\", "/")
|
||||||
@@ -65,6 +65,11 @@ class HttpCli(object):
|
|||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
"Cache-Control": "no-store; max-age=0",
|
"Cache-Control": "no-store; max-age=0",
|
||||||
}
|
}
|
||||||
|
h = self.args.html_head
|
||||||
|
if self.args.no_robots:
|
||||||
|
h = META_NOBOTS + (("\n" + h) if h else "")
|
||||||
|
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
||||||
|
self.html_head = h
|
||||||
|
|
||||||
def log(self, msg, c=0):
|
def log(self, msg, c=0):
|
||||||
ptn = self.asrv.re_pwd
|
ptn = self.asrv.re_pwd
|
||||||
@@ -93,6 +98,7 @@ class HttpCli(object):
|
|||||||
if ka:
|
if ka:
|
||||||
ka["ts"] = self.conn.hsrv.cachebuster()
|
ka["ts"] = self.conn.hsrv.cachebuster()
|
||||||
ka["svcname"] = self.args.doctitle
|
ka["svcname"] = self.args.doctitle
|
||||||
|
ka["html_head"] = self.html_head
|
||||||
return tpl.render(**ka)
|
return tpl.render(**ka)
|
||||||
|
|
||||||
return tpl
|
return tpl
|
||||||
@@ -640,7 +646,7 @@ class HttpCli(object):
|
|||||||
with ren_open(fn, *open_a, **params) as f:
|
with ren_open(fn, *open_a, **params) as f:
|
||||||
f, fn = f["orz"]
|
f, fn = f["orz"]
|
||||||
path = os.path.join(fdir, fn)
|
path = os.path.join(fdir, fn)
|
||||||
post_sz, sha_hex, sha_b64 = hashcopy(reader, f)
|
post_sz, sha_hex, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
|
||||||
|
|
||||||
if lim:
|
if lim:
|
||||||
lim.nup(self.ip)
|
lim.nup(self.ip)
|
||||||
@@ -689,7 +695,8 @@ class HttpCli(object):
|
|||||||
def handle_stash(self):
|
def handle_stash(self):
|
||||||
post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file()
|
post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file()
|
||||||
spd = self._spd(post_sz)
|
spd = self._spd(post_sz)
|
||||||
self.log("{} wrote {}/{} bytes to {}".format(spd, post_sz, remains, path))
|
m = "{} wrote {}/{} bytes to {} # {}"
|
||||||
|
self.log(m.format(spd, post_sz, remains, path, sha_b64[:28])) # 21
|
||||||
m = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
|
m = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
|
||||||
self.reply(m.encode("utf-8"))
|
self.reply(m.encode("utf-8"))
|
||||||
return True
|
return True
|
||||||
@@ -921,7 +928,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
f.seek(cstart[0])
|
f.seek(cstart[0])
|
||||||
post_sz, _, sha_b64 = hashcopy(reader, f)
|
post_sz, _, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
|
||||||
|
|
||||||
if sha_b64 != chash:
|
if sha_b64 != chash:
|
||||||
m = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
|
m = "your chunk got corrupted somehow (received {} bytes); expected vs received hash:\n{}\n{}"
|
||||||
@@ -1114,7 +1121,7 @@ class HttpCli(object):
|
|||||||
f, fname = f["orz"]
|
f, fname = f["orz"]
|
||||||
abspath = os.path.join(fdir, fname)
|
abspath = os.path.join(fdir, fname)
|
||||||
self.log("writing to {}".format(abspath))
|
self.log("writing to {}".format(abspath))
|
||||||
sz, sha_hex, sha_b64 = hashcopy(p_data, f)
|
sz, sha_hex, sha_b64 = hashcopy(p_data, f, self.args.s_wr_slp)
|
||||||
if sz == 0:
|
if sz == 0:
|
||||||
raise Pebkac(400, "empty files in post")
|
raise Pebkac(400, "empty files in post")
|
||||||
|
|
||||||
@@ -1332,7 +1339,7 @@ class HttpCli(object):
|
|||||||
raise Pebkac(400, "expected body, got {}".format(p_field))
|
raise Pebkac(400, "expected body, got {}".format(p_field))
|
||||||
|
|
||||||
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
||||||
sz, sha512, _ = hashcopy(p_data, f)
|
sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
|
||||||
|
|
||||||
if lim:
|
if lim:
|
||||||
lim.nup(self.ip)
|
lim.nup(self.ip)
|
||||||
@@ -1676,13 +1683,15 @@ class HttpCli(object):
|
|||||||
|
|
||||||
boundary = "\roll\tide"
|
boundary = "\roll\tide"
|
||||||
targs = {
|
targs = {
|
||||||
|
"ts": self.conn.hsrv.cachebuster(),
|
||||||
|
"svcname": self.args.doctitle,
|
||||||
|
"html_head": self.html_head,
|
||||||
"edit": "edit" in self.uparam,
|
"edit": "edit" in self.uparam,
|
||||||
"title": html_escape(self.vpath, crlf=True),
|
"title": html_escape(self.vpath, crlf=True),
|
||||||
"lastmod": int(ts_md * 1000),
|
"lastmod": int(ts_md * 1000),
|
||||||
"md_plug": "true" if self.args.emp else "false",
|
"md_plug": "true" if self.args.emp else "false",
|
||||||
"md_chk_rate": self.args.mcr,
|
"md_chk_rate": self.args.mcr,
|
||||||
"md": boundary,
|
"md": boundary,
|
||||||
"ts": self.conn.hsrv.cachebuster(),
|
|
||||||
"arg_base": arg_base,
|
"arg_base": arg_base,
|
||||||
}
|
}
|
||||||
html = template.render(**targs).encode("utf-8", "replace")
|
html = template.render(**targs).encode("utf-8", "replace")
|
||||||
@@ -1731,6 +1740,31 @@ class HttpCli(object):
|
|||||||
vstate = {}
|
vstate = {}
|
||||||
vs = {"scanning": None, "hashq": None, "tagq": None, "mtpq": None}
|
vs = {"scanning": None, "hashq": None, "tagq": None, "mtpq": None}
|
||||||
|
|
||||||
|
if self.uparam.get("ls") in ["v", "t", "txt"]:
|
||||||
|
if self.uname == "*":
|
||||||
|
txt = "howdy stranger (you're not logged in)"
|
||||||
|
else:
|
||||||
|
txt = "welcome back {}".format(self.uname)
|
||||||
|
|
||||||
|
if vstate:
|
||||||
|
txt += "\nstatus:"
|
||||||
|
for k in ["scanning", "hashq", "tagq", "mtpq"]:
|
||||||
|
txt += " {}({})".format(k, vs[k])
|
||||||
|
|
||||||
|
if rvol:
|
||||||
|
txt += "\nyou can browse:"
|
||||||
|
for v in rvol:
|
||||||
|
txt += "\n " + v
|
||||||
|
|
||||||
|
if wvol:
|
||||||
|
txt += "\nyou can upload to:"
|
||||||
|
for v in wvol:
|
||||||
|
txt += "\n " + v
|
||||||
|
|
||||||
|
txt = txt.encode("utf-8", "replace") + b"\n"
|
||||||
|
self.reply(txt, mime="text/plain; charset=utf-8")
|
||||||
|
return True
|
||||||
|
|
||||||
html = self.j2(
|
html = self.j2(
|
||||||
"splash",
|
"splash",
|
||||||
this=self,
|
this=self,
|
||||||
@@ -2040,6 +2074,12 @@ class HttpCli(object):
|
|||||||
):
|
):
|
||||||
raise Pebkac(403)
|
raise Pebkac(403)
|
||||||
|
|
||||||
|
self.html_head = vn.flags.get("html_head", "")
|
||||||
|
if vn.flags.get("norobots"):
|
||||||
|
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
||||||
|
else:
|
||||||
|
self.out_headers.pop("X-Robots-Tag", None)
|
||||||
|
|
||||||
is_dir = stat.S_ISDIR(st.st_mode)
|
is_dir = stat.S_ISDIR(st.st_mode)
|
||||||
if self.can_read:
|
if self.can_read:
|
||||||
th_fmt = self.uparam.get("th")
|
th_fmt = self.uparam.get("th")
|
||||||
@@ -2057,9 +2097,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
thp = None
|
thp = None
|
||||||
if self.thumbcli:
|
if self.thumbcli:
|
||||||
thp = self.thumbcli.get(
|
thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
|
||||||
dbv.realpath, vrem, int(st.st_mtime), th_fmt
|
|
||||||
)
|
|
||||||
|
|
||||||
if thp:
|
if thp:
|
||||||
return self.tx_file(thp)
|
return self.tx_file(thp)
|
||||||
@@ -2109,12 +2147,11 @@ class HttpCli(object):
|
|||||||
free = humansize(sv.f_frsize * sv.f_bfree, True)
|
free = humansize(sv.f_frsize * sv.f_bfree, True)
|
||||||
total = humansize(sv.f_frsize * sv.f_blocks, True)
|
total = humansize(sv.f_frsize * sv.f_blocks, True)
|
||||||
|
|
||||||
srv_info.append(free + " free")
|
srv_info.append("{} free of {}".format(free, total))
|
||||||
srv_info.append(total)
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
srv_info = "</span> /// <span>".join(srv_info)
|
srv_info = "</span> // <span>".join(srv_info)
|
||||||
|
|
||||||
perms = []
|
perms = []
|
||||||
if self.can_read:
|
if self.can_read:
|
||||||
@@ -2130,11 +2167,12 @@ class HttpCli(object):
|
|||||||
|
|
||||||
url_suf = self.urlq({}, [])
|
url_suf = self.urlq({}, [])
|
||||||
is_ls = "ls" in self.uparam
|
is_ls = "ls" in self.uparam
|
||||||
is_js = self.cookies.get("js") == "y"
|
is_js = self.args.force_js or self.cookies.get("js") == "y"
|
||||||
|
|
||||||
tpl = "browser"
|
tpl = "browser"
|
||||||
if "b" in self.uparam:
|
if "b" in self.uparam:
|
||||||
tpl = "browser2"
|
tpl = "browser2"
|
||||||
|
is_js = False
|
||||||
|
|
||||||
logues = ["", ""]
|
logues = ["", ""]
|
||||||
if not self.args.no_logues:
|
if not self.args.no_logues:
|
||||||
@@ -2186,6 +2224,8 @@ class HttpCli(object):
|
|||||||
"readme": readme,
|
"readme": readme,
|
||||||
"title": html_escape(self.vpath, crlf=True),
|
"title": html_escape(self.vpath, crlf=True),
|
||||||
"srv_info": srv_info,
|
"srv_info": srv_info,
|
||||||
|
"dtheme": self.args.theme,
|
||||||
|
"themes": self.args.themes,
|
||||||
}
|
}
|
||||||
if not self.can_read:
|
if not self.can_read:
|
||||||
if is_ls:
|
if is_ls:
|
||||||
@@ -2233,10 +2273,6 @@ class HttpCli(object):
|
|||||||
if not self.args.ed or "dots" not in self.uparam:
|
if not self.args.ed or "dots" not in self.uparam:
|
||||||
vfs_ls = exclude_dotfiles(vfs_ls)
|
vfs_ls = exclude_dotfiles(vfs_ls)
|
||||||
|
|
||||||
hidden = []
|
|
||||||
if rem == ".hist":
|
|
||||||
hidden = ["up2k."]
|
|
||||||
|
|
||||||
icur = None
|
icur = None
|
||||||
if "e2t" in vn.flags:
|
if "e2t" in vn.flags:
|
||||||
idx = self.conn.get_u2idx()
|
idx = self.conn.get_u2idx()
|
||||||
@@ -2255,8 +2291,6 @@ class HttpCli(object):
|
|||||||
|
|
||||||
if fn in vfs_virt:
|
if fn in vfs_virt:
|
||||||
fspath = vfs_virt[fn].realpath
|
fspath = vfs_virt[fn].realpath
|
||||||
elif hidden and any(fn.startswith(x) for x in hidden):
|
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
fspath = fsroot + "/" + fn
|
fspath = fsroot + "/" + fn
|
||||||
|
|
||||||
@@ -2373,7 +2407,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
doc = self.uparam.get("doc") if self.can_read else None
|
doc = self.uparam.get("doc") if self.can_read else None
|
||||||
if doc:
|
if doc:
|
||||||
doc = unquotep(doc.replace("+", " "))
|
doc = unquotep(doc.replace("+", " ").split("?")[0])
|
||||||
j2a["docname"] = doc
|
j2a["docname"] = doc
|
||||||
if next((x for x in files if x["name"] == doc), None):
|
if next((x for x in files if x["name"] == doc), None):
|
||||||
with open(os.path.join(abspath, doc), "rb") as f:
|
with open(os.path.join(abspath, doc), "rb") as f:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from .util import Unrecv
|
|||||||
from .httpcli import HttpCli
|
from .httpcli import HttpCli
|
||||||
from .u2idx import U2idx
|
from .u2idx import U2idx
|
||||||
from .th_cli import ThumbCli
|
from .th_cli import ThumbCli
|
||||||
from .th_srv import HAVE_PIL
|
from .th_srv import HAVE_PIL, HAVE_VIPS
|
||||||
from .ico import Ico
|
from .ico import Ico
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class HttpConn(object):
|
|||||||
self.cert_path = hsrv.cert_path
|
self.cert_path = hsrv.cert_path
|
||||||
self.u2fh = hsrv.u2fh
|
self.u2fh = hsrv.u2fh
|
||||||
|
|
||||||
enth = HAVE_PIL and not self.args.no_thumb
|
enth = (HAVE_PIL or HAVE_VIPS) and not self.args.no_thumb
|
||||||
self.thumbcli = ThumbCli(hsrv) if enth else None
|
self.thumbcli = ThumbCli(hsrv) if enth else None
|
||||||
self.ico = Ico(self.args)
|
self.ico = Ico(self.args)
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ class HttpSrv(object):
|
|||||||
self.cb_ts = 0
|
self.cb_ts = 0
|
||||||
self.cb_v = 0
|
self.cb_v = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
x = self.broker.put(True, "thumbsrv.getcfg")
|
||||||
|
self.th_cfg = x.get()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
env = jinja2.Environment()
|
env = jinja2.Environment()
|
||||||
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
||||||
self.j2 = {
|
self.j2 = {
|
||||||
|
|||||||
@@ -477,13 +477,13 @@ class MTag(object):
|
|||||||
env["PYTHONPATH"] = pypath
|
env["PYTHONPATH"] = pypath
|
||||||
|
|
||||||
ret = {}
|
ret = {}
|
||||||
for tagname, mp in parsers.items():
|
for tagname, parser in parsers.items():
|
||||||
try:
|
try:
|
||||||
cmd = [mp.bin, abspath]
|
cmd = [parser.bin, abspath]
|
||||||
if mp.bin.endswith(".py"):
|
if parser.bin.endswith(".py"):
|
||||||
cmd = [sys.executable] + cmd
|
cmd = [sys.executable] + cmd
|
||||||
|
|
||||||
args = {"env": env, "timeout": mp.timeout}
|
args = {"env": env, "timeout": parser.timeout}
|
||||||
|
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
args["creationflags"] = 0x4000
|
args["creationflags"] = 0x4000
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import tarfile
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .sutil import errdesc
|
from .sutil import errdesc
|
||||||
from .util import Queue, fsenc
|
from .util import Queue, fsenc, min_ex
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
@@ -88,8 +88,9 @@ class StreamTar(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.ser(f)
|
self.ser(f)
|
||||||
except Exception as ex:
|
except Exception:
|
||||||
errors.append([f["vp"], repr(ex)])
|
ex = min_ex(5, True).replace("\n", "\n-- ")
|
||||||
|
errors.append([f["vp"], ex])
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
self.errf, txt = errdesc(errors)
|
self.errf, txt = errdesc(errors)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from .util import mp, start_log_thrs, start_stackmon, min_ex, ansi_re
|
|||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
from .th_srv import ThumbSrv, HAVE_PIL, HAVE_WEBP
|
from .th_srv import ThumbSrv, HAVE_PIL, HAVE_VIPS, HAVE_WEBP
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +70,13 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.log("root", m, c=3)
|
self.log("root", m, c=3)
|
||||||
|
|
||||||
|
bri = "zy"[args.theme % 2 :][:1]
|
||||||
|
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
||||||
|
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
||||||
|
|
||||||
|
if not args.hardlink and args.never_symlink:
|
||||||
|
args.no_dedup = True
|
||||||
|
|
||||||
# initiate all services to manage
|
# initiate all services to manage
|
||||||
self.asrv = AuthSrv(self.args, self.log)
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
if args.ls:
|
if args.ls:
|
||||||
@@ -78,20 +85,30 @@ class SvcHub(object):
|
|||||||
self.tcpsrv = TcpSrv(self)
|
self.tcpsrv = TcpSrv(self)
|
||||||
self.up2k = Up2k(self)
|
self.up2k = Up2k(self)
|
||||||
|
|
||||||
|
decs = {k: 1 for k in self.args.th_dec.split(",")}
|
||||||
|
if not HAVE_VIPS:
|
||||||
|
decs.pop("vips", None)
|
||||||
|
if not HAVE_PIL:
|
||||||
|
decs.pop("pil", None)
|
||||||
|
if not HAVE_FFMPEG or not HAVE_FFPROBE:
|
||||||
|
decs.pop("ff", None)
|
||||||
|
|
||||||
|
self.args.th_dec = list(decs.keys())
|
||||||
self.thumbsrv = None
|
self.thumbsrv = None
|
||||||
if not args.no_thumb:
|
if not args.no_thumb:
|
||||||
if HAVE_PIL:
|
m = "decoder preference: {}".format(", ".join(self.args.th_dec))
|
||||||
if not HAVE_WEBP:
|
self.log("thumb", m)
|
||||||
args.th_no_webp = True
|
|
||||||
msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
|
|
||||||
self.log("thumb", msg, c=3)
|
|
||||||
|
|
||||||
|
if "pil" in self.args.th_dec and not HAVE_WEBP:
|
||||||
|
msg = "disabling webp thumbnails because either libwebp is not available or your Pillow is too old"
|
||||||
|
self.log("thumb", msg, c=3)
|
||||||
|
|
||||||
|
if self.args.th_dec:
|
||||||
self.thumbsrv = ThumbSrv(self)
|
self.thumbsrv = ThumbSrv(self)
|
||||||
else:
|
else:
|
||||||
msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n"
|
msg = "need either Pillow, pyvips, or FFmpeg to create thumbnails; for example:\n{0}{1} -m pip install --user Pillow\n{0}{1} -m pip install --user pyvips\n{0}apt install ffmpeg"
|
||||||
self.log(
|
msg = msg.format(" " * 37, os.path.basename(sys.executable))
|
||||||
"thumb", msg.format(" " * 37, os.path.basename(sys.executable)), c=3
|
self.log("thumb", msg, c=3)
|
||||||
)
|
|
||||||
|
|
||||||
if not args.no_acode and args.no_thumb:
|
if not args.no_acode and args.no_thumb:
|
||||||
msg = "setting --no-acode because --no-thumb (sorry)"
|
msg = "setting --no-acode because --no-thumb (sorry)"
|
||||||
@@ -105,6 +122,11 @@ class SvcHub(object):
|
|||||||
|
|
||||||
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
||||||
|
|
||||||
|
if args.ftp or args.ftps:
|
||||||
|
from .ftpd import Ftpd
|
||||||
|
|
||||||
|
self.ftpd = Ftpd(self)
|
||||||
|
|
||||||
# decide which worker impl to use
|
# decide which worker impl to use
|
||||||
if self.check_mp_enable():
|
if self.check_mp_enable():
|
||||||
from .broker_mp import BrokerMp as Broker
|
from .broker_mp import BrokerMp as Broker
|
||||||
@@ -357,7 +379,7 @@ class SvcHub(object):
|
|||||||
src = ansi_re.sub("", src)
|
src = ansi_re.sub("", src)
|
||||||
elif c:
|
elif c:
|
||||||
if isinstance(c, int):
|
if isinstance(c, int):
|
||||||
msg = "\033[3{}m{}".format(c, msg)
|
msg = "\033[3{}m{}\033[0m".format(c, msg)
|
||||||
elif "\033" not in c:
|
elif "\033" not in c:
|
||||||
msg = "\033[{}m{}\033[0m".format(c, msg)
|
msg = "\033[{}m{}\033[0m".format(c, msg)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .sutil import errdesc
|
from .sutil import errdesc
|
||||||
from .util import yieldfile, sanitize_fn, spack, sunpack
|
from .util import yieldfile, sanitize_fn, spack, sunpack, min_ex
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +35,10 @@ def unixtime2dos(ts):
|
|||||||
|
|
||||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||||
bt = (th << 11) + (tm << 5) + ts // 2
|
bt = (th << 11) + (tm << 5) + ts // 2
|
||||||
return spack(b"<HH", bt, bd)
|
try:
|
||||||
|
return spack(b"<HH", bt, bd)
|
||||||
|
except:
|
||||||
|
return b"\x00\x00\x21\x00"
|
||||||
|
|
||||||
|
|
||||||
def gen_fdesc(sz, crc32, z64):
|
def gen_fdesc(sz, crc32, z64):
|
||||||
@@ -244,8 +246,9 @@ class StreamZip(object):
|
|||||||
try:
|
try:
|
||||||
for x in self.ser(f):
|
for x in self.ser(f):
|
||||||
yield x
|
yield x
|
||||||
except Exception as ex:
|
except Exception:
|
||||||
errors.append([f["vp"], repr(ex)])
|
ex = min_ex(5, True).replace("\n", "\n-- ")
|
||||||
|
errors.append([f["vp"], ex])
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
errf, txt = errdesc(errors)
|
errf, txt = errdesc(errors)
|
||||||
|
|||||||
@@ -57,13 +57,19 @@ class TcpSrv(object):
|
|||||||
msgs = []
|
msgs = []
|
||||||
title_tab = {}
|
title_tab = {}
|
||||||
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
|
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
|
||||||
m = "available @ http://{}:{}/ (\033[33m{}\033[0m)"
|
m = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
||||||
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
||||||
for port in sorted(self.args.p):
|
for port in sorted(self.args.p):
|
||||||
if port not in ok.get(ip, ok.get("0.0.0.0", [])):
|
if port not in ok.get(ip, ok.get("0.0.0.0", [])):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
msgs.append(m.format(ip, port, desc))
|
proto = " http"
|
||||||
|
if self.args.http_only:
|
||||||
|
pass
|
||||||
|
elif self.args.https_only or port == 443:
|
||||||
|
proto = "https"
|
||||||
|
|
||||||
|
msgs.append(m.format(proto, ip, port, desc))
|
||||||
|
|
||||||
if not self.args.wintitle:
|
if not self.args.wintitle:
|
||||||
continue
|
continue
|
||||||
@@ -144,10 +150,15 @@ class TcpSrv(object):
|
|||||||
return eps
|
return eps
|
||||||
|
|
||||||
r = re.compile(r"^\s+inet ([^ ]+)/.* (.*)")
|
r = re.compile(r"^\s+inet ([^ ]+)/.* (.*)")
|
||||||
|
ri = re.compile(r"^\s*[0-9]+\s*:.*")
|
||||||
|
up = False
|
||||||
for ln in txt.split("\n"):
|
for ln in txt.split("\n"):
|
||||||
|
if ri.match(ln):
|
||||||
|
up = "UP" in re.split("[>,< ]", ln)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ip, dev = r.match(ln.rstrip()).groups()
|
ip, dev = r.match(ln.rstrip()).groups()
|
||||||
eps[ip] = dev
|
eps[ip] = dev + ("" if up else ", \033[31mLINK-DOWN")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -177,6 +188,7 @@ class TcpSrv(object):
|
|||||||
|
|
||||||
def ips_windows_ipconfig(self):
|
def ips_windows_ipconfig(self):
|
||||||
eps = {}
|
eps = {}
|
||||||
|
offs = {}
|
||||||
try:
|
try:
|
||||||
txt, _ = chkcmd(["ipconfig"])
|
txt, _ = chkcmd(["ipconfig"])
|
||||||
except:
|
except:
|
||||||
@@ -184,18 +196,29 @@ class TcpSrv(object):
|
|||||||
|
|
||||||
rdev = re.compile(r"(^[^ ].*):$")
|
rdev = re.compile(r"(^[^ ].*):$")
|
||||||
rip = re.compile(r"^ +IPv?4? [^:]+: *([0-9\.]{7,15})$")
|
rip = re.compile(r"^ +IPv?4? [^:]+: *([0-9\.]{7,15})$")
|
||||||
|
roff = re.compile(r".*: Media disconnected$")
|
||||||
dev = None
|
dev = None
|
||||||
for ln in txt.replace("\r", "").split("\n"):
|
for ln in txt.replace("\r", "").split("\n"):
|
||||||
m = rdev.match(ln)
|
m = rdev.match(ln)
|
||||||
if m:
|
if m:
|
||||||
|
if dev and dev not in eps.values():
|
||||||
|
offs[dev] = 1
|
||||||
|
|
||||||
dev = m.group(1).split(" adapter ", 1)[-1]
|
dev = m.group(1).split(" adapter ", 1)[-1]
|
||||||
|
|
||||||
|
if dev and roff.match(ln):
|
||||||
|
offs[dev] = 1
|
||||||
|
dev = None
|
||||||
|
|
||||||
m = rip.match(ln)
|
m = rip.match(ln)
|
||||||
if m and dev:
|
if m and dev:
|
||||||
eps[m.group(1)] = dev
|
eps[m.group(1)] = dev
|
||||||
dev = None
|
dev = None
|
||||||
|
|
||||||
return eps
|
if dev and dev not in eps.values():
|
||||||
|
offs[dev] = 1
|
||||||
|
|
||||||
|
return eps, offs
|
||||||
|
|
||||||
def ips_windows_netsh(self):
|
def ips_windows_netsh(self):
|
||||||
eps = {}
|
eps = {}
|
||||||
@@ -215,7 +238,6 @@ class TcpSrv(object):
|
|||||||
m = rip.match(ln)
|
m = rip.match(ln)
|
||||||
if m and dev:
|
if m and dev:
|
||||||
eps[m.group(1)] = dev
|
eps[m.group(1)] = dev
|
||||||
dev = None
|
|
||||||
|
|
||||||
return eps
|
return eps
|
||||||
|
|
||||||
@@ -223,8 +245,11 @@ class TcpSrv(object):
|
|||||||
if MACOS:
|
if MACOS:
|
||||||
eps = self.ips_macos()
|
eps = self.ips_macos()
|
||||||
elif ANYWIN:
|
elif ANYWIN:
|
||||||
eps = self.ips_windows_ipconfig() # sees more interfaces
|
eps, off = self.ips_windows_ipconfig() # sees more interfaces + link state
|
||||||
eps.update(self.ips_windows_netsh()) # has better names
|
eps.update(self.ips_windows_netsh()) # has better names
|
||||||
|
for k, v in eps.items():
|
||||||
|
if v in off:
|
||||||
|
eps[k] += ", \033[31mLINK-DOWN"
|
||||||
else:
|
else:
|
||||||
eps = self.ips_linux()
|
eps = self.ips_linux()
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from .util import Cooldown
|
from .util import Cooldown
|
||||||
from .th_srv import thumb_path, THUMBABLE, FMT_FFV, FMT_FFA
|
from .th_srv import thumb_path, HAVE_WEBP
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
|
||||||
|
|
||||||
@@ -18,30 +18,53 @@ class ThumbCli(object):
|
|||||||
# cache on both sides for less broker spam
|
# cache on both sides for less broker spam
|
||||||
self.cooldown = Cooldown(self.args.th_poke)
|
self.cooldown = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c = hsrv.th_cfg
|
||||||
|
except:
|
||||||
|
c = {k: {} for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]}
|
||||||
|
|
||||||
|
self.thumbable = c["thumbable"]
|
||||||
|
self.fmt_pil = c["pil"]
|
||||||
|
self.fmt_vips = c["vips"]
|
||||||
|
self.fmt_ffi = c["ffi"]
|
||||||
|
self.fmt_ffv = c["ffv"]
|
||||||
|
self.fmt_ffa = c["ffa"]
|
||||||
|
|
||||||
|
# defer args.th_ff_jpg, can change at runtime
|
||||||
|
d = next((x for x in self.args.th_dec if x in ("vips", "pil")), None)
|
||||||
|
self.can_webp = HAVE_WEBP or d == "vips"
|
||||||
|
|
||||||
def log(self, msg, c=0):
|
def log(self, msg, c=0):
|
||||||
self.log_func("thumbcli", msg, c)
|
self.log_func("thumbcli", msg, c)
|
||||||
|
|
||||||
def get(self, ptop, rem, mtime, fmt):
|
def get(self, dbv, rem, mtime, fmt):
|
||||||
|
ptop = dbv.realpath
|
||||||
ext = rem.rsplit(".")[-1].lower()
|
ext = rem.rsplit(".")[-1].lower()
|
||||||
if ext not in THUMBABLE:
|
if ext not in self.thumbable or "dthumb" in dbv.flags:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
is_vid = ext in FMT_FFV
|
is_vid = ext in self.fmt_ffv
|
||||||
if is_vid and self.args.no_vthumb:
|
if is_vid and "dvthumb" in dbv.flags:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
want_opus = fmt in ("opus", "caf")
|
want_opus = fmt in ("opus", "caf")
|
||||||
is_au = ext in FMT_FFA
|
is_au = ext in self.fmt_ffa
|
||||||
if is_au:
|
if is_au:
|
||||||
if want_opus:
|
if want_opus:
|
||||||
if self.args.no_acode:
|
if self.args.no_acode:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if self.args.no_athumb:
|
if "dathumb" in dbv.flags:
|
||||||
return None
|
return None
|
||||||
elif want_opus:
|
elif want_opus:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
is_img = not is_vid and not is_au
|
||||||
|
if is_img and "dithumb" in dbv.flags:
|
||||||
|
return None
|
||||||
|
|
||||||
|
preferred = self.args.th_dec[0] if self.args.th_dec else ""
|
||||||
|
|
||||||
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
|
||||||
return os.path.join(ptop, rem)
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
@@ -49,7 +72,11 @@ class ThumbCli(object):
|
|||||||
fmt = "w"
|
fmt = "w"
|
||||||
|
|
||||||
if fmt == "w":
|
if fmt == "w":
|
||||||
if self.args.th_no_webp or ((is_vid or is_au) and self.args.th_ff_jpg):
|
if (
|
||||||
|
self.args.th_no_webp
|
||||||
|
or (is_img and not self.can_webp)
|
||||||
|
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
|
||||||
|
):
|
||||||
fmt = "j"
|
fmt = "j"
|
||||||
|
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
@@ -58,15 +85,23 @@ class ThumbCli(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
tpath = thumb_path(histpath, rem, mtime, fmt)
|
tpath = thumb_path(histpath, rem, mtime, fmt)
|
||||||
|
tpaths = [tpath]
|
||||||
|
if fmt == "w":
|
||||||
|
# also check for jpg (maybe webp is unavailable)
|
||||||
|
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
try:
|
abort = False
|
||||||
st = bos.stat(tpath)
|
for tp in tpaths:
|
||||||
if st.st_size:
|
try:
|
||||||
ret = tpath
|
st = bos.stat(tp)
|
||||||
else:
|
if st.st_size:
|
||||||
return None
|
ret = tpath = tp
|
||||||
except:
|
fmt = ret.rsplit(".")[1]
|
||||||
pass
|
else:
|
||||||
|
abort = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if ret:
|
if ret:
|
||||||
tdir = os.path.dirname(tpath)
|
tdir = os.path.dirname(tpath)
|
||||||
@@ -80,5 +115,8 @@ class ThumbCli(object):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
if abort:
|
||||||
|
return None
|
||||||
|
|
||||||
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
|
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime, fmt)
|
||||||
return x.get()
|
return x.get()
|
||||||
|
|||||||
@@ -47,31 +47,12 @@ try:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
try:
|
||||||
# ffmpeg -formats
|
import pyvips
|
||||||
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
|
|
||||||
FMT_FFV = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc mts h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
|
|
||||||
FMT_FFA = "aac m4a ogg opus flac alac mp3 mp2 ac3 dts wma ra wav aif aiff au alaw ulaw mulaw amr gsm ape tak tta wv"
|
|
||||||
|
|
||||||
if HAVE_HEIF:
|
HAVE_VIPS = True
|
||||||
FMT_PIL += " heif heifs heic heics"
|
except:
|
||||||
|
HAVE_VIPS = False
|
||||||
if HAVE_AVIF:
|
|
||||||
FMT_PIL += " avif avifs"
|
|
||||||
|
|
||||||
FMT_PIL, FMT_FFV, FMT_FFA = [
|
|
||||||
{x: True for x in y.split(" ") if x} for y in [FMT_PIL, FMT_FFV, FMT_FFA]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
THUMBABLE = {}
|
|
||||||
|
|
||||||
if HAVE_PIL:
|
|
||||||
THUMBABLE.update(FMT_PIL)
|
|
||||||
|
|
||||||
if HAVE_FFMPEG and HAVE_FFPROBE:
|
|
||||||
THUMBABLE.update(FMT_FFV)
|
|
||||||
THUMBABLE.update(FMT_FFA)
|
|
||||||
|
|
||||||
|
|
||||||
def thumb_path(histpath, rem, mtime, fmt):
|
def thumb_path(histpath, rem, mtime, fmt):
|
||||||
@@ -141,6 +122,37 @@ class ThumbSrv(object):
|
|||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
self.fmt_pil, self.fmt_vips, self.fmt_ffi, self.fmt_ffv, self.fmt_ffa = [
|
||||||
|
{x: True for x in y.split(",")}
|
||||||
|
for y in [
|
||||||
|
self.args.th_r_pil,
|
||||||
|
self.args.th_r_vips,
|
||||||
|
self.args.th_r_ffi,
|
||||||
|
self.args.th_r_ffv,
|
||||||
|
self.args.th_r_ffa,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
if not HAVE_HEIF:
|
||||||
|
for f in "heif heifs heic heics".split(" "):
|
||||||
|
self.fmt_pil.pop(f, None)
|
||||||
|
|
||||||
|
if not HAVE_AVIF:
|
||||||
|
for f in "avif avifs".split(" "):
|
||||||
|
self.fmt_pil.pop(f, None)
|
||||||
|
|
||||||
|
self.thumbable = {}
|
||||||
|
|
||||||
|
if "pil" in self.args.th_dec:
|
||||||
|
self.thumbable.update(self.fmt_pil)
|
||||||
|
|
||||||
|
if "vips" in self.args.th_dec:
|
||||||
|
self.thumbable.update(self.fmt_vips)
|
||||||
|
|
||||||
|
if "ff" in self.args.th_dec:
|
||||||
|
for t in [self.fmt_ffi, self.fmt_ffv, self.fmt_ffa]:
|
||||||
|
self.thumbable.update(t)
|
||||||
|
|
||||||
def log(self, msg, c=0):
|
def log(self, msg, c=0):
|
||||||
self.log_func("thumb", msg, c)
|
self.log_func("thumb", msg, c)
|
||||||
|
|
||||||
@@ -201,6 +213,16 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getcfg(self):
|
||||||
|
return {
|
||||||
|
"thumbable": self.thumbable,
|
||||||
|
"pil": self.fmt_pil,
|
||||||
|
"vips": self.fmt_vips,
|
||||||
|
"ffi": self.fmt_ffi,
|
||||||
|
"ffv": self.fmt_ffv,
|
||||||
|
"ffa": self.fmt_ffa,
|
||||||
|
}
|
||||||
|
|
||||||
def worker(self):
|
def worker(self):
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
task = self.q.get()
|
task = self.q.get()
|
||||||
@@ -211,15 +233,20 @@ class ThumbSrv(object):
|
|||||||
ext = abspath.split(".")[-1].lower()
|
ext = abspath.split(".")[-1].lower()
|
||||||
fun = None
|
fun = None
|
||||||
if not bos.path.exists(tpath):
|
if not bos.path.exists(tpath):
|
||||||
if ext in FMT_PIL:
|
for lib in self.args.th_dec:
|
||||||
fun = self.conv_pil
|
if fun:
|
||||||
elif ext in FMT_FFV:
|
break
|
||||||
fun = self.conv_ffmpeg
|
elif lib == "pil" and ext in self.fmt_pil:
|
||||||
elif ext in FMT_FFA:
|
fun = self.conv_pil
|
||||||
if tpath.endswith(".opus") or tpath.endswith(".caf"):
|
elif lib == "vips" and ext in self.fmt_vips:
|
||||||
fun = self.conv_opus
|
fun = self.conv_vips
|
||||||
else:
|
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
|
||||||
fun = self.conv_spec
|
fun = self.conv_ffmpeg
|
||||||
|
elif lib == "ff" and ext in self.fmt_ffa:
|
||||||
|
if tpath.endswith(".opus") or tpath.endswith(".caf"):
|
||||||
|
fun = self.conv_opus
|
||||||
|
else:
|
||||||
|
fun = self.conv_spec
|
||||||
|
|
||||||
if fun:
|
if fun:
|
||||||
try:
|
try:
|
||||||
@@ -296,11 +323,29 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
im.save(tpath, **args)
|
im.save(tpath, **args)
|
||||||
|
|
||||||
|
def conv_vips(self, abspath, tpath):
|
||||||
|
crops = ["centre", "none"]
|
||||||
|
if self.args.th_no_crop:
|
||||||
|
crops = ["none"]
|
||||||
|
|
||||||
|
w, h = self.res
|
||||||
|
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||||
|
|
||||||
|
for c in crops:
|
||||||
|
try:
|
||||||
|
kw["crop"] = c
|
||||||
|
img = pyvips.Image.thumbnail(abspath, w, **kw)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
img.write_to_file(tpath, Q=40)
|
||||||
|
|
||||||
def conv_ffmpeg(self, abspath, tpath):
|
def conv_ffmpeg(self, abspath, tpath):
|
||||||
ret, _ = ffprobe(abspath)
|
ret, _ = ffprobe(abspath)
|
||||||
|
|
||||||
ext = abspath.rsplit(".")[-1]
|
ext = abspath.rsplit(".")[-1].lower()
|
||||||
if ext in ["h264", "h265"]:
|
if ext in ["h264", "h265"] or ext in self.fmt_ffi:
|
||||||
seek = []
|
seek = []
|
||||||
else:
|
else:
|
||||||
dur = ret[".dur"][1] if ".dur" in ret else 4
|
dur = ret[".dur"][1] if ".dur" in ret else 4
|
||||||
@@ -350,11 +395,31 @@ class ThumbSrv(object):
|
|||||||
def _run_ff(self, cmd):
|
def _run_ff(self, cmd):
|
||||||
# self.log((b" ".join(cmd)).decode("utf-8"))
|
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||||
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
|
ret, sout, serr = runcmd(cmd, timeout=self.args.th_convt)
|
||||||
if ret != 0:
|
if not ret:
|
||||||
m = "FFmpeg failed (probably a corrupt video file):\n"
|
return
|
||||||
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
|
|
||||||
self.log(m, c="1;30")
|
c = "1;30"
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
m = "FFmpeg failed (probably a corrupt video file):\n"
|
||||||
|
if cmd[-1].lower().endswith(b".webp") and (
|
||||||
|
"Error selecting an encoder" in serr
|
||||||
|
or "Automatic encoder selection failed" in serr
|
||||||
|
or "Default encoder for format webp" in serr
|
||||||
|
or "Please choose an encoder manually" in serr
|
||||||
|
):
|
||||||
|
self.args.th_ff_jpg = True
|
||||||
|
m = "FFmpeg failed because it was compiled without libwebp; enabling --th-ff-jpg to force jpeg output:\n"
|
||||||
|
c = 1
|
||||||
|
|
||||||
|
if (
|
||||||
|
"Requested resampling engine is unavailable" in serr
|
||||||
|
or "output pad on Parsed_aresample_" in serr
|
||||||
|
):
|
||||||
|
m = "FFmpeg failed because it was compiled without libsox; you must set --th-ff-swr to force swr resampling:\n"
|
||||||
|
c = 1
|
||||||
|
|
||||||
|
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
|
||||||
|
self.log(m, c=c)
|
||||||
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
def conv_spec(self, abspath, tpath):
|
def conv_spec(self, abspath, tpath):
|
||||||
ret, _ = ffprobe(abspath)
|
ret, _ = ffprobe(abspath)
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ class U2idx(object):
|
|||||||
fhash = body["hash"]
|
fhash = body["hash"]
|
||||||
wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash)
|
wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash)
|
||||||
|
|
||||||
uq = "where substr(w,1,16) = ? and w = ?"
|
uq = "substr(w,1,16) = ? and w = ?"
|
||||||
uv = [wark[:16], wark]
|
uv = [wark[:16], wark]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.run_query(vols, uq, uv)[0]
|
return self.run_query(vols, uq, uv, True, False)[0]
|
||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
@@ -87,17 +87,16 @@ class U2idx(object):
|
|||||||
|
|
||||||
q = ""
|
q = ""
|
||||||
va = []
|
va = []
|
||||||
joins = ""
|
have_up = False # query has up.* operands
|
||||||
|
have_mt = False
|
||||||
is_key = True
|
is_key = True
|
||||||
is_size = False
|
is_size = False
|
||||||
is_date = False
|
is_date = False
|
||||||
|
field_end = "" # closing parenthesis or whatever
|
||||||
kw_key = ["(", ")", "and ", "or ", "not "]
|
kw_key = ["(", ")", "and ", "or ", "not "]
|
||||||
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
|
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
|
||||||
ptn_mt = re.compile(r"^\.?[a-z_-]+$")
|
ptn_mt = re.compile(r"^\.?[a-z_-]+$")
|
||||||
mt_ctr = 0
|
ptn_lc = re.compile(r" (mt\.v) ([=<!>]+) \? \) $")
|
||||||
mt_keycmp = "substr(up.w,1,16)"
|
|
||||||
mt_keycmp2 = None
|
|
||||||
ptn_lc = re.compile(r" (mt[0-9]+\.v) ([=<!>]+) \? $")
|
|
||||||
ptn_lcv = re.compile(r"[a-zA-Z]")
|
ptn_lcv = re.compile(r"[a-zA-Z]")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -133,28 +132,31 @@ class U2idx(object):
|
|||||||
if v == "size":
|
if v == "size":
|
||||||
v = "up.sz"
|
v = "up.sz"
|
||||||
is_size = True
|
is_size = True
|
||||||
|
have_up = True
|
||||||
|
|
||||||
elif v == "date":
|
elif v == "date":
|
||||||
v = "up.mt"
|
v = "up.mt"
|
||||||
is_date = True
|
is_date = True
|
||||||
|
have_up = True
|
||||||
|
|
||||||
elif v == "path":
|
elif v == "path":
|
||||||
v = "up.rd"
|
v = "trim(?||up.rd,'/')"
|
||||||
|
va.append("\nrd")
|
||||||
|
have_up = True
|
||||||
|
|
||||||
elif v == "name":
|
elif v == "name":
|
||||||
v = "up.fn"
|
v = "up.fn"
|
||||||
|
have_up = True
|
||||||
|
|
||||||
elif v == "tags" or ptn_mt.match(v):
|
elif v == "tags" or ptn_mt.match(v):
|
||||||
mt_ctr += 1
|
have_mt = True
|
||||||
mt_keycmp2 = "mt{}.w".format(mt_ctr)
|
field_end = ") "
|
||||||
joins += "inner join mt mt{} on {} = {} ".format(
|
|
||||||
mt_ctr, mt_keycmp, mt_keycmp2
|
|
||||||
)
|
|
||||||
mt_keycmp = mt_keycmp2
|
|
||||||
if v == "tags":
|
if v == "tags":
|
||||||
v = "mt{0}.v".format(mt_ctr)
|
vq = "mt.v"
|
||||||
else:
|
else:
|
||||||
v = "+mt{0}.k = '{1}' and mt{0}.v".format(mt_ctr, v)
|
vq = "+mt.k = '{}' and mt.v".format(v)
|
||||||
|
|
||||||
|
v = "exists(select 1 from mt where mt.w = mtw and " + vq
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Pebkac(400, "invalid key [" + v + "]")
|
raise Pebkac(400, "invalid key [" + v + "]")
|
||||||
@@ -200,6 +202,10 @@ class U2idx(object):
|
|||||||
va.append(v)
|
va.append(v)
|
||||||
is_key = True
|
is_key = True
|
||||||
|
|
||||||
|
if field_end:
|
||||||
|
q += field_end
|
||||||
|
field_end = ""
|
||||||
|
|
||||||
# lowercase tag searches
|
# lowercase tag searches
|
||||||
m = ptn_lc.search(q)
|
m = ptn_lc.search(q)
|
||||||
if not m or not ptn_lcv.search(unicode(v)):
|
if not m or not ptn_lcv.search(unicode(v)):
|
||||||
@@ -211,16 +217,16 @@ class U2idx(object):
|
|||||||
|
|
||||||
field, oper = m.groups()
|
field, oper = m.groups()
|
||||||
if oper in ["=", "=="]:
|
if oper in ["=", "=="]:
|
||||||
q += " {} like ? ".format(field)
|
q += " {} like ? ) ".format(field)
|
||||||
else:
|
else:
|
||||||
q += " lower({}) {} ? ".format(field, oper)
|
q += " lower({}) {} ? ) ".format(field, oper)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.run_query(vols, joins + "where " + q, va)
|
return self.run_query(vols, q, va, have_up, have_mt)
|
||||||
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):
|
def run_query(self, vols, uq, uv, have_up, have_mt):
|
||||||
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
|
||||||
@@ -237,16 +243,19 @@ class U2idx(object):
|
|||||||
thr.start()
|
thr.start()
|
||||||
|
|
||||||
if not uq or not uv:
|
if not uq or not uv:
|
||||||
q = "select * from up"
|
uq = "select * from up"
|
||||||
v = ()
|
uv = ()
|
||||||
|
elif have_mt:
|
||||||
|
uq = "select up.*, substr(up.w,1,16) mtw from up where " + uq
|
||||||
|
uv = tuple(uv)
|
||||||
else:
|
else:
|
||||||
q = "select up.* from up " + uq
|
uq = "select up.* from up where " + uq
|
||||||
v = tuple(uv)
|
uv = tuple(uv)
|
||||||
|
|
||||||
self.log("qs: {!r} {!r}".format(q, v))
|
self.log("qs: {!r} {!r}".format(uq, uv))
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
lim = 1000
|
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)
|
||||||
@@ -255,11 +264,19 @@ class U2idx(object):
|
|||||||
|
|
||||||
self.active_cur = cur
|
self.active_cur = cur
|
||||||
|
|
||||||
|
vuv = []
|
||||||
|
for v in uv:
|
||||||
|
if v == "\nrd":
|
||||||
|
v = vtop + "/"
|
||||||
|
|
||||||
|
vuv.append(v)
|
||||||
|
vuv = tuple(vuv)
|
||||||
|
|
||||||
sret = []
|
sret = []
|
||||||
fk = flags.get("fk")
|
fk = flags.get("fk")
|
||||||
c = cur.execute(q, v)
|
c = cur.execute(uq, vuv)
|
||||||
for hit in c:
|
for hit in c:
|
||||||
w, ts, sz, rd, fn, ip, at = hit
|
w, ts, sz, rd, fn, ip, at = hit[:7]
|
||||||
lim -= 1
|
lim -= 1
|
||||||
if lim <= 0:
|
if lim <= 0:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -470,9 +470,11 @@ class Up2k(object):
|
|||||||
ft = "\033[0;32m{}{:.0}"
|
ft = "\033[0;32m{}{:.0}"
|
||||||
ff = "\033[0;35m{}{:.0}"
|
ff = "\033[0;35m{}{:.0}"
|
||||||
fv = "\033[0;36m{}:\033[1;30m{}"
|
fv = "\033[0;36m{}:\033[1;30m{}"
|
||||||
|
fx = set(("html_head",))
|
||||||
a = [
|
a = [
|
||||||
(ft if v is True else ff if v is False else fv).format(k, str(v))
|
(ft if v is True else ff if v is False else fv).format(k, str(v))
|
||||||
for k, v in flags.items()
|
for k, v in flags.items()
|
||||||
|
if k not in fx
|
||||||
]
|
]
|
||||||
if a:
|
if a:
|
||||||
vpath = "?"
|
vpath = "?"
|
||||||
@@ -594,6 +596,9 @@ class Up2k(object):
|
|||||||
if stat.S_ISDIR(inf.st_mode):
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
if abspath in excl or abspath == histpath:
|
if abspath in excl or abspath == histpath:
|
||||||
continue
|
continue
|
||||||
|
if iname == ".th" and bos.path.isdir(os.path.join(abspath, "top")):
|
||||||
|
# abandoned or foreign, skip
|
||||||
|
continue
|
||||||
# self.log(" dir: {}".format(abspath))
|
# self.log(" dir: {}".format(abspath))
|
||||||
try:
|
try:
|
||||||
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)
|
ret += self._build_dir(dbw, top, excl, abspath, rei, reh, seen)
|
||||||
@@ -1137,9 +1142,9 @@ class Up2k(object):
|
|||||||
m = "database is version {}, this copyparty only supports versions <= {}"
|
m = "database is version {}, this copyparty only supports versions <= {}"
|
||||||
raise Exception(m.format(ver, DB_VER))
|
raise Exception(m.format(ver, DB_VER))
|
||||||
|
|
||||||
msg = "creating new DB (old is bad); backup: {}"
|
msg = "creating new DB (old is bad); backup: "
|
||||||
if ver:
|
if ver:
|
||||||
msg = "creating new DB (too old to upgrade); backup: {}"
|
msg = "creating new DB (too old to upgrade); backup: "
|
||||||
|
|
||||||
cur = self._backup_db(db_path, cur, ver, msg)
|
cur = self._backup_db(db_path, cur, ver, msg)
|
||||||
db = cur.connection
|
db = cur.connection
|
||||||
@@ -1235,6 +1240,11 @@ class Up2k(object):
|
|||||||
wark = self._get_wark(cj)
|
wark = self._get_wark(cj)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
job = None
|
job = None
|
||||||
|
try:
|
||||||
|
dev = bos.stat(os.path.join(cj["ptop"], cj["prel"])).st_dev
|
||||||
|
except:
|
||||||
|
dev = 0
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
cur = self.cur.get(cj["ptop"])
|
cur = self.cur.get(cj["ptop"])
|
||||||
reg = self.registry[cj["ptop"]]
|
reg = self.registry[cj["ptop"]]
|
||||||
@@ -1246,37 +1256,42 @@ class Up2k(object):
|
|||||||
q = r"select * from up where substr(w,1,16) = ? and w = ?"
|
q = r"select * from up where substr(w,1,16) = ? and w = ?"
|
||||||
argv = (wark[:16], wark)
|
argv = (wark[:16], wark)
|
||||||
|
|
||||||
|
alts = []
|
||||||
cur = cur.execute(q, argv)
|
cur = cur.execute(q, argv)
|
||||||
for _, dtime, dsize, dp_dir, dp_fn, ip, at in cur:
|
for _, dtime, dsize, dp_dir, dp_fn, ip, at in cur:
|
||||||
if dp_dir.startswith("//") or dp_fn.startswith("//"):
|
if dp_dir.startswith("//") or dp_fn.startswith("//"):
|
||||||
dp_dir, dp_fn = s3dec(dp_dir, dp_fn)
|
dp_dir, dp_fn = s3dec(dp_dir, dp_fn)
|
||||||
|
|
||||||
if job and (dp_dir != cj["prel"] or dp_fn != cj["name"]):
|
dp_abs = "/".join([cj["ptop"], dp_dir, dp_fn])
|
||||||
|
try:
|
||||||
|
st = bos.stat(dp_abs)
|
||||||
|
if stat.S_ISLNK(st.st_mode):
|
||||||
|
# broken symlink
|
||||||
|
raise Exception()
|
||||||
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dp_abs = "/".join([cj["ptop"], dp_dir, dp_fn])
|
j = {
|
||||||
# relying on this to fail on broken symlinks
|
"name": dp_fn,
|
||||||
try:
|
"prel": dp_dir,
|
||||||
sz = bos.path.getsize(dp_abs)
|
"vtop": cj["vtop"],
|
||||||
except:
|
"ptop": cj["ptop"],
|
||||||
sz = 0
|
"size": dsize,
|
||||||
|
"lmod": dtime,
|
||||||
if sz:
|
"addr": ip,
|
||||||
# self.log("--- " + wark + " " + dp_abs + " found file", 4)
|
"at": at,
|
||||||
job = {
|
"hash": [],
|
||||||
"name": dp_fn,
|
"need": [],
|
||||||
"prel": dp_dir,
|
"busy": {},
|
||||||
"vtop": cj["vtop"],
|
}
|
||||||
"ptop": cj["ptop"],
|
score = (
|
||||||
"size": dsize,
|
(3 if st.st_dev == dev else 0)
|
||||||
"lmod": dtime,
|
+ (2 if dp_dir == cj["prel"] else 0)
|
||||||
"addr": ip,
|
+ (1 if dp_fn == cj["name"] else 0)
|
||||||
"at": at,
|
)
|
||||||
"hash": [],
|
alts.append([score, -len(alts), j])
|
||||||
"need": [],
|
|
||||||
"busy": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
job = sorted(alts, reverse=True)[0][2] if alts else None
|
||||||
if job and wark in reg:
|
if job and wark in reg:
|
||||||
# self.log("pop " + wark + " " + job["name"] + " handle_json db", 4)
|
# self.log("pop " + wark + " " + job["name"] + " handle_json db", 4)
|
||||||
del reg[wark]
|
del reg[wark]
|
||||||
@@ -1417,14 +1432,14 @@ class Up2k(object):
|
|||||||
|
|
||||||
linked = False
|
linked = False
|
||||||
try:
|
try:
|
||||||
if self.args.no_symlink:
|
if self.args.no_dedup:
|
||||||
raise Exception("disabled in config")
|
raise Exception("disabled in config")
|
||||||
|
|
||||||
lsrc = src
|
lsrc = src
|
||||||
ldst = dst
|
ldst = dst
|
||||||
fs1 = bos.stat(os.path.dirname(src)).st_dev
|
fs1 = bos.stat(os.path.dirname(src)).st_dev
|
||||||
fs2 = bos.stat(os.path.dirname(dst)).st_dev
|
fs2 = bos.stat(os.path.dirname(dst)).st_dev
|
||||||
if fs1 == 0:
|
if fs1 == 0 or fs2 == 0:
|
||||||
# py2 on winxp or other unsupported combination
|
# py2 on winxp or other unsupported combination
|
||||||
raise OSError()
|
raise OSError()
|
||||||
elif fs1 == fs2:
|
elif fs1 == fs2:
|
||||||
@@ -1445,10 +1460,21 @@ class Up2k(object):
|
|||||||
lsrc = nsrc[nc:]
|
lsrc = nsrc[nc:]
|
||||||
hops = len(ndst[nc:]) - 1
|
hops = len(ndst[nc:]) - 1
|
||||||
lsrc = "../" * hops + "/".join(lsrc)
|
lsrc = "../" * hops + "/".join(lsrc)
|
||||||
os.symlink(fsenc(lsrc), fsenc(ldst))
|
|
||||||
linked = True
|
try:
|
||||||
|
if self.args.hardlink:
|
||||||
|
os.link(fsenc(src), fsenc(dst))
|
||||||
|
linked = True
|
||||||
|
except Exception as ex:
|
||||||
|
self.log("cannot hardlink: " + repr(ex))
|
||||||
|
if self.args.never_symlink:
|
||||||
|
raise Exception("symlink-fallback disabled in cfg")
|
||||||
|
|
||||||
|
if not linked:
|
||||||
|
os.symlink(fsenc(lsrc), fsenc(ldst))
|
||||||
|
linked = True
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("cannot symlink; creating copy: " + repr(ex))
|
self.log("cannot link; creating copy: " + repr(ex))
|
||||||
shutil.copy2(fsenc(src), fsenc(dst))
|
shutil.copy2(fsenc(src), fsenc(dst))
|
||||||
|
|
||||||
if lmod and (not linked or SYMTIME):
|
if lmod and (not linked or SYMTIME):
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ SYMTIME = sys.version_info >= (3, 6) and os.supports_follow_symlinks
|
|||||||
|
|
||||||
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
|
HTTP_TS_FMT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||||
|
|
||||||
|
META_NOBOTS = '<meta name="robots" content="noindex, nofollow">'
|
||||||
|
|
||||||
HTTPCODE = {
|
HTTPCODE = {
|
||||||
200: "OK",
|
200: "OK",
|
||||||
204: "No Content",
|
204: "No Content",
|
||||||
@@ -483,13 +485,13 @@ def vol_san(vols, txt):
|
|||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
|
||||||
def min_ex():
|
def min_ex(max_lines=8, reverse=False):
|
||||||
et, ev, tb = sys.exc_info()
|
et, ev, tb = sys.exc_info()
|
||||||
tb = traceback.extract_tb(tb)
|
tb = traceback.extract_tb(tb)
|
||||||
fmt = "{} @ {} <{}>: {}"
|
fmt = "{} @ {} <{}>: {}"
|
||||||
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
|
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
|
||||||
ex.append("[{}] {}".format(et.__name__, ev))
|
ex.append("[{}] {}".format(et.__name__, ev))
|
||||||
return "\n".join(ex[-8:])
|
return "\n".join(ex[-max_lines:][:: -1 if reverse else 1])
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@@ -1164,13 +1166,15 @@ def yieldfile(fn):
|
|||||||
yield buf
|
yield buf
|
||||||
|
|
||||||
|
|
||||||
def hashcopy(fin, fout):
|
def hashcopy(fin, fout, slp=0):
|
||||||
hashobj = hashlib.sha512()
|
hashobj = hashlib.sha512()
|
||||||
tlen = 0
|
tlen = 0
|
||||||
for buf in fin:
|
for buf in fin:
|
||||||
tlen += len(buf)
|
tlen += len(buf)
|
||||||
hashobj.update(buf)
|
hashobj.update(buf)
|
||||||
fout.write(buf)
|
fout.write(buf)
|
||||||
|
if slp:
|
||||||
|
time.sleep(slp)
|
||||||
|
|
||||||
digest = hashobj.digest()[:33]
|
digest = hashobj.digest()[:33]
|
||||||
digest_b64 = base64.urlsafe_b64encode(digest).decode("utf-8")
|
digest_b64 = base64.urlsafe_b64encode(digest).decode("utf-8")
|
||||||
|
|||||||
@@ -17,12 +17,11 @@ window.baguetteBox = (function () {
|
|||||||
titleTag: false,
|
titleTag: false,
|
||||||
async: false,
|
async: false,
|
||||||
preload: 2,
|
preload: 2,
|
||||||
animation: 'slideIn',
|
|
||||||
afterShow: null,
|
afterShow: null,
|
||||||
afterHide: null,
|
afterHide: null,
|
||||||
onChange: null,
|
onChange: null,
|
||||||
},
|
},
|
||||||
overlay, slider, btnPrev, btnNext, btnHelp, btnRotL, btnRotR, btnSel, btnVmode, btnClose,
|
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnVmode, btnClose,
|
||||||
currentGallery = [],
|
currentGallery = [],
|
||||||
currentIndex = 0,
|
currentIndex = 0,
|
||||||
isOverlayVisible = false,
|
isOverlayVisible = false,
|
||||||
@@ -30,6 +29,7 @@ window.baguetteBox = (function () {
|
|||||||
touchFlag = false, // busy
|
touchFlag = false, // busy
|
||||||
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
|
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
|
||||||
re_v = /.+\.(webm|mp4)(\?|$)/i,
|
re_v = /.+\.(webm|mp4)(\?|$)/i,
|
||||||
|
anims = ['slideIn', 'fadeIn', 'none'],
|
||||||
data = {}, // all galleries
|
data = {}, // all galleries
|
||||||
imagesElements = [],
|
imagesElements = [],
|
||||||
documentLastFocus = null,
|
documentLastFocus = null,
|
||||||
@@ -178,6 +178,7 @@ window.baguetteBox = (function () {
|
|||||||
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">></button>' +
|
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">></button>' +
|
||||||
'<div id="bbox-btns">' +
|
'<div id="bbox-btns">' +
|
||||||
'<button id="bbox-help" type="button">?</button>' +
|
'<button id="bbox-help" type="button">?</button>' +
|
||||||
|
'<button id="bbox-anim" type="button" tt="a">-</button>' +
|
||||||
'<button id="bbox-rotl" type="button">↶</button>' +
|
'<button id="bbox-rotl" type="button">↶</button>' +
|
||||||
'<button id="bbox-rotr" type="button">↷</button>' +
|
'<button id="bbox-rotr" type="button">↷</button>' +
|
||||||
'<button id="bbox-tsel" type="button">sel</button>' +
|
'<button id="bbox-tsel" type="button">sel</button>' +
|
||||||
@@ -193,6 +194,7 @@ window.baguetteBox = (function () {
|
|||||||
btnPrev = ebi('bbox-prev');
|
btnPrev = ebi('bbox-prev');
|
||||||
btnNext = ebi('bbox-next');
|
btnNext = ebi('bbox-next');
|
||||||
btnHelp = ebi('bbox-help');
|
btnHelp = ebi('bbox-help');
|
||||||
|
btnAnim = ebi('bbox-anim');
|
||||||
btnRotL = ebi('bbox-rotl');
|
btnRotL = ebi('bbox-rotl');
|
||||||
btnRotR = ebi('bbox-rotr');
|
btnRotR = ebi('bbox-rotr');
|
||||||
btnSel = ebi('bbox-tsel');
|
btnSel = ebi('bbox-tsel');
|
||||||
@@ -284,6 +286,16 @@ window.baguetteBox = (function () {
|
|||||||
rotn(e.shiftKey ? -1 : 1);
|
rotn(e.shiftKey ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function anim() {
|
||||||
|
var i = (anims.indexOf(options.animation) + 1) % anims.length,
|
||||||
|
o = options;
|
||||||
|
swrite('ganim', anims[i]);
|
||||||
|
options = {};
|
||||||
|
setOptions(o);
|
||||||
|
if (tt.en)
|
||||||
|
tt.show.bind(this)();
|
||||||
|
}
|
||||||
|
|
||||||
function setVmode() {
|
function setVmode() {
|
||||||
var v = vid();
|
var v = vid();
|
||||||
ebi('bbox-vmode').style.display = v ? '' : 'none';
|
ebi('bbox-vmode').style.display = v ? '' : 'none';
|
||||||
@@ -397,6 +409,7 @@ window.baguetteBox = (function () {
|
|||||||
bind(btnClose, 'click', hideOverlay);
|
bind(btnClose, 'click', hideOverlay);
|
||||||
bind(btnVmode, 'click', tglVmode);
|
bind(btnVmode, 'click', tglVmode);
|
||||||
bind(btnHelp, 'click', halp);
|
bind(btnHelp, 'click', halp);
|
||||||
|
bind(btnAnim, 'click', anim);
|
||||||
bind(btnRotL, 'click', rotl);
|
bind(btnRotL, 'click', rotl);
|
||||||
bind(btnRotR, 'click', rotr);
|
bind(btnRotR, 'click', rotr);
|
||||||
bind(btnSel, 'click', tglsel);
|
bind(btnSel, 'click', tglsel);
|
||||||
@@ -414,6 +427,7 @@ window.baguetteBox = (function () {
|
|||||||
unbind(btnClose, 'click', hideOverlay);
|
unbind(btnClose, 'click', hideOverlay);
|
||||||
unbind(btnVmode, 'click', tglVmode);
|
unbind(btnVmode, 'click', tglVmode);
|
||||||
unbind(btnHelp, 'click', halp);
|
unbind(btnHelp, 'click', halp);
|
||||||
|
unbind(btnAnim, 'click', anim);
|
||||||
unbind(btnRotL, 'click', rotl);
|
unbind(btnRotL, 'click', rotl);
|
||||||
unbind(btnRotR, 'click', rotr);
|
unbind(btnRotR, 'click', rotr);
|
||||||
unbind(btnSel, 'click', tglsel);
|
unbind(btnSel, 'click', tglsel);
|
||||||
@@ -459,7 +473,12 @@ window.baguetteBox = (function () {
|
|||||||
if (typeof newOptions[item] !== 'undefined')
|
if (typeof newOptions[item] !== 'undefined')
|
||||||
options[item] = newOptions[item];
|
options[item] = newOptions[item];
|
||||||
}
|
}
|
||||||
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .4s ease' :
|
|
||||||
|
var an = options.animation = sread('ganim') || anims[ANIM ? 0 : 2];
|
||||||
|
btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)];
|
||||||
|
btnAnim.setAttribute('tt', 'animation: ' + an);
|
||||||
|
|
||||||
|
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .3s ease' :
|
||||||
options.animation === 'slideIn' ? '' : 'none');
|
options.animation === 'slideIn' ? '' : 'none');
|
||||||
|
|
||||||
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
|
if (options.buttons === 'auto' && ('ontouchstart' in window || currentGallery.length === 1))
|
||||||
@@ -520,6 +539,7 @@ window.baguetteBox = (function () {
|
|||||||
if (overlay.style.display === 'none')
|
if (overlay.style.display === 'none')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
sethash('');
|
||||||
unbind(document, 'keydown', keyDownHandler);
|
unbind(document, 'keydown', keyDownHandler);
|
||||||
unbind(document, 'keyup', keyUpHandler);
|
unbind(document, 'keyup', keyUpHandler);
|
||||||
unbind(document, 'fullscreenchange', onFSC);
|
unbind(document, 'fullscreenchange', onFSC);
|
||||||
@@ -806,7 +826,7 @@ window.baguetteBox = (function () {
|
|||||||
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||||
slider.style.left = offset;
|
slider.style.left = offset;
|
||||||
slider.style.opacity = 1;
|
slider.style.opacity = 1;
|
||||||
}, 400);
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
xform ?
|
xform ?
|
||||||
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
slider.style.transform = 'translate3d(' + offset + ',0,0)' :
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
|||||||
<title>⇆🎉 {{ title }}</title>
|
<title>⇆🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
||||||
{%- if css %}
|
{%- if css %}
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
<input type="file" name="f" multiple /><br />
|
<input type="file" name="f" multiple /><br />
|
||||||
<input type="submit" value="start upload">
|
<input type="submit" value="start upload">
|
||||||
</form>
|
</form>
|
||||||
|
<a id="bbsw" href="?b=u"><br />switch to basic browser</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="op_mkdir" class="opview opbox act">
|
<div id="op_mkdir" class="opview opbox act">
|
||||||
@@ -133,6 +135,9 @@
|
|||||||
<script>
|
<script>
|
||||||
var acct = "{{ acct }}",
|
var acct = "{{ acct }}",
|
||||||
perms = {{ perms }},
|
perms = {{ perms }},
|
||||||
|
themes = {{ themes }},
|
||||||
|
dtheme = "{{ dtheme }}",
|
||||||
|
srvinf = "{{ srv_info }}",
|
||||||
def_hcols = {{ def_hcols|tojson }},
|
def_hcols = {{ def_hcols|tojson }},
|
||||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
@@ -146,9 +151,10 @@
|
|||||||
readme = {{ readme|tojson }},
|
readme = {{ readme|tojson }},
|
||||||
ls0 = {{ ls0|tojson }};
|
ls0 = {{ ls0|tojson }};
|
||||||
|
|
||||||
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
document.documentElement.setAttribute("class", localStorage.theme || dtheme);
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
|
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/browser.js?_={{ ts }}"></script>
|
<script src="/.cpr/browser.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/up2k.js?_={{ ts }}"></script>
|
<script src="/.cpr/up2k.js?_={{ ts }}"></script>
|
||||||
{%- if js %}
|
{%- if js %}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ function dbg(msg) {
|
|||||||
|
|
||||||
// toolbar
|
// toolbar
|
||||||
ebi('ops').innerHTML = (
|
ebi('ops').innerHTML = (
|
||||||
'<a href="#" data-dest="" tt="close submenu">--</a>\n' +
|
'<a href="#" data-dest="" tt="close submenu">--</a>' +
|
||||||
'<a href="#" data-perm="read" data-dep="idx" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those$N$N<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' +
|
'<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>\n' : '') +
|
(have_del && have_unpost ? '<a href="#" data-dest="unpost" data-dep="idx" tt="unpost: delete your recent uploads">🧯</a>' : '') +
|
||||||
'<a href="#" data-dest="up2k">🚀</a>\n' +
|
'<a href="#" data-dest="up2k">🚀</a>' +
|
||||||
'<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>\n' +
|
'<a href="#" data-perm="write" data-dest="bup" tt="bup: basic uploader, even supports netscape 4.0">🎈</a>' +
|
||||||
'<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>\n' +
|
'<a href="#" data-perm="write" data-dest="mkdir" tt="mkdir: create a new directory">📂</a>' +
|
||||||
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>\n' +
|
'<a href="#" data-perm="read write" data-dest="new_md" tt="new-md: create a new markdown document">📝</a>' +
|
||||||
'<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>\n' +
|
'<a href="#" data-perm="write" data-dest="msg" tt="msg: send a message to the server log">📟</a>' +
|
||||||
'<a href="#" data-dest="player" tt="media player options">🎺</a>\n' +
|
'<a href="#" data-dest="player" tt="media player options">🎺</a>' +
|
||||||
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>\n' +
|
'<a href="#" data-dest="cfg" tt="configuration options">⚙️</a>' +
|
||||||
'<div id="opdesc"></div>'
|
'<div id="opdesc"></div>'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ ebi('op_up2k').innerHTML = (
|
|||||||
' <td class="c"><br />parallel uploads:</td>\n' +
|
' <td class="c"><br />parallel uploads:</td>\n' +
|
||||||
' <td class="c" rowspan="2">\n' +
|
' <td class="c" rowspan="2">\n' +
|
||||||
' <input type="checkbox" id="multitask" />\n' +
|
' <input type="checkbox" id="multitask" />\n' +
|
||||||
' <label for="multitask" tt="continue hashing other files while uploading">🏃</label>\n' +
|
' <label for="multitask" tt="continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck">🏃</label>\n' +
|
||||||
' </td>\n' +
|
' </td>\n' +
|
||||||
' <td class="c" rowspan="2">\n' +
|
' <td class="c" rowspan="2">\n' +
|
||||||
' <input type="checkbox" id="ask_up" />\n' +
|
' <input type="checkbox" id="ask_up" />\n' +
|
||||||
@@ -74,7 +74,7 @@ ebi('op_up2k').innerHTML = (
|
|||||||
' <tr>\n' +
|
' <tr>\n' +
|
||||||
' <td class="c">\n' +
|
' <td class="c">\n' +
|
||||||
' <a href="#" class="b" id="nthread_sub">–</a><input\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' +
|
' href="#" class="b" id="nthread_add">+</a><br /> \n' +
|
||||||
' </td>\n' +
|
' </td>\n' +
|
||||||
' </tr>\n' +
|
' </tr>\n' +
|
||||||
@@ -84,10 +84,9 @@ ebi('op_up2k').innerHTML = (
|
|||||||
|
|
||||||
'<div id="u2btn_ct">\n' +
|
'<div id="u2btn_ct">\n' +
|
||||||
' <div id="u2btn">\n' +
|
' <div id="u2btn">\n' +
|
||||||
' <span id="u2bm"></span><br />\n' +
|
' <span id="u2bm"></span>\n' +
|
||||||
' drag/drop files<br />\n' +
|
' drop files / folders<br />\n' +
|
||||||
' and folders here<br />\n' +
|
' here (or click me)\n' +
|
||||||
' (or click me)\n' +
|
|
||||||
' </div>\n' +
|
' </div>\n' +
|
||||||
'</div>\n' +
|
'</div>\n' +
|
||||||
|
|
||||||
@@ -121,8 +120,7 @@ ebi('op_up2k').innerHTML = (
|
|||||||
'</table></div>\n' +
|
'</table></div>\n' +
|
||||||
|
|
||||||
'<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' +
|
'<p id="u2flagblock"><b>the files were added to the queue</b><br />however there is a busy up2k in another browser tab,<br />so waiting for that to finish first</p>\n' +
|
||||||
'<p id="u2foot"></p>\n' +
|
'<p id="u2foot"></p>'
|
||||||
'<p id="u2footfoot" data-perm="write">( you can use the <a href="#" id="u2nope">basic uploader</a> if you don\'t need lastmod timestamps, resumable uploads, or progress bars )</p>'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -146,7 +144,6 @@ ebi('op_cfg').innerHTML = (
|
|||||||
' <h3>switches</h3>\n' +
|
' <h3>switches</h3>\n' +
|
||||||
' <div>\n' +
|
' <div>\n' +
|
||||||
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔">ℹ️ tooltips</a>\n' +
|
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔">ℹ️ tooltips</a>\n' +
|
||||||
' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' +
|
|
||||||
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
|
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
|
||||||
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
|
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
|
||||||
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
|
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
|
||||||
@@ -154,6 +151,11 @@ ebi('op_cfg').innerHTML = (
|
|||||||
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
|
' <a id="spafiles" class="tgl btn" href="#" tt="speedboost when not using the navpane;$Nturn it off if things arent loading somehow">spa</a>\n' +
|
||||||
' </div>\n' +
|
' </div>\n' +
|
||||||
'</div>\n' +
|
'</div>\n' +
|
||||||
|
'<div>\n' +
|
||||||
|
' <h3>themes</h3>\n' +
|
||||||
|
' <div id="themes">\n' +
|
||||||
|
' </div>\n' +
|
||||||
|
'</div>\n' +
|
||||||
(have_zip ? (
|
(have_zip ? (
|
||||||
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
||||||
) : '') +
|
) : '') +
|
||||||
@@ -262,6 +264,8 @@ function goto(dest) {
|
|||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clmod(document.documentElement, 'op_open', dest);
|
||||||
|
|
||||||
if (window['treectl'])
|
if (window['treectl'])
|
||||||
treectl.onscroll();
|
treectl.onscroll();
|
||||||
}
|
}
|
||||||
@@ -427,7 +431,7 @@ var mpl = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function announce() {
|
function announce() {
|
||||||
if (!r.os_ctl)
|
if (!r.os_ctl || !mp.au)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var np = get_np()[0],
|
var np = get_np()[0],
|
||||||
@@ -509,7 +513,7 @@ catch (ex) { }
|
|||||||
|
|
||||||
var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i :
|
var re_au_native = can_ogg ? /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i :
|
||||||
have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i,
|
have_acode ? /\.(opus|m4a|aac|mp3|wav|flac)$/i : /\.(m4a|aac|mp3|wav|flac)$/i,
|
||||||
re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv)$/i;
|
re_au_all = /\.(aac|m4a|ogg|opus|flac|alac|mp3|mp2|ac3|dts|wma|ra|wav|aif|aiff|au|alaw|ulaw|mulaw|amr|gsm|ape|tak|tta|wv|mpc)$/i;
|
||||||
|
|
||||||
|
|
||||||
// extract songs + add play column
|
// extract songs + add play column
|
||||||
@@ -703,8 +707,12 @@ var widget = (function () {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
clmod(document.documentElement, 'np_open', is_open);
|
clmod(document.documentElement, 'np_open', is_open);
|
||||||
widget.className = is_open ? 'open' : '';
|
clmod(widget, 'open', is_open);
|
||||||
bcfg_set('au_open', r.is_open = is_open);
|
bcfg_set('au_open', r.is_open = is_open);
|
||||||
|
if (window.vbar) {
|
||||||
|
pbar.onresize();
|
||||||
|
vbar.onresize();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
r.toggle = function (e) {
|
r.toggle = function (e) {
|
||||||
@@ -743,17 +751,29 @@ var widget = (function () {
|
|||||||
o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
|
o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
|
||||||
o.value = m;
|
o.value = m;
|
||||||
document.body.appendChild(o);
|
document.body.appendChild(o);
|
||||||
o.focus();
|
|
||||||
o.select();
|
var cln = function () {
|
||||||
document.execCommand("copy");
|
o.value = 'copied to clipboard ';
|
||||||
o.value = 'copied to clipboard ';
|
setTimeout(function () {
|
||||||
setTimeout(function () {
|
document.body.removeChild(o);
|
||||||
document.body.removeChild(o);
|
}, 500);
|
||||||
}, 500);
|
};
|
||||||
|
var fb = function () {
|
||||||
|
console.log('fb');
|
||||||
|
o.focus();
|
||||||
|
o.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
cln();
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
// https only
|
||||||
|
navigator.clipboard.writeText(m).then(cln, fb);
|
||||||
|
}
|
||||||
|
catch (ex) { fb(); }
|
||||||
};
|
};
|
||||||
r.set(sread('au_open') == 1);
|
r.set(sread('au_open') == 1);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
clmod(ebi('widget'), 'anim', 1);
|
clmod(widget, 'anim', 1);
|
||||||
}, 10);
|
}, 10);
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
@@ -794,6 +814,9 @@ var pbar = (function () {
|
|||||||
grad;
|
grad;
|
||||||
|
|
||||||
r.onresize = function () {
|
r.onresize = function () {
|
||||||
|
if (!widget.is_open && r.buf)
|
||||||
|
return;
|
||||||
|
|
||||||
r.buf = canvas_cfg(ebi('barbuf'));
|
r.buf = canvas_cfg(ebi('barbuf'));
|
||||||
r.pos = canvas_cfg(ebi('barpos'));
|
r.pos = canvas_cfg(ebi('barpos'));
|
||||||
r.drawbuf();
|
r.drawbuf();
|
||||||
@@ -895,6 +918,9 @@ var vbar = (function () {
|
|||||||
can, ctx, w, h, grad1, grad2;
|
can, ctx, w, h, grad1, grad2;
|
||||||
|
|
||||||
r.onresize = function () {
|
r.onresize = function () {
|
||||||
|
if (!widget.is_open && r.can)
|
||||||
|
return;
|
||||||
|
|
||||||
r.can = canvas_cfg(ebi('pvol'));
|
r.can = canvas_cfg(ebi('pvol'));
|
||||||
can = r.can.can;
|
can = r.can.can;
|
||||||
ctx = r.can.ctx;
|
ctx = r.can.ctx;
|
||||||
@@ -1567,25 +1593,62 @@ function autoplay_blocked(seek) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function scan_hash(v) {
|
||||||
|
if (!v)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var m = /^#([ag])(f-[0-9a-f]{8,16})(&.+)?/.exec(v + '');
|
||||||
|
if (!m)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var mtype = m[1],
|
||||||
|
id = m[2],
|
||||||
|
ts = null;
|
||||||
|
|
||||||
|
if (m.length > 3) {
|
||||||
|
m = /^&[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(m[3]);
|
||||||
|
if (m) {
|
||||||
|
ts = parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [mtype, id, ts];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function eval_hash() {
|
function eval_hash() {
|
||||||
var v = hash0;
|
var v = hash0;
|
||||||
hash0 = null;
|
hash0 = null;
|
||||||
if (!v)
|
if (!v)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (v.indexOf('#af-') === 0) {
|
var media = scan_hash(v);
|
||||||
var id = v.slice(2).split('&');
|
if (media) {
|
||||||
if (id[0].length < 10)
|
var mtype = media[0],
|
||||||
return;
|
id = media[1],
|
||||||
|
ts = media[2];
|
||||||
|
|
||||||
if (id.length == 1)
|
if (mtype == 'a') {
|
||||||
return play(id[0]);
|
if (!ts)
|
||||||
|
return play(id);
|
||||||
|
|
||||||
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]);
|
return play(id, false, ts);
|
||||||
if (!m)
|
}
|
||||||
return play(id[0]);
|
|
||||||
|
|
||||||
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
if (mtype == 'g') {
|
||||||
|
if (!thegrid.en)
|
||||||
|
ebi('griden').click();
|
||||||
|
|
||||||
|
var t = setInterval(function () {
|
||||||
|
if (!thegrid.bbox)
|
||||||
|
return;
|
||||||
|
|
||||||
|
clearInterval(t);
|
||||||
|
var im = QS('#ggrid a[ref="' + id + '"]');
|
||||||
|
im.click();
|
||||||
|
im.scrollIntoView();
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.indexOf('#q=') === 0) {
|
if (v.indexOf('#q=') === 0) {
|
||||||
@@ -2314,10 +2377,12 @@ var showfile = (function () {
|
|||||||
'.bas': 'basic',
|
'.bas': 'basic',
|
||||||
'.bat': 'batch',
|
'.bat': 'batch',
|
||||||
'.cxx': 'cpp',
|
'.cxx': 'cpp',
|
||||||
|
'.diz': 'ans',
|
||||||
'.h': 'c',
|
'.h': 'c',
|
||||||
'.hpp': 'cpp',
|
'.hpp': 'cpp',
|
||||||
'.htm': 'html',
|
'.htm': 'html',
|
||||||
'.hxx': 'cpp',
|
'.hxx': 'cpp',
|
||||||
|
'.log': 'ans',
|
||||||
'.patch': 'diff',
|
'.patch': 'diff',
|
||||||
'.ps1': 'powershell',
|
'.ps1': 'powershell',
|
||||||
'.psm1': 'powershell',
|
'.psm1': 'powershell',
|
||||||
@@ -2334,7 +2399,7 @@ var showfile = (function () {
|
|||||||
'cmakelists.txt': 'cmake',
|
'cmakelists.txt': 'cmake',
|
||||||
'dockerfile': 'docker'
|
'dockerfile': 'docker'
|
||||||
};
|
};
|
||||||
var x = txt_ext + ' c cfg conf cpp cs css diff go html ini java js json jsx kt kts less latex lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml';
|
var x = txt_ext + ' ans c cfg conf cpp cs css diff go html ini java js json jsx kt kts latex less lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml';
|
||||||
x = x.split(/ +/g);
|
x = x.split(/ +/g);
|
||||||
for (var a = 0; a < x.length; a++)
|
for (var a = 0; a < x.length; a++)
|
||||||
r.map["." + x[a]] = x[a];
|
r.map["." + x[a]] = x[a];
|
||||||
@@ -2389,6 +2454,7 @@ var showfile = (function () {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
td.innerHTML = '<a href="#" class="doc bri" hl="' + link.id + '">-txt-</a>';
|
td.innerHTML = '<a href="#" class="doc bri" hl="' + link.id + '">-txt-</a>';
|
||||||
|
td.getElementsByTagName('a')[0].setAttribute('href', '?doc=' + fn);
|
||||||
}
|
}
|
||||||
r.mktree();
|
r.mktree();
|
||||||
if (em) {
|
if (em) {
|
||||||
@@ -2425,6 +2491,7 @@ var showfile = (function () {
|
|||||||
lnh = doc[1],
|
lnh = doc[1],
|
||||||
txt = doc[2],
|
txt = doc[2],
|
||||||
name = url.split('/').pop(),
|
name = url.split('/').pop(),
|
||||||
|
tname = uricom_dec(name)[0],
|
||||||
lang = r.getlang(name),
|
lang = r.getlang(name),
|
||||||
is_md = lang == 'md';
|
is_md = lang == 'md';
|
||||||
|
|
||||||
@@ -2439,7 +2506,10 @@ var showfile = (function () {
|
|||||||
if (lnh.slice(0, 5) == '#doc.')
|
if (lnh.slice(0, 5) == '#doc.')
|
||||||
sethash(lnh.slice(1));
|
sethash(lnh.slice(1));
|
||||||
|
|
||||||
Prism.highlightElement(el || QS('#doc>code'));
|
el = el || QS('#doc>code');
|
||||||
|
Prism.highlightElement(el);
|
||||||
|
if (el.getAttribute('class') == 'language-ans')
|
||||||
|
r.ansify(el);
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
}
|
}
|
||||||
@@ -2471,13 +2541,14 @@ var showfile = (function () {
|
|||||||
wr.style.display = '';
|
wr.style.display = '';
|
||||||
set_tabindex();
|
set_tabindex();
|
||||||
|
|
||||||
|
wintitle(tname + ' \u2014 ');
|
||||||
document.documentElement.scrollTop = 0;
|
document.documentElement.scrollTop = 0;
|
||||||
var hfun = no_push ? hist_replace : hist_push;
|
var hfun = no_push ? hist_replace : hist_push;
|
||||||
hfun(get_evpath() + '?doc=' + url.split('/').pop());
|
hfun(get_evpath() + '?doc=' + url.split('/').pop());
|
||||||
|
|
||||||
qsr('#docname');
|
qsr('#docname');
|
||||||
el = mknod('span');
|
el = mknod('span');
|
||||||
el.textContent = uricom_dec(name)[0];
|
el.textContent = tname;
|
||||||
el.setAttribute('id', 'docname');
|
el.setAttribute('id', 'docname');
|
||||||
ebi('path').appendChild(el);
|
ebi('path').appendChild(el);
|
||||||
|
|
||||||
@@ -2486,6 +2557,65 @@ var showfile = (function () {
|
|||||||
tree_scrollto();
|
tree_scrollto();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.ansify = function (el) {
|
||||||
|
var ctab = (light ?
|
||||||
|
'bfbfbf d30253 497600 b96900 006fbb a50097 288276 2d2d2d 9f9f9f 943b55 3a5600 7f4f00 00507d 683794 004343 000000' :
|
||||||
|
'404040 f03669 b8e346 ffa402 02a2ff f65be3 3da698 d2d2d2 606060 c75b79 c8e37e ffbe4a 71cbff b67fe3 9cf0ed ffffff').split(/ /g),
|
||||||
|
src = el.innerHTML.split(/\x1b\[/g),
|
||||||
|
out = ['<span>'], fg = 7, bg = null, bfg = 0, bbg = 0, inv = 0, bold = 0;
|
||||||
|
|
||||||
|
for (var a = 0; a < src.length; a++) {
|
||||||
|
var m = /^([0-9;]+)m/.exec(src[a]);
|
||||||
|
if (!m) {
|
||||||
|
if (a || src[a])
|
||||||
|
out.push('\x1b[' + src[a]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs = m[1].split(/;/g),
|
||||||
|
txt = src[a].slice(m[1].length + 1);
|
||||||
|
|
||||||
|
for (var b = 0; b < cs.length; b++) {
|
||||||
|
var c = parseInt(cs[b]);
|
||||||
|
if (c == 0) {
|
||||||
|
fg = 7;
|
||||||
|
bg = null;
|
||||||
|
bfg = bbg = bold = inv = 0;
|
||||||
|
}
|
||||||
|
if (c == 1) bfg = bold = 1;
|
||||||
|
if (c == 7) inv = 1;
|
||||||
|
if (c == 22) bfg = bold = 0;
|
||||||
|
if (c == 27) inv = 0;
|
||||||
|
if (c >= 30 && c <= 37) fg = c - 30;
|
||||||
|
if (c >= 40 && c <= 47) bg = c - 40;
|
||||||
|
if (c >= 90 && c <= 97) {
|
||||||
|
fg = c - 90;
|
||||||
|
bfg = 1;
|
||||||
|
}
|
||||||
|
if (c >= 100 && c <= 107) {
|
||||||
|
bg = c - 100;
|
||||||
|
bbg = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg = fg, cbg = bg;
|
||||||
|
if (inv) {
|
||||||
|
cbg = fg;
|
||||||
|
cfg = bg || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = '</span><span style="color:#' + ctab[cfg + bfg * 8];
|
||||||
|
if (cbg !== null)
|
||||||
|
s += ';background:#' + ctab[cbg + bbg * 8];
|
||||||
|
if (bold)
|
||||||
|
s += ';font-weight:bold';
|
||||||
|
|
||||||
|
out.push(s + '">' + txt);
|
||||||
|
}
|
||||||
|
el.innerHTML = out.join('');
|
||||||
|
};
|
||||||
|
|
||||||
r.mktree = function () {
|
r.mktree = function () {
|
||||||
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
|
var html = ['<li class="bn">list of textfiles in<br />' + linksplit(get_vpath()).join('') + '</li>'];
|
||||||
for (var a = 0; a < r.files.length; a++) {
|
for (var a = 0; a < r.files.length; a++) {
|
||||||
@@ -2612,6 +2742,7 @@ var thegrid = (function () {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
hist_push(get_evpath());
|
hist_push(get_evpath());
|
||||||
|
wintitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
var vis = has(perms, "read");
|
var vis = has(perms, "read");
|
||||||
@@ -2856,6 +2987,9 @@ var thegrid = (function () {
|
|||||||
return '<a download href="' + h +
|
return '<a download href="' + h +
|
||||||
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
|
'">' + (idx + 1) + ' / ' + r.bbox.length + ' -- ' +
|
||||||
esc(uricom_dec(h.split('/').pop())[0]) + '</a>';
|
esc(uricom_dec(h.split('/').pop())[0]) + '</a>';
|
||||||
|
},
|
||||||
|
onChange: function (i) {
|
||||||
|
sethash('g' + r.bbox[i].imageElement.getAttribute('ref'));
|
||||||
}
|
}
|
||||||
})[0];
|
})[0];
|
||||||
};
|
};
|
||||||
@@ -2869,10 +3003,6 @@ var thegrid = (function () {
|
|||||||
});
|
});
|
||||||
ebi('wtgrid').onclick = ebi('griden').onclick;
|
ebi('wtgrid').onclick = ebi('griden').onclick;
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
import_js('/.cpr/baguettebox.js', r.bagit);
|
|
||||||
}, 1);
|
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -3025,7 +3155,7 @@ document.onkeydown = function (e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ae.closest('pre')) {
|
if (ae && ae.closest('pre')) {
|
||||||
if (k == 'KeyA' && ctrl(e)) {
|
if (k == 'KeyA' && ctrl(e)) {
|
||||||
var sel = document.getSelection(),
|
var sel = document.getSelection(),
|
||||||
ran = document.createRange();
|
ran = document.createRange();
|
||||||
@@ -3219,7 +3349,7 @@ document.onkeydown = function (e) {
|
|||||||
clearTimeout(defer_timeout);
|
clearTimeout(defer_timeout);
|
||||||
clearTimeout(search_timeout);
|
clearTimeout(search_timeout);
|
||||||
search_timeout = setTimeout(do_search,
|
search_timeout = setTimeout(do_search,
|
||||||
v && v.length < (is_touch ? 4 : 3) ? 600 : 200);
|
v && v.length < (is_touch ? 4 : 3) ? 1000 : 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3289,10 +3419,10 @@ document.onkeydown = function (e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (k == 'path' || k == 'name' || k == 'tags') {
|
if (k == 'path' || k == 'name' || k == 'tags') {
|
||||||
var not = ' ';
|
var not = '';
|
||||||
if (tv.slice(0, 1) == '-') {
|
if (tv.slice(0, 1) == '-') {
|
||||||
tv = tv.slice(1);
|
tv = tv.slice(1);
|
||||||
not = ' not ';
|
not = 'not ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tv.slice(0, 1) == '^') {
|
if (tv.slice(0, 1) == '^') {
|
||||||
@@ -3313,7 +3443,7 @@ document.onkeydown = function (e) {
|
|||||||
tv = '"' + tv + '"';
|
tv = '"' + tv + '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
q += k + not + 'like ' + tv;
|
q += not + k + ' like ' + tv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3361,6 +3491,7 @@ document.onkeydown = function (e) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
treectl.hide();
|
treectl.hide();
|
||||||
|
thegrid.setvis(true);
|
||||||
|
|
||||||
var html = mk_files_header(tagord), seen = {};
|
var html = mk_files_header(tagord), seen = {};
|
||||||
html.push('<tbody>');
|
html.push('<tbody>');
|
||||||
@@ -3607,7 +3738,7 @@ var treectl = (function () {
|
|||||||
treeh = winh - atop;
|
treeh = winh - atop;
|
||||||
|
|
||||||
tree.style.top = top + 'px';
|
tree.style.top = top + 'px';
|
||||||
tree.style.height = treeh < 10 ? '' : Math.floor(treeh - 2) + 'px';
|
tree.style.height = treeh < 10 ? '' : Math.floor(treeh) + 'px';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.add(onscroll2, true);
|
timer.add(onscroll2, true);
|
||||||
@@ -3875,7 +4006,8 @@ var treectl = (function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>';
|
srvinf = res.srvinf;
|
||||||
|
ebi('srv_info').innerHTML = ebi('srv_info2').innerHTML = '<span>' + res.srvinf + '</span>';
|
||||||
|
|
||||||
if (this.hpush && !showfile.active())
|
if (this.hpush && !showfile.active())
|
||||||
hist_push(this.top);
|
hist_push(this.top);
|
||||||
@@ -3925,7 +4057,7 @@ var treectl = (function () {
|
|||||||
showfile.files.push({ 'id': id, 'name': fname });
|
showfile.files.push({ 'id': id, 'name': fname });
|
||||||
|
|
||||||
if (tn.lead == '-')
|
if (tn.lead == '-')
|
||||||
tn.lead = '<a href="#" class="doc' + (lang ? ' bri' : '') +
|
tn.lead = '<a href="?doc=' + tn.href + '" class="doc' + (lang ? ' bri' : '') +
|
||||||
'" hl="' + id + '" name="' + hname + '">-txt-</a>';
|
'" hl="' + id + '" name="' + hname + '">-txt-</a>';
|
||||||
|
|
||||||
var ln = ['<tr><td>' + tn.lead + '</td><td sortv="' + sortv +
|
var ln = ['<tr><td>' + tn.lead + '</td><td sortv="' + sortv +
|
||||||
@@ -3949,21 +4081,42 @@ var treectl = (function () {
|
|||||||
html = html.join('\n');
|
html = html.join('\n');
|
||||||
set_files_html(html);
|
set_files_html(html);
|
||||||
|
|
||||||
filecols.set_style();
|
function asdf() {
|
||||||
showfile.mktree();
|
filecols.set_style();
|
||||||
mukey.render();
|
showfile.mktree();
|
||||||
reload_tree();
|
mukey.render();
|
||||||
reload_browser();
|
reload_tree();
|
||||||
tree_scrollto();
|
reload_browser();
|
||||||
if (res.acct) {
|
tree_scrollto();
|
||||||
acct = res.acct;
|
if (res.acct) {
|
||||||
have_up2k_idx = res.idx;
|
acct = res.acct;
|
||||||
apply_perms(res.perms);
|
have_up2k_idx = res.idx;
|
||||||
fileman.render();
|
apply_perms(res.perms);
|
||||||
|
fileman.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var m = scan_hash(hash0),
|
||||||
|
url = null;
|
||||||
|
|
||||||
|
if (m) {
|
||||||
|
url = ebi(m[1]);
|
||||||
|
if (url) {
|
||||||
|
url = url.href;
|
||||||
|
var mt = m[0] == 'a' ? 'audio' : /\.(webm|mkv)($|\?)/i.exec(url) ? 'video' : 'image'
|
||||||
|
if (mt == 'image') {
|
||||||
|
url += url.indexOf('?') < 0 ? '?cache' : '&cache';
|
||||||
|
console.log(url);
|
||||||
|
new Image().src = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url) setTimeout(asdf, 1); else asdf();
|
||||||
}
|
}
|
||||||
|
|
||||||
r.hydrate = function () {
|
r.hydrate = function () {
|
||||||
|
qsr('#bbsw');
|
||||||
if (ls0 === null) {
|
if (ls0 === null) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', '/?am_js', true);
|
xhr.open('GET', '/?am_js', true);
|
||||||
@@ -3973,12 +4126,9 @@ var treectl = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.gentab(get_evpath(), ls0);
|
r.gentab(get_evpath(), ls0);
|
||||||
reload_browser();
|
|
||||||
pbar.onresize();
|
pbar.onresize();
|
||||||
vbar.onresize();
|
vbar.onresize();
|
||||||
mukey.render();
|
|
||||||
showfile.addlinks();
|
showfile.addlinks();
|
||||||
thegrid.setdirty();
|
|
||||||
setTimeout(eval_hash, 1);
|
setTimeout(eval_hash, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4077,14 +4227,16 @@ function apply_perms(newperms) {
|
|||||||
perms = newperms || [];
|
perms = newperms || [];
|
||||||
|
|
||||||
var a = QS('#ops a[data-dest="up2k"]');
|
var a = QS('#ops a[data-dest="up2k"]');
|
||||||
|
var suf = 'multithreaded, and file timestamps are preserved, but it uses more CPU than the basic uploader';
|
||||||
if (have_up2k_idx) {
|
if (have_up2k_idx) {
|
||||||
a.removeAttribute('data-perm');
|
a.removeAttribute('data-perm');
|
||||||
a.setAttribute('tt', 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server');
|
a.setAttribute('tt', 'up2k: upload files (if you have write-access) or toggle into the search-mode to see if they exist somewhere on the server$N$Nuploads are resumable, ' + suf);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
a.setAttribute('data-perm', 'write');
|
a.setAttribute('data-perm', 'write');
|
||||||
a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)');
|
a.setAttribute('tt', 'up2k: upload files with resume support (close your browser and drop the same files in later)$N$N' + suf);
|
||||||
}
|
}
|
||||||
|
a.style.display = '';
|
||||||
tt.att(QS('#ops'));
|
tt.att(QS('#ops'));
|
||||||
|
|
||||||
var axs = [],
|
var axs = [],
|
||||||
@@ -4101,10 +4253,11 @@ function apply_perms(newperms) {
|
|||||||
axs += '-Only';
|
axs += '-Only';
|
||||||
}
|
}
|
||||||
|
|
||||||
ebi('acc_info').innerHTML = '<span' + aclass + axs + ' access</span>' + (acct != '*' ?
|
ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
|
||||||
'<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>');
|
'</span></span><span' + aclass + axs + ' access</span>' + (acct != '*' ?
|
||||||
|
'<a href="/?pw=x">Logout ' + acct + '</a>' : '<a href="/?h">Login</a>');
|
||||||
|
|
||||||
var o = QSA('#ops>a[data-perm], #u2footfoot');
|
var o = QSA('#ops>a[data-perm]');
|
||||||
for (var a = 0; a < o.length; a++) {
|
for (var a = 0; a < o.length; a++) {
|
||||||
var display = '';
|
var display = '';
|
||||||
var needed = o[a].getAttribute('data-perm').split(' ');
|
var needed = o[a].getAttribute('data-perm').split(' ');
|
||||||
@@ -4459,20 +4612,51 @@ var mukey = (function () {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
var light;
|
var light, theme;
|
||||||
(function () {
|
var settheme = (function () {
|
||||||
|
var ax = 'abcdefghijklmnopqrstuvwx';
|
||||||
|
|
||||||
|
theme = sread('theme') || 'a';
|
||||||
|
if (!/^[a-x][yz]/.exec(theme))
|
||||||
|
theme = dtheme;
|
||||||
|
|
||||||
|
light = !!(theme.indexOf('y') + 1);
|
||||||
|
|
||||||
function freshen() {
|
function freshen() {
|
||||||
clmod(document.documentElement, "light", light);
|
var cl = document.documentElement.getAttribute('class');
|
||||||
clmod(document.documentElement, "dark", !light);
|
cl = cl.replace(/\b(light|dark|[a-z]{1,2})\b/g, '').replace(/ +/g, ' ');
|
||||||
|
document.documentElement.setAttribute('class', 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);
|
||||||
|
for (var a = 0; a < themes; a++)
|
||||||
|
html.push('<a href="#" class="btn tgl' + (a == itheme ? ' on' : '') + '">' + 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
bcfg_bind(window, 'light', 'lightmode', false, freshen);
|
function settheme(e) {
|
||||||
|
var i = e;
|
||||||
|
try { ev(e); i = e.target.textContent; } catch (ex) { }
|
||||||
|
light = i % 2 == 1;
|
||||||
|
var c = ax.charAt(Math.floor(i / 2)),
|
||||||
|
l = light ? 'y' : 'z';
|
||||||
|
theme = c + l + ' ' + c + ' ' + l;
|
||||||
|
swrite('theme', theme);
|
||||||
|
freshen();
|
||||||
|
}
|
||||||
|
|
||||||
freshen();
|
freshen();
|
||||||
|
return settheme;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
@@ -5029,6 +5213,9 @@ ebi('path').onclick = function (e) {
|
|||||||
|
|
||||||
|
|
||||||
ebi('files').onclick = ebi('docul').onclick = function (e) {
|
ebi('files').onclick = ebi('docul').onclick = function (e) {
|
||||||
|
if (ctrl(e))
|
||||||
|
return true;
|
||||||
|
|
||||||
var tgt = e.target.closest('a[id]');
|
var tgt = e.target.closest('a[id]');
|
||||||
if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) {
|
if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) {
|
||||||
var el = treectl.find(tgt.textContent.slice(0, -1));
|
var el = treectl.find(tgt.textContent.slice(0, -1));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
{{ html_head }}
|
||||||
<style>
|
<style>
|
||||||
html{font-family:sans-serif}
|
html{font-family:sans-serif}
|
||||||
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
|
td{border:1px solid #999;border-width:1px 1px 0 0;padding:0 5px}
|
||||||
|
|||||||
@@ -219,48 +219,48 @@ blink {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.dark,
|
html.z,
|
||||||
html.dark body {
|
html.z body {
|
||||||
background: #222;
|
background: #222;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
html.dark #toc a {
|
html.z #toc a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
border-left: .4em solid #444;
|
border-left: .4em solid #444;
|
||||||
border-bottom: .1em solid #333;
|
border-bottom: .1em solid #333;
|
||||||
}
|
}
|
||||||
html.dark #toc a.act {
|
html.z #toc a.act {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-left: .4em solid #3ad;
|
border-left: .4em solid #3ad;
|
||||||
}
|
}
|
||||||
html.dark #toc li {
|
html.z #toc li {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
html.dark #mn a:not(:last-child)::after {
|
html.z #mn a:not(:last-child)::after {
|
||||||
border-color: rgba(255,255,255,0.3);
|
border-color: rgba(255,255,255,0.3);
|
||||||
}
|
}
|
||||||
html.dark #mn a {
|
html.z #mn a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
html.dark #mn {
|
html.z #mn {
|
||||||
border-bottom: 1px solid #333;
|
border-bottom: 1px solid #333;
|
||||||
}
|
}
|
||||||
html.dark #mn,
|
html.z #mn,
|
||||||
html.dark #mh {
|
html.z #mh {
|
||||||
background: #222;
|
background: #222;
|
||||||
}
|
}
|
||||||
html.dark #mh a {
|
html.z #mh a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
html.dark #mh a:hover {
|
html.z #mh a:hover {
|
||||||
background: #333;
|
background: #333;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
html.dark #toolsbox {
|
html.z #toolsbox {
|
||||||
background: #222;
|
background: #222;
|
||||||
}
|
}
|
||||||
html.dark #toolsbox.open {
|
html.z #toolsbox.open {
|
||||||
box-shadow: 0 .2em .2em #069;
|
box-shadow: 0 .2em .2em #069;
|
||||||
border-radius: 0 0 .4em .4em;
|
border-radius: 0 0 .4em .4em;
|
||||||
}
|
}
|
||||||
@@ -308,23 +308,23 @@ blink {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.dark #toc {
|
html.z #toc {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
border-top: 1px solid #2c2c2c;
|
border-top: 1px solid #2c2c2c;
|
||||||
box-shadow: 0 0 1em #181818;
|
box-shadow: 0 0 1em #181818;
|
||||||
}
|
}
|
||||||
html.dark #toc,
|
html.z #toc,
|
||||||
html.dark #mw {
|
html.z #mw {
|
||||||
scrollbar-color: #b80 #282828;
|
scrollbar-color: #b80 #282828;
|
||||||
}
|
}
|
||||||
html.dark #toc::-webkit-scrollbar-track {
|
html.z #toc::-webkit-scrollbar-track {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
}
|
}
|
||||||
html.dark #toc::-webkit-scrollbar {
|
html.z #toc::-webkit-scrollbar {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
width: .8em;
|
width: .8em;
|
||||||
}
|
}
|
||||||
html.dark #toc::-webkit-scrollbar-thumb {
|
html.z #toc::-webkit-scrollbar-thumb {
|
||||||
background: #b80;
|
background: #b80;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,16 +432,16 @@ blink {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.dark .mdo a {
|
html.z .mdo a {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
html.dark .mdo pre,
|
html.z .mdo pre,
|
||||||
html.dark .mdo code {
|
html.z .mdo code {
|
||||||
color: #240;
|
color: #240;
|
||||||
}
|
}
|
||||||
html.dark .mdo p>em,
|
html.z .mdo p>em,
|
||||||
html.dark .mdo li>em,
|
html.z .mdo li>em,
|
||||||
html.dark .mdo td>em {
|
html.z .mdo td>em {
|
||||||
color: #940;
|
color: #940;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<title>📝🎉 {{ title }}</title>
|
<title>📝🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
|
{{ html_head }}
|
||||||
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}">
|
<link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}">
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
@@ -135,13 +136,13 @@ var md_opt = {
|
|||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var l = localStorage,
|
var l = localStorage,
|
||||||
drk = l.lightmode != 1,
|
drk = l.light != 1,
|
||||||
btn = document.getElementById("lightswitch"),
|
btn = document.getElementById("lightswitch"),
|
||||||
f = function (e) {
|
f = function (e) {
|
||||||
if (e) { e.preventDefault(); drk = !drk; }
|
if (e) { e.preventDefault(); drk = !drk; }
|
||||||
document.documentElement.setAttribute("class", drk? "dark":"light");
|
document.documentElement.setAttribute("class", drk? "z":"y");
|
||||||
btn.innerHTML = "go " + (drk ? "light":"dark");
|
btn.innerHTML = "go " + (drk ? "light":"dark");
|
||||||
l.lightmode = drk? 0:1;
|
l.light = drk? 0:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
btn.onclick = f;
|
btn.onclick = f;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
scrollbar-color: #eb0 #f7f7f7;
|
scrollbar-color: #eb0 #f7f7f7;
|
||||||
}
|
}
|
||||||
html.dark #mt {
|
html.z #mt {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
background: #222;
|
background: #222;
|
||||||
border: 1px solid #777;
|
border: 1px solid #777;
|
||||||
@@ -77,7 +77,7 @@ html.dark #mt {
|
|||||||
background: #f97;
|
background: #f97;
|
||||||
border-radius: .15em;
|
border-radius: .15em;
|
||||||
}
|
}
|
||||||
html.dark #save.force-save {
|
html.z #save.force-save {
|
||||||
color: #fca;
|
color: #fca;
|
||||||
background: #720;
|
background: #720;
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ html.dark #save.force-save {
|
|||||||
#helpclose {
|
#helpclose {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
html.dark #helpbox {
|
html.z #helpbox {
|
||||||
box-shadow: 0 .5em 2em #444;
|
box-shadow: 0 .5em 2em #444;
|
||||||
background: #222;
|
background: #222;
|
||||||
border: 1px solid #079;
|
border: 1px solid #079;
|
||||||
|
|||||||
@@ -84,24 +84,24 @@ html .editor-toolbar>button.save.force-save {
|
|||||||
|
|
||||||
|
|
||||||
/* darkmode */
|
/* darkmode */
|
||||||
html.dark .mdo,
|
html.z .mdo,
|
||||||
html.dark .CodeMirror {
|
html.z .CodeMirror {
|
||||||
border-color: #222;
|
border-color: #222;
|
||||||
}
|
}
|
||||||
html.dark,
|
html.z,
|
||||||
html.dark body,
|
html.z body,
|
||||||
html.dark .CodeMirror {
|
html.z .CodeMirror {
|
||||||
background: #222;
|
background: #222;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
html.dark .CodeMirror-cursor {
|
html.z .CodeMirror-cursor {
|
||||||
border-color: #fff;
|
border-color: #fff;
|
||||||
}
|
}
|
||||||
html.dark .CodeMirror-selected {
|
html.z .CodeMirror-selected {
|
||||||
box-shadow: 0 0 1px #0cf inset;
|
box-shadow: 0 0 1px #0cf inset;
|
||||||
}
|
}
|
||||||
html.dark .CodeMirror-selected,
|
html.z .CodeMirror-selected,
|
||||||
html.dark .CodeMirror-selectedtext {
|
html.z .CodeMirror-selectedtext {
|
||||||
border-radius: .1em;
|
border-radius: .1em;
|
||||||
background: #246;
|
background: #246;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -109,37 +109,37 @@ html.dark .CodeMirror-selectedtext {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.dark #mn a {
|
html.z #mn a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
html.dark #mn a:not(:last-child):after {
|
html.z #mn a:not(:last-child):after {
|
||||||
border-color: rgba(255,255,255,0.3);
|
border-color: rgba(255,255,255,0.3);
|
||||||
}
|
}
|
||||||
html.dark .editor-toolbar {
|
html.z .editor-toolbar {
|
||||||
border-color: #2c2c2c;
|
border-color: #2c2c2c;
|
||||||
background: #1c1c1c;
|
background: #1c1c1c;
|
||||||
}
|
}
|
||||||
html.dark .editor-toolbar>i.separator {
|
html.z .editor-toolbar>i.separator {
|
||||||
border-left: 1px solid #444;
|
border-left: 1px solid #444;
|
||||||
border-right: 1px solid #111;
|
border-right: 1px solid #111;
|
||||||
}
|
}
|
||||||
html.dark .editor-toolbar>button {
|
html.z .editor-toolbar>button {
|
||||||
margin-left: -1px; border: 1px solid rgba(255,255,255,0.1);
|
margin-left: -1px; border: 1px solid rgba(255,255,255,0.1);
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.dark .editor-toolbar>button:hover {
|
html.z .editor-toolbar>button:hover {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
html.dark .editor-toolbar>button.active {
|
html.z .editor-toolbar>button.active {
|
||||||
color: #333;
|
color: #333;
|
||||||
border-color: #ec1;
|
border-color: #ec1;
|
||||||
background: #c90;
|
background: #c90;
|
||||||
}
|
}
|
||||||
html.dark .editor-toolbar::after,
|
html.z .editor-toolbar::after,
|
||||||
html.dark .editor-toolbar::before {
|
html.z .editor-toolbar::before {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +150,6 @@ html.dark .editor-toolbar::before {
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
}
|
}
|
||||||
html.dark .mdo {
|
html.z .mdo {
|
||||||
background: #1c1c1c;
|
background: #1c1c1c;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<title>📝🎉 {{ title }}</title>
|
<title>📝🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
|
{{ html_head }}
|
||||||
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}">
|
<link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}">
|
<link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||||
@@ -33,11 +34,11 @@ var md_opt = {
|
|||||||
|
|
||||||
var lightswitch = (function () {
|
var lightswitch = (function () {
|
||||||
var l = localStorage,
|
var l = localStorage,
|
||||||
drk = l.lightmode != 1,
|
drk = l.light != 1,
|
||||||
f = function (e) {
|
f = function (e) {
|
||||||
if (e) drk = !drk;
|
if (e) drk = !drk;
|
||||||
document.documentElement.setAttribute("class", drk? "dark":"light");
|
document.documentElement.setAttribute("class", drk? "z":"y");
|
||||||
l.lightmode = drk? 0:1;
|
l.light = drk? 0:1;
|
||||||
};
|
};
|
||||||
f();
|
f();
|
||||||
return f;
|
return f;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<title>{{ svcname }}</title>
|
<title>{{ svcname }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -88,27 +88,27 @@ blockquote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html.dark,
|
html.z,
|
||||||
html.dark body,
|
html.z body,
|
||||||
html.dark #wrap {
|
html.z #wrap {
|
||||||
background: #222;
|
background: #222;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
html.dark h1 {
|
html.z h1 {
|
||||||
border-color: #777;
|
border-color: #777;
|
||||||
}
|
}
|
||||||
html.dark a {
|
html.z a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #057;
|
background: #057;
|
||||||
border-color: #37a;
|
border-color: #37a;
|
||||||
}
|
}
|
||||||
html.dark .logout,
|
html.z .logout,
|
||||||
html.dark .btns a,
|
html.z .btns a,
|
||||||
html.dark a.r {
|
html.z a.r {
|
||||||
background: #804;
|
background: #804;
|
||||||
border-color: #c28;
|
border-color: #c28;
|
||||||
}
|
}
|
||||||
html.dark input {
|
html.z input {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #626;
|
background: #626;
|
||||||
border: 1px solid #c2c;
|
border: 1px solid #c2c;
|
||||||
@@ -117,6 +117,6 @@ html.dark input {
|
|||||||
padding: .5em .7em;
|
padding: .5em .7em;
|
||||||
margin: 0 .5em 0 0;
|
margin: 0 .5em 0 0;
|
||||||
}
|
}
|
||||||
html.dark .num {
|
html.z .num {
|
||||||
border-color: #777;
|
border-color: #777;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<title>{{ svcname }}</title>
|
<title>{{ svcname }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
@@ -96,7 +97,7 @@
|
|||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");
|
document.documentElement.setAttribute("class", localStorage.light == 1 ? "y" : "z");
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
|
|||||||
@@ -157,23 +157,23 @@ html {
|
|||||||
#tt em {
|
#tt em {
|
||||||
color: #f6a;
|
color: #f6a;
|
||||||
}
|
}
|
||||||
html.light #tt {
|
html.y #tt {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-color: #888 #000 #777 #000;
|
border-color: #888 #000 #777 #000;
|
||||||
}
|
}
|
||||||
html.light #tt,
|
html.y #tt,
|
||||||
html.light #toast {
|
html.y #toast {
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
#modalc code,
|
#modalc code,
|
||||||
html.light #tt code {
|
html.y #tt code {
|
||||||
background: #060;
|
background: #060;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
html.light #tt em {
|
html.y #tt em {
|
||||||
color: #d38;
|
color: #d38;
|
||||||
}
|
}
|
||||||
html.light #tth {
|
html.y #tth {
|
||||||
color: #000;
|
color: #000;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
@@ -273,9 +273,9 @@ html.light #tth {
|
|||||||
box-shadow: 0 .1em .2em #fc0 inset;
|
box-shadow: 0 .1em .2em #fc0 inset;
|
||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
}
|
}
|
||||||
html.light *:focus,
|
html.y *:focus,
|
||||||
html.light #pctl *:focus,
|
html.y #pctl *:focus,
|
||||||
html.light .btn:focus {
|
html.y .btn:focus {
|
||||||
box-shadow: 0 .1em .2em #037 inset;
|
box-shadow: 0 .1em .2em #037 inset;
|
||||||
}
|
}
|
||||||
input[type="text"]:focus,
|
input[type="text"]:focus,
|
||||||
@@ -283,9 +283,9 @@ input:not([type]):focus,
|
|||||||
textarea:focus {
|
textarea:focus {
|
||||||
box-shadow: 0 .1em .3em #fc0, 0 -.1em .3em #fc0;
|
box-shadow: 0 .1em .3em #fc0, 0 -.1em .3em #fc0;
|
||||||
}
|
}
|
||||||
html.light input[type="text"]:focus,
|
html.y input[type="text"]:focus,
|
||||||
html.light input:not([type]):focus,
|
html.y input:not([type]):focus,
|
||||||
html.light textarea:focus {
|
html.y textarea:focus {
|
||||||
box-shadow: 0 .1em .3em #037, 0 -.1em .3em #037;
|
box-shadow: 0 .1em .3em #037, 0 -.1em .3em #037;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ html.light textarea:focus {
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-wrap: break-word; /*ie*/
|
word-wrap: break-word; /*ie*/
|
||||||
}
|
}
|
||||||
html.light .mdo a,
|
html.y .mdo a,
|
||||||
.mdo a {
|
.mdo a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #39b;
|
background: #39b;
|
||||||
@@ -443,48 +443,48 @@ html.light textarea:focus {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
html.dark .mdo a {
|
html.z .mdo a {
|
||||||
background: #057;
|
background: #057;
|
||||||
}
|
}
|
||||||
html.dark .mdo h1 a, html.dark .mdo h4 a,
|
html.z .mdo h1 a, html.z .mdo h4 a,
|
||||||
html.dark .mdo h2 a, html.dark .mdo h5 a,
|
html.z .mdo h2 a, html.z .mdo h5 a,
|
||||||
html.dark .mdo h3 a, html.dark .mdo h6 a {
|
html.z .mdo h3 a, html.z .mdo h6 a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
html.dark .mdo pre,
|
html.z .mdo pre,
|
||||||
html.dark .mdo code {
|
html.z .mdo code {
|
||||||
color: #8c0;
|
color: #8c0;
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
border: .07em solid #333;
|
border: .07em solid #333;
|
||||||
}
|
}
|
||||||
html.dark .mdo ul,
|
html.z .mdo ul,
|
||||||
html.dark .mdo ol {
|
html.z .mdo ol {
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
}
|
}
|
||||||
html.dark .mdo strong {
|
html.z .mdo strong {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
html.dark .mdo p>em,
|
html.z .mdo p>em,
|
||||||
html.dark .mdo li>em,
|
html.z .mdo li>em,
|
||||||
html.dark .mdo td>em {
|
html.z .mdo td>em {
|
||||||
color: #f94;
|
color: #f94;
|
||||||
border-color: #666;
|
border-color: #666;
|
||||||
}
|
}
|
||||||
html.dark .mdo h1 {
|
html.z .mdo h1 {
|
||||||
background: #383838;
|
background: #383838;
|
||||||
border-top: .4em solid #b80;
|
border-top: .4em solid #b80;
|
||||||
border-bottom: .4em solid #4c4c4c;
|
border-bottom: .4em solid #4c4c4c;
|
||||||
}
|
}
|
||||||
html.dark .mdo h2 {
|
html.z .mdo h2 {
|
||||||
background: #444;
|
background: #444;
|
||||||
border-bottom: .22em solid #555;
|
border-bottom: .22em solid #555;
|
||||||
}
|
}
|
||||||
html.dark .mdo td,
|
html.z .mdo td,
|
||||||
html.dark .mdo th {
|
html.z .mdo th {
|
||||||
border-color: #444;
|
border-color: #444;
|
||||||
}
|
}
|
||||||
html.dark .mdo blockquote {
|
html.z .mdo blockquote {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
border: .07em dashed #444;
|
border: .07em dashed #444;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,12 +644,6 @@ function up2k_init(subtle) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ebi('u2nope').onclick = function (e) {
|
|
||||||
ev(e);
|
|
||||||
setmsg(suggest_up2k, 'msg');
|
|
||||||
goto('bup');
|
|
||||||
};
|
|
||||||
|
|
||||||
setmsg(suggest_up2k, 'msg');
|
setmsg(suggest_up2k, 'msg');
|
||||||
|
|
||||||
if (!String.prototype.format) {
|
if (!String.prototype.format) {
|
||||||
@@ -1173,7 +1167,7 @@ function up2k_init(subtle) {
|
|||||||
var t = st.todo.handshake[0],
|
var t = st.todo.handshake[0],
|
||||||
cd = t.cooldown;
|
cd = t.cooldown;
|
||||||
|
|
||||||
if (cd && cd - Date.now() > 0)
|
if (cd && cd > Date.now())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// keepalive or verify
|
// keepalive or verify
|
||||||
@@ -1370,6 +1364,14 @@ function up2k_init(subtle) {
|
|||||||
return taskerd;
|
return taskerd;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
function chill(t) {
|
||||||
|
var now = Date.now();
|
||||||
|
if ((t.coolmul || 0) < 2 || now - t.cooldown < t.coolmul * 700)
|
||||||
|
t.coolmul = Math.min((t.coolmul || 0.5) * 2, 32);
|
||||||
|
|
||||||
|
t.cooldown = Math.max(t.cooldown || 1, Date.now() + t.coolmul * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
/////
|
/////
|
||||||
////
|
////
|
||||||
/// hashing
|
/// hashing
|
||||||
@@ -1468,7 +1470,6 @@ function up2k_init(subtle) {
|
|||||||
min_filebuf = 1;
|
min_filebuf = 1;
|
||||||
var td = Date.now() - t0;
|
var td = Date.now() - t0;
|
||||||
if (td > 50) {
|
if (td > 50) {
|
||||||
ebi('u2foot').innerHTML += "<p>excessive filereader latency (" + td + " ms), increasing readahead</p>";
|
|
||||||
min_filebuf = 32 * 1024 * 1024;
|
min_filebuf = 32 * 1024 * 1024;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1756,8 +1757,12 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
pvis.move(t.n, 'ok');
|
pvis.move(t.n, 'ok');
|
||||||
}
|
}
|
||||||
else t.t_uploaded = undefined;
|
else {
|
||||||
|
if (t.t_uploaded)
|
||||||
|
chill(t);
|
||||||
|
|
||||||
|
t.t_uploaded = undefined;
|
||||||
|
}
|
||||||
tasker();
|
tasker();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1869,7 +1874,8 @@ function up2k_init(subtle) {
|
|||||||
else {
|
else {
|
||||||
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
|
toast.err(0, "server broke; cu-err {0} on file [{1}]:\n".format(
|
||||||
xhr.status, t.name) + (txt || "no further information"));
|
xhr.status, t.name) + (txt || "no further information"));
|
||||||
return;
|
|
||||||
|
chill(t);
|
||||||
}
|
}
|
||||||
orz2(xhr);
|
orz2(xhr);
|
||||||
}
|
}
|
||||||
@@ -1920,6 +1926,7 @@ function up2k_init(subtle) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
function onresize(e) {
|
function onresize(e) {
|
||||||
|
// 10x faster than matchMedia('(min-width
|
||||||
var bar = ebi('ops'),
|
var bar = ebi('ops'),
|
||||||
wpx = window.innerWidth,
|
wpx = window.innerWidth,
|
||||||
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
fpx = parseInt(getComputedStyle(bar)['font-size']),
|
||||||
@@ -1929,7 +1936,6 @@ function up2k_init(subtle) {
|
|||||||
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
|
parent = ebi(wide && write ? 'u2btn_cw' : 'u2btn_ct'),
|
||||||
btn = ebi('u2btn');
|
btn = ebi('u2btn');
|
||||||
|
|
||||||
//console.log([wpx, fpx, wem]);
|
|
||||||
if (btn.parentNode !== parent) {
|
if (btn.parentNode !== parent) {
|
||||||
parent.appendChild(btn);
|
parent.appendChild(btn);
|
||||||
ebi('u2conf').setAttribute('class', wide);
|
ebi('u2conf').setAttribute('class', wide);
|
||||||
@@ -2057,10 +2063,10 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var ico = uc.fsearch ? '🔎' : '🚀',
|
var ico = uc.fsearch ? '🔎' : '🚀',
|
||||||
desc = uc.fsearch ? 'Search' : 'Upload';
|
desc = uc.fsearch ? 'S E A R C H' : 'U P L O A D';
|
||||||
|
|
||||||
clmod(ebi('op_up2k'), 'srch', uc.fsearch);
|
clmod(ebi('op_up2k'), 'srch', uc.fsearch);
|
||||||
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
ebi('u2bm').innerHTML = ico + ' <sup>' + desc + '</sup>';
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
|
|||||||
@@ -332,6 +332,16 @@ function clgot(el, cls) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var ANIM = true;
|
||||||
|
if (window.matchMedia) {
|
||||||
|
var mq = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||||
|
mq.onchange = function () {
|
||||||
|
ANIM = !mq.matches;
|
||||||
|
};
|
||||||
|
ANIM = !mq.matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function showsort(tab) {
|
function showsort(tab) {
|
||||||
var v, vn, v1, v2, th = tab.tHead,
|
var v, vn, v1, v2, th = tab.tHead,
|
||||||
sopts = jread('fsort', [["href", 1, ""]]);
|
sopts = jread('fsort', [["href", 1, ""]]);
|
||||||
@@ -872,7 +882,7 @@ var tt = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.getmsg = function (el) {
|
r.getmsg = function (el) {
|
||||||
if (QS('body.bbox-open'))
|
if (IPHONE && QS('body.bbox-open'))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var cfg = sread('tooltips');
|
var cfg = sread('tooltips');
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ echo not a script
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## add index.html banners
|
||||||
|
|
||||||
|
find -name index.html | sed -r 's/index.html$//' | while IFS= read -r dir; do f="$dir/.prologue.html"; [ -e "$f" ] || echo '<h1><a href="index.html">open index.html</a></h1>' >"$f"; done
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## delete all partial uploads
|
## delete all partial uploads
|
||||||
## (supports linux/macos, probably windows+msys2)
|
## (supports linux/macos, probably windows+msys2)
|
||||||
@@ -95,6 +101,7 @@ var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.quer
|
|||||||
# debug md-editor line tracking
|
# debug md-editor line tracking
|
||||||
var s=mknod('style');s.innerHTML='*[data-ln]:before {content:attr(data-ln)!important;color:#f0c;background:#000;position:absolute;left:-1.5em;font-size:1rem}';document.head.appendChild(s);
|
var s=mknod('style');s.innerHTML='*[data-ln]:before {content:attr(data-ln)!important;color:#f0c;background:#000;position:absolute;left:-1.5em;font-size:1rem}';document.head.appendChild(s);
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## bash oneliners
|
## bash oneliners
|
||||||
|
|
||||||
@@ -199,6 +206,7 @@ git remote add all git@github.com:9001/copyparty.git
|
|||||||
git remote set-url --add --push all git@gitlab.com:9001/copyparty.git
|
git remote set-url --add --push all git@gitlab.com:9001/copyparty.git
|
||||||
git remote set-url --add --push all git@github.com:9001/copyparty.git
|
git remote set-url --add --push all git@github.com:9001/copyparty.git
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## http 206
|
## http 206
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,18 @@ set -e
|
|||||||
#
|
#
|
||||||
# output summary (filesizes and contents):
|
# output summary (filesizes and contents):
|
||||||
#
|
#
|
||||||
# 535672 copyparty-extras/sfx-full/copyparty-sfx.sh
|
|
||||||
# 550760 copyparty-extras/sfx-full/copyparty-sfx.py
|
# 550760 copyparty-extras/sfx-full/copyparty-sfx.py
|
||||||
# `- original unmodified sfx from github
|
# `- original unmodified sfx from github
|
||||||
#
|
#
|
||||||
# 572923 copyparty-extras/sfx-full/copyparty-sfx-gz.py
|
# 572923 copyparty-extras/sfx-full/copyparty-sfx-gz.py
|
||||||
# `- unmodified but recompressed from bzip2 to gzip
|
# `- unmodified but recompressed from bzip2 to gzip
|
||||||
#
|
#
|
||||||
# 341792 copyparty-extras/sfx-ent/copyparty-sfx.sh
|
|
||||||
# 353975 copyparty-extras/sfx-ent/copyparty-sfx.py
|
# 353975 copyparty-extras/sfx-ent/copyparty-sfx.py
|
||||||
# 376934 copyparty-extras/sfx-ent/copyparty-sfx-gz.py
|
# 376934 copyparty-extras/sfx-ent/copyparty-sfx-gz.py
|
||||||
# `- removed iOS ogg/opus/vorbis audio decoder,
|
# `- removed iOS ogg/opus/vorbis audio decoder,
|
||||||
# removed the audio tray mouse cursor,
|
# removed the audio tray mouse cursor,
|
||||||
# "enterprise edition"
|
# "enterprise edition"
|
||||||
#
|
#
|
||||||
# 259288 copyparty-extras/sfx-lite/copyparty-sfx.sh
|
|
||||||
# 270004 copyparty-extras/sfx-lite/copyparty-sfx.py
|
# 270004 copyparty-extras/sfx-lite/copyparty-sfx.py
|
||||||
# 293159 copyparty-extras/sfx-lite/copyparty-sfx-gz.py
|
# 293159 copyparty-extras/sfx-lite/copyparty-sfx-gz.py
|
||||||
# `- also removed the codemirror markdown editor
|
# `- also removed the codemirror markdown editor
|
||||||
@@ -81,7 +78,7 @@ cache="$od/.copyparty-repack.cache"
|
|||||||
# fallback to awk (sorry)
|
# fallback to awk (sorry)
|
||||||
awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
|
awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
|
||||||
) |
|
) |
|
||||||
grep -E '(sfx\.(sh|py)|tar\.gz)$' |
|
grep -E '(sfx\.py|tar\.gz)$' |
|
||||||
tee /dev/stderr |
|
tee /dev/stderr |
|
||||||
tr -d '\r' | tr '\n' '\0' |
|
tr -d '\r' | tr '\n' '\0' |
|
||||||
xargs -0 bash -c 'dl_files "$@"' _
|
xargs -0 bash -c 'dl_files "$@"' _
|
||||||
@@ -139,11 +136,11 @@ repack() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
repack sfx-full "re gz no-sh"
|
repack sfx-full "re gz"
|
||||||
repack sfx-ent "re no-dd"
|
repack sfx-ent "re no-dd"
|
||||||
repack sfx-ent "re no-dd gz no-sh"
|
repack sfx-ent "re no-dd gz"
|
||||||
repack sfx-lite "re no-dd no-cm no-hl"
|
repack sfx-lite "re no-dd no-cm no-hl"
|
||||||
repack sfx-lite "re no-dd no-cm no-hl gz no-sh"
|
repack sfx-lite "re no-dd no-cm no-hl gz"
|
||||||
|
|
||||||
|
|
||||||
# move fuse and up2k clients into copyparty-extras/,
|
# move fuse and up2k clients into copyparty-extras/,
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ FROM alpine:3.15
|
|||||||
WORKDIR /z
|
WORKDIR /z
|
||||||
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
|
ENV ver_asmcrypto=5b994303a9d3e27e0915f72a10b6c2c51535a4dc \
|
||||||
ver_hashwasm=4.9.0 \
|
ver_hashwasm=4.9.0 \
|
||||||
ver_marked=4.0.10 \
|
ver_marked=4.0.12 \
|
||||||
ver_mde=2.15.0 \
|
ver_mde=2.16.1 \
|
||||||
ver_codemirror=5.64.0 \
|
ver_codemirror=5.65.2 \
|
||||||
ver_fontawesome=5.13.0 \
|
ver_fontawesome=5.13.0 \
|
||||||
ver_zopfli=1.0.3
|
ver_zopfli=1.0.3
|
||||||
|
|
||||||
|
|
||||||
# download;
|
# download;
|
||||||
# the scp url is latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap
|
# the scp url is regular latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap
|
||||||
RUN mkdir -p /z/dist/no-pk \
|
RUN mkdir -p /z/dist/no-pk \
|
||||||
&& wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \
|
&& wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \
|
||||||
&& apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \
|
&& apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \
|
||||||
@@ -97,15 +97,14 @@ RUN cd CodeMirror-$ver_codemirror \
|
|||||||
|
|
||||||
|
|
||||||
# build easymde
|
# build easymde
|
||||||
COPY easymde-marked6.patch /z/
|
|
||||||
COPY easymde.patch /z/
|
COPY easymde.patch /z/
|
||||||
RUN cd easy-markdown-editor-$ver_mde \
|
RUN cd easy-markdown-editor-$ver_mde \
|
||||||
&& patch -p1 < /z/easymde-marked6.patch \
|
|
||||||
&& patch -p1 < /z/easymde.patch \
|
&& patch -p1 < /z/easymde.patch \
|
||||||
&& sed -ri 's`https://registry.npmjs.org/marked/-/marked-[0-9\.]+.tgz`file:/z/nodepkgs/marked`' package-lock.json \
|
&& sed -ri 's`https://registry.npmjs.org/marked/-/marked-[0-9\.]+.tgz`file:/z/nodepkgs/marked`' package-lock.json \
|
||||||
|
&& sed -ri 's`https://registry.npmjs.org/codemirror/-/codemirror-[0-9\.]+.tgz`file:/z/nodepkgs/codemirror`' package-lock.json \
|
||||||
&& sed -ri 's`("marked": ")[^"]+`\1file:/z/nodepkgs/marked`' ./package.json \
|
&& sed -ri 's`("marked": ")[^"]+`\1file:/z/nodepkgs/marked`' ./package.json \
|
||||||
&& sed -ri 's`("codemirror": ")[^"]+`\1file:/z/nodepkgs/codemirror`' ./package.json \
|
&& sed -ri 's`("codemirror": ")[^"]+`\1file:/z/nodepkgs/codemirror`' ./package.json \
|
||||||
&& sed -ri 's`^var marked = require\(.marked/lib/marked.\);$`var marked = window.marked;`' src/js/easymde.js \
|
&& sed -ri 's`^var marked = require\(.marked.\).marked;$`var marked = window.marked;`' src/js/easymde.js \
|
||||||
&& npm install
|
&& npm install
|
||||||
|
|
||||||
COPY easymde-ln.patch /z/
|
COPY easymde-ln.patch /z/
|
||||||
@@ -119,6 +118,7 @@ RUN cd easy-markdown-editor-$ver_mde \
|
|||||||
# build fontawesome and scp
|
# build fontawesome and scp
|
||||||
COPY mini-fa.sh /z
|
COPY mini-fa.sh /z
|
||||||
COPY mini-fa.css /z
|
COPY mini-fa.css /z
|
||||||
|
COPY shiftbase.py /z
|
||||||
RUN /bin/ash /z/mini-fa.sh
|
RUN /bin/ash /z/mini-fa.sh
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
diff -NarU2 codemirror-5.59.3-orig/mode/gfm/gfm.js codemirror-5.59.3/mode/gfm/gfm.js
|
diff -wNarU2 codemirror-5.65.1-orig/mode/gfm/gfm.js codemirror-5.65.1/mode/gfm/gfm.js
|
||||||
--- codemirror-5.59.3-orig/mode/gfm/gfm.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/mode/gfm/gfm.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/mode/gfm/gfm.js 2021-02-21 20:42:02.166174775 +0000
|
+++ codemirror-5.65.1/mode/gfm/gfm.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -97,5 +97,5 @@
|
@@ -97,5 +97,5 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,9 @@ diff -NarU2 codemirror-5.59.3-orig/mode/gfm/gfm.js codemirror-5.59.3/mode/gfm/gf
|
|||||||
+ }*/
|
+ }*/
|
||||||
stream.next();
|
stream.next();
|
||||||
return null;
|
return null;
|
||||||
diff -NarU2 codemirror-5.59.3-orig/mode/meta.js codemirror-5.59.3/mode/meta.js
|
diff -wNarU2 codemirror-5.65.1-orig/mode/meta.js codemirror-5.65.1/mode/meta.js
|
||||||
--- codemirror-5.59.3-orig/mode/meta.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/mode/meta.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/mode/meta.js 2021-02-21 20:42:54.798742821 +0000
|
+++ codemirror-5.65.1/mode/meta.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -13,4 +13,5 @@
|
@@ -13,4 +13,5 @@
|
||||||
|
|
||||||
CodeMirror.modeInfo = [
|
CodeMirror.modeInfo = [
|
||||||
@@ -62,10 +62,10 @@ diff -NarU2 codemirror-5.59.3-orig/mode/meta.js codemirror-5.59.3/mode/meta.js
|
|||||||
+ */
|
+ */
|
||||||
];
|
];
|
||||||
// Ensure all modes have a mime property for backwards compatibility
|
// Ensure all modes have a mime property for backwards compatibility
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/display/selection.js codemirror-5.59.3/src/display/selection.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/display/selection.js codemirror-5.65.1/src/display/selection.js
|
||||||
--- codemirror-5.59.3-orig/src/display/selection.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/display/selection.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/display/selection.js 2021-02-21 20:44:14.860894328 +0000
|
+++ codemirror-5.65.1/src/display/selection.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -84,29 +84,21 @@
|
@@ -96,29 +96,21 @@
|
||||||
let order = getOrder(lineObj, doc.direction)
|
let order = getOrder(lineObj, doc.direction)
|
||||||
iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => {
|
iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => {
|
||||||
- let ltr = dir == "ltr"
|
- let ltr = dir == "ltr"
|
||||||
@@ -105,24 +105,24 @@ diff -NarU2 codemirror-5.59.3-orig/src/display/selection.js codemirror-5.59.3/sr
|
|||||||
+ botRight = openEnd && last ? rightSide : toPos.right
|
+ botRight = openEnd && last ? rightSide : toPos.right
|
||||||
add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
|
add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
|
||||||
if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top)
|
if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top)
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/input/ContentEditableInput.js codemirror-5.59.3/src/input/ContentEditableInput.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/input/ContentEditableInput.js codemirror-5.65.1/src/input/ContentEditableInput.js
|
||||||
--- codemirror-5.59.3-orig/src/input/ContentEditableInput.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/input/ContentEditableInput.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/input/ContentEditableInput.js 2021-02-21 20:44:33.273953867 +0000
|
+++ codemirror-5.65.1/src/input/ContentEditableInput.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -399,4 +399,5 @@
|
@@ -400,4 +400,5 @@
|
||||||
let info = mapFromLineView(view, line, pos.line)
|
let info = mapFromLineView(view, line, pos.line)
|
||||||
|
|
||||||
+ /*
|
+ /*
|
||||||
let order = getOrder(line, cm.doc.direction), side = "left"
|
let order = getOrder(line, cm.doc.direction), side = "left"
|
||||||
if (order) {
|
if (order) {
|
||||||
@@ -404,4 +405,5 @@
|
@@ -405,4 +406,5 @@
|
||||||
side = partPos % 2 ? "right" : "left"
|
side = partPos % 2 ? "right" : "left"
|
||||||
}
|
}
|
||||||
+ */
|
+ */
|
||||||
let result = nodeAndOffsetInLineMap(info.map, pos.ch, side)
|
let result = nodeAndOffsetInLineMap(info.map, pos.ch, side)
|
||||||
result.offset = result.collapse == "right" ? result.end : result.start
|
result.offset = result.collapse == "right" ? result.end : result.start
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/input/movement.js codemirror-5.59.3/src/input/movement.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/input/movement.js codemirror-5.65.1/src/input/movement.js
|
||||||
--- codemirror-5.59.3-orig/src/input/movement.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/input/movement.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/input/movement.js 2021-02-21 20:45:12.763093671 +0000
|
+++ codemirror-5.65.1/src/input/movement.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -15,4 +15,5 @@
|
@@ -15,4 +15,5 @@
|
||||||
|
|
||||||
export function endOfLine(visually, cm, lineObj, lineNo, dir) {
|
export function endOfLine(visually, cm, lineObj, lineNo, dir) {
|
||||||
@@ -146,9 +146,16 @@ diff -NarU2 codemirror-5.59.3-orig/src/input/movement.js codemirror-5.59.3/src/i
|
|||||||
return null
|
return null
|
||||||
+ */
|
+ */
|
||||||
}
|
}
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/line/line_data.js codemirror-5.59.3/src/line/line_data.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/line/line_data.js codemirror-5.65.1/src/line/line_data.js
|
||||||
--- codemirror-5.59.3-orig/src/line/line_data.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/line/line_data.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/line/line_data.js 2021-02-21 20:45:36.472549599 +0000
|
+++ codemirror-5.65.1/src/line/line_data.js 2022-02-09 22:54:11.542722046 +0100
|
||||||
|
@@ -3,5 +3,5 @@
|
||||||
|
import { elt, eltP, joinClasses } from "../util/dom.js"
|
||||||
|
import { eventMixin, signal } from "../util/event.js"
|
||||||
|
-import { hasBadBidiRects, zeroWidthElement } from "../util/feature_detection.js"
|
||||||
|
+import { zeroWidthElement } from "../util/feature_detection.js"
|
||||||
|
import { lst, spaceStr } from "../util/misc.js"
|
||||||
|
|
||||||
@@ -79,6 +79,6 @@
|
@@ -79,6 +79,6 @@
|
||||||
// Optionally wire in some hacks into the token-rendering
|
// Optionally wire in some hacks into the token-rendering
|
||||||
// algorithm, to deal with browser quirks.
|
// algorithm, to deal with browser quirks.
|
||||||
@@ -158,10 +165,10 @@ diff -NarU2 codemirror-5.59.3-orig/src/line/line_data.js codemirror-5.59.3/src/l
|
|||||||
+ // builder.addToken = buildTokenBadBidi(builder.addToken, order)
|
+ // builder.addToken = buildTokenBadBidi(builder.addToken, order)
|
||||||
builder.map = []
|
builder.map = []
|
||||||
let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line)
|
let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line)
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/measurement/position_measurement.js codemirror-5.59.3/src/measurement/position_measurement.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/measurement/position_measurement.js codemirror-5.65.1/src/measurement/position_measurement.js
|
||||||
--- codemirror-5.59.3-orig/src/measurement/position_measurement.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/measurement/position_measurement.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/measurement/position_measurement.js 2021-02-21 20:50:52.372945293 +0000
|
+++ codemirror-5.65.1/src/measurement/position_measurement.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -380,5 +380,6 @@
|
@@ -382,5 +382,6 @@
|
||||||
sticky = "after"
|
sticky = "after"
|
||||||
}
|
}
|
||||||
- if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before")
|
- if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before")
|
||||||
@@ -169,39 +176,39 @@ diff -NarU2 codemirror-5.59.3-orig/src/measurement/position_measurement.js codem
|
|||||||
+ /*
|
+ /*
|
||||||
|
|
||||||
function getBidi(ch, partPos, invert) {
|
function getBidi(ch, partPos, invert) {
|
||||||
@@ -391,4 +392,5 @@
|
@@ -393,4 +394,5 @@
|
||||||
if (other != null) val.other = getBidi(ch, other, sticky != "before")
|
if (other != null) val.other = getBidi(ch, other, sticky != "before")
|
||||||
return val
|
return val
|
||||||
+ */
|
+ */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,4 +470,5 @@
|
@@ -470,4 +472,5 @@
|
||||||
let begin = 0, end = lineObj.text.length, ltr = true
|
let begin = 0, end = lineObj.text.length, ltr = true
|
||||||
|
|
||||||
+ /*
|
+ /*
|
||||||
let order = getOrder(lineObj, cm.doc.direction)
|
let order = getOrder(lineObj, cm.doc.direction)
|
||||||
// If the line isn't plain left-to-right text, first figure out
|
// If the line isn't plain left-to-right text, first figure out
|
||||||
@@ -482,4 +485,5 @@
|
@@ -484,4 +487,5 @@
|
||||||
end = ltr ? part.to : part.from - 1
|
end = ltr ? part.to : part.from - 1
|
||||||
}
|
}
|
||||||
+ */
|
+ */
|
||||||
|
|
||||||
// A binary search to find the first character whose bounding box
|
// A binary search to find the first character whose bounding box
|
||||||
@@ -526,4 +530,5 @@
|
@@ -528,4 +532,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
+/*
|
+/*
|
||||||
function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {
|
function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {
|
||||||
// Bidi parts are sorted left-to-right, and in a non-line-wrapping
|
// Bidi parts are sorted left-to-right, and in a non-line-wrapping
|
||||||
@@ -580,4 +585,5 @@
|
@@ -582,4 +587,5 @@
|
||||||
return part
|
return part
|
||||||
}
|
}
|
||||||
+*/
|
+*/
|
||||||
|
|
||||||
let measureText
|
let measureText
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/util/bidi.js codemirror-5.59.3/src/util/bidi.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/util/bidi.js codemirror-5.65.1/src/util/bidi.js
|
||||||
--- codemirror-5.59.3-orig/src/util/bidi.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/util/bidi.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/util/bidi.js 2021-02-21 20:52:18.168092225 +0000
|
+++ codemirror-5.65.1/src/util/bidi.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -4,5 +4,5 @@
|
@@ -4,5 +4,5 @@
|
||||||
|
|
||||||
export function iterateBidiSections(order, from, to, f) {
|
export function iterateBidiSections(order, from, to, f) {
|
||||||
@@ -259,9 +266,9 @@ diff -NarU2 codemirror-5.59.3-orig/src/util/bidi.js codemirror-5.59.3/src/util/b
|
|||||||
- return order
|
- return order
|
||||||
+ return false;
|
+ return false;
|
||||||
}
|
}
|
||||||
diff -NarU2 codemirror-5.59.3-orig/src/util/feature_detection.js codemirror-5.59.3/src/util/feature_detection.js
|
diff -wNarU2 codemirror-5.65.1-orig/src/util/feature_detection.js codemirror-5.65.1/src/util/feature_detection.js
|
||||||
--- codemirror-5.59.3-orig/src/util/feature_detection.js 2021-02-20 21:24:57.000000000 +0000
|
--- codemirror-5.65.1-orig/src/util/feature_detection.js 2022-01-20 13:06:23.000000000 +0100
|
||||||
+++ codemirror-5.59.3/src/util/feature_detection.js 2021-02-21 20:49:22.191269270 +0000
|
+++ codemirror-5.65.1/src/util/feature_detection.js 2022-02-09 22:50:18.145862052 +0100
|
||||||
@@ -25,4 +25,5 @@
|
@@ -25,4 +25,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
diff -NarU2 easy-markdown-editor-2.14.0-orig/gulpfile.js easy-markdown-editor-2.14.0/gulpfile.js
|
diff -wNarU2 easy-markdown-editor-2.16.1-orig/gulpfile.js easy-markdown-editor-2.16.1/gulpfile.js
|
||||||
--- easy-markdown-editor-2.14.0-orig/gulpfile.js 2021-02-14 12:11:48.000000000 +0000
|
--- easy-markdown-editor-2.16.1-orig/gulpfile.js 2022-01-14 23:27:44.000000000 +0100
|
||||||
+++ easy-markdown-editor-2.14.0/gulpfile.js 2021-02-21 20:55:37.134701007 +0000
|
+++ easy-markdown-editor-2.16.1/gulpfile.js 2022-02-09 23:06:01.694592535 +0100
|
||||||
@@ -25,5 +25,4 @@
|
@@ -25,5 +25,4 @@
|
||||||
'./node_modules/codemirror/lib/codemirror.css',
|
'./node_modules/codemirror/lib/codemirror.css',
|
||||||
'./src/css/*.css',
|
'./src/css/*.css',
|
||||||
- './node_modules/codemirror-spell-checker/src/css/spell-checker.css',
|
- './node_modules/codemirror-spell-checker/src/css/spell-checker.css',
|
||||||
];
|
];
|
||||||
|
|
||||||
diff -NarU2 easy-markdown-editor-2.14.0-orig/package.json easy-markdown-editor-2.14.0/package.json
|
diff -wNarU2 easy-markdown-editor-2.16.1-orig/package.json easy-markdown-editor-2.16.1/package.json
|
||||||
--- easy-markdown-editor-2.14.0-orig/package.json 2021-02-14 12:11:48.000000000 +0000
|
--- easy-markdown-editor-2.16.1-orig/package.json 2022-01-14 23:27:44.000000000 +0100
|
||||||
+++ easy-markdown-editor-2.14.0/package.json 2021-02-21 20:55:47.761190082 +0000
|
+++ easy-markdown-editor-2.16.1/package.json 2022-02-09 23:06:24.778501888 +0100
|
||||||
@@ -21,5 +21,4 @@
|
@@ -23,5 +23,4 @@
|
||||||
"dependencies": {
|
"@types/marked": "^4.0.1",
|
||||||
"codemirror": "^5.59.2",
|
"codemirror": "^5.63.1",
|
||||||
- "codemirror-spell-checker": "1.1.2",
|
- "codemirror-spell-checker": "1.1.2",
|
||||||
"marked": "^2.0.0"
|
"marked": "^4.0.10"
|
||||||
},
|
},
|
||||||
diff -NarU2 easy-markdown-editor-2.14.0-orig/src/js/easymde.js easy-markdown-editor-2.14.0/src/js/easymde.js
|
diff -wNarU2 easy-markdown-editor-2.16.1-orig/src/js/easymde.js easy-markdown-editor-2.16.1/src/js/easymde.js
|
||||||
--- easy-markdown-editor-2.14.0-orig/src/js/easymde.js 2021-02-14 12:11:48.000000000 +0000
|
--- easy-markdown-editor-2.16.1-orig/src/js/easymde.js 2022-01-14 23:27:44.000000000 +0100
|
||||||
+++ easy-markdown-editor-2.14.0/src/js/easymde.js 2021-02-21 20:57:09.143171536 +0000
|
+++ easy-markdown-editor-2.16.1/src/js/easymde.js 2022-02-09 23:07:21.203131415 +0100
|
||||||
@@ -12,5 +12,4 @@
|
@@ -12,5 +12,4 @@
|
||||||
require('codemirror/mode/gfm/gfm.js');
|
require('codemirror/mode/gfm/gfm.js');
|
||||||
require('codemirror/mode/xml/xml.js');
|
require('codemirror/mode/xml/xml.js');
|
||||||
-var CodeMirrorSpellChecker = require('codemirror-spell-checker');
|
-var CodeMirrorSpellChecker = require('codemirror-spell-checker');
|
||||||
var marked = require('marked/lib/marked');
|
var marked = require('marked').marked;
|
||||||
|
|
||||||
@@ -1762,9 +1761,4 @@
|
@@ -1816,9 +1815,4 @@
|
||||||
options.autosave.uniqueId = options.autosave.unique_id;
|
options.autosave.uniqueId = options.autosave.unique_id;
|
||||||
|
|
||||||
- // If overlay mode is specified and combine is not provided, default it to true
|
- // If overlay mode is specified and combine is not provided, default it to true
|
||||||
- if (options.overlayMode && options.overlayMode.combine === undefined) {
|
- if (options.overlayMode && options.overlayMode.combine === undefined) {
|
||||||
- options.overlayMode.combine = true;
|
- options.overlayMode.combine = true;
|
||||||
- }
|
- }
|
||||||
-
|
-
|
||||||
// Update this options
|
// Update this options
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@@ -2003,28 +1997,7 @@
|
@@ -2057,34 +2051,7 @@
|
||||||
var mode, backdrop;
|
var mode, backdrop;
|
||||||
|
|
||||||
- // CodeMirror overlay mode
|
- // CodeMirror overlay mode
|
||||||
- if (options.overlayMode) {
|
- if (options.overlayMode) {
|
||||||
- CodeMirror.defineMode('overlay-mode', function(config) {
|
- CodeMirror.defineMode('overlay-mode', function (config) {
|
||||||
- return CodeMirror.overlayMode(CodeMirror.getMode(config, options.spellChecker !== false ? 'spell-checker' : 'gfm'), options.overlayMode.mode, options.overlayMode.combine);
|
- return CodeMirror.overlayMode(CodeMirror.getMode(config, options.spellChecker !== false ? 'spell-checker' : 'gfm'), options.overlayMode.mode, options.overlayMode.combine);
|
||||||
- });
|
- });
|
||||||
-
|
-
|
||||||
- mode = 'overlay-mode';
|
- mode = 'overlay-mode';
|
||||||
- backdrop = options.parsingConfig;
|
- backdrop = options.parsingConfig;
|
||||||
- backdrop.gitHubSpice = false;
|
- backdrop.gitHubSpice = false;
|
||||||
- } else {
|
- } else {
|
||||||
mode = options.parsingConfig;
|
mode = options.parsingConfig;
|
||||||
mode.name = 'gfm';
|
mode.name = 'gfm';
|
||||||
@@ -58,31 +58,35 @@ diff -NarU2 easy-markdown-editor-2.14.0-orig/src/js/easymde.js easy-markdown-edi
|
|||||||
- backdrop.name = 'gfm';
|
- backdrop.name = 'gfm';
|
||||||
- backdrop.gitHubSpice = false;
|
- backdrop.gitHubSpice = false;
|
||||||
-
|
-
|
||||||
- CodeMirrorSpellChecker({
|
- if (typeof options.spellChecker === 'function') {
|
||||||
- codeMirrorInstance: CodeMirror,
|
- options.spellChecker({
|
||||||
- });
|
- codeMirrorInstance: CodeMirror,
|
||||||
|
- });
|
||||||
|
- } else {
|
||||||
|
- CodeMirrorSpellChecker({
|
||||||
|
- codeMirrorInstance: CodeMirror,
|
||||||
|
- });
|
||||||
|
- }
|
||||||
- }
|
- }
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
diff -NarU2 easy-markdown-editor-2.14.0-orig/types/easymde.d.ts easy-markdown-editor-2.14.0/types/easymde.d.ts
|
diff -wNarU2 easy-markdown-editor-2.16.1-orig/types/easymde.d.ts easy-markdown-editor-2.16.1/types/easymde.d.ts
|
||||||
--- easy-markdown-editor-2.14.0-orig/types/easymde.d.ts 2021-02-14 12:11:48.000000000 +0000
|
--- easy-markdown-editor-2.16.1-orig/types/easymde.d.ts 2022-01-14 23:27:44.000000000 +0100
|
||||||
+++ easy-markdown-editor-2.14.0/types/easymde.d.ts 2021-02-21 20:57:42.492620979 +0000
|
+++ easy-markdown-editor-2.16.1/types/easymde.d.ts 2022-02-09 23:07:55.427605243 +0100
|
||||||
@@ -160,9 +160,4 @@
|
@@ -167,9 +167,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- interface OverlayModeOptions {
|
- interface OverlayModeOptions {
|
||||||
- mode: CodeMirror.Mode<any>
|
- mode: CodeMirror.Mode<any>;
|
||||||
- combine?: boolean
|
- combine?: boolean;
|
||||||
- }
|
- }
|
||||||
-
|
-
|
||||||
interface Options {
|
interface SpellCheckerOptions {
|
||||||
autoDownloadFontAwesome?: boolean;
|
codeMirrorInstance: CodeMirror.Editor;
|
||||||
@@ -214,7 +209,5 @@
|
@@ -229,6 +224,4 @@
|
||||||
|
syncSideBySidePreviewScroll?: boolean;
|
||||||
|
|
||||||
promptTexts?: PromptTexts;
|
- overlayMode?: OverlayModeOptions;
|
||||||
- syncSideBySidePreviewScroll?: boolean;
|
|
||||||
-
|
-
|
||||||
- overlayMode?: OverlayModeOptions
|
direction?: 'ltr' | 'rtl';
|
||||||
+ syncSideBySidePreviewScroll?: boolean
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,3 +29,10 @@ pyftsubset "$orig_woff" --unicodes-file=/z/icon.list --no-ignore-missing-unicode
|
|||||||
|
|
||||||
# scp is easier, just want basic latin
|
# scp is easier, just want basic latin
|
||||||
pyftsubset /z/scp.woff2 --unicodes="20-7e,ab,b7,bb,2022" --no-ignore-missing-unicodes --flavor=woff2 --output-file=/z/dist/no-pk/scp.woff2 --verbose
|
pyftsubset /z/scp.woff2 --unicodes="20-7e,ab,b7,bb,2022" --no-ignore-missing-unicodes --flavor=woff2 --output-file=/z/dist/no-pk/scp.woff2 --verbose
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
# kinda works but ruins hinting on windows, just use the old version of the font which has correct baseline
|
||||||
|
python3 shiftbase.py /z/dist/no-pk/scp.woff2
|
||||||
|
cd /z/dist/no-pk/
|
||||||
|
mv scp.woff2.woff2 scp.woff2
|
||||||
|
|||||||
27
scripts/deps-docker/shiftbase.py
Executable file
27
scripts/deps-docker/shiftbase.py
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from fontTools.ttLib import TTFont, newTable
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
woff = sys.argv[1]
|
||||||
|
font = TTFont(woff)
|
||||||
|
print(repr(font["hhea"].__dict__))
|
||||||
|
print(repr(font["OS/2"].__dict__))
|
||||||
|
# font["hhea"].ascent = round(base_asc * mul)
|
||||||
|
# font["hhea"].descent = round(base_desc * mul)
|
||||||
|
# font["OS/2"].usWinAscent = round(base_asc * mul)
|
||||||
|
font["OS/2"].usWinDescent = round(font["OS/2"].usWinDescent * 1.1)
|
||||||
|
font["OS/2"].sTypoDescender = round(font["OS/2"].sTypoDescender * 1.1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
del font["post"].mapping["Delta#1"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
font.save(woff + ".woff2")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -14,8 +14,6 @@ help() { exec cat <<'EOF'
|
|||||||
#
|
#
|
||||||
# `gz` creates a gzip-compressed python sfx instead of bzip2
|
# `gz` creates a gzip-compressed python sfx instead of bzip2
|
||||||
#
|
#
|
||||||
# `no-sh` makes just the python sfx, skips the sh/unix sfx
|
|
||||||
#
|
|
||||||
# `no-cm` saves ~82k by removing easymde/codemirror
|
# `no-cm` saves ~82k by removing easymde/codemirror
|
||||||
# (the fancy markdown editor)
|
# (the fancy markdown editor)
|
||||||
#
|
#
|
||||||
@@ -64,8 +62,6 @@ pybin=$(command -v python3 || command -v python) || {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use_gz=
|
use_gz=
|
||||||
do_sh=1
|
|
||||||
do_py=1
|
|
||||||
zopf=2560
|
zopf=2560
|
||||||
while [ ! -z "$1" ]; do
|
while [ ! -z "$1" ]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -76,8 +72,6 @@ while [ ! -z "$1" ]; do
|
|||||||
no-hl) no_hl=1 ; ;;
|
no-hl) no_hl=1 ; ;;
|
||||||
no-dd) no_dd=1 ; ;;
|
no-dd) no_dd=1 ; ;;
|
||||||
no-cm) no_cm=1 ; ;;
|
no-cm) no_cm=1 ; ;;
|
||||||
no-sh) do_sh= ; ;;
|
|
||||||
no-py) do_py= ; ;;
|
|
||||||
fast) zopf=100 ; ;;
|
fast) zopf=100 ; ;;
|
||||||
*) help ; ;;
|
*) help ; ;;
|
||||||
esac
|
esac
|
||||||
@@ -107,7 +101,7 @@ tmpdir="$(
|
|||||||
[ $repack ] && {
|
[ $repack ] && {
|
||||||
old="$tmpdir/pe-copyparty"
|
old="$tmpdir/pe-copyparty"
|
||||||
echo "repack of files in $old"
|
echo "repack of files in $old"
|
||||||
cp -pR "$old/"*{dep-j2,copyparty} .
|
cp -pR "$old/"*{dep-j2,dep-ftp,copyparty} .
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $repack ] || {
|
[ $repack ] || {
|
||||||
@@ -134,6 +128,27 @@ tmpdir="$(
|
|||||||
mkdir dep-j2/
|
mkdir dep-j2/
|
||||||
mv {markupsafe,jinja2} dep-j2/
|
mv {markupsafe,jinja2} dep-j2/
|
||||||
|
|
||||||
|
echo collecting pyftpdlib
|
||||||
|
f="../build/pyftpdlib-1.5.6.tar.gz"
|
||||||
|
[ -e "$f" ] ||
|
||||||
|
(url=https://github.com/giampaolo/pyftpdlib/archive/refs/tags/release-1.5.6.tar.gz;
|
||||||
|
wget -O$f "$url" || curl -L "$url" >$f)
|
||||||
|
|
||||||
|
tar -zxf $f
|
||||||
|
mv pyftpdlib-release-*/pyftpdlib .
|
||||||
|
rm -rf pyftpdlib-release-* pyftpdlib/test
|
||||||
|
|
||||||
|
mkdir dep-ftp/
|
||||||
|
mv pyftpdlib dep-ftp/
|
||||||
|
|
||||||
|
echo collecting asyncore, asynchat
|
||||||
|
for n in asyncore.py asynchat.py; do
|
||||||
|
f=../build/$n
|
||||||
|
[ -e "$f" ] ||
|
||||||
|
(url=https://raw.githubusercontent.com/python/cpython/c4d45ee670c09d4f6da709df072ec80cb7dfad22/Lib/$n;
|
||||||
|
wget -O$f "$url" || curl -L "$url" >$f)
|
||||||
|
done
|
||||||
|
|
||||||
# msys2 tar is bad, make the best of it
|
# msys2 tar is bad, make the best of it
|
||||||
echo collecting source
|
echo collecting source
|
||||||
[ $clean ] && {
|
[ $clean ] && {
|
||||||
@@ -144,6 +159,12 @@ tmpdir="$(
|
|||||||
(cd .. && tar -cf tar copyparty) && tar -xf ../tar
|
(cd .. && tar -cf tar copyparty) && tar -xf ../tar
|
||||||
}
|
}
|
||||||
rm -f ../tar
|
rm -f ../tar
|
||||||
|
|
||||||
|
# insert asynchat
|
||||||
|
mkdir copyparty/vend
|
||||||
|
for n in asyncore.py asynchat.py; do
|
||||||
|
awk 'NR<4||NR>27;NR==4{print"# license: https://opensource.org/licenses/ISC\n"}' ../build/$n >copyparty/vend/$n
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
ver=
|
ver=
|
||||||
@@ -245,7 +266,7 @@ rm have
|
|||||||
find | grep -E '\.py$' |
|
find | grep -E '\.py$' |
|
||||||
grep -vE '__version__' |
|
grep -vE '__version__' |
|
||||||
tr '\n' '\0' |
|
tr '\n' '\0' |
|
||||||
xargs -0 $pybin ../scripts/uncomment.py
|
xargs -0 "$pybin" ../scripts/uncomment.py
|
||||||
|
|
||||||
f=dep-j2/jinja2/constants.py
|
f=dep-j2/jinja2/constants.py
|
||||||
awk '/^LOREM_IPSUM_WORDS/{o=1;print "LOREM_IPSUM_WORDS = u\"a\"";next} !o; /"""/{o=0}' <$f >t
|
awk '/^LOREM_IPSUM_WORDS/{o=1;print "LOREM_IPSUM_WORDS = u\"a\"";next} !o; /"""/{o=0}' <$f >t
|
||||||
@@ -331,11 +352,18 @@ nf=$(ls -1 "$zdir"/arc.* | wc -l)
|
|||||||
|
|
||||||
|
|
||||||
echo gen tarlist
|
echo gen tarlist
|
||||||
for d in copyparty dep-j2; do find $d -type f; done |
|
for d in copyparty dep-j2 dep-ftp; do find $d -type f; done |
|
||||||
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
|
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |
|
||||||
sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
|
sed -r 's/([^ ]*) (.*)/\2.\1/' | grep -vE '/list1?$' > list1
|
||||||
|
|
||||||
(grep -vE '\.(gz|br)$' list1; grep -E '\.(gz|br)$' list1 | shuf) >list || true
|
for n in {1..50}; do
|
||||||
|
(grep -vE '\.(gz|br)$' list1; grep -E '\.(gz|br)$' list1 | shuf) >list || true
|
||||||
|
s=$(md5sum list | cut -c-16)
|
||||||
|
grep -q $s "$zdir/h" && continue
|
||||||
|
echo $s >> "$zdir/h"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
[ $n -eq 50 ] && exit
|
||||||
|
|
||||||
echo creating tar
|
echo creating tar
|
||||||
args=(--owner=1000 --group=1000)
|
args=(--owner=1000 --group=1000)
|
||||||
@@ -350,41 +378,27 @@ pe=bz2
|
|||||||
|
|
||||||
echo compressing tar
|
echo compressing tar
|
||||||
# detect best level; bzip2 -7 is usually better than -9
|
# detect best level; bzip2 -7 is usually better than -9
|
||||||
[ $do_py ] && { for n in {2..9}; do cp tar t.$n; $pc -$n t.$n & done; wait; mv -v $(ls -1S t.*.$pe | tail -n 1) tar.bz2; }
|
for n in {2..9}; do cp tar t.$n; $pc -$n t.$n & done; wait; mv -v $(ls -1S t.*.$pe | tail -n 1) tar.bz2
|
||||||
[ $do_sh ] && { for n in {2..9}; do cp tar t.$n; xz -ze$n t.$n & done; wait; mv -v $(ls -1S t.*.xz | tail -n 1) tar.xz; }
|
|
||||||
rm t.* || true
|
rm t.* || true
|
||||||
exts=()
|
exts=()
|
||||||
|
|
||||||
|
|
||||||
[ $do_sh ] && {
|
echo creating sfx
|
||||||
exts+=(.sh)
|
|
||||||
echo creating unix sfx
|
py=../scripts/sfx.py
|
||||||
(
|
suf=
|
||||||
sed "s/PACK_TS/$ts/; s/PACK_HTS/$hts/; s/CPP_VER/$ver/" <../scripts/sfx.sh |
|
[ $use_gz ] && {
|
||||||
grep -E '^sfx_eof$' -B 9001;
|
sed -r 's/"r:bz2"/"r:gz"/' <$py >$py.t
|
||||||
cat tar.xz
|
py=$py.t
|
||||||
) >$sfx_out.sh
|
suf=-gz
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"$pybin" $py --sfx-make tar.bz2 $ver $ts
|
||||||
|
mv sfx.out $sfx_out$suf.py
|
||||||
|
|
||||||
[ $do_py ] && {
|
exts+=($suf.py)
|
||||||
echo creating generic sfx
|
[ $use_gz ] &&
|
||||||
|
rm $py
|
||||||
py=../scripts/sfx.py
|
|
||||||
suf=
|
|
||||||
[ $use_gz ] && {
|
|
||||||
sed -r 's/"r:bz2"/"r:gz"/' <$py >$py.t
|
|
||||||
py=$py.t
|
|
||||||
suf=-gz
|
|
||||||
}
|
|
||||||
|
|
||||||
$pybin $py --sfx-make tar.bz2 $ver $ts
|
|
||||||
mv sfx.out $sfx_out$suf.py
|
|
||||||
|
|
||||||
exts+=($suf.py)
|
|
||||||
[ $use_gz ] &&
|
|
||||||
rm $py
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chmod 755 $sfx_out*
|
chmod 755 $sfx_out*
|
||||||
@@ -395,4 +409,4 @@ for ext in ${exts[@]}; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# apk add bash python3 tar xz bzip2
|
# apk add bash python3 tar xz bzip2
|
||||||
# while true; do ./make-sfx.sh; for f in ..//dist/copyparty-sfx.{sh,py}; do mv $f $f.$(wc -c <$f | awk '{print$1}'); done; done
|
# while true; do ./make-sfx.sh; f=../dist/copyparty-sfx.py; mv $f $f.$(wc -c <$f | awk '{print$1}'); done
|
||||||
|
|||||||
@@ -4,34 +4,31 @@ set -e
|
|||||||
cd ~/dev/copyparty/scripts
|
cd ~/dev/copyparty/scripts
|
||||||
|
|
||||||
v=$1
|
v=$1
|
||||||
printf '%s\n' "$v" | grep -qE '^[0-9\.]+$' || exit 1
|
|
||||||
grep -E "(${v//./, })" ../copyparty/__version__.py || exit 1
|
|
||||||
|
|
||||||
git push all
|
[ "$v" = sfx ] || {
|
||||||
git tag v$v
|
printf '%s\n' "$v" | grep -qE '^[0-9\.]+$' || exit 1
|
||||||
git push all --tags
|
grep -E "(${v//./, })" ../copyparty/__version__.py || exit 1
|
||||||
|
|
||||||
rm -rf ../dist
|
git push all
|
||||||
|
git tag v$v
|
||||||
|
git push all --tags
|
||||||
|
|
||||||
./make-pypi-release.sh u
|
rm -rf ../dist
|
||||||
(cd .. && python3 ./setup.py clean2)
|
|
||||||
|
|
||||||
./make-tgz-release.sh $v
|
./make-pypi-release.sh u
|
||||||
|
(cd .. && python3 ./setup.py clean2)
|
||||||
|
|
||||||
|
./make-tgz-release.sh $v
|
||||||
|
}
|
||||||
|
|
||||||
rm -f ../dist/copyparty-sfx.*
|
rm -f ../dist/copyparty-sfx.*
|
||||||
./make-sfx.sh no-sh
|
f=../dist/copyparty-sfx.py
|
||||||
../dist/copyparty-sfx.py -h
|
./make-sfx.sh
|
||||||
|
$f -h
|
||||||
|
|
||||||
ar=
|
|
||||||
while true; do
|
while true; do
|
||||||
for ((a=0; a<100; a++)); do
|
mv $f $f.$(wc -c <$f | awk '{print$1}')
|
||||||
for f in ../dist/copyparty-sfx.{py,sh}; do
|
./make-sfx.sh re $ar
|
||||||
[ -e $f ] || continue;
|
|
||||||
mv $f $f.$(wc -c <$f | awk '{print$1}')
|
|
||||||
done
|
|
||||||
./make-sfx.sh re $ar
|
|
||||||
done
|
|
||||||
ar=no-sh
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# git tag -d v$v; git push --delete origin v$v
|
# git tag -d v$v; git push --delete origin v$v
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ copyparty/broker_mp.py,
|
|||||||
copyparty/broker_mpw.py,
|
copyparty/broker_mpw.py,
|
||||||
copyparty/broker_thr.py,
|
copyparty/broker_thr.py,
|
||||||
copyparty/broker_util.py,
|
copyparty/broker_util.py,
|
||||||
|
copyparty/ftpd.py,
|
||||||
copyparty/httpcli.py,
|
copyparty/httpcli.py,
|
||||||
copyparty/httpconn.py,
|
copyparty/httpconn.py,
|
||||||
copyparty/httpsrv.py,
|
copyparty/httpsrv.py,
|
||||||
@@ -31,6 +32,9 @@ copyparty/th_srv.py,
|
|||||||
copyparty/u2idx.py,
|
copyparty/u2idx.py,
|
||||||
copyparty/up2k.py,
|
copyparty/up2k.py,
|
||||||
copyparty/util.py,
|
copyparty/util.py,
|
||||||
|
copyparty/vend,
|
||||||
|
copyparty/vend/asynchat.py,
|
||||||
|
copyparty/vend/asyncore.py,
|
||||||
copyparty/web,
|
copyparty/web,
|
||||||
copyparty/web/baguettebox.js,
|
copyparty/web/baguettebox.js,
|
||||||
copyparty/web/browser.css,
|
copyparty/web/browser.css,
|
||||||
|
|||||||
@@ -342,14 +342,15 @@ def get_payload():
|
|||||||
|
|
||||||
|
|
||||||
def utime(top):
|
def utime(top):
|
||||||
|
# avoid cleaners
|
||||||
i = 0
|
i = 0
|
||||||
files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
|
files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
|
||||||
while WINDOWS:
|
while WINDOWS or os.path.exists("/etc/systemd"):
|
||||||
t = int(time.time())
|
t = int(time.time())
|
||||||
if i:
|
if i:
|
||||||
msg("utime {}, {}".format(i, t))
|
msg("utime {}, {}".format(i, t))
|
||||||
|
|
||||||
for f in files:
|
for f in [top] + files:
|
||||||
os.utime(f, (t, t))
|
os.utime(f, (t, t))
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
@@ -368,28 +369,18 @@ def confirm(rv):
|
|||||||
sys.exit(rv or 1)
|
sys.exit(rv or 1)
|
||||||
|
|
||||||
|
|
||||||
def run(tmp, j2):
|
def run(tmp, j2, ftp):
|
||||||
msg("jinja2:", j2 or "bundled")
|
msg("jinja2:", j2 or "bundled")
|
||||||
|
msg("pyftpd:", ftp or "bundled")
|
||||||
msg("sfxdir:", tmp)
|
msg("sfxdir:", tmp)
|
||||||
msg()
|
msg()
|
||||||
|
|
||||||
# block systemd-tmpfiles-clean.timer
|
|
||||||
try:
|
|
||||||
import fcntl
|
|
||||||
|
|
||||||
fd = os.open(tmp, os.O_RDONLY)
|
|
||||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
||||||
except Exception as ex:
|
|
||||||
if not WINDOWS:
|
|
||||||
msg("\033[31mflock:{!r}\033[0m".format(ex))
|
|
||||||
|
|
||||||
t = threading.Thread(target=utime, args=(tmp,))
|
t = threading.Thread(target=utime, args=(tmp,))
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
ld = [tmp, os.path.join(tmp, "dep-j2")]
|
ld = (("", ""), (j2, "dep-j2"), (ftp, "dep-ftp"))
|
||||||
if j2:
|
ld = [os.path.join(tmp, b) for a, b in ld if not a]
|
||||||
del ld[-1]
|
|
||||||
|
|
||||||
if any([re.match(r"^-.*j[0-9]", x) for x in sys.argv]):
|
if any([re.match(r"^-.*j[0-9]", x) for x in sys.argv]):
|
||||||
run_s(ld)
|
run_s(ld)
|
||||||
@@ -462,7 +453,12 @@ def main():
|
|||||||
j2 = None
|
j2 = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
run(tmp, j2)
|
from pyftpdlib.__init__ import __ver__ as ftp
|
||||||
|
except:
|
||||||
|
ftp = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(tmp, j2, ftp)
|
||||||
except SystemExit as ex:
|
except SystemExit as ex:
|
||||||
c = ex.code
|
c = ex.code
|
||||||
if c not in [0, -15]:
|
if c not in [0, -15]:
|
||||||
|
|||||||
8
setup.py
8
setup.py
@@ -112,7 +112,13 @@ args = {
|
|||||||
"data_files": data_files,
|
"data_files": data_files,
|
||||||
"packages": find_packages(),
|
"packages": find_packages(),
|
||||||
"install_requires": ["jinja2"],
|
"install_requires": ["jinja2"],
|
||||||
"extras_require": {"thumbnails": ["Pillow"], "audiotags": ["mutagen"]},
|
"extras_require": {
|
||||||
|
"thumbnails": ["Pillow"],
|
||||||
|
"thumbnails2": ["pyvips"],
|
||||||
|
"audiotags": ["mutagen"],
|
||||||
|
"ftpd": ["pyftpdlib"],
|
||||||
|
"ftps": ["pyftpdlib", "pyopenssl"],
|
||||||
|
},
|
||||||
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
|
"entry_points": {"console_scripts": ["copyparty = copyparty.__main__:main"]},
|
||||||
"scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"],
|
"scripts": ["bin/copyparty-fuse.py", "bin/up2k.py"],
|
||||||
"cmdclass": {"clean2": clean2},
|
"cmdclass": {"clean2": clean2},
|
||||||
|
|||||||
@@ -52,9 +52,12 @@ class Cfg(Namespace):
|
|||||||
mth="",
|
mth="",
|
||||||
textfiles="",
|
textfiles="",
|
||||||
doctitle="",
|
doctitle="",
|
||||||
|
html_head="",
|
||||||
hist=None,
|
hist=None,
|
||||||
no_idx=None,
|
no_idx=None,
|
||||||
no_hash=None,
|
no_hash=None,
|
||||||
|
force_js=False,
|
||||||
|
no_robots=False,
|
||||||
js_browser=None,
|
js_browser=None,
|
||||||
css_browser=None,
|
css_browser=None,
|
||||||
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr no_acode".split()}
|
**{k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr no_acode".split()}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ 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"
|
ex = "nw e2d e2ds e2dsa e2t e2ts e2tsr no_logues no_readme no_acode force_js no_robots"
|
||||||
ex = {k: False for k in ex.split()}
|
ex = {k: False for k in ex.split()}
|
||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
"mte": "a",
|
"mte": "a",
|
||||||
"mth": "",
|
"mth": "",
|
||||||
"doctitle": "",
|
"doctitle": "",
|
||||||
|
"html_head": "",
|
||||||
"hist": None,
|
"hist": None,
|
||||||
"no_idx": None,
|
"no_idx": None,
|
||||||
"no_hash": None,
|
"no_hash": None,
|
||||||
|
|||||||
Reference in New Issue
Block a user