Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
31
README.md
31
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
|
||||
* [themes](#themes)
|
||||
* [complete examples](#complete-examples)
|
||||
* [reverse-proxy](#reverse-proxy) - running copyparty next to other websites
|
||||
* [browser support](#browser-support) - TLDR: yes
|
||||
* [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
|
||||
* [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
|
||||
* [client-side](#client-side) - when uploading files
|
||||
* [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)
|
||||
* [client crashes](#client-crashes)
|
||||
* [frefox wsod](#frefox-wsod) - firefox 87 can crash during uploads
|
||||
* [HTTP API](#HTTP-API)
|
||||
* [read](#read)
|
||||
* [write](#write)
|
||||
* [admin](#admin)
|
||||
* [general](#general)
|
||||
* [HTTP API](#HTTP-API) - see [devnotes](#./docs/devnotes.md#http-api)
|
||||
* [dependencies](#dependencies) - mandatory deps
|
||||
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
||||
* [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/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:
|
||||
```
|
||||
@@ -724,6 +720,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
|
||||
|
||||
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
|
||||
|
||||
@@ -1068,6 +1068,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`
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
TLDR: yes
|
||||
@@ -1139,7 +1154,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`
|
||||
|
||||
* 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)
|
||||
* see [./bin/README.md#up2kpy](bin/README.md#up2kpy)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# [`up2k.py`](up2k.py)
|
||||
* command-line up2k client [(webm)](https://ocv.me/stuff/u2cli.webm)
|
||||
* 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
|
||||
|
||||
|
||||
|
||||
197
bin/up2k.py
197
bin/up2k.py
@@ -3,14 +3,12 @@ from __future__ import print_function, unicode_literals
|
||||
|
||||
"""
|
||||
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
|
||||
|
||||
- dependencies: requests
|
||||
- supports python 2.6, 2.7, and 3.3 through 3.11
|
||||
|
||||
- almost zero error-handling
|
||||
- but if something breaks just try again and it'll autoresume
|
||||
- supports python 2.6, 2.7, and 3.3 through 3.12
|
||||
- if something breaks just try again and it'll autoresume
|
||||
"""
|
||||
|
||||
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 = [" https://pypi.org/project/" + x + "/#files" for x in m.split()]
|
||||
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))
|
||||
sys.exit(1)
|
||||
@@ -262,10 +261,10 @@ def termsize():
|
||||
try:
|
||||
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:
|
||||
return
|
||||
return cr
|
||||
return None
|
||||
|
||||
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
||||
if not cr:
|
||||
@@ -275,12 +274,11 @@ def termsize():
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
if not cr:
|
||||
try:
|
||||
cr = (env["LINES"], env["COLUMNS"])
|
||||
except:
|
||||
cr = (25, 80)
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
try:
|
||||
return cr or (int(env["COLUMNS"]), int(env["LINES"]))
|
||||
except:
|
||||
return 80, 25
|
||||
|
||||
|
||||
class CTermsize(object):
|
||||
@@ -362,26 +360,29 @@ def walkdir(err, top, seen):
|
||||
|
||||
seen = seen[:] + [atop]
|
||||
for ap, inf in sorted(statdir(err, top)):
|
||||
yield ap, inf
|
||||
if stat.S_ISDIR(inf.st_mode):
|
||||
try:
|
||||
for x in walkdir(err, ap, seen):
|
||||
yield x
|
||||
except Exception as ex:
|
||||
err.append((ap, str(ex)))
|
||||
else:
|
||||
yield ap, inf
|
||||
|
||||
|
||||
def walkdirs(err, tops):
|
||||
"""recursive statdir for a list of tops, yields [top, relpath, stat]"""
|
||||
sep = "{0}".format(os.sep).encode("ascii")
|
||||
for top in tops:
|
||||
isdir = os.path.isdir(top)
|
||||
if top[-1:] == sep:
|
||||
stop = top.rstrip(sep)
|
||||
yield stop, b"", os.stat(stop)
|
||||
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, []):
|
||||
yield stop, ap[len(stop) :].lstrip(sep), inf
|
||||
else:
|
||||
@@ -472,14 +473,17 @@ def get_hashlist(file, pcb, mth):
|
||||
file.kchunks[k] = [v1, v2]
|
||||
|
||||
|
||||
def handshake(url, file, pw, search):
|
||||
# type: (str, File, Any, bool) -> tuple[list[str], bool]
|
||||
def handshake(ar, file, search):
|
||||
# type: (argparse.Namespace, File, bool) -> tuple[list[str], bool]
|
||||
"""
|
||||
performs a handshake with the server; reply is:
|
||||
if search, a list of search results
|
||||
otherwise, a list of chunks to upload
|
||||
"""
|
||||
|
||||
url = ar.url
|
||||
pw = ar.a
|
||||
|
||||
req = {
|
||||
"hash": [x[0] for x in file.cids],
|
||||
"name": file.name,
|
||||
@@ -488,11 +492,14 @@ def handshake(url, file, pw, search):
|
||||
}
|
||||
if search:
|
||||
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:
|
||||
headers["Cookie"] = "=".join(["cppwd", pw])
|
||||
|
||||
file.recheck = False
|
||||
if file.url:
|
||||
url = file.url
|
||||
elif b"/" in file.rel:
|
||||
@@ -540,7 +547,7 @@ def handshake(url, file, pw, search):
|
||||
|
||||
|
||||
def upload(file, cid, pw):
|
||||
# type: (File, str, Any) -> None
|
||||
# type: (File, str, str) -> None
|
||||
"""upload one specific chunk, `cid` (a chunk-hash)"""
|
||||
|
||||
headers = {
|
||||
@@ -564,27 +571,20 @@ def upload(file, cid, pw):
|
||||
|
||||
class Ctl(object):
|
||||
"""
|
||||
this will be the coordinator which runs everything in parallel
|
||||
(hashing, handshakes, uploads) but right now it's p dumb
|
||||
the coordinator which runs everything in parallel
|
||||
(hashing, handshakes, uploads)
|
||||
"""
|
||||
|
||||
def __init__(self, ar):
|
||||
self.ar = 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
|
||||
|
||||
def _scan(self):
|
||||
ar = self.ar
|
||||
eprint("\nscanning {0} locations\n".format(len(ar.files)))
|
||||
|
||||
nfiles = 0
|
||||
nbytes = 0
|
||||
err = []
|
||||
for _, _, inf in walkdirs(err, ar.files):
|
||||
if stat.S_ISDIR(inf.st_mode):
|
||||
continue
|
||||
|
||||
nfiles += 1
|
||||
nbytes += inf.st_size
|
||||
|
||||
@@ -606,8 +606,15 @@ class Ctl(object):
|
||||
return
|
||||
|
||||
eprint("found {0} files, {1}\n\n".format(nfiles, humansize(nbytes)))
|
||||
self.nfiles = nfiles
|
||||
self.nbytes = nbytes
|
||||
return nfiles, 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:
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
@@ -651,6 +658,9 @@ class Ctl(object):
|
||||
"""minimal basic slow boring fallback codepath"""
|
||||
search = self.ar.s
|
||||
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)
|
||||
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] + "/"
|
||||
while True:
|
||||
print(" hs...")
|
||||
hs, _ = handshake(self.ar.url, file, self.ar.a, search)
|
||||
hs, _ = handshake(self.ar, file, search)
|
||||
if search:
|
||||
if hs:
|
||||
for hit in hs:
|
||||
@@ -688,7 +698,7 @@ class Ctl(object):
|
||||
|
||||
eprint("finalizing {0} duplicate files".format(len(self.recheck)))
|
||||
for file in self.recheck:
|
||||
handshake(self.ar.url, file, self.ar.a, search)
|
||||
handshake(self.ar, file, search)
|
||||
|
||||
def _fancy(self):
|
||||
if VT100:
|
||||
@@ -765,7 +775,7 @@ class Ctl(object):
|
||||
|
||||
eprint("finalizing {0} duplicate files".format(len(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):
|
||||
ss.scroll_region(None)
|
||||
@@ -778,8 +788,10 @@ class Ctl(object):
|
||||
prd = None
|
||||
ls = {}
|
||||
for top, rel, inf in self.filegen:
|
||||
if self.ar.z:
|
||||
rd = os.path.dirname(rel)
|
||||
isdir = stat.S_ISDIR(inf.st_mode)
|
||||
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:
|
||||
prd = rd
|
||||
headers = {}
|
||||
@@ -788,19 +800,37 @@ class Ctl(object):
|
||||
|
||||
ls = {}
|
||||
try:
|
||||
print(" ls ~{0}".format(rd.decode("utf-8", "replace")))
|
||||
r = req_ses.get(
|
||||
self.ar.url.encode("utf-8") + quotep(rd) + b"?ls",
|
||||
headers=headers,
|
||||
)
|
||||
for f in r.json()["files"]:
|
||||
rfn = f["href"].split("?")[0].encode("utf-8", "replace")
|
||||
ls[unquote(rfn)] = f
|
||||
except:
|
||||
print(" mkdir ~{0}".format(rd.decode("utf-8", "replace")))
|
||||
print(" ls ~{0}".format(srd))
|
||||
zb = self.ar.url.encode("utf-8")
|
||||
zb += quotep(rd.replace(b"\\", b"/"))
|
||||
r = req_ses.get(zb + b"?ls&dots", headers=headers)
|
||||
if not r:
|
||||
raise Exception("HTTP {}".format(r.status_code))
|
||||
|
||||
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)
|
||||
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.nbytes -= inf.st_size
|
||||
continue
|
||||
@@ -850,7 +880,7 @@ class Ctl(object):
|
||||
self.handshaker_busy += 1
|
||||
|
||||
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 hs:
|
||||
for hit in hs:
|
||||
@@ -883,6 +913,9 @@ class Ctl(object):
|
||||
self.up_c += len(file.cids) - file.up_c
|
||||
self.up_b += file.size - file.up_b
|
||||
|
||||
if not file.recheck:
|
||||
self.up_done(file)
|
||||
|
||||
if hs and file.up_c:
|
||||
# some chunks failed
|
||||
self.up_c -= len(hs)
|
||||
@@ -917,7 +950,7 @@ class Ctl(object):
|
||||
upload(file, cid, self.ar.a)
|
||||
except:
|
||||
eprint("upload failed, retrying: {0} #{1}\n".format(file.name, cid[:8]))
|
||||
pass # handshake will fix it
|
||||
# handshake will fix it
|
||||
|
||||
with self.mutex:
|
||||
sz = file.kchunks[cid][1]
|
||||
@@ -933,6 +966,10 @@ class Ctl(object):
|
||||
self.up_c += 1
|
||||
self.uploader_busy -= 1
|
||||
|
||||
def up_done(self, file):
|
||||
if self.ar.dl:
|
||||
os.unlink(file.abs)
|
||||
|
||||
|
||||
class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
|
||||
pass
|
||||
@@ -960,18 +997,64 @@ source file/folder selection uses rsync syntax, meaning that:
|
||||
ap.add_argument("-a", metavar="PASSWORD", help="password")
|
||||
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 = 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.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("-nh", action="store_true", help="disable hashing while uploading")
|
||||
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 = app.add_argument_group("tls")
|
||||
ap.add_argument("-te", metavar="PEM_FILE", help="certificate to expect/verify")
|
||||
ap.add_argument("-td", action="store_true", help="disable certificate check")
|
||||
# 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.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__":
|
||||
|
||||
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
|
||||
# (not that i can really think of any good examples)
|
||||
#
|
||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||
|
||||
upstream cpp {
|
||||
server 127.0.0.1:3923;
|
||||
|
||||
@@ -54,6 +54,7 @@ except:
|
||||
HAVE_SSL = False
|
||||
|
||||
printed: list[str] = []
|
||||
u = unicode
|
||||
|
||||
|
||||
class RiceFormatter(argparse.HelpFormatter):
|
||||
@@ -238,18 +239,23 @@ def get_srvname() -> str:
|
||||
|
||||
|
||||
def ensure_locale() -> None:
|
||||
safe = "en_US.UTF-8"
|
||||
for x in [
|
||||
"en_US.UTF-8",
|
||||
safe,
|
||||
"English_United States.UTF8",
|
||||
"English_United States.1252",
|
||||
]:
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, x)
|
||||
lprint("Locale: {}\n".format(x))
|
||||
break
|
||||
if x != safe:
|
||||
lprint("Locale: {}\n".format(x))
|
||||
return
|
||||
except:
|
||||
continue
|
||||
|
||||
t = "setlocale {} failed,\n sorting and dates will be funky"
|
||||
warn(t.format(safe))
|
||||
|
||||
|
||||
def ensure_cert() -> None:
|
||||
"""
|
||||
@@ -267,8 +273,8 @@ def ensure_cert() -> None:
|
||||
try:
|
||||
if filecmp.cmp(cert_cfg, cert_insec):
|
||||
lprint(
|
||||
"\033[33m using default TLS certificate; https will be insecure."
|
||||
+ "\033[36m\n certificate location: {}\033[0m\n".format(cert_cfg)
|
||||
"\033[33musing default TLS certificate; https will be insecure."
|
||||
+ "\033[36m\ncertificate location: {}\033[0m\n".format(cert_cfg)
|
||||
)
|
||||
except:
|
||||
pass
|
||||
@@ -441,27 +447,8 @@ def showlic() -> None:
|
||||
print(f.read().decode("utf-8", "replace"))
|
||||
|
||||
|
||||
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, 3) # 4% faster than 4+ on py3.9 @ r5-4500U
|
||||
|
||||
tty = os.environ.get("TERM", "").lower() == "linux"
|
||||
|
||||
srvname = get_srvname()
|
||||
|
||||
sects = [
|
||||
def get_sects():
|
||||
return [
|
||||
[
|
||||
"accounts",
|
||||
"accounts and volumes",
|
||||
@@ -540,6 +527,7 @@ def run_argparse(
|
||||
\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[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[36mxdev\033[35m do not descend into other filesystems
|
||||
\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.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")
|
||||
@@ -619,6 +629,8 @@ def run_argparse(
|
||||
ap2.add_argument("--license", action="store_true", help="show licenses 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.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")
|
||||
@@ -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("--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.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")
|
||||
@@ -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("--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.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("--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("--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-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")
|
||||
|
||||
|
||||
def add_tls(ap):
|
||||
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("--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-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.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-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-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="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("--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.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-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-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="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("--zm6", action="store_true", help="IPv6 only")
|
||||
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-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.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-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-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="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("--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")
|
||||
|
||||
|
||||
def add_ftp(ap):
|
||||
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("--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-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.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-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.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)")
|
||||
@@ -720,6 +754,8 @@ def run_argparse(
|
||||
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
||||
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
||||
|
||||
|
||||
def add_optouts(ap):
|
||||
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("--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-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.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")
|
||||
@@ -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("--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.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("--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.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")
|
||||
@@ -765,11 +807,15 @@ def run_argparse(
|
||||
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")
|
||||
|
||||
|
||||
def add_admin(ap):
|
||||
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-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
||||
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.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)")
|
||||
@@ -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-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.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")
|
||||
|
||||
|
||||
def add_db_general(ap, hcores):
|
||||
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("-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-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("--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("--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("--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)")
|
||||
@@ -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-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.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")
|
||||
@@ -838,11 +892,14 @@ def run_argparse(
|
||||
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")
|
||||
|
||||
|
||||
def add_ui(ap, retry):
|
||||
ap2 = ap.add_argument_group('ui options')
|
||||
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("--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("--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("--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")
|
||||
@@ -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("--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.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")
|
||||
@@ -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("--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")
|
||||
# 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")
|
||||
sects = get_sects()
|
||||
for k, h, _ in sects:
|
||||
ap2.add_argument("--help-" + k, action="store_true", help=h)
|
||||
|
||||
@@ -981,8 +1087,11 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||
retry = True
|
||||
lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex()))
|
||||
|
||||
assert al # type: ignore
|
||||
al.E = E # __init__ is not shared when oxidized
|
||||
try:
|
||||
assert al # type: ignore
|
||||
al.E = E # __init__ is not shared when oxidized
|
||||
except:
|
||||
sys.exit(1)
|
||||
|
||||
if WINDOWS and not al.keep_qem:
|
||||
try:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 5, 1)
|
||||
VERSION = (1, 5, 3)
|
||||
CODENAME = "babel"
|
||||
BUILD_DT = (2022, 12, 3)
|
||||
BUILD_DT = (2022, 12, 13)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
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]:
|
||||
"""return [vfs,remainder]"""
|
||||
vpath = undot(vpath)
|
||||
if vpath == "":
|
||||
return self, ""
|
||||
|
||||
@@ -381,7 +380,7 @@ class VFS(object):
|
||||
rem = ""
|
||||
|
||||
if name in self.nodes:
|
||||
return self.nodes[name]._find(rem)
|
||||
return self.nodes[name]._find(undot(rem))
|
||||
|
||||
return self, vpath
|
||||
|
||||
@@ -389,7 +388,7 @@ class VFS(object):
|
||||
self, vpath: str, uname: str
|
||||
) -> tuple[bool, bool, bool, bool, bool, bool]:
|
||||
"""can Read,Write,Move,Delete,Get,Upget"""
|
||||
vn, _ = self._find(vpath)
|
||||
vn, _ = self._find(undot(vpath))
|
||||
c = vn.axs
|
||||
return (
|
||||
uname in c.uread or "*" in c.uread,
|
||||
@@ -419,7 +418,7 @@ class VFS(object):
|
||||
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
||||
raise Pebkac(404)
|
||||
|
||||
vn, rem = self._find(vpath)
|
||||
vn, rem = self._find(undot(vpath))
|
||||
c: AXS = vn.axs
|
||||
|
||||
for req, d, msg in [
|
||||
@@ -1136,6 +1135,12 @@ class AuthSrv(object):
|
||||
if k1 in vol.flags:
|
||||
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
|
||||
if "mte" not in vol.flags:
|
||||
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))]
|
||||
|
||||
|
||||
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)
|
||||
try:
|
||||
os.makedirs(bname, mode)
|
||||
return True
|
||||
except:
|
||||
if not exist_ok or not os.path.isdir(bname):
|
||||
raise
|
||||
return False
|
||||
|
||||
|
||||
def mkdir(p: str, mode: int = 0o755) -> None:
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
|
||||
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import queue
|
||||
|
||||
@@ -93,12 +94,15 @@ class BrokerMp(object):
|
||||
|
||||
else:
|
||||
# new ipc invoking managed service in hub
|
||||
obj = self.hub
|
||||
for node in dest.split("."):
|
||||
obj = getattr(obj, node)
|
||||
try:
|
||||
obj = self.hub
|
||||
for node in dest.split("."):
|
||||
obj = getattr(obj, node)
|
||||
|
||||
# TODO will deadlock if dest performs another ipc
|
||||
rv = try_exec(retq_id, obj, *args)
|
||||
# TODO will deadlock if dest performs another ipc
|
||||
rv = try_exec(retq_id, obj, *args)
|
||||
except:
|
||||
rv = ["exception", "stack", traceback.format_exc()]
|
||||
|
||||
if retq_id:
|
||||
proc.q_pend.put((retq_id, "retq", rv))
|
||||
|
||||
@@ -63,6 +63,7 @@ from .util import (
|
||||
read_socket_unbounded,
|
||||
relchk,
|
||||
ren_open,
|
||||
hidedir,
|
||||
s3enc,
|
||||
sanitize_fn,
|
||||
sendfile_kern,
|
||||
@@ -118,6 +119,7 @@ class HttpCli(object):
|
||||
# placeholders; assigned by run()
|
||||
self.keepalive = False
|
||||
self.is_https = False
|
||||
self.is_vproxied = False
|
||||
self.in_hdr_recv = True
|
||||
self.headers: dict[str, str] = {}
|
||||
self.mode = " "
|
||||
@@ -190,6 +192,7 @@ class HttpCli(object):
|
||||
|
||||
def j2s(self, name: str, **ka: Any) -> str:
|
||||
tpl = self.conn.hsrv.j2[name]
|
||||
ka["r"] = self.args.SR if self.is_vproxied else ""
|
||||
ka["ts"] = self.conn.hsrv.cachebuster()
|
||||
ka["lang"] = self.args.lang
|
||||
ka["favico"] = self.args.favico
|
||||
@@ -240,7 +243,7 @@ class HttpCli(object):
|
||||
self.http_ver = "HTTP/1.1"
|
||||
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
||||
self.keepalive = False
|
||||
h = {"WWW-Authenticate": "Basic"} if ex.code == 401 else {}
|
||||
h = {"WWW-Authenticate": 'Basic realm="a"'} if ex.code == 401 else {}
|
||||
try:
|
||||
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
||||
return self.keepalive
|
||||
@@ -252,7 +255,7 @@ class HttpCli(object):
|
||||
self.is_ancient = self.ua.startswith("Mozilla/4.")
|
||||
|
||||
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.is_https = (
|
||||
@@ -275,6 +278,7 @@ class HttpCli(object):
|
||||
self.log(t.format(self.args.rproxy, zso), c=3)
|
||||
|
||||
self.log_src = self.conn.set_rproxy(self.ip)
|
||||
self.is_vproxied = bool(self.args.R)
|
||||
|
||||
if self.is_banned():
|
||||
return False
|
||||
@@ -319,6 +323,13 @@ class HttpCli(object):
|
||||
else:
|
||||
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()}
|
||||
|
||||
if self.args.rsp_slp:
|
||||
@@ -447,7 +458,7 @@ class HttpCli(object):
|
||||
msg += "hint: important info in the server log\r\n"
|
||||
|
||||
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)
|
||||
return self.keepalive
|
||||
except Pebkac:
|
||||
@@ -603,10 +614,11 @@ class HttpCli(object):
|
||||
status: int = 200,
|
||||
use302: bool = False,
|
||||
) -> bool:
|
||||
vp = self.args.RS + vpath
|
||||
html = self.j2s(
|
||||
"msg",
|
||||
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,
|
||||
click=click,
|
||||
@@ -641,6 +653,15 @@ class HttpCli(object):
|
||||
if self.vpath.startswith(".cpr/ssdp"):
|
||||
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:])
|
||||
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>',
|
||||
}
|
||||
if not isdir:
|
||||
pvs["getcontenttype"] = guess_mime(rp)
|
||||
pvs["getcontenttype"] = html_escape(guess_mime(rp))
|
||||
pvs["getcontentlength"] = str(st.st_size)
|
||||
|
||||
for k, v in pvs.items():
|
||||
@@ -1126,7 +1147,11 @@ class HttpCli(object):
|
||||
if "multipart/form-data" in ctype:
|
||||
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()
|
||||
|
||||
if "application/octet-stream" in ctype:
|
||||
@@ -1293,7 +1318,7 @@ class HttpCli(object):
|
||||
if self.args.dotpart:
|
||||
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))
|
||||
and bos.path.exists(path)
|
||||
and not bos.path.getsize(path)
|
||||
@@ -1365,7 +1390,7 @@ class HttpCli(object):
|
||||
url = "{}://{}/{}".format(
|
||||
"https" if self.is_https else "http",
|
||||
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
|
||||
@@ -1530,11 +1555,6 @@ class HttpCli(object):
|
||||
if "delete" in self.uparam:
|
||||
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"])
|
||||
if "/" in name:
|
||||
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["vcfg"] = dbv.flags
|
||||
|
||||
if not self.can_delete:
|
||||
body.pop("replace", None)
|
||||
|
||||
if rem:
|
||||
dst = vfs.canonical(rem)
|
||||
try:
|
||||
@@ -1568,6 +1591,10 @@ class HttpCli(object):
|
||||
|
||||
x = self.conn.hsrv.broker.ask("up2k.handle_json", body)
|
||||
ret = x.get()
|
||||
if self.is_vproxied:
|
||||
if "purl" in ret:
|
||||
ret["purl"] = self.args.SR + ret["purl"]
|
||||
|
||||
ret = json.dumps(ret)
|
||||
self.log(ret)
|
||||
self.reply(ret.encode("utf-8"), mime="application/json")
|
||||
@@ -1626,6 +1653,10 @@ class HttpCli(object):
|
||||
if t not in order:
|
||||
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")
|
||||
self.reply(r, mime="application/json")
|
||||
return True
|
||||
@@ -2024,7 +2055,7 @@ class HttpCli(object):
|
||||
)[: vfs.flags["fk"]]
|
||||
|
||||
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(
|
||||
sha_hex[:56],
|
||||
sha_b64,
|
||||
@@ -2168,7 +2199,9 @@ class HttpCli(object):
|
||||
mdir, mfile = os.path.split(fp)
|
||||
mfile2 = "{}.{:.3f}.md".format(mfile[:-3], srv_lastmod)
|
||||
try:
|
||||
bos.mkdir(os.path.join(mdir, ".hist"))
|
||||
dp = os.path.join(mdir, ".hist")
|
||||
bos.mkdir(dp)
|
||||
hidedir(dp)
|
||||
except:
|
||||
pass
|
||||
bos.rename(fp, os.path.join(mdir, ".hist", mfile2))
|
||||
@@ -2527,6 +2560,7 @@ class HttpCli(object):
|
||||
|
||||
boundary = "\roll\tide"
|
||||
targs = {
|
||||
"r": self.args.SR if self.is_vproxied else "",
|
||||
"ts": self.conn.hsrv.cachebuster(),
|
||||
"svcname": self.args.doctitle,
|
||||
"html_head": self.html_head,
|
||||
@@ -2685,13 +2719,14 @@ class HttpCli(object):
|
||||
def tx_404(self, is_403: bool = False) -> bool:
|
||||
rc = 404
|
||||
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:
|
||||
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
|
||||
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)
|
||||
self.reply(html.encode("utf-8"), status=rc)
|
||||
return True
|
||||
@@ -2755,6 +2790,11 @@ class HttpCli(object):
|
||||
dst = dst[len(top) + 1 :]
|
||||
|
||||
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)
|
||||
self.reply(zs.encode("utf-8"), mime="application/json")
|
||||
return True
|
||||
@@ -2865,6 +2905,11 @@ class HttpCli(object):
|
||||
break
|
||||
|
||||
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")
|
||||
self.log("{} #{} {:.2f}sec".format(lm, len(ret), time.time() - t0))
|
||||
self.reply(jtxt, mime="application/json")
|
||||
@@ -2879,6 +2924,8 @@ class HttpCli(object):
|
||||
|
||||
if not req:
|
||||
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)
|
||||
lim = [nlim, nlim] if nlim else []
|
||||
@@ -2890,6 +2937,10 @@ class HttpCli(object):
|
||||
def handle_mv(self) -> bool:
|
||||
# full path of new loc (incl filename)
|
||||
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:
|
||||
raise Pebkac(400, "need dst vpath")
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ except ImportError:
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
from .__init__ import MACOS, TYPE_CHECKING, EnvParams
|
||||
from .__init__ import ANYWIN, MACOS, TYPE_CHECKING, EnvParams
|
||||
from .bos import bos
|
||||
from .httpconn import HttpConn
|
||||
from .util import (
|
||||
@@ -178,13 +178,11 @@ class HttpSrv(object):
|
||||
def listen(self, sck: socket.socket, nlisteners: int) -> None:
|
||||
if self.args.j != 1:
|
||||
# lost in the pickle; redefine
|
||||
try:
|
||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
except:
|
||||
pass
|
||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
if not ANYWIN or self.args.reuseaddr:
|
||||
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
|
||||
sck.settimeout(None) # < does not inherit, ^ opts above do
|
||||
|
||||
self.ip, self.port = sck.getsockname()[:2]
|
||||
self.srvs.append(sck)
|
||||
|
||||
@@ -313,25 +313,30 @@ class MDNS(MCast):
|
||||
self.running = False
|
||||
if not panic:
|
||||
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 = {}
|
||||
|
||||
def eat(self, buf: bytes, addr: tuple[str, int], sck: socket.socket) -> None:
|
||||
cip = addr[0]
|
||||
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
|
||||
|
||||
cache = self.rx6 if v6 else self.rx4
|
||||
if buf in cache.c:
|
||||
return
|
||||
|
||||
cache.add(buf)
|
||||
srv: Optional[MDNS_Sck] = self.srv[sck] if v6 else self.map_client(cip) # type: ignore
|
||||
if not srv:
|
||||
return
|
||||
|
||||
cache.add(buf)
|
||||
now = time.time()
|
||||
|
||||
if self.args.zmv and cip != srv.ip and cip not in srv.ips:
|
||||
|
||||
@@ -5,7 +5,14 @@ import socket
|
||||
import time
|
||||
|
||||
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 .util import MACOS, Netdev, min_ex, spack
|
||||
@@ -75,6 +82,7 @@ class MCast(object):
|
||||
|
||||
self.srv: dict[socket.socket, MC_Sck] = {} # listening sockets
|
||||
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.b4: 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[:]
|
||||
for lst in (on, off):
|
||||
for av in list(lst):
|
||||
try:
|
||||
arg_net = ip_network(av, False)
|
||||
except:
|
||||
arg_net = None
|
||||
|
||||
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:
|
||||
lst.append(sk)
|
||||
|
||||
@@ -166,9 +184,21 @@ class MCast(object):
|
||||
srv.ips[oth_ip.split("/")[0]] = ipaddress.ip_network(oth_ip, False)
|
||||
|
||||
# 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:
|
||||
self.log("no routable IPs on {}; skipping [{}]".format(netdev, ip), 3)
|
||||
self.log("no IPs on {}; skipping [{}]".format(netdev, ip), 3)
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -318,6 +348,16 @@ class MCast(object):
|
||||
# just give it something
|
||||
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:
|
||||
t = "new client on {} ({}): {}"
|
||||
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 .multicast import MC_Sck, MCast
|
||||
from .util import CachedSet, min_ex
|
||||
from .util import CachedSet, min_ex, html_escape
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .broker_util import BrokerCli
|
||||
@@ -73,13 +73,14 @@ class SSDPr(object):
|
||||
</device>
|
||||
</root>"""
|
||||
|
||||
c = html_escape
|
||||
sip, sport = hc.s.getsockname()[:2]
|
||||
proto = "https" if self.args.https_only else "http"
|
||||
ubase = "{}://{}:{}".format(proto, sip, sport)
|
||||
zsl = self.args.zsl
|
||||
url = zsl if "://" in zsl else ubase + "/" + zsl.lstrip("/")
|
||||
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"))
|
||||
return False # close connectino
|
||||
|
||||
@@ -147,17 +148,17 @@ class SSDPd(MCast):
|
||||
|
||||
def eat(self, buf: bytes, addr: tuple[str, int]) -> None:
|
||||
cip = addr[0]
|
||||
if cip.startswith("169.254"):
|
||||
if cip.startswith("169.254") and not self.ll_ok:
|
||||
return
|
||||
|
||||
if buf in self.rxc.c:
|
||||
return
|
||||
|
||||
self.rxc.add(buf)
|
||||
srv: Optional[SSDP_Sck] = self.map_client(cip) # type: ignore
|
||||
if not srv:
|
||||
return
|
||||
|
||||
self.rxc.add(buf)
|
||||
if not buf.startswith(b"M-SEARCH * HTTP/1."):
|
||||
raise Exception("not an ssdp message")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# coding: utf-8
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import stat
|
||||
import tarfile
|
||||
|
||||
from queue import Queue
|
||||
@@ -79,6 +80,9 @@ class StreamTar(StreamArc):
|
||||
src = f["ap"]
|
||||
fsi = f["st"]
|
||||
|
||||
if stat.S_ISDIR(fsi.st_mode):
|
||||
return
|
||||
|
||||
inf = tarfile.TarInfo(name=name)
|
||||
inf.mode = fsi.st_mode
|
||||
inf.size = fsi.st_size
|
||||
|
||||
@@ -296,10 +296,20 @@ class SvcHub(object):
|
||||
al.zm_off = al.zm_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"):
|
||||
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]
|
||||
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
|
||||
|
||||
def _setlimits(self) -> None:
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import print_function, unicode_literals
|
||||
|
||||
import calendar
|
||||
import time
|
||||
import stat
|
||||
import zlib
|
||||
|
||||
from .bos import bos
|
||||
@@ -238,6 +239,9 @@ class StreamZip(StreamArc):
|
||||
src = f["ap"]
|
||||
st = f["st"]
|
||||
|
||||
if stat.S_ISDIR(st.st_mode):
|
||||
return
|
||||
|
||||
sz = st.st_size
|
||||
ts = st.st_mtime
|
||||
|
||||
|
||||
@@ -121,6 +121,20 @@ class TcpSrv(object):
|
||||
else:
|
||||
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]] = {}
|
||||
qr2: dict[str, list[int]] = {}
|
||||
msgs = []
|
||||
@@ -128,7 +142,7 @@ class TcpSrv(object):
|
||||
title_vars = [x[1:] for x in self.args.wintitle.split(" ") if x.startswith("$")]
|
||||
t = "available @ {}://{}:{}/ (\033[33m{}\033[0m)"
|
||||
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
|
||||
|
||||
for port in sorted(self.args.p):
|
||||
@@ -195,13 +209,12 @@ class TcpSrv(object):
|
||||
def _listen(self, ip: str, port: int) -> None:
|
||||
ipv = socket.AF_INET6 if ":" in ip else socket.AF_INET
|
||||
srv = socket.socket(ipv, socket.SOCK_STREAM)
|
||||
try:
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
except:
|
||||
pass
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
if not ANYWIN or self.args.reuseaddr:
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 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:
|
||||
srv.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
|
||||
@@ -276,10 +289,6 @@ class TcpSrv(object):
|
||||
for nip in nic.ips:
|
||||
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
|
||||
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, "")
|
||||
eps[sip] = nd
|
||||
try:
|
||||
|
||||
@@ -97,14 +97,17 @@ class U2idx(object):
|
||||
return None
|
||||
|
||||
cur = None
|
||||
if ANYWIN:
|
||||
if ANYWIN and not bos.path.exists(db_path + "-wal"):
|
||||
uri = ""
|
||||
try:
|
||||
uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
|
||||
cur = sqlite3.connect(uri, 2, uri=True).cursor()
|
||||
cur.execute('pragma table_info("up")').fetchone()
|
||||
self.log("ro: {}".format(db_path))
|
||||
except:
|
||||
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:
|
||||
# on windows, this steals the write-lock from up2k.deferred_init --
|
||||
|
||||
@@ -38,6 +38,7 @@ from .util import (
|
||||
db_ex_chk,
|
||||
djoin,
|
||||
fsenc,
|
||||
hidedir,
|
||||
min_ex,
|
||||
quotep,
|
||||
ren_open,
|
||||
@@ -152,6 +153,7 @@ class Up2k(object):
|
||||
if ANYWIN:
|
||||
# usually fails to set lastmod too quickly
|
||||
self.lastmod_q: list[tuple[str, int, tuple[int, int], bool]] = []
|
||||
self.lastmod_q2 = self.lastmod_q[:]
|
||||
Daemon(self._lastmodder, "up2k-lastmod")
|
||||
|
||||
self.fstab = Fstab(self.log_func)
|
||||
@@ -180,6 +182,17 @@ class Up2k(object):
|
||||
all_vols = self.asrv.vfs.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":
|
||||
return self.hub.sigterm()
|
||||
|
||||
@@ -463,6 +476,10 @@ class Up2k(object):
|
||||
if next((zv for zv in vols if "e2ds" in zv.flags), None):
|
||||
self._block("indexing")
|
||||
|
||||
if self.args.re_dhash:
|
||||
self.args.re_dhash = False
|
||||
self._drop_caches()
|
||||
|
||||
for vol in vols:
|
||||
if self.stop:
|
||||
break
|
||||
@@ -550,6 +567,34 @@ class Up2k(object):
|
||||
if self.stop:
|
||||
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
|
||||
|
||||
msg = "{} volumes in {:.2f} sec"
|
||||
@@ -654,11 +699,45 @@ class Up2k(object):
|
||||
if not HAVE_SQLITE3 or "e2d" not in flags or "d2d" in flags:
|
||||
return None
|
||||
|
||||
bos.makedirs(histpath)
|
||||
if bos.makedirs(histpath):
|
||||
hidedir(histpath)
|
||||
|
||||
try:
|
||||
cur = self._open_db(db_path)
|
||||
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
|
||||
except:
|
||||
msg = "cannot use database at [{}]:\n{}"
|
||||
@@ -1179,6 +1258,18 @@ class Up2k(object):
|
||||
|
||||
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:
|
||||
if self.args.no_dhash:
|
||||
return False
|
||||
@@ -1727,9 +1818,13 @@ class Up2k(object):
|
||||
self._set_tagscan(write_cur, True)
|
||||
return ret
|
||||
|
||||
def _trace(self, msg: str) -> None:
|
||||
self.log("ST: {}".format(msg))
|
||||
|
||||
def _orz(self, db_path: str) -> "sqlite3.Cursor":
|
||||
return sqlite3.connect(db_path, self.timeout, check_same_thread=False).cursor()
|
||||
# x.set_trace_callback(trace)
|
||||
c = sqlite3.connect(db_path, self.timeout, check_same_thread=False).cursor()
|
||||
# c.connection.set_trace_callback(self._trace)
|
||||
return c
|
||||
|
||||
def _open_db(self, db_path: str) -> "sqlite3.Cursor":
|
||||
existed = bos.path.exists(db_path)
|
||||
@@ -2043,7 +2138,7 @@ class Up2k(object):
|
||||
job[k] = cj[k]
|
||||
|
||||
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"])
|
||||
if not self.args.nw:
|
||||
bos.unlink(dst) # TODO ed pls
|
||||
@@ -2090,7 +2185,7 @@ class Up2k(object):
|
||||
]:
|
||||
job[k] = cj[k]
|
||||
|
||||
for k in ["life"]:
|
||||
for k in ["life", "replace"]:
|
||||
if k in cj:
|
||||
job[k] = cj[k]
|
||||
|
||||
@@ -2122,10 +2217,18 @@ class Up2k(object):
|
||||
"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:
|
||||
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:
|
||||
dip = ip.replace(":", ".")
|
||||
else:
|
||||
@@ -2900,7 +3003,7 @@ class Up2k(object):
|
||||
return
|
||||
|
||||
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:
|
||||
# raise Exception("aaa")
|
||||
|
||||
@@ -2968,11 +3071,11 @@ class Up2k(object):
|
||||
|
||||
def _lastmodder(self) -> None:
|
||||
while True:
|
||||
ready = self.lastmod_q
|
||||
ready = self.lastmod_q2
|
||||
self.lastmod_q2 = self.lastmod_q
|
||||
self.lastmod_q = []
|
||||
|
||||
# self.log("lmod: got {}".format(len(ready)))
|
||||
time.sleep(5)
|
||||
time.sleep(1)
|
||||
for path, sz, times, sparse in ready:
|
||||
self.log("lmod: setting times {} on {}".format(times, path))
|
||||
try:
|
||||
@@ -3064,7 +3167,8 @@ class Up2k(object):
|
||||
if etag == self.snap_prev.get(ptop):
|
||||
return
|
||||
|
||||
bos.makedirs(histpath)
|
||||
if bos.makedirs(histpath):
|
||||
hidedir(histpath)
|
||||
|
||||
path2 = "{}.{}".format(path, os.getpid())
|
||||
body = {"droppable": self.droppable[ptop], "registry": reg}
|
||||
@@ -3177,6 +3281,7 @@ class Up2k(object):
|
||||
if self.mth:
|
||||
self.mth.stop = True
|
||||
|
||||
# in case we're killed early
|
||||
for x in list(self.spools):
|
||||
self._unspool(x)
|
||||
|
||||
@@ -3184,6 +3289,16 @@ class Up2k(object):
|
||||
self.log("writing 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:
|
||||
chunksize = 1024 * 1024
|
||||
|
||||
@@ -148,6 +148,7 @@ HTTPCODE = {
|
||||
204: "No Content",
|
||||
206: "Partial Content",
|
||||
207: "Multi-Status",
|
||||
301: "Moved Permanently",
|
||||
302: "Found",
|
||||
304: "Not Modified",
|
||||
400: "Bad Request",
|
||||
@@ -2536,7 +2537,7 @@ def termsize() -> tuple[int, int]:
|
||||
def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]:
|
||||
try:
|
||||
cr = sunpack(b"hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, b"AAAA"))
|
||||
return int(cr[1]), int(cr[0])
|
||||
return cr[::-1]
|
||||
except:
|
||||
return None
|
||||
|
||||
@@ -2549,15 +2550,23 @@ def termsize() -> tuple[int, int]:
|
||||
except:
|
||||
pass
|
||||
|
||||
if cr:
|
||||
return cr
|
||||
|
||||
try:
|
||||
return int(env["COLUMNS"]), int(env["LINES"])
|
||||
return cr or (int(env["COLUMNS"]), int(env["LINES"]))
|
||||
except:
|
||||
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):
|
||||
def __init__(self, code: int, msg: Optional[str] = None) -> None:
|
||||
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
||||
|
||||
@@ -1075,18 +1075,18 @@ html.y #widget.open {
|
||||
top: -.12em;
|
||||
}
|
||||
#wtico {
|
||||
cursor: url(/.cpr/dd/4.png), pointer;
|
||||
cursor: url(dd/4.png), pointer;
|
||||
animation: cursor 500ms;
|
||||
}
|
||||
#wtico:hover {
|
||||
animation: cursor 500ms infinite;
|
||||
}
|
||||
@keyframes cursor {
|
||||
0% {cursor: url(/.cpr/dd/2.png), pointer}
|
||||
30% {cursor: url(/.cpr/dd/3.png), pointer}
|
||||
50% {cursor: url(/.cpr/dd/4.png), pointer}
|
||||
75% {cursor: url(/.cpr/dd/5.png), pointer}
|
||||
85% {cursor: url(/.cpr/dd/4.png), pointer}
|
||||
0% {cursor: url(dd/2.png), pointer}
|
||||
30% {cursor: url(dd/3.png), pointer}
|
||||
50% {cursor: url(dd/4.png), pointer}
|
||||
75% {cursor: url(dd/5.png), pointer}
|
||||
85% {cursor: url(dd/4.png), pointer}
|
||||
}
|
||||
@keyframes spin {
|
||||
100% {transform: rotate(360deg)}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
|
||||
<meta name="theme-color" content="#333">
|
||||
{{ html_head }}
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
|
||||
{%- if css %}
|
||||
<link rel="stylesheet" media="screen" href="{{ css }}?_={{ ts }}">
|
||||
{%- endif %}
|
||||
@@ -71,7 +71,7 @@
|
||||
<h1 id="path">
|
||||
<a href="#" id="entree">🌲</a>
|
||||
{%- for n in vpnodes %}
|
||||
<a href="/{{ n[0] }}">{{ n[1] }}</a>
|
||||
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
|
||||
{%- endfor %}
|
||||
</h1>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
<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>
|
||||
|
||||
@@ -134,7 +134,8 @@
|
||||
<div id="widget"></div>
|
||||
|
||||
<script>
|
||||
var acct = "{{ acct }}",
|
||||
var SR = {{ r|tojson }},
|
||||
acct = "{{ acct }}",
|
||||
perms = {{ perms }},
|
||||
themes = {{ themes }},
|
||||
dtheme = "{{ dtheme }}",
|
||||
@@ -160,10 +161,10 @@
|
||||
|
||||
document.documentElement.className = localStorage.theme || dtheme;
|
||||
</script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/baguettebox.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/browser.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/up2k.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/browser.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/up2k.js?_={{ ts }}"></script>
|
||||
{%- if js %}
|
||||
<script src="{{ js }}?_={{ ts }}"></script>
|
||||
{%- endif %}
|
||||
|
||||
@@ -94,6 +94,9 @@ var Ls = {
|
||||
"ht_and": " and ",
|
||||
|
||||
"goh": "control-panel",
|
||||
"gop": 'previous sibling">prev',
|
||||
"gou": 'parent folder">up',
|
||||
"gon": 'next folder">next',
|
||||
"logout": "Logout ",
|
||||
"access": " access",
|
||||
"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_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",
|
||||
"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',
|
||||
@@ -534,6 +539,9 @@ var Ls = {
|
||||
"ht_and": " og ",
|
||||
|
||||
"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 ",
|
||||
"access": " tilgang",
|
||||
"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_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",
|
||||
"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',
|
||||
@@ -2078,8 +2088,13 @@ function prev_song(e) {
|
||||
return song_skip(-1);
|
||||
}
|
||||
function dl_song() {
|
||||
if (!mp || !mp.au)
|
||||
return;
|
||||
if (!mp || !mp.au) {
|
||||
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];
|
||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987';
|
||||
@@ -2865,7 +2880,7 @@ function eval_hash() {
|
||||
|
||||
// folder nav
|
||||
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('gon').onclick = function () { tree_neigh(1); }
|
||||
ebi('gou').onclick = function () { tree_up(true); }
|
||||
@@ -3641,7 +3656,7 @@ var showfile = (function () {
|
||||
qsr('#prism_css');
|
||||
var el = mknod('link', 'prism_css');
|
||||
el.rel = 'stylesheet';
|
||||
el.href = '/.cpr/deps/prism' + (light ? '' : 'd') + '.css';
|
||||
el.href = SR + '/.cpr/deps/prism' + (light ? '' : 'd') + '.css';
|
||||
document.head.appendChild(el);
|
||||
};
|
||||
|
||||
@@ -3769,7 +3784,7 @@ var showfile = (function () {
|
||||
if (!defer)
|
||||
fun(el.firstChild);
|
||||
else
|
||||
import_js('/.cpr/deps/prism.js', function () { fun(); });
|
||||
import_js(SR + '/.cpr/deps/prism.js', function () { fun(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4175,10 +4190,10 @@ var thegrid = (function () {
|
||||
if (r.thumbs) {
|
||||
ihref += '?th=' + (have_webp ? 'w' : 'j');
|
||||
if (href == "#")
|
||||
ihref = '/.cpr/ico/⏏️';
|
||||
ihref = SR + '/.cpr/ico/⏏️';
|
||||
}
|
||||
else if (isdir) {
|
||||
ihref = '/.cpr/ico/folder';
|
||||
ihref = SR + '/.cpr/ico/folder';
|
||||
}
|
||||
else {
|
||||
var ar = href.split('.');
|
||||
@@ -4203,7 +4218,7 @@ var thegrid = (function () {
|
||||
else
|
||||
ext = "unk";
|
||||
}
|
||||
ihref = '/.cpr/ico/' + ext;
|
||||
ihref = SR + '/.cpr/ico/' + ext;
|
||||
}
|
||||
ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i';
|
||||
|
||||
@@ -4778,7 +4793,7 @@ document.onkeydown = function (e) {
|
||||
clearTimeout(search_timeout);
|
||||
|
||||
var xhr = new XHR();
|
||||
xhr.open('POST', '/?srch', true);
|
||||
xhr.open('POST', SR + '/?srch', true);
|
||||
xhr.setRequestHeader('Content-Type', 'text/plain');
|
||||
xhr.onload = xhr.onerror = xhr_search_results;
|
||||
xhr.ts = Date.now();
|
||||
@@ -5317,7 +5332,12 @@ var treectl = (function () {
|
||||
treegrow.call(this.previousSibling, e);
|
||||
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;
|
||||
thegrid.setvis(true);
|
||||
}
|
||||
@@ -5541,7 +5561,7 @@ var treectl = (function () {
|
||||
qsr('#bbsw');
|
||||
if (ls0 === null) {
|
||||
var xhr = new XHR();
|
||||
xhr.open('GET', '/?am_js', true);
|
||||
xhr.open('GET', SR + '/?am_js', true);
|
||||
xhr.send();
|
||||
|
||||
r.ls_cb = showfile.addlinks;
|
||||
@@ -5739,7 +5759,8 @@ function apply_perms(newperms) {
|
||||
|
||||
ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
|
||||
'</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]');
|
||||
for (var a = 0; a < o.length; a++) {
|
||||
@@ -6546,7 +6567,7 @@ function show_md(md, name, div, url, depth) {
|
||||
if (depth)
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -6678,7 +6699,7 @@ var unpost = (function () {
|
||||
else
|
||||
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 b = 0; b < mods.length; b++)
|
||||
if (a % mods[b] == 0 && res.length > a + mods[b] / 10)
|
||||
@@ -6699,7 +6720,7 @@ var unpost = (function () {
|
||||
r.me = me;
|
||||
}
|
||||
|
||||
var q = '/?ups';
|
||||
var q = SR + '/?ups';
|
||||
if (filt.value)
|
||||
q += '&filter=' + uricom_enc(filt.value, true);
|
||||
|
||||
@@ -6765,7 +6786,7 @@ var unpost = (function () {
|
||||
var xhr = new XHR();
|
||||
xhr.n = n;
|
||||
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.send(JSON.stringify(req));
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<div>{{ logues[1] }}</div><br />
|
||||
{%- 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>
|
||||
</html>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||
<meta name="theme-color" content="#333">
|
||||
{{ html_head }}
|
||||
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="/.cpr/md.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/md.css?_={{ ts }}">
|
||||
{%- if edit %}
|
||||
<link rel="stylesheet" href="/.cpr/md2.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/md2.css?_={{ ts }}">
|
||||
{%- endif %}
|
||||
</head>
|
||||
<body>
|
||||
@@ -128,7 +128,8 @@ write markdown (most html is 🙆 too)
|
||||
|
||||
<script>
|
||||
|
||||
var last_modified = {{ lastmod }},
|
||||
var SR = {{ r|tojson }},
|
||||
last_modified = {{ lastmod }},
|
||||
have_emp = {{ have_emp|tojson }},
|
||||
dfavico = "{{ favico }}";
|
||||
|
||||
@@ -153,10 +154,10 @@ l.light = drk? 0:1;
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/md.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/md.js?_={{ ts }}"></script>
|
||||
{%- if edit %}
|
||||
<script src="/.cpr/md2.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
|
||||
{%- endif %}
|
||||
</body></html>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||
<meta name="theme-color" content="#333">
|
||||
{{ html_head }}
|
||||
<link rel="stylesheet" href="/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="/.cpr/mde.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="/.cpr/deps/easymde.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/mde.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/deps/easymde.css?_={{ ts }}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mw">
|
||||
@@ -26,7 +26,8 @@
|
||||
<a href="#" id="repl">π</a>
|
||||
<script>
|
||||
|
||||
var last_modified = {{ lastmod }},
|
||||
var SR = {{ r|tojson }},
|
||||
last_modified = {{ lastmod }},
|
||||
have_emp = {{ have_emp|tojson }},
|
||||
dfavico = "{{ favico }}";
|
||||
|
||||
@@ -48,8 +49,8 @@ l.light = drk? 0:1;
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/mde.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/mde.js?_={{ ts }}"></script>
|
||||
</body></html>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<meta name="theme-color" content="#333">
|
||||
{{ html_head }}
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/msg.css?_={{ ts }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -16,7 +16,8 @@ html {
|
||||
h1 {
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin: 2em 0 .4em 0;
|
||||
padding: 0 0 .2em 0;
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
li {
|
||||
@@ -26,6 +27,7 @@ a {
|
||||
color: #047;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid #8ab;
|
||||
border-radius: .2em;
|
||||
padding: .2em .6em;
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<meta name="theme-color" content="#333">
|
||||
{{ html_head }}
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<a id="a" href="/?h" class="af">refresh</a>
|
||||
<a id="v" href="/?hc" class="af">connect</a>
|
||||
<a id="a" href="{{ r }}/?h" class="af">refresh</a>
|
||||
<a id="v" href="{{ r }}/?hc" class="af">connect</a>
|
||||
|
||||
{%- if this.uname == '*' %}
|
||||
<p id="b">howdy stranger <small>(you're not logged in)</small></p>
|
||||
{%- 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>
|
||||
{%- endif %}
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
</table>
|
||||
</td></tr></table>
|
||||
<div class="btns">
|
||||
<a id="d" href="/?stack">dump stack</a>
|
||||
<a id="e" href="/?reload=cfg">reload cfg</a>
|
||||
<a id="d" href="{{ r }}/?stack">dump stack</a>
|
||||
<a id="e" href="{{ r }}/?reload=cfg">reload cfg</a>
|
||||
</div>
|
||||
{%- endif %}
|
||||
|
||||
@@ -79,18 +79,18 @@
|
||||
<h1 id="cc">client config:</h1>
|
||||
<ul>
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
<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>
|
||||
|
||||
<h1 id="l">login for more:</h1>
|
||||
<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="password" name="cppwd" />
|
||||
<input type="submit" value="Login" />
|
||||
@@ -100,13 +100,14 @@
|
||||
<a href="#" id="repl">π</a>
|
||||
<script>
|
||||
|
||||
var lang="{{ lang }}",
|
||||
var SR = {{ r|tojson }},
|
||||
lang="{{ lang }}",
|
||||
dfavico="{{ favico }}";
|
||||
|
||||
document.documentElement.className=localStorage.theme||"{{ this.args.theme }}";
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/splash.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -17,9 +17,9 @@ var Ls = {
|
||||
"l1": "logg inn:",
|
||||
"m1": "velkommen tilbake,",
|
||||
"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 ~┻━┻",
|
||||
"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",
|
||||
".s1": "kartlegg",
|
||||
"t1": "handling",
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<meta name="theme-color" content="#333">
|
||||
{{ html_head }}
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrap" class="w">
|
||||
<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>
|
||||
<div class="ossel">
|
||||
<a id="swin" href="#">Windows</a>
|
||||
@@ -28,7 +28,7 @@
|
||||
make this server appear on your computer as a regular HDD!<br />
|
||||
pick your favorite below (sorted by performance, best first) and lets 🎉<br />
|
||||
<br />
|
||||
placeholders:
|
||||
<span class="os win lin mac">placeholders:</span>
|
||||
<span class="os win">
|
||||
{% if accs %}<code><b>{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
|
||||
</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>
|
||||
{% 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>
|
||||
net use <b>w:</b> http{{ s }}://{{ ep }}/{{ vp }}{% if accs %} k /user:<b>{{ pw }}</b>{% endif %}
|
||||
</pre>
|
||||
@@ -144,7 +144,7 @@
|
||||
|
||||
<h1>partyfuse</h1>
|
||||
<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 lin">doesn't need root</span>
|
||||
</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>
|
||||
{% endif %}
|
||||
<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>
|
||||
|
||||
|
||||
@@ -188,13 +188,14 @@
|
||||
<a href="#" id="repl">π</a>
|
||||
<script>
|
||||
|
||||
var lang="{{ lang }}",
|
||||
var SR = {{ r|tojson }},
|
||||
lang="{{ lang }}",
|
||||
dfavico="{{ favico }}";
|
||||
|
||||
document.documentElement.className=localStorage.theme||"{{ args.theme }}";
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="/.cpr/svcs.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
|
||||
<script src="{{ r }}/.cpr/svcs.js?_={{ ts }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -39,4 +39,4 @@ function setos(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-family: 'scp';
|
||||
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 {
|
||||
touch-action: manipulation;
|
||||
|
||||
@@ -672,7 +672,7 @@ function Donut(uc, st) {
|
||||
favico.upd();
|
||||
wintitle();
|
||||
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 = ['████████████████', '________________', '████████████████'];
|
||||
tstrober = setInterval(strobe, 300);
|
||||
|
||||
@@ -779,7 +779,7 @@ function up2k_init(subtle) {
|
||||
|
||||
setTimeout(function () {
|
||||
if (window.WebAssembly && !hws.length)
|
||||
fetch('/.cpr/w.hash.js' + CB);
|
||||
fetch(SR + '/.cpr/w.hash.js' + CB);
|
||||
}, 1000);
|
||||
|
||||
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;
|
||||
|
||||
showmodal('<h1>loading ' + fn + '</h1>');
|
||||
import_js('/.cpr/deps/' + fn, unmodal);
|
||||
import_js(SR + '/.cpr/deps/' + fn, unmodal);
|
||||
|
||||
if (HTTPS) {
|
||||
// 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, 'hashw', 'hashw', !!window.WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw);
|
||||
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
|
||||
bcfg_bind(uc, 'upsfx', 'upsfx', false);
|
||||
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
|
||||
|
||||
var st = {
|
||||
"files": [],
|
||||
@@ -904,6 +904,16 @@ function up2k_init(subtle) {
|
||||
"u": "",
|
||||
"t": ""
|
||||
},
|
||||
"etaw": {
|
||||
"h": [['', 0, 0, 0]],
|
||||
"u": [['', 0, 0, 0]],
|
||||
"t": [['', 0, 0, 0]]
|
||||
},
|
||||
"etac": {
|
||||
"h": 0,
|
||||
"u": 0,
|
||||
"t": 0
|
||||
},
|
||||
"car": 0,
|
||||
"slow_io": null,
|
||||
"oserr": false,
|
||||
@@ -1312,7 +1322,7 @@ function up2k_init(subtle) {
|
||||
|
||||
if (window.WebAssembly && !hws.length) {
|
||||
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");
|
||||
}
|
||||
@@ -1480,10 +1490,20 @@ function up2k_init(subtle) {
|
||||
}
|
||||
}
|
||||
for (var a = 0; a < t.length; a++) {
|
||||
var rem = st.bytes.total - t[a][2],
|
||||
bps = t[a][1] / t[a][3],
|
||||
hid = t[a][0],
|
||||
var hid = t[a][0],
|
||||
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);
|
||||
|
||||
if (t[a][1] < 1024 || t[a][3] < 0.1) {
|
||||
@@ -1854,6 +1874,7 @@ function up2k_init(subtle) {
|
||||
cdr = Math.min(chunksize + car, t.size);
|
||||
|
||||
st.bytes.hashed += cdr - car;
|
||||
st.etac.h++;
|
||||
|
||||
function orz(e) {
|
||||
bpend--;
|
||||
@@ -2403,6 +2424,8 @@ function up2k_init(subtle) {
|
||||
st.bytes.finished += cdr - car;
|
||||
st.bytes.uploaded += cdr - car;
|
||||
t.bytes_uploaded += cdr - car;
|
||||
st.etac.u++;
|
||||
st.etac.t++;
|
||||
}
|
||||
else if (txt.indexOf('already got that') + 1 ||
|
||||
txt.indexOf('already being written') + 1) {
|
||||
@@ -2749,6 +2772,21 @@ function up2k_init(subtle) {
|
||||
|
||||
if (en && Notification.permission == 'default')
|
||||
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'))
|
||||
|
||||
@@ -9,6 +9,8 @@ if (!window.console || !console.log)
|
||||
var wah = '',
|
||||
L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
|
||||
CB = '?_=' + Date.now(),
|
||||
R = SR.slice(1),
|
||||
RS = R ? "/" + R : "",
|
||||
HALFMAX = 8192 * 8192 * 8192 * 8192,
|
||||
HTTPS = (window.location + '').indexOf('https:') === 0,
|
||||
TOUCH = 'ontouchstart' in window,
|
||||
@@ -193,8 +195,12 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||
var lsk = Object.keys(ls);
|
||||
lsk.sort();
|
||||
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('</p>');
|
||||
}
|
||||
catch (e) { }
|
||||
@@ -1678,7 +1684,7 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
|
||||
|
||||
qsr('#cf_frame');
|
||||
var fr = mknod('iframe', 'cf_frame');
|
||||
fr.src = '/?cf_challenge';
|
||||
fr.src = SR + '/?cf_challenge';
|
||||
document.body.appendChild(fr);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ catch (ex) {
|
||||
}
|
||||
function load_fb() {
|
||||
subtle = null;
|
||||
importScripts('/.cpr/deps/sha512.hw.js');
|
||||
importScripts('deps/sha512.hw.js');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,71 @@
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 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
|
||||
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
* top
|
||||
* [future plans](#future-plans) - some improvement ideas
|
||||
* [design](#design)
|
||||
* [up2k](#up2k) - quick outline of the up2k protocol
|
||||
* [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)
|
||||
* [mdns](#mdns)
|
||||
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
||||
@@ -35,7 +41,7 @@ some improvement ideas
|
||||
|
||||
## 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
|
||||
* 1 MiB each, unless that becomes more than 256 chunks
|
||||
* tries 1.5M, 2M, 3, 4, 6, ... until <= 256 chunks or size >= 32M
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM alpine:3.16
|
||||
WORKDIR /z
|
||||
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
||||
ver_hashwasm=4.9.0 \
|
||||
ver_marked=4.2.3 \
|
||||
ver_marked=4.2.4 \
|
||||
ver_mde=2.18.0 \
|
||||
ver_codemirror=5.65.10 \
|
||||
ver_fontawesome=5.13.0 \
|
||||
|
||||
@@ -9,7 +9,7 @@ font-family: 'fa';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("/.cpr/deps/mini-fa.woff") format("woff");
|
||||
src: url("mini-fa.woff") format("woff");
|
||||
}
|
||||
|
||||
.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
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@@ -429,7 +429,7 @@ def run_i(ld):
|
||||
|
||||
def run_s(ld):
|
||||
# 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:])]
|
||||
# fmt: on
|
||||
msg("\n", c, "\n")
|
||||
|
||||
1
setup.py
1
setup.py
@@ -107,6 +107,7 @@ args = {
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: Jython",
|
||||
"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"
|
||||
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()})
|
||||
|
||||
super(Cfg, self).__init__(
|
||||
@@ -118,6 +118,7 @@ class Cfg(Namespace):
|
||||
v=v or [],
|
||||
c=c,
|
||||
E=E,
|
||||
dbd="wal",
|
||||
s_wr_sz=512 * 1024,
|
||||
unpost=600,
|
||||
u2sort="s",
|
||||
|
||||
Reference in New Issue
Block a user