Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dacca18863 | ||
|
|
53d92cc0a6 | ||
|
|
434823f6f0 | ||
|
|
2cb1f50370 | ||
|
|
03f53f6392 | ||
|
|
a70ecd7af0 | ||
|
|
8b81e58205 | ||
|
|
4500c04edf | ||
|
|
6222ddd720 | ||
|
|
8a7135cf41 | ||
|
|
b4c7282956 | ||
|
|
8491a40a04 | ||
|
|
343d38b693 | ||
|
|
6cf53d7364 | ||
|
|
b070d44de7 | ||
|
|
79aa40fdea | ||
|
|
dcaff2785f | ||
|
|
497f5b4307 | ||
|
|
be32ad0da6 | ||
|
|
8ee2bf810b | ||
|
|
28232656a9 | ||
|
|
fbc2424e8f | ||
|
|
94cd13e8b8 | ||
|
|
447ed5ab37 | ||
|
|
af59808611 | ||
|
|
e3406a9f86 | ||
|
|
7fd1d6a4e8 | ||
|
|
0ab2a665de | ||
|
|
3895575bc2 | ||
|
|
138c2bbcbb | ||
|
|
bc7af1d1c8 | ||
|
|
19cd96e392 | ||
|
|
db194ab519 | ||
|
|
02ad4bfab2 | ||
|
|
56b73dcc8a | ||
|
|
7704b9c8a2 | ||
|
|
999b7ae919 | ||
|
|
252b5a88b1 | ||
|
|
01e2681a07 | ||
|
|
aa32f30202 | ||
|
|
195eb53995 | ||
|
|
06fa78f54a | ||
|
|
7a57c9dbf1 | ||
|
|
bb657bfa85 | ||
|
|
87181726b0 | ||
|
|
f1477a1c14 | ||
|
|
4f94a9e38b | ||
|
|
fbed322d3b | ||
|
|
9b0f519e4e | ||
|
|
6cd6dadd06 | ||
|
|
9a28afcb48 | ||
|
|
45b701801d | ||
|
|
062246fb12 | ||
|
|
416ebfdd68 | ||
|
|
731eb92f33 |
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@@ -8,6 +8,7 @@
|
|||||||
"module": "copyparty",
|
"module": "copyparty",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
|
"justMyCode": false,
|
||||||
"args": [
|
"args": [
|
||||||
//"-nw",
|
//"-nw",
|
||||||
"-ed",
|
"-ed",
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -52,9 +52,11 @@
|
|||||||
"--disable=missing-module-docstring",
|
"--disable=missing-module-docstring",
|
||||||
"--disable=missing-class-docstring",
|
"--disable=missing-class-docstring",
|
||||||
"--disable=missing-function-docstring",
|
"--disable=missing-function-docstring",
|
||||||
|
"--disable=import-outside-toplevel",
|
||||||
"--disable=wrong-import-position",
|
"--disable=wrong-import-position",
|
||||||
"--disable=raise-missing-from",
|
"--disable=raise-missing-from",
|
||||||
"--disable=bare-except",
|
"--disable=bare-except",
|
||||||
|
"--disable=broad-except",
|
||||||
"--disable=invalid-name",
|
"--disable=invalid-name",
|
||||||
"--disable=line-too-long",
|
"--disable=line-too-long",
|
||||||
"--disable=consider-using-f-string"
|
"--disable=consider-using-f-string"
|
||||||
@@ -64,6 +66,7 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
|
"editor.autoIndent": "keep",
|
||||||
},
|
},
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -79,10 +79,10 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
|
|||||||
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
||||||
* [themes](#themes)
|
* [themes](#themes)
|
||||||
* [complete examples](#complete-examples)
|
* [complete examples](#complete-examples)
|
||||||
|
* [reverse-proxy](#reverse-proxy) - running copyparty next to other websites
|
||||||
* [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
|
||||||
* [mount as drive](#mount-as-drive) - a remote copyparty server as a local filesystem
|
* [mount as drive](#mount-as-drive) - a remote copyparty server as a local filesystem
|
||||||
* [up2k](#up2k) - quick outline of the up2k protocol, see [uploading](#uploading) for the web-client
|
|
||||||
* [performance](#performance) - defaults are usually fine - expect `8 GiB/s` download, `1 GiB/s` upload
|
* [performance](#performance) - defaults are usually fine - expect `8 GiB/s` download, `1 GiB/s` upload
|
||||||
* [client-side](#client-side) - when uploading files
|
* [client-side](#client-side) - when uploading files
|
||||||
* [security](#security) - some notes on hardening
|
* [security](#security) - some notes on hardening
|
||||||
@@ -90,11 +90,7 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
|
|||||||
* [recovering from crashes](#recovering-from-crashes)
|
* [recovering from crashes](#recovering-from-crashes)
|
||||||
* [client crashes](#client-crashes)
|
* [client crashes](#client-crashes)
|
||||||
* [frefox wsod](#frefox-wsod) - firefox 87 can crash during uploads
|
* [frefox wsod](#frefox-wsod) - firefox 87 can crash during uploads
|
||||||
* [HTTP API](#HTTP-API)
|
* [HTTP API](#HTTP-API) - see [devnotes](#./docs/devnotes.md#http-api)
|
||||||
* [read](#read)
|
|
||||||
* [write](#write)
|
|
||||||
* [admin](#admin)
|
|
||||||
* [general](#general)
|
|
||||||
* [dependencies](#dependencies) - mandatory deps
|
* [dependencies](#dependencies) - mandatory deps
|
||||||
* [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)
|
||||||
@@ -128,7 +124,7 @@ you may also want these, especially on servers:
|
|||||||
|
|
||||||
* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service
|
* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service
|
||||||
* [contrib/systemd/prisonparty.service](contrib/systemd/prisonparty.service) to run it in a chroot (for extra security)
|
* [contrib/systemd/prisonparty.service](contrib/systemd/prisonparty.service) to run it in a chroot (for extra security)
|
||||||
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
|
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to [reverse-proxy](#reverse-proxy) behind nginx (for better https)
|
||||||
|
|
||||||
and remember to open the ports you want; here's a complete example including every feature copyparty has to offer:
|
and remember to open the ports you want; here's a complete example including every feature copyparty has to offer:
|
||||||
```
|
```
|
||||||
@@ -234,7 +230,7 @@ browser-specific:
|
|||||||
* Android-Chrome: increase "parallel uploads" for higher speed (android bug)
|
* Android-Chrome: increase "parallel uploads" for higher speed (android bug)
|
||||||
* Android-Firefox: takes a while to select files (their fix for ☝️)
|
* Android-Firefox: takes a while to select files (their fix for ☝️)
|
||||||
* Desktop-Firefox: ~~may use gigabytes of RAM if your files are massive~~ *seems to be OK now*
|
* Desktop-Firefox: ~~may use gigabytes of RAM if your files are massive~~ *seems to be OK now*
|
||||||
* Desktop-Firefox: may stop you from deleting files you've uploaded until you visit `about:memory` and click `Minimize memory usage`
|
* Desktop-Firefox: [may stop you from unplugging USB flashdrives](https://bugzilla.mozilla.org/show_bug.cgi?id=1792598) until you visit `about:memory` and click `Minimize memory usage`
|
||||||
|
|
||||||
server-os-specific:
|
server-os-specific:
|
||||||
* RHEL8 / Rocky8: you can run copyparty using `/usr/libexec/platform-python`
|
* RHEL8 / Rocky8: you can run copyparty using `/usr/libexec/platform-python`
|
||||||
@@ -252,23 +248,15 @@ server-os-specific:
|
|||||||
* Windows: if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
|
* Windows: if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
|
||||||
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
|
* 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
|
||||||
* [the database can get stuck](https://github.com/9001/copyparty/issues/10)
|
|
||||||
* has only happened once but that is once too many
|
|
||||||
* luckily not dangerous for file integrity and doesn't really stop uploads or anything like that
|
|
||||||
* but would really appreciate some logs if anyone ever runs into it again
|
|
||||||
* probably more, pls let me know
|
* probably more, pls let me know
|
||||||
|
|
||||||
## not my bugs
|
## not my bugs
|
||||||
|
|
||||||
* [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
|
* [Chrome issue 1317069](https://bugs.chromium.org/p/chromium/issues/detail?id=1317069) -- if you try to upload a folder which contains symlinks by dragging it into the browser, the symlinked files will not get uploaded
|
||||||
|
|
||||||
* [Chrome issue 1354816](https://bugs.chromium.org/p/chromium/issues/detail?id=1354816) -- chrome may eat all RAM uploading over plaintext http with `mt` enabled
|
* [Chrome issue 1352210](https://bugs.chromium.org/p/chromium/issues/detail?id=1352210) -- plaintext http may be faster at filehashing than https (but also extremely CPU-intensive)
|
||||||
|
|
||||||
* more amusingly, [Chrome issue 1354800](https://bugs.chromium.org/p/chromium/issues/detail?id=1354800) -- chrome may eat all RAM uploading in general (altho you probably won't run into this one)
|
* [Firefox issue 1790500](https://bugzilla.mozilla.org/show_bug.cgi?id=1790500) -- entire browser can crash after uploading ~4000 small files
|
||||||
|
|
||||||
* [Chrome issue 1352210](https://bugs.chromium.org/p/chromium/issues/detail?id=1352210) -- plaintext http may be faster at filehashing than https (but also extremely CPU-intensive and likely to run into the above gc bugs)
|
|
||||||
|
|
||||||
* [Firefox issue 1790500](https://bugzilla.mozilla.org/show_bug.cgi?id=1790500) -- sometimes forgets to close filedescriptors during upload so the browser can crash after ~4000 files
|
|
||||||
|
|
||||||
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
|
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
|
||||||
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
|
* *future workaround:* enable the equalizer, make it all-zero, and set a negative boost to reduce the volume
|
||||||
@@ -724,6 +712,10 @@ uses [ssdp](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol) to
|
|||||||
|
|
||||||
doubleclicking the icon opens the "connect" page which explains how to mount copyparty as a local filesystem
|
doubleclicking the icon opens the "connect" page which explains how to mount copyparty as a local filesystem
|
||||||
|
|
||||||
|
if copyparty does not appear in windows explorer, use `--zsv` to see why:
|
||||||
|
|
||||||
|
* maybe the discovery multicast was sent from an IP which does not intersect with the server subnets
|
||||||
|
|
||||||
|
|
||||||
## qr-code
|
## qr-code
|
||||||
|
|
||||||
@@ -1068,6 +1060,21 @@ see the top of [./copyparty/web/browser.css](./copyparty/web/browser.css) where
|
|||||||
`-lo log/cpp-%Y-%m%d-%H%M%S.txt.xz`
|
`-lo log/cpp-%Y-%m%d-%H%M%S.txt.xz`
|
||||||
|
|
||||||
|
|
||||||
|
## reverse-proxy
|
||||||
|
|
||||||
|
running copyparty next to other websites hosted on an existing webserver such as nginx or apache
|
||||||
|
|
||||||
|
you can either:
|
||||||
|
* give copyparty its own domain or subdomain (recommended)
|
||||||
|
* or do location-based proxying, using `--rp-loc=/stuff` to tell copyparty where it is mounted -- has a slight performance cost and higher chance of bugs
|
||||||
|
* if copyparty says `incorrect --rp-loc or webserver config; expected vpath starting with [...]` it's likely because the webserver is stripping away the proxy location from the request URLs -- see the `ProxyPass` in the apache example below
|
||||||
|
|
||||||
|
example webserver configs:
|
||||||
|
|
||||||
|
* [nginx config](contrib/nginx/copyparty.conf) -- entire domain/subdomain
|
||||||
|
* [apache2 config](contrib/apache/copyparty.conf) -- location-based
|
||||||
|
|
||||||
|
|
||||||
# browser support
|
# browser support
|
||||||
|
|
||||||
TLDR: yes
|
TLDR: yes
|
||||||
@@ -1139,7 +1146,7 @@ interact with copyparty using non-browser clients
|
|||||||
* `(printf 'PUT / HTTP/1.1\r\n\r\n'; cat movie.mkv) >/dev/tcp/127.0.0.1/3923`
|
* `(printf 'PUT / HTTP/1.1\r\n\r\n'; cat movie.mkv) >/dev/tcp/127.0.0.1/3923`
|
||||||
|
|
||||||
* python: [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) is a command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
* python: [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py) is a command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
||||||
* file uploads, file-search, autoresume of aborted/broken uploads
|
* file uploads, file-search, folder sync, autoresume of aborted/broken uploads
|
||||||
* can be downloaded from copyparty: controlpanel -> connect -> [up2k.py](http://127.0.0.1:3923/.cpr/a/up2k.py)
|
* can be downloaded from copyparty: controlpanel -> connect -> [up2k.py](http://127.0.0.1:3923/.cpr/a/up2k.py)
|
||||||
* see [./bin/README.md#up2kpy](bin/README.md#up2kpy)
|
* see [./bin/README.md#up2kpy](bin/README.md#up2kpy)
|
||||||
|
|
||||||
|
|||||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
if you hit something extra juicy pls let me know on either of the following
|
||||||
|
* email -- `copyparty@ocv.ze` except `ze` should be `me`
|
||||||
|
* [mastodon dm](https://layer8.space/@tripflag) -- `@tripflag@layer8.space`
|
||||||
|
* [github private vulnerability report](https://github.com/9001/copyparty/security/advisories/new), wow that form is complicated
|
||||||
|
* [twitter dm](https://twitter.com/tripflag) (if im somehow not banned yet)
|
||||||
|
|
||||||
|
no bug bounties sorry! all i can offer is greetz in the release notes
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# [`up2k.py`](up2k.py)
|
# [`up2k.py`](up2k.py)
|
||||||
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
||||||
* file uploads, file-search, autoresume of aborted/broken uploads
|
* file uploads, file-search, autoresume of aborted/broken uploads
|
||||||
* faster than browsers
|
* sync local folder to server
|
||||||
|
* generally faster than browsers
|
||||||
* if something breaks just restart it
|
* if something breaks just restart it
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -997,7 +997,7 @@ def main():
|
|||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"-cf", metavar="NUM_BLOCKS", type=int, default=nf, help="file cache"
|
"-cf", metavar="NUM_BLOCKS", type=int, default=nf, help="file cache"
|
||||||
)
|
)
|
||||||
ap.add_argument("-a", metavar="PASSWORD", help="password")
|
ap.add_argument("-a", metavar="PASSWORD", help="password or $filepath")
|
||||||
ap.add_argument("-d", action="store_true", help="enable debug")
|
ap.add_argument("-d", action="store_true", help="enable debug")
|
||||||
ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
|
ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
|
||||||
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
||||||
|
|||||||
205
bin/up2k.py
205
bin/up2k.py
@@ -3,14 +3,12 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
up2k.py: upload to copyparty
|
up2k.py: upload to copyparty
|
||||||
2022-11-29, v0.22, ed <irc.rizon.net>, MIT-Licensed
|
2022-12-13, v1.1, ed <irc.rizon.net>, MIT-Licensed
|
||||||
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
|
https://github.com/9001/copyparty/blob/hovudstraum/bin/up2k.py
|
||||||
|
|
||||||
- dependencies: requests
|
- dependencies: requests
|
||||||
- supports python 2.6, 2.7, and 3.3 through 3.11
|
- supports python 2.6, 2.7, and 3.3 through 3.12
|
||||||
|
- if something breaks just try again and it'll autoresume
|
||||||
- almost zero error-handling
|
|
||||||
- but if something breaks just try again and it'll autoresume
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -42,6 +40,7 @@ except ImportError:
|
|||||||
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
m = "requests/2.18.4 urllib3/1.23 chardet/3.0.4 certifi/2020.4.5.1 idna/2.7"
|
||||||
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
m = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
||||||
m = "\n ERROR: need these:\n" + "\n".join(m) + "\n"
|
m = "\n ERROR: need these:\n" + "\n".join(m) + "\n"
|
||||||
|
m += "\n for f in *.whl; do unzip $f; done; rm -r *.dist-info\n"
|
||||||
|
|
||||||
print(m.format(sys.executable))
|
print(m.format(sys.executable))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -262,10 +261,10 @@ def termsize():
|
|||||||
try:
|
try:
|
||||||
import fcntl, termios, struct
|
import fcntl, termios, struct
|
||||||
|
|
||||||
cr = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234"))
|
r = struct.unpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
|
||||||
|
return r[::-1]
|
||||||
except:
|
except:
|
||||||
return
|
return None
|
||||||
return cr
|
|
||||||
|
|
||||||
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
||||||
if not cr:
|
if not cr:
|
||||||
@@ -275,12 +274,11 @@ def termsize():
|
|||||||
os.close(fd)
|
os.close(fd)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if not cr:
|
|
||||||
try:
|
try:
|
||||||
cr = (env["LINES"], env["COLUMNS"])
|
return cr or (int(env["COLUMNS"]), int(env["LINES"]))
|
||||||
except:
|
except:
|
||||||
cr = (25, 80)
|
return 80, 25
|
||||||
return int(cr[1]), int(cr[0])
|
|
||||||
|
|
||||||
|
|
||||||
class CTermsize(object):
|
class CTermsize(object):
|
||||||
@@ -362,26 +360,29 @@ def walkdir(err, top, seen):
|
|||||||
|
|
||||||
seen = seen[:] + [atop]
|
seen = seen[:] + [atop]
|
||||||
for ap, inf in sorted(statdir(err, top)):
|
for ap, inf in sorted(statdir(err, top)):
|
||||||
|
yield ap, inf
|
||||||
if stat.S_ISDIR(inf.st_mode):
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
try:
|
try:
|
||||||
for x in walkdir(err, ap, seen):
|
for x in walkdir(err, ap, seen):
|
||||||
yield x
|
yield x
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
err.append((ap, str(ex)))
|
err.append((ap, str(ex)))
|
||||||
else:
|
|
||||||
yield ap, inf
|
|
||||||
|
|
||||||
|
|
||||||
def walkdirs(err, tops):
|
def walkdirs(err, tops):
|
||||||
"""recursive statdir for a list of tops, yields [top, relpath, stat]"""
|
"""recursive statdir for a list of tops, yields [top, relpath, stat]"""
|
||||||
sep = "{0}".format(os.sep).encode("ascii")
|
sep = "{0}".format(os.sep).encode("ascii")
|
||||||
for top in tops:
|
for top in tops:
|
||||||
|
isdir = os.path.isdir(top)
|
||||||
if top[-1:] == sep:
|
if top[-1:] == sep:
|
||||||
stop = top.rstrip(sep)
|
stop = top.rstrip(sep)
|
||||||
|
yield stop, b"", os.stat(stop)
|
||||||
else:
|
else:
|
||||||
stop = os.path.dirname(top)
|
stop, dn = os.path.split(top)
|
||||||
|
if isdir:
|
||||||
|
yield stop, dn, os.stat(stop)
|
||||||
|
|
||||||
if os.path.isdir(top):
|
if isdir:
|
||||||
for ap, inf in walkdir(err, top, []):
|
for ap, inf in walkdir(err, top, []):
|
||||||
yield stop, ap[len(stop) :].lstrip(sep), inf
|
yield stop, ap[len(stop) :].lstrip(sep), inf
|
||||||
else:
|
else:
|
||||||
@@ -472,14 +473,17 @@ def get_hashlist(file, pcb, mth):
|
|||||||
file.kchunks[k] = [v1, v2]
|
file.kchunks[k] = [v1, v2]
|
||||||
|
|
||||||
|
|
||||||
def handshake(url, file, pw, search):
|
def handshake(ar, file, search):
|
||||||
# type: (str, File, Any, bool) -> tuple[list[str], bool]
|
# type: (argparse.Namespace, File, bool) -> tuple[list[str], bool]
|
||||||
"""
|
"""
|
||||||
performs a handshake with the server; reply is:
|
performs a handshake with the server; reply is:
|
||||||
if search, a list of search results
|
if search, a list of search results
|
||||||
otherwise, a list of chunks to upload
|
otherwise, a list of chunks to upload
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
url = ar.url
|
||||||
|
pw = ar.a
|
||||||
|
|
||||||
req = {
|
req = {
|
||||||
"hash": [x[0] for x in file.cids],
|
"hash": [x[0] for x in file.cids],
|
||||||
"name": file.name,
|
"name": file.name,
|
||||||
@@ -488,11 +492,14 @@ def handshake(url, file, pw, search):
|
|||||||
}
|
}
|
||||||
if search:
|
if search:
|
||||||
req["srch"] = 1
|
req["srch"] = 1
|
||||||
|
elif ar.dr:
|
||||||
|
req["replace"] = True
|
||||||
|
|
||||||
headers = {"Content-Type": "text/plain"} # wtf ed
|
headers = {"Content-Type": "text/plain"} # <=1.5.1 compat
|
||||||
if pw:
|
if pw:
|
||||||
headers["Cookie"] = "=".join(["cppwd", pw])
|
headers["Cookie"] = "=".join(["cppwd", pw])
|
||||||
|
|
||||||
|
file.recheck = False
|
||||||
if file.url:
|
if file.url:
|
||||||
url = file.url
|
url = file.url
|
||||||
elif b"/" in file.rel:
|
elif b"/" in file.rel:
|
||||||
@@ -540,7 +547,7 @@ def handshake(url, file, pw, search):
|
|||||||
|
|
||||||
|
|
||||||
def upload(file, cid, pw):
|
def upload(file, cid, pw):
|
||||||
# type: (File, str, Any) -> None
|
# type: (File, str, str) -> None
|
||||||
"""upload one specific chunk, `cid` (a chunk-hash)"""
|
"""upload one specific chunk, `cid` (a chunk-hash)"""
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
@@ -564,27 +571,20 @@ def upload(file, cid, pw):
|
|||||||
|
|
||||||
class Ctl(object):
|
class Ctl(object):
|
||||||
"""
|
"""
|
||||||
this will be the coordinator which runs everything in parallel
|
the coordinator which runs everything in parallel
|
||||||
(hashing, handshakes, uploads) but right now it's p dumb
|
(hashing, handshakes, uploads)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ar):
|
def _scan(self):
|
||||||
self.ar = ar
|
ar = self.ar
|
||||||
ar.files = [
|
|
||||||
os.path.abspath(os.path.realpath(x.encode("utf-8")))
|
|
||||||
+ (x[-1:] if x[-1:] == os.sep else "").encode("utf-8")
|
|
||||||
for x in ar.files
|
|
||||||
]
|
|
||||||
ar.url = ar.url.rstrip("/") + "/"
|
|
||||||
if "://" not in ar.url:
|
|
||||||
ar.url = "http://" + ar.url
|
|
||||||
|
|
||||||
eprint("\nscanning {0} locations\n".format(len(ar.files)))
|
eprint("\nscanning {0} locations\n".format(len(ar.files)))
|
||||||
|
|
||||||
nfiles = 0
|
nfiles = 0
|
||||||
nbytes = 0
|
nbytes = 0
|
||||||
err = []
|
err = []
|
||||||
for _, _, inf in walkdirs(err, ar.files):
|
for _, _, inf in walkdirs(err, ar.files):
|
||||||
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
|
continue
|
||||||
|
|
||||||
nfiles += 1
|
nfiles += 1
|
||||||
nbytes += inf.st_size
|
nbytes += inf.st_size
|
||||||
|
|
||||||
@@ -606,8 +606,15 @@ class Ctl(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
eprint("found {0} files, {1}\n\n".format(nfiles, humansize(nbytes)))
|
eprint("found {0} files, {1}\n\n".format(nfiles, humansize(nbytes)))
|
||||||
self.nfiles = nfiles
|
return nfiles, nbytes
|
||||||
self.nbytes = nbytes
|
|
||||||
|
def __init__(self, ar, stats=None):
|
||||||
|
self.ar = ar
|
||||||
|
self.stats = stats or self._scan()
|
||||||
|
if not self.stats:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.nfiles, self.nbytes = self.stats
|
||||||
|
|
||||||
if ar.td:
|
if ar.td:
|
||||||
requests.packages.urllib3.disable_warnings()
|
requests.packages.urllib3.disable_warnings()
|
||||||
@@ -651,6 +658,9 @@ class Ctl(object):
|
|||||||
"""minimal basic slow boring fallback codepath"""
|
"""minimal basic slow boring fallback codepath"""
|
||||||
search = self.ar.s
|
search = self.ar.s
|
||||||
for nf, (top, rel, inf) in enumerate(self.filegen):
|
for nf, (top, rel, inf) in enumerate(self.filegen):
|
||||||
|
if stat.S_ISDIR(inf.st_mode) or not rel:
|
||||||
|
continue
|
||||||
|
|
||||||
file = File(top, rel, inf.st_size, inf.st_mtime)
|
file = File(top, rel, inf.st_size, inf.st_mtime)
|
||||||
upath = file.abs.decode("utf-8", "replace")
|
upath = file.abs.decode("utf-8", "replace")
|
||||||
|
|
||||||
@@ -660,7 +670,7 @@ class Ctl(object):
|
|||||||
burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/"
|
burl = self.ar.url[:12] + self.ar.url[8:].split("/")[0] + "/"
|
||||||
while True:
|
while True:
|
||||||
print(" hs...")
|
print(" hs...")
|
||||||
hs, _ = handshake(self.ar.url, file, self.ar.a, search)
|
hs, _ = handshake(self.ar, file, search)
|
||||||
if search:
|
if search:
|
||||||
if hs:
|
if hs:
|
||||||
for hit in hs:
|
for hit in hs:
|
||||||
@@ -688,7 +698,7 @@ class Ctl(object):
|
|||||||
|
|
||||||
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
||||||
for file in self.recheck:
|
for file in self.recheck:
|
||||||
handshake(self.ar.url, file, self.ar.a, search)
|
handshake(self.ar, file, search)
|
||||||
|
|
||||||
def _fancy(self):
|
def _fancy(self):
|
||||||
if VT100:
|
if VT100:
|
||||||
@@ -765,7 +775,7 @@ class Ctl(object):
|
|||||||
|
|
||||||
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
||||||
for file in self.recheck:
|
for file in self.recheck:
|
||||||
handshake(self.ar.url, file, self.ar.a, False)
|
handshake(self.ar, file, False)
|
||||||
|
|
||||||
def cleanup_vt100(self):
|
def cleanup_vt100(self):
|
||||||
ss.scroll_region(None)
|
ss.scroll_region(None)
|
||||||
@@ -778,8 +788,10 @@ class Ctl(object):
|
|||||||
prd = None
|
prd = None
|
||||||
ls = {}
|
ls = {}
|
||||||
for top, rel, inf in self.filegen:
|
for top, rel, inf in self.filegen:
|
||||||
if self.ar.z:
|
isdir = stat.S_ISDIR(inf.st_mode)
|
||||||
rd = os.path.dirname(rel)
|
if self.ar.z or self.ar.drd:
|
||||||
|
rd = rel if isdir else os.path.dirname(rel)
|
||||||
|
srd = rd.decode("utf-8", "replace").replace("\\", "/")
|
||||||
if prd != rd:
|
if prd != rd:
|
||||||
prd = rd
|
prd = rd
|
||||||
headers = {}
|
headers = {}
|
||||||
@@ -788,19 +800,37 @@ class Ctl(object):
|
|||||||
|
|
||||||
ls = {}
|
ls = {}
|
||||||
try:
|
try:
|
||||||
print(" ls ~{0}".format(rd.decode("utf-8", "replace")))
|
print(" ls ~{0}".format(srd))
|
||||||
r = req_ses.get(
|
zb = self.ar.url.encode("utf-8")
|
||||||
self.ar.url.encode("utf-8") + quotep(rd) + b"?ls",
|
zb += quotep(rd.replace(b"\\", b"/"))
|
||||||
headers=headers,
|
r = req_ses.get(zb + b"?ls&dots", headers=headers)
|
||||||
)
|
if not r:
|
||||||
for f in r.json()["files"]:
|
raise Exception("HTTP {}".format(r.status_code))
|
||||||
rfn = f["href"].split("?")[0].encode("utf-8", "replace")
|
|
||||||
ls[unquote(rfn)] = f
|
|
||||||
except:
|
|
||||||
print(" mkdir ~{0}".format(rd.decode("utf-8", "replace")))
|
|
||||||
|
|
||||||
|
j = r.json()
|
||||||
|
for f in j["dirs"] + j["files"]:
|
||||||
|
rfn = f["href"].split("?")[0].rstrip("/")
|
||||||
|
ls[unquote(rfn.encode("utf-8", "replace"))] = f
|
||||||
|
except Exception as ex:
|
||||||
|
print(" mkdir ~{0} ({1})".format(srd, ex))
|
||||||
|
|
||||||
|
if self.ar.drd:
|
||||||
|
dp = os.path.join(top, rd)
|
||||||
|
lnodes = set(os.listdir(dp))
|
||||||
|
bnames = [x for x in ls if x not in lnodes]
|
||||||
|
if bnames:
|
||||||
|
vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
|
||||||
|
names = [x.decode("utf-8", "replace") for x in bnames]
|
||||||
|
locs = [vpath + srd + "/" + x for x in names]
|
||||||
|
print("DELETING ~{0}/#{1}".format(srd, len(names)))
|
||||||
|
req_ses.post(self.ar.url + "?delete", json=locs)
|
||||||
|
|
||||||
|
if isdir:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.ar.z:
|
||||||
rf = ls.get(os.path.basename(rel), None)
|
rf = ls.get(os.path.basename(rel), None)
|
||||||
if rf and rf["sz"] == inf.st_size and abs(rf["ts"] - inf.st_mtime) <= 1:
|
if rf and rf["sz"] == inf.st_size and abs(rf["ts"] - inf.st_mtime) <= 2:
|
||||||
self.nfiles -= 1
|
self.nfiles -= 1
|
||||||
self.nbytes -= inf.st_size
|
self.nbytes -= inf.st_size
|
||||||
continue
|
continue
|
||||||
@@ -850,7 +880,7 @@ class Ctl(object):
|
|||||||
self.handshaker_busy += 1
|
self.handshaker_busy += 1
|
||||||
|
|
||||||
upath = file.abs.decode("utf-8", "replace")
|
upath = file.abs.decode("utf-8", "replace")
|
||||||
hs, sprs = handshake(self.ar.url, file, self.ar.a, search)
|
hs, sprs = handshake(self.ar, file, search)
|
||||||
if search:
|
if search:
|
||||||
if hs:
|
if hs:
|
||||||
for hit in hs:
|
for hit in hs:
|
||||||
@@ -883,6 +913,9 @@ class Ctl(object):
|
|||||||
self.up_c += len(file.cids) - file.up_c
|
self.up_c += len(file.cids) - file.up_c
|
||||||
self.up_b += file.size - file.up_b
|
self.up_b += file.size - file.up_b
|
||||||
|
|
||||||
|
if not file.recheck:
|
||||||
|
self.up_done(file)
|
||||||
|
|
||||||
if hs and file.up_c:
|
if hs and file.up_c:
|
||||||
# some chunks failed
|
# some chunks failed
|
||||||
self.up_c -= len(hs)
|
self.up_c -= len(hs)
|
||||||
@@ -917,7 +950,7 @@ class Ctl(object):
|
|||||||
upload(file, cid, self.ar.a)
|
upload(file, cid, self.ar.a)
|
||||||
except:
|
except:
|
||||||
eprint("upload failed, retrying: {0} #{1}\n".format(file.name, cid[:8]))
|
eprint("upload failed, retrying: {0} #{1}\n".format(file.name, cid[:8]))
|
||||||
pass # handshake will fix it
|
# handshake will fix it
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
sz = file.kchunks[cid][1]
|
sz = file.kchunks[cid][1]
|
||||||
@@ -933,6 +966,10 @@ class Ctl(object):
|
|||||||
self.up_c += 1
|
self.up_c += 1
|
||||||
self.uploader_busy -= 1
|
self.uploader_busy -= 1
|
||||||
|
|
||||||
|
def up_done(self, file):
|
||||||
|
if self.ar.dl:
|
||||||
|
os.unlink(file.abs)
|
||||||
|
|
||||||
|
|
||||||
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
|
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
|
||||||
pass
|
pass
|
||||||
@@ -957,21 +994,73 @@ source file/folder selection uses rsync syntax, meaning that:
|
|||||||
ap.add_argument("url", type=unicode, help="server url, including destination folder")
|
ap.add_argument("url", type=unicode, help="server url, including destination folder")
|
||||||
ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process")
|
ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process")
|
||||||
ap.add_argument("-v", action="store_true", help="verbose")
|
ap.add_argument("-v", action="store_true", help="verbose")
|
||||||
ap.add_argument("-a", metavar="PASSWORD", help="password")
|
ap.add_argument("-a", metavar="PASSWORD", help="password or $filepath")
|
||||||
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
||||||
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
ap.add_argument("--ok", action="store_true", help="continue even if some local files are inaccessible")
|
||||||
|
|
||||||
|
ap = app.add_argument_group("compatibility")
|
||||||
|
ap.add_argument("--cls", action="store_true", help="clear screen before start")
|
||||||
|
ap.add_argument("--ws", action="store_true", help="copyparty is running on windows; wait before deleting files after uploading")
|
||||||
|
|
||||||
|
ap = app.add_argument_group("folder sync")
|
||||||
|
ap.add_argument("--dl", action="store_true", help="delete local files after uploading")
|
||||||
|
ap.add_argument("--dr", action="store_true", help="delete remote files which don't exist locally")
|
||||||
|
ap.add_argument("--drd", action="store_true", help="delete remote files during upload instead of afterwards; reduces peak disk space usage, but will reupload instead of detecting renames")
|
||||||
|
|
||||||
ap = app.add_argument_group("performance tweaks")
|
ap = app.add_argument_group("performance tweaks")
|
||||||
ap.add_argument("-j", type=int, metavar="THREADS", default=4, help="parallel connections")
|
ap.add_argument("-j", type=int, metavar="THREADS", default=4, help="parallel connections")
|
||||||
ap.add_argument("-J", type=int, metavar="THREADS", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
ap.add_argument("-J", type=int, metavar="THREADS", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
|
||||||
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
|
ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
|
||||||
ap.add_argument("--safe", action="store_true", help="use simple fallback approach")
|
ap.add_argument("--safe", action="store_true", help="use simple fallback approach")
|
||||||
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
|
ap.add_argument("-z", action="store_true", help="ZOOMIN' (skip uploading files if they exist at the destination with the ~same last-modified timestamp, so same as yolo / turbo with date-chk but even faster)")
|
||||||
|
|
||||||
ap = app.add_argument_group("tls")
|
ap = app.add_argument_group("tls")
|
||||||
ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
|
ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
|
||||||
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
Ctl(app.parse_args())
|
ar = app.parse_args()
|
||||||
|
if ar.drd:
|
||||||
|
ar.dr = True
|
||||||
|
|
||||||
|
for k in "dl dr drd".split():
|
||||||
|
errs = []
|
||||||
|
if ar.safe and getattr(ar, k):
|
||||||
|
errs.append(k)
|
||||||
|
|
||||||
|
if errs:
|
||||||
|
raise Exception("--safe is incompatible with " + str(errs))
|
||||||
|
|
||||||
|
ar.files = [
|
||||||
|
os.path.abspath(os.path.realpath(x.encode("utf-8")))
|
||||||
|
+ (x[-1:] if x[-1:] == os.sep else "").encode("utf-8")
|
||||||
|
for x in ar.files
|
||||||
|
]
|
||||||
|
|
||||||
|
ar.url = ar.url.rstrip("/") + "/"
|
||||||
|
if "://" not in ar.url:
|
||||||
|
ar.url = "http://" + ar.url
|
||||||
|
|
||||||
|
if ar.a and ar.a.startswith("$"):
|
||||||
|
fn = ar.a[1:]
|
||||||
|
print("reading password from file [{}]".format(fn))
|
||||||
|
with open(fn, "rb") as f:
|
||||||
|
ar.a = f.read().decode("utf-8").strip()
|
||||||
|
|
||||||
|
if ar.cls:
|
||||||
|
print("\x1b\x5b\x48\x1b\x5b\x32\x4a\x1b\x5b\x33\x4a", end="")
|
||||||
|
|
||||||
|
ctl = Ctl(ar)
|
||||||
|
|
||||||
|
if ar.dr and not ar.drd:
|
||||||
|
print("\npass 2/2: delete")
|
||||||
|
if getattr(ctl, "up_br") and ar.ws:
|
||||||
|
# wait for up2k to mtime if there was uploads
|
||||||
|
time.sleep(4)
|
||||||
|
|
||||||
|
ar.drd = True
|
||||||
|
ar.z = True
|
||||||
|
Ctl(ar, ctl.stats)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
15
contrib/apache/copyparty.conf
Normal file
15
contrib/apache/copyparty.conf
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# when running copyparty behind a reverse proxy,
|
||||||
|
# the following arguments are recommended:
|
||||||
|
#
|
||||||
|
# --http-only lower latency on initial connection
|
||||||
|
# -i 127.0.0.1 only accept connections from nginx
|
||||||
|
#
|
||||||
|
# if you are doing location-based proxying (such as `/stuff` below)
|
||||||
|
# you must run copyparty with --rp-loc=stuff
|
||||||
|
#
|
||||||
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
|
|
||||||
|
LoadModule proxy_module modules/mod_proxy.so
|
||||||
|
ProxyPass "/stuff" "http://127.0.0.1:3923/stuff"
|
||||||
|
# do not specify ProxyPassReverse
|
||||||
|
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
#
|
#
|
||||||
# you may also consider adding -j0 for CPU-intensive configurations
|
# you may also consider adding -j0 for CPU-intensive configurations
|
||||||
# (not that i can really think of any good examples)
|
# (not that i can really think of any good examples)
|
||||||
|
#
|
||||||
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
|
|
||||||
upstream cpp {
|
upstream cpp {
|
||||||
server 127.0.0.1:3923;
|
server 127.0.0.1:3923;
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ name="$SVCNAME"
|
|||||||
command_background=true
|
command_background=true
|
||||||
pidfile="/var/run/$SVCNAME.pid"
|
pidfile="/var/run/$SVCNAME.pid"
|
||||||
|
|
||||||
command="/usr/bin/python /usr/local/bin/copyparty-sfx.py"
|
command="/usr/bin/python3 /usr/local/bin/copyparty-sfx.py"
|
||||||
command_args="-q -v /mnt::rw"
|
command_args="-q -v /mnt::rw"
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ except:
|
|||||||
HAVE_SSL = False
|
HAVE_SSL = False
|
||||||
|
|
||||||
printed: list[str] = []
|
printed: list[str] = []
|
||||||
|
u = unicode
|
||||||
|
|
||||||
|
|
||||||
class RiceFormatter(argparse.HelpFormatter):
|
class RiceFormatter(argparse.HelpFormatter):
|
||||||
@@ -238,18 +239,23 @@ def get_srvname() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def ensure_locale() -> None:
|
def ensure_locale() -> None:
|
||||||
|
safe = "en_US.UTF-8"
|
||||||
for x in [
|
for x in [
|
||||||
"en_US.UTF-8",
|
safe,
|
||||||
"English_United States.UTF8",
|
"English_United States.UTF8",
|
||||||
"English_United States.1252",
|
"English_United States.1252",
|
||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
locale.setlocale(locale.LC_ALL, x)
|
locale.setlocale(locale.LC_ALL, x)
|
||||||
lprint("Locale: {}\n".format(x))
|
if x != safe:
|
||||||
break
|
lprint("Locale: {}\n".format(x))
|
||||||
|
return
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
t = "setlocale {} failed,\n sorting and dates might get funky\n"
|
||||||
|
warn(t.format(safe))
|
||||||
|
|
||||||
|
|
||||||
def ensure_cert() -> None:
|
def ensure_cert() -> None:
|
||||||
"""
|
"""
|
||||||
@@ -267,8 +273,8 @@ def ensure_cert() -> None:
|
|||||||
try:
|
try:
|
||||||
if filecmp.cmp(cert_cfg, cert_insec):
|
if filecmp.cmp(cert_cfg, cert_insec):
|
||||||
lprint(
|
lprint(
|
||||||
"\033[33m using default TLS certificate; https will be insecure."
|
"\033[33musing default TLS certificate; https will be insecure."
|
||||||
+ "\033[36m\n certificate location: {}\033[0m\n".format(cert_cfg)
|
+ "\033[36m\ncertificate location: {}\033[0m\n".format(cert_cfg)
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -441,27 +447,8 @@ def showlic() -> None:
|
|||||||
print(f.read().decode("utf-8", "replace"))
|
print(f.read().decode("utf-8", "replace"))
|
||||||
|
|
||||||
|
|
||||||
def run_argparse(
|
def get_sects():
|
||||||
argv: list[str], formatter: Any, retry: bool, nc: int
|
return [
|
||||||
) -> argparse.Namespace:
|
|
||||||
ap = argparse.ArgumentParser(
|
|
||||||
formatter_class=formatter,
|
|
||||||
prog="copyparty",
|
|
||||||
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
fk_salt = unicode(os.path.getmtime(os.path.join(E.cfg, "cert.pem")))
|
|
||||||
except:
|
|
||||||
fk_salt = "hunter2"
|
|
||||||
|
|
||||||
hcores = min(CORES, 3) # 4% faster than 4+ on py3.9 @ r5-4500U
|
|
||||||
|
|
||||||
tty = os.environ.get("TERM", "").lower() == "linux"
|
|
||||||
|
|
||||||
srvname = get_srvname()
|
|
||||||
|
|
||||||
sects = [
|
|
||||||
[
|
[
|
||||||
"accounts",
|
"accounts",
|
||||||
"accounts and volumes",
|
"accounts and volumes",
|
||||||
@@ -540,6 +527,7 @@ def run_argparse(
|
|||||||
\033[36mnohash=\\.iso$\033[35m skips hashing file contents if path matches *.iso
|
\033[36mnohash=\\.iso$\033[35m skips hashing file contents if path matches *.iso
|
||||||
\033[36mnoidx=\\.iso$\033[35m fully ignores the contents at paths matching *.iso
|
\033[36mnoidx=\\.iso$\033[35m fully ignores the contents at paths matching *.iso
|
||||||
\033[36mnoforget$\033[35m don't forget files when deleted from disk
|
\033[36mnoforget$\033[35m don't forget files when deleted from disk
|
||||||
|
\033[36mdbd=[acid|swal|wal|yolo]\033[35m database speed-durability tradeoff
|
||||||
\033[36mxlink$\033[35m cross-volume dupe detection / linking
|
\033[36mxlink$\033[35m cross-volume dupe detection / linking
|
||||||
\033[36mxdev\033[35m do not descend into other filesystems
|
\033[36mxdev\033[35m do not descend into other filesystems
|
||||||
\033[36mxvol\033[35m skip symlinks leaving the volume root
|
\033[36mxvol\033[35m skip symlinks leaving the volume root
|
||||||
@@ -600,10 +588,32 @@ def run_argparse(
|
|||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"dbd",
|
||||||
|
"database durability profiles",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
mainly affects uploads of many small files on slow HDDs; speeds measured uploading 520 files on a WD20SPZX (SMR 2.5" 5400rpm 4kb)
|
||||||
|
|
||||||
|
\033[32macid\033[0m = extremely safe but slow; the old default. Should never lose any data no matter what
|
||||||
|
|
||||||
|
\033[32mswal\033[0m = 2.4x faster uploads yet 99.9%% as safe -- theoretical chance of losing metadata for the ~200 most recently uploaded files if there's a power-loss or your OS crashes
|
||||||
|
|
||||||
|
\033[32mwal\033[0m = another 21x faster on HDDs yet 90%% as safe; same pitfall as \033[33mswal\033[0m except more likely
|
||||||
|
|
||||||
|
\033[32myolo\033[0m = another 1.5x faster, and removes the occasional sudden upload-pause while the disk syncs, but now you're at risk of losing the entire database in a powerloss / OS-crash
|
||||||
|
|
||||||
|
profiles can be set globally (--dbd=yolo), or per-volume with volflags: -v ~/Music:music:r:c,dbd=acid
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
u = unicode
|
# fmt: off
|
||||||
|
|
||||||
|
|
||||||
|
def add_general(ap, nc, srvname):
|
||||||
ap2 = ap.add_argument_group('general options')
|
ap2 = ap.add_argument_group('general options')
|
||||||
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
|
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
|
||||||
ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
|
ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
|
||||||
@@ -619,6 +629,8 @@ def run_argparse(
|
|||||||
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
||||||
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
||||||
|
|
||||||
|
|
||||||
|
def add_qr(ap, tty):
|
||||||
ap2 = ap.add_argument_group('qr options')
|
ap2 = ap.add_argument_group('qr options')
|
||||||
ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup")
|
ap2.add_argument("--qr", action="store_true", help="show http:// QR-code on startup")
|
||||||
ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup")
|
ap2.add_argument("--qrs", action="store_true", help="show https:// QR-code on startup")
|
||||||
@@ -629,6 +641,8 @@ def run_argparse(
|
|||||||
ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)")
|
ap2.add_argument("--qrp", metavar="CELLS", type=int, default=4, help="padding (spec says 4 or more, but 1 is usually fine)")
|
||||||
ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
|
ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
|
||||||
|
|
||||||
|
|
||||||
|
def add_upload(ap):
|
||||||
ap2 = ap.add_argument_group('upload options')
|
ap2 = ap.add_argument_group('upload options')
|
||||||
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless -ed")
|
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless -ed")
|
||||||
ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
|
ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
|
||||||
@@ -648,14 +662,22 @@ def run_argparse(
|
|||||||
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
|
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
|
||||||
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
|
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
|
||||||
|
|
||||||
|
|
||||||
|
def add_network(ap):
|
||||||
ap2 = ap.add_argument_group('network options')
|
ap2 = ap.add_argument_group('network options')
|
||||||
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6")
|
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6")
|
||||||
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
||||||
|
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 even if the NIC has routable IPs (breaks some mdns clients)")
|
||||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd), [\033[32m2\033[0m]=cloudflare, [\033[32m3\033[0m]=nginx, [\033[32m-1\033[0m]=closest proxy")
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd), [\033[32m2\033[0m]=cloudflare, [\033[32m3\033[0m]=nginx, [\033[32m-1\033[0m]=closest proxy")
|
||||||
|
ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here (eg. /foo/bar)")
|
||||||
|
if ANYWIN:
|
||||||
|
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
|
||||||
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
||||||
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
|
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
|
||||||
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
|
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def add_tls(ap):
|
||||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
|
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
|
||||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
|
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
|
||||||
@@ -664,17 +686,21 @@ def run_argparse(
|
|||||||
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 for later decryption in wireshark")
|
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets for later decryption in wireshark")
|
||||||
|
|
||||||
|
|
||||||
|
def add_zeroconf(ap):
|
||||||
ap2 = ap.add_argument_group("Zeroconf options")
|
ap2 = ap.add_argument_group("Zeroconf options")
|
||||||
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
||||||
ap2.add_argument("--z-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes\n └─example: \033[32meth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96\033[0m")
|
ap2.add_argument("--z-on", metavar="NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes\n └─example: \033[32meth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96\033[0m")
|
||||||
ap2.add_argument("--z-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
|
ap2.add_argument("--z-off", metavar="NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
|
||||||
ap2.add_argument("-zv", action="store_true", help="verbose all zeroconf backends")
|
ap2.add_argument("-zv", action="store_true", help="verbose all zeroconf backends")
|
||||||
ap2.add_argument("--mc-hop", metavar="SEC", type=int, default=0, help="rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [\033[32m300\033[0m] or [\033[32m180\033[0m]")
|
ap2.add_argument("--mc-hop", metavar="SEC", type=int, default=0, help="rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [\033[32m300\033[0m] or [\033[32m180\033[0m]")
|
||||||
|
|
||||||
|
|
||||||
|
def add_zc_mdns(ap):
|
||||||
ap2 = ap.add_argument_group("Zeroconf-mDNS options:")
|
ap2 = ap.add_argument_group("Zeroconf-mDNS options:")
|
||||||
ap2.add_argument("--zm", action="store_true", help="announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ...")
|
ap2.add_argument("--zm", action="store_true", help="announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ...")
|
||||||
ap2.add_argument("--zm-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
|
ap2.add_argument("--zm-on", metavar="NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
|
||||||
ap2.add_argument("--zm-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
|
ap2.add_argument("--zm-off", metavar="NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
|
||||||
ap2.add_argument("--zm4", action="store_true", help="IPv4 only -- try this if some clients can't connect")
|
ap2.add_argument("--zm4", action="store_true", help="IPv4 only -- try this if some clients can't connect")
|
||||||
ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
|
ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
|
||||||
ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
|
ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
|
||||||
@@ -688,14 +714,18 @@ def run_argparse(
|
|||||||
ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working")
|
ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working")
|
||||||
ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
|
ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
|
||||||
|
|
||||||
|
|
||||||
|
def add_zc_ssdp(ap):
|
||||||
ap2 = ap.add_argument_group("Zeroconf-SSDP options:")
|
ap2 = ap.add_argument_group("Zeroconf-SSDP options:")
|
||||||
ap2.add_argument("--zs", action="store_true", help="announce the enabled protocols over SSDP -- compatible with Windows")
|
ap2.add_argument("--zs", action="store_true", help="announce the enabled protocols over SSDP -- compatible with Windows")
|
||||||
ap2.add_argument("--zs-on", metavar="NICS/NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
|
ap2.add_argument("--zs-on", metavar="NETS", type=u, default="", help="enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes")
|
||||||
ap2.add_argument("--zs-off", metavar="NICS/NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
|
ap2.add_argument("--zs-off", metavar="NETS", type=u, default="", help="disable zeroconf on the comma-separated list of subnets and/or interface names/indexes")
|
||||||
ap2.add_argument("--zsv", action="store_true", help="verbose SSDP")
|
ap2.add_argument("--zsv", action="store_true", help="verbose SSDP")
|
||||||
ap2.add_argument("--zsl", metavar="PATH", type=u, default="/?hc", help="location to include in the url (or a complete external URL), for example [\033[32mpriv/?pw=hunter2\033[0m] or [\033[32mpriv/?pw=hunter2\033[0m]")
|
ap2.add_argument("--zsl", metavar="PATH", type=u, default="/?hc", help="location to include in the url (or a complete external URL), for example [\033[32mpriv/?pw=hunter2\033[0m] (goes directly to /priv/ with password hunter2) or [\033[32m?hc=priv&pw=hunter2\033[0m] (shows mounting options for /priv/ with password)")
|
||||||
ap2.add_argument("--zsid", metavar="UUID", type=u, default=uuid.uuid4().urn[4:], help="USN (device identifier) to announce")
|
ap2.add_argument("--zsid", metavar="UUID", type=u, default=uuid.uuid4().urn[4:], help="USN (device identifier) to announce")
|
||||||
|
|
||||||
|
|
||||||
|
def add_ftp(ap):
|
||||||
ap2 = ap.add_argument_group('FTP options')
|
ap2 = ap.add_argument_group('FTP options')
|
||||||
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example \033[32m3921")
|
ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on PORT, for example \033[32m3921")
|
||||||
ap2.add_argument("--ftps", metavar="PORT", type=int, help="enable FTPS server on PORT, for example \033[32m3990")
|
ap2.add_argument("--ftps", metavar="PORT", type=int, help="enable FTPS server on PORT, for example \033[32m3990")
|
||||||
@@ -704,11 +734,15 @@ def run_argparse(
|
|||||||
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections")
|
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 \033[32m12000-13000")
|
ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example \033[32m12000-13000")
|
||||||
|
|
||||||
|
|
||||||
|
def add_webdav(ap):
|
||||||
ap2 = ap.add_argument_group('WebDAV options')
|
ap2 = ap.add_argument_group('WebDAV options')
|
||||||
ap2.add_argument("--daw", action="store_true", help="enable full write support. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)")
|
ap2.add_argument("--daw", action="store_true", help="enable full write support. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)")
|
||||||
ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this")
|
ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this")
|
||||||
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
|
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
|
||||||
|
|
||||||
|
|
||||||
|
def add_smb(ap):
|
||||||
ap2 = ap.add_argument_group('SMB/CIFS options')
|
ap2 = ap.add_argument_group('SMB/CIFS options')
|
||||||
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is dangerous! Never expose to the internet. Account permissions are coalesced; if one account has write-access to a volume, then all accounts do.")
|
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is dangerous! Never expose to the internet. Account permissions are coalesced; if one account has write-access to a volume, then all accounts do.")
|
||||||
ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)")
|
ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)")
|
||||||
@@ -720,6 +754,8 @@ def run_argparse(
|
|||||||
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
||||||
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
||||||
|
|
||||||
|
|
||||||
|
def add_optouts(ap):
|
||||||
ap2 = ap.add_argument_group('opt-outs')
|
ap2 = ap.add_argument_group('opt-outs')
|
||||||
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
|
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
|
||||||
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection which will deadlock copyparty)")
|
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection which will deadlock copyparty)")
|
||||||
@@ -731,6 +767,8 @@ def run_argparse(
|
|||||||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||||
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (as specified by the 'lifetime' volflag)")
|
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (as specified by the 'lifetime' volflag)")
|
||||||
|
|
||||||
|
|
||||||
|
def add_safety(ap, fk_salt):
|
||||||
ap2 = ap.add_argument_group('safety options')
|
ap2 = ap.add_argument_group('safety options')
|
||||||
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
|
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
|
||||||
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --no-dot-mv --no-dot-ren --unpost=0 --no-del --no-mv --hardlink --vague-403 --ban-404=50,60,1440 -nih")
|
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --no-dot-mv --no-dot-ren --unpost=0 --no-del --no-mv --hardlink --vague-403 --ban-404=50,60,1440 -nih")
|
||||||
@@ -751,11 +789,15 @@ def run_argparse(
|
|||||||
ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0")
|
ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0")
|
||||||
ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for B minutes; disable with [\033[32m0\033[0m]")
|
ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for B minutes; disable with [\033[32m0\033[0m]")
|
||||||
|
|
||||||
|
|
||||||
|
def add_shutdown(ap):
|
||||||
ap2 = ap.add_argument_group('shutdown options')
|
ap2 = ap.add_argument_group('shutdown 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")
|
||||||
ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all")
|
ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all")
|
||||||
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after WHEN has finished; for example [\033[32midx\033[0m] will do volume indexing + metadata analysis")
|
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after WHEN has finished; for example [\033[32midx\033[0m] will do volume indexing + metadata analysis")
|
||||||
|
|
||||||
|
|
||||||
|
def add_logging(ap):
|
||||||
ap2 = ap.add_argument_group('logging options')
|
ap2 = ap.add_argument_group('logging options')
|
||||||
ap2.add_argument("-q", action="store_true", help="quiet")
|
ap2.add_argument("-q", action="store_true", help="quiet")
|
||||||
ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
|
ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
|
||||||
@@ -765,11 +807,15 @@ def run_argparse(
|
|||||||
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
||||||
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching")
|
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching")
|
||||||
|
|
||||||
|
|
||||||
|
def add_admin(ap):
|
||||||
ap2 = ap.add_argument_group('admin panel options')
|
ap2 = ap.add_argument_group('admin panel options')
|
||||||
ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
|
ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
|
||||||
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
||||||
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
|
ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
|
||||||
|
|
||||||
|
|
||||||
|
def add_thumbnail(ap):
|
||||||
ap2 = ap.add_argument_group('thumbnail options')
|
ap2 = ap.add_argument_group('thumbnail options')
|
||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
||||||
@@ -796,10 +842,14 @@ def run_argparse(
|
|||||||
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-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.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")
|
||||||
|
|
||||||
|
|
||||||
|
def add_transcoding(ap):
|
||||||
ap2 = ap.add_argument_group('transcoding options')
|
ap2 = ap.add_argument_group('transcoding options')
|
||||||
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
||||||
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after SEC seconds")
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after SEC seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def add_db_general(ap, hcores):
|
||||||
ap2 = ap.add_argument_group('general db options')
|
ap2 = ap.add_argument_group('general db options')
|
||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplocation")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database, making files searchable + enables upload deduplocation")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets -e2d")
|
ap2.add_argument("-e2ds", action="store_true", help="scan writable folders for new files on startup; sets -e2d")
|
||||||
@@ -811,7 +861,9 @@ def run_argparse(
|
|||||||
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans (volflag=nohash)")
|
ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching paths during e2ds folder scans (volflag=nohash)")
|
||||||
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans (volflag=noidx)")
|
ap2.add_argument("--no-idx", metavar="PTN", type=u, help="regex: disable indexing of matching paths during e2ds folder scans (volflag=noidx)")
|
||||||
ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
|
ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
|
||||||
|
ap2.add_argument("--re-dhash", action="store_true", help="rebuild the cache if it gets out of sync (for example crash on startup during metadata scanning)")
|
||||||
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice (volflag=noforget)")
|
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice (volflag=noforget)")
|
||||||
|
ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see --help-dbd (volflag=dbd)")
|
||||||
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
|
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
|
||||||
ap2.add_argument("--xdev", action="store_true", help="do not descend into other filesystems (symlink or bind-mount to another HDD, ...) (volflag=xdev)")
|
ap2.add_argument("--xdev", action="store_true", help="do not descend into other filesystems (symlink or bind-mount to another HDD, ...) (volflag=xdev)")
|
||||||
ap2.add_argument("--xvol", action="store_true", help="skip symlinks leaving the volume root (volflag=xvol)")
|
ap2.add_argument("--xvol", action="store_true", help="skip symlinks leaving the volume root (volflag=xvol)")
|
||||||
@@ -821,6 +873,8 @@ def run_argparse(
|
|||||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than SEC seconds")
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than SEC seconds")
|
||||||
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
|
||||||
|
|
||||||
|
|
||||||
|
def add_db_metadata(ap):
|
||||||
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; makes it possible to search for artist/title/codec/resolution/...")
|
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing; makes it possible to search for artist/title/codec/resolution/...")
|
||||||
ap2.add_argument("-e2ts", action="store_true", help="scan existing files on startup; sets -e2t")
|
ap2.add_argument("-e2ts", action="store_true", help="scan existing files on startup; sets -e2t")
|
||||||
@@ -838,11 +892,14 @@ def run_argparse(
|
|||||||
default=".vq,.aq,vc,ac,fmt,res,.fps")
|
default=".vq,.aq,vc,ac,fmt,res,.fps")
|
||||||
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
|
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
|
||||||
|
|
||||||
|
|
||||||
|
def add_ui(ap, retry):
|
||||||
ap2 = ap.add_argument_group('ui options')
|
ap2 = ap.add_argument_group('ui options')
|
||||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language")
|
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language")
|
||||||
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
|
||||||
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
||||||
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
||||||
|
ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
|
||||||
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
||||||
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
||||||
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
|
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
|
||||||
@@ -850,6 +907,8 @@ def run_argparse(
|
|||||||
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
||||||
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")
|
||||||
|
|
||||||
|
|
||||||
|
def add_debug(ap):
|
||||||
ap2 = ap.add_argument_group('debug options')
|
ap2 = ap.add_argument_group('debug options')
|
||||||
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile; instead using a traditional file read loop")
|
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile; instead using a traditional file read loop")
|
||||||
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir; instead using listdir + stat on each file")
|
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir; instead using listdir + stat on each file")
|
||||||
@@ -862,9 +921,56 @@ def run_argparse(
|
|||||||
ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir")
|
ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir")
|
||||||
ap2.add_argument("--bf-nc", metavar="NUM", type=int, default=200, help="bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M)")
|
ap2.add_argument("--bf-nc", metavar="NUM", type=int, default=200, help="bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M)")
|
||||||
ap2.add_argument("--bf-dir", metavar="PATH", type=u, default="bf", help="bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started")
|
ap2.add_argument("--bf-dir", metavar="PATH", type=u, default="bf", help="bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started")
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
def run_argparse(
|
||||||
|
argv: list[str], formatter: Any, retry: bool, nc: int
|
||||||
|
) -> argparse.Namespace:
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
formatter_class=formatter,
|
||||||
|
prog="copyparty",
|
||||||
|
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fk_salt = unicode(os.path.getmtime(os.path.join(E.cfg, "cert.pem")))
|
||||||
|
except:
|
||||||
|
fk_salt = "hunter2"
|
||||||
|
|
||||||
|
hcores = min(CORES, 4) # optimal on py3.11 @ r5-4500U
|
||||||
|
|
||||||
|
tty = os.environ.get("TERM", "").lower() == "linux"
|
||||||
|
|
||||||
|
srvname = get_srvname()
|
||||||
|
|
||||||
|
add_general(ap, nc, srvname)
|
||||||
|
add_network(ap)
|
||||||
|
add_tls(ap)
|
||||||
|
add_qr(ap, tty)
|
||||||
|
add_zeroconf(ap)
|
||||||
|
add_zc_mdns(ap)
|
||||||
|
add_zc_ssdp(ap)
|
||||||
|
add_upload(ap)
|
||||||
|
add_db_general(ap, hcores)
|
||||||
|
add_db_metadata(ap)
|
||||||
|
add_thumbnail(ap)
|
||||||
|
add_transcoding(ap)
|
||||||
|
add_ftp(ap)
|
||||||
|
add_webdav(ap)
|
||||||
|
add_smb(ap)
|
||||||
|
add_safety(ap, fk_salt)
|
||||||
|
add_optouts(ap)
|
||||||
|
add_shutdown(ap)
|
||||||
|
add_ui(ap, retry)
|
||||||
|
add_admin(ap)
|
||||||
|
add_logging(ap)
|
||||||
|
add_debug(ap)
|
||||||
|
|
||||||
ap2 = ap.add_argument_group("help sections")
|
ap2 = ap.add_argument_group("help sections")
|
||||||
|
sects = get_sects()
|
||||||
for k, h, _ in sects:
|
for k, h, _ in sects:
|
||||||
ap2.add_argument("--help-" + k, action="store_true", help=h)
|
ap2.add_argument("--help-" + k, action="store_true", help=h)
|
||||||
|
|
||||||
@@ -981,8 +1087,11 @@ def main(argv: Optional[list[str]] = None) -> None:
|
|||||||
retry = True
|
retry = True
|
||||||
lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex()))
|
lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex()))
|
||||||
|
|
||||||
assert al # type: ignore
|
try:
|
||||||
al.E = E # __init__ is not shared when oxidized
|
assert al # type: ignore
|
||||||
|
al.E = E # __init__ is not shared when oxidized
|
||||||
|
except:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if WINDOWS and not al.keep_qem:
|
if WINDOWS and not al.keep_qem:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 5, 1)
|
VERSION = (1, 5, 6)
|
||||||
CODENAME = "babel"
|
CODENAME = "babel"
|
||||||
BUILD_DT = (2022, 12, 3)
|
BUILD_DT = (2023, 1, 12)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -370,7 +370,6 @@ class VFS(object):
|
|||||||
|
|
||||||
def _find(self, vpath: str) -> tuple["VFS", str]:
|
def _find(self, vpath: str) -> tuple["VFS", str]:
|
||||||
"""return [vfs,remainder]"""
|
"""return [vfs,remainder]"""
|
||||||
vpath = undot(vpath)
|
|
||||||
if vpath == "":
|
if vpath == "":
|
||||||
return self, ""
|
return self, ""
|
||||||
|
|
||||||
@@ -381,7 +380,7 @@ class VFS(object):
|
|||||||
rem = ""
|
rem = ""
|
||||||
|
|
||||||
if name in self.nodes:
|
if name in self.nodes:
|
||||||
return self.nodes[name]._find(rem)
|
return self.nodes[name]._find(undot(rem))
|
||||||
|
|
||||||
return self, vpath
|
return self, vpath
|
||||||
|
|
||||||
@@ -389,7 +388,7 @@ class VFS(object):
|
|||||||
self, vpath: str, uname: str
|
self, vpath: str, uname: str
|
||||||
) -> tuple[bool, bool, bool, bool, bool, bool]:
|
) -> tuple[bool, bool, bool, bool, bool, bool]:
|
||||||
"""can Read,Write,Move,Delete,Get,Upget"""
|
"""can Read,Write,Move,Delete,Get,Upget"""
|
||||||
vn, _ = self._find(vpath)
|
vn, _ = self._find(undot(vpath))
|
||||||
c = vn.axs
|
c = vn.axs
|
||||||
return (
|
return (
|
||||||
uname in c.uread or "*" in c.uread,
|
uname in c.uread or "*" in c.uread,
|
||||||
@@ -419,7 +418,7 @@ class VFS(object):
|
|||||||
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
||||||
raise Pebkac(404)
|
raise Pebkac(404)
|
||||||
|
|
||||||
vn, rem = self._find(vpath)
|
vn, rem = self._find(undot(vpath))
|
||||||
c: AXS = vn.axs
|
c: AXS = vn.axs
|
||||||
|
|
||||||
for req, d, msg in [
|
for req, d, msg in [
|
||||||
@@ -1136,6 +1135,12 @@ class AuthSrv(object):
|
|||||||
if k1 in vol.flags:
|
if k1 in vol.flags:
|
||||||
vol.flags[k2] = False
|
vol.flags[k2] = False
|
||||||
|
|
||||||
|
dbds = "acid|swal|wal|yolo"
|
||||||
|
vol.flags["dbd"] = dbd = vol.flags.get("dbd") or self.args.dbd
|
||||||
|
if dbd not in dbds.split("|"):
|
||||||
|
t = "invalid dbd [{}]; must be one of [{}]"
|
||||||
|
raise Exception(t.format(dbd, dbds))
|
||||||
|
|
||||||
# default tag cfgs if unset
|
# default tag cfgs if unset
|
||||||
if "mte" not in vol.flags:
|
if "mte" not in vol.flags:
|
||||||
vol.flags["mte"] = self.args.mte
|
vol.flags["mte"] = self.args.mte
|
||||||
|
|||||||
@@ -24,13 +24,15 @@ def listdir(p: str = ".") -> list[str]:
|
|||||||
return [fsdec(x) for x in os.listdir(fsenc(p))]
|
return [fsdec(x) for x in os.listdir(fsenc(p))]
|
||||||
|
|
||||||
|
|
||||||
def makedirs(name: str, mode: int = 0o755, exist_ok: bool = True) -> None:
|
def makedirs(name: str, mode: int = 0o755, exist_ok: bool = True) -> bool:
|
||||||
bname = fsenc(name)
|
bname = fsenc(name)
|
||||||
try:
|
try:
|
||||||
os.makedirs(bname, mode)
|
os.makedirs(bname, mode)
|
||||||
|
return True
|
||||||
except:
|
except:
|
||||||
if not exist_ok or not os.path.isdir(bname):
|
if not exist_ok or not os.path.isdir(bname):
|
||||||
raise
|
raise
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def mkdir(p: str, mode: int = 0o755) -> None:
|
def mkdir(p: str, mode: int = 0o755) -> None:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
@@ -93,12 +94,15 @@ class BrokerMp(object):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# new ipc invoking managed service in hub
|
# new ipc invoking managed service in hub
|
||||||
obj = self.hub
|
try:
|
||||||
for node in dest.split("."):
|
obj = self.hub
|
||||||
obj = getattr(obj, node)
|
for node in dest.split("."):
|
||||||
|
obj = getattr(obj, node)
|
||||||
|
|
||||||
# TODO will deadlock if dest performs another ipc
|
# TODO will deadlock if dest performs another ipc
|
||||||
rv = try_exec(retq_id, obj, *args)
|
rv = try_exec(retq_id, obj, *args)
|
||||||
|
except:
|
||||||
|
rv = ["exception", "stack", traceback.format_exc()]
|
||||||
|
|
||||||
if retq_id:
|
if retq_id:
|
||||||
proc.q_pend.put((retq_id, "retq", rv))
|
proc.q_pend.put((retq_id, "retq", rv))
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ from .util import (
|
|||||||
read_socket_unbounded,
|
read_socket_unbounded,
|
||||||
relchk,
|
relchk,
|
||||||
ren_open,
|
ren_open,
|
||||||
|
hidedir,
|
||||||
s3enc,
|
s3enc,
|
||||||
sanitize_fn,
|
sanitize_fn,
|
||||||
sendfile_kern,
|
sendfile_kern,
|
||||||
@@ -118,6 +119,7 @@ class HttpCli(object):
|
|||||||
# placeholders; assigned by run()
|
# placeholders; assigned by run()
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
self.is_https = False
|
self.is_https = False
|
||||||
|
self.is_vproxied = False
|
||||||
self.in_hdr_recv = True
|
self.in_hdr_recv = True
|
||||||
self.headers: dict[str, str] = {}
|
self.headers: dict[str, str] = {}
|
||||||
self.mode = " "
|
self.mode = " "
|
||||||
@@ -190,6 +192,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
def j2s(self, name: str, **ka: Any) -> str:
|
def j2s(self, name: str, **ka: Any) -> str:
|
||||||
tpl = self.conn.hsrv.j2[name]
|
tpl = self.conn.hsrv.j2[name]
|
||||||
|
ka["r"] = self.args.SR if self.is_vproxied else ""
|
||||||
ka["ts"] = self.conn.hsrv.cachebuster()
|
ka["ts"] = self.conn.hsrv.cachebuster()
|
||||||
ka["lang"] = self.args.lang
|
ka["lang"] = self.args.lang
|
||||||
ka["favico"] = self.args.favico
|
ka["favico"] = self.args.favico
|
||||||
@@ -240,7 +243,7 @@ class HttpCli(object):
|
|||||||
self.http_ver = "HTTP/1.1"
|
self.http_ver = "HTTP/1.1"
|
||||||
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
h = {"WWW-Authenticate": "Basic"} if ex.code == 401 else {}
|
h = {"WWW-Authenticate": 'Basic realm="a"'} if ex.code == 401 else {}
|
||||||
try:
|
try:
|
||||||
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
@@ -252,7 +255,7 @@ class HttpCli(object):
|
|||||||
self.is_ancient = self.ua.startswith("Mozilla/4.")
|
self.is_ancient = self.ua.startswith("Mozilla/4.")
|
||||||
|
|
||||||
zs = self.headers.get("connection", "").lower()
|
zs = self.headers.get("connection", "").lower()
|
||||||
self.keepalive = not zs.startswith("close") and (
|
self.keepalive = "close" not in zs and (
|
||||||
self.http_ver != "HTTP/1.0" or zs == "keep-alive"
|
self.http_ver != "HTTP/1.0" or zs == "keep-alive"
|
||||||
)
|
)
|
||||||
self.is_https = (
|
self.is_https = (
|
||||||
@@ -275,6 +278,7 @@ class HttpCli(object):
|
|||||||
self.log(t.format(self.args.rproxy, zso), c=3)
|
self.log(t.format(self.args.rproxy, zso), c=3)
|
||||||
|
|
||||||
self.log_src = self.conn.set_rproxy(self.ip)
|
self.log_src = self.conn.set_rproxy(self.ip)
|
||||||
|
self.is_vproxied = bool(self.args.R)
|
||||||
|
|
||||||
if self.is_banned():
|
if self.is_banned():
|
||||||
return False
|
return False
|
||||||
@@ -319,6 +323,13 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
uparam[k.lower()] = ""
|
uparam[k.lower()] = ""
|
||||||
|
|
||||||
|
if self.is_vproxied:
|
||||||
|
if vpath.startswith(self.args.R):
|
||||||
|
vpath = vpath[len(self.args.R) + 1 :]
|
||||||
|
else:
|
||||||
|
t = "incorrect --rp-loc or webserver config; expected vpath starting with [{}] but got [{}]"
|
||||||
|
self.log(t.format(self.args.R, vpath), 1)
|
||||||
|
|
||||||
self.ouparam = {k: zs for k, zs in uparam.items()}
|
self.ouparam = {k: zs for k, zs in uparam.items()}
|
||||||
|
|
||||||
if self.args.rsp_slp:
|
if self.args.rsp_slp:
|
||||||
@@ -447,7 +458,7 @@ class HttpCli(object):
|
|||||||
msg += "hint: important info in the server log\r\n"
|
msg += "hint: important info in the server log\r\n"
|
||||||
|
|
||||||
zb = b"<pre>" + html_escape(msg).encode("utf-8", "replace")
|
zb = b"<pre>" + html_escape(msg).encode("utf-8", "replace")
|
||||||
h = {"WWW-Authenticate": "Basic"} if pex.code == 401 else {}
|
h = {"WWW-Authenticate": 'Basic realm="a"'} if pex.code == 401 else {}
|
||||||
self.reply(zb, status=pex.code, headers=h, volsan=True)
|
self.reply(zb, status=pex.code, headers=h, volsan=True)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
@@ -603,10 +614,11 @@ class HttpCli(object):
|
|||||||
status: int = 200,
|
status: int = 200,
|
||||||
use302: bool = False,
|
use302: bool = False,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
vp = self.args.RS + vpath
|
||||||
html = self.j2s(
|
html = self.j2s(
|
||||||
"msg",
|
"msg",
|
||||||
h2='<a href="/{}">{} /{}</a>'.format(
|
h2='<a href="/{}">{} /{}</a>'.format(
|
||||||
quotep(vpath) + suf, flavor, html_escape(vpath, crlf=True) + suf
|
quotep(vp) + suf, flavor, html_escape(vp, crlf=True) + suf
|
||||||
),
|
),
|
||||||
pre=msg,
|
pre=msg,
|
||||||
click=click,
|
click=click,
|
||||||
@@ -641,6 +653,15 @@ class HttpCli(object):
|
|||||||
if self.vpath.startswith(".cpr/ssdp"):
|
if self.vpath.startswith(".cpr/ssdp"):
|
||||||
return self.conn.hsrv.ssdp.reply(self)
|
return self.conn.hsrv.ssdp.reply(self)
|
||||||
|
|
||||||
|
if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
|
||||||
|
if self.args.mpmc == ".":
|
||||||
|
raise Pebkac(404)
|
||||||
|
|
||||||
|
loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
|
||||||
|
h = {"Location": loc, "Cache-Control": "max-age=39"}
|
||||||
|
self.reply(b"", 301, headers=h)
|
||||||
|
return True
|
||||||
|
|
||||||
static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
|
static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
|
||||||
return self.tx_file(static_path)
|
return self.tx_file(static_path)
|
||||||
|
|
||||||
@@ -839,7 +860,7 @@ class HttpCli(object):
|
|||||||
"supportedlock": '<D:lockentry xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>',
|
"supportedlock": '<D:lockentry xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>',
|
||||||
}
|
}
|
||||||
if not isdir:
|
if not isdir:
|
||||||
pvs["getcontenttype"] = guess_mime(rp)
|
pvs["getcontenttype"] = html_escape(guess_mime(rp))
|
||||||
pvs["getcontentlength"] = str(st.st_size)
|
pvs["getcontentlength"] = str(st.st_size)
|
||||||
|
|
||||||
for k, v in pvs.items():
|
for k, v in pvs.items():
|
||||||
@@ -1126,7 +1147,11 @@ class HttpCli(object):
|
|||||||
if "multipart/form-data" in ctype:
|
if "multipart/form-data" in ctype:
|
||||||
return self.handle_post_multipart()
|
return self.handle_post_multipart()
|
||||||
|
|
||||||
if "text/plain" in ctype or "application/xml" in ctype:
|
if (
|
||||||
|
"application/json" in ctype
|
||||||
|
or "text/plain" in ctype
|
||||||
|
or "application/xml" in ctype
|
||||||
|
):
|
||||||
return self.handle_post_json()
|
return self.handle_post_json()
|
||||||
|
|
||||||
if "application/octet-stream" in ctype:
|
if "application/octet-stream" in ctype:
|
||||||
@@ -1293,7 +1318,7 @@ class HttpCli(object):
|
|||||||
if self.args.dotpart:
|
if self.args.dotpart:
|
||||||
tnam = "." + tnam
|
tnam = "." + tnam
|
||||||
|
|
||||||
if ("daw" in vfs.flags and self.can_delete) or (
|
if (vfs.flags.get("daw") and self.can_delete) or (
|
||||||
not bos.path.exists(os.path.join(fdir, tnam))
|
not bos.path.exists(os.path.join(fdir, tnam))
|
||||||
and bos.path.exists(path)
|
and bos.path.exists(path)
|
||||||
and not bos.path.getsize(path)
|
and not bos.path.getsize(path)
|
||||||
@@ -1365,7 +1390,7 @@ class HttpCli(object):
|
|||||||
url = "{}://{}/{}".format(
|
url = "{}://{}/{}".format(
|
||||||
"https" if self.is_https else "http",
|
"https" if self.is_https else "http",
|
||||||
self.headers.get("host") or "{}:{}".format(*list(self.s.getsockname()[:2])),
|
self.headers.get("host") or "{}:{}".format(*list(self.s.getsockname()[:2])),
|
||||||
vpath + vsuf,
|
self.args.RS + vpath + vsuf,
|
||||||
)
|
)
|
||||||
|
|
||||||
return post_sz, sha_hex, sha_b64, remains, path, url
|
return post_sz, sha_hex, sha_b64, remains, path, url
|
||||||
@@ -1530,11 +1555,6 @@ class HttpCli(object):
|
|||||||
if "delete" in self.uparam:
|
if "delete" in self.uparam:
|
||||||
return self.handle_rm(body)
|
return self.handle_rm(body)
|
||||||
|
|
||||||
# up2k-php compat
|
|
||||||
for k in "chunkpit.php", "handshake.php":
|
|
||||||
if self.vpath.endswith(k):
|
|
||||||
self.vpath = self.vpath[: -len(k)]
|
|
||||||
|
|
||||||
name = undot(body["name"])
|
name = undot(body["name"])
|
||||||
if "/" in name:
|
if "/" in name:
|
||||||
raise Pebkac(400, "your client is old; press CTRL-SHIFT-R and try again")
|
raise Pebkac(400, "your client is old; press CTRL-SHIFT-R and try again")
|
||||||
@@ -1548,6 +1568,9 @@ class HttpCli(object):
|
|||||||
body["addr"] = self.ip
|
body["addr"] = self.ip
|
||||||
body["vcfg"] = dbv.flags
|
body["vcfg"] = dbv.flags
|
||||||
|
|
||||||
|
if not self.can_delete:
|
||||||
|
body.pop("replace", None)
|
||||||
|
|
||||||
if rem:
|
if rem:
|
||||||
dst = vfs.canonical(rem)
|
dst = vfs.canonical(rem)
|
||||||
try:
|
try:
|
||||||
@@ -1568,6 +1591,10 @@ class HttpCli(object):
|
|||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_json", body)
|
x = self.conn.hsrv.broker.ask("up2k.handle_json", body)
|
||||||
ret = x.get()
|
ret = x.get()
|
||||||
|
if self.is_vproxied:
|
||||||
|
if "purl" in ret:
|
||||||
|
ret["purl"] = self.args.SR + ret["purl"]
|
||||||
|
|
||||||
ret = json.dumps(ret)
|
ret = json.dumps(ret)
|
||||||
self.log(ret)
|
self.log(ret)
|
||||||
self.reply(ret.encode("utf-8"), mime="application/json")
|
self.reply(ret.encode("utf-8"), mime="application/json")
|
||||||
@@ -1626,6 +1653,10 @@ class HttpCli(object):
|
|||||||
if t not in order:
|
if t not in order:
|
||||||
order.append(t)
|
order.append(t)
|
||||||
|
|
||||||
|
if self.is_vproxied:
|
||||||
|
for hit in hits:
|
||||||
|
hit["rp"] = self.args.RS + hit["rp"]
|
||||||
|
|
||||||
r = json.dumps({"hits": hits, "tag_order": order}).encode("utf-8")
|
r = json.dumps({"hits": hits, "tag_order": order}).encode("utf-8")
|
||||||
self.reply(r, mime="application/json")
|
self.reply(r, mime="application/json")
|
||||||
return True
|
return True
|
||||||
@@ -2024,7 +2055,7 @@ class HttpCli(object):
|
|||||||
)[: vfs.flags["fk"]]
|
)[: vfs.flags["fk"]]
|
||||||
|
|
||||||
vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
|
vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
|
||||||
rel_url = quotep(vpath) + vsuf
|
rel_url = quotep(self.args.RS + vpath) + vsuf
|
||||||
msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
|
msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
|
||||||
sha_hex[:56],
|
sha_hex[:56],
|
||||||
sha_b64,
|
sha_b64,
|
||||||
@@ -2168,7 +2199,9 @@ class HttpCli(object):
|
|||||||
mdir, mfile = os.path.split(fp)
|
mdir, mfile = os.path.split(fp)
|
||||||
mfile2 = "{}.{:.3f}.md".format(mfile[:-3], srv_lastmod)
|
mfile2 = "{}.{:.3f}.md".format(mfile[:-3], srv_lastmod)
|
||||||
try:
|
try:
|
||||||
bos.mkdir(os.path.join(mdir, ".hist"))
|
dp = os.path.join(mdir, ".hist")
|
||||||
|
bos.mkdir(dp)
|
||||||
|
hidedir(dp)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
bos.rename(fp, os.path.join(mdir, ".hist", mfile2))
|
bos.rename(fp, os.path.join(mdir, ".hist", mfile2))
|
||||||
@@ -2527,6 +2560,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
boundary = "\roll\tide"
|
boundary = "\roll\tide"
|
||||||
targs = {
|
targs = {
|
||||||
|
"r": self.args.SR if self.is_vproxied else "",
|
||||||
"ts": self.conn.hsrv.cachebuster(),
|
"ts": self.conn.hsrv.cachebuster(),
|
||||||
"svcname": self.args.doctitle,
|
"svcname": self.args.doctitle,
|
||||||
"html_head": self.html_head,
|
"html_head": self.html_head,
|
||||||
@@ -2685,13 +2719,14 @@ class HttpCli(object):
|
|||||||
def tx_404(self, is_403: bool = False) -> bool:
|
def tx_404(self, is_403: bool = False) -> bool:
|
||||||
rc = 404
|
rc = 404
|
||||||
if self.args.vague_403:
|
if self.args.vague_403:
|
||||||
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
|
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p id="o">or maybe you don\'t have access -- try logging in or <a href="{}/?h">go home</a></p>'
|
||||||
elif is_403:
|
elif is_403:
|
||||||
t = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">you\'ll have to log in or <a href="/?h">go home</a></p>'
|
t = '<h1 id="p">403 forbiddena ~┻━┻</h1><p id="q">you\'ll have to log in or <a href="{}/?h">go home</a></p>'
|
||||||
rc = 403
|
rc = 403
|
||||||
else:
|
else:
|
||||||
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p><a id="r" href="/?h">go home</a></p>'
|
t = '<h1 id="n">404 not found ┐( ´ -`)┌</h1><p><a id="r" href="{}/?h">go home</a></p>'
|
||||||
|
|
||||||
|
t = t.format(self.args.SR)
|
||||||
html = self.j2s("splash", this=self, qvpath=quotep(self.vpath), msg=t)
|
html = self.j2s("splash", this=self, qvpath=quotep(self.vpath), msg=t)
|
||||||
self.reply(html.encode("utf-8"), status=rc)
|
self.reply(html.encode("utf-8"), status=rc)
|
||||||
return True
|
return True
|
||||||
@@ -2755,6 +2790,11 @@ class HttpCli(object):
|
|||||||
dst = dst[len(top) + 1 :]
|
dst = dst[len(top) + 1 :]
|
||||||
|
|
||||||
ret = self.gen_tree(top, dst)
|
ret = self.gen_tree(top, dst)
|
||||||
|
if self.is_vproxied:
|
||||||
|
parents = self.args.R.split("/")
|
||||||
|
for parent in parents[::-1]:
|
||||||
|
ret = {"k{}".format(parent): ret, "a": []}
|
||||||
|
|
||||||
zs = json.dumps(ret)
|
zs = json.dumps(ret)
|
||||||
self.reply(zs.encode("utf-8"), mime="application/json")
|
self.reply(zs.encode("utf-8"), mime="application/json")
|
||||||
return True
|
return True
|
||||||
@@ -2865,6 +2905,11 @@ class HttpCli(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
ret = ret[:2000]
|
ret = ret[:2000]
|
||||||
|
|
||||||
|
if self.is_vproxied:
|
||||||
|
for v in ret:
|
||||||
|
v["vp"] = self.args.SR + v["vp"]
|
||||||
|
|
||||||
jtxt = json.dumps(ret, indent=2, sort_keys=True).encode("utf-8", "replace")
|
jtxt = json.dumps(ret, indent=2, sort_keys=True).encode("utf-8", "replace")
|
||||||
self.log("{} #{} {:.2f}sec".format(lm, len(ret), time.time() - t0))
|
self.log("{} #{} {:.2f}sec".format(lm, len(ret), time.time() - t0))
|
||||||
self.reply(jtxt, mime="application/json")
|
self.reply(jtxt, mime="application/json")
|
||||||
@@ -2879,6 +2924,8 @@ class HttpCli(object):
|
|||||||
|
|
||||||
if not req:
|
if not req:
|
||||||
req = [self.vpath]
|
req = [self.vpath]
|
||||||
|
elif self.is_vproxied:
|
||||||
|
req = [x[len(self.args.SR) :] for x in req]
|
||||||
|
|
||||||
nlim = int(self.uparam.get("lim") or 0)
|
nlim = int(self.uparam.get("lim") or 0)
|
||||||
lim = [nlim, nlim] if nlim else []
|
lim = [nlim, nlim] if nlim else []
|
||||||
@@ -2890,6 +2937,10 @@ class HttpCli(object):
|
|||||||
def handle_mv(self) -> bool:
|
def handle_mv(self) -> bool:
|
||||||
# full path of new loc (incl filename)
|
# full path of new loc (incl filename)
|
||||||
dst = self.uparam.get("move")
|
dst = self.uparam.get("move")
|
||||||
|
|
||||||
|
if self.is_vproxied and dst and dst.startswith(self.args.SR):
|
||||||
|
dst = dst[len(self.args.RS) :]
|
||||||
|
|
||||||
if not dst:
|
if not dst:
|
||||||
raise Pebkac(400, "need dst vpath")
|
raise Pebkac(400, "need dst vpath")
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import errno
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
@@ -28,7 +29,7 @@ except ImportError:
|
|||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from .__init__ import MACOS, TYPE_CHECKING, EnvParams
|
from .__init__ import ANYWIN, MACOS, TYPE_CHECKING, EnvParams
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .httpconn import HttpConn
|
from .httpconn import HttpConn
|
||||||
from .util import (
|
from .util import (
|
||||||
@@ -81,8 +82,7 @@ class HttpSrv(object):
|
|||||||
self.bans: dict[str, int] = {}
|
self.bans: dict[str, int] = {}
|
||||||
self.aclose: dict[str, int] = {}
|
self.aclose: dict[str, int] = {}
|
||||||
|
|
||||||
self.ip = ""
|
self.bound: set[tuple[str, int]] = set()
|
||||||
self.port = 0
|
|
||||||
self.name = "hsrv" + nsuf
|
self.name = "hsrv" + nsuf
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
@@ -142,7 +142,11 @@ class HttpSrv(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
|
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
|
||||||
self.nm = NetMap([self.ip], netdevs)
|
ips = set()
|
||||||
|
for ip, _ in self.bound:
|
||||||
|
ips.add(ip)
|
||||||
|
|
||||||
|
self.nm = NetMap(list(ips), netdevs)
|
||||||
|
|
||||||
def start_threads(self, n: int) -> None:
|
def start_threads(self, n: int) -> None:
|
||||||
self.tp_nthr += n
|
self.tp_nthr += n
|
||||||
@@ -178,20 +182,19 @@ class HttpSrv(object):
|
|||||||
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
||||||
if self.args.j != 1:
|
if self.args.j != 1:
|
||||||
# lost in the pickle; redefine
|
# lost in the pickle; redefine
|
||||||
try:
|
if not ANYWIN or self.args.reuseaddr:
|
||||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
||||||
sck.settimeout(None) # < does not inherit, ^ does
|
|
||||||
|
|
||||||
self.ip, self.port = sck.getsockname()[:2]
|
sck.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
sck.settimeout(None) # < does not inherit, ^ opts above do
|
||||||
|
|
||||||
|
ip, port = sck.getsockname()[:2]
|
||||||
self.srvs.append(sck)
|
self.srvs.append(sck)
|
||||||
|
self.bound.add((ip, port))
|
||||||
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
||||||
Daemon(
|
Daemon(
|
||||||
self.thr_listen,
|
self.thr_listen,
|
||||||
"httpsrv-n{}-listen-{}-{}".format(self.nid or "0", self.ip, self.port),
|
"httpsrv-n{}-listen-{}-{}".format(self.nid or "0", ip, port),
|
||||||
(sck,),
|
(sck,),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -284,6 +287,16 @@ class HttpSrv(object):
|
|||||||
if self.stopping:
|
if self.stopping:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if (
|
||||||
|
ex.errno == errno.EINVAL
|
||||||
|
and ip == "0.0.0.0"
|
||||||
|
and ("::", port) in self.bound
|
||||||
|
):
|
||||||
|
t = "accept({}): {} -- probably due to dualstack; terminating ({}, {})"
|
||||||
|
self.log(self.name, t.format(fno, ex, ip, port), c=6)
|
||||||
|
srv_sck.close()
|
||||||
|
return
|
||||||
|
|
||||||
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from .stolen.dnslib import (
|
|||||||
DNSQuestion,
|
DNSQuestion,
|
||||||
DNSRecord,
|
DNSRecord,
|
||||||
)
|
)
|
||||||
from .util import CachedSet, Daemon, Netdev, min_ex
|
from .util import CachedSet, Daemon, Netdev, list_ips, min_ex
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
@@ -55,6 +55,7 @@ class MDNS_Sck(MC_Sck):
|
|||||||
self.bp_bye = b""
|
self.bp_bye = b""
|
||||||
|
|
||||||
self.last_tx = 0.0
|
self.last_tx = 0.0
|
||||||
|
self.tx_ex = False
|
||||||
|
|
||||||
|
|
||||||
class MDNS(MCast):
|
class MDNS(MCast):
|
||||||
@@ -313,25 +314,30 @@ class MDNS(MCast):
|
|||||||
self.running = False
|
self.running = False
|
||||||
if not panic:
|
if not panic:
|
||||||
for srv in self.srv.values():
|
for srv in self.srv.values():
|
||||||
srv.sck.sendto(srv.bp_bye, (srv.grp, 5353))
|
try:
|
||||||
|
srv.sck.sendto(srv.bp_bye, (srv.grp, 5353))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
self.srv = {}
|
self.srv = {}
|
||||||
|
|
||||||
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
||||||
cip = addr[0]
|
cip = addr[0]
|
||||||
v6 = ":" in cip
|
v6 = ":" in cip
|
||||||
if cip.startswith("169.254") or v6 and not cip.startswith("fe80"):
|
if (cip.startswith("169.254") and not self.ll_ok) or (
|
||||||
|
v6 and not cip.startswith("fe80")
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
cache = self.rx6 if v6 else self.rx4
|
cache = self.rx6 if v6 else self.rx4
|
||||||
if buf in cache.c:
|
if buf in cache.c:
|
||||||
return
|
return
|
||||||
|
|
||||||
cache.add(buf)
|
|
||||||
srv: Optional[MDNS_Sck] = self.srv[sck] if v6 else self.map_client(cip) # type: ignore
|
srv: Optional[MDNS_Sck] = self.srv[sck] if v6 else self.map_client(cip) # type: ignore
|
||||||
if not srv:
|
if not srv:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
cache.add(buf)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if self.args.zmv and cip != srv.ip and cip not in srv.ips:
|
if self.args.zmv and cip != srv.ip and cip not in srv.ips:
|
||||||
@@ -369,6 +375,14 @@ class MDNS(MCast):
|
|||||||
# avahi broadcasting 127.0.0.1-only packets
|
# avahi broadcasting 127.0.0.1-only packets
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# check if we've been given additional IPs
|
||||||
|
for ip in list_ips():
|
||||||
|
if ip in cips:
|
||||||
|
self.sips.add(ip)
|
||||||
|
|
||||||
|
if not self.sips.isdisjoint(cips):
|
||||||
|
return
|
||||||
|
|
||||||
t = "mdns zeroconf: "
|
t = "mdns zeroconf: "
|
||||||
if self.probing:
|
if self.probing:
|
||||||
t += "Cannot start; hostname '{}' is occupied"
|
t += "Cannot start; hostname '{}' is occupied"
|
||||||
@@ -502,6 +516,15 @@ class MDNS(MCast):
|
|||||||
if now < srv.last_tx + cooldown:
|
if now < srv.last_tx + cooldown:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
srv.sck.sendto(msg, (srv.grp, 5353))
|
try:
|
||||||
srv.last_tx = now
|
srv.sck.sendto(msg, (srv.grp, 5353))
|
||||||
|
srv.last_tx = now
|
||||||
|
except Exception as ex:
|
||||||
|
if srv.tx_ex:
|
||||||
|
return True
|
||||||
|
|
||||||
|
srv.tx_ex = True
|
||||||
|
t = "tx({},|{}|,{}): {}"
|
||||||
|
self.log(t.format(srv.ip, len(msg), cooldown, ex), 3)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ import socket
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
|
from ipaddress import (
|
||||||
|
IPv4Address,
|
||||||
|
IPv4Network,
|
||||||
|
IPv6Address,
|
||||||
|
IPv6Network,
|
||||||
|
ip_address,
|
||||||
|
ip_network,
|
||||||
|
)
|
||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import TYPE_CHECKING
|
||||||
from .util import MACOS, Netdev, min_ex, spack
|
from .util import MACOS, Netdev, min_ex, spack
|
||||||
@@ -75,6 +82,7 @@ class MCast(object):
|
|||||||
|
|
||||||
self.srv: dict[socket.socket, MC_Sck] = {} # listening sockets
|
self.srv: dict[socket.socket, MC_Sck] = {} # listening sockets
|
||||||
self.sips: set[str] = set() # all listening ips (including failed attempts)
|
self.sips: set[str] = set() # all listening ips (including failed attempts)
|
||||||
|
self.ll_ok: set[str] = set() # fallback linklocal IPv4 and IPv6 addresses
|
||||||
self.b2srv: dict[bytes, MC_Sck] = {} # binary-ip -> server socket
|
self.b2srv: dict[bytes, MC_Sck] = {} # binary-ip -> server socket
|
||||||
self.b4: list[bytes] = [] # sorted list of binary-ips
|
self.b4: list[bytes] = [] # sorted list of binary-ips
|
||||||
self.b6: list[bytes] = [] # sorted list of binary-ips
|
self.b6: list[bytes] = [] # sorted list of binary-ips
|
||||||
@@ -110,7 +118,17 @@ class MCast(object):
|
|||||||
off = self.off[:]
|
off = self.off[:]
|
||||||
for lst in (on, off):
|
for lst in (on, off):
|
||||||
for av in list(lst):
|
for av in list(lst):
|
||||||
|
try:
|
||||||
|
arg_net = ip_network(av, False)
|
||||||
|
except:
|
||||||
|
arg_net = None
|
||||||
|
|
||||||
for sk, sv in netdevs.items():
|
for sk, sv in netdevs.items():
|
||||||
|
if arg_net:
|
||||||
|
net_ip = ip_address(sk.split("/")[0])
|
||||||
|
if net_ip in arg_net and sk not in lst:
|
||||||
|
lst.append(sk)
|
||||||
|
|
||||||
if (av == str(sv.idx) or av == sv.name) and sk not in lst:
|
if (av == str(sv.idx) or av == sv.name) and sk not in lst:
|
||||||
lst.append(sk)
|
lst.append(sk)
|
||||||
|
|
||||||
@@ -166,9 +184,21 @@ class MCast(object):
|
|||||||
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
|
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
|
||||||
|
|
||||||
# gvfs breaks if a linklocal ip appears in a dns reply
|
# gvfs breaks if a linklocal ip appears in a dns reply
|
||||||
srv.ips = {k: v for k, v in srv.ips.items() if not k.startswith("fe80")}
|
ll = {
|
||||||
|
k: v
|
||||||
|
for k, v in srv.ips.items()
|
||||||
|
if k.startswith("169.254") or k.startswith("fe80")
|
||||||
|
}
|
||||||
|
rt = {k: v for k, v in srv.ips.items() if k not in ll}
|
||||||
|
|
||||||
|
if self.args.ll or not rt:
|
||||||
|
self.ll_ok.update(list(ll))
|
||||||
|
|
||||||
|
if not self.args.ll:
|
||||||
|
srv.ips = rt or ll
|
||||||
|
|
||||||
if not srv.ips:
|
if not srv.ips:
|
||||||
self.log("no routable IPs on {}; skipping [{}]".format(netdev, ip), 3)
|
self.log("no IPs on {}; skipping [{}]".format(netdev, ip), 3)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -318,6 +348,16 @@ class MCast(object):
|
|||||||
# just give it something
|
# just give it something
|
||||||
ret = list(self.srv.values())[0]
|
ret = list(self.srv.values())[0]
|
||||||
|
|
||||||
|
if not ret and cip.startswith("169.254"):
|
||||||
|
# idk how to map LL IPv4 msgs to nics;
|
||||||
|
# just pick one and hope for the best
|
||||||
|
lls = (
|
||||||
|
x
|
||||||
|
for x in self.srv.values()
|
||||||
|
if next((y for y in x.ips if y in self.ll_ok), None)
|
||||||
|
)
|
||||||
|
ret = next(lls, None)
|
||||||
|
|
||||||
if ret:
|
if ret:
|
||||||
t = "new client on {} ({}): {}"
|
t = "new client on {} ({}): {}"
|
||||||
self.log(t.format(ret.name, ret.net, cip), 6)
|
self.log(t.format(ret.name, ret.net, cip), 6)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from email.utils import formatdate
|
|||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import TYPE_CHECKING
|
||||||
from .multicast import MC_Sck, MCast
|
from .multicast import MC_Sck, MCast
|
||||||
from .util import CachedSet, min_ex
|
from .util import CachedSet, min_ex, html_escape
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .broker_util import BrokerCli
|
from .broker_util import BrokerCli
|
||||||
@@ -73,13 +73,15 @@ class SSDPr(object):
|
|||||||
</device>
|
</device>
|
||||||
</root>"""
|
</root>"""
|
||||||
|
|
||||||
|
c = html_escape
|
||||||
sip, sport = hc.s.getsockname()[:2]
|
sip, sport = hc.s.getsockname()[:2]
|
||||||
|
sip = sip.replace("::ffff:", "")
|
||||||
proto = "https" if self.args.https_only else "http"
|
proto = "https" if self.args.https_only else "http"
|
||||||
ubase = "{}://{}:{}".format(proto, sip, sport)
|
ubase = "{}://{}:{}".format(proto, sip, sport)
|
||||||
zsl = self.args.zsl
|
zsl = self.args.zsl
|
||||||
url = zsl if "://" in zsl else ubase + "/" + zsl.lstrip("/")
|
url = zsl if "://" in zsl else ubase + "/" + zsl.lstrip("/")
|
||||||
name = "{} @ {}".format(self.args.doctitle, self.args.name)
|
name = "{} @ {}".format(self.args.doctitle, self.args.name)
|
||||||
zs = zs.strip().format(ubase, url, name, self.args.zsid)
|
zs = zs.strip().format(c(ubase), c(url), c(name), c(self.args.zsid))
|
||||||
hc.reply(zs.encode("utf-8", "replace"))
|
hc.reply(zs.encode("utf-8", "replace"))
|
||||||
return False # close connectino
|
return False # close connectino
|
||||||
|
|
||||||
@@ -147,19 +149,19 @@ class SSDPd(MCast):
|
|||||||
|
|
||||||
def eat(self, buf: bytes, addr: tuple[str, int]) -> None:
|
def eat(self, buf: bytes, addr: tuple[str, int]) -> None:
|
||||||
cip = addr[0]
|
cip = addr[0]
|
||||||
if cip.startswith("169.254"):
|
if cip.startswith("169.254") and not self.ll_ok:
|
||||||
return
|
return
|
||||||
|
|
||||||
if buf in self.rxc.c:
|
if buf in self.rxc.c:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.rxc.add(buf)
|
|
||||||
srv: Optional[SSDP_Sck] = self.map_client(cip) # type: ignore
|
srv: Optional[SSDP_Sck] = self.map_client(cip) # type: ignore
|
||||||
if not srv:
|
if not srv:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.rxc.add(buf)
|
||||||
if not buf.startswith(b"M-SEARCH * HTTP/1."):
|
if not buf.startswith(b"M-SEARCH * HTTP/1."):
|
||||||
raise Exception("not an ssdp message")
|
return
|
||||||
|
|
||||||
if not self.ptn_st.search(buf):
|
if not self.ptn_st.search(buf):
|
||||||
return
|
return
|
||||||
@@ -183,7 +185,8 @@ BOOTID.UPNP.ORG: 0
|
|||||||
CONFIGID.UPNP.ORG: 1
|
CONFIGID.UPNP.ORG: 1
|
||||||
|
|
||||||
"""
|
"""
|
||||||
zs = zs.format(formatdate(usegmt=True), srv.ip, srv.hport, self.args.zsid)
|
v4 = srv.ip.replace("::ffff:", "")
|
||||||
|
zs = zs.format(formatdate(usegmt=True), v4, srv.hport, self.args.zsid)
|
||||||
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
zb = zs[1:].replace("\n", "\r\n").encode("utf-8", "replace")
|
||||||
srv.sck.sendto(zb, addr[:2])
|
srv.sck.sendto(zb, addr[:2])
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import stat
|
||||||
import tarfile
|
import tarfile
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
@@ -79,6 +80,9 @@ class StreamTar(StreamArc):
|
|||||||
src = f["ap"]
|
src = f["ap"]
|
||||||
fsi = f["st"]
|
fsi = f["st"]
|
||||||
|
|
||||||
|
if stat.S_ISDIR(fsi.st_mode):
|
||||||
|
return
|
||||||
|
|
||||||
inf = tarfile.TarInfo(name=name)
|
inf = tarfile.TarInfo(name=name)
|
||||||
inf.mode = fsi.st_mode
|
inf.mode = fsi.st_mode
|
||||||
inf.size = fsi.st_size
|
inf.size = fsi.st_size
|
||||||
|
|||||||
@@ -296,10 +296,20 @@ class SvcHub(object):
|
|||||||
al.zm_off = al.zm_off or al.z_off
|
al.zm_off = al.zm_off or al.z_off
|
||||||
al.zs_off = al.zs_off or al.z_off
|
al.zs_off = al.zs_off or al.z_off
|
||||||
for n in ("zm_on", "zm_off", "zs_on", "zs_off"):
|
for n in ("zm_on", "zm_off", "zs_on", "zs_off"):
|
||||||
vs = getattr(al, n).replace(" ", ",").split(",")
|
vs = getattr(al, n).split(",")
|
||||||
|
vs = [x.strip() for x in vs]
|
||||||
vs = [x for x in vs if x]
|
vs = [x for x in vs if x]
|
||||||
setattr(al, n, vs)
|
setattr(al, n, vs)
|
||||||
|
|
||||||
|
R = al.rp_loc
|
||||||
|
if "//" in R or ":" in R:
|
||||||
|
t = "found URL in --rp-loc; it should be just the location, for example /foo/bar"
|
||||||
|
raise Exception(t)
|
||||||
|
|
||||||
|
al.R = R = R.strip("/")
|
||||||
|
al.SR = "/" + R if R else ""
|
||||||
|
al.RS = R + "/" if R else ""
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _setlimits(self) -> None:
|
def _setlimits(self) -> None:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
import time
|
import time
|
||||||
|
import stat
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
@@ -238,6 +239,9 @@ class StreamZip(StreamArc):
|
|||||||
src = f["ap"]
|
src = f["ap"]
|
||||||
st = f["st"]
|
st = f["st"]
|
||||||
|
|
||||||
|
if stat.S_ISDIR(st.st_mode):
|
||||||
|
return
|
||||||
|
|
||||||
sz = st.st_size
|
sz = st.st_size
|
||||||
ts = st.st_mtime
|
ts = st.st_mtime
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,20 @@ class TcpSrv(object):
|
|||||||
else:
|
else:
|
||||||
self.netdevs = {}
|
self.netdevs = {}
|
||||||
|
|
||||||
|
# keep IPv6 LL-only nics
|
||||||
|
ll_ok: set[str] = set()
|
||||||
|
for ip, nd in self.netdevs.items():
|
||||||
|
if not ip.startswith("fe80"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
just_ll = True
|
||||||
|
for ip2, nd2 in self.netdevs.items():
|
||||||
|
if nd == nd2 and ":" in ip2 and not ip2.startswith("fe80"):
|
||||||
|
just_ll = False
|
||||||
|
|
||||||
|
if just_ll or self.args.ll:
|
||||||
|
ll_ok.add(ip.split("/")[0])
|
||||||
|
|
||||||
qr1: dict[str, list[int]] = {}
|
qr1: dict[str, list[int]] = {}
|
||||||
qr2: dict[str, list[int]] = {}
|
qr2: dict[str, list[int]] = {}
|
||||||
msgs = []
|
msgs = []
|
||||||
@@ -128,7 +142,7 @@ class TcpSrv(object):
|
|||||||
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("$")]
|
||||||
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
t = "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]):
|
||||||
if ip.startswith("fe80"):
|
if ip.startswith("fe80") and ip not in ll_ok:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for port in sorted(self.args.p):
|
for port in sorted(self.args.p):
|
||||||
@@ -195,13 +209,12 @@ class TcpSrv(object):
|
|||||||
def _listen(self, ip: str, port: int) -> None:
|
def _listen(self, ip: str, port: int) -> None:
|
||||||
ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
||||||
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
||||||
try:
|
|
||||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
if not ANYWIN or self.args.reuseaddr:
|
||||||
except:
|
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
pass
|
|
||||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
srv.settimeout(None) # < does not inherit, ^ does
|
srv.settimeout(None) # < does not inherit, ^ opts above do
|
||||||
|
|
||||||
try:
|
try:
|
||||||
srv.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
|
srv.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
|
||||||
@@ -276,10 +289,6 @@ class TcpSrv(object):
|
|||||||
for nip in nic.ips:
|
for nip in nic.ips:
|
||||||
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
|
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
|
||||||
sip = "{}/{}".format(ipa, nip.network_prefix)
|
sip = "{}/{}".format(ipa, nip.network_prefix)
|
||||||
if sip.startswith("169.254"):
|
|
||||||
# browsers dont impl linklocal
|
|
||||||
continue
|
|
||||||
|
|
||||||
nd = Netdev(sip, nic.index or 0, nic.nice_name, "")
|
nd = Netdev(sip, nic.index or 0, nic.nice_name, "")
|
||||||
eps[sip] = nd
|
eps[sip] = nd
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
@@ -61,12 +62,16 @@ try:
|
|||||||
HAVE_AVIF = True
|
HAVE_AVIF = True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
logging.getLogger("PIL").setLevel(logging.WARNING)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
HAVE_VIPS = True
|
HAVE_VIPS = True
|
||||||
import pyvips
|
import pyvips
|
||||||
|
|
||||||
|
logging.getLogger("pyvips").setLevel(logging.WARNING)
|
||||||
except:
|
except:
|
||||||
HAVE_VIPS = False
|
HAVE_VIPS = False
|
||||||
|
|
||||||
@@ -242,40 +247,40 @@ class ThumbSrv(object):
|
|||||||
abspath, tpath = task
|
abspath, tpath = task
|
||||||
ext = abspath.split(".")[-1].lower()
|
ext = abspath.split(".")[-1].lower()
|
||||||
png_ok = False
|
png_ok = False
|
||||||
fun = None
|
funs = []
|
||||||
if not bos.path.exists(tpath):
|
if not bos.path.exists(tpath):
|
||||||
for lib in self.args.th_dec:
|
for lib in self.args.th_dec:
|
||||||
if fun:
|
if lib == "pil" and ext in self.fmt_pil:
|
||||||
break
|
funs.append(self.conv_pil)
|
||||||
elif lib == "pil" and ext in self.fmt_pil:
|
|
||||||
fun = self.conv_pil
|
|
||||||
elif lib == "vips" and ext in self.fmt_vips:
|
elif lib == "vips" and ext in self.fmt_vips:
|
||||||
fun = self.conv_vips
|
funs.append(self.conv_vips)
|
||||||
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
|
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
|
||||||
fun = self.conv_ffmpeg
|
funs.append(self.conv_ffmpeg)
|
||||||
elif lib == "ff" and ext in self.fmt_ffa:
|
elif lib == "ff" and ext in self.fmt_ffa:
|
||||||
if tpath.endswith(".opus") or tpath.endswith(".caf"):
|
if tpath.endswith(".opus") or tpath.endswith(".caf"):
|
||||||
fun = self.conv_opus
|
funs.append(self.conv_opus)
|
||||||
elif tpath.endswith(".png"):
|
elif tpath.endswith(".png"):
|
||||||
fun = self.conv_waves
|
funs.append(self.conv_waves)
|
||||||
png_ok = True
|
png_ok = True
|
||||||
else:
|
else:
|
||||||
fun = self.conv_spec
|
funs.append(self.conv_spec)
|
||||||
|
|
||||||
if not png_ok and tpath.endswith(".png"):
|
if not png_ok and tpath.endswith(".png"):
|
||||||
raise Pebkac(400, "png only allowed for waveforms")
|
raise Pebkac(400, "png only allowed for waveforms")
|
||||||
|
|
||||||
if fun:
|
for fun in funs:
|
||||||
try:
|
try:
|
||||||
fun(abspath, tpath)
|
fun(abspath, tpath)
|
||||||
|
break
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "{} could not create thumbnail of {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
msg = msg.format(fun.__name__, abspath, min_ex())
|
msg = msg.format(fun.__name__, abspath, min_ex())
|
||||||
c: Union[str, int] = 1 if "<Signals.SIG" in msg else "90"
|
c: Union[str, int] = 1 if "<Signals.SIG" in msg else "90"
|
||||||
self.log(msg, c)
|
self.log(msg, c)
|
||||||
if getattr(ex, "returncode", 0) != 321:
|
if getattr(ex, "returncode", 0) != 321:
|
||||||
with open(tpath, "wb") as _:
|
if fun == funs[-1]:
|
||||||
pass
|
with open(tpath, "wb") as _:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
# ffmpeg may spawn empty files on windows
|
# ffmpeg may spawn empty files on windows
|
||||||
try:
|
try:
|
||||||
@@ -363,7 +368,8 @@ class ThumbSrv(object):
|
|||||||
img = pyvips.Image.thumbnail(abspath, w, **kw)
|
img = pyvips.Image.thumbnail(abspath, w, **kw)
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
if c == crops[-1]:
|
||||||
|
raise
|
||||||
|
|
||||||
img.write_to_file(tpath, Q=40)
|
img.write_to_file(tpath, Q=40)
|
||||||
|
|
||||||
|
|||||||
@@ -97,14 +97,17 @@ class U2idx(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
cur = None
|
cur = None
|
||||||
if ANYWIN:
|
if ANYWIN and not bos.path.exists(db_path + "-wal"):
|
||||||
uri = ""
|
uri = ""
|
||||||
try:
|
try:
|
||||||
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
||||||
cur = sqlite3.connect(uri, 2, uri=True).cursor()
|
cur = sqlite3.connect(uri, 2, uri=True).cursor()
|
||||||
|
cur.execute('pragma table_info("up")').fetchone()
|
||||||
self.log("ro: {}".format(db_path))
|
self.log("ro: {}".format(db_path))
|
||||||
except:
|
except:
|
||||||
self.log("could not open read-only: {}\n{}".format(uri, min_ex()))
|
self.log("could not open read-only: {}\n{}".format(uri, min_ex()))
|
||||||
|
# may not fail until the pragma so unset it
|
||||||
|
cur = None
|
||||||
|
|
||||||
if not cur:
|
if not cur:
|
||||||
# on windows, this steals the write-lock from up2k.deferred_init --
|
# on windows, this steals the write-lock from up2k.deferred_init --
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from .util import (
|
|||||||
db_ex_chk,
|
db_ex_chk,
|
||||||
djoin,
|
djoin,
|
||||||
fsenc,
|
fsenc,
|
||||||
|
hidedir,
|
||||||
min_ex,
|
min_ex,
|
||||||
quotep,
|
quotep,
|
||||||
ren_open,
|
ren_open,
|
||||||
@@ -152,6 +153,7 @@ class Up2k(object):
|
|||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
# usually fails to set lastmod too quickly
|
# usually fails to set lastmod too quickly
|
||||||
self.lastmod_q: list[tuple[str, int, tuple[int, int], bool]] = []
|
self.lastmod_q: list[tuple[str, int, tuple[int, int], bool]] = []
|
||||||
|
self.lastmod_q2 = self.lastmod_q[:]
|
||||||
Daemon(self._lastmodder, "up2k-lastmod")
|
Daemon(self._lastmodder, "up2k-lastmod")
|
||||||
|
|
||||||
self.fstab = Fstab(self.log_func)
|
self.fstab = Fstab(self.log_func)
|
||||||
@@ -180,6 +182,17 @@ class Up2k(object):
|
|||||||
all_vols = self.asrv.vfs.all_vols
|
all_vols = self.asrv.vfs.all_vols
|
||||||
have_e2d = self.init_indexes(all_vols, [])
|
have_e2d = self.init_indexes(all_vols, [])
|
||||||
|
|
||||||
|
if self.stop:
|
||||||
|
# up-mt consistency not guaranteed if init is interrupted;
|
||||||
|
# drop caches for a full scan on next boot
|
||||||
|
self._drop_caches()
|
||||||
|
|
||||||
|
if self.pp:
|
||||||
|
self.pp.end = True
|
||||||
|
self.pp = None
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
if not self.pp and self.args.exit == "idx":
|
if not self.pp and self.args.exit == "idx":
|
||||||
return self.hub.sigterm()
|
return self.hub.sigterm()
|
||||||
|
|
||||||
@@ -463,6 +476,10 @@ class Up2k(object):
|
|||||||
if next((zv for zv in vols if "e2ds" in zv.flags), None):
|
if next((zv for zv in vols if "e2ds" in zv.flags), None):
|
||||||
self._block("indexing")
|
self._block("indexing")
|
||||||
|
|
||||||
|
if self.args.re_dhash:
|
||||||
|
self.args.re_dhash = False
|
||||||
|
self._drop_caches()
|
||||||
|
|
||||||
for vol in vols:
|
for vol in vols:
|
||||||
if self.stop:
|
if self.stop:
|
||||||
break
|
break
|
||||||
@@ -550,6 +567,34 @@ class Up2k(object):
|
|||||||
if self.stop:
|
if self.stop:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
for vol in all_vols.values():
|
||||||
|
if vol.flags["dbd"] == "acid":
|
||||||
|
continue
|
||||||
|
|
||||||
|
reg = self.register_vpath(vol.realpath, vol.flags)
|
||||||
|
try:
|
||||||
|
assert reg
|
||||||
|
cur, db_path = reg
|
||||||
|
if bos.path.getsize(db_path + "-wal") < 1024 * 1024 * 5:
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self.mutex:
|
||||||
|
cur.execute("pragma wal_checkpoint(truncate)")
|
||||||
|
try:
|
||||||
|
cur.execute("commit") # absolutely necessary! for some reason
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
cur.connection.commit() # this one maybe not
|
||||||
|
except Exception as ex:
|
||||||
|
self.log("checkpoint failed: {}".format(ex), 3)
|
||||||
|
|
||||||
|
if self.stop:
|
||||||
|
return False
|
||||||
|
|
||||||
self.pp.end = True
|
self.pp.end = True
|
||||||
|
|
||||||
msg = "{} volumes in {:.2f} sec"
|
msg = "{} volumes in {:.2f} sec"
|
||||||
@@ -597,9 +642,15 @@ class Up2k(object):
|
|||||||
ff = "\033[0;35m{}{:.0}"
|
ff = "\033[0;35m{}{:.0}"
|
||||||
fv = "\033[0;36m{}:\033[90m{}"
|
fv = "\033[0;36m{}:\033[90m{}"
|
||||||
fx = set(("html_head",))
|
fx = set(("html_head",))
|
||||||
|
fd = {"dbd": "dbd"}
|
||||||
|
fl = {
|
||||||
|
k: v
|
||||||
|
for k, v in flags.items()
|
||||||
|
if k not in fd or v != getattr(self.args, fd[k])
|
||||||
|
}
|
||||||
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 fl.items()
|
||||||
if k not in fx
|
if k not in fx
|
||||||
]
|
]
|
||||||
if a:
|
if a:
|
||||||
@@ -654,11 +705,45 @@ class Up2k(object):
|
|||||||
if not HAVE_SQLITE3 or "e2d" not in flags or "d2d" in flags:
|
if not HAVE_SQLITE3 or "e2d" not in flags or "d2d" in flags:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
bos.makedirs(histpath)
|
if bos.makedirs(histpath):
|
||||||
|
hidedir(histpath)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cur = self._open_db(db_path)
|
cur = self._open_db(db_path)
|
||||||
self.cur[ptop] = cur
|
self.cur[ptop] = cur
|
||||||
|
|
||||||
|
# speeds measured uploading 520 small files on a WD20SPZX (SMR 2.5" 5400rpm 4kb)
|
||||||
|
dbd = flags["dbd"]
|
||||||
|
if dbd == "acid":
|
||||||
|
# 217.5s; python-defaults
|
||||||
|
zs = "delete"
|
||||||
|
sync = "full"
|
||||||
|
elif dbd == "swal":
|
||||||
|
# 88.0s; still 99.9% safe (can lose a bit of on OS crash)
|
||||||
|
zs = "wal"
|
||||||
|
sync = "full"
|
||||||
|
elif dbd == "yolo":
|
||||||
|
# 2.7s; may lose entire db on OS crash
|
||||||
|
zs = "wal"
|
||||||
|
sync = "off"
|
||||||
|
else:
|
||||||
|
# 4.1s; corruption-safe but more likely to lose wal
|
||||||
|
zs = "wal"
|
||||||
|
sync = "normal"
|
||||||
|
|
||||||
|
try:
|
||||||
|
amode = cur.execute("pragma journal_mode=" + zs).fetchone()[0]
|
||||||
|
if amode.lower() != zs.lower():
|
||||||
|
t = "sqlite failed to set journal_mode {}; got {}"
|
||||||
|
raise Exception(t.format(zs, amode))
|
||||||
|
except Exception as ex:
|
||||||
|
if sync != "off":
|
||||||
|
sync = "full"
|
||||||
|
t = "reverting to sync={} because {}"
|
||||||
|
self.log(t.format(sync, ex))
|
||||||
|
|
||||||
|
cur.execute("pragma synchronous=" + sync)
|
||||||
|
cur.connection.commit()
|
||||||
return cur, db_path
|
return cur, db_path
|
||||||
except:
|
except:
|
||||||
msg = "cannot use database at [{}]:\n{}"
|
msg = "cannot use database at [{}]:\n{}"
|
||||||
@@ -763,6 +848,7 @@ class Up2k(object):
|
|||||||
seen = seen + [rcdir]
|
seen = seen + [rcdir]
|
||||||
unreg: list[str] = []
|
unreg: list[str] = []
|
||||||
files: list[tuple[int, int, str]] = []
|
files: list[tuple[int, int, str]] = []
|
||||||
|
fat32 = True
|
||||||
|
|
||||||
assert self.pp and self.mem_cur
|
assert self.pp and self.mem_cur
|
||||||
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
||||||
@@ -787,6 +873,9 @@ class Up2k(object):
|
|||||||
|
|
||||||
lmod = int(inf.st_mtime)
|
lmod = int(inf.st_mtime)
|
||||||
sz = inf.st_size
|
sz = inf.st_size
|
||||||
|
if fat32 and inf.st_mtime % 2:
|
||||||
|
fat32 = False
|
||||||
|
|
||||||
if stat.S_ISDIR(inf.st_mode):
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
rap = absreal(abspath)
|
rap = absreal(abspath)
|
||||||
if dev and inf.st_dev != dev:
|
if dev and inf.st_dev != dev:
|
||||||
@@ -874,6 +963,9 @@ class Up2k(object):
|
|||||||
self.log(t.format(top, rp, len(in_db), rep_db))
|
self.log(t.format(top, rp, len(in_db), rep_db))
|
||||||
dts = -1
|
dts = -1
|
||||||
|
|
||||||
|
if fat32 and abs(dts - lmod) == 1:
|
||||||
|
dts = lmod
|
||||||
|
|
||||||
if dts == lmod and dsz == sz and (nohash or dw[0] != "#" or not sz):
|
if dts == lmod and dsz == sz and (nohash or dw[0] != "#" or not sz):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1179,6 +1271,18 @@ class Up2k(object):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def _drop_caches(self) -> None:
|
||||||
|
self.log("dropping caches for a full filesystem scan")
|
||||||
|
for vol in self.asrv.vfs.all_vols.values():
|
||||||
|
reg = self.register_vpath(vol.realpath, vol.flags)
|
||||||
|
if not reg:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur, _ = reg
|
||||||
|
self._set_tagscan(cur, True)
|
||||||
|
cur.execute("delete from dh")
|
||||||
|
cur.connection.commit()
|
||||||
|
|
||||||
def _set_tagscan(self, cur: "sqlite3.Cursor", need: bool) -> bool:
|
def _set_tagscan(self, cur: "sqlite3.Cursor", need: bool) -> bool:
|
||||||
if self.args.no_dhash:
|
if self.args.no_dhash:
|
||||||
return False
|
return False
|
||||||
@@ -1727,9 +1831,13 @@ class Up2k(object):
|
|||||||
self._set_tagscan(write_cur, True)
|
self._set_tagscan(write_cur, True)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def _trace(self, msg: str) -> None:
|
||||||
|
self.log("ST: {}".format(msg))
|
||||||
|
|
||||||
def _orz(self, db_path: str) -> "sqlite3.Cursor":
|
def _orz(self, db_path: str) -> "sqlite3.Cursor":
|
||||||
return sqlite3.connect(db_path, self.timeout, check_same_thread=False).cursor()
|
c = sqlite3.connect(db_path, self.timeout, check_same_thread=False).cursor()
|
||||||
# x.set_trace_callback(trace)
|
# c.connection.set_trace_callback(self._trace)
|
||||||
|
return c
|
||||||
|
|
||||||
def _open_db(self, db_path: str) -> "sqlite3.Cursor":
|
def _open_db(self, db_path: str) -> "sqlite3.Cursor":
|
||||||
existed = bos.path.exists(db_path)
|
existed = bos.path.exists(db_path)
|
||||||
@@ -2043,7 +2151,7 @@ class Up2k(object):
|
|||||||
job[k] = cj[k]
|
job[k] = cj[k]
|
||||||
|
|
||||||
pdir = djoin(cj["ptop"], cj["prel"])
|
pdir = djoin(cj["ptop"], cj["prel"])
|
||||||
job["name"] = self._untaken(pdir, cj["name"], now, cj["addr"])
|
job["name"] = self._untaken(pdir, cj, now)
|
||||||
dst = os.path.join(job["ptop"], job["prel"], job["name"])
|
dst = os.path.join(job["ptop"], job["prel"], job["name"])
|
||||||
if not self.args.nw:
|
if not self.args.nw:
|
||||||
bos.unlink(dst) # TODO ed pls
|
bos.unlink(dst) # TODO ed pls
|
||||||
@@ -2090,7 +2198,7 @@ class Up2k(object):
|
|||||||
]:
|
]:
|
||||||
job[k] = cj[k]
|
job[k] = cj[k]
|
||||||
|
|
||||||
for k in ["life"]:
|
for k in ["life", "replace"]:
|
||||||
if k in cj:
|
if k in cj:
|
||||||
job[k] = cj[k]
|
job[k] = cj[k]
|
||||||
|
|
||||||
@@ -2122,10 +2230,18 @@ class Up2k(object):
|
|||||||
"wark": wark,
|
"wark": wark,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _untaken(self, fdir: str, fname: str, ts: float, ip: str) -> str:
|
def _untaken(self, fdir: str, job: dict[str, Any], ts: float) -> str:
|
||||||
|
fname = job["name"]
|
||||||
|
ip = job["addr"]
|
||||||
|
|
||||||
if self.args.nw:
|
if self.args.nw:
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
|
fp = os.path.join(fdir, fname)
|
||||||
|
if job.get("replace") and bos.path.exists(fp):
|
||||||
|
self.log("replacing existing file at {}".format(fp))
|
||||||
|
bos.unlink(fp)
|
||||||
|
|
||||||
if self.args.plain_ip:
|
if self.args.plain_ip:
|
||||||
dip = ip.replace(":", ".")
|
dip = ip.replace(":", ".")
|
||||||
else:
|
else:
|
||||||
@@ -2900,7 +3016,7 @@ class Up2k(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.registry[job["ptop"]][job["wark"]] = job
|
self.registry[job["ptop"]][job["wark"]] = job
|
||||||
job["name"] = self._untaken(pdir, job["name"], job["t0"], job["addr"])
|
job["name"] = self._untaken(pdir, job, job["t0"])
|
||||||
# if len(job["name"].split(".")) > 8:
|
# if len(job["name"].split(".")) > 8:
|
||||||
# raise Exception("aaa")
|
# raise Exception("aaa")
|
||||||
|
|
||||||
@@ -2968,11 +3084,11 @@ class Up2k(object):
|
|||||||
|
|
||||||
def _lastmodder(self) -> None:
|
def _lastmodder(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
ready = self.lastmod_q
|
ready = self.lastmod_q2
|
||||||
|
self.lastmod_q2 = self.lastmod_q
|
||||||
self.lastmod_q = []
|
self.lastmod_q = []
|
||||||
|
|
||||||
# self.log("lmod: got {}".format(len(ready)))
|
time.sleep(1)
|
||||||
time.sleep(5)
|
|
||||||
for path, sz, times, sparse in ready:
|
for path, sz, times, sparse in ready:
|
||||||
self.log("lmod: setting times {} on {}".format(times, path))
|
self.log("lmod: setting times {} on {}".format(times, path))
|
||||||
try:
|
try:
|
||||||
@@ -3064,7 +3180,8 @@ class Up2k(object):
|
|||||||
if etag == self.snap_prev.get(ptop):
|
if etag == self.snap_prev.get(ptop):
|
||||||
return
|
return
|
||||||
|
|
||||||
bos.makedirs(histpath)
|
if bos.makedirs(histpath):
|
||||||
|
hidedir(histpath)
|
||||||
|
|
||||||
path2 = "{}.{}".format(path, os.getpid())
|
path2 = "{}.{}".format(path, os.getpid())
|
||||||
body = {"droppable": self.droppable[ptop], "registry": reg}
|
body = {"droppable": self.droppable[ptop], "registry": reg}
|
||||||
@@ -3177,6 +3294,7 @@ class Up2k(object):
|
|||||||
if self.mth:
|
if self.mth:
|
||||||
self.mth.stop = True
|
self.mth.stop = True
|
||||||
|
|
||||||
|
# in case we're killed early
|
||||||
for x in list(self.spools):
|
for x in list(self.spools):
|
||||||
self._unspool(x)
|
self._unspool(x)
|
||||||
|
|
||||||
@@ -3184,6 +3302,16 @@ class Up2k(object):
|
|||||||
self.log("writing snapshot")
|
self.log("writing snapshot")
|
||||||
self.do_snapshot()
|
self.do_snapshot()
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
while self.pp:
|
||||||
|
time.sleep(0.1)
|
||||||
|
if time.time() - t0 >= 1:
|
||||||
|
break
|
||||||
|
|
||||||
|
# if there is time
|
||||||
|
for x in list(self.spools):
|
||||||
|
self._unspool(x)
|
||||||
|
|
||||||
|
|
||||||
def up2k_chunksize(filesize: int) -> int:
|
def up2k_chunksize(filesize: int) -> int:
|
||||||
chunksize = 1024 * 1024
|
chunksize = 1024 * 1024
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ HTTPCODE = {
|
|||||||
204: "No Content",
|
204: "No Content",
|
||||||
206: "Partial Content",
|
206: "Partial Content",
|
||||||
207: "Multi-Status",
|
207: "Multi-Status",
|
||||||
|
301: "Moved Permanently",
|
||||||
302: "Found",
|
302: "Found",
|
||||||
304: "Not Modified",
|
304: "Not Modified",
|
||||||
400: "Bad Request",
|
400: "Bad Request",
|
||||||
@@ -436,9 +437,7 @@ class HLog(logging.Handler):
|
|||||||
else:
|
else:
|
||||||
c = 1
|
c = 1
|
||||||
|
|
||||||
if record.name.startswith("PIL") and lv < logging.WARNING:
|
if record.name == "pyftpdlib":
|
||||||
return
|
|
||||||
elif record.name == "pyftpdlib":
|
|
||||||
m = self.ptn_ftp.match(msg)
|
m = self.ptn_ftp.match(msg)
|
||||||
if m:
|
if m:
|
||||||
ip = m.group(1)
|
ip = m.group(1)
|
||||||
@@ -2007,6 +2006,20 @@ def read_socket_chunked(
|
|||||||
raise Pebkac(400, t.format(x))
|
raise Pebkac(400, t.format(x))
|
||||||
|
|
||||||
|
|
||||||
|
def list_ips() -> list[str]:
|
||||||
|
from .stolen.ifaddr import get_adapters
|
||||||
|
|
||||||
|
ret: set[str] = set()
|
||||||
|
for nic in get_adapters():
|
||||||
|
for ipo in nic.ips:
|
||||||
|
if len(ipo.ip) < 7:
|
||||||
|
ret.add(ipo.ip[0]) # ipv6 is (ip,0,0)
|
||||||
|
else:
|
||||||
|
ret.add(ipo.ip)
|
||||||
|
|
||||||
|
return list(ret)
|
||||||
|
|
||||||
|
|
||||||
def yieldfile(fn: str) -> Generator[bytes, None, None]:
|
def yieldfile(fn: str) -> Generator[bytes, None, None]:
|
||||||
with open(fsenc(fn), "rb", 512 * 1024) as f:
|
with open(fsenc(fn), "rb", 512 * 1024) as f:
|
||||||
while True:
|
while True:
|
||||||
@@ -2536,7 +2549,7 @@ def termsize() -> tuple[int, int]:
|
|||||||
def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]:
|
def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]:
|
||||||
try:
|
try:
|
||||||
cr = sunpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
|
cr = sunpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
|
||||||
return int(cr[1]), int(cr[0])
|
return cr[::-1]
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -2549,15 +2562,23 @@ def termsize() -> tuple[int, int]:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if cr:
|
|
||||||
return cr
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return int(env["COLUMNS"]), int(env["LINES"])
|
return cr or (int(env["COLUMNS"]), int(env["LINES"]))
|
||||||
except:
|
except:
|
||||||
return 80, 25
|
return 80, 25
|
||||||
|
|
||||||
|
|
||||||
|
def hidedir(dp) -> None:
|
||||||
|
if ANYWIN:
|
||||||
|
try:
|
||||||
|
k32 = ctypes.WinDLL("kernel32")
|
||||||
|
attrs = k32.GetFileAttributesW(dp)
|
||||||
|
if attrs >= 0:
|
||||||
|
k32.SetFileAttributesW(dp, attrs | 2)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Pebkac(Exception):
|
class Pebkac(Exception):
|
||||||
def __init__(self, code: int, msg: Optional[str] = None) -> None:
|
def __init__(self, code: int, msg: Optional[str] = None) -> None:
|
||||||
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ window.baguetteBox = (function () {
|
|||||||
isOverlayVisible = false,
|
isOverlayVisible = false,
|
||||||
touch = {}, // start-pos
|
touch = {}, // start-pos
|
||||||
touchFlag = false, // busy
|
touchFlag = false, // busy
|
||||||
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
|
re_i = /.+\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp)(\?|$)/i,
|
||||||
re_v = /.+\.(webm|mkv|mp4)(\?|$)/i,
|
re_v = /.+\.(webm|mkv|mp4)(\?|$)/i,
|
||||||
anims = ['slideIn', 'fadeIn', 'none'],
|
anims = ['slideIn', 'fadeIn', 'none'],
|
||||||
data = {}, // all galleries
|
data = {}, // all galleries
|
||||||
@@ -277,8 +277,8 @@ window.baguetteBox = (function () {
|
|||||||
playpause();
|
playpause();
|
||||||
else if (k == "KeyU" || k == "KeyO")
|
else if (k == "KeyU" || k == "KeyO")
|
||||||
relseek(k == "KeyU" ? -10 : 10);
|
relseek(k == "KeyU" ? -10 : 10);
|
||||||
else if (k.indexOf('Digit') === 0)
|
else if (k.indexOf('Digit') === 0 && v)
|
||||||
vid().currentTime = vid().duration * parseInt(k.slice(-1)) * 0.1;
|
v.currentTime = v.duration * parseInt(k.slice(-1)) * 0.1;
|
||||||
else if (k == "KeyM" && v) {
|
else if (k == "KeyM" && v) {
|
||||||
v.muted = vmute = !vmute;
|
v.muted = vmute = !vmute;
|
||||||
mp_ctl();
|
mp_ctl();
|
||||||
|
|||||||
@@ -1075,18 +1075,18 @@ html.y #widget.open {
|
|||||||
top: -.12em;
|
top: -.12em;
|
||||||
}
|
}
|
||||||
#wtico {
|
#wtico {
|
||||||
cursor: url(/.cpr/dd/4.png), pointer;
|
cursor: url(dd/4.png), pointer;
|
||||||
animation: cursor 500ms;
|
animation: cursor 500ms;
|
||||||
}
|
}
|
||||||
#wtico:hover {
|
#wtico:hover {
|
||||||
animation: cursor 500ms infinite;
|
animation: cursor 500ms infinite;
|
||||||
}
|
}
|
||||||
@keyframes cursor {
|
@keyframes cursor {
|
||||||
0% {cursor: url(/.cpr/dd/2.png), pointer}
|
0% {cursor: url(dd/2.png), pointer}
|
||||||
30% {cursor: url(/.cpr/dd/3.png), pointer}
|
30% {cursor: url(dd/3.png), pointer}
|
||||||
50% {cursor: url(/.cpr/dd/4.png), pointer}
|
50% {cursor: url(dd/4.png), pointer}
|
||||||
75% {cursor: url(/.cpr/dd/5.png), pointer}
|
75% {cursor: url(dd/5.png), pointer}
|
||||||
85% {cursor: url(/.cpr/dd/4.png), pointer}
|
85% {cursor: url(dd/4.png), pointer}
|
||||||
}
|
}
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
100% {transform: rotate(360deg)}
|
100% {transform: rotate(360deg)}
|
||||||
@@ -2557,7 +2557,6 @@ html.b #u2conf a.b:hover {
|
|||||||
#u2conf input[type="checkbox"]:checked+label:hover {
|
#u2conf input[type="checkbox"]:checked+label:hover {
|
||||||
background: var(--u2-o-1h-bg);
|
background: var(--u2-o-1h-bg);
|
||||||
}
|
}
|
||||||
#op_up2k.srch #u2conf td:nth-child(1)>*,
|
|
||||||
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
#op_up2k.srch #u2conf td:nth-child(2)>*,
|
||||||
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
#op_up2k.srch #u2conf td:nth-child(3)>* {
|
||||||
background: #777;
|
background: #777;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#333">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
|
||||||
{%- if css %}
|
{%- if css %}
|
||||||
<link rel="stylesheet" media="screen" href="{{ css }}?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ css }}?_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<h1 id="path">
|
<h1 id="path">
|
||||||
<a href="#" id="entree">🌲</a>
|
<a href="#" id="entree">🌲</a>
|
||||||
{%- for n in vpnodes %}
|
{%- for n in vpnodes %}
|
||||||
<a href="/{{ n[0] }}">{{ n[1] }}</a>
|
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
<div id="epi" class="logue">{{ logues[1] }}</div>
|
<div id="epi" class="logue">{{ logues[1] }}</div>
|
||||||
|
|
||||||
<h2><a href="/?h" id="goh">control-panel</a></h2>
|
<h2><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
|
||||||
|
|
||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
|
|
||||||
@@ -134,7 +134,8 @@
|
|||||||
<div id="widget"></div>
|
<div id="widget"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var acct = "{{ acct }}",
|
var SR = {{ r|tojson }},
|
||||||
|
acct = "{{ acct }}",
|
||||||
perms = {{ perms }},
|
perms = {{ perms }},
|
||||||
themes = {{ themes }},
|
themes = {{ themes }},
|
||||||
dtheme = "{{ dtheme }}",
|
dtheme = "{{ dtheme }}",
|
||||||
@@ -160,10 +161,10 @@
|
|||||||
|
|
||||||
document.documentElement.className = localStorage.theme || dtheme;
|
document.documentElement.className = localStorage.theme || dtheme;
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/browser.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/browser.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/up2k.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/up2k.js?_={{ ts }}"></script>
|
||||||
{%- if js %}
|
{%- if js %}
|
||||||
<script src="{{ js }}?_={{ ts }}"></script>
|
<script src="{{ js }}?_={{ ts }}"></script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ var Ls = {
|
|||||||
"ht_and": " and ",
|
"ht_and": " and ",
|
||||||
|
|
||||||
"goh": "control-panel",
|
"goh": "control-panel",
|
||||||
|
"gop": 'previous sibling">prev',
|
||||||
|
"gou": 'parent folder">up',
|
||||||
|
"gon": 'next folder">next',
|
||||||
"logout": "Logout ",
|
"logout": "Logout ",
|
||||||
"access": " access",
|
"access": " access",
|
||||||
"ot_close": "close submenu",
|
"ot_close": "close submenu",
|
||||||
@@ -257,6 +260,8 @@ var Ls = {
|
|||||||
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
"fbd_more": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_more">show {2}</a> or <a href="#" id="bd_all">show all</a></div>',
|
||||||
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
"fbd_all": '<div id="blazy">showing <code>{0}</code> of <code>{1}</code> files; <a href="#" id="bd_all">show all</a></div>',
|
||||||
|
|
||||||
|
"f_dls": 'the file links in the current folder have\nbeen changed into download links',
|
||||||
|
|
||||||
"ft_paste": "paste {0} items$NHotkey: ctrl-V",
|
"ft_paste": "paste {0} items$NHotkey: ctrl-V",
|
||||||
"fr_eperm": 'cannot rename:\nyou do not have “move” permission in this folder',
|
"fr_eperm": 'cannot rename:\nyou do not have “move” permission in this folder',
|
||||||
"fd_eperm": 'cannot delete:\nyou do not have “delete” permission in this folder',
|
"fd_eperm": 'cannot delete:\nyou do not have “delete” permission in this folder',
|
||||||
@@ -534,6 +539,9 @@ var Ls = {
|
|||||||
"ht_and": " og ",
|
"ht_and": " og ",
|
||||||
|
|
||||||
"goh": "kontrollpanel",
|
"goh": "kontrollpanel",
|
||||||
|
"gop": 'naviger til mappen før denne">forr.',
|
||||||
|
"gou": 'naviger ett nivå opp">opp',
|
||||||
|
"gon": 'naviger til mappen etter denne">neste',
|
||||||
"logout": "Logg ut ",
|
"logout": "Logg ut ",
|
||||||
"access": " tilgang",
|
"access": " tilgang",
|
||||||
"ot_close": "lukk verktøy",
|
"ot_close": "lukk verktøy",
|
||||||
@@ -697,6 +705,8 @@ var Ls = {
|
|||||||
"fbd_more": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_more">vis {2}</a> eller <a href="#" id="bd_all">vis alle</a></div>',
|
"fbd_more": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_more">vis {2}</a> eller <a href="#" id="bd_all">vis alle</a></div>',
|
||||||
"fbd_all": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_all">vis alle</a></div>',
|
"fbd_all": '<div id="blazy">viser <code>{0}</code> av <code>{1}</code> filer; <a href="#" id="bd_all">vis alle</a></div>',
|
||||||
|
|
||||||
|
"f_dls": 'linkene i denne mappen er nå\nomgjort til nedlastningsknapper',
|
||||||
|
|
||||||
"ft_paste": "Lim inn {0} filer$NSnarvei: ctrl-V",
|
"ft_paste": "Lim inn {0} filer$NSnarvei: ctrl-V",
|
||||||
"fr_eperm": 'kan ikke endre navn:\ndu har ikke “move”-rettigheten i denne mappen',
|
"fr_eperm": 'kan ikke endre navn:\ndu har ikke “move”-rettigheten i denne mappen',
|
||||||
"fd_eperm": 'kan ikke slette:\ndu har ikke “delete”-rettigheten i denne mappen',
|
"fd_eperm": 'kan ikke slette:\ndu har ikke “delete”-rettigheten i denne mappen',
|
||||||
@@ -943,7 +953,7 @@ ebi('op_up2k').innerHTML = (
|
|||||||
|
|
||||||
'<table id="u2conf">\n' +
|
'<table id="u2conf">\n' +
|
||||||
' <tr>\n' +
|
' <tr>\n' +
|
||||||
' <td class="c"><br />' + L.ul_par + '</td>\n' +
|
' <td class="c" data-perm="read"><br />' + L.ul_par + '</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="' + L.ut_mt + '">🏃</label>\n' +
|
' <label for="multitask" tt="' + L.ut_mt + '">🏃</label>\n' +
|
||||||
@@ -964,7 +974,7 @@ ebi('op_up2k').innerHTML = (
|
|||||||
' <td data-perm="read" rowspan="2" id="u2c3w"></td>\n' +
|
' <td data-perm="read" rowspan="2" id="u2c3w"></td>\n' +
|
||||||
' </tr>\n' +
|
' </tr>\n' +
|
||||||
' <tr>\n' +
|
' <tr>\n' +
|
||||||
' <td class="c">\n' +
|
' <td class="c" data-perm="read">\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="' + L.ut_par + '"/><a\n' +
|
' class="txtbox" id="nthread" value="2" tt="' + L.ut_par + '"/><a\n' +
|
||||||
' href="#" class="b" id="nthread_add">+</a><br /> \n' +
|
' href="#" class="b" id="nthread_add">+</a><br /> \n' +
|
||||||
@@ -2078,8 +2088,13 @@ function prev_song(e) {
|
|||||||
return song_skip(-1);
|
return song_skip(-1);
|
||||||
}
|
}
|
||||||
function dl_song() {
|
function dl_song() {
|
||||||
if (!mp || !mp.au)
|
if (!mp || !mp.au) {
|
||||||
return;
|
var o = QSA('#files a[id]');
|
||||||
|
for (var a = 0; a < o.length; a++)
|
||||||
|
o[a].setAttribute('download', '');
|
||||||
|
|
||||||
|
return toast.inf(10, L.f_dls);
|
||||||
|
}
|
||||||
|
|
||||||
var url = mp.tracks[mp.au.tid];
|
var url = mp.tracks[mp.au.tid];
|
||||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987';
|
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987';
|
||||||
@@ -2865,7 +2880,7 @@ function eval_hash() {
|
|||||||
|
|
||||||
// folder nav
|
// folder nav
|
||||||
ebi('goh').parentElement.appendChild(mknod('span', null,
|
ebi('goh').parentElement.appendChild(mknod('span', null,
|
||||||
'<a href="#" id="gop">prev</a>/<a href="#" id="gou">up</a>/<a href="#" id="gon">next</a>'));
|
'<a href="#" id="gop" tt="' + L.gop + '</a>/<a href="#" id="gou" tt="' + L.gou + '</a>/<a href="#" id="gon" tt="' + L.gon + '</a>'));
|
||||||
ebi('gop').onclick = function () { tree_neigh(-1); }
|
ebi('gop').onclick = function () { tree_neigh(-1); }
|
||||||
ebi('gon').onclick = function () { tree_neigh(1); }
|
ebi('gon').onclick = function () { tree_neigh(1); }
|
||||||
ebi('gou').onclick = function () { tree_up(true); }
|
ebi('gou').onclick = function () { tree_up(true); }
|
||||||
@@ -3641,7 +3656,7 @@ var showfile = (function () {
|
|||||||
qsr('#prism_css');
|
qsr('#prism_css');
|
||||||
var el = mknod('link', 'prism_css');
|
var el = mknod('link', 'prism_css');
|
||||||
el.rel = 'stylesheet';
|
el.rel = 'stylesheet';
|
||||||
el.href = '/.cpr/deps/prism' + (light ? '' : 'd') + '.css';
|
el.href = SR + '/.cpr/deps/prism' + (light ? '' : 'd') + '.css';
|
||||||
document.head.appendChild(el);
|
document.head.appendChild(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3769,7 +3784,7 @@ var showfile = (function () {
|
|||||||
if (!defer)
|
if (!defer)
|
||||||
fun(el.firstChild);
|
fun(el.firstChild);
|
||||||
else
|
else
|
||||||
import_js('/.cpr/deps/prism.js', function () { fun(); });
|
import_js(SR + '/.cpr/deps/prism.js', function () { fun(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4053,7 +4068,7 @@ var thegrid = (function () {
|
|||||||
var oth = ebi(this.getAttribute('ref')),
|
var oth = ebi(this.getAttribute('ref')),
|
||||||
href = noq_href(this),
|
href = noq_href(this),
|
||||||
aplay = ebi('a' + oth.getAttribute('id')),
|
aplay = ebi('a' + oth.getAttribute('id')),
|
||||||
is_img = /\.(gif|jpe?g|png|webp|webm|mkv|mp4)(\?|$)/i.test(href),
|
is_img = /\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp|webm|mkv|mp4)(\?|$)/i.test(href),
|
||||||
is_dir = href.endsWith('/'),
|
is_dir = href.endsWith('/'),
|
||||||
in_tree = is_dir && treectl.find(oth.textContent.slice(0, -1)),
|
in_tree = is_dir && treectl.find(oth.textContent.slice(0, -1)),
|
||||||
have_sel = QS('#files tr.sel'),
|
have_sel = QS('#files tr.sel'),
|
||||||
@@ -4175,10 +4190,10 @@ var thegrid = (function () {
|
|||||||
if (r.thumbs) {
|
if (r.thumbs) {
|
||||||
ihref += '?th=' + (have_webp ? 'w' : 'j');
|
ihref += '?th=' + (have_webp ? 'w' : 'j');
|
||||||
if (href == "#")
|
if (href == "#")
|
||||||
ihref = '/.cpr/ico/⏏️';
|
ihref = SR + '/.cpr/ico/⏏️';
|
||||||
}
|
}
|
||||||
else if (isdir) {
|
else if (isdir) {
|
||||||
ihref = '/.cpr/ico/folder';
|
ihref = SR + '/.cpr/ico/folder';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var ar = href.split('.');
|
var ar = href.split('.');
|
||||||
@@ -4203,7 +4218,7 @@ var thegrid = (function () {
|
|||||||
else
|
else
|
||||||
ext = "unk";
|
ext = "unk";
|
||||||
}
|
}
|
||||||
ihref = '/.cpr/ico/' + ext;
|
ihref = SR + '/.cpr/ico/' + ext;
|
||||||
}
|
}
|
||||||
ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i';
|
ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i';
|
||||||
|
|
||||||
@@ -4778,7 +4793,7 @@ document.onkeydown = function (e) {
|
|||||||
clearTimeout(search_timeout);
|
clearTimeout(search_timeout);
|
||||||
|
|
||||||
var xhr = new XHR();
|
var xhr = new XHR();
|
||||||
xhr.open('POST', '/?srch', true);
|
xhr.open('POST', SR + '/?srch', true);
|
||||||
xhr.setRequestHeader('Content-Type', 'text/plain');
|
xhr.setRequestHeader('Content-Type', 'text/plain');
|
||||||
xhr.onload = xhr.onerror = xhr_search_results;
|
xhr.onload = xhr.onerror = xhr_search_results;
|
||||||
xhr.ts = Date.now();
|
xhr.ts = Date.now();
|
||||||
@@ -5317,7 +5332,12 @@ var treectl = (function () {
|
|||||||
treegrow.call(this.previousSibling, e);
|
treegrow.call(this.previousSibling, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
r.reqls(this.getAttribute('href'), true);
|
var href = this.getAttribute('href');
|
||||||
|
if (R && !href.startsWith(SR)) {
|
||||||
|
window.location = href;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r.reqls(href, true);
|
||||||
r.dir_cb = tree_scrollto;
|
r.dir_cb = tree_scrollto;
|
||||||
thegrid.setvis(true);
|
thegrid.setvis(true);
|
||||||
}
|
}
|
||||||
@@ -5541,7 +5561,7 @@ var treectl = (function () {
|
|||||||
qsr('#bbsw');
|
qsr('#bbsw');
|
||||||
if (ls0 === null) {
|
if (ls0 === null) {
|
||||||
var xhr = new XHR();
|
var xhr = new XHR();
|
||||||
xhr.open('GET', '/?am_js', true);
|
xhr.open('GET', SR + '/?am_js', true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
|
|
||||||
r.ls_cb = showfile.addlinks;
|
r.ls_cb = showfile.addlinks;
|
||||||
@@ -5739,7 +5759,8 @@ function apply_perms(newperms) {
|
|||||||
|
|
||||||
ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
|
ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
|
||||||
'</span></span><span' + aclass + axs + L.access + '</span>' + (acct != '*' ?
|
'</span></span><span' + aclass + axs + L.access + '</span>' + (acct != '*' ?
|
||||||
'<a href="/?pw=x">' + L.logout + acct + '</a>' : '<a href="/?h">Login</a>');
|
'<a href="' + SR + '/?pw=x">' + L.logout + acct + '</a>' :
|
||||||
|
'<a href="' + SR + '/?h">Login</a>');
|
||||||
|
|
||||||
var o = QSA('#ops>a[data-perm]');
|
var o = QSA('#ops>a[data-perm]');
|
||||||
for (var a = 0; a < o.length; a++) {
|
for (var a = 0; a < o.length; a++) {
|
||||||
@@ -6546,7 +6567,7 @@ function show_md(md, name, div, url, depth) {
|
|||||||
if (depth)
|
if (depth)
|
||||||
return toast.warn(10, errmsg + 'failed to load marked.js')
|
return toast.warn(10, errmsg + 'failed to load marked.js')
|
||||||
|
|
||||||
return import_js('/.cpr/deps/marked.js', function () {
|
return import_js(SR + '/.cpr/deps/marked.js', function () {
|
||||||
show_md(md, name, div, url, 1);
|
show_md(md, name, div, url, 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -6678,7 +6699,7 @@ var unpost = (function () {
|
|||||||
else
|
else
|
||||||
html.push('-- <em>' + (filt.value ? L.un_no2 : L.un_no1) + '</em>');
|
html.push('-- <em>' + (filt.value ? L.un_no2 : L.un_no1) + '</em>');
|
||||||
|
|
||||||
var mods = [1000, 100, 10];
|
var mods = [10, 100, 1000];
|
||||||
for (var a = 0; a < res.length; a++) {
|
for (var a = 0; a < res.length; a++) {
|
||||||
for (var b = 0; b < mods.length; b++)
|
for (var b = 0; b < mods.length; b++)
|
||||||
if (a % mods[b] == 0 && res.length > a + mods[b] / 10)
|
if (a % mods[b] == 0 && res.length > a + mods[b] / 10)
|
||||||
@@ -6699,7 +6720,7 @@ var unpost = (function () {
|
|||||||
r.me = me;
|
r.me = me;
|
||||||
}
|
}
|
||||||
|
|
||||||
var q = '/?ups';
|
var q = SR + '/?ups';
|
||||||
if (filt.value)
|
if (filt.value)
|
||||||
q += '&filter=' + uricom_enc(filt.value, true);
|
q += '&filter=' + uricom_enc(filt.value, true);
|
||||||
|
|
||||||
@@ -6765,7 +6786,7 @@ var unpost = (function () {
|
|||||||
var xhr = new XHR();
|
var xhr = new XHR();
|
||||||
xhr.n = n;
|
xhr.n = n;
|
||||||
xhr.n2 = n2;
|
xhr.n2 = n2;
|
||||||
xhr.open('POST', '/?delete&lim=' + req.length, true);
|
xhr.open('POST', SR + '/?delete&lim=' + req.length, true);
|
||||||
xhr.onload = xhr.onerror = unpost_delete_cb;
|
xhr.onload = xhr.onerror = unpost_delete_cb;
|
||||||
xhr.send(JSON.stringify(req));
|
xhr.send(JSON.stringify(req));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<div>{{ logues[1] }}</div><br />
|
<div>{{ logues[1] }}</div><br />
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h2><a href="/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#333">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/md.css?_={{ ts }}">
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<link rel="stylesheet" href="/.cpr/md2.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/md2.css?_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -128,7 +128,8 @@ write markdown (most html is 🙆 too)
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var last_modified = {{ lastmod }},
|
var SR = {{ r|tojson }},
|
||||||
|
last_modified = {{ lastmod }},
|
||||||
have_emp = {{ have_emp|tojson }},
|
have_emp = {{ have_emp|tojson }},
|
||||||
dfavico = "{{ favico }}";
|
dfavico = "{{ favico }}";
|
||||||
|
|
||||||
@@ -153,10 +154,10 @@ l.light = drk? 0:1;
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/md.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/md.js?_={{ ts }}"></script>
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<script src="/.cpr/md2.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#333">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/mde.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" href="/.cpr/deps/easymde.css?_={{ ts }}">
|
<link rel="stylesheet" href="{{ r }}/.cpr/deps/easymde.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mw">
|
<div id="mw">
|
||||||
@@ -26,7 +26,8 @@
|
|||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var last_modified = {{ lastmod }},
|
var SR = {{ r|tojson }},
|
||||||
|
last_modified = {{ lastmod }},
|
||||||
have_emp = {{ have_emp|tojson }},
|
have_emp = {{ have_emp|tojson }},
|
||||||
dfavico = "{{ favico }}";
|
dfavico = "{{ favico }}";
|
||||||
|
|
||||||
@@ -48,8 +49,8 @@ l.light = drk? 0:1;
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/mde.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#333">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/msg.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ html {
|
|||||||
h1 {
|
h1 {
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
margin: 2em 0 .4em 0;
|
margin: 2em 0 .4em 0;
|
||||||
padding: 0 0 .2em 0;
|
padding: 0;
|
||||||
|
line-height: 1em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
@@ -26,6 +27,7 @@ a {
|
|||||||
color: #047;
|
color: #047;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
border-bottom: 1px solid #8ab;
|
border-bottom: 1px solid #8ab;
|
||||||
border-radius: .2em;
|
border-radius: .2em;
|
||||||
padding: .2em .6em;
|
padding: .2em .6em;
|
||||||
|
|||||||
@@ -8,19 +8,19 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#333">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
<a id="a" href="/?h" class="af">refresh</a>
|
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
||||||
<a id="v" href="/?hc" class="af">connect</a>
|
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
||||||
|
|
||||||
{%- if this.uname == '*' %}
|
{%- if this.uname == '*' %}
|
||||||
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<a id="c" href="/?pw=x" class="logout">logout</a>
|
<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
|
||||||
<p><span id="m">welcome back,</span> <strong>{{ this.uname }}</strong></p>
|
<p><span id="m">welcome back,</span> <strong>{{ this.uname }}</strong></p>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a id="d" href="/?stack">dump stack</a>
|
<a id="d" href="{{ r }}/?stack">dump stack</a>
|
||||||
<a id="e" href="/?reload=cfg">reload cfg</a>
|
<a id="e" href="{{ r }}/?reload=cfg">reload cfg</a>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
@@ -79,18 +79,18 @@
|
|||||||
<h1 id="cc">client config:</h1>
|
<h1 id="cc">client config:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% if k304 %}
|
{% if k304 %}
|
||||||
<li><a id="h" href="/?k304=n">disable k304</a> (currently enabled)
|
<li><a id="h" href="{{ r }}/?k304=n">disable k304</a> (currently enabled)
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<li><a id="i" href="/?k304=y" class="r">enable k304</a> (currently disabled)
|
<li><a id="i" href="{{ r }}/?k304=y" class="r">enable k304</a> (currently disabled)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<blockquote id="j">enabling this will disconnect your client on every HTTP 304, which can prevent some buggy proxies from getting stuck (suddenly not loading pages), <em>but</em> it will also make things slower in general</blockquote></li>
|
<blockquote id="j">enabling this will disconnect your client on every HTTP 304, which can prevent some buggy proxies from getting stuck (suddenly not loading pages), <em>but</em> it will also make things slower in general</blockquote></li>
|
||||||
|
|
||||||
<li><a id="k" href="/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h1 id="l">login for more:</h1>
|
<h1 id="l">login for more:</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<form method="post" enctype="multipart/form-data" action="/{{ qvpath }}">
|
<form method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||||
<input type="hidden" name="act" value="login" />
|
<input type="hidden" name="act" value="login" />
|
||||||
<input type="password" name="cppwd" />
|
<input type="password" name="cppwd" />
|
||||||
<input type="submit" value="Login" />
|
<input type="submit" value="Login" />
|
||||||
@@ -100,13 +100,14 @@
|
|||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var lang="{{ lang }}",
|
var SR = {{ r|tojson }},
|
||||||
|
lang="{{ lang }}",
|
||||||
dfavico="{{ favico }}";
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
document.documentElement.className=localStorage.theme||"{{ this.args.theme }}";
|
document.documentElement.className=localStorage.theme||"{{ this.args.theme }}";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/splash.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ var Ls = {
|
|||||||
"l1": "logg inn:",
|
"l1": "logg inn:",
|
||||||
"m1": "velkommen tilbake,",
|
"m1": "velkommen tilbake,",
|
||||||
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
"n1": "404: filen finnes ikke ┐( ´ -`)┌",
|
||||||
"o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller <a href="/?h">gå hjem</a>',
|
"o1": 'eller kanskje du ikke har tilgang? prøv å logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||||
"p1": "403: tilgang nektet ~┻━┻",
|
"p1": "403: tilgang nektet ~┻━┻",
|
||||||
"q1": 'du må logge inn eller <a href="/?h">gå hjem</a>',
|
"q1": 'du må logge inn eller <a href="' + SR + '/?h">gå hjem</a>',
|
||||||
"r1": "gå hjem",
|
"r1": "gå hjem",
|
||||||
".s1": "kartlegg",
|
".s1": "kartlegg",
|
||||||
"t1": "handling",
|
"t1": "handling",
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<meta name="theme-color" content="#333">
|
<meta name="theme-color" content="#333">
|
||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap" class="w">
|
<div id="wrap" class="w">
|
||||||
<div class="cn">
|
<div class="cn">
|
||||||
<p class="btns"><a href="/">browse files</a> // <a href="/?h">control panel</a></p>
|
<p class="btns"><a href="{{ r }}/{{ vp }}">browse files</a> // <a href="{{ r }}/?h">control panel</a></p>
|
||||||
<p>or choose your OS for cooler alternatives:</p>
|
<p>or choose your OS for cooler alternatives:</p>
|
||||||
<div class="ossel">
|
<div class="ossel">
|
||||||
<a id="swin" href="#">Windows</a>
|
<a id="swin" href="#">Windows</a>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
make this server appear on your computer as a regular HDD!<br />
|
make this server appear on your computer as a regular HDD!<br />
|
||||||
pick your favorite below (sorted by performance, best first) and lets 🎉<br />
|
pick your favorite below (sorted by performance, best first) and lets 🎉<br />
|
||||||
<br />
|
<br />
|
||||||
placeholders:
|
<span class="os win lin mac">placeholders:</span>
|
||||||
<span class="os win">
|
<span class="os win">
|
||||||
{% if accs %}<code><b>{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
|
{% if accs %}<code><b>{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
|
||||||
</span>
|
</span>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>--no-check-certificate</code> to the mount command</em><br />---</p>
|
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>--no-check-certificate</code> to the mount command</em><br />---</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
||||||
<pre>
|
<pre>
|
||||||
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ vp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ vp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
||||||
</pre>
|
</pre>
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
|
|
||||||
<h1>partyfuse</h1>
|
<h1>partyfuse</h1>
|
||||||
<p>
|
<p>
|
||||||
<a href="/.cpr/a/partyfuse.py">partyfuse.py</a> -- fast, read-only,
|
<a href="{{ r }}/.cpr/a/partyfuse.py">partyfuse.py</a> -- fast, read-only,
|
||||||
<span class="os win">needs <a href="https://winfsp.dev/rel/">winfsp</a></span>
|
<span class="os win">needs <a href="https://winfsp.dev/rel/">winfsp</a></span>
|
||||||
<span class="os lin">doesn't need root</span>
|
<span class="os lin">doesn't need root</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>-td</code></em></p>
|
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>-td</code></em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
you can use <a href="/.cpr/a/up2k.py">up2k.py</a> to upload (sometimes faster than web-browsers)
|
you can use <a href="{{ r }}/.cpr/a/up2k.py">up2k.py</a> to upload (sometimes faster than web-browsers)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -188,13 +188,14 @@
|
|||||||
<a href="#" id="repl">π</a>
|
<a href="#" id="repl">π</a>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var lang="{{ lang }}",
|
var SR = {{ r|tojson }},
|
||||||
|
lang="{{ lang }}",
|
||||||
dfavico="{{ favico }}";
|
dfavico="{{ favico }}";
|
||||||
|
|
||||||
document.documentElement.className=localStorage.theme||"{{ args.theme }}";
|
document.documentElement.className=localStorage.theme||"{{ args.theme }}";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/svcs.js?_={{ ts }}"></script>
|
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -39,4 +39,4 @@ function setos(os) {
|
|||||||
clmod(oa[a], 'g', oa[a].id.slice(1) == os);
|
clmod(oa[a], 'g', oa[a].id.slice(1) == os);
|
||||||
}
|
}
|
||||||
|
|
||||||
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : '');
|
setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : 'idk');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'scp';
|
font-family: 'scp';
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(/.cpr/deps/scp.woff2) format('woff2');
|
src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(deps/scp.woff2) format('woff2');
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
|
|||||||
@@ -672,7 +672,7 @@ function Donut(uc, st) {
|
|||||||
favico.upd();
|
favico.upd();
|
||||||
wintitle();
|
wintitle();
|
||||||
if (document.visibilityState == 'hidden')
|
if (document.visibilityState == 'hidden')
|
||||||
tenstrobe = setTimeout(enstrobe, 500); //debounce
|
tenstrobe = setTimeout(r.enstrobe, 500); //debounce
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -709,7 +709,7 @@ function Donut(uc, st) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function enstrobe() {
|
r.enstrobe = function () {
|
||||||
strobes = ['████████████████', '________________', '████████████████'];
|
strobes = ['████████████████', '________________', '████████████████'];
|
||||||
tstrober = setInterval(strobe, 300);
|
tstrober = setInterval(strobe, 300);
|
||||||
|
|
||||||
@@ -779,7 +779,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (window.WebAssembly && !hws.length)
|
if (window.WebAssembly && !hws.length)
|
||||||
fetch('/.cpr/w.hash.js' + CB);
|
fetch(SR + '/.cpr/w.hash.js' + CB);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
function showmodal(msg) {
|
function showmodal(msg) {
|
||||||
@@ -809,7 +809,7 @@ function up2k_init(subtle) {
|
|||||||
m = L.u_https1 + ' <a href="' + (window.location + '').replace(':', 's:') + '">' + L.u_https2 + '</a> ' + L.u_https3;
|
m = L.u_https1 + ' <a href="' + (window.location + '').replace(':', 's:') + '">' + L.u_https2 + '</a> ' + L.u_https3;
|
||||||
|
|
||||||
showmodal('<h1>loading ' + fn + '</h1>');
|
showmodal('<h1>loading ' + fn + '</h1>');
|
||||||
import_js('/.cpr/deps/' + fn, unmodal);
|
import_js(SR + '/.cpr/deps/' + fn, unmodal);
|
||||||
|
|
||||||
if (HTTPS) {
|
if (HTTPS) {
|
||||||
// chrome<37 firefox<34 edge<12 opera<24 safari<7
|
// chrome<37 firefox<34 edge<12 opera<24 safari<7
|
||||||
@@ -867,7 +867,7 @@ function up2k_init(subtle) {
|
|||||||
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
|
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
|
||||||
bcfg_bind(uc, 'hashw', 'hashw', !!window.WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw);
|
bcfg_bind(uc, 'hashw', 'hashw', !!window.WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw);
|
||||||
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
||||||
bcfg_bind(uc, 'upsfx', 'upsfx', false);
|
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
||||||
|
|
||||||
var st = {
|
var st = {
|
||||||
"files": [],
|
"files": [],
|
||||||
@@ -904,6 +904,16 @@ function up2k_init(subtle) {
|
|||||||
"u": "",
|
"u": "",
|
||||||
"t": ""
|
"t": ""
|
||||||
},
|
},
|
||||||
|
"etaw": {
|
||||||
|
"h": [['', 0, 0, 0]],
|
||||||
|
"u": [['', 0, 0, 0]],
|
||||||
|
"t": [['', 0, 0, 0]]
|
||||||
|
},
|
||||||
|
"etac": {
|
||||||
|
"h": 0,
|
||||||
|
"u": 0,
|
||||||
|
"t": 0
|
||||||
|
},
|
||||||
"car": 0,
|
"car": 0,
|
||||||
"slow_io": null,
|
"slow_io": null,
|
||||||
"oserr": false,
|
"oserr": false,
|
||||||
@@ -1312,7 +1322,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (window.WebAssembly && !hws.length) {
|
if (window.WebAssembly && !hws.length) {
|
||||||
for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++)
|
for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++)
|
||||||
hws.push(new Worker('/.cpr/w.hash.js' + CB));
|
hws.push(new Worker(SR + '/.cpr/w.hash.js' + CB));
|
||||||
|
|
||||||
console.log(hws.length + " hashers");
|
console.log(hws.length + " hashers");
|
||||||
}
|
}
|
||||||
@@ -1480,10 +1490,20 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var a = 0; a < t.length; a++) {
|
for (var a = 0; a < t.length; a++) {
|
||||||
var rem = st.bytes.total - t[a][2],
|
var hid = t[a][0],
|
||||||
bps = t[a][1] / t[a][3],
|
|
||||||
hid = t[a][0],
|
|
||||||
eid = hid.slice(-1),
|
eid = hid.slice(-1),
|
||||||
|
etaw = st.etaw[eid];
|
||||||
|
|
||||||
|
if (st.etac[eid] > 100) { // num chunks
|
||||||
|
st.etac[eid] = 0;
|
||||||
|
etaw.push(jcp(t[a]));
|
||||||
|
if (etaw.length > 5)
|
||||||
|
etaw.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = etaw[0],
|
||||||
|
rem = st.bytes.total - t[a][2],
|
||||||
|
bps = (t[a][1] - h[1]) / Math.max(0.1, t[a][3] - h[3]),
|
||||||
eta = Math.floor(rem / bps);
|
eta = Math.floor(rem / bps);
|
||||||
|
|
||||||
if (t[a][1] < 1024 || t[a][3] < 0.1) {
|
if (t[a][1] < 1024 || t[a][3] < 0.1) {
|
||||||
@@ -1535,11 +1555,11 @@ function up2k_init(subtle) {
|
|||||||
st.busy.handshake.length)
|
st.busy.handshake.length)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (t.n - st.car > 8)
|
if (t.n - st.car > Math.max(8, parallel_uploads))
|
||||||
// prevent runahead from a stuck upload (slow server hdd)
|
// prevent runahead from a stuck upload (slow server hdd)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ((uc.multitask ? 1 : 0) <
|
if ((uc.multitask ? parallel_uploads : 0) <
|
||||||
st.todo.upload.length +
|
st.todo.upload.length +
|
||||||
st.busy.upload.length)
|
st.busy.upload.length)
|
||||||
return false;
|
return false;
|
||||||
@@ -1551,21 +1571,22 @@ function up2k_init(subtle) {
|
|||||||
if (!parallel_uploads)
|
if (!parallel_uploads)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
var nhs = st.todo.handshake.length + st.busy.handshake.length,
|
||||||
|
nup = st.todo.upload.length + st.busy.upload.length;
|
||||||
|
|
||||||
if (uc.multitask) {
|
if (uc.multitask) {
|
||||||
|
if (nhs + nup < parallel_uploads)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (!uc.az)
|
if (!uc.az)
|
||||||
return st.todo.handshake.length + st.busy.handshake.length < 2;
|
return nhs < 2;
|
||||||
|
|
||||||
var ahead = st.bytes.hashed - st.bytes.finished,
|
var ahead = st.bytes.hashed - st.bytes.finished,
|
||||||
nmax = ahead < biggest_file / 8 ? 32 : 16;
|
nmax = ahead < biggest_file / 8 ? 32 : 16;
|
||||||
|
|
||||||
return ahead < biggest_file &&
|
return ahead < biggest_file && nhs < nmax;
|
||||||
st.todo.handshake.length + st.busy.handshake.length < nmax;
|
|
||||||
}
|
}
|
||||||
return handshakes_permitted() && 0 ==
|
return handshakes_permitted() && 0 == nhs + nup;
|
||||||
st.todo.handshake.length +
|
|
||||||
st.busy.handshake.length +
|
|
||||||
st.todo.upload.length +
|
|
||||||
st.busy.upload.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasker = (function () {
|
var tasker = (function () {
|
||||||
@@ -1730,20 +1751,22 @@ function up2k_init(subtle) {
|
|||||||
var sr = uc.fsearch,
|
var sr = uc.fsearch,
|
||||||
ok = pvis.ctr.ok,
|
ok = pvis.ctr.ok,
|
||||||
ng = pvis.ctr.ng,
|
ng = pvis.ctr.ng,
|
||||||
|
spd = Math.floor(st.bytes.finished / st.time.busy),
|
||||||
|
suf = '\n\n{0} @ {1}/s'.format(shumantime(st.time.busy), humansize(spd)),
|
||||||
t = uc.ask_up ? 0 : 10;
|
t = uc.ask_up ? 0 : 10;
|
||||||
|
|
||||||
console.log('toast', ok, ng);
|
console.log('toast', ok, ng);
|
||||||
|
|
||||||
if (ok && ng)
|
if (ok && ng)
|
||||||
toast.warn(t, uc.nagtxt = (sr ? L.ur_sm : L.ur_um).format(ok, ng));
|
toast.warn(t, uc.nagtxt = (sr ? L.ur_sm : L.ur_um).format(ok, ng) + suf);
|
||||||
else if (ok > 1)
|
else if (ok > 1)
|
||||||
toast.ok(t, uc.nagtxt = (sr ? L.ur_aso : L.ur_auo).format(ok));
|
toast.ok(t, uc.nagtxt = (sr ? L.ur_aso : L.ur_auo).format(ok) + suf);
|
||||||
else if (ok)
|
else if (ok)
|
||||||
toast.ok(t, uc.nagtxt = sr ? L.ur_1so : L.ur_1uo);
|
toast.ok(t, uc.nagtxt = (sr ? L.ur_1so : L.ur_1uo) + suf);
|
||||||
else if (ng > 1)
|
else if (ng > 1)
|
||||||
toast.err(t, uc.nagtxt = (sr ? L.ur_asn : L.ur_aun).format(ng));
|
toast.err(t, uc.nagtxt = (sr ? L.ur_asn : L.ur_aun).format(ng) + suf);
|
||||||
else if (ng)
|
else if (ng)
|
||||||
toast.err(t, uc.nagtxt = sr ? L.ur_1sn : L.ur_1un);
|
toast.err(t, uc.nagtxt = (sr ? L.ur_1sn : L.ur_1un) + suf);
|
||||||
|
|
||||||
timer.rm(etafun);
|
timer.rm(etafun);
|
||||||
timer.rm(donut.do);
|
timer.rm(donut.do);
|
||||||
@@ -1854,6 +1877,7 @@ function up2k_init(subtle) {
|
|||||||
cdr = Math.min(chunksize + car, t.size);
|
cdr = Math.min(chunksize + car, t.size);
|
||||||
|
|
||||||
st.bytes.hashed += cdr - car;
|
st.bytes.hashed += cdr - car;
|
||||||
|
st.etac.h++;
|
||||||
|
|
||||||
function orz(e) {
|
function orz(e) {
|
||||||
bpend--;
|
bpend--;
|
||||||
@@ -2357,8 +2381,17 @@ function up2k_init(subtle) {
|
|||||||
function can_upload_next() {
|
function can_upload_next() {
|
||||||
var upt = st.todo.upload[0],
|
var upt = st.todo.upload[0],
|
||||||
upf = st.files[upt.nfile],
|
upf = st.files[upt.nfile],
|
||||||
|
nhs = st.busy.handshake.length,
|
||||||
|
hs = nhs && st.busy.handshake[0],
|
||||||
now = Date.now();
|
now = Date.now();
|
||||||
|
|
||||||
|
if (nhs >= 16)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (hs && hs.t_uploaded && Date.now() - hs.t_busied > 10000)
|
||||||
|
// verification HS possibly held back by uploads
|
||||||
|
return false;
|
||||||
|
|
||||||
for (var a = 0, aa = st.busy.handshake.length; a < aa; a++) {
|
for (var a = 0, aa = st.busy.handshake.length; a < aa; a++) {
|
||||||
var hs = st.busy.handshake[a];
|
var hs = st.busy.handshake[a];
|
||||||
if (hs.n < upt.nfile && hs.t_busied > now - 10 * 1000 && !st.files[hs.n].bytes_uploaded)
|
if (hs.n < upt.nfile && hs.t_busied > now - 10 * 1000 && !st.files[hs.n].bytes_uploaded)
|
||||||
@@ -2403,6 +2436,8 @@ function up2k_init(subtle) {
|
|||||||
st.bytes.finished += cdr - car;
|
st.bytes.finished += cdr - car;
|
||||||
st.bytes.uploaded += cdr - car;
|
st.bytes.uploaded += cdr - car;
|
||||||
t.bytes_uploaded += cdr - car;
|
t.bytes_uploaded += cdr - car;
|
||||||
|
st.etac.u++;
|
||||||
|
st.etac.t++;
|
||||||
}
|
}
|
||||||
else if (txt.indexOf('already got that') + 1 ||
|
else if (txt.indexOf('already got that') + 1 ||
|
||||||
txt.indexOf('already being written') + 1) {
|
txt.indexOf('already being written') + 1) {
|
||||||
@@ -2530,9 +2565,15 @@ function up2k_init(subtle) {
|
|||||||
if (dir.target) {
|
if (dir.target) {
|
||||||
clmod(obj, 'err', 1);
|
clmod(obj, 'err', 1);
|
||||||
var v = Math.floor(parseInt(obj.value));
|
var v = Math.floor(parseInt(obj.value));
|
||||||
if (v < 0 || v > 64 || v !== v)
|
if (v < 0 || v !== v)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (v > 64) {
|
||||||
|
var p = obj.selectionStart;
|
||||||
|
v = obj.value = 64;
|
||||||
|
obj.selectionStart = obj.selectionEnd = p;
|
||||||
|
}
|
||||||
|
|
||||||
parallel_uploads = v;
|
parallel_uploads = v;
|
||||||
swrite('nthread', v);
|
swrite('nthread', v);
|
||||||
clmod(obj, 'err');
|
clmod(obj, 'err');
|
||||||
@@ -2749,6 +2790,21 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (en && Notification.permission == 'default')
|
if (en && Notification.permission == 'default')
|
||||||
Notification.requestPermission().then(chknag, chknag);
|
Notification.requestPermission().then(chknag, chknag);
|
||||||
|
|
||||||
|
set_upsfx(en);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_upsfx(en) {
|
||||||
|
if (!en)
|
||||||
|
return;
|
||||||
|
|
||||||
|
toast.inf(10, 'OK -- <a href="#" id="nagtest">test it!</a>')
|
||||||
|
|
||||||
|
ebi('nagtest').onclick = function () {
|
||||||
|
start_actx();
|
||||||
|
uc.nagtxt = ':^)';
|
||||||
|
setTimeout(donut.enstrobe, 200);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uc.upnag && (!window.Notification || Notification.permission != 'granted'))
|
if (uc.upnag && (!window.Notification || Notification.permission != 'granted'))
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ if (!window.console || !console.log)
|
|||||||
var wah = '',
|
var wah = '',
|
||||||
L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
|
L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
|
||||||
CB = '?_=' + Date.now(),
|
CB = '?_=' + Date.now(),
|
||||||
|
R = SR.slice(1),
|
||||||
|
RS = R ? "/" + R : "",
|
||||||
HALFMAX = 8192 * 8192 * 8192 * 8192,
|
HALFMAX = 8192 * 8192 * 8192 * 8192,
|
||||||
HTTPS = (window.location + '').indexOf('https:') === 0,
|
HTTPS = (window.location + '').indexOf('https:') === 0,
|
||||||
TOUCH = 'ontouchstart' in window,
|
TOUCH = 'ontouchstart' in window,
|
||||||
@@ -193,8 +195,12 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
|||||||
var lsk = Object.keys(ls);
|
var lsk = Object.keys(ls);
|
||||||
lsk.sort();
|
lsk.sort();
|
||||||
html.push('<p class="b">');
|
html.push('<p class="b">');
|
||||||
for (var a = 0; a < lsk.length; a++)
|
for (var a = 0; a < lsk.length; a++) {
|
||||||
|
if (ls[lsk[a]].length > 9000)
|
||||||
|
continue;
|
||||||
|
|
||||||
html.push(' <b>' + esc(lsk[a]) + '</b> <code>' + esc(ls[lsk[a]]) + '</code> ');
|
html.push(' <b>' + esc(lsk[a]) + '</b> <code>' + esc(ls[lsk[a]]) + '</code> ');
|
||||||
|
}
|
||||||
html.push('</p>');
|
html.push('</p>');
|
||||||
}
|
}
|
||||||
catch (e) { }
|
catch (e) { }
|
||||||
@@ -1678,7 +1684,7 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
|
|||||||
|
|
||||||
qsr('#cf_frame');
|
qsr('#cf_frame');
|
||||||
var fr = mknod('iframe', 'cf_frame');
|
var fr = mknod('iframe', 'cf_frame');
|
||||||
fr.src = '/?cf_challenge';
|
fr.src = SR + '/?cf_challenge';
|
||||||
document.body.appendChild(fr);
|
document.body.appendChild(fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ catch (ex) {
|
|||||||
}
|
}
|
||||||
function load_fb() {
|
function load_fb() {
|
||||||
subtle = null;
|
subtle = null;
|
||||||
importScripts('/.cpr/deps/sha512.hw.js');
|
importScripts('deps/sha512.hw.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,121 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2022-1230-0754 `v1.5.5` made in japan
|
||||||
|
|
||||||
|
hello from tokyo
|
||||||
|
* read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* image viewer now supports heif, avif, apng, svg
|
||||||
|
* [partyfuse and up2k.py](https://github.com/9001/copyparty/tree/hovudstraum/bin): option to read password from textfile
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* thumbnailing could fail if a primitive build of libvips is installed
|
||||||
|
* ssdp was wonky on dualstack ipv6
|
||||||
|
* mdns could crash on networks with invalid routes
|
||||||
|
* support fat32 timestamp precisions
|
||||||
|
* fixes spurious file reindexing in volumes located on SD cards on android tablets which lie about timestamps until the next device reboot or filesystem remount
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2022-1213-1956 `v1.5.3` folder-sync + turbo-rust
|
||||||
|
|
||||||
|
* read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* one-way folder sync (client to server) using [up2k.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/README.md#up2kpy) `-z --dr`
|
||||||
|
* great rsync alternative when combined with `-e2ds --hardlink` deduplication on the server
|
||||||
|
* **50x faster** when uploading small files to HDD, especially SMR
|
||||||
|
* by switching sqlite to WAL which carries a small chance of temporarily forgetting the ~200 most recent uploads if you have a power outage or your OS crashes; see `--help-dbd` if you have `-mtp` plugins which produces metadata you can't afford to lose
|
||||||
|
* location-based [reverse-proxying](https://github.com/9001/copyparty/#reverse-proxy) (but it's still recommended to use a dedicated domain/subdomain instead)
|
||||||
|
* IPv6 link-local automatically enabled for TCP and zeroconf on NICs without a routable IPv6
|
||||||
|
* zeroconf network filters now accept subnets too, for example `--z-on 192.168.0.0/16`
|
||||||
|
* `.hist` folders are hidden on windows
|
||||||
|
* ux:
|
||||||
|
* more accurate total ETA on upload
|
||||||
|
* sorting of batch-unpost links was unintuitive / dangerous
|
||||||
|
* hotkey `Y` turns files into download links if nothing's selected
|
||||||
|
* option to replace or disable the mediaplayer-toggle mouse cursor with `--mpmc`
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* WAL probably/hopefully fixes #10 (we'll know in 6 months roughly)
|
||||||
|
* repair db inconsistencies (which can happen if terminated during startup)
|
||||||
|
* [davfs2](https://wiki.archlinux.org/title/Davfs2) did not approve of the authentication prompt
|
||||||
|
* the `connect` button on the control-panel didn't work on phones
|
||||||
|
* couldn't specify windows NICs in arguments `--z-on` / `--z-off` and friends
|
||||||
|
* ssdp xml escaping for `--zsl` URL
|
||||||
|
* no longer possible to accidentally launch multiple copyparty instances on the same port on windows
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2022-1203-2048 `v1.5.1` babel
|
||||||
|
|
||||||
|
named after [that other thing](https://en.wikipedia.org/wiki/Tower_of_Babel), not [the song](https://soundcloud.com/kanaze/babel-dimension-0-remix)
|
||||||
|
* read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* new protocols!
|
||||||
|
* native IPv6 support, no longer requiring a reverse-proxy for that
|
||||||
|
* [webdav server](https://github.com/9001/copyparty#webdav-server) -- read/write-access to copyparty straight from windows explorer, macos finder, kde/gnome
|
||||||
|
* [smb/cifs server](https://github.com/9001/copyparty#smb-server) -- extremely buggy and unsafe, for when there is no other choice
|
||||||
|
* [zeroconf](https://github.com/9001/copyparty#zeroconf) -- copyparty announces itself on the LAN, showing up in various file managers
|
||||||
|
* [mdns](https://github.com/9001/copyparty#mdns) -- macos/kde/gnome + makes copyparty available at http://hostname.local/
|
||||||
|
* [ssdp](https://github.com/9001/copyparty#ssdp) -- windows
|
||||||
|
* commands to mount copyparty as a local disk are in the web-UI at control-panel --> `connect`
|
||||||
|
* detect buggy / malicious clients spamming the server with idle connections
|
||||||
|
* first tries to be nice with `Connection: close` (enough to fix windows-webdav)
|
||||||
|
* eventually bans the IP for `--loris` minutes (default: 1 hour)
|
||||||
|
* new arg `--xlink` for cross-volume detection of duplicate files on upload
|
||||||
|
* new arg `--no-snap` to disable upload tracking on restart
|
||||||
|
* will not create `.hist` folders unless required for thumbnails or markdown backups
|
||||||
|
* [config includes](https://github.com/9001/copyparty/blob/hovudstraum/docs/example2.conf) -- split your config across multiple config files
|
||||||
|
* ux improvements
|
||||||
|
* hotkey `?` shows a summary of all the hotkeys
|
||||||
|
* hotkey `Y` to download selected files
|
||||||
|
* position indicator when hovering over the audio scrubber
|
||||||
|
* textlabel on the volume slider
|
||||||
|
* placeholder values in textboxes
|
||||||
|
* options to hide scrollbars, compact media player, follow playing song
|
||||||
|
* phone-specific
|
||||||
|
* buttons for prev/next folder
|
||||||
|
* much better ui for hiding folder columns
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* now possible to upload files larger than 697 GiB
|
||||||
|
* technically a [breaking change](https://github.com/9001/copyparty#breaking-changes) if you wrote your own up2k client
|
||||||
|
* please let me know if you did because that's awesome
|
||||||
|
* several macos issues due to hardcoded syscall numbers
|
||||||
|
* sfx: fix python 3.12 support (forbids nullbytes in source code)
|
||||||
|
* use ctypes to discover network config -- fixes grapheneos, non-english windows
|
||||||
|
* detect firefox showing stale markdown documents in the editor
|
||||||
|
* detect+ban password bruteforcing on ftp too
|
||||||
|
* http 206 failing on empty files
|
||||||
|
* incorrect header timestamps on non-english locales
|
||||||
|
* remind ftp clients that you cannot cd into an image file -- fixes kde dolphin
|
||||||
|
* ux fixes
|
||||||
|
* uploader survives running into inaccessible folders
|
||||||
|
* middleclick documents in the textviewer sidebar to open in a new tab
|
||||||
|
* playing really long audio files (1 week or more) would spinlock the browser
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
* autodetect max number of clients based on OS limits
|
||||||
|
* `-nc` is probably no longer necessary when running behind a reverse-proxy
|
||||||
|
* allow/try playing mkv files in chrome
|
||||||
|
* markdown documents returned as plaintext unless `?v`
|
||||||
|
* only compress `-lo` logfiles if filename ends with `.xz`
|
||||||
|
* changed sfx compression from bz2 to gz
|
||||||
|
* startup is slightly faster
|
||||||
|
* better compatibility with embedded linux
|
||||||
|
* copyparty64.exe -- 64bit edition for [running inside WinPE](https://user-images.githubusercontent.com/241032/205454984-e6b550df-3c49-486d-9267-1614078dd0dd.png)
|
||||||
|
* which was an actual feature request, believe it or not!
|
||||||
|
* more attempts at avoiding the [firefox fd leak](https://bugzilla.mozilla.org/show_bug.cgi?id=1790500)
|
||||||
|
* if you are uploading many small files and the browser keeps crashing, use chrome instead
|
||||||
|
* or the commandline client, which is now available for download straight from copyparty
|
||||||
|
* control-panel --> `connect` --> `up2k.py`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2022-1013-1937 `v1.4.6` wav2opus
|
# 2022-1013-1937 `v1.4.6` wav2opus
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,13 @@
|
|||||||
* top
|
* top
|
||||||
* [future plans](#future-plans) - some improvement ideas
|
* [future plans](#future-plans) - some improvement ideas
|
||||||
* [design](#design)
|
* [design](#design)
|
||||||
|
* [up2k](#up2k) - quick outline of the up2k protocol
|
||||||
* [why chunk-hashes](#why-chunk-hashes) - a single sha512 would be better, right?
|
* [why chunk-hashes](#why-chunk-hashes) - a single sha512 would be better, right?
|
||||||
|
* [http api](#http-api)
|
||||||
|
* [read](#read)
|
||||||
|
* [write](#write)
|
||||||
|
* [admin](#admin)
|
||||||
|
* [general](#general)
|
||||||
* [assumptions](#assumptions)
|
* [assumptions](#assumptions)
|
||||||
* [mdns](#mdns)
|
* [mdns](#mdns)
|
||||||
* [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
|
||||||
@@ -35,7 +41,7 @@ some improvement ideas
|
|||||||
|
|
||||||
## up2k
|
## up2k
|
||||||
|
|
||||||
quick outline of the up2k protocol, see [uploading](#uploading) for the web-client
|
quick outline of the up2k protocol, see [uploading](https://github.com/9001/copyparty#uploading) for the web-client
|
||||||
* the up2k client splits a file into an "optimal" number of chunks
|
* the up2k client splits a file into an "optimal" number of chunks
|
||||||
* 1 MiB each, unless that becomes more than 256 chunks
|
* 1 MiB each, unless that becomes more than 256 chunks
|
||||||
* tries 1.5M, 2M, 3, 4, 6, ... until <= 256 chunks or size >= 32M
|
* tries 1.5M, 2M, 3, 4, 6, ... until <= 256 chunks or size >= 32M
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ FROM alpine:3.16
|
|||||||
WORKDIR /z
|
WORKDIR /z
|
||||||
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
||||||
ver_hashwasm=4.9.0 \
|
ver_hashwasm=4.9.0 \
|
||||||
ver_marked=4.2.3 \
|
ver_marked=4.2.5 \
|
||||||
ver_mde=2.18.0 \
|
ver_mde=2.18.0 \
|
||||||
ver_codemirror=5.65.10 \
|
ver_codemirror=5.65.11 \
|
||||||
ver_fontawesome=5.13.0 \
|
ver_fontawesome=5.13.0 \
|
||||||
ver_zopfli=1.0.3
|
ver_zopfli=1.0.3
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ font-family: 'fa';
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url("/.cpr/deps/mini-fa.woff") format("woff");
|
src: url("mini-fa.woff") format("woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa,
|
.fa,
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ if possible, for performance and security reasons, please use this instead:
|
|||||||
https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py
|
https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print(v.replace("\n", "\n▒▌ ")[1:] + "\n")
|
try:
|
||||||
|
print(v.replace("\n", "\n▒▌ ")[1:] + "\n")
|
||||||
|
except:
|
||||||
|
print(v.replace("\n", "\n|| ")[1:] + "\n")
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ def run_i(ld):
|
|||||||
|
|
||||||
def run_s(ld):
|
def run_s(ld):
|
||||||
# fmt: off
|
# fmt: off
|
||||||
c = "import sys,runpy;" + "".join(['sys.path.insert(0,r"' + x + '");' for x in ld]) + 'runpy.run_module("copyparty",run_name="__main__")'
|
c = "import sys,runpy;" + "".join(['sys.path.insert(0,r"' + x.replace("\\", "/") + '");' for x in ld]) + 'runpy.run_module("copyparty",run_name="__main__")'
|
||||||
c = [str(x) for x in [sys.executable, "-c", c] + list(sys.argv[1:])]
|
c = [str(x) for x in [sys.executable, "-c", c] + list(sys.argv[1:])]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
msg("\n", c, "\n")
|
msg("\n", c, "\n")
|
||||||
|
|||||||
1
setup.py
1
setup.py
@@ -107,6 +107,7 @@ args = {
|
|||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: Jython",
|
"Programming Language :: Python :: Implementation :: Jython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class Cfg(Namespace):
|
|||||||
ex = "df loris re_maxage rproxy rsp_slp s_wr_slp theme themes turbo"
|
ex = "df loris re_maxage rproxy rsp_slp s_wr_slp theme themes turbo"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "doctitle favico html_head log_fk mth textfiles"
|
ex = "doctitle favico html_head log_fk mth textfiles R RS SR"
|
||||||
ka.update(**{k: "" for k in ex.split()})
|
ka.update(**{k: "" for k in ex.split()})
|
||||||
|
|
||||||
super(Cfg, self).__init__(
|
super(Cfg, self).__init__(
|
||||||
@@ -118,6 +118,7 @@ class Cfg(Namespace):
|
|||||||
v=v or [],
|
v=v or [],
|
||||||
c=c,
|
c=c,
|
||||||
E=E,
|
E=E,
|
||||||
|
dbd="wal",
|
||||||
s_wr_sz=512 * 1024,
|
s_wr_sz=512 * 1024,
|
||||||
unpost=600,
|
unpost=600,
|
||||||
u2sort="s",
|
u2sort="s",
|
||||||
|
|||||||
Reference in New Issue
Block a user